UNPKG

3.78 kBJavaScriptView Raw
1const lodash = require('lodash');
2
3// ============================================================================
4class ParserError extends Error {
5 constructor() {
6 super();
7 this.msg = '';
8 this.path = [];
9 this.origin = undefined;
10 }
11
12 set(error, key, origin) {
13 if (error instanceof ParserError) { // include ParserError
14 this.msg = error.msg;
15 this.path = error.path;
16 } else if (error instanceof Error) {
17 this.msg = error.message;
18 } else {
19 this.msg = `${error}`;
20 }
21
22 if (key !== undefined) {
23 this.path.unshift(key);
24 }
25
26 this.origin = origin;
27 return this;
28 }
29
30 get message() {
31 return JSON.stringify({
32 msg: this.msg,
33 path: this.path.join(''),
34 origin: this.origin,
35 });
36 }
37}
38
39// ----------------------------------------------------------------------------
40function valueParser(schema) {
41 return asParser(value => {
42 if (!(value === schema)) {
43 throw new Error(`${value} not equal ${schema}`);
44 }
45 return value;
46 });
47}
48
49function functionParser(func) {
50 return asParser(value => func(value));
51}
52
53function arrayParser(schema) {
54 const func = schema.length ? parser(schema[0]) : v => v;
55
56 return asParser(array => {
57 if (!Array.isArray(array)) {
58 throw new Error(`expected array, got ${typeof array}`);
59 }
60
61 const error = new ParserError(); // create Error here for shallow stack
62 return array.map((v, i) => {
63 try {
64 return func(v);
65 } catch (e) {
66 throw error.set(e, `[${i}]`, array);
67 }
68 });
69 });
70}
71
72function objectParser(schema, pick = false) {
73 const keyToFunc = lodash.mapValues(schema, parser);
74
75 return asParser(object => {
76 if (!lodash.isObject(object)) {
77 throw new Error(`expected plain object, got ${typeof object}`);
78 }
79
80 const error = new ParserError(); // create Error here for shallow stack
81 const picked = lodash.mapValues(keyToFunc, (func, key) => {
82 try {
83 return func(object[key]);
84 } catch (e) {
85 throw error.set(e, `.${key}`, object);
86 }
87 });
88
89 return pick ? picked : lodash.defaults(picked, object);
90 });
91}
92
93function $before(func) {
94 return asParser(value => {
95 return this(func(value));
96 });
97}
98
99function $after(func) {
100 return asParser(value => {
101 return func(this(value));
102 });
103}
104
105function $default(defaultValue) {
106 return asParser(value => {
107 if (value === undefined) {
108 value = defaultValue;
109 }
110 return this(value);
111 });
112}
113
114function $validate(func, name = func.name) {
115 return asParser(value => {
116 value = this(value);
117 if (!func(value)) {
118 throw new Error(`${value} not match ${name}`);
119 }
120 return value;
121 });
122}
123
124function $or(schema) {
125 const other = parser(schema);
126
127 return asParser(value => {
128 const funcArray = [this, other];
129
130 const errorArray = [];
131 for (const func of funcArray) {
132 try {
133 return func(value);
134 } catch (e) {
135 errorArray.push(e instanceof ParserError ? e.msg : e.message);
136 }
137 }
138
139 throw new Error(errorArray.map(e => `(${e})`).join(' && '));
140 });
141}
142
143function asParser(func) {
144 func.$before = $before;
145 func.$after = $after;
146 func.$default = $default;
147 func.$validate = $validate;
148 func.$or = $or;
149 return func;
150}
151
152function parser(arg, ...args) {
153 if (Array.isArray(arg)) {
154 return arrayParser(arg, ...args);
155 }
156
157 if (lodash.isPlainObject(arg)) {
158 return objectParser(arg, ...args);
159 }
160
161 if (lodash.isFunction(arg)) {
162 return functionParser(arg, ...args);
163 }
164
165 return valueParser(arg, ...args);
166}
167
168module.exports = parser;