@bedrockio/yada 1.2.9 → 1.4.0
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 +11 -0
- package/README.md +88 -3
- package/dist/cjs/Schema.js +9 -4
- package/dist/cjs/array.js +2 -2
- package/dist/cjs/date.js +1 -5
- package/dist/cjs/index.js +7 -6
- package/dist/cjs/object.js +158 -53
- package/eslint.config.js +3 -0
- package/package.json +5 -5
- package/src/Schema.js +8 -3
- package/src/array.js +3 -2
- package/src/date.js +3 -7
- package/src/index.js +3 -1
- package/src/object.js +157 -48
- package/src/password.js +4 -4
- package/src/string.js +2 -1
- package/types/Schema.d.ts +3 -2
- package/types/Schema.d.ts.map +1 -1
- package/types/array.d.ts.map +1 -1
- package/types/date.d.ts.map +1 -1
- package/types/index.d.ts +2 -2
- package/types/index.d.ts.map +1 -1
- package/types/object.d.ts +47 -3
- package/types/object.d.ts.map +1 -1
- package/types/string.d.ts.map +1 -1
- package/.prettierrc.cjs +0 -1
- package/dist/cjs/utils.js +0 -37
- package/src/utils.js +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 1.4.0
|
|
2
|
+
|
|
3
|
+
- Added object schema "require".
|
|
4
|
+
- Added object schema "unwind" to get an inner array schema.
|
|
5
|
+
- Added object schema "export" to allow object spreading.
|
|
6
|
+
- "append" now handles handle deep fields.
|
|
7
|
+
|
|
8
|
+
## 1.3.0
|
|
9
|
+
|
|
10
|
+
- Added object schema "get".
|
|
11
|
+
|
|
1
12
|
## 1.2.9
|
|
2
13
|
|
|
3
14
|
- Fixed field missing on empty strings.
|
package/README.md
CHANGED
|
@@ -331,7 +331,7 @@ const schema = yd
|
|
|
331
331
|
.required();
|
|
332
332
|
```
|
|
333
333
|
|
|
334
|
-
###
|
|
334
|
+
### Selecting Fields
|
|
335
335
|
|
|
336
336
|
Object schemas also have a `pick` and `omit` method that allows custom field
|
|
337
337
|
selection. These methods accept an array of field names or enumerated arguments:
|
|
@@ -350,6 +350,87 @@ schema.pick(['firstName', 'lastName']);
|
|
|
350
350
|
schema.omit('firstName', 'lastName');
|
|
351
351
|
```
|
|
352
352
|
|
|
353
|
+
To get a single field use the `get` method:
|
|
354
|
+
|
|
355
|
+
```js
|
|
356
|
+
const schema = yd.object({
|
|
357
|
+
profile: yd.object({
|
|
358
|
+
firstName: yd.string().required(),
|
|
359
|
+
lastName: yd.string().required(),
|
|
360
|
+
}),
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Gets the inner "profile" schema.
|
|
364
|
+
schema.get('profile');
|
|
365
|
+
|
|
366
|
+
// Gets just the "firstName" schema.
|
|
367
|
+
schema.get('profile.firstName');
|
|
368
|
+
|
|
369
|
+
// May also pass an array of path segments.
|
|
370
|
+
schema.get(['profile', 'firstName']);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
To get a nested array field use the `unwind` method:
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
const schema = yd.object({
|
|
377
|
+
paymentMethods: yd.array(
|
|
378
|
+
yd.object({
|
|
379
|
+
brand: yd.string().allow('visa', 'mastercard'),
|
|
380
|
+
last4: yd.string().length(4),
|
|
381
|
+
expMonth: yd.number(),
|
|
382
|
+
expYear: yd.number(),
|
|
383
|
+
}),
|
|
384
|
+
),
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Gets the inner "paymentMethods" object schema.
|
|
388
|
+
// Will accept a single paymentMethod.
|
|
389
|
+
schema.unwind('paymentMethods');
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Compare `unwind` to `get` which would validate against an array instead of an
|
|
393
|
+
object. Note that `unwind` only makes sense when there is exactly one inner
|
|
394
|
+
schema and will error otherwise.
|
|
395
|
+
|
|
396
|
+
### Augmenting Fields
|
|
397
|
+
|
|
398
|
+
The `require` method allows augmentation of the schema to enforce specific
|
|
399
|
+
fields:
|
|
400
|
+
|
|
401
|
+
```js
|
|
402
|
+
const schema = yd.object({
|
|
403
|
+
email: yd.string().email().required(),
|
|
404
|
+
phone: yd.string().phone(),
|
|
405
|
+
dob: yd.date(),
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// For example a schema for a signup flow that
|
|
409
|
+
// requires more fields for normal users than admins.
|
|
410
|
+
const signupSchema = schema.require('phone', 'dob');
|
|
411
|
+
|
|
412
|
+
// Also accepts an array.
|
|
413
|
+
schema.require(['phone', 'dob']);
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Note that there is no way to "unrequire" a field. The idea is to start with the
|
|
417
|
+
minimal schema as a base and lock down more from there.
|
|
418
|
+
|
|
419
|
+
### Merging Fields
|
|
420
|
+
|
|
421
|
+
The [append](#append) method merges object schemas together, however it
|
|
422
|
+
preserves custom and required assertions on the object itself which is not
|
|
423
|
+
always desired. It also lacks the elegance of the spread operator. For this the
|
|
424
|
+
`export` method is provided which will simply export the schema's fields as an
|
|
425
|
+
object:
|
|
426
|
+
|
|
427
|
+
```js
|
|
428
|
+
const newSchema = yd.object({
|
|
429
|
+
...schema1.export(),
|
|
430
|
+
...schema2.export(),
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
353
434
|
## Date
|
|
354
435
|
|
|
355
436
|
Dates are similar to the basic types with the exception that in addition to date
|
|
@@ -448,6 +529,10 @@ const schema2 = yd.object({
|
|
|
448
529
|
const schema = schema1.append(schema2);
|
|
449
530
|
```
|
|
450
531
|
|
|
532
|
+
Note however that this preserves custom and required assertions on the object
|
|
533
|
+
itself. To simply merge fields as an object use the [export](#merging-fields)
|
|
534
|
+
method instead.
|
|
535
|
+
|
|
451
536
|
### Custom
|
|
452
537
|
|
|
453
538
|
The `custom` schema allows for custom validations expressed in code. A custom
|
|
@@ -727,7 +812,7 @@ The delimiter can be customized with the `delimiter` option:
|
|
|
727
812
|
console.info(
|
|
728
813
|
error.getFullMessage({
|
|
729
814
|
delimiter: '\n',
|
|
730
|
-
})
|
|
815
|
+
}),
|
|
731
816
|
);
|
|
732
817
|
// "email" is required
|
|
733
818
|
// "lastName" is required
|
|
@@ -740,7 +825,7 @@ labels:
|
|
|
740
825
|
console.info(
|
|
741
826
|
error.getFullMessage({
|
|
742
827
|
natural: true,
|
|
743
|
-
})
|
|
828
|
+
}),
|
|
744
829
|
);
|
|
745
830
|
// "Email" is required
|
|
746
831
|
// "Last Name" is required
|
package/dist/cjs/Schema.js
CHANGED
|
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
exports.isSchema = isSchema;
|
|
8
|
+
var _lodashEs = require("lodash-es");
|
|
8
9
|
var _errors = require("./errors");
|
|
9
|
-
var _utils = require("./utils");
|
|
10
10
|
const INITIAL_TYPES = ['default', 'required', 'type', 'transform', 'empty'];
|
|
11
11
|
const REQUIRED_TYPES = ['default', 'required', 'missing'];
|
|
12
12
|
class Schema {
|
|
@@ -151,7 +151,6 @@ class Schema {
|
|
|
151
151
|
async validate(value, options = {}) {
|
|
152
152
|
let details = [];
|
|
153
153
|
options = {
|
|
154
|
-
root: value,
|
|
155
154
|
...options,
|
|
156
155
|
...this.meta,
|
|
157
156
|
original: value
|
|
@@ -204,7 +203,7 @@ class Schema {
|
|
|
204
203
|
|
|
205
204
|
/**
|
|
206
205
|
* Appends another schema. [Link](https://github.com/bedrockio/yada#append)
|
|
207
|
-
* @returns {
|
|
206
|
+
* @returns {Schema}
|
|
208
207
|
*/
|
|
209
208
|
append(schema) {
|
|
210
209
|
const merged = this.clone(schema.meta);
|
|
@@ -274,6 +273,12 @@ class Schema {
|
|
|
274
273
|
inspect() {
|
|
275
274
|
return JSON.stringify(this.toOpenApi(), null, 2);
|
|
276
275
|
}
|
|
276
|
+
get() {
|
|
277
|
+
const {
|
|
278
|
+
name
|
|
279
|
+
} = this.constructor;
|
|
280
|
+
throw new Error(`"get" not implemented by ${name}.`);
|
|
281
|
+
}
|
|
277
282
|
|
|
278
283
|
// Private
|
|
279
284
|
|
|
@@ -302,7 +307,7 @@ class Schema {
|
|
|
302
307
|
// Must not pass cast option down when allowing
|
|
303
308
|
// other schema types as they may be allowed, for
|
|
304
309
|
// example allowing a string or array of strings.
|
|
305
|
-
options = (0,
|
|
310
|
+
options = (0, _lodashEs.omit)(options, 'cast');
|
|
306
311
|
return await el.validate(val, options);
|
|
307
312
|
} catch (err) {
|
|
308
313
|
const [first] = err.details;
|
package/dist/cjs/array.js
CHANGED
|
@@ -4,10 +4,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = _default;
|
|
7
|
+
var _lodashEs = require("lodash-es");
|
|
7
8
|
var _Schema = _interopRequireDefault(require("./Schema"));
|
|
8
9
|
var _TypeSchema = _interopRequireDefault(require("./TypeSchema"));
|
|
9
10
|
var _errors = require("./errors");
|
|
10
|
-
var _utils = require("./utils");
|
|
11
11
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
12
|
class ArraySchema extends _TypeSchema.default {
|
|
13
13
|
constructor(schemas) {
|
|
@@ -42,7 +42,7 @@ class ArraySchema extends _TypeSchema.default {
|
|
|
42
42
|
try {
|
|
43
43
|
// Allow enum message to take
|
|
44
44
|
// precedence over generic array message.
|
|
45
|
-
options = (0,
|
|
45
|
+
options = (0, _lodashEs.omit)(options, 'message');
|
|
46
46
|
result.push(await schema.validate(el, options));
|
|
47
47
|
} catch (error) {
|
|
48
48
|
errors.push(new _errors.ElementError(message, i, error.details));
|
package/dist/cjs/date.js
CHANGED
|
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = _default;
|
|
7
7
|
var _validator = _interopRequireDefault(require("validator"));
|
|
8
|
-
var _errors = require("./errors");
|
|
9
8
|
var _Schema = _interopRequireDefault(require("./Schema"));
|
|
9
|
+
var _errors = require("./errors");
|
|
10
10
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
11
|
class DateSchema extends _Schema.default {
|
|
12
12
|
constructor() {
|
|
@@ -32,7 +32,6 @@ class DateSchema extends _Schema.default {
|
|
|
32
32
|
return this.clone().assert('min', date => {
|
|
33
33
|
if (date < min) {
|
|
34
34
|
throw new _errors.LocalizedError('Must be after {date}.', {
|
|
35
|
-
// @ts-ignore
|
|
36
35
|
date: min.toISOString()
|
|
37
36
|
});
|
|
38
37
|
}
|
|
@@ -47,7 +46,6 @@ class DateSchema extends _Schema.default {
|
|
|
47
46
|
return this.clone().assert('max', date => {
|
|
48
47
|
if (date > max) {
|
|
49
48
|
throw new _errors.LocalizedError('Must be before {date}.', {
|
|
50
|
-
// @ts-ignore
|
|
51
49
|
date: max.toISOString()
|
|
52
50
|
});
|
|
53
51
|
}
|
|
@@ -62,7 +60,6 @@ class DateSchema extends _Schema.default {
|
|
|
62
60
|
return this.clone().assert('before', date => {
|
|
63
61
|
if (date >= max) {
|
|
64
62
|
throw new _errors.LocalizedError('Must be before {date}.', {
|
|
65
|
-
// @ts-ignore
|
|
66
63
|
date: max.toISOString()
|
|
67
64
|
});
|
|
68
65
|
}
|
|
@@ -77,7 +74,6 @@ class DateSchema extends _Schema.default {
|
|
|
77
74
|
return this.clone().assert('after', date => {
|
|
78
75
|
if (date <= min) {
|
|
79
76
|
throw new _errors.LocalizedError('Must be after {date}.', {
|
|
80
|
-
// @ts-ignore
|
|
81
77
|
date: min.toISOString()
|
|
82
78
|
});
|
|
83
79
|
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -40,13 +40,13 @@ Object.defineProperty(exports, "getLocalizedMessages", {
|
|
|
40
40
|
Object.defineProperty(exports, "isSchema", {
|
|
41
41
|
enumerable: true,
|
|
42
42
|
get: function () {
|
|
43
|
-
return
|
|
43
|
+
return _Schema.isSchema;
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
Object.defineProperty(exports, "isSchemaError", {
|
|
47
47
|
enumerable: true,
|
|
48
48
|
get: function () {
|
|
49
|
-
return
|
|
49
|
+
return _errors.isSchemaError;
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
52
|
Object.defineProperty(exports, "number", {
|
|
@@ -87,10 +87,11 @@ var _date = _interopRequireDefault(require("./date"));
|
|
|
87
87
|
var _object = _interopRequireDefault(require("./object"));
|
|
88
88
|
var _array = _interopRequireDefault(require("./array"));
|
|
89
89
|
var _tuple = _interopRequireDefault(require("./tuple"));
|
|
90
|
-
var _Schema =
|
|
91
|
-
var _utils = require("./utils");
|
|
90
|
+
var _Schema = _interopRequireWildcard(require("./Schema"));
|
|
92
91
|
var _localization = require("./localization");
|
|
93
92
|
var _errors = require("./errors");
|
|
93
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
94
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
94
95
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
95
96
|
/**
|
|
96
97
|
* Accepts anything.
|
|
@@ -132,8 +133,8 @@ var _default = {
|
|
|
132
133
|
allow,
|
|
133
134
|
reject,
|
|
134
135
|
custom,
|
|
135
|
-
isSchema:
|
|
136
|
-
isSchemaError:
|
|
136
|
+
isSchema: _Schema.isSchema,
|
|
137
|
+
isSchemaError: _errors.isSchemaError,
|
|
137
138
|
useLocalizer: _localization.useLocalizer,
|
|
138
139
|
getLocalizedMessages: _localization.getLocalizedMessages,
|
|
139
140
|
LocalizedError: _errors.LocalizedError
|
package/dist/cjs/object.js
CHANGED
|
@@ -4,25 +4,22 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = _default;
|
|
7
|
+
var _lodashEs = require("lodash-es");
|
|
7
8
|
var _TypeSchema = _interopRequireDefault(require("./TypeSchema"));
|
|
8
9
|
var _errors = require("./errors");
|
|
9
|
-
var _utils = require("./utils");
|
|
10
10
|
var _Schema = _interopRequireWildcard(require("./Schema"));
|
|
11
11
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
12
12
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
13
13
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
14
|
-
const
|
|
14
|
+
const APPEND_ASSERTION_TYPES = ['required', 'type', 'custom'];
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @typedef {{ [key: string]: Schema } | {}} SchemaMap
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
class ObjectSchema extends _TypeSchema.default {
|
|
21
|
-
constructor(
|
|
22
|
-
super(Object,
|
|
23
|
-
...meta,
|
|
24
|
-
fields
|
|
25
|
-
});
|
|
21
|
+
constructor(meta) {
|
|
22
|
+
super(Object, meta);
|
|
26
23
|
this.setup();
|
|
27
24
|
}
|
|
28
25
|
setup() {
|
|
@@ -60,14 +57,15 @@ class ObjectSchema extends _TypeSchema.default {
|
|
|
60
57
|
return result;
|
|
61
58
|
}
|
|
62
59
|
});
|
|
63
|
-
for (let [key, schema] of Object.entries(this.
|
|
60
|
+
for (let [key, schema] of Object.entries(this.export())) {
|
|
64
61
|
if (!(0, _Schema.isSchema)(schema)) {
|
|
65
|
-
throw new Error(`Key "${key}" must be a schema
|
|
62
|
+
throw new Error(`Key "${key}" must be a schema.`);
|
|
66
63
|
}
|
|
67
64
|
this.assert('field', async (obj, options) => {
|
|
68
65
|
if (obj) {
|
|
69
66
|
const {
|
|
70
|
-
path = []
|
|
67
|
+
path = [],
|
|
68
|
+
original
|
|
71
69
|
} = options;
|
|
72
70
|
const {
|
|
73
71
|
strip,
|
|
@@ -86,12 +84,16 @@ class ObjectSchema extends _TypeSchema.default {
|
|
|
86
84
|
try {
|
|
87
85
|
// Do not pass down message into validators
|
|
88
86
|
// to allow custom messages to take precedence.
|
|
89
|
-
options = (0,
|
|
87
|
+
options = (0, _lodashEs.omit)(options, 'message');
|
|
90
88
|
const result = await schema.validate(val, {
|
|
91
89
|
...options,
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
90
|
+
// The root object may have been transformed here
|
|
91
|
+
// by defaults or values returned by custom functions
|
|
92
|
+
// so re-pass it here.
|
|
93
|
+
root: obj,
|
|
94
|
+
// The original root represents the root object
|
|
95
|
+
// before it was transformed.
|
|
96
|
+
originalRoot: original
|
|
95
97
|
});
|
|
96
98
|
if (result !== undefined) {
|
|
97
99
|
return {
|
|
@@ -109,73 +111,156 @@ class ObjectSchema extends _TypeSchema.default {
|
|
|
109
111
|
});
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
|
-
getFields() {
|
|
113
|
-
return this.meta.fields || {};
|
|
114
|
-
}
|
|
115
114
|
|
|
116
115
|
/**
|
|
117
|
-
*
|
|
116
|
+
* Gets the schema for the given field. Deep fields accept
|
|
117
|
+
* either a string using dot syntax or an array representing
|
|
118
|
+
* the path.
|
|
119
|
+
*
|
|
120
|
+
* @param {string|Array<string>} [path] The path of the field.
|
|
118
121
|
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
get(path) {
|
|
123
|
+
const {
|
|
124
|
+
fields
|
|
125
|
+
} = this.meta;
|
|
126
|
+
if (!fields) {
|
|
127
|
+
throw new Error('Cannot select field on an open object schema.');
|
|
128
|
+
}
|
|
129
|
+
path = Array.isArray(path) ? path : path.split('.');
|
|
130
|
+
const [name, ...rest] = path;
|
|
131
|
+
const schema = fields[name];
|
|
132
|
+
if (!schema) {
|
|
133
|
+
throw new Error(`Cannot find field "${name}".`);
|
|
134
|
+
}
|
|
135
|
+
if (rest.length) {
|
|
136
|
+
return schema.get(rest);
|
|
128
137
|
} else {
|
|
129
|
-
schema
|
|
138
|
+
return schema;
|
|
130
139
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns the inner schema of an array field. This only makes
|
|
144
|
+
* sense if the array field holds a single schema, so all other
|
|
145
|
+
* scenarios will throw an error.
|
|
146
|
+
*
|
|
147
|
+
* @param {string|Array<string>} [path] The path of the field.
|
|
148
|
+
*/
|
|
149
|
+
unwind(path) {
|
|
150
|
+
path = Array.isArray(path) ? path.join('.') : path;
|
|
151
|
+
const field = this.get(path);
|
|
152
|
+
const {
|
|
153
|
+
schemas
|
|
154
|
+
} = field.meta;
|
|
155
|
+
if (!schemas) {
|
|
156
|
+
throw new Error(`Field "${path}" is not an array schema.`);
|
|
157
|
+
} else if (schemas.length !== 1) {
|
|
158
|
+
throw new Error(`Field "${path}" should contain only one schema.`);
|
|
147
159
|
}
|
|
148
|
-
return
|
|
160
|
+
return schemas[0];
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
/**
|
|
164
|
+
* Returns a new schema that only validates the selected fields.
|
|
165
|
+
*
|
|
152
166
|
* @param {...string} [names] Names to include.
|
|
153
167
|
*/
|
|
154
168
|
pick(...names) {
|
|
155
169
|
if (Array.isArray(names[0])) {
|
|
156
170
|
names = names[0];
|
|
157
171
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
...this.meta
|
|
172
|
+
return new ObjectSchema({
|
|
173
|
+
fields: (0, _lodashEs.pick)(this.meta.fields, names)
|
|
161
174
|
});
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
/**
|
|
178
|
+
* Returns a new schema that omits fields.
|
|
179
|
+
*
|
|
165
180
|
* @param {...string} [names] Names to exclude.
|
|
166
181
|
*/
|
|
167
182
|
omit(...names) {
|
|
168
183
|
if (Array.isArray(names[0])) {
|
|
169
184
|
names = names[0];
|
|
170
185
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
186
|
+
return new ObjectSchema({
|
|
187
|
+
fields: (0, _lodashEs.omit)(this.meta.fields, names)
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Augments the object schema to make fields required.
|
|
193
|
+
* Field names may be passed as an array or arguments.
|
|
194
|
+
* Field names may be deep using dot syntax.
|
|
195
|
+
*
|
|
196
|
+
* @param {...string} fields
|
|
197
|
+
* @param {Array<string>} fields
|
|
198
|
+
*/
|
|
199
|
+
require(...fields) {
|
|
200
|
+
if (!fields.length) {
|
|
201
|
+
throw new Error('No fields specified.');
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(fields[0])) {
|
|
204
|
+
fields = fields[0];
|
|
205
|
+
}
|
|
206
|
+
const update = {};
|
|
207
|
+
for (let field of fields) {
|
|
208
|
+
(0, _lodashEs.set)(update, field, this.get(field).required());
|
|
209
|
+
}
|
|
210
|
+
return this.append(update);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Returns the schema's fields as an object allowing them
|
|
215
|
+
* to be "spread" to create new schemas. Note that doing
|
|
216
|
+
* this will mean that custom and required assertions will
|
|
217
|
+
* not be preserved. Compare to {@link append} which
|
|
218
|
+
* preserves all assertions on the base schema.
|
|
219
|
+
*/
|
|
220
|
+
export() {
|
|
221
|
+
return this.meta.fields || {};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Appends another schema and returns a new one. Appended
|
|
226
|
+
* schemas may have nested fields. Note that custom and
|
|
227
|
+
* required assertions on the schemas are preserved. This
|
|
228
|
+
* means that if either object schema is required then the
|
|
229
|
+
* resulting merged schema will also be required. Compare
|
|
230
|
+
* to {@link export} which exports the fields as an object.
|
|
231
|
+
*
|
|
232
|
+
* @param {SchemaMap|Schema} arg Object or schema to append.
|
|
233
|
+
*/
|
|
234
|
+
append(arg) {
|
|
235
|
+
let meta;
|
|
236
|
+
let assertions = [...this.assertions];
|
|
237
|
+
if (arg instanceof _Schema.default) {
|
|
238
|
+
meta = arg.meta;
|
|
239
|
+
assertions = [...assertions, ...arg.assertions];
|
|
240
|
+
} else {
|
|
241
|
+
meta = {
|
|
242
|
+
fields: arg
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const fields = mergeFields(this.meta.fields, meta?.fields);
|
|
246
|
+
const schema = new ObjectSchema({
|
|
247
|
+
...this.meta,
|
|
248
|
+
...meta,
|
|
249
|
+
fields
|
|
174
250
|
});
|
|
251
|
+
for (let assertion of assertions) {
|
|
252
|
+
const {
|
|
253
|
+
type
|
|
254
|
+
} = assertion;
|
|
255
|
+
if (APPEND_ASSERTION_TYPES.includes(type)) {
|
|
256
|
+
schema.pushAssertion(assertion);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return schema;
|
|
175
260
|
}
|
|
176
261
|
toOpenApi(extra) {
|
|
177
262
|
const properties = {};
|
|
178
|
-
for (let [key, schema] of Object.entries(this.
|
|
263
|
+
for (let [key, schema] of Object.entries(this.export())) {
|
|
179
264
|
properties[key] = schema.toOpenApi(extra);
|
|
180
265
|
}
|
|
181
266
|
return {
|
|
@@ -207,6 +292,24 @@ function expandDotProperties(obj) {
|
|
|
207
292
|
}
|
|
208
293
|
return result;
|
|
209
294
|
}
|
|
295
|
+
function mergeFields(aFields, bFields) {
|
|
296
|
+
if (!aFields || !bFields) {
|
|
297
|
+
return aFields || bFields;
|
|
298
|
+
}
|
|
299
|
+
const result = {
|
|
300
|
+
...aFields
|
|
301
|
+
};
|
|
302
|
+
for (let key of Object.keys(bFields)) {
|
|
303
|
+
const aSchema = aFields[key];
|
|
304
|
+
const bSchema = bFields[key];
|
|
305
|
+
if (aSchema instanceof ObjectSchema) {
|
|
306
|
+
result[key] = aSchema.append(bSchema);
|
|
307
|
+
} else {
|
|
308
|
+
result[key] = bSchema;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
210
313
|
|
|
211
314
|
/**
|
|
212
315
|
* Creates an [object schema](https://github.com/bedrockio/yada#object).
|
|
@@ -216,5 +319,7 @@ function expandDotProperties(obj) {
|
|
|
216
319
|
* empty object is passed then no keys will be allowed.
|
|
217
320
|
*/
|
|
218
321
|
function _default(map) {
|
|
219
|
-
return new ObjectSchema(
|
|
322
|
+
return new ObjectSchema({
|
|
323
|
+
fields: map
|
|
324
|
+
});
|
|
220
325
|
}
|
package/eslint.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrockio/yada",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Validation library inspired by Joi.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "jest",
|
|
@@ -17,19 +17,19 @@
|
|
|
17
17
|
"author": "Andrew Plummer <plummer.andrew@gmail.com>",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"lodash-es": "^4.17.21",
|
|
20
21
|
"validator": "^13.9.0"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@babel/cli": "^7.20.7",
|
|
24
25
|
"@babel/core": "^7.20.12",
|
|
25
26
|
"@babel/preset-env": "^7.20.2",
|
|
27
|
+
"@bedrockio/eslint-plugin": "^1.1.7",
|
|
26
28
|
"@bedrockio/prettier-config": "^1.0.2",
|
|
27
29
|
"babel-plugin-add-module-exports": "^1.0.4",
|
|
28
|
-
"eslint": "^
|
|
29
|
-
"eslint-plugin-bedrock": "^1.0.24",
|
|
30
|
+
"eslint": "^9.19.0",
|
|
30
31
|
"jest": "^29.6.2",
|
|
31
|
-
"prettier": "^
|
|
32
|
-
"prettier-eslint": "^15.0.1",
|
|
32
|
+
"prettier": "^3.4.2",
|
|
33
33
|
"typescript": "^5.7.2"
|
|
34
34
|
},
|
|
35
35
|
"prettier": "@bedrockio/prettier-config",
|
package/src/Schema.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { omit } from 'lodash-es';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
TypeError,
|
|
3
5
|
FormatError,
|
|
@@ -6,7 +8,6 @@ import {
|
|
|
6
8
|
LocalizedError,
|
|
7
9
|
ValidationError,
|
|
8
10
|
} from './errors';
|
|
9
|
-
import { omit } from './utils';
|
|
10
11
|
|
|
11
12
|
const INITIAL_TYPES = ['default', 'required', 'type', 'transform', 'empty'];
|
|
12
13
|
const REQUIRED_TYPES = ['default', 'required', 'missing'];
|
|
@@ -143,7 +144,6 @@ export default class Schema {
|
|
|
143
144
|
let details = [];
|
|
144
145
|
|
|
145
146
|
options = {
|
|
146
|
-
root: value,
|
|
147
147
|
...options,
|
|
148
148
|
...this.meta,
|
|
149
149
|
original: value,
|
|
@@ -194,7 +194,7 @@ export default class Schema {
|
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
196
|
* Appends another schema. [Link](https://github.com/bedrockio/yada#append)
|
|
197
|
-
* @returns {
|
|
197
|
+
* @returns {Schema}
|
|
198
198
|
*/
|
|
199
199
|
append(schema) {
|
|
200
200
|
const merged = this.clone(schema.meta);
|
|
@@ -257,6 +257,11 @@ export default class Schema {
|
|
|
257
257
|
return JSON.stringify(this.toOpenApi(), null, 2);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
get() {
|
|
261
|
+
const { name } = this.constructor;
|
|
262
|
+
throw new Error(`"get" not implemented by ${name}.`);
|
|
263
|
+
}
|
|
264
|
+
|
|
260
265
|
// Private
|
|
261
266
|
|
|
262
267
|
/**
|
package/src/array.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { omit } from 'lodash-es';
|
|
2
|
+
|
|
1
3
|
import Schema from './Schema';
|
|
2
4
|
import TypeSchema from './TypeSchema';
|
|
3
5
|
import { ArrayError, ElementError, LocalizedError } from './errors';
|
|
4
|
-
import { omit } from './utils';
|
|
5
6
|
|
|
6
7
|
class ArraySchema extends TypeSchema {
|
|
7
8
|
constructor(schemas) {
|
|
@@ -82,7 +83,7 @@ class ArraySchema extends TypeSchema {
|
|
|
82
83
|
{
|
|
83
84
|
length,
|
|
84
85
|
s,
|
|
85
|
-
}
|
|
86
|
+
},
|
|
86
87
|
);
|
|
87
88
|
}
|
|
88
89
|
});
|
package/src/date.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import validator from 'validator';
|
|
2
|
-
import { LocalizedError } from './errors';
|
|
3
2
|
|
|
4
3
|
import Schema from './Schema';
|
|
4
|
+
import { LocalizedError } from './errors';
|
|
5
5
|
|
|
6
6
|
class DateSchema extends Schema {
|
|
7
7
|
constructor() {
|
|
@@ -24,7 +24,6 @@ class DateSchema extends Schema {
|
|
|
24
24
|
return this.clone().assert('min', (date) => {
|
|
25
25
|
if (date < min) {
|
|
26
26
|
throw new LocalizedError('Must be after {date}.', {
|
|
27
|
-
// @ts-ignore
|
|
28
27
|
date: min.toISOString(),
|
|
29
28
|
});
|
|
30
29
|
}
|
|
@@ -39,7 +38,6 @@ class DateSchema extends Schema {
|
|
|
39
38
|
return this.clone().assert('max', (date) => {
|
|
40
39
|
if (date > max) {
|
|
41
40
|
throw new LocalizedError('Must be before {date}.', {
|
|
42
|
-
// @ts-ignore
|
|
43
41
|
date: max.toISOString(),
|
|
44
42
|
});
|
|
45
43
|
}
|
|
@@ -54,7 +52,6 @@ class DateSchema extends Schema {
|
|
|
54
52
|
return this.clone().assert('before', (date) => {
|
|
55
53
|
if (date >= max) {
|
|
56
54
|
throw new LocalizedError('Must be before {date}.', {
|
|
57
|
-
// @ts-ignore
|
|
58
55
|
date: max.toISOString(),
|
|
59
56
|
});
|
|
60
57
|
}
|
|
@@ -69,7 +66,6 @@ class DateSchema extends Schema {
|
|
|
69
66
|
return this.clone().assert('after', (date) => {
|
|
70
67
|
if (date <= min) {
|
|
71
68
|
throw new LocalizedError('Must be after {date}.', {
|
|
72
|
-
// @ts-ignore
|
|
73
69
|
date: min.toISOString(),
|
|
74
70
|
});
|
|
75
71
|
}
|
|
@@ -118,7 +114,7 @@ class DateSchema extends Schema {
|
|
|
118
114
|
if (typeof original !== 'number' && !options.default) {
|
|
119
115
|
throw new LocalizedError('Must be a timestamp in milliseconds.');
|
|
120
116
|
}
|
|
121
|
-
}
|
|
117
|
+
},
|
|
122
118
|
);
|
|
123
119
|
}
|
|
124
120
|
|
|
@@ -134,7 +130,7 @@ class DateSchema extends Schema {
|
|
|
134
130
|
} else {
|
|
135
131
|
return new Date(original * 1000);
|
|
136
132
|
}
|
|
137
|
-
}
|
|
133
|
+
},
|
|
138
134
|
);
|
|
139
135
|
}
|
|
140
136
|
|
package/src/index.js
CHANGED
|
@@ -7,10 +7,12 @@ import array from './array';
|
|
|
7
7
|
import tuple from './tuple';
|
|
8
8
|
|
|
9
9
|
import Schema from './Schema';
|
|
10
|
-
import { isSchema, isSchemaError } from './utils';
|
|
11
10
|
import { useLocalizer, getLocalizedMessages } from './localization';
|
|
12
11
|
import { LocalizedError } from './errors';
|
|
13
12
|
|
|
13
|
+
import { isSchema } from './Schema';
|
|
14
|
+
import { isSchemaError } from './errors';
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* Accepts anything.
|
|
16
18
|
*/
|
package/src/object.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import { set, pick, omit } from 'lodash-es';
|
|
2
|
+
|
|
1
3
|
import TypeSchema from './TypeSchema';
|
|
2
4
|
import { FieldError, LocalizedError } from './errors';
|
|
3
|
-
import { pick, omit } from './utils';
|
|
4
5
|
import Schema, { isSchema } from './Schema';
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const APPEND_ASSERTION_TYPES = ['required', 'type', 'custom'];
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @typedef {{ [key: string]: Schema } | {}} SchemaMap
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
class ObjectSchema extends TypeSchema {
|
|
13
|
-
constructor(
|
|
14
|
-
super(Object,
|
|
14
|
+
constructor(meta) {
|
|
15
|
+
super(Object, meta);
|
|
15
16
|
this.setup();
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -46,13 +47,13 @@ class ObjectSchema extends TypeSchema {
|
|
|
46
47
|
return result;
|
|
47
48
|
}
|
|
48
49
|
});
|
|
49
|
-
for (let [key, schema] of Object.entries(this.
|
|
50
|
+
for (let [key, schema] of Object.entries(this.export())) {
|
|
50
51
|
if (!isSchema(schema)) {
|
|
51
|
-
throw new Error(`Key "${key}" must be a schema
|
|
52
|
+
throw new Error(`Key "${key}" must be a schema.`);
|
|
52
53
|
}
|
|
53
54
|
this.assert('field', async (obj, options) => {
|
|
54
55
|
if (obj) {
|
|
55
|
-
const { path = [] } = options;
|
|
56
|
+
const { path = [], original } = options;
|
|
56
57
|
const { strip, required } = schema.meta;
|
|
57
58
|
const val = obj[key];
|
|
58
59
|
|
|
@@ -72,9 +73,13 @@ class ObjectSchema extends TypeSchema {
|
|
|
72
73
|
options = omit(options, 'message');
|
|
73
74
|
const result = await schema.validate(val, {
|
|
74
75
|
...options,
|
|
75
|
-
//
|
|
76
|
-
//
|
|
76
|
+
// The root object may have been transformed here
|
|
77
|
+
// by defaults or values returned by custom functions
|
|
78
|
+
// so re-pass it here.
|
|
77
79
|
root: obj,
|
|
80
|
+
// The original root represents the root object
|
|
81
|
+
// before it was transformed.
|
|
82
|
+
originalRoot: original,
|
|
78
83
|
});
|
|
79
84
|
if (result !== undefined) {
|
|
80
85
|
return {
|
|
@@ -91,78 +96,162 @@ class ObjectSchema extends TypeSchema {
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
getFields() {
|
|
95
|
-
return this.meta.fields || {};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
99
|
/**
|
|
99
|
-
*
|
|
100
|
+
* Gets the schema for the given field. Deep fields accept
|
|
101
|
+
* either a string using dot syntax or an array representing
|
|
102
|
+
* the path.
|
|
103
|
+
*
|
|
104
|
+
* @param {string|Array<string>} [path] The path of the field.
|
|
100
105
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
schema = arg;
|
|
106
|
-
} else if (arg instanceof Schema) {
|
|
107
|
-
// If the schema is of a different type then
|
|
108
|
-
// simply append it and don't merge fields.
|
|
109
|
-
return super.append(arg);
|
|
110
|
-
} else {
|
|
111
|
-
schema = new ObjectSchema(arg);
|
|
106
|
+
get(path) {
|
|
107
|
+
const { fields } = this.meta;
|
|
108
|
+
if (!fields) {
|
|
109
|
+
throw new Error('Cannot select field on an open object schema.');
|
|
112
110
|
}
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
...this.meta.fields,
|
|
116
|
-
...schema.meta.fields,
|
|
117
|
-
};
|
|
112
|
+
path = Array.isArray(path) ? path : path.split('.');
|
|
118
113
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
...schema.meta,
|
|
122
|
-
});
|
|
114
|
+
const [name, ...rest] = path;
|
|
115
|
+
const schema = fields[name];
|
|
123
116
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const { type } = assertion;
|
|
127
|
-
if (!BASE_ASSERTIONS.includes(type)) {
|
|
128
|
-
merged.pushAssertion(assertion);
|
|
129
|
-
}
|
|
117
|
+
if (!schema) {
|
|
118
|
+
throw new Error(`Cannot find field "${name}".`);
|
|
130
119
|
}
|
|
131
120
|
|
|
132
|
-
|
|
121
|
+
if (rest.length) {
|
|
122
|
+
return schema.get(rest);
|
|
123
|
+
} else {
|
|
124
|
+
return schema;
|
|
125
|
+
}
|
|
133
126
|
}
|
|
134
127
|
|
|
135
128
|
/**
|
|
129
|
+
* Returns the inner schema of an array field. This only makes
|
|
130
|
+
* sense if the array field holds a single schema, so all other
|
|
131
|
+
* scenarios will throw an error.
|
|
132
|
+
*
|
|
133
|
+
* @param {string|Array<string>} [path] The path of the field.
|
|
134
|
+
*/
|
|
135
|
+
unwind(path) {
|
|
136
|
+
path = Array.isArray(path) ? path.join('.') : path;
|
|
137
|
+
const field = this.get(path);
|
|
138
|
+
const { schemas } = field.meta;
|
|
139
|
+
if (!schemas) {
|
|
140
|
+
throw new Error(`Field "${path}" is not an array schema.`);
|
|
141
|
+
} else if (schemas.length !== 1) {
|
|
142
|
+
throw new Error(`Field "${path}" should contain only one schema.`);
|
|
143
|
+
}
|
|
144
|
+
return schemas[0];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Returns a new schema that only validates the selected fields.
|
|
149
|
+
*
|
|
136
150
|
* @param {...string} [names] Names to include.
|
|
137
151
|
*/
|
|
138
152
|
pick(...names) {
|
|
139
153
|
if (Array.isArray(names[0])) {
|
|
140
154
|
names = names[0];
|
|
141
155
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return new ObjectSchema(fields, {
|
|
145
|
-
...this.meta,
|
|
156
|
+
return new ObjectSchema({
|
|
157
|
+
fields: pick(this.meta.fields, names),
|
|
146
158
|
});
|
|
147
159
|
}
|
|
148
160
|
|
|
149
161
|
/**
|
|
162
|
+
* Returns a new schema that omits fields.
|
|
163
|
+
*
|
|
150
164
|
* @param {...string} [names] Names to exclude.
|
|
151
165
|
*/
|
|
152
166
|
omit(...names) {
|
|
153
167
|
if (Array.isArray(names[0])) {
|
|
154
168
|
names = names[0];
|
|
155
169
|
}
|
|
156
|
-
|
|
170
|
+
return new ObjectSchema({
|
|
171
|
+
fields: omit(this.meta.fields, names),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Augments the object schema to make fields required.
|
|
177
|
+
* Field names may be passed as an array or arguments.
|
|
178
|
+
* Field names may be deep using dot syntax.
|
|
179
|
+
*
|
|
180
|
+
* @param {...string} fields
|
|
181
|
+
* @param {Array<string>} fields
|
|
182
|
+
*/
|
|
183
|
+
require(...fields) {
|
|
184
|
+
if (!fields.length) {
|
|
185
|
+
throw new Error('No fields specified.');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (Array.isArray(fields[0])) {
|
|
189
|
+
fields = fields[0];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const update = {};
|
|
193
|
+
for (let field of fields) {
|
|
194
|
+
set(update, field, this.get(field).required());
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return this.append(update);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Returns the schema's fields as an object allowing them
|
|
202
|
+
* to be "spread" to create new schemas. Note that doing
|
|
203
|
+
* this will mean that custom and required assertions will
|
|
204
|
+
* not be preserved. Compare to {@link append} which
|
|
205
|
+
* preserves all assertions on the base schema.
|
|
206
|
+
*/
|
|
207
|
+
export() {
|
|
208
|
+
return this.meta.fields || {};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Appends another schema and returns a new one. Appended
|
|
213
|
+
* schemas may have nested fields. Note that custom and
|
|
214
|
+
* required assertions on the schemas are preserved. This
|
|
215
|
+
* means that if either object schema is required then the
|
|
216
|
+
* resulting merged schema will also be required. Compare
|
|
217
|
+
* to {@link export} which exports the fields as an object.
|
|
218
|
+
*
|
|
219
|
+
* @param {SchemaMap|Schema} arg Object or schema to append.
|
|
220
|
+
*/
|
|
221
|
+
append(arg) {
|
|
222
|
+
let meta;
|
|
223
|
+
let assertions = [...this.assertions];
|
|
224
|
+
|
|
225
|
+
if (arg instanceof Schema) {
|
|
226
|
+
meta = arg.meta;
|
|
227
|
+
assertions = [...assertions, ...arg.assertions];
|
|
228
|
+
} else {
|
|
229
|
+
meta = {
|
|
230
|
+
fields: arg,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
157
233
|
|
|
158
|
-
|
|
234
|
+
const fields = mergeFields(this.meta.fields, meta?.fields);
|
|
235
|
+
|
|
236
|
+
const schema = new ObjectSchema({
|
|
159
237
|
...this.meta,
|
|
238
|
+
...meta,
|
|
239
|
+
fields,
|
|
160
240
|
});
|
|
241
|
+
|
|
242
|
+
for (let assertion of assertions) {
|
|
243
|
+
const { type } = assertion;
|
|
244
|
+
if (APPEND_ASSERTION_TYPES.includes(type)) {
|
|
245
|
+
schema.pushAssertion(assertion);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return schema;
|
|
161
250
|
}
|
|
162
251
|
|
|
163
252
|
toOpenApi(extra) {
|
|
164
253
|
const properties = {};
|
|
165
|
-
for (let [key, schema] of Object.entries(this.
|
|
254
|
+
for (let [key, schema] of Object.entries(this.export())) {
|
|
166
255
|
properties[key] = schema.toOpenApi(extra);
|
|
167
256
|
}
|
|
168
257
|
return {
|
|
@@ -196,6 +285,24 @@ function expandDotProperties(obj) {
|
|
|
196
285
|
return result;
|
|
197
286
|
}
|
|
198
287
|
|
|
288
|
+
function mergeFields(aFields, bFields) {
|
|
289
|
+
if (!aFields || !bFields) {
|
|
290
|
+
return aFields || bFields;
|
|
291
|
+
}
|
|
292
|
+
const result = { ...aFields };
|
|
293
|
+
for (let key of Object.keys(bFields)) {
|
|
294
|
+
const aSchema = aFields[key];
|
|
295
|
+
const bSchema = bFields[key];
|
|
296
|
+
|
|
297
|
+
if (aSchema instanceof ObjectSchema) {
|
|
298
|
+
result[key] = aSchema.append(bSchema);
|
|
299
|
+
} else {
|
|
300
|
+
result[key] = bSchema;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
199
306
|
/**
|
|
200
307
|
* Creates an [object schema](https://github.com/bedrockio/yada#object).
|
|
201
308
|
*
|
|
@@ -204,5 +311,7 @@ function expandDotProperties(obj) {
|
|
|
204
311
|
* empty object is passed then no keys will be allowed.
|
|
205
312
|
*/
|
|
206
313
|
export default function (map) {
|
|
207
|
-
return new ObjectSchema(
|
|
314
|
+
return new ObjectSchema({
|
|
315
|
+
fields: map,
|
|
316
|
+
});
|
|
208
317
|
}
|
package/src/password.js
CHANGED
|
@@ -38,22 +38,22 @@ export function validateLength(expected) {
|
|
|
38
38
|
|
|
39
39
|
export const validateLowercase = validateRegex(
|
|
40
40
|
LOWER_REG,
|
|
41
|
-
'Must contain at least {length} lowercase character{s}.'
|
|
41
|
+
'Must contain at least {length} lowercase character{s}.',
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
export const validateUppercase = validateRegex(
|
|
45
45
|
UPPER_REG,
|
|
46
|
-
'Must contain at least {length} uppercase character{s}.'
|
|
46
|
+
'Must contain at least {length} uppercase character{s}.',
|
|
47
47
|
);
|
|
48
48
|
|
|
49
49
|
export const validateNumbers = validateRegex(
|
|
50
50
|
NUMBER_REG,
|
|
51
|
-
'Must contain at least {length} number{s}.'
|
|
51
|
+
'Must contain at least {length} number{s}.',
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
export const validateSymbols = validateRegex(
|
|
55
55
|
SYMBOL_REG,
|
|
56
|
-
'Must contain at least {length} symbol{s}.'
|
|
56
|
+
'Must contain at least {length} symbol{s}.',
|
|
57
57
|
);
|
|
58
58
|
|
|
59
59
|
function validateRegex(reg, message) {
|
package/src/string.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import validator from 'validator';
|
|
2
|
+
|
|
2
3
|
import TypeSchema from './TypeSchema';
|
|
3
4
|
import { LocalizedError } from './errors';
|
|
4
5
|
import {
|
|
@@ -412,7 +413,7 @@ class StringSchema extends TypeSchema {
|
|
|
412
413
|
return;
|
|
413
414
|
}
|
|
414
415
|
fn(val, options);
|
|
415
|
-
}
|
|
416
|
+
},
|
|
416
417
|
);
|
|
417
418
|
}
|
|
418
419
|
|
package/types/Schema.d.ts
CHANGED
|
@@ -68,9 +68,9 @@ export default class Schema {
|
|
|
68
68
|
clone(meta: any): this;
|
|
69
69
|
/**
|
|
70
70
|
* Appends another schema. [Link](https://github.com/bedrockio/yada#append)
|
|
71
|
-
* @returns {
|
|
71
|
+
* @returns {Schema}
|
|
72
72
|
*/
|
|
73
|
-
append(schema: any):
|
|
73
|
+
append(schema: any): Schema;
|
|
74
74
|
toOpenApi(extra: any): any;
|
|
75
75
|
getAnyType(): {
|
|
76
76
|
type: string[];
|
|
@@ -85,6 +85,7 @@ export default class Schema {
|
|
|
85
85
|
};
|
|
86
86
|
expandExtra(extra?: {}): {};
|
|
87
87
|
inspect(): string;
|
|
88
|
+
get(): void;
|
|
88
89
|
/**
|
|
89
90
|
* @returns {this}
|
|
90
91
|
*/
|
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":"AAoaA,kDAEC;AAxZD;IACE,uBAGC;IAFC,kBAAoB;IACpB,SAAgB;IAKlB;;OAEG;IACH,YAFa,IAAI,CAQhB;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;;;OAGG;IACH,YAFa,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,iDAwCC;IAED;;OAEG;IACH,kBAFa,IAAI,CAOhB;IAED;;;OAGG;IACH,qBAFa,MAAM,CAMlB;IAED,2BAYC;IAED;;MAOC;IAED;;;;MASC;IAED;;MAOC;IAED,4BAMC;IAED,kBAEC;IAED,YAGC;IAID;;OAEG;IACH,kCAFa,IAAI,CAiDhB;IAED;;OAEG;IACH,4BAFa,IAAI,CAUhB;IAED,oCAKC;IAED,gEAQC;IAED;;OAEG;IACH,oBAFa,IAAI,CAShB;IAED,gCAGC;IAED,qEAYC;IAED,qBAsCC;CACF"}
|
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":"AAqIA;;;;;;;GAOG;AACH,8CALc,MAAM,EAAA,eAUnB;mBAhJkB,UAAU;AAI7B;IACE,0BAGC;IAED,cAsCC;IAED,0BAUC;IAED,uBAUC;IAED,uBAaC;IAED,eAaC;IAED,mBAEC;CAuBF;uBAhIsB,cAAc"}
|
package/types/date.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../src/date.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../src/date.js"],"names":[],"mappings":"AAgKA;;GAEG;AACH,+CAEC;AAhKD;IACE,cAUC;IAED;;OAEG;IACH,SAFW,MAAM,GAAC,MAAM,GAAC,IAAI,QAW5B;IAED;;OAEG;IACH,SAFW,MAAM,GAAC,MAAM,GAAC,IAAI,QAW5B;IAED;;OAEG;IACH,YAFW,MAAM,GAAC,MAAM,GAAC,IAAI,QAW5B;IAED;;OAEG;IACH,WAFW,MAAM,GAAC,MAAM,GAAC,IAAI,QAW5B;IAED,aAOC;IAED,eAOC;IAED;;OAEG;IACH,aAFW,MAAM,GAAG,WAAW,QAa9B;IAED,kBAUC;IAED,aAcC;CAwBF;mBA5JkB,UAAU"}
|
package/types/index.d.ts
CHANGED
|
@@ -41,8 +41,8 @@ export function reject(...args: any[]): Schema;
|
|
|
41
41
|
* @param {Function} fn
|
|
42
42
|
*/
|
|
43
43
|
export function custom(fn: Function): Schema;
|
|
44
|
-
import { isSchema } from './
|
|
45
|
-
import { isSchemaError } from './
|
|
44
|
+
import { isSchema } from './Schema';
|
|
45
|
+
import { isSchemaError } from './errors';
|
|
46
46
|
import { useLocalizer } from './localization';
|
|
47
47
|
import { getLocalizedMessages } from './localization';
|
|
48
48
|
import { LocalizedError } from './errors';
|
package/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;kBAKkB,SAAS;oBAHP,WAAW;iBACd,QAAQ;mBAFN,UAAU;mBAGV,UAAU;mBAJV,UAAU;kBAMX,SAAS;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;kBAKkB,SAAS;oBAHP,WAAW;iBACd,QAAQ;mBAFN,UAAU;mBAGV,UAAU;mBAJV,UAAU;kBAMX,SAAS;AAS3B;;GAEG;AACH,8BAEC;AAED;;GAEG;AACH,8CAEC;AAED;;GAEG;AACH,+CAEC;AAED;;;GAGG;AACH,6CAEC;yBA9BwB,UAAU;8BACL,UAAU;6BAJW,gBAAgB;qCAAhB,gBAAgB;+BACpC,UAAU;mBAFtB,UAAU"}
|
package/types/object.d.ts
CHANGED
|
@@ -13,20 +13,64 @@ export type SchemaMap = {
|
|
|
13
13
|
* @typedef {{ [key: string]: Schema } | {}} SchemaMap
|
|
14
14
|
*/
|
|
15
15
|
declare class ObjectSchema extends TypeSchema {
|
|
16
|
+
constructor(meta: any);
|
|
16
17
|
setup(): void;
|
|
17
|
-
getFields(): any;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Gets the schema for the given field. Deep fields accept
|
|
20
|
+
* either a string using dot syntax or an array representing
|
|
21
|
+
* the path.
|
|
22
|
+
*
|
|
23
|
+
* @param {string|Array<string>} [path] The path of the field.
|
|
20
24
|
*/
|
|
21
|
-
|
|
25
|
+
get(path?: string | Array<string>): any;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the inner schema of an array field. This only makes
|
|
28
|
+
* sense if the array field holds a single schema, so all other
|
|
29
|
+
* scenarios will throw an error.
|
|
30
|
+
*
|
|
31
|
+
* @param {string|Array<string>} [path] The path of the field.
|
|
32
|
+
*/
|
|
33
|
+
unwind(path?: string | Array<string>): any;
|
|
22
34
|
/**
|
|
35
|
+
* Returns a new schema that only validates the selected fields.
|
|
36
|
+
*
|
|
23
37
|
* @param {...string} [names] Names to include.
|
|
24
38
|
*/
|
|
25
39
|
pick(...names?: string[]): ObjectSchema;
|
|
26
40
|
/**
|
|
41
|
+
* Returns a new schema that omits fields.
|
|
42
|
+
*
|
|
27
43
|
* @param {...string} [names] Names to exclude.
|
|
28
44
|
*/
|
|
29
45
|
omit(...names?: string[]): ObjectSchema;
|
|
46
|
+
/**
|
|
47
|
+
* Augments the object schema to make fields required.
|
|
48
|
+
* Field names may be passed as an array or arguments.
|
|
49
|
+
* Field names may be deep using dot syntax.
|
|
50
|
+
*
|
|
51
|
+
* @param {...string} fields
|
|
52
|
+
* @param {Array<string>} fields
|
|
53
|
+
*/
|
|
54
|
+
require(...fields: string[]): ObjectSchema;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the schema's fields as an object allowing them
|
|
57
|
+
* to be "spread" to create new schemas. Note that doing
|
|
58
|
+
* this will mean that custom and required assertions will
|
|
59
|
+
* not be preserved. Compare to {@link append} which
|
|
60
|
+
* preserves all assertions on the base schema.
|
|
61
|
+
*/
|
|
62
|
+
export(): any;
|
|
63
|
+
/**
|
|
64
|
+
* Appends another schema and returns a new one. Appended
|
|
65
|
+
* schemas may have nested fields. Note that custom and
|
|
66
|
+
* required assertions on the schemas are preserved. This
|
|
67
|
+
* means that if either object schema is required then the
|
|
68
|
+
* resulting merged schema will also be required. Compare
|
|
69
|
+
* to {@link export} which exports the fields as an object.
|
|
70
|
+
*
|
|
71
|
+
* @param {SchemaMap|Schema} arg Object or schema to append.
|
|
72
|
+
*/
|
|
73
|
+
append(arg: SchemaMap | Schema): ObjectSchema;
|
|
30
74
|
}
|
|
31
75
|
import Schema from './Schema';
|
|
32
76
|
import TypeSchema from './TypeSchema';
|
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":"AAiTA;;;;;;GAMG;AACH,uCAJW,SAAS,gBAQnB;wBAnTY;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,EAAE;AAD3C;;GAEG;AAEH;IACE,uBAGC;IAED,cA8EC;IAED;;;;;;OAMG;IACH,WAFW,MAAM,GAAC,KAAK,CAAC,MAAM,CAAC,OAsB9B;IAED;;;;;;OAMG;IACH,cAFW,MAAM,GAAC,KAAK,CAAC,MAAM,CAAC,OAY9B;IAED;;;;OAIG;IACH,gBAFc,MAAM,EAAA,gBASnB;IAED;;;;OAIG;IACH,gBAFc,MAAM,EAAA,gBASnB;IAED;;;;;;;OAOG;IACH,mBAHc,MAAM,EAAA,gBAkBnB;IAED;;;;;;OAMG;IACH,cAEC;IAED;;;;;;;;;OASG;IACH,YAFW,SAAS,GAAC,MAAM,gBA+B1B;CAcF;mBAnQgC,UAAU;uBAFpB,cAAc"}
|
package/types/string.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../src/string.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../src/string.js"],"names":[],"mappings":"AAibA;;GAEG;AACH,iDAEC;AA/ZD;IACE,cAoBC;IAED;;OAEG;IACH,YAFa,IAAI,CAQhB;IAED;;OAEG;IACH,eAFW,MAAM,QAUhB;IAED;;OAEG;IACH,YAFW,MAAM,QAUhB;IAED;;OAEG;IACH,YAFW,MAAM,QAUhB;IAED,aAIC;IAED;;OAEG;IACH,mBAFW,OAAO,QAYjB;IAED;;OAEG;IACH,mBAFW,OAAO,QAYjB;IAED;;OAEG;IACH,WAFW,MAAM,QAahB;IAED,cAMC;IAED,uBASC;IAED,YAMC;IAED,YAMC;IAED,aAMC;IAED,cAMC;IAED;;;OAGG;IACH,iBAFG;QAA0B,OAAO,GAAzB,OAAO;KAAmB,QAQpC;IAED,mBAMC;IAED,WAMC;IAED,gBAMC;IAED,eAMC;IAED,YAMC;IAED,aAOC;IAED,eAMC;IAED;;OAEG;IACH,oBAFW,MAAM,QAQhB;IAED,gBAMC;IAED;;;;;;;OAOG;IACH,mBANG;QAAyB,SAAS,GAA1B,MAAM;QACW,UAAU,GAA3B,MAAM;QACW,UAAU,GAA3B,MAAM;QACW,YAAY,GAA7B,MAAM;QACW,YAAY,GAA7B,MAAM;KAAwB,QA+BxC;IAED;;;;;;;;;;;OAWG;IACH,cAVG;QAA0B,gBAAgB,GAAlC,OAAO;QACW,sBAAsB,GAAxC,OAAO;QACW,YAAY,GAA9B,OAAO;QACW,YAAY,GAA9B,OAAO;QACW,4BAA4B,GAA9C,OAAO;QACW,eAAe,GAAjC,OAAO;QACW,sBAAsB,GAAxC,OAAO;QACW,eAAe,GAAjC,OAAO;QACY,SAAS,GAA5B,MAAM,EAAE;KAAqB,QAQvC;IAED;;;;;;;;OAQG;IACH,iBAPG;QAA0B,WAAW,GAA7B,OAAO;QACW,iBAAiB,GAAnC,OAAO;QACW,kBAAkB,GAApC,OAAO;QACW,iBAAiB,GAAnC,OAAO;QACW,cAAc,GAAhC,OAAO;QACW,iBAAiB,GAAnC,OAAO;KAAmC,QAQpD;IAED;;OAEG;IACH,eAFW,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,QAQ3B;IAED,YAMC;IAED,YAMC;IAED,cAMC;IAED,cAMC;IAED,iCAWC;CAcF;uBA7asB,cAAc"}
|
package/.prettierrc.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('@bedrockio/prettier-config');
|
package/dist/cjs/utils.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
Object.defineProperty(exports, "isSchema", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: function () {
|
|
9
|
-
return _Schema.isSchema;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
Object.defineProperty(exports, "isSchemaError", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _errors.isSchemaError;
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
exports.omit = omit;
|
|
19
|
-
exports.pick = pick;
|
|
20
|
-
var _Schema = require("./Schema");
|
|
21
|
-
var _errors = require("./errors");
|
|
22
|
-
function pick(obj, keys) {
|
|
23
|
-
const result = {};
|
|
24
|
-
for (let key of keys) {
|
|
25
|
-
result[key] = obj[key];
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
function omit(obj, keys) {
|
|
30
|
-
const result = {};
|
|
31
|
-
for (let key of Object.keys(obj || {})) {
|
|
32
|
-
if (!keys.includes(key)) {
|
|
33
|
-
result[key] = obj[key];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return result;
|
|
37
|
-
}
|
package/src/utils.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export { isSchema } from './Schema';
|
|
2
|
-
export { isSchemaError } from './errors';
|
|
3
|
-
|
|
4
|
-
export function pick(obj, keys) {
|
|
5
|
-
const result = {};
|
|
6
|
-
for (let key of keys) {
|
|
7
|
-
result[key] = obj[key];
|
|
8
|
-
}
|
|
9
|
-
return result;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function omit(obj, keys) {
|
|
13
|
-
const result = {};
|
|
14
|
-
for (let key of Object.keys(obj || {})) {
|
|
15
|
-
if (!keys.includes(key)) {
|
|
16
|
-
result[key] = obj[key];
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return result;
|
|
20
|
-
}
|