@cloudflare/cabidela 0.0.17 → 0.0.19
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 +20 -0
- package/README.md +52 -10
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +71 -80
- package/dist/index.mjs +71 -80
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
## [0.0.19] - 2025-02-26
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- Added support for applying defaults with anyOf/oneOf and tests
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
|
13
|
+
- Minor code refactoring for better readability
|
14
|
+
|
15
|
+
## [0.0.18] - 2025-02-20
|
16
|
+
|
17
|
+
### Added
|
18
|
+
|
19
|
+
- Constant values support https://json-schema.org/understanding-json-schema/reference/const
|
20
|
+
- CHANGELOG.md file
|
package/README.md
CHANGED
@@ -4,16 +4,15 @@
|
|
4
4
|
</a>
|
5
5
|
</div>
|
6
6
|
|
7
|
-
|
8
7
|
<p align="center">
|
9
|
-
<em>Small, fast, eval-less,
|
8
|
+
<em>Small, fast, eval-less, <a href="https://developers.cloudflare.com/workers/">Cloudflare Workers</a> compatible, dynamic JSON Schema validator.</em>
|
10
9
|
</p>
|
11
10
|
|
12
11
|
<hr />
|
13
12
|
|
14
|
-
|
13
|
+
## What is
|
15
14
|
|
16
|
-
Cabidela is a small, fast, eval-less,
|
15
|
+
Cabidela is a small, fast, eval-less, Cloudflare Workers compatible, dynamic JSON Schema validator. It implements a large subset of <https://json-schema.org/draft/2020-12/json-schema-validation> that should cover most use-cases. But not all. See limitations below.
|
17
16
|
|
18
17
|
## How to use
|
19
18
|
|
@@ -136,6 +135,51 @@ console.log(payload);
|
|
136
135
|
|
137
136
|
```
|
138
137
|
|
138
|
+
### oneOf defaults
|
139
|
+
|
140
|
+
Using `applyDefaults` with `oneOf` will one apply the default value of the sub-schema that matches the condition. For
|
141
|
+
example, using this schema:
|
142
|
+
|
143
|
+
```javascript
|
144
|
+
{
|
145
|
+
type: "object",
|
146
|
+
oneOf: [
|
147
|
+
{
|
148
|
+
type: "object",
|
149
|
+
properties: {
|
150
|
+
sun: {
|
151
|
+
type: "number",
|
152
|
+
default: 9000,
|
153
|
+
},
|
154
|
+
moon: {
|
155
|
+
type: "number",
|
156
|
+
default: 9000,
|
157
|
+
},
|
158
|
+
},
|
159
|
+
required: ["sun"],
|
160
|
+
},
|
161
|
+
{
|
162
|
+
type: "object",
|
163
|
+
properties: {
|
164
|
+
sun: {
|
165
|
+
type: "number",
|
166
|
+
default: 9000,
|
167
|
+
},
|
168
|
+
moon: {
|
169
|
+
type: "number",
|
170
|
+
default: 9000,
|
171
|
+
},
|
172
|
+
},
|
173
|
+
required: ["moon"],
|
174
|
+
},
|
175
|
+
],
|
176
|
+
};
|
177
|
+
```
|
178
|
+
|
179
|
+
- The payload `{ sun: 10}` will be modified to `{ sun: 10, moon: 9000 }`.
|
180
|
+
- The payload `{ moon: 10}` will be modified to `{ sun: 9000, moon: 10 }`.
|
181
|
+
- The payload `{ saturn: 10}` will throw an error because no condition is met.
|
182
|
+
|
139
183
|
## Custom errors
|
140
184
|
|
141
185
|
If the new instance options has the `errorMessages` flag set to true, you can use the property `errorMessage` in the schema to define custom error messages.
|
@@ -160,7 +204,7 @@ const payload = {
|
|
160
204
|
|
161
205
|
cabidela.validate(payload);
|
162
206
|
// throws "Error: prompt required"
|
163
|
-
|
207
|
+
````
|
164
208
|
|
165
209
|
## Tests
|
166
210
|
|
@@ -187,11 +231,11 @@ JSON Schema validators like Ajv tend to follow this pattern:
|
|
187
231
|
2. Compile the schema.
|
188
232
|
3. Validate one or more payloads against the (compiled) schema.
|
189
233
|
|
190
|
-
All of these steps have a cost. Compiling the schema makes sense if you are going to validate multiple payloads in the same session. But in the case of a Workers
|
234
|
+
All of these steps have a cost. Compiling the schema makes sense if you are going to validate multiple payloads in the same session. But in the case of a Workers application we typically want to validate with the HTTP request, one payload at a time, and then we discard the validator.
|
191
235
|
|
192
236
|
Cabidela skips the compilation step and validates the payload directly against the schema.
|
193
237
|
|
194
|
-
In our benchmarks, Cabidela is significantly faster than Ajv on all operations if you don't reuse the validator. Even when we skip the instantiation and compilation steps from Ajv, Cabidela still performs
|
238
|
+
In our benchmarks, Cabidela is significantly faster than Ajv on all operations if you don't reuse the validator. Even when we skip the instantiation and compilation steps from Ajv, Cabidela still performs relatively well.
|
195
239
|
|
196
240
|
Here are some results:
|
197
241
|
|
@@ -236,10 +280,9 @@ We use Vitest's [bench](https://vitest.dev/api/#bench) feature to run the benchm
|
|
236
280
|
npm run benchmark
|
237
281
|
```
|
238
282
|
|
239
|
-
|
240
283
|
## Current limitations
|
241
284
|
|
242
|
-
Cabidela supports most of JSON Schema specification for
|
285
|
+
Cabidela supports most of JSON Schema specification, and should be useful for many applications, but it's not complete. **Currently** we do not support:
|
243
286
|
|
244
287
|
- Multiple (array of) types `{ "type": ["number", "string"] }`
|
245
288
|
- Regular expressions
|
@@ -249,4 +292,3 @@ Cabidela supports most of JSON Schema specification for most use-cases, we think
|
|
249
292
|
- `$ref`, `$defs` and `$id`
|
250
293
|
|
251
294
|
yet.
|
252
|
-
|
package/dist/index.d.mts
CHANGED
@@ -9,8 +9,10 @@ type SchemaNavigation = {
|
|
9
9
|
payload: any;
|
10
10
|
evaluatedProperties: Set<string>;
|
11
11
|
carryProperties?: boolean;
|
12
|
+
deferredApplyDefaults?: boolean;
|
12
13
|
absorvErrors?: boolean;
|
13
14
|
errors: Set<string>;
|
15
|
+
defaultsCallbacks: Array<any>;
|
14
16
|
};
|
15
17
|
declare class Cabidela {
|
16
18
|
schema: any;
|
package/dist/index.d.ts
CHANGED
@@ -9,8 +9,10 @@ type SchemaNavigation = {
|
|
9
9
|
payload: any;
|
10
10
|
evaluatedProperties: Set<string>;
|
11
11
|
carryProperties?: boolean;
|
12
|
+
deferredApplyDefaults?: boolean;
|
12
13
|
absorvErrors?: boolean;
|
13
14
|
errors: Set<string>;
|
15
|
+
defaultsCallbacks: Array<any>;
|
14
16
|
};
|
15
17
|
declare class Cabidela {
|
16
18
|
schema: any;
|
package/dist/index.js
CHANGED
@@ -37,7 +37,7 @@ var pathToString = (path) => {
|
|
37
37
|
var getMetaData = (value) => {
|
38
38
|
let size = 0;
|
39
39
|
let types = /* @__PURE__ */ new Set([]);
|
40
|
-
let properties =
|
40
|
+
let properties = [];
|
41
41
|
if (value === null) {
|
42
42
|
types.add("null");
|
43
43
|
} else if (typeof value == "string") {
|
@@ -61,7 +61,7 @@ var getMetaData = (value) => {
|
|
61
61
|
} else if (typeof value == "object") {
|
62
62
|
types.add("object");
|
63
63
|
size = Object.keys(value).length;
|
64
|
-
properties =
|
64
|
+
properties = Object.keys(value);
|
65
65
|
}
|
66
66
|
return { types, size, properties };
|
67
67
|
};
|
@@ -87,19 +87,14 @@ var Cabidela = class {
|
|
87
87
|
}
|
88
88
|
throw(message, needle) {
|
89
89
|
const error = `${message}${this.options.fullErrors && needle.absorvErrors !== true && needle.errors.size > 0 ? `: ${Array.from(needle.errors).join(", ")}` : ``}`;
|
90
|
-
throw new Error(
|
91
|
-
this.options.errorMessages ? needle.schema.errorMessage ?? error : error
|
92
|
-
);
|
90
|
+
throw new Error(this.options.errorMessages ? needle.schema.errorMessage ?? error : error);
|
93
91
|
}
|
94
92
|
parseAdditionalProperties(needle, contextAdditionalProperties, contextEvaluatedProperties) {
|
95
93
|
let matchCount = 0;
|
96
|
-
const { metadata, resolvedObject } = resolvePayload(
|
97
|
-
|
98
|
-
needle.
|
99
|
-
);
|
100
|
-
const unevaluatedProperties = metadata.properties.difference(
|
101
|
-
contextEvaluatedProperties
|
102
|
-
);
|
94
|
+
const { metadata, resolvedObject } = resolvePayload(needle.path, needle.payload);
|
95
|
+
const unevaluatedProperties = new Set(
|
96
|
+
metadata.properties.map((r) => pathToString([...needle.path, r]))
|
97
|
+
).difference(contextEvaluatedProperties);
|
103
98
|
if (contextAdditionalProperties === false) {
|
104
99
|
if (unevaluatedProperties.size > 0) {
|
105
100
|
this.throw(
|
@@ -114,14 +109,15 @@ var Cabidela = class {
|
|
114
109
|
} else {
|
115
110
|
for (let property of unevaluatedProperties) {
|
116
111
|
if (this.parseSubSchema({
|
117
|
-
path: [property],
|
112
|
+
path: [property.split(".").slice(-1)[0]],
|
118
113
|
schema: contextAdditionalProperties,
|
119
114
|
payload: resolvedObject,
|
120
115
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
121
|
-
errors: /* @__PURE__ */ new Set()
|
116
|
+
errors: /* @__PURE__ */ new Set(),
|
117
|
+
defaultsCallbacks: []
|
122
118
|
})) {
|
123
119
|
matchCount++;
|
124
|
-
needle.evaluatedProperties.add(property);
|
120
|
+
needle.evaluatedProperties.add(pathToString([property]));
|
125
121
|
}
|
126
122
|
}
|
127
123
|
}
|
@@ -149,12 +145,13 @@ var Cabidela = class {
|
|
149
145
|
let matchCount = 0;
|
150
146
|
if (needle.schema.hasOwnProperty("properties")) {
|
151
147
|
for (let property in needle.schema.properties) {
|
152
|
-
|
148
|
+
const matches = this.parseSubSchema({
|
153
149
|
...needle,
|
154
150
|
path: [...needle.path, property],
|
155
151
|
schema: needle.schema.properties[property]
|
156
|
-
})
|
157
|
-
|
152
|
+
});
|
153
|
+
if (matches > 0) {
|
154
|
+
localEvaluatedProperties.add(pathToString([...needle.path, property]));
|
158
155
|
matchCount++;
|
159
156
|
}
|
160
157
|
}
|
@@ -167,10 +164,7 @@ var Cabidela = class {
|
|
167
164
|
);
|
168
165
|
}
|
169
166
|
if (needle.schema.hasOwnProperty("unevaluatedProperties")) {
|
170
|
-
needle.evaluatedProperties = /* @__PURE__ */ new Set([
|
171
|
-
...needle.evaluatedProperties,
|
172
|
-
...localEvaluatedProperties
|
173
|
-
]);
|
167
|
+
needle.evaluatedProperties = /* @__PURE__ */ new Set([...needle.evaluatedProperties, ...localEvaluatedProperties]);
|
174
168
|
matchCount += this.parseAdditionalProperties(
|
175
169
|
needle,
|
176
170
|
needle.schema.unevaluatedProperties,
|
@@ -178,32 +172,37 @@ var Cabidela = class {
|
|
178
172
|
);
|
179
173
|
}
|
180
174
|
if (needle.schema.hasOwnProperty("required")) {
|
181
|
-
if (new Set(needle.schema.required).difference(
|
175
|
+
if (new Set(needle.schema.required.map((r) => pathToString([...needle.path, r]))).difference(
|
182
176
|
needle.evaluatedProperties.union(localEvaluatedProperties)
|
183
177
|
).size > 0) {
|
184
|
-
this.throw(
|
185
|
-
`required properties at '${pathToString(needle.path)}' is '${needle.schema.required}'`,
|
186
|
-
needle
|
187
|
-
);
|
178
|
+
this.throw(`required properties at '${pathToString(needle.path)}' is '${needle.schema.required}'`, needle);
|
188
179
|
}
|
189
180
|
}
|
190
181
|
return matchCount ? true : false;
|
191
182
|
}
|
192
183
|
parseList(list, needle, breakCondition) {
|
193
184
|
let rounds = 0;
|
185
|
+
const defaultsCallbacks = [];
|
194
186
|
for (let option in list) {
|
195
187
|
try {
|
196
|
-
|
188
|
+
const matches = this.parseSubSchema({
|
197
189
|
...needle,
|
198
190
|
schema: { type: needle.schema.type, ...list[option] },
|
199
|
-
carryProperties:
|
200
|
-
absorvErrors: true
|
191
|
+
carryProperties: false,
|
192
|
+
absorvErrors: true,
|
193
|
+
deferredApplyDefaults: true
|
201
194
|
});
|
195
|
+
rounds += matches;
|
202
196
|
if (breakCondition && breakCondition(rounds)) break;
|
197
|
+
defaultsCallbacks.push(...needle.defaultsCallbacks);
|
198
|
+
needle.defaultsCallbacks = [];
|
203
199
|
} catch (e) {
|
204
200
|
needle.errors.add(e.message);
|
201
|
+
needle.defaultsCallbacks = [];
|
205
202
|
}
|
206
203
|
}
|
204
|
+
for (const callback of defaultsCallbacks) callback();
|
205
|
+
needle.defaultsCallbacks = [];
|
207
206
|
return rounds;
|
208
207
|
}
|
209
208
|
// Parses a JSON Schema sub-schema object - reentrant
|
@@ -212,9 +211,10 @@ var Cabidela = class {
|
|
212
211
|
this.throw(`No schema for path '${pathToString(needle.path)}'`, needle);
|
213
212
|
}
|
214
213
|
if (needle.schema.hasOwnProperty("oneOf")) {
|
215
|
-
|
214
|
+
const rounds = this.parseList(needle.schema.oneOf, needle, (r) => r !== 1);
|
215
|
+
if (rounds !== 1) {
|
216
216
|
if (needle.path.length == 0) {
|
217
|
-
this.throw(`oneOf at '${pathToString(needle.path)}' not met`, needle);
|
217
|
+
this.throw(`oneOf at '${pathToString(needle.path)}' not met, ${rounds} matches`, needle);
|
218
218
|
}
|
219
219
|
return 0;
|
220
220
|
}
|
@@ -230,10 +230,7 @@ var Cabidela = class {
|
|
230
230
|
return 1;
|
231
231
|
}
|
232
232
|
if (needle.schema.hasOwnProperty("allOf")) {
|
233
|
-
const conditions = needle.schema.allOf.reduce(
|
234
|
-
(r, c) => Object.assign(r, c),
|
235
|
-
{}
|
236
|
-
);
|
233
|
+
const conditions = needle.schema.allOf.reduce((r, c) => Object.assign(r, c), {});
|
237
234
|
try {
|
238
235
|
this.parseSubSchema({
|
239
236
|
...needle,
|
@@ -248,10 +245,7 @@ var Cabidela = class {
|
|
248
245
|
return 0;
|
249
246
|
}
|
250
247
|
}
|
251
|
-
const { metadata, resolvedObject } = resolvePayload(
|
252
|
-
needle.path,
|
253
|
-
needle.payload
|
254
|
-
);
|
248
|
+
const { metadata, resolvedObject } = resolvePayload(needle.path, needle.payload);
|
255
249
|
if (needle.schema.type === "array" && !metadata.types.has("binary") && !metadata.types.has("string")) {
|
256
250
|
let matched = 0;
|
257
251
|
for (let item in resolvedObject) {
|
@@ -265,6 +259,16 @@ var Cabidela = class {
|
|
265
259
|
} else if (needle.schema.type === "object" || needle.schema.properties) {
|
266
260
|
return this.parseObject(needle) ? 1 : 0;
|
267
261
|
} else if (resolvedObject !== void 0) {
|
262
|
+
if (needle.schema.hasOwnProperty("const")) {
|
263
|
+
if (resolvedObject !== needle.schema.const) {
|
264
|
+
this.throw(
|
265
|
+
`const ${resolvedObject} doesn't match ${needle.schema.const} at '${pathToString(needle.path)}'`,
|
266
|
+
needle
|
267
|
+
);
|
268
|
+
} else {
|
269
|
+
if (needle.schema.type == void 0) return 1;
|
270
|
+
}
|
271
|
+
}
|
268
272
|
if (needle.schema.hasOwnProperty("enum")) {
|
269
273
|
if (Array.isArray(needle.schema.enum)) {
|
270
274
|
if (!needle.schema.enum.includes(resolvedObject)) {
|
@@ -276,15 +280,12 @@ var Cabidela = class {
|
|
276
280
|
if (needle.schema.type == void 0) return 1;
|
277
281
|
}
|
278
282
|
} else {
|
279
|
-
this.throw(
|
280
|
-
`enum should be an array at '${pathToString(needle.path)}'`,
|
281
|
-
needle
|
282
|
-
);
|
283
|
+
this.throw(`enum should be an array at '${pathToString(needle.path)}'`, needle);
|
283
284
|
}
|
284
285
|
}
|
285
286
|
if (needle.schema.hasOwnProperty("type") && !metadata.types.has(needle.schema.type)) {
|
286
287
|
this.throw(
|
287
|
-
`Type mismatch of '${pathToString(needle.path)}', '${needle.schema.type}' not in ${
|
288
|
+
`Type mismatch of '${pathToString(needle.path)}', '${needle.schema.type}' not in ${Array.from(metadata.types).map((e) => `'${e}'`).join(",")}`,
|
288
289
|
needle
|
289
290
|
);
|
290
291
|
}
|
@@ -292,10 +293,7 @@ var Cabidela = class {
|
|
292
293
|
switch (needle.schema.type) {
|
293
294
|
case "string":
|
294
295
|
if (needle.schema.hasOwnProperty("maxLength") && metadata.size > needle.schema.maxLength) {
|
295
|
-
this.throw(
|
296
|
-
`Length of '${pathToString(needle.path)}' must be <= ${needle.schema.maxLength}`,
|
297
|
-
needle
|
298
|
-
);
|
296
|
+
this.throw(`Length of '${pathToString(needle.path)}' must be <= ${needle.schema.maxLength}`, needle);
|
299
297
|
}
|
300
298
|
if (needle.schema.hasOwnProperty("minLength") && metadata.size < needle.schema.minLength) {
|
301
299
|
this.throw(
|
@@ -307,60 +305,53 @@ var Cabidela = class {
|
|
307
305
|
case "number":
|
308
306
|
case "integer":
|
309
307
|
if (needle.schema.hasOwnProperty("minimum") && resolvedObject < needle.schema.minimum) {
|
310
|
-
this.throw(
|
311
|
-
`'${pathToString(needle.path)}' must be >= ${needle.schema.minimum}`,
|
312
|
-
needle
|
313
|
-
);
|
308
|
+
this.throw(`'${pathToString(needle.path)}' must be >= ${needle.schema.minimum}`, needle);
|
314
309
|
}
|
315
310
|
if (needle.schema.hasOwnProperty("exclusiveMinimum") && resolvedObject <= needle.schema.exclusiveMinimum) {
|
316
|
-
this.throw(
|
317
|
-
`'${pathToString(needle.path)}' must be > ${needle.schema.exclusiveMinimum}`,
|
318
|
-
needle
|
319
|
-
);
|
311
|
+
this.throw(`'${pathToString(needle.path)}' must be > ${needle.schema.exclusiveMinimum}`, needle);
|
320
312
|
}
|
321
313
|
if (needle.schema.hasOwnProperty("maximum") && resolvedObject > needle.schema.maximum) {
|
322
|
-
this.throw(
|
323
|
-
`'${pathToString(needle.path)}' must be <= ${needle.schema.maximum}`,
|
324
|
-
needle
|
325
|
-
);
|
314
|
+
this.throw(`'${pathToString(needle.path)}' must be <= ${needle.schema.maximum}`, needle);
|
326
315
|
}
|
327
316
|
if (needle.schema.hasOwnProperty("exclusiveMaximum") && resolvedObject >= needle.schema.exclusiveMaximum) {
|
328
|
-
this.throw(
|
329
|
-
`'${pathToString(needle.path)}' must be < ${needle.schema.exclusiveMaximum}`,
|
330
|
-
needle
|
331
|
-
);
|
317
|
+
this.throw(`'${pathToString(needle.path)}' must be < ${needle.schema.exclusiveMaximum}`, needle);
|
332
318
|
}
|
333
319
|
if (needle.schema.hasOwnProperty("multipleOf") && resolvedObject % needle.schema.multipleOf !== 0) {
|
334
|
-
this.throw(
|
335
|
-
`'${pathToString(needle.path)}' must be multiple of ${needle.schema.multipleOf}`,
|
336
|
-
needle
|
337
|
-
);
|
320
|
+
this.throw(`'${pathToString(needle.path)}' must be multiple of ${needle.schema.multipleOf}`, needle);
|
338
321
|
}
|
339
322
|
break;
|
340
323
|
}
|
341
324
|
}
|
342
325
|
if (needle.carryProperties) {
|
343
|
-
needle.evaluatedProperties.add(needle.path
|
326
|
+
needle.evaluatedProperties.add(pathToString(needle.path));
|
344
327
|
}
|
345
328
|
return 1;
|
346
329
|
}
|
347
330
|
if (this.options.applyDefaults === true && needle.schema.hasOwnProperty("default")) {
|
348
|
-
|
349
|
-
|
350
|
-
prev[curr]
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
331
|
+
const applyDefaults = () => {
|
332
|
+
needle.path.reduce(function(prev, curr, index) {
|
333
|
+
if (prev[curr] === void 0) {
|
334
|
+
prev[curr] = {};
|
335
|
+
}
|
336
|
+
if (index == needle.path.length - 1) {
|
337
|
+
prev[curr] = needle.schema.default;
|
338
|
+
needle.evaluatedProperties.add(pathToString(needle.path));
|
339
|
+
}
|
340
|
+
return prev ? prev[curr] : void 0;
|
341
|
+
}, needle.payload);
|
342
|
+
};
|
343
|
+
if (needle.deferredApplyDefaults === true) {
|
344
|
+
needle.defaultsCallbacks.push(applyDefaults);
|
345
|
+
} else {
|
346
|
+
applyDefaults();
|
347
|
+
}
|
358
348
|
}
|
359
349
|
return 0;
|
360
350
|
}
|
361
351
|
validate(payload) {
|
362
352
|
const needle = {
|
363
353
|
errors: /* @__PURE__ */ new Set(),
|
354
|
+
defaultsCallbacks: [],
|
364
355
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
365
356
|
path: [],
|
366
357
|
schema: this.schema,
|
package/dist/index.mjs
CHANGED
@@ -11,7 +11,7 @@ var pathToString = (path) => {
|
|
11
11
|
var getMetaData = (value) => {
|
12
12
|
let size = 0;
|
13
13
|
let types = /* @__PURE__ */ new Set([]);
|
14
|
-
let properties =
|
14
|
+
let properties = [];
|
15
15
|
if (value === null) {
|
16
16
|
types.add("null");
|
17
17
|
} else if (typeof value == "string") {
|
@@ -35,7 +35,7 @@ var getMetaData = (value) => {
|
|
35
35
|
} else if (typeof value == "object") {
|
36
36
|
types.add("object");
|
37
37
|
size = Object.keys(value).length;
|
38
|
-
properties =
|
38
|
+
properties = Object.keys(value);
|
39
39
|
}
|
40
40
|
return { types, size, properties };
|
41
41
|
};
|
@@ -61,19 +61,14 @@ var Cabidela = class {
|
|
61
61
|
}
|
62
62
|
throw(message, needle) {
|
63
63
|
const error = `${message}${this.options.fullErrors && needle.absorvErrors !== true && needle.errors.size > 0 ? `: ${Array.from(needle.errors).join(", ")}` : ``}`;
|
64
|
-
throw new Error(
|
65
|
-
this.options.errorMessages ? needle.schema.errorMessage ?? error : error
|
66
|
-
);
|
64
|
+
throw new Error(this.options.errorMessages ? needle.schema.errorMessage ?? error : error);
|
67
65
|
}
|
68
66
|
parseAdditionalProperties(needle, contextAdditionalProperties, contextEvaluatedProperties) {
|
69
67
|
let matchCount = 0;
|
70
|
-
const { metadata, resolvedObject } = resolvePayload(
|
71
|
-
|
72
|
-
needle.
|
73
|
-
);
|
74
|
-
const unevaluatedProperties = metadata.properties.difference(
|
75
|
-
contextEvaluatedProperties
|
76
|
-
);
|
68
|
+
const { metadata, resolvedObject } = resolvePayload(needle.path, needle.payload);
|
69
|
+
const unevaluatedProperties = new Set(
|
70
|
+
metadata.properties.map((r) => pathToString([...needle.path, r]))
|
71
|
+
).difference(contextEvaluatedProperties);
|
77
72
|
if (contextAdditionalProperties === false) {
|
78
73
|
if (unevaluatedProperties.size > 0) {
|
79
74
|
this.throw(
|
@@ -88,14 +83,15 @@ var Cabidela = class {
|
|
88
83
|
} else {
|
89
84
|
for (let property of unevaluatedProperties) {
|
90
85
|
if (this.parseSubSchema({
|
91
|
-
path: [property],
|
86
|
+
path: [property.split(".").slice(-1)[0]],
|
92
87
|
schema: contextAdditionalProperties,
|
93
88
|
payload: resolvedObject,
|
94
89
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
95
|
-
errors: /* @__PURE__ */ new Set()
|
90
|
+
errors: /* @__PURE__ */ new Set(),
|
91
|
+
defaultsCallbacks: []
|
96
92
|
})) {
|
97
93
|
matchCount++;
|
98
|
-
needle.evaluatedProperties.add(property);
|
94
|
+
needle.evaluatedProperties.add(pathToString([property]));
|
99
95
|
}
|
100
96
|
}
|
101
97
|
}
|
@@ -123,12 +119,13 @@ var Cabidela = class {
|
|
123
119
|
let matchCount = 0;
|
124
120
|
if (needle.schema.hasOwnProperty("properties")) {
|
125
121
|
for (let property in needle.schema.properties) {
|
126
|
-
|
122
|
+
const matches = this.parseSubSchema({
|
127
123
|
...needle,
|
128
124
|
path: [...needle.path, property],
|
129
125
|
schema: needle.schema.properties[property]
|
130
|
-
})
|
131
|
-
|
126
|
+
});
|
127
|
+
if (matches > 0) {
|
128
|
+
localEvaluatedProperties.add(pathToString([...needle.path, property]));
|
132
129
|
matchCount++;
|
133
130
|
}
|
134
131
|
}
|
@@ -141,10 +138,7 @@ var Cabidela = class {
|
|
141
138
|
);
|
142
139
|
}
|
143
140
|
if (needle.schema.hasOwnProperty("unevaluatedProperties")) {
|
144
|
-
needle.evaluatedProperties = /* @__PURE__ */ new Set([
|
145
|
-
...needle.evaluatedProperties,
|
146
|
-
...localEvaluatedProperties
|
147
|
-
]);
|
141
|
+
needle.evaluatedProperties = /* @__PURE__ */ new Set([...needle.evaluatedProperties, ...localEvaluatedProperties]);
|
148
142
|
matchCount += this.parseAdditionalProperties(
|
149
143
|
needle,
|
150
144
|
needle.schema.unevaluatedProperties,
|
@@ -152,32 +146,37 @@ var Cabidela = class {
|
|
152
146
|
);
|
153
147
|
}
|
154
148
|
if (needle.schema.hasOwnProperty("required")) {
|
155
|
-
if (new Set(needle.schema.required).difference(
|
149
|
+
if (new Set(needle.schema.required.map((r) => pathToString([...needle.path, r]))).difference(
|
156
150
|
needle.evaluatedProperties.union(localEvaluatedProperties)
|
157
151
|
).size > 0) {
|
158
|
-
this.throw(
|
159
|
-
`required properties at '${pathToString(needle.path)}' is '${needle.schema.required}'`,
|
160
|
-
needle
|
161
|
-
);
|
152
|
+
this.throw(`required properties at '${pathToString(needle.path)}' is '${needle.schema.required}'`, needle);
|
162
153
|
}
|
163
154
|
}
|
164
155
|
return matchCount ? true : false;
|
165
156
|
}
|
166
157
|
parseList(list, needle, breakCondition) {
|
167
158
|
let rounds = 0;
|
159
|
+
const defaultsCallbacks = [];
|
168
160
|
for (let option in list) {
|
169
161
|
try {
|
170
|
-
|
162
|
+
const matches = this.parseSubSchema({
|
171
163
|
...needle,
|
172
164
|
schema: { type: needle.schema.type, ...list[option] },
|
173
|
-
carryProperties:
|
174
|
-
absorvErrors: true
|
165
|
+
carryProperties: false,
|
166
|
+
absorvErrors: true,
|
167
|
+
deferredApplyDefaults: true
|
175
168
|
});
|
169
|
+
rounds += matches;
|
176
170
|
if (breakCondition && breakCondition(rounds)) break;
|
171
|
+
defaultsCallbacks.push(...needle.defaultsCallbacks);
|
172
|
+
needle.defaultsCallbacks = [];
|
177
173
|
} catch (e) {
|
178
174
|
needle.errors.add(e.message);
|
175
|
+
needle.defaultsCallbacks = [];
|
179
176
|
}
|
180
177
|
}
|
178
|
+
for (const callback of defaultsCallbacks) callback();
|
179
|
+
needle.defaultsCallbacks = [];
|
181
180
|
return rounds;
|
182
181
|
}
|
183
182
|
// Parses a JSON Schema sub-schema object - reentrant
|
@@ -186,9 +185,10 @@ var Cabidela = class {
|
|
186
185
|
this.throw(`No schema for path '${pathToString(needle.path)}'`, needle);
|
187
186
|
}
|
188
187
|
if (needle.schema.hasOwnProperty("oneOf")) {
|
189
|
-
|
188
|
+
const rounds = this.parseList(needle.schema.oneOf, needle, (r) => r !== 1);
|
189
|
+
if (rounds !== 1) {
|
190
190
|
if (needle.path.length == 0) {
|
191
|
-
this.throw(`oneOf at '${pathToString(needle.path)}' not met`, needle);
|
191
|
+
this.throw(`oneOf at '${pathToString(needle.path)}' not met, ${rounds} matches`, needle);
|
192
192
|
}
|
193
193
|
return 0;
|
194
194
|
}
|
@@ -204,10 +204,7 @@ var Cabidela = class {
|
|
204
204
|
return 1;
|
205
205
|
}
|
206
206
|
if (needle.schema.hasOwnProperty("allOf")) {
|
207
|
-
const conditions = needle.schema.allOf.reduce(
|
208
|
-
(r, c) => Object.assign(r, c),
|
209
|
-
{}
|
210
|
-
);
|
207
|
+
const conditions = needle.schema.allOf.reduce((r, c) => Object.assign(r, c), {});
|
211
208
|
try {
|
212
209
|
this.parseSubSchema({
|
213
210
|
...needle,
|
@@ -222,10 +219,7 @@ var Cabidela = class {
|
|
222
219
|
return 0;
|
223
220
|
}
|
224
221
|
}
|
225
|
-
const { metadata, resolvedObject } = resolvePayload(
|
226
|
-
needle.path,
|
227
|
-
needle.payload
|
228
|
-
);
|
222
|
+
const { metadata, resolvedObject } = resolvePayload(needle.path, needle.payload);
|
229
223
|
if (needle.schema.type === "array" && !metadata.types.has("binary") && !metadata.types.has("string")) {
|
230
224
|
let matched = 0;
|
231
225
|
for (let item in resolvedObject) {
|
@@ -239,6 +233,16 @@ var Cabidela = class {
|
|
239
233
|
} else if (needle.schema.type === "object" || needle.schema.properties) {
|
240
234
|
return this.parseObject(needle) ? 1 : 0;
|
241
235
|
} else if (resolvedObject !== void 0) {
|
236
|
+
if (needle.schema.hasOwnProperty("const")) {
|
237
|
+
if (resolvedObject !== needle.schema.const) {
|
238
|
+
this.throw(
|
239
|
+
`const ${resolvedObject} doesn't match ${needle.schema.const} at '${pathToString(needle.path)}'`,
|
240
|
+
needle
|
241
|
+
);
|
242
|
+
} else {
|
243
|
+
if (needle.schema.type == void 0) return 1;
|
244
|
+
}
|
245
|
+
}
|
242
246
|
if (needle.schema.hasOwnProperty("enum")) {
|
243
247
|
if (Array.isArray(needle.schema.enum)) {
|
244
248
|
if (!needle.schema.enum.includes(resolvedObject)) {
|
@@ -250,15 +254,12 @@ var Cabidela = class {
|
|
250
254
|
if (needle.schema.type == void 0) return 1;
|
251
255
|
}
|
252
256
|
} else {
|
253
|
-
this.throw(
|
254
|
-
`enum should be an array at '${pathToString(needle.path)}'`,
|
255
|
-
needle
|
256
|
-
);
|
257
|
+
this.throw(`enum should be an array at '${pathToString(needle.path)}'`, needle);
|
257
258
|
}
|
258
259
|
}
|
259
260
|
if (needle.schema.hasOwnProperty("type") && !metadata.types.has(needle.schema.type)) {
|
260
261
|
this.throw(
|
261
|
-
`Type mismatch of '${pathToString(needle.path)}', '${needle.schema.type}' not in ${
|
262
|
+
`Type mismatch of '${pathToString(needle.path)}', '${needle.schema.type}' not in ${Array.from(metadata.types).map((e) => `'${e}'`).join(",")}`,
|
262
263
|
needle
|
263
264
|
);
|
264
265
|
}
|
@@ -266,10 +267,7 @@ var Cabidela = class {
|
|
266
267
|
switch (needle.schema.type) {
|
267
268
|
case "string":
|
268
269
|
if (needle.schema.hasOwnProperty("maxLength") && metadata.size > needle.schema.maxLength) {
|
269
|
-
this.throw(
|
270
|
-
`Length of '${pathToString(needle.path)}' must be <= ${needle.schema.maxLength}`,
|
271
|
-
needle
|
272
|
-
);
|
270
|
+
this.throw(`Length of '${pathToString(needle.path)}' must be <= ${needle.schema.maxLength}`, needle);
|
273
271
|
}
|
274
272
|
if (needle.schema.hasOwnProperty("minLength") && metadata.size < needle.schema.minLength) {
|
275
273
|
this.throw(
|
@@ -281,60 +279,53 @@ var Cabidela = class {
|
|
281
279
|
case "number":
|
282
280
|
case "integer":
|
283
281
|
if (needle.schema.hasOwnProperty("minimum") && resolvedObject < needle.schema.minimum) {
|
284
|
-
this.throw(
|
285
|
-
`'${pathToString(needle.path)}' must be >= ${needle.schema.minimum}`,
|
286
|
-
needle
|
287
|
-
);
|
282
|
+
this.throw(`'${pathToString(needle.path)}' must be >= ${needle.schema.minimum}`, needle);
|
288
283
|
}
|
289
284
|
if (needle.schema.hasOwnProperty("exclusiveMinimum") && resolvedObject <= needle.schema.exclusiveMinimum) {
|
290
|
-
this.throw(
|
291
|
-
`'${pathToString(needle.path)}' must be > ${needle.schema.exclusiveMinimum}`,
|
292
|
-
needle
|
293
|
-
);
|
285
|
+
this.throw(`'${pathToString(needle.path)}' must be > ${needle.schema.exclusiveMinimum}`, needle);
|
294
286
|
}
|
295
287
|
if (needle.schema.hasOwnProperty("maximum") && resolvedObject > needle.schema.maximum) {
|
296
|
-
this.throw(
|
297
|
-
`'${pathToString(needle.path)}' must be <= ${needle.schema.maximum}`,
|
298
|
-
needle
|
299
|
-
);
|
288
|
+
this.throw(`'${pathToString(needle.path)}' must be <= ${needle.schema.maximum}`, needle);
|
300
289
|
}
|
301
290
|
if (needle.schema.hasOwnProperty("exclusiveMaximum") && resolvedObject >= needle.schema.exclusiveMaximum) {
|
302
|
-
this.throw(
|
303
|
-
`'${pathToString(needle.path)}' must be < ${needle.schema.exclusiveMaximum}`,
|
304
|
-
needle
|
305
|
-
);
|
291
|
+
this.throw(`'${pathToString(needle.path)}' must be < ${needle.schema.exclusiveMaximum}`, needle);
|
306
292
|
}
|
307
293
|
if (needle.schema.hasOwnProperty("multipleOf") && resolvedObject % needle.schema.multipleOf !== 0) {
|
308
|
-
this.throw(
|
309
|
-
`'${pathToString(needle.path)}' must be multiple of ${needle.schema.multipleOf}`,
|
310
|
-
needle
|
311
|
-
);
|
294
|
+
this.throw(`'${pathToString(needle.path)}' must be multiple of ${needle.schema.multipleOf}`, needle);
|
312
295
|
}
|
313
296
|
break;
|
314
297
|
}
|
315
298
|
}
|
316
299
|
if (needle.carryProperties) {
|
317
|
-
needle.evaluatedProperties.add(needle.path
|
300
|
+
needle.evaluatedProperties.add(pathToString(needle.path));
|
318
301
|
}
|
319
302
|
return 1;
|
320
303
|
}
|
321
304
|
if (this.options.applyDefaults === true && needle.schema.hasOwnProperty("default")) {
|
322
|
-
|
323
|
-
|
324
|
-
prev[curr]
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
305
|
+
const applyDefaults = () => {
|
306
|
+
needle.path.reduce(function(prev, curr, index) {
|
307
|
+
if (prev[curr] === void 0) {
|
308
|
+
prev[curr] = {};
|
309
|
+
}
|
310
|
+
if (index == needle.path.length - 1) {
|
311
|
+
prev[curr] = needle.schema.default;
|
312
|
+
needle.evaluatedProperties.add(pathToString(needle.path));
|
313
|
+
}
|
314
|
+
return prev ? prev[curr] : void 0;
|
315
|
+
}, needle.payload);
|
316
|
+
};
|
317
|
+
if (needle.deferredApplyDefaults === true) {
|
318
|
+
needle.defaultsCallbacks.push(applyDefaults);
|
319
|
+
} else {
|
320
|
+
applyDefaults();
|
321
|
+
}
|
332
322
|
}
|
333
323
|
return 0;
|
334
324
|
}
|
335
325
|
validate(payload) {
|
336
326
|
const needle = {
|
337
327
|
errors: /* @__PURE__ */ new Set(),
|
328
|
+
defaultsCallbacks: [],
|
338
329
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
339
330
|
path: [],
|
340
331
|
schema: this.schema,
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@cloudflare/cabidela",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.19",
|
4
4
|
"description": "Cabidela is a small, fast, eval-less, Cloudflare Workers compatible, dynamic JSON Schema validator",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"module": "dist/index.mjs",
|
@@ -24,7 +24,8 @@
|
|
24
24
|
"files": [
|
25
25
|
"dist",
|
26
26
|
"LICENSE",
|
27
|
-
"README.md"
|
27
|
+
"README.md",
|
28
|
+
"CHANGELOG.md"
|
28
29
|
],
|
29
30
|
"repository": {
|
30
31
|
"type": "git",
|