@bedrockio/yada 1.10.0 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +26 -8
- package/dist/cjs/Schema.js +2 -0
- package/dist/cjs/array.js +21 -34
- package/dist/cjs/errors.js +8 -1
- package/dist/cjs/object.js +203 -141
- package/package.json +1 -1
- package/src/Schema.js +3 -0
- package/src/array.js +23 -34
- package/src/errors.js +7 -0
- package/src/object.js +220 -134
- package/types/Schema.d.ts.map +1 -1
- package/types/array.d.ts +4 -6
- package/types/array.d.ts.map +1 -1
- package/types/errors.d.ts +4 -0
- package/types/errors.d.ts.map +1 -1
- package/types/object.d.ts +17 -0
- package/types/object.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 1.10.2
|
|
2
|
+
|
|
3
|
+
- Shortcut methods for object options.
|
|
4
|
+
|
|
5
|
+
## 1.10.1
|
|
6
|
+
|
|
7
|
+
- Refactored object field validation.
|
|
8
|
+
- Fixed issue with base object failing on flat keys.
|
|
9
|
+
- Ensure that arrays only accept a single inner schema.
|
|
10
|
+
|
|
1
11
|
## 1.10.0
|
|
2
12
|
|
|
3
13
|
- Added `style` option to `toJsonSchema` to control disallowed formats for
|
package/README.md
CHANGED
|
@@ -255,19 +255,37 @@ be a boolean, either `true` or `false`.
|
|
|
255
255
|
|
|
256
256
|
### Array
|
|
257
257
|
|
|
258
|
-
Array schemas validate that input is an array
|
|
259
|
-
type
|
|
258
|
+
Array schemas validate that input is an array and elements are of the provided
|
|
259
|
+
type:
|
|
260
260
|
|
|
261
261
|
```js
|
|
262
|
-
// Allows arrays containing
|
|
263
|
-
const schema = yd.array(yd.string()
|
|
262
|
+
// Allows arrays containing strings.
|
|
263
|
+
const schema = yd.array(yd.string());
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
-
|
|
266
|
+
These types can be object of a specific shape:
|
|
267
267
|
|
|
268
268
|
```js
|
|
269
|
-
// Allows arrays containing
|
|
270
|
-
const schema = yd.array(
|
|
269
|
+
// Allows arrays containing objects that follow a schema.
|
|
270
|
+
const schema = yd.array(
|
|
271
|
+
yd.object({
|
|
272
|
+
name: yd.string().required(),
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
If no inner schema is provided any element type is allowed:
|
|
278
|
+
|
|
279
|
+
```js
|
|
280
|
+
// Allows any type of element inside the array:
|
|
281
|
+
const schema = yd.array();
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
To allow mixed types inside the same array use allow:
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
// Allows both strings and numbers in the array:
|
|
288
|
+
const schema = yd.array(yd.allow(yd.string(), yd.number()));
|
|
271
289
|
```
|
|
272
290
|
|
|
273
291
|
#### Methods:
|
|
@@ -285,7 +303,7 @@ well as types for a given position. Compare the following:
|
|
|
285
303
|
|
|
286
304
|
```js
|
|
287
305
|
// Accepts any length of either strings or numbers.
|
|
288
|
-
const schema1 = yd.array(yd.string(), yd.number());
|
|
306
|
+
const schema1 = yd.array(yd.allow(yd.string(), yd.number()));
|
|
289
307
|
// Accepts exactly 2 elements. The first MUST be a
|
|
290
308
|
// string and the second MUST be a number.
|
|
291
309
|
const schema2 = yd.tuple(yd.string(), yd.number());
|
package/dist/cjs/Schema.js
CHANGED
|
@@ -181,6 +181,8 @@ class Schema {
|
|
|
181
181
|
details.push(new _errors.FormatError(message, this.meta.format));
|
|
182
182
|
} else if (error instanceof _errors.LocalizedError) {
|
|
183
183
|
details.push(new _errors.AssertionError(message, type));
|
|
184
|
+
} else if (error instanceof _errors.AggregateError) {
|
|
185
|
+
details = [...details, ...error.errors];
|
|
184
186
|
} else {
|
|
185
187
|
details.push(error);
|
|
186
188
|
}
|
package/dist/cjs/array.js
CHANGED
|
@@ -5,23 +5,18 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = _default;
|
|
7
7
|
var _lodash = require("lodash");
|
|
8
|
-
var _Schema = _interopRequireDefault(require("./Schema"));
|
|
9
8
|
var _TypeSchema = _interopRequireDefault(require("./TypeSchema"));
|
|
10
9
|
var _errors = require("./errors");
|
|
11
10
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
11
|
class ArraySchema extends _TypeSchema.default {
|
|
13
|
-
constructor(
|
|
14
|
-
super(Array,
|
|
15
|
-
...meta,
|
|
16
|
-
schemas
|
|
17
|
-
});
|
|
12
|
+
constructor(meta) {
|
|
13
|
+
super(Array, meta);
|
|
18
14
|
this.setup();
|
|
19
15
|
}
|
|
20
16
|
setup() {
|
|
21
17
|
const {
|
|
22
|
-
|
|
18
|
+
schema
|
|
23
19
|
} = this.meta;
|
|
24
|
-
const schema = schemas.length > 1 ? new _Schema.default().allow(schemas) : schemas[0];
|
|
25
20
|
this.assert('type', (val, options) => {
|
|
26
21
|
if (typeof val === 'string' && options.cast) {
|
|
27
22
|
val = val.split(',');
|
|
@@ -115,13 +110,15 @@ class ArraySchema extends _TypeSchema.default {
|
|
|
115
110
|
*/
|
|
116
111
|
transform(fn, root = true) {
|
|
117
112
|
const {
|
|
118
|
-
|
|
113
|
+
schema,
|
|
119
114
|
...rest
|
|
120
115
|
} = this.meta;
|
|
121
|
-
const
|
|
122
|
-
|
|
116
|
+
const transformed = new ArraySchema({
|
|
117
|
+
...rest,
|
|
118
|
+
...(schema && {
|
|
119
|
+
schema: schema.transform(fn, false)
|
|
120
|
+
})
|
|
123
121
|
});
|
|
124
|
-
const transformed = new ArraySchema(newSchemas, rest);
|
|
125
122
|
if (root) {
|
|
126
123
|
// @ts-ignore
|
|
127
124
|
return transformed;
|
|
@@ -136,24 +133,14 @@ class ArraySchema extends _TypeSchema.default {
|
|
|
136
133
|
return 'array';
|
|
137
134
|
}
|
|
138
135
|
toJsonSchema(options) {
|
|
139
|
-
let other;
|
|
140
136
|
const {
|
|
141
|
-
|
|
137
|
+
schema
|
|
142
138
|
} = this.meta;
|
|
143
|
-
if (schemas.length > 1) {
|
|
144
|
-
other = {
|
|
145
|
-
anyOf: schemas.map(schema => {
|
|
146
|
-
return schema.toJsonSchema(options);
|
|
147
|
-
})
|
|
148
|
-
};
|
|
149
|
-
} else if (schemas.length === 1) {
|
|
150
|
-
other = {
|
|
151
|
-
items: schemas[0].toJsonSchema(options)
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
139
|
return {
|
|
155
140
|
...super.toJsonSchema(options),
|
|
156
|
-
...
|
|
141
|
+
...(schema && {
|
|
142
|
+
items: schema?.toJsonSchema(options)
|
|
143
|
+
})
|
|
157
144
|
};
|
|
158
145
|
}
|
|
159
146
|
}
|
|
@@ -161,14 +148,14 @@ class ArraySchema extends _TypeSchema.default {
|
|
|
161
148
|
/**
|
|
162
149
|
* Creates an [array schema](https://github.com/bedrockio/yada#array).
|
|
163
150
|
*
|
|
164
|
-
* @param {
|
|
165
|
-
* the
|
|
166
|
-
* no arguments are passed elements may be of any type. Also
|
|
167
|
-
* accepts a single array argument.
|
|
151
|
+
* @param {*} [schema] - The schema to validate elements in
|
|
152
|
+
* the array. If not passed then elements may be of any type.
|
|
168
153
|
*/
|
|
169
|
-
function _default(
|
|
170
|
-
if (
|
|
171
|
-
|
|
154
|
+
function _default(schema) {
|
|
155
|
+
if (arguments.length > 1) {
|
|
156
|
+
throw new Error('Arrays may only have a single schema. Use "allow" instead.');
|
|
172
157
|
}
|
|
173
|
-
return new ArraySchema(
|
|
158
|
+
return new ArraySchema({
|
|
159
|
+
schema
|
|
160
|
+
});
|
|
174
161
|
}
|
package/dist/cjs/errors.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.ValidationError = exports.TypeError = exports.LocalizedError = exports.FormatError = exports.FieldError = exports.ElementError = exports.AssertionError = exports.ArrayError = exports.AllowedError = void 0;
|
|
6
|
+
exports.ValidationError = exports.TypeError = exports.LocalizedError = exports.FormatError = exports.FieldError = exports.ElementError = exports.AssertionError = exports.ArrayError = exports.AllowedError = exports.AggregateError = void 0;
|
|
7
7
|
exports.isSchemaError = isSchemaError;
|
|
8
8
|
var _localization = require("./localization");
|
|
9
9
|
var _messages = require("./messages");
|
|
@@ -54,6 +54,13 @@ class ValidationError extends Error {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
exports.ValidationError = ValidationError;
|
|
57
|
+
class AggregateError extends Error {
|
|
58
|
+
constructor(message, errors) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.errors = errors;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.AggregateError = AggregateError;
|
|
57
64
|
class AssertionError extends ValidationError {
|
|
58
65
|
constructor(message, type = 'assertion') {
|
|
59
66
|
super(message);
|
package/dist/cjs/object.js
CHANGED
|
@@ -21,92 +21,105 @@ const INTEGER_REG = /^\d+$/;
|
|
|
21
21
|
class ObjectSchema extends _TypeSchema.default {
|
|
22
22
|
constructor(meta) {
|
|
23
23
|
super(Object, meta);
|
|
24
|
+
this.validateInput();
|
|
24
25
|
this.setup();
|
|
25
26
|
}
|
|
27
|
+
validateInput() {
|
|
28
|
+
const {
|
|
29
|
+
fields
|
|
30
|
+
} = this.meta;
|
|
31
|
+
for (let [key, value] of Object.entries(fields || {})) {
|
|
32
|
+
if (!(0, _Schema.isSchema)(value)) {
|
|
33
|
+
throw new Error(`Key "${key}" must be a schema.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
26
37
|
setup() {
|
|
27
38
|
this.assert('type', val => {
|
|
28
39
|
if (val === null || typeof val !== 'object') {
|
|
29
40
|
throw new _errors.LocalizedError('Must be an object.');
|
|
30
41
|
}
|
|
31
42
|
});
|
|
32
|
-
this.
|
|
43
|
+
this.assert('fields', async (obj, options) => {
|
|
33
44
|
const {
|
|
45
|
+
path = [],
|
|
46
|
+
original,
|
|
47
|
+
stripEmpty,
|
|
34
48
|
stripUnknown,
|
|
35
|
-
|
|
49
|
+
allowFlatKeys,
|
|
50
|
+
expandFlatKeys
|
|
36
51
|
} = options;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
const {
|
|
53
|
+
fields
|
|
54
|
+
} = this.meta;
|
|
55
|
+
if (!fields) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let passed = obj;
|
|
59
|
+
if (expandFlatKeys) {
|
|
60
|
+
passed = expandFlatSyntax(passed);
|
|
61
|
+
}
|
|
62
|
+
const keys = new Set([...Object.keys(fields || {}), ...Object.keys(passed)]);
|
|
63
|
+
let errors = [];
|
|
64
|
+
const result = {};
|
|
65
|
+
for (let key of keys) {
|
|
66
|
+
const value = passed[key];
|
|
67
|
+
if (value === '' && stripEmpty) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const schema = getSchema(fields, key, options);
|
|
71
|
+
if (!schema) {
|
|
72
|
+
if (stripUnknown) {
|
|
44
73
|
continue;
|
|
45
|
-
} else if (isUnknown) {
|
|
46
|
-
throw new _errors.LocalizedError('Unknown field "{key}".', {
|
|
47
|
-
key,
|
|
48
|
-
type: 'field'
|
|
49
|
-
});
|
|
50
|
-
} else {
|
|
51
|
-
result[key] = obj[key];
|
|
52
74
|
}
|
|
75
|
+
throw new _errors.LocalizedError('Unknown field "{key}".', {
|
|
76
|
+
type: 'field',
|
|
77
|
+
key
|
|
78
|
+
});
|
|
53
79
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
throw new Error(`Key "${key}" must be a schema.`);
|
|
60
|
-
}
|
|
61
|
-
this.assert('field', async (obj, options) => {
|
|
62
|
-
if (obj) {
|
|
63
|
-
const {
|
|
64
|
-
path = [],
|
|
65
|
-
original
|
|
66
|
-
} = options;
|
|
67
|
-
const {
|
|
68
|
-
strip,
|
|
69
|
-
required
|
|
70
|
-
} = schema.meta;
|
|
71
|
-
const val = obj[key];
|
|
72
|
-
options = {
|
|
80
|
+
if (schema.meta.strip?.(value, options)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const validateOptions = {
|
|
73
85
|
...options,
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
path: [...path, key],
|
|
87
|
+
required: schema.meta.required,
|
|
88
|
+
// The root object may be transformed by defaults
|
|
89
|
+
// or returned values so re-pass it here.
|
|
90
|
+
root: {
|
|
91
|
+
...obj,
|
|
92
|
+
...result
|
|
93
|
+
},
|
|
94
|
+
// The original root represents the root object
|
|
95
|
+
// before it was transformed.
|
|
96
|
+
originalRoot: original
|
|
76
97
|
};
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// The root object may have been transformed here
|
|
88
|
-
// by defaults or values returned by custom functions
|
|
89
|
-
// so re-pass it here.
|
|
90
|
-
root: obj,
|
|
91
|
-
// The original root represents the root object
|
|
92
|
-
// before it was transformed.
|
|
93
|
-
originalRoot: original
|
|
94
|
-
});
|
|
95
|
-
if (result !== undefined) {
|
|
96
|
-
return {
|
|
97
|
-
...obj,
|
|
98
|
-
[key]: result
|
|
99
|
-
};
|
|
98
|
+
if (allowFlatKeys && !value) {
|
|
99
|
+
// When allowing keys like "profile.name", "profile" will
|
|
100
|
+
// not be passed so expand the passed object and make sure
|
|
101
|
+
// the base validates, but do not transform the result.
|
|
102
|
+
const expanded = expandFlatSyntax(passed);
|
|
103
|
+
await schema.validate(expanded[key], validateOptions);
|
|
104
|
+
} else {
|
|
105
|
+
const transformed = await schema.validate(value, validateOptions);
|
|
106
|
+
if (transformed !== undefined) {
|
|
107
|
+
result[key] = transformed;
|
|
100
108
|
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
const {
|
|
103
|
-
message
|
|
104
|
-
} = schema.meta;
|
|
105
|
-
throw new _errors.FieldError(message, key, error.details);
|
|
106
109
|
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const {
|
|
112
|
+
message
|
|
113
|
+
} = schema.meta;
|
|
114
|
+
errors.push(new _errors.FieldError(message, key, error.details));
|
|
107
115
|
}
|
|
108
|
-
}
|
|
109
|
-
|
|
116
|
+
}
|
|
117
|
+
if (errors.length) {
|
|
118
|
+
errors = normalizeErrors(errors, options);
|
|
119
|
+
throw new _errors.AggregateError(this.meta.message, errors);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
});
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
/**
|
|
@@ -126,11 +139,8 @@ class ObjectSchema extends _TypeSchema.default {
|
|
|
126
139
|
path = Array.isArray(path) ? path : path.split('.');
|
|
127
140
|
const [name, ...rest] = path;
|
|
128
141
|
const schema = fields[name];
|
|
129
|
-
if (!schema) {
|
|
130
|
-
throw new Error(`Cannot find field "${name}".`);
|
|
131
|
-
}
|
|
132
142
|
if (rest.length) {
|
|
133
|
-
return schema
|
|
143
|
+
return schema?.get(rest);
|
|
134
144
|
} else {
|
|
135
145
|
return schema;
|
|
136
146
|
}
|
|
@@ -147,14 +157,15 @@ class ObjectSchema extends _TypeSchema.default {
|
|
|
147
157
|
path = Array.isArray(path) ? path.join('.') : path;
|
|
148
158
|
const field = this.get(path);
|
|
149
159
|
const {
|
|
150
|
-
|
|
160
|
+
type,
|
|
161
|
+
schema
|
|
151
162
|
} = field.meta;
|
|
152
|
-
if (
|
|
163
|
+
if (type !== 'array') {
|
|
153
164
|
throw new Error(`Field "${path}" is not an array schema.`);
|
|
154
|
-
} else if (
|
|
155
|
-
throw new Error(`Field "${path}"
|
|
165
|
+
} else if (!schema) {
|
|
166
|
+
throw new Error(`Field "${path}" does not have an inner schema.`);
|
|
156
167
|
}
|
|
157
|
-
return
|
|
168
|
+
return schema;
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
/**
|
|
@@ -202,7 +213,11 @@ class ObjectSchema extends _TypeSchema.default {
|
|
|
202
213
|
}
|
|
203
214
|
const update = {};
|
|
204
215
|
for (let field of fields) {
|
|
205
|
-
|
|
216
|
+
const schema = this.get(field);
|
|
217
|
+
if (!schema) {
|
|
218
|
+
throw new Error(`Cannot find field "${field}".`);
|
|
219
|
+
}
|
|
220
|
+
(0, _lodash.set)(update, field, schema.required());
|
|
206
221
|
}
|
|
207
222
|
return this.append(update);
|
|
208
223
|
}
|
|
@@ -288,6 +303,44 @@ class ObjectSchema extends _TypeSchema.default {
|
|
|
288
303
|
return schema;
|
|
289
304
|
}
|
|
290
305
|
|
|
306
|
+
// Options
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Remove properties that are empty strings.
|
|
310
|
+
*/
|
|
311
|
+
stripEmpty() {
|
|
312
|
+
return this.options({
|
|
313
|
+
stripEmpty: true
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Remove properties not in the schema.
|
|
319
|
+
*/
|
|
320
|
+
stripUnknown() {
|
|
321
|
+
return this.options({
|
|
322
|
+
stripUnknown: true
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Allow flat keys like `profile.name`.
|
|
328
|
+
*/
|
|
329
|
+
allowFlatKeys() {
|
|
330
|
+
return this.options({
|
|
331
|
+
allowFlatKeys: true
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Expand flat keys into nested objects.
|
|
337
|
+
*/
|
|
338
|
+
expandFlatKeys() {
|
|
339
|
+
return this.options({
|
|
340
|
+
expandFlatKeys: true
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
291
344
|
/**
|
|
292
345
|
* `stripEmpty` - Removes properties that are empty strings.
|
|
293
346
|
* `stripUnknown` - Removes properties not in the schema.
|
|
@@ -328,10 +381,7 @@ class ObjectSchema extends _TypeSchema.default {
|
|
|
328
381
|
};
|
|
329
382
|
}
|
|
330
383
|
}
|
|
331
|
-
function expandFlatSyntax(obj
|
|
332
|
-
if (!options.expandFlatKeys) {
|
|
333
|
-
return obj;
|
|
334
|
-
}
|
|
384
|
+
function expandFlatSyntax(obj) {
|
|
335
385
|
const result = {
|
|
336
386
|
...obj
|
|
337
387
|
};
|
|
@@ -343,66 +393,6 @@ function expandFlatSyntax(obj, options) {
|
|
|
343
393
|
}
|
|
344
394
|
return result;
|
|
345
395
|
}
|
|
346
|
-
function isKnownKey(key, schema, options) {
|
|
347
|
-
const {
|
|
348
|
-
fields
|
|
349
|
-
} = schema.meta;
|
|
350
|
-
const {
|
|
351
|
-
allowFlatKeys
|
|
352
|
-
} = options;
|
|
353
|
-
if (!fields) {
|
|
354
|
-
// No fields defined -> all keys are "known".
|
|
355
|
-
return true;
|
|
356
|
-
} else if (key in fields) {
|
|
357
|
-
// Exact match -> key is known.
|
|
358
|
-
return true;
|
|
359
|
-
} else if (allowFlatKeys && key.includes('.')) {
|
|
360
|
-
// Flat syntax "foo.bar".
|
|
361
|
-
const [base, ...rest] = key.split('.');
|
|
362
|
-
let subschema = fields[base];
|
|
363
|
-
if (!subschema) {
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
const {
|
|
367
|
-
type,
|
|
368
|
-
schemas
|
|
369
|
-
} = subschema.meta;
|
|
370
|
-
let subkey;
|
|
371
|
-
if (type === 'array') {
|
|
372
|
-
// If the subschema is an array then take the first of
|
|
373
|
-
// its defined schemas as we can safely assume that an
|
|
374
|
-
// array of objects will be defined as a single element
|
|
375
|
-
// or multiple schemas will only set the base property.
|
|
376
|
-
// Test that the element key is valid and take any
|
|
377
|
-
// further properties as the subkey. Examples:
|
|
378
|
-
// - profiles.0.name (array of objects)
|
|
379
|
-
// - profiles.0 (array of stringsmk)
|
|
380
|
-
const [index, ...other] = rest;
|
|
381
|
-
if (!INTEGER_REG.test(index)) {
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
384
|
-
subschema = schemas[0];
|
|
385
|
-
subkey = other.join('.');
|
|
386
|
-
} else if (type === 'object') {
|
|
387
|
-
// If the subschema is an object then simply take any
|
|
388
|
-
// further properties as the subkey. Example:
|
|
389
|
-
// - profile.name
|
|
390
|
-
subkey = rest.join('.');
|
|
391
|
-
} else {
|
|
392
|
-
// If the subschema is anything else then disallow it
|
|
393
|
-
// further properties as the subkey. Example:
|
|
394
|
-
// - profile.name
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
if (subschema.meta.type === 'object') {
|
|
398
|
-
return isKnownKey(subkey, subschema, options);
|
|
399
|
-
} else {
|
|
400
|
-
return !subkey;
|
|
401
|
-
}
|
|
402
|
-
} else {
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
396
|
function mergeFields(aFields, bFields) {
|
|
407
397
|
if (!aFields || !bFields) {
|
|
408
398
|
return aFields || bFields;
|
|
@@ -422,6 +412,78 @@ function mergeFields(aFields, bFields) {
|
|
|
422
412
|
return result;
|
|
423
413
|
}
|
|
424
414
|
|
|
415
|
+
// Gets the schema for a field allowing for flat
|
|
416
|
+
// keys which may deeply traverse into the object.
|
|
417
|
+
function getSchema(fields, key, options) {
|
|
418
|
+
const {
|
|
419
|
+
allowFlatKeys
|
|
420
|
+
} = options;
|
|
421
|
+
let schema = fields[key];
|
|
422
|
+
if (schema) {
|
|
423
|
+
return schema;
|
|
424
|
+
}
|
|
425
|
+
if (!allowFlatKeys || !key.includes('.')) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
schema = fields;
|
|
429
|
+
for (let part of key.split('.')) {
|
|
430
|
+
const {
|
|
431
|
+
type,
|
|
432
|
+
fields
|
|
433
|
+
} = schema?.meta || {};
|
|
434
|
+
if (type === 'array') {
|
|
435
|
+
// If the schema is an array schema then take the first
|
|
436
|
+
// subschema, however only allow integers in this case,
|
|
437
|
+
// for example: profiles.0.name.
|
|
438
|
+
|
|
439
|
+
if (!INTEGER_REG.test(part)) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
schema = schema.meta.schema;
|
|
443
|
+
} else if (fields) {
|
|
444
|
+
schema = fields[part];
|
|
445
|
+
} else {
|
|
446
|
+
schema = schema?.[part];
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return schema;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// If flat keys are allowed then filter out errors
|
|
453
|
+
// that are the nested duplicates so that they match
|
|
454
|
+
// the actual fields that were passed.
|
|
455
|
+
function normalizeErrors(errors, options) {
|
|
456
|
+
const {
|
|
457
|
+
allowFlatKeys
|
|
458
|
+
} = options;
|
|
459
|
+
if (allowFlatKeys) {
|
|
460
|
+
errors = errors.filter(error => {
|
|
461
|
+
const {
|
|
462
|
+
field
|
|
463
|
+
} = error;
|
|
464
|
+
const flatField = getFlatField(error);
|
|
465
|
+
if (field !== flatField) {
|
|
466
|
+
return !errors.some(error => {
|
|
467
|
+
return error.field === flatField;
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return true;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
return errors;
|
|
474
|
+
}
|
|
475
|
+
function getFlatField(error) {
|
|
476
|
+
const {
|
|
477
|
+
field,
|
|
478
|
+
details
|
|
479
|
+
} = error;
|
|
480
|
+
const path = [field];
|
|
481
|
+
if (details.length === 1 && details[0] instanceof _errors.FieldError) {
|
|
482
|
+
path.push(getFlatField(details[0]));
|
|
483
|
+
}
|
|
484
|
+
return path.join('.');
|
|
485
|
+
}
|
|
486
|
+
|
|
425
487
|
/**
|
|
426
488
|
* Creates an [object schema](https://github.com/bedrockio/yada#object).
|
|
427
489
|
*
|
package/package.json
CHANGED
package/src/Schema.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { omit, uniqBy } from 'lodash';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
AggregateError,
|
|
4
5
|
AllowedError,
|
|
5
6
|
AssertionError,
|
|
6
7
|
FormatError,
|
|
@@ -177,6 +178,8 @@ export default class Schema {
|
|
|
177
178
|
details.push(new FormatError(message, this.meta.format));
|
|
178
179
|
} else if (error instanceof LocalizedError) {
|
|
179
180
|
details.push(new AssertionError(message, type));
|
|
181
|
+
} else if (error instanceof AggregateError) {
|
|
182
|
+
details = [...details, ...error.errors];
|
|
180
183
|
} else {
|
|
181
184
|
details.push(error);
|
|
182
185
|
}
|
package/src/array.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { omit } from 'lodash';
|
|
2
2
|
|
|
3
|
-
import Schema from './Schema';
|
|
4
3
|
import TypeSchema from './TypeSchema';
|
|
5
4
|
import { ArrayError, ElementError, LocalizedError } from './errors';
|
|
6
5
|
|
|
7
6
|
class ArraySchema extends TypeSchema {
|
|
8
|
-
constructor(
|
|
9
|
-
super(Array,
|
|
7
|
+
constructor(meta) {
|
|
8
|
+
super(Array, meta);
|
|
10
9
|
this.setup();
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
setup() {
|
|
14
|
-
const {
|
|
15
|
-
const schema =
|
|
16
|
-
schemas.length > 1 ? new Schema().allow(schemas) : schemas[0];
|
|
13
|
+
const { schema } = this.meta;
|
|
17
14
|
|
|
18
15
|
this.assert('type', (val, options) => {
|
|
19
16
|
if (typeof val === 'string' && options.cast) {
|
|
@@ -111,14 +108,15 @@ class ArraySchema extends TypeSchema {
|
|
|
111
108
|
* @returns {this}
|
|
112
109
|
*/
|
|
113
110
|
transform(fn, root = true) {
|
|
114
|
-
const {
|
|
111
|
+
const { schema, ...rest } = this.meta;
|
|
115
112
|
|
|
116
|
-
const
|
|
117
|
-
|
|
113
|
+
const transformed = new ArraySchema({
|
|
114
|
+
...rest,
|
|
115
|
+
...(schema && {
|
|
116
|
+
schema: schema.transform(fn, false),
|
|
117
|
+
}),
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
const transformed = new ArraySchema(newSchemas, rest);
|
|
121
|
-
|
|
122
120
|
if (root) {
|
|
123
121
|
// @ts-ignore
|
|
124
122
|
return transformed;
|
|
@@ -134,23 +132,12 @@ class ArraySchema extends TypeSchema {
|
|
|
134
132
|
}
|
|
135
133
|
|
|
136
134
|
toJsonSchema(options) {
|
|
137
|
-
|
|
138
|
-
const { schemas } = this.meta;
|
|
139
|
-
if (schemas.length > 1) {
|
|
140
|
-
other = {
|
|
141
|
-
anyOf: schemas.map((schema) => {
|
|
142
|
-
return schema.toJsonSchema(options);
|
|
143
|
-
}),
|
|
144
|
-
};
|
|
145
|
-
} else if (schemas.length === 1) {
|
|
146
|
-
other = {
|
|
147
|
-
items: schemas[0].toJsonSchema(options),
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
135
|
+
const { schema } = this.meta;
|
|
151
136
|
return {
|
|
152
137
|
...super.toJsonSchema(options),
|
|
153
|
-
...
|
|
138
|
+
...(schema && {
|
|
139
|
+
items: schema?.toJsonSchema(options),
|
|
140
|
+
}),
|
|
154
141
|
};
|
|
155
142
|
}
|
|
156
143
|
}
|
|
@@ -158,14 +145,16 @@ class ArraySchema extends TypeSchema {
|
|
|
158
145
|
/**
|
|
159
146
|
* Creates an [array schema](https://github.com/bedrockio/yada#array).
|
|
160
147
|
*
|
|
161
|
-
* @param {
|
|
162
|
-
* the
|
|
163
|
-
* no arguments are passed elements may be of any type. Also
|
|
164
|
-
* accepts a single array argument.
|
|
148
|
+
* @param {*} [schema] - The schema to validate elements in
|
|
149
|
+
* the array. If not passed then elements may be of any type.
|
|
165
150
|
*/
|
|
166
|
-
export default function (
|
|
167
|
-
if (
|
|
168
|
-
|
|
151
|
+
export default function (schema) {
|
|
152
|
+
if (arguments.length > 1) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
'Arrays may only have a single schema. Use "allow" instead.',
|
|
155
|
+
);
|
|
169
156
|
}
|
|
170
|
-
return new ArraySchema(
|
|
157
|
+
return new ArraySchema({
|
|
158
|
+
schema,
|
|
159
|
+
});
|
|
171
160
|
}
|
package/src/errors.js
CHANGED
|
@@ -46,6 +46,13 @@ export class ValidationError extends Error {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export class AggregateError extends Error {
|
|
50
|
+
constructor(message, errors) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.errors = errors;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
export class AssertionError extends ValidationError {
|
|
50
57
|
constructor(message, type = 'assertion') {
|
|
51
58
|
super(message);
|
package/src/object.js
CHANGED
|
@@ -2,7 +2,7 @@ import { omit, pick, set } from 'lodash';
|
|
|
2
2
|
|
|
3
3
|
import Schema, { isSchema } from './Schema';
|
|
4
4
|
import TypeSchema from './TypeSchema';
|
|
5
|
-
import { FieldError, LocalizedError } from './errors';
|
|
5
|
+
import { AggregateError, FieldError, LocalizedError } from './errors';
|
|
6
6
|
|
|
7
7
|
const APPEND_ASSERTION_TYPES = ['required', 'type', 'custom'];
|
|
8
8
|
|
|
@@ -15,88 +15,124 @@ const INTEGER_REG = /^\d+$/;
|
|
|
15
15
|
class ObjectSchema extends TypeSchema {
|
|
16
16
|
constructor(meta) {
|
|
17
17
|
super(Object, meta);
|
|
18
|
+
this.validateInput();
|
|
18
19
|
this.setup();
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
validateInput() {
|
|
23
|
+
const { fields } = this.meta;
|
|
24
|
+
for (let [key, value] of Object.entries(fields || {})) {
|
|
25
|
+
if (!isSchema(value)) {
|
|
26
|
+
throw new Error(`Key "${key}" must be a schema.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
setup() {
|
|
22
32
|
this.assert('type', (val) => {
|
|
23
33
|
if (val === null || typeof val !== 'object') {
|
|
24
34
|
throw new LocalizedError('Must be an object.');
|
|
25
35
|
}
|
|
26
36
|
});
|
|
27
|
-
this.transformValue((obj, options) => {
|
|
28
|
-
const { stripUnknown, stripEmpty } = options;
|
|
29
|
-
if (obj) {
|
|
30
|
-
const result = {};
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
this.assert('fields', async (obj, options) => {
|
|
39
|
+
const {
|
|
40
|
+
path = [],
|
|
41
|
+
original,
|
|
42
|
+
stripEmpty,
|
|
43
|
+
stripUnknown,
|
|
44
|
+
allowFlatKeys,
|
|
45
|
+
expandFlatKeys,
|
|
46
|
+
} = options;
|
|
47
|
+
|
|
48
|
+
const { fields } = this.meta;
|
|
49
|
+
|
|
50
|
+
if (!fields) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let passed = obj;
|
|
55
|
+
|
|
56
|
+
if (expandFlatKeys) {
|
|
57
|
+
passed = expandFlatSyntax(passed);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const keys = new Set([
|
|
61
|
+
...Object.keys(fields || {}),
|
|
62
|
+
...Object.keys(passed),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
let errors = [];
|
|
66
|
+
const result = {};
|
|
33
67
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const isUnknown = !isKnownKey(key, this, options);
|
|
68
|
+
for (let key of keys) {
|
|
69
|
+
const value = passed[key];
|
|
37
70
|
|
|
38
|
-
|
|
71
|
+
if (value === '' && stripEmpty) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const schema = getSchema(fields, key, options);
|
|
76
|
+
|
|
77
|
+
if (!schema) {
|
|
78
|
+
if (stripUnknown) {
|
|
39
79
|
continue;
|
|
40
|
-
} else if (isUnknown) {
|
|
41
|
-
throw new LocalizedError('Unknown field "{key}".', {
|
|
42
|
-
key,
|
|
43
|
-
type: 'field',
|
|
44
|
-
});
|
|
45
|
-
} else {
|
|
46
|
-
result[key] = obj[key];
|
|
47
80
|
}
|
|
81
|
+
throw new LocalizedError('Unknown field "{key}".', {
|
|
82
|
+
type: 'field',
|
|
83
|
+
key,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (schema.meta.strip?.(value, options)) {
|
|
88
|
+
continue;
|
|
48
89
|
}
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
for (let [key, schema] of Object.entries(this.export())) {
|
|
53
|
-
if (!isSchema(schema)) {
|
|
54
|
-
throw new Error(`Key "${key}" must be a schema.`);
|
|
55
|
-
}
|
|
56
|
-
this.assert('field', async (obj, options) => {
|
|
57
|
-
if (obj) {
|
|
58
|
-
const { path = [], original } = options;
|
|
59
|
-
const { strip, required } = schema.meta;
|
|
60
|
-
const val = obj[key];
|
|
61
90
|
|
|
62
|
-
|
|
91
|
+
try {
|
|
92
|
+
const validateOptions = {
|
|
63
93
|
...options,
|
|
64
|
-
|
|
94
|
+
|
|
65
95
|
path: [...path, key],
|
|
96
|
+
required: schema.meta.required,
|
|
97
|
+
|
|
98
|
+
// The root object may be transformed by defaults
|
|
99
|
+
// or returned values so re-pass it here.
|
|
100
|
+
root: {
|
|
101
|
+
...obj,
|
|
102
|
+
...result,
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// The original root represents the root object
|
|
106
|
+
// before it was transformed.
|
|
107
|
+
originalRoot: original,
|
|
66
108
|
};
|
|
67
109
|
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
110
|
+
if (allowFlatKeys && !value) {
|
|
111
|
+
// When allowing keys like "profile.name", "profile" will
|
|
112
|
+
// not be passed so expand the passed object and make sure
|
|
113
|
+
// the base validates, but do not transform the result.
|
|
114
|
+
const expanded = expandFlatSyntax(passed);
|
|
72
115
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
...options,
|
|
79
|
-
// The root object may have been transformed here
|
|
80
|
-
// by defaults or values returned by custom functions
|
|
81
|
-
// so re-pass it here.
|
|
82
|
-
root: obj,
|
|
83
|
-
// The original root represents the root object
|
|
84
|
-
// before it was transformed.
|
|
85
|
-
originalRoot: original,
|
|
86
|
-
});
|
|
87
|
-
if (result !== undefined) {
|
|
88
|
-
return {
|
|
89
|
-
...obj,
|
|
90
|
-
[key]: result,
|
|
91
|
-
};
|
|
116
|
+
await schema.validate(expanded[key], validateOptions);
|
|
117
|
+
} else {
|
|
118
|
+
const transformed = await schema.validate(value, validateOptions);
|
|
119
|
+
if (transformed !== undefined) {
|
|
120
|
+
result[key] = transformed;
|
|
92
121
|
}
|
|
93
|
-
} catch (error) {
|
|
94
|
-
const { message } = schema.meta;
|
|
95
|
-
throw new FieldError(message, key, error.details);
|
|
96
122
|
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
const { message } = schema.meta;
|
|
125
|
+
errors.push(new FieldError(message, key, error.details));
|
|
97
126
|
}
|
|
98
|
-
}
|
|
99
|
-
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (errors.length) {
|
|
130
|
+
errors = normalizeErrors(errors, options);
|
|
131
|
+
throw new AggregateError(this.meta.message, errors);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
});
|
|
100
136
|
}
|
|
101
137
|
|
|
102
138
|
/**
|
|
@@ -117,12 +153,8 @@ class ObjectSchema extends TypeSchema {
|
|
|
117
153
|
const [name, ...rest] = path;
|
|
118
154
|
const schema = fields[name];
|
|
119
155
|
|
|
120
|
-
if (!schema) {
|
|
121
|
-
throw new Error(`Cannot find field "${name}".`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
156
|
if (rest.length) {
|
|
125
|
-
return schema
|
|
157
|
+
return schema?.get(rest);
|
|
126
158
|
} else {
|
|
127
159
|
return schema;
|
|
128
160
|
}
|
|
@@ -138,13 +170,15 @@ class ObjectSchema extends TypeSchema {
|
|
|
138
170
|
unwind(path) {
|
|
139
171
|
path = Array.isArray(path) ? path.join('.') : path;
|
|
140
172
|
const field = this.get(path);
|
|
141
|
-
const {
|
|
142
|
-
|
|
173
|
+
const { type, schema } = field.meta;
|
|
174
|
+
|
|
175
|
+
if (type !== 'array') {
|
|
143
176
|
throw new Error(`Field "${path}" is not an array schema.`);
|
|
144
|
-
} else if (
|
|
145
|
-
throw new Error(`Field "${path}"
|
|
177
|
+
} else if (!schema) {
|
|
178
|
+
throw new Error(`Field "${path}" does not have an inner schema.`);
|
|
146
179
|
}
|
|
147
|
-
|
|
180
|
+
|
|
181
|
+
return schema;
|
|
148
182
|
}
|
|
149
183
|
|
|
150
184
|
/**
|
|
@@ -195,7 +229,11 @@ class ObjectSchema extends TypeSchema {
|
|
|
195
229
|
const update = {};
|
|
196
230
|
|
|
197
231
|
for (let field of fields) {
|
|
198
|
-
|
|
232
|
+
const schema = this.get(field);
|
|
233
|
+
if (!schema) {
|
|
234
|
+
throw new Error(`Cannot find field "${field}".`);
|
|
235
|
+
}
|
|
236
|
+
set(update, field, schema.required());
|
|
199
237
|
}
|
|
200
238
|
|
|
201
239
|
return this.append(update);
|
|
@@ -290,6 +328,44 @@ class ObjectSchema extends TypeSchema {
|
|
|
290
328
|
return schema;
|
|
291
329
|
}
|
|
292
330
|
|
|
331
|
+
// Options
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Remove properties that are empty strings.
|
|
335
|
+
*/
|
|
336
|
+
stripEmpty() {
|
|
337
|
+
return this.options({
|
|
338
|
+
stripEmpty: true,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Remove properties not in the schema.
|
|
344
|
+
*/
|
|
345
|
+
stripUnknown() {
|
|
346
|
+
return this.options({
|
|
347
|
+
stripUnknown: true,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Allow flat keys like `profile.name`.
|
|
353
|
+
*/
|
|
354
|
+
allowFlatKeys() {
|
|
355
|
+
return this.options({
|
|
356
|
+
allowFlatKeys: true,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Expand flat keys into nested objects.
|
|
362
|
+
*/
|
|
363
|
+
expandFlatKeys() {
|
|
364
|
+
return this.options({
|
|
365
|
+
expandFlatKeys: true,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
293
369
|
/**
|
|
294
370
|
* `stripEmpty` - Removes properties that are empty strings.
|
|
295
371
|
* `stripUnknown` - Removes properties not in the schema.
|
|
@@ -330,11 +406,7 @@ class ObjectSchema extends TypeSchema {
|
|
|
330
406
|
}
|
|
331
407
|
}
|
|
332
408
|
|
|
333
|
-
function expandFlatSyntax(obj
|
|
334
|
-
if (!options.expandFlatKeys) {
|
|
335
|
-
return obj;
|
|
336
|
-
}
|
|
337
|
-
|
|
409
|
+
function expandFlatSyntax(obj) {
|
|
338
410
|
const result = { ...obj };
|
|
339
411
|
for (let [key, value] of Object.entries(result)) {
|
|
340
412
|
if (key.includes('.')) {
|
|
@@ -345,65 +417,6 @@ function expandFlatSyntax(obj, options) {
|
|
|
345
417
|
return result;
|
|
346
418
|
}
|
|
347
419
|
|
|
348
|
-
function isKnownKey(key, schema, options) {
|
|
349
|
-
const { fields } = schema.meta;
|
|
350
|
-
const { allowFlatKeys } = options;
|
|
351
|
-
if (!fields) {
|
|
352
|
-
// No fields defined -> all keys are "known".
|
|
353
|
-
return true;
|
|
354
|
-
} else if (key in fields) {
|
|
355
|
-
// Exact match -> key is known.
|
|
356
|
-
return true;
|
|
357
|
-
} else if (allowFlatKeys && key.includes('.')) {
|
|
358
|
-
// Flat syntax "foo.bar".
|
|
359
|
-
const [base, ...rest] = key.split('.');
|
|
360
|
-
let subschema = fields[base];
|
|
361
|
-
|
|
362
|
-
if (!subschema) {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const { type, schemas } = subschema.meta;
|
|
367
|
-
|
|
368
|
-
let subkey;
|
|
369
|
-
|
|
370
|
-
if (type === 'array') {
|
|
371
|
-
// If the subschema is an array then take the first of
|
|
372
|
-
// its defined schemas as we can safely assume that an
|
|
373
|
-
// array of objects will be defined as a single element
|
|
374
|
-
// or multiple schemas will only set the base property.
|
|
375
|
-
// Test that the element key is valid and take any
|
|
376
|
-
// further properties as the subkey. Examples:
|
|
377
|
-
// - profiles.0.name (array of objects)
|
|
378
|
-
// - profiles.0 (array of stringsmk)
|
|
379
|
-
const [index, ...other] = rest;
|
|
380
|
-
if (!INTEGER_REG.test(index)) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
subschema = schemas[0];
|
|
384
|
-
subkey = other.join('.');
|
|
385
|
-
} else if (type === 'object') {
|
|
386
|
-
// If the subschema is an object then simply take any
|
|
387
|
-
// further properties as the subkey. Example:
|
|
388
|
-
// - profile.name
|
|
389
|
-
subkey = rest.join('.');
|
|
390
|
-
} else {
|
|
391
|
-
// If the subschema is anything else then disallow it
|
|
392
|
-
// further properties as the subkey. Example:
|
|
393
|
-
// - profile.name
|
|
394
|
-
return false;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (subschema.meta.type === 'object') {
|
|
398
|
-
return isKnownKey(subkey, subschema, options);
|
|
399
|
-
} else {
|
|
400
|
-
return !subkey;
|
|
401
|
-
}
|
|
402
|
-
} else {
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
420
|
function mergeFields(aFields, bFields) {
|
|
408
421
|
if (!aFields || !bFields) {
|
|
409
422
|
return aFields || bFields;
|
|
@@ -422,6 +435,79 @@ function mergeFields(aFields, bFields) {
|
|
|
422
435
|
return result;
|
|
423
436
|
}
|
|
424
437
|
|
|
438
|
+
// Gets the schema for a field allowing for flat
|
|
439
|
+
// keys which may deeply traverse into the object.
|
|
440
|
+
function getSchema(fields, key, options) {
|
|
441
|
+
const { allowFlatKeys } = options;
|
|
442
|
+
|
|
443
|
+
let schema = fields[key];
|
|
444
|
+
|
|
445
|
+
if (schema) {
|
|
446
|
+
return schema;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (!allowFlatKeys || !key.includes('.')) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
schema = fields;
|
|
454
|
+
|
|
455
|
+
for (let part of key.split('.')) {
|
|
456
|
+
const { type, fields } = schema?.meta || {};
|
|
457
|
+
|
|
458
|
+
if (type === 'array') {
|
|
459
|
+
// If the schema is an array schema then take the first
|
|
460
|
+
// subschema, however only allow integers in this case,
|
|
461
|
+
// for example: profiles.0.name.
|
|
462
|
+
|
|
463
|
+
if (!INTEGER_REG.test(part)) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
schema = schema.meta.schema;
|
|
468
|
+
} else if (fields) {
|
|
469
|
+
schema = fields[part];
|
|
470
|
+
} else {
|
|
471
|
+
schema = schema?.[part];
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return schema;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// If flat keys are allowed then filter out errors
|
|
479
|
+
// that are the nested duplicates so that they match
|
|
480
|
+
// the actual fields that were passed.
|
|
481
|
+
function normalizeErrors(errors, options) {
|
|
482
|
+
const { allowFlatKeys } = options;
|
|
483
|
+
if (allowFlatKeys) {
|
|
484
|
+
errors = errors.filter((error) => {
|
|
485
|
+
const { field } = error;
|
|
486
|
+
const flatField = getFlatField(error);
|
|
487
|
+
|
|
488
|
+
if (field !== flatField) {
|
|
489
|
+
return !errors.some((error) => {
|
|
490
|
+
return error.field === flatField;
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return true;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return errors;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function getFlatField(error) {
|
|
501
|
+
const { field, details } = error;
|
|
502
|
+
const path = [field];
|
|
503
|
+
|
|
504
|
+
if (details.length === 1 && details[0] instanceof FieldError) {
|
|
505
|
+
path.push(getFlatField(details[0]));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return path.join('.');
|
|
509
|
+
}
|
|
510
|
+
|
|
425
511
|
/**
|
|
426
512
|
* Creates an [object schema](https://github.com/bedrockio/yada#object).
|
|
427
513
|
*
|
package/types/Schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../src/Schema.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../src/Schema.js"],"names":[],"mappings":"AAkBA,kDAEC;AAED;IACE,uBAGC;IAFC,kBAAoB;IACpB,SAAgB;IAKlB;;;OAGG;IACH,iBAHW,OAAO,GACL,IAAI,CAUhB;IAED;;;OAGG;IACH,mBAFa,IAAI,CAShB;IAED;;;;OAIG;IACH,sBAFa,IAAI,CAShB;IAED;;;;OAIG;IACH,uBAFa,IAAI,CAShB;IAED;;;;OAIG;IACH,mBAFa,IAAI,CAIhB;IAED;;;OAGG;IACH,sBAFa,IAAI,CAIhB;IAED;;;OAGG;IACH,uBAFa,IAAI,CAIhB;IAED;;;;OAIG;IACH,iBAHW,OAAO,GACL,IAAI,CAIhB;IAED;;OAEG;IACH,uBAFa,IAAI,CAIhB;IAED;;OAEG;IACH,gBAFa,IAAI,CAShB;IAED;;OAEG;IACH,+BAFa,IAAI,CAMhB;IAED;;OAEG;IACH,uBAFa,IAAI,CAIhB;IAED,iDA0CC;IAED;;OAEG;IACH,kBAFa,IAAI,CAOhB;IAED;;;OAGG;IACH,qBAFa,MAAM,CAMlB;IAED;;;;;;;;;;OAUG;IACH,uBANG;QAA2B,GAAG;QACH,KAAK,GAAxB,QAAQ;QAGU,eAAe,GAAjC,OAAO;KACjB,OASA;IAED;;;OAGG;IACH,6BAEC;IAED;;OAEG;IACH,cAEC;IAED;;MAWC;IAED;;MAKC;IAED;;;;MASC;IAED,2BAgDC;IAED,2BAmBC;IAED;;;;;OAKG;IACH,oCAFa,IAAI,CAgBhB;IAED,kBAEC;IAED,YAGC;IAID;;OAEG;IACH,kCAFa,IAAI,CAsDhB;IAED;;OAEG;IACH,4BAFa,IAAI,CAUhB;IAED,oCAKC;IAED,gEAQC;IAED;;OAEG;IACH,yBAFa,IAAI,CAShB;IAED,gCAGC;IAED,qEAYC;CACF"}
|
package/types/array.d.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creates an [array schema](https://github.com/bedrockio/yada#array).
|
|
3
3
|
*
|
|
4
|
-
* @param {
|
|
5
|
-
* the
|
|
6
|
-
* no arguments are passed elements may be of any type. Also
|
|
7
|
-
* accepts a single array argument.
|
|
4
|
+
* @param {*} [schema] - The schema to validate elements in
|
|
5
|
+
* the array. If not passed then elements may be of any type.
|
|
8
6
|
*/
|
|
9
|
-
export default function _default(
|
|
10
|
-
import Schema from './Schema';
|
|
7
|
+
export default function _default(schema?: any, ...args: any[]): ArraySchema;
|
|
11
8
|
declare class ArraySchema extends TypeSchema {
|
|
9
|
+
constructor(meta: any);
|
|
12
10
|
setup(): void;
|
|
13
11
|
length(length: any): this;
|
|
14
12
|
min(length: any): this;
|
package/types/array.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../src/array.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"array.d.ts","sourceRoot":"","sources":["../src/array.js"],"names":[],"mappings":"AAgJA;;;;;GAKG;AACH,0CAHW,GAAC,+BAYX;AA1JD;IACE,uBAGC;IAED,cAoCC;IAED,0BAUC;IAED,uBAUC;IAED,uBAaC;IAED,eAaC;IAED;;;;;OAKG;IACH,yCAFa,IAAI,CAkBhB;IAID,mBAEC;IAED,gCAQC;CACF;uBA5IsB,cAAc"}
|
package/types/errors.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export class ValidationError extends Error {
|
|
|
15
15
|
getMessage(): any;
|
|
16
16
|
getFullMessage(options: any): any;
|
|
17
17
|
}
|
|
18
|
+
export class AggregateError extends Error {
|
|
19
|
+
constructor(message: any, errors: any);
|
|
20
|
+
errors: any;
|
|
21
|
+
}
|
|
18
22
|
export class AssertionError extends ValidationError {
|
|
19
23
|
constructor(message: any, type?: string);
|
|
20
24
|
}
|
package/types/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.js"],"names":[],"mappings":"AA4IA,gEAEC;AA3ID;IACE,uCAEC;CACF;AAED;IACE,+BAA8C;IAE9C,+CAIC;IAFC,aAAwB;IACxB,eAAsB;IAGxB;;;;MAWC;IAED,kBAOC;IAED,kCAKC;CACF;AAED;IACE,uCAGC;IADC,YAAoB;CAEvB;AAED;IACE,yCAGC;CACF;AAED;IACE,qCAIC;IADC,UAAgB;IAGlB;;;;;MAKC;CACF;AAED;IACE,uCAIC;IADC,YAAoB;IAGtB;;;;;MAKC;CACF;AAED;IAGE,uDAIC;IADC,WAAkB;IAGpB;;;;;MAKC;CACF;AAED;IAGE,uDAIC;IADC,WAAkB;IAGpB;;;;;MAKC;CACF;AAED;IACE,wCAGC;CACF;AAED;IACE,wCAGC;CACF"}
|
package/types/object.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type SchemaMap = {
|
|
|
14
14
|
*/
|
|
15
15
|
declare class ObjectSchema extends TypeSchema {
|
|
16
16
|
constructor(meta: any);
|
|
17
|
+
validateInput(): void;
|
|
17
18
|
setup(): void;
|
|
18
19
|
/**
|
|
19
20
|
* Gets the schema for the given field. Deep fields accept
|
|
@@ -83,6 +84,22 @@ declare class ObjectSchema extends TypeSchema {
|
|
|
83
84
|
* @param {SchemaMap|Schema} arg Object or schema to append.
|
|
84
85
|
*/
|
|
85
86
|
append(arg: SchemaMap | Schema): ObjectSchema;
|
|
87
|
+
/**
|
|
88
|
+
* Remove properties that are empty strings.
|
|
89
|
+
*/
|
|
90
|
+
stripEmpty(): this;
|
|
91
|
+
/**
|
|
92
|
+
* Remove properties not in the schema.
|
|
93
|
+
*/
|
|
94
|
+
stripUnknown(): this;
|
|
95
|
+
/**
|
|
96
|
+
* Allow flat keys like `profile.name`.
|
|
97
|
+
*/
|
|
98
|
+
allowFlatKeys(): this;
|
|
99
|
+
/**
|
|
100
|
+
* Expand flat keys into nested objects.
|
|
101
|
+
*/
|
|
102
|
+
expandFlatKeys(): this;
|
|
86
103
|
/**
|
|
87
104
|
* `stripEmpty` - Removes properties that are empty strings.
|
|
88
105
|
* `stripUnknown` - Removes properties not in the schema.
|
package/types/object.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../src/object.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../src/object.js"],"names":[],"mappings":"AA8fA;;;;;;GAMG;AACH,uCAJW,SAAS,gBAQnB;wBA9fY;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,EAAE;AAD3C;;GAEG;AAEH;IACE,uBAIC;IAED,sBAOC;IAED,cAyGC;IAED;;;;;;OAMG;IACH,WAFW,MAAM,GAAC,KAAK,CAAC,MAAM,CAAC,OAkB9B;IAED;;;;;;OAMG;IACH,cAFW,MAAM,GAAC,KAAK,CAAC,MAAM,CAAC,OAc9B;IAED;;;;OAIG;IACH,gBAFc,MAAM,EAAA,gBASnB;IAED;;;;OAIG;IACH,gBAFc,MAAM,EAAA,gBASnB;IAED;;;;;;;OAOG;IACH,mBAHc,MAAM,EAAA,gBAuBnB;IAED;;OAEG;IACH,2BAQC;IAED;;;;;;OAMG;IACH,yCAFa,IAAI,CAiBhB;IAED;;;;;;OAMG;IACH,cAEC;IAED;;;;;;;;;OASG;IACH,YAFW,SAAS,GAAC,MAAM,gBA+B1B;IAID;;OAEG;IACH,mBAIC;IAED;;OAEG;IACH,qBAIC;IAED;;OAEG;IACH,sBAIC;IAED;;OAEG;IACH,uBAIC;IAED;;;;;;;;;;;OAWG;IACH,kBALG;QAA0B,UAAU,GAA5B,OAAO;QACW,YAAY,GAA9B,OAAO;QACW,aAAa,GAA/B,OAAO;QACW,cAAc,GAAhC,OAAO;KAA0B,QAI3C;IAID,gCAmBC;CACF;mBApZgC,UAAU;uBACpB,cAAc"}
|