schiva 0.1.0 → 0.2.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/README.md +183 -14
- package/package.json +14 -2
- package/src/index.js +148 -10
- package/src/schema.js +10 -5
- package/src/types/all-of.js +2 -2
- package/src/types/any-of.js +2 -2
- package/src/types/array-of.js +20 -6
- package/src/types/conditional.js +4 -4
- package/src/types/not.js +2 -2
- package/src/types/one-of.js +2 -2
- package/src/types/ref.js +2 -2
- package/src/types/validate-type.js +38 -0
- package/src/types/when.js +2 -2
package/README.md
CHANGED
|
@@ -1,20 +1,56 @@
|
|
|
1
1
|
# schiva
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/schiva)
|
|
4
|
+
[](https://github.com/jesus-seijas-sp/schiva/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](package.json)
|
|
7
|
+
|
|
3
8
|
Fast schema validation for JavaScript. Describe data with a small schema DSL or with JSON Schema (draft-07), and
|
|
4
9
|
schiva compiles it into a JavaScript function generated for that schema, so validating is several times faster than
|
|
5
10
|
walking the schema for every value.
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
+
**Documentation: [schiva.js.org](https://schiva.js.org)**
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
|
|
16
|
+
- [Features](#features)
|
|
17
|
+
- [Install](#install)
|
|
18
|
+
- [Getting started](#getting-started)
|
|
19
|
+
- [Modes](#modes)
|
|
20
|
+
- [Compile once](#compile-once)
|
|
21
|
+
- [Schema DSL](#schema-dsl)
|
|
22
|
+
- [JSON Schema](#json-schema)
|
|
23
|
+
- [Errors](#errors)
|
|
24
|
+
- [Types of your own](#types-of-your-own)
|
|
25
|
+
- [Security considerations](#security-considerations)
|
|
26
|
+
- [Performance](#performance)
|
|
27
|
+
- [FAQ](#faq)
|
|
28
|
+
- [Contributing](#contributing)
|
|
29
|
+
- [License](#license)
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Two schema languages, one engine**: a small DSL (`String()`, `Integer({ min: 18 })`...) and JSON Schema draft-07
|
|
34
|
+
compile to the same types and the same generated code.
|
|
35
|
+
- **Complete draft-07**: passes the whole [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
|
|
36
|
+
for draft-07 (929 of 929 tests), including `$ref` with JSON pointers, `$id` base URIs, anchors, recursive schemas
|
|
37
|
+
and references to other documents.
|
|
38
|
+
- **Readable errors** with the path of each field: `lines[12].price must be a number`.
|
|
39
|
+
- **Three modes**: every error, the first error only, or just `true`/`false`.
|
|
40
|
+
- **Fast**: faster than ajv in every [benchmark](#performance) we run, and about 50 times faster at compiling.
|
|
41
|
+
- **Strict**: unknown or unsupported keywords throw when compiling instead of being silently ignored.
|
|
42
|
+
- **No dependencies**, CommonJS and ESM, Node.js 18 and later, and browsers through any bundler.
|
|
43
|
+
- **Extensible**: classes of your own with a `validate()` method work inside compiled schemas.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
12
46
|
|
|
13
47
|
```sh
|
|
14
48
|
npm install schiva
|
|
15
49
|
```
|
|
16
50
|
|
|
17
|
-
##
|
|
51
|
+
## Getting started
|
|
52
|
+
|
|
53
|
+
With the schema DSL:
|
|
18
54
|
|
|
19
55
|
```js
|
|
20
56
|
const { ClosedSchema, String, Integer, ArrayOf } = require('schiva');
|
|
@@ -36,7 +72,7 @@ validatePerson({ id: 1, age: 10, extra: 1 });
|
|
|
36
72
|
With JSON Schema:
|
|
37
73
|
|
|
38
74
|
```js
|
|
39
|
-
|
|
75
|
+
import { compileJsonSchema } from 'schiva';
|
|
40
76
|
|
|
41
77
|
const validate = compileJsonSchema({
|
|
42
78
|
type: 'object',
|
|
@@ -48,6 +84,8 @@ validate({ id: 'AB' }); // []
|
|
|
48
84
|
validate({ id: 'ab' }); // ['id does not match the required pattern']
|
|
49
85
|
```
|
|
50
86
|
|
|
87
|
+
`require('schiva')` and `import { ... } from 'schiva'` export the same names.
|
|
88
|
+
|
|
51
89
|
## Modes
|
|
52
90
|
|
|
53
91
|
`compile(options)` (on schemas and on every type) and `compileJsonSchema(json, options)` return a function:
|
|
@@ -58,6 +96,14 @@ validate({ id: 'ab' }); // ['id does not match the required pattern']
|
|
|
58
96
|
| `{ allErrors: false }` | only the first error message, `[]` when valid | one message is enough; stops at the first failing check |
|
|
59
97
|
| `{ errors: false }` | `true` or `false` | only validity matters; builds no messages |
|
|
60
98
|
|
|
99
|
+
```js
|
|
100
|
+
const isPerson = person.compile({ errors: false });
|
|
101
|
+
|
|
102
|
+
if (!isPerson(body)) {
|
|
103
|
+
// reject the request
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
61
107
|
Schemas and types also have `validate(value)`, which checks without compiling (10 to 25 times slower), and
|
|
62
108
|
`isValid(value)`.
|
|
63
109
|
|
|
@@ -68,27 +114,43 @@ The compiled function is a snapshot of the schema: changes made to the schema or
|
|
|
68
114
|
again if it changes. Compiling takes about 0.1 ms for a schema of moderate size, so compile when the program starts
|
|
69
115
|
(or cache the function), not for every value.
|
|
70
116
|
|
|
117
|
+
```js
|
|
118
|
+
// Good: compiled once, reused for every request.
|
|
119
|
+
const validateOrder = orderSchema.compile();
|
|
120
|
+
app.post('/orders', (req, res) => {
|
|
121
|
+
const errors = validateOrder(req.body);
|
|
122
|
+
if (errors.length) return res.status(400).json({ errors });
|
|
123
|
+
// ...
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
71
127
|
The generated code uses `new Function`, so it does not run where code generation is forbidden (for example with a
|
|
72
|
-
strict Content Security Policy).
|
|
128
|
+
strict Content Security Policy). See [Security considerations](#security-considerations).
|
|
73
129
|
|
|
74
130
|
## Schema DSL
|
|
75
131
|
|
|
76
132
|
A `Schema` takes an object whose keys are the expected properties. Plain objects inside it become nested schemas.
|
|
77
133
|
|
|
78
134
|
```js
|
|
79
|
-
const { Schema, String, Float, Enum } = require('schiva');
|
|
135
|
+
const { Schema, String, Float, Enum, ArrayOf, Integer } = require('schiva');
|
|
80
136
|
|
|
81
137
|
const order = new Schema(
|
|
82
138
|
{
|
|
83
139
|
id: String({ pattern: /^ORD-\d{8}$/ }),
|
|
84
140
|
status: Enum({ options: ['draft', 'placed'] }),
|
|
85
141
|
customer: { name: String({ min: 1 }), email: String({ isMandatory: false }) },
|
|
142
|
+
lines: ArrayOf({ type: { sku: String(), qty: Integer({ min: 1 }) }, min: 1 }),
|
|
86
143
|
total: Float({ min: 0 }),
|
|
87
144
|
},
|
|
88
145
|
{ isOpen: false }
|
|
89
146
|
);
|
|
90
147
|
```
|
|
91
148
|
|
|
149
|
+
Wherever a type is expected (`ArrayOf`, `AnyOf`, `Not`, `additionalType`...), a plain object stands for
|
|
150
|
+
`new Schema(object)`, as `lines` shows above. That schema is open even inside a `ClosedSchema`; write
|
|
151
|
+
`new ClosedSchema({ ... })` to reject unknown keys there too. A value that is neither a type nor an object of types
|
|
152
|
+
throws when the type is built.
|
|
153
|
+
|
|
92
154
|
Every type takes `isMandatory` (default `true`: `undefined` is an error) and `isNullable` (default `false`: `null` is
|
|
93
155
|
an error), and has `.optional()`, `.required()`, `.nullable()` and `.notNull()`.
|
|
94
156
|
|
|
@@ -111,11 +173,27 @@ Schema options: `isOpen` (default `true`; `ClosedSchema` sets it to `false` and
|
|
|
111
173
|
`dependencies` (`[{ key, required: [...] }]` or `[{ key, type }]`), `isMandatory`, `isNullable`.
|
|
112
174
|
|
|
113
175
|
Short helpers are also exported: `str`, `int`, `float`, `bool`, `arrOf`, `obj`, `any`, `anyOf`, `allOf`, `oneOf`,
|
|
114
|
-
`not`, `enumt`, and optional versions prefixed with `o` (`ostr`, `oint`...).
|
|
176
|
+
`not`, `enumt`, and optional versions prefixed with `o` (`ostr`, `oint`...). `arrOf(String())` and
|
|
177
|
+
`arrOf({ sku: String() })` take the type of the elements; `arrOf({ type, min, ... })` takes the options.
|
|
115
178
|
|
|
116
179
|
Declared keys are read as own properties only, so `{}.toString` or a key added to `Object.prototype` never counts as
|
|
117
180
|
present.
|
|
118
181
|
|
|
182
|
+
### Recursive schemas
|
|
183
|
+
|
|
184
|
+
Create the `Ref` first and point it at the schema once the schema exists:
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
const { Schema, Integer, ArrayOf, Ref } = require('schiva');
|
|
188
|
+
|
|
189
|
+
const child = Ref();
|
|
190
|
+
const node = new Schema({ value: Integer(), children: ArrayOf({ type: child, isMandatory: false }) });
|
|
191
|
+
child.target = node;
|
|
192
|
+
|
|
193
|
+
node.compile()({ value: 1, children: [{ value: 2, children: [{ value: 'x' }] }] });
|
|
194
|
+
// ['children[0].children[0].value must be a number']
|
|
195
|
+
```
|
|
196
|
+
|
|
119
197
|
## JSON Schema
|
|
120
198
|
|
|
121
199
|
`fromJsonSchema(json, options)` converts a JSON Schema (draft-07) into the same types, and
|
|
@@ -123,6 +201,19 @@ present.
|
|
|
123
201
|
`definitions` and `$ref` (JSON pointers, `$id` base URIs and anchors, recursive schemas). String lengths count Unicode
|
|
124
202
|
code points, as the specification says.
|
|
125
203
|
|
|
204
|
+
A keyword schiva does not know throws when compiling, with the place where it was found:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
compileJsonSchema({ type: 'string', maxLenght: 10 });
|
|
208
|
+
// Error: Unsupported JSON Schema keyword "maxLenght" at #
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Annotations (`title`, `description`, `default`, `examples`, `$comment`, `readOnly`, `writeOnly`, `deprecated`) are
|
|
212
|
+
accepted and ignored. So is **`format`**: draft-07 makes checking it optional, and schiva does not check it. Use
|
|
213
|
+
`pattern`, or a [type of your own](#types-of-your-own), for values such as emails or dates.
|
|
214
|
+
|
|
215
|
+
### Several documents
|
|
216
|
+
|
|
126
217
|
References to other documents resolve against the documents given in `options.schemas`, as `{ uri: schema }` or as an
|
|
127
218
|
array of schemas with `$id`. Nothing is loaded from the network, and a reference that cannot be resolved throws when
|
|
128
219
|
compiling:
|
|
@@ -136,16 +227,68 @@ const validateOrder = compileJsonSchema(orderSchema, {
|
|
|
136
227
|
To validate schemas against the draft-07 meta-schema, register it in `schemas` (ajv ships a copy as
|
|
137
228
|
`ajv/dist/refs/json-schema-draft-07.json`).
|
|
138
229
|
|
|
230
|
+
## Errors
|
|
231
|
+
|
|
232
|
+
Errors are plain strings that start with the path of the field, so they can be logged or returned as they are:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
[
|
|
236
|
+
'customer.name is mandatory',
|
|
237
|
+
'lines[1].sku must be a string',
|
|
238
|
+
'lines[1].price must be at least 0',
|
|
239
|
+
'Unexpected key: extra',
|
|
240
|
+
];
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
A value checked on its own (not inside a schema) is called `Value`: `Integer().compile()('x')` returns
|
|
244
|
+
`['Value must be a number']`.
|
|
245
|
+
|
|
139
246
|
## Types of your own
|
|
140
247
|
|
|
141
248
|
Classes that extend `ValidateType` (or a built-in type) with their own `validate()` or `isValid()` keep working when
|
|
142
|
-
compiled: the generated code calls them for their part of the schema.
|
|
249
|
+
compiled: the generated code calls them for their part of the schema. `validate()` returns `undefined` when the value
|
|
250
|
+
is valid and an error message otherwise.
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
const { Schema, ValidateType } = require('schiva');
|
|
254
|
+
|
|
255
|
+
class Even extends ValidateType {
|
|
256
|
+
validate(value, fieldName = 'Value') {
|
|
257
|
+
const presence = super.validate(value, fieldName); // isMandatory and isNullable
|
|
258
|
+
if (presence !== undefined || value === undefined || value === null) return presence;
|
|
259
|
+
return value % 2 === 0 ? undefined : `${fieldName} must be even`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
new Schema({ n: new Even() }).compile()({ n: 3 }); // ['n must be even']
|
|
264
|
+
```
|
|
143
265
|
|
|
144
|
-
##
|
|
266
|
+
## Security considerations
|
|
267
|
+
|
|
268
|
+
- **Schemas are code.** Compiling turns a schema into JavaScript, so treat schemas like the code of your program:
|
|
269
|
+
compile schemas you wrote or trust, not schemas sent by users. A large or deeply nested schema is slow to compile
|
|
270
|
+
and to validate.
|
|
271
|
+
- **Regular expressions.** `pattern`, `patternProperties` and `String({ pattern })` run the regular expression you
|
|
272
|
+
give them on the data. A badly written one can take exponential time on some inputs
|
|
273
|
+
([ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)). Keep them simple,
|
|
274
|
+
and limit the length of strings (`maxLength`, `String({ max })`) before matching long input.
|
|
275
|
+
- **Every error or the first one.** Collecting every error keeps checking after the first failure, so an invalid
|
|
276
|
+
value costs more. For untrusted input where one message is enough, use `{ allErrors: false }` or `{ errors: false }`.
|
|
277
|
+
- **Large inputs.** `uniqueItems`/`unique` compares items with each other, so limit the size of arrays
|
|
278
|
+
(`maxItems`, `ArrayOf({ max })`) that come from outside.
|
|
279
|
+
- **Content Security Policy.** The generated code is created with `new Function`, which needs `'unsafe-eval'` in the
|
|
280
|
+
`script-src` of a Content Security Policy. Where that is not allowed, use `validate()` and `isValid()`, which do not
|
|
281
|
+
generate code.
|
|
282
|
+
- **Circular data.** Values that contain themselves (`a.self = a`) are not supported.
|
|
283
|
+
|
|
284
|
+
Report security problems privately through [GitHub security advisories](https://github.com/jesus-seijas-sp/schiva/security/advisories/new),
|
|
285
|
+
not in public issues.
|
|
286
|
+
|
|
287
|
+
## Performance
|
|
145
288
|
|
|
146
289
|
Compared with ajv and the other validators of
|
|
147
290
|
[json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark), each measurement in its own process
|
|
148
|
-
(see `bench/`):
|
|
291
|
+
(see [`bench/`](bench)):
|
|
149
292
|
|
|
150
293
|
| | schiva | ajv | @exodus/schemasafe |
|
|
151
294
|
|---|---|---|---|
|
|
@@ -161,6 +304,32 @@ pnpm run bench # every validator in its own process (about 8 minutes)
|
|
|
161
304
|
pnpm run bench:quick # every validator in one process (about a minute)
|
|
162
305
|
```
|
|
163
306
|
|
|
307
|
+
## FAQ
|
|
308
|
+
|
|
309
|
+
**Should I use the DSL or JSON Schema?** Use JSON Schema when the schema is shared with other languages or tools
|
|
310
|
+
(OpenAPI, forms, other services). Use the DSL when the schema lives in your JavaScript code: it is shorter, and
|
|
311
|
+
`RegExp` patterns and types of your own fit in directly. Both compile to the same code, so the speed is the same.
|
|
312
|
+
|
|
313
|
+
**Why are errors strings and not objects?** Most errors end up in a log or an HTTP response. Strings with the path in
|
|
314
|
+
front are ready for that, and building them is cheap. If you only need to know whether a value is valid, use
|
|
315
|
+
`{ errors: false }`.
|
|
316
|
+
|
|
317
|
+
**Does it support draft 2019-09 or 2020-12?** Not yet: schiva implements draft-07. Keywords from later drafts, such as
|
|
318
|
+
`unevaluatedProperties` or `$defs`, throw when compiling.
|
|
319
|
+
|
|
320
|
+
**Does it check `format`?** No, see [JSON Schema](#json-schema).
|
|
321
|
+
|
|
322
|
+
**Does it include TypeScript types?** Not yet.
|
|
323
|
+
|
|
324
|
+
## Contributing
|
|
325
|
+
|
|
326
|
+
Issues and pull requests are welcome at [github.com/jesus-seijas-sp/schiva](https://github.com/jesus-seijas-sp/schiva).
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
pnpm install
|
|
330
|
+
pnpm test # ESLint and Jest with coverage
|
|
331
|
+
```
|
|
332
|
+
|
|
164
333
|
## License
|
|
165
334
|
|
|
166
|
-
MIT
|
|
335
|
+
[MIT](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "schiva",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Fast schema validation for JavaScript: a schema DSL and JSON Schema (draft-07), compiled to generated code",
|
|
5
|
+
"homepage": "https://schiva.js.org",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jesus-seijas-sp/schiva.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/jesus-seijas-sp/schiva/issues"
|
|
12
|
+
},
|
|
5
13
|
"main": "./src/index.js",
|
|
6
14
|
"type": "commonjs",
|
|
7
15
|
"files": [
|
|
@@ -14,7 +22,11 @@
|
|
|
14
22
|
"json-schema",
|
|
15
23
|
"draft-07",
|
|
16
24
|
"compiler",
|
|
17
|
-
"fast"
|
|
25
|
+
"fast",
|
|
26
|
+
"jsonschema",
|
|
27
|
+
"schema-validation",
|
|
28
|
+
"dsl",
|
|
29
|
+
"ajv-alternative"
|
|
18
30
|
],
|
|
19
31
|
"author": "Jesús Seijas <jseijas@gmail.com>",
|
|
20
32
|
"license": "MIT",
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,151 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
1
|
+
const { ClosedSchema } = require('./closed-schema');
|
|
2
|
+
const { compileErrors, compileFirstError, compileIsValid, compileType } = require('./compile');
|
|
3
|
+
const { fromJsonSchema, compileJsonSchema } = require('./json-schema');
|
|
4
|
+
const { Schema } = require('./schema');
|
|
5
|
+
const {
|
|
6
|
+
AllOfType,
|
|
7
|
+
AllOf,
|
|
8
|
+
allOf,
|
|
9
|
+
oallOf,
|
|
10
|
+
AnyType,
|
|
11
|
+
Any,
|
|
12
|
+
any,
|
|
13
|
+
oany,
|
|
14
|
+
AnyOfType,
|
|
15
|
+
AnyOf,
|
|
16
|
+
anyOf,
|
|
17
|
+
oanyOf,
|
|
18
|
+
ArrayOfType,
|
|
19
|
+
ArrayOf,
|
|
20
|
+
arrOf,
|
|
21
|
+
oarrOf,
|
|
22
|
+
BooleanType,
|
|
23
|
+
Boolean,
|
|
24
|
+
bool,
|
|
25
|
+
obool,
|
|
26
|
+
ConditionalType,
|
|
27
|
+
Conditional,
|
|
28
|
+
EnumType,
|
|
29
|
+
Enum,
|
|
30
|
+
enumt,
|
|
31
|
+
oenumt,
|
|
32
|
+
oenum,
|
|
33
|
+
FloatType,
|
|
34
|
+
Float,
|
|
35
|
+
float,
|
|
36
|
+
ofloat,
|
|
37
|
+
num,
|
|
38
|
+
onum,
|
|
39
|
+
IntegerType,
|
|
40
|
+
Integer,
|
|
41
|
+
int,
|
|
42
|
+
oint,
|
|
43
|
+
NeverType,
|
|
44
|
+
Never,
|
|
45
|
+
never,
|
|
46
|
+
NotType,
|
|
47
|
+
Not,
|
|
48
|
+
not,
|
|
49
|
+
onot,
|
|
50
|
+
ObjType,
|
|
51
|
+
Obj,
|
|
52
|
+
obj,
|
|
53
|
+
oobj,
|
|
54
|
+
OneOfType,
|
|
55
|
+
OneOf,
|
|
56
|
+
oneOf,
|
|
57
|
+
ooneOf,
|
|
58
|
+
RefType,
|
|
59
|
+
Ref,
|
|
60
|
+
StringType,
|
|
61
|
+
String,
|
|
62
|
+
str,
|
|
63
|
+
ostr,
|
|
64
|
+
ValidateType,
|
|
65
|
+
hasErrors,
|
|
66
|
+
toErrors,
|
|
67
|
+
ValuesType,
|
|
68
|
+
Values,
|
|
69
|
+
Const,
|
|
70
|
+
WhenType,
|
|
71
|
+
When,
|
|
72
|
+
isJsonType,
|
|
73
|
+
} = require('./types');
|
|
6
74
|
|
|
7
75
|
module.exports = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
76
|
+
ClosedSchema,
|
|
77
|
+
compileErrors,
|
|
78
|
+
compileFirstError,
|
|
79
|
+
compileIsValid,
|
|
80
|
+
compileType,
|
|
81
|
+
fromJsonSchema,
|
|
82
|
+
compileJsonSchema,
|
|
83
|
+
Schema,
|
|
84
|
+
AllOfType,
|
|
85
|
+
AllOf,
|
|
86
|
+
allOf,
|
|
87
|
+
oallOf,
|
|
88
|
+
AnyType,
|
|
89
|
+
Any,
|
|
90
|
+
any,
|
|
91
|
+
oany,
|
|
92
|
+
AnyOfType,
|
|
93
|
+
AnyOf,
|
|
94
|
+
anyOf,
|
|
95
|
+
oanyOf,
|
|
96
|
+
ArrayOfType,
|
|
97
|
+
ArrayOf,
|
|
98
|
+
arrOf,
|
|
99
|
+
oarrOf,
|
|
100
|
+
BooleanType,
|
|
101
|
+
Boolean,
|
|
102
|
+
bool,
|
|
103
|
+
obool,
|
|
104
|
+
ConditionalType,
|
|
105
|
+
Conditional,
|
|
106
|
+
EnumType,
|
|
107
|
+
Enum,
|
|
108
|
+
enumt,
|
|
109
|
+
oenumt,
|
|
110
|
+
oenum,
|
|
111
|
+
FloatType,
|
|
112
|
+
Float,
|
|
113
|
+
float,
|
|
114
|
+
ofloat,
|
|
115
|
+
num,
|
|
116
|
+
onum,
|
|
117
|
+
IntegerType,
|
|
118
|
+
Integer,
|
|
119
|
+
int,
|
|
120
|
+
oint,
|
|
121
|
+
NeverType,
|
|
122
|
+
Never,
|
|
123
|
+
never,
|
|
124
|
+
NotType,
|
|
125
|
+
Not,
|
|
126
|
+
not,
|
|
127
|
+
onot,
|
|
128
|
+
ObjType,
|
|
129
|
+
Obj,
|
|
130
|
+
obj,
|
|
131
|
+
oobj,
|
|
132
|
+
OneOfType,
|
|
133
|
+
OneOf,
|
|
134
|
+
oneOf,
|
|
135
|
+
ooneOf,
|
|
136
|
+
RefType,
|
|
137
|
+
Ref,
|
|
138
|
+
StringType,
|
|
139
|
+
String,
|
|
140
|
+
str,
|
|
141
|
+
ostr,
|
|
142
|
+
ValidateType,
|
|
143
|
+
hasErrors,
|
|
144
|
+
toErrors,
|
|
145
|
+
ValuesType,
|
|
146
|
+
Values,
|
|
147
|
+
Const,
|
|
148
|
+
WhenType,
|
|
149
|
+
When,
|
|
150
|
+
isJsonType,
|
|
13
151
|
};
|
package/src/schema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { ObjType, ValidateType } = require('./types');
|
|
1
|
+
const { ObjType, ValidateType, toType } = require('./types');
|
|
2
2
|
|
|
3
3
|
// Declared keys are read as own properties only: {}.toString or {}.constructor must not count as present.
|
|
4
4
|
// A value read from a plain object is its own unless Object.prototype has the key, which avoids the slower
|
|
@@ -22,16 +22,21 @@ class Schema {
|
|
|
22
22
|
this.isMandatory = options.isMandatory === undefined ? true : options.isMandatory;
|
|
23
23
|
this.isNullable = options.isNullable === undefined ? false : options.isNullable;
|
|
24
24
|
// Type that keys not declared in the schema must satisfy (only used when the schema is open).
|
|
25
|
-
this.additionalType = options.additionalType;
|
|
25
|
+
this.additionalType = toType(options.additionalType, 'Schema additionalType');
|
|
26
26
|
// [{ pattern, type }]: keys matching a pattern must satisfy its type, and are not checked by additionalType.
|
|
27
|
-
this.patternTypes = options.patternTypes || []
|
|
27
|
+
this.patternTypes = (options.patternTypes || []).map((item, i) => ({
|
|
28
|
+
...item,
|
|
29
|
+
type: toType(item.type, `Schema patternTypes[${i}].type`),
|
|
30
|
+
}));
|
|
28
31
|
this.minProperties = options.minProperties;
|
|
29
32
|
this.maxProperties = options.maxProperties;
|
|
30
33
|
// [{ key, required: [properties] } or { key, type }]: when key is present, the properties must be present too,
|
|
31
34
|
// or the whole object must satisfy type.
|
|
32
|
-
this.dependencies = options.dependencies || []
|
|
35
|
+
this.dependencies = (options.dependencies || []).map((item, i) =>
|
|
36
|
+
item.type === undefined ? item : { ...item, type: toType(item.type, `Schema dependencies[${i}].type`) }
|
|
37
|
+
);
|
|
33
38
|
// Type every key must satisfy, reported as "Key <name>".
|
|
34
|
-
this.propertyNameType = options.propertyNameType;
|
|
39
|
+
this.propertyNameType = toType(options.propertyNameType, 'Schema propertyNameType');
|
|
35
40
|
this.visitObjs();
|
|
36
41
|
this.keys = Object.keys(this.schema);
|
|
37
42
|
this.keySet = new Set(this.keys);
|
package/src/types/all-of.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const { ValidateType } = require('./validate-type');
|
|
1
|
+
const { ValidateType, toTypes } = require('./validate-type');
|
|
2
2
|
|
|
3
3
|
// Value must satisfy every type; reports the errors of the first type that fails.
|
|
4
4
|
class AllOfType extends ValidateType {
|
|
5
5
|
constructor(options = {}) {
|
|
6
6
|
super(options);
|
|
7
|
-
this.types = options.types || [];
|
|
7
|
+
this.types = toTypes(options.types, 'AllOf types') || [];
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
validate(value, fieldName = 'Value') {
|
package/src/types/any-of.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const { ValidateType } = require('./validate-type');
|
|
1
|
+
const { ValidateType, toTypes } = require('./validate-type');
|
|
2
2
|
|
|
3
3
|
class AnyOfType extends ValidateType {
|
|
4
4
|
constructor(options = {}) {
|
|
5
5
|
super(options);
|
|
6
|
-
this.types = options.types;
|
|
6
|
+
this.types = toTypes(options.types, 'AnyOf types');
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
validate(value, fieldName = 'Value') {
|
package/src/types/array-of.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
const { hasDuplicates } = require('./has-duplicates');
|
|
2
|
-
const { ValidateType } = require('./validate-type');
|
|
2
|
+
const { ValidateType, isPlainObject, toType, toTypes } = require('./validate-type');
|
|
3
3
|
|
|
4
4
|
class ArrayOfType extends ValidateType {
|
|
5
5
|
constructor(options = {}) {
|
|
6
6
|
super(options);
|
|
7
7
|
// A type for every element, or an array of types for the elements at each position (a tuple).
|
|
8
|
-
this.type = options.type
|
|
8
|
+
this.type = Array.isArray(options.type)
|
|
9
|
+
? toTypes(options.type, 'ArrayOf type')
|
|
10
|
+
: toType(options.type, 'ArrayOf type');
|
|
9
11
|
this.min = options.min;
|
|
10
12
|
this.max = options.max;
|
|
11
13
|
this.unique = options.unique;
|
|
12
14
|
// At least one element must satisfy it.
|
|
13
|
-
this.contains = options.contains;
|
|
15
|
+
this.contains = toType(options.contains, 'ArrayOf contains');
|
|
14
16
|
// With a tuple, the elements after its last position must satisfy it.
|
|
15
|
-
this.additionalType = options.additionalType;
|
|
17
|
+
this.additionalType = toType(options.additionalType, 'ArrayOf additionalType');
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
hasMatch(value) {
|
|
@@ -115,15 +117,27 @@ function ArrayOf(options) {
|
|
|
115
117
|
return new ArrayOfType(options);
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
const OPTION_KEYS = ['type', 'min', 'max', 'unique', 'contains', 'additionalType', 'isMandatory', 'isNullable'];
|
|
121
|
+
|
|
122
|
+
// The first argument of arrOf() is the options when it is a plain object that is empty or has an option key;
|
|
123
|
+
// otherwise it is the type of the elements (a type, a schema, or a plain object of types).
|
|
124
|
+
function isOptions(value) {
|
|
125
|
+
if (!isPlainObject(value)) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const keys = Object.keys(value);
|
|
129
|
+
return keys.length === 0 || keys.some((key) => OPTION_KEYS.includes(key));
|
|
130
|
+
}
|
|
131
|
+
|
|
118
132
|
function arrOf(type, min, max, isMandatory = true, isNullable = false) {
|
|
119
|
-
if (type
|
|
133
|
+
if (isOptions(type)) {
|
|
120
134
|
return new ArrayOfType(type);
|
|
121
135
|
}
|
|
122
136
|
return new ArrayOfType({ type, min, max, isMandatory, isNullable });
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
function oarrOf(type, min, max, isMandatory = false, isNullable = false) {
|
|
126
|
-
if (type
|
|
140
|
+
if (isOptions(type)) {
|
|
127
141
|
return new ArrayOfType({ isMandatory: false, ...type });
|
|
128
142
|
}
|
|
129
143
|
return new ArrayOfType({ type, min, max, isMandatory, isNullable });
|
package/src/types/conditional.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
const { ValidateType } = require('./validate-type');
|
|
1
|
+
const { ValidateType, toType } = require('./validate-type');
|
|
2
2
|
|
|
3
3
|
// When the value satisfies `ifType` it must satisfy `thenType`, otherwise `elseType`; a missing branch accepts
|
|
4
4
|
// anything. Only the errors of the branch are reported, like JSON Schema if/then/else.
|
|
5
5
|
class ConditionalType extends ValidateType {
|
|
6
6
|
constructor(options = {}) {
|
|
7
7
|
super(options);
|
|
8
|
-
this.ifType = options.ifType;
|
|
9
|
-
this.thenType = options.thenType;
|
|
10
|
-
this.elseType = options.elseType;
|
|
8
|
+
this.ifType = toType(options.ifType, 'Conditional ifType');
|
|
9
|
+
this.thenType = toType(options.thenType, 'Conditional thenType');
|
|
10
|
+
this.elseType = toType(options.elseType, 'Conditional elseType');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
branch(value) {
|
package/src/types/not.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const { ValidateType } = require('./validate-type');
|
|
1
|
+
const { ValidateType, toType } = require('./validate-type');
|
|
2
2
|
|
|
3
3
|
// Value must not satisfy `type`.
|
|
4
4
|
class NotType extends ValidateType {
|
|
5
5
|
constructor(options = {}) {
|
|
6
6
|
super(options);
|
|
7
|
-
this.type = options.type;
|
|
7
|
+
this.type = toType(options.type, 'Not type');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
validate(value, fieldName = 'Value') {
|
package/src/types/one-of.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const { ValidateType } = require('./validate-type');
|
|
1
|
+
const { ValidateType, toTypes } = require('./validate-type');
|
|
2
2
|
|
|
3
3
|
// Value must satisfy exactly one of the types. When none does, reports the errors of every type, like AnyOfType.
|
|
4
4
|
class OneOfType extends ValidateType {
|
|
5
5
|
constructor(options = {}) {
|
|
6
6
|
super(options);
|
|
7
|
-
this.types = options.types || [];
|
|
7
|
+
this.types = toTypes(options.types, 'OneOf types') || [];
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
// Number of types the value satisfies, counting up to 2.
|
package/src/types/ref.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { ValidateType } = require('./validate-type');
|
|
1
|
+
const { ValidateType, toType } = require('./validate-type');
|
|
2
2
|
|
|
3
3
|
// Validates with the type it refers to, which is set once references are resolved; recursive schemas refer back to a
|
|
4
4
|
// type that contains the reference. Only undefined is handled here (isMandatory); null and other values go to the
|
|
@@ -7,7 +7,7 @@ class RefType extends ValidateType {
|
|
|
7
7
|
constructor(options = {}) {
|
|
8
8
|
super(options);
|
|
9
9
|
this.ref = options.ref;
|
|
10
|
-
this.target = options.target;
|
|
10
|
+
this.target = toType(options.target, 'Ref target');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
getTarget() {
|
|
@@ -91,8 +91,46 @@ function toErrors(result) {
|
|
|
91
91
|
return result ? [result] : [];
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function isPlainObject(value) {
|
|
95
|
+
if (value === null || typeof value !== 'object') {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const proto = Object.getPrototypeOf(value);
|
|
99
|
+
return proto === Object.prototype || proto === null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Normalizes an option that holds a type. A plain object stands for new Schema(object), as it does for a key of a
|
|
103
|
+
// Schema; other objects (types, schemas) are kept; anything else throws now instead of failing when validating.
|
|
104
|
+
function toType(value, name) {
|
|
105
|
+
if (value === undefined || value instanceof ValidateType) {
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
if (isPlainObject(value)) {
|
|
109
|
+
// eslint-disable-next-line global-require -- schema.js requires this module
|
|
110
|
+
const { Schema } = require('../schema');
|
|
111
|
+
return new Schema(value);
|
|
112
|
+
}
|
|
113
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
114
|
+
throw new TypeError(`${name} must be a type or an object of types`);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function toTypes(values, name) {
|
|
120
|
+
if (values === undefined) {
|
|
121
|
+
return values;
|
|
122
|
+
}
|
|
123
|
+
if (!Array.isArray(values)) {
|
|
124
|
+
throw new TypeError(`${name} must be an array of types`);
|
|
125
|
+
}
|
|
126
|
+
return values.map((value, i) => toType(value, `${name}[${i}]`));
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
module.exports = {
|
|
95
130
|
ValidateType,
|
|
96
131
|
hasErrors,
|
|
97
132
|
toErrors,
|
|
133
|
+
isPlainObject,
|
|
134
|
+
toType,
|
|
135
|
+
toTypes,
|
|
98
136
|
};
|
package/src/types/when.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { ValidateType } = require('./validate-type');
|
|
1
|
+
const { ValidateType, toType } = require('./validate-type');
|
|
2
2
|
|
|
3
3
|
const JSON_TYPES = ['object', 'array', 'string', 'number'];
|
|
4
4
|
|
|
@@ -25,7 +25,7 @@ class WhenType extends ValidateType {
|
|
|
25
25
|
throw new Error(`WhenType jsonType must be one of: ${JSON_TYPES.join(', ')}`);
|
|
26
26
|
}
|
|
27
27
|
this.jsonType = options.jsonType;
|
|
28
|
-
this.type = options.type;
|
|
28
|
+
this.type = toType(options.type, 'When type');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
validate(value, fieldName) {
|