@cloudflare/cabidela 0.0.18 → 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 +46 -4
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +50 -27
- package/dist/index.mjs +50 -27
- 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,7 +4,6 @@
|
|
4
4
|
</a>
|
5
5
|
</div>
|
6
6
|
|
7
|
-
|
8
7
|
<p align="center">
|
9
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>
|
@@ -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
|
|
@@ -236,7 +280,6 @@ 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
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:
|
@@ -249,4 +292,3 @@ Cabidela supports most of JSON Schema specification, and should be useful for ma
|
|
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
|
};
|
@@ -92,7 +92,9 @@ var Cabidela = class {
|
|
92
92
|
parseAdditionalProperties(needle, contextAdditionalProperties, contextEvaluatedProperties) {
|
93
93
|
let matchCount = 0;
|
94
94
|
const { metadata, resolvedObject } = resolvePayload(needle.path, needle.payload);
|
95
|
-
const unevaluatedProperties =
|
95
|
+
const unevaluatedProperties = new Set(
|
96
|
+
metadata.properties.map((r) => pathToString([...needle.path, r]))
|
97
|
+
).difference(contextEvaluatedProperties);
|
96
98
|
if (contextAdditionalProperties === false) {
|
97
99
|
if (unevaluatedProperties.size > 0) {
|
98
100
|
this.throw(
|
@@ -107,14 +109,15 @@ var Cabidela = class {
|
|
107
109
|
} else {
|
108
110
|
for (let property of unevaluatedProperties) {
|
109
111
|
if (this.parseSubSchema({
|
110
|
-
path: [property],
|
112
|
+
path: [property.split(".").slice(-1)[0]],
|
111
113
|
schema: contextAdditionalProperties,
|
112
114
|
payload: resolvedObject,
|
113
115
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
114
|
-
errors: /* @__PURE__ */ new Set()
|
116
|
+
errors: /* @__PURE__ */ new Set(),
|
117
|
+
defaultsCallbacks: []
|
115
118
|
})) {
|
116
119
|
matchCount++;
|
117
|
-
needle.evaluatedProperties.add(property);
|
120
|
+
needle.evaluatedProperties.add(pathToString([property]));
|
118
121
|
}
|
119
122
|
}
|
120
123
|
}
|
@@ -142,12 +145,13 @@ var Cabidela = class {
|
|
142
145
|
let matchCount = 0;
|
143
146
|
if (needle.schema.hasOwnProperty("properties")) {
|
144
147
|
for (let property in needle.schema.properties) {
|
145
|
-
|
148
|
+
const matches = this.parseSubSchema({
|
146
149
|
...needle,
|
147
150
|
path: [...needle.path, property],
|
148
151
|
schema: needle.schema.properties[property]
|
149
|
-
})
|
150
|
-
|
152
|
+
});
|
153
|
+
if (matches > 0) {
|
154
|
+
localEvaluatedProperties.add(pathToString([...needle.path, property]));
|
151
155
|
matchCount++;
|
152
156
|
}
|
153
157
|
}
|
@@ -168,7 +172,9 @@ var Cabidela = class {
|
|
168
172
|
);
|
169
173
|
}
|
170
174
|
if (needle.schema.hasOwnProperty("required")) {
|
171
|
-
if (new Set(needle.schema.required
|
175
|
+
if (new Set(needle.schema.required.map((r) => pathToString([...needle.path, r]))).difference(
|
176
|
+
needle.evaluatedProperties.union(localEvaluatedProperties)
|
177
|
+
).size > 0) {
|
172
178
|
this.throw(`required properties at '${pathToString(needle.path)}' is '${needle.schema.required}'`, needle);
|
173
179
|
}
|
174
180
|
}
|
@@ -176,19 +182,27 @@ var Cabidela = class {
|
|
176
182
|
}
|
177
183
|
parseList(list, needle, breakCondition) {
|
178
184
|
let rounds = 0;
|
185
|
+
const defaultsCallbacks = [];
|
179
186
|
for (let option in list) {
|
180
187
|
try {
|
181
|
-
|
188
|
+
const matches = this.parseSubSchema({
|
182
189
|
...needle,
|
183
190
|
schema: { type: needle.schema.type, ...list[option] },
|
184
|
-
carryProperties:
|
185
|
-
absorvErrors: true
|
191
|
+
carryProperties: false,
|
192
|
+
absorvErrors: true,
|
193
|
+
deferredApplyDefaults: true
|
186
194
|
});
|
195
|
+
rounds += matches;
|
187
196
|
if (breakCondition && breakCondition(rounds)) break;
|
197
|
+
defaultsCallbacks.push(...needle.defaultsCallbacks);
|
198
|
+
needle.defaultsCallbacks = [];
|
188
199
|
} catch (e) {
|
189
200
|
needle.errors.add(e.message);
|
201
|
+
needle.defaultsCallbacks = [];
|
190
202
|
}
|
191
203
|
}
|
204
|
+
for (const callback of defaultsCallbacks) callback();
|
205
|
+
needle.defaultsCallbacks = [];
|
192
206
|
return rounds;
|
193
207
|
}
|
194
208
|
// Parses a JSON Schema sub-schema object - reentrant
|
@@ -197,9 +211,10 @@ var Cabidela = class {
|
|
197
211
|
this.throw(`No schema for path '${pathToString(needle.path)}'`, needle);
|
198
212
|
}
|
199
213
|
if (needle.schema.hasOwnProperty("oneOf")) {
|
200
|
-
|
214
|
+
const rounds = this.parseList(needle.schema.oneOf, needle, (r) => r !== 1);
|
215
|
+
if (rounds !== 1) {
|
201
216
|
if (needle.path.length == 0) {
|
202
|
-
this.throw(`oneOf at '${pathToString(needle.path)}' not met`, needle);
|
217
|
+
this.throw(`oneOf at '${pathToString(needle.path)}' not met, ${rounds} matches`, needle);
|
203
218
|
}
|
204
219
|
return 0;
|
205
220
|
}
|
@@ -270,7 +285,7 @@ var Cabidela = class {
|
|
270
285
|
}
|
271
286
|
if (needle.schema.hasOwnProperty("type") && !metadata.types.has(needle.schema.type)) {
|
272
287
|
this.throw(
|
273
|
-
`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(",")}`,
|
274
289
|
needle
|
275
290
|
);
|
276
291
|
}
|
@@ -308,27 +323,35 @@ var Cabidela = class {
|
|
308
323
|
}
|
309
324
|
}
|
310
325
|
if (needle.carryProperties) {
|
311
|
-
needle.evaluatedProperties.add(needle.path
|
326
|
+
needle.evaluatedProperties.add(pathToString(needle.path));
|
312
327
|
}
|
313
328
|
return 1;
|
314
329
|
}
|
315
330
|
if (this.options.applyDefaults === true && needle.schema.hasOwnProperty("default")) {
|
316
|
-
|
317
|
-
|
318
|
-
prev[curr]
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
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
|
+
}
|
326
348
|
}
|
327
349
|
return 0;
|
328
350
|
}
|
329
351
|
validate(payload) {
|
330
352
|
const needle = {
|
331
353
|
errors: /* @__PURE__ */ new Set(),
|
354
|
+
defaultsCallbacks: [],
|
332
355
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
333
356
|
path: [],
|
334
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
|
};
|
@@ -66,7 +66,9 @@ var Cabidela = class {
|
|
66
66
|
parseAdditionalProperties(needle, contextAdditionalProperties, contextEvaluatedProperties) {
|
67
67
|
let matchCount = 0;
|
68
68
|
const { metadata, resolvedObject } = resolvePayload(needle.path, needle.payload);
|
69
|
-
const unevaluatedProperties =
|
69
|
+
const unevaluatedProperties = new Set(
|
70
|
+
metadata.properties.map((r) => pathToString([...needle.path, r]))
|
71
|
+
).difference(contextEvaluatedProperties);
|
70
72
|
if (contextAdditionalProperties === false) {
|
71
73
|
if (unevaluatedProperties.size > 0) {
|
72
74
|
this.throw(
|
@@ -81,14 +83,15 @@ var Cabidela = class {
|
|
81
83
|
} else {
|
82
84
|
for (let property of unevaluatedProperties) {
|
83
85
|
if (this.parseSubSchema({
|
84
|
-
path: [property],
|
86
|
+
path: [property.split(".").slice(-1)[0]],
|
85
87
|
schema: contextAdditionalProperties,
|
86
88
|
payload: resolvedObject,
|
87
89
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
88
|
-
errors: /* @__PURE__ */ new Set()
|
90
|
+
errors: /* @__PURE__ */ new Set(),
|
91
|
+
defaultsCallbacks: []
|
89
92
|
})) {
|
90
93
|
matchCount++;
|
91
|
-
needle.evaluatedProperties.add(property);
|
94
|
+
needle.evaluatedProperties.add(pathToString([property]));
|
92
95
|
}
|
93
96
|
}
|
94
97
|
}
|
@@ -116,12 +119,13 @@ var Cabidela = class {
|
|
116
119
|
let matchCount = 0;
|
117
120
|
if (needle.schema.hasOwnProperty("properties")) {
|
118
121
|
for (let property in needle.schema.properties) {
|
119
|
-
|
122
|
+
const matches = this.parseSubSchema({
|
120
123
|
...needle,
|
121
124
|
path: [...needle.path, property],
|
122
125
|
schema: needle.schema.properties[property]
|
123
|
-
})
|
124
|
-
|
126
|
+
});
|
127
|
+
if (matches > 0) {
|
128
|
+
localEvaluatedProperties.add(pathToString([...needle.path, property]));
|
125
129
|
matchCount++;
|
126
130
|
}
|
127
131
|
}
|
@@ -142,7 +146,9 @@ var Cabidela = class {
|
|
142
146
|
);
|
143
147
|
}
|
144
148
|
if (needle.schema.hasOwnProperty("required")) {
|
145
|
-
if (new Set(needle.schema.required
|
149
|
+
if (new Set(needle.schema.required.map((r) => pathToString([...needle.path, r]))).difference(
|
150
|
+
needle.evaluatedProperties.union(localEvaluatedProperties)
|
151
|
+
).size > 0) {
|
146
152
|
this.throw(`required properties at '${pathToString(needle.path)}' is '${needle.schema.required}'`, needle);
|
147
153
|
}
|
148
154
|
}
|
@@ -150,19 +156,27 @@ var Cabidela = class {
|
|
150
156
|
}
|
151
157
|
parseList(list, needle, breakCondition) {
|
152
158
|
let rounds = 0;
|
159
|
+
const defaultsCallbacks = [];
|
153
160
|
for (let option in list) {
|
154
161
|
try {
|
155
|
-
|
162
|
+
const matches = this.parseSubSchema({
|
156
163
|
...needle,
|
157
164
|
schema: { type: needle.schema.type, ...list[option] },
|
158
|
-
carryProperties:
|
159
|
-
absorvErrors: true
|
165
|
+
carryProperties: false,
|
166
|
+
absorvErrors: true,
|
167
|
+
deferredApplyDefaults: true
|
160
168
|
});
|
169
|
+
rounds += matches;
|
161
170
|
if (breakCondition && breakCondition(rounds)) break;
|
171
|
+
defaultsCallbacks.push(...needle.defaultsCallbacks);
|
172
|
+
needle.defaultsCallbacks = [];
|
162
173
|
} catch (e) {
|
163
174
|
needle.errors.add(e.message);
|
175
|
+
needle.defaultsCallbacks = [];
|
164
176
|
}
|
165
177
|
}
|
178
|
+
for (const callback of defaultsCallbacks) callback();
|
179
|
+
needle.defaultsCallbacks = [];
|
166
180
|
return rounds;
|
167
181
|
}
|
168
182
|
// Parses a JSON Schema sub-schema object - reentrant
|
@@ -171,9 +185,10 @@ var Cabidela = class {
|
|
171
185
|
this.throw(`No schema for path '${pathToString(needle.path)}'`, needle);
|
172
186
|
}
|
173
187
|
if (needle.schema.hasOwnProperty("oneOf")) {
|
174
|
-
|
188
|
+
const rounds = this.parseList(needle.schema.oneOf, needle, (r) => r !== 1);
|
189
|
+
if (rounds !== 1) {
|
175
190
|
if (needle.path.length == 0) {
|
176
|
-
this.throw(`oneOf at '${pathToString(needle.path)}' not met`, needle);
|
191
|
+
this.throw(`oneOf at '${pathToString(needle.path)}' not met, ${rounds} matches`, needle);
|
177
192
|
}
|
178
193
|
return 0;
|
179
194
|
}
|
@@ -244,7 +259,7 @@ var Cabidela = class {
|
|
244
259
|
}
|
245
260
|
if (needle.schema.hasOwnProperty("type") && !metadata.types.has(needle.schema.type)) {
|
246
261
|
this.throw(
|
247
|
-
`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(",")}`,
|
248
263
|
needle
|
249
264
|
);
|
250
265
|
}
|
@@ -282,27 +297,35 @@ var Cabidela = class {
|
|
282
297
|
}
|
283
298
|
}
|
284
299
|
if (needle.carryProperties) {
|
285
|
-
needle.evaluatedProperties.add(needle.path
|
300
|
+
needle.evaluatedProperties.add(pathToString(needle.path));
|
286
301
|
}
|
287
302
|
return 1;
|
288
303
|
}
|
289
304
|
if (this.options.applyDefaults === true && needle.schema.hasOwnProperty("default")) {
|
290
|
-
|
291
|
-
|
292
|
-
prev[curr]
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
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
|
+
}
|
300
322
|
}
|
301
323
|
return 0;
|
302
324
|
}
|
303
325
|
validate(payload) {
|
304
326
|
const needle = {
|
305
327
|
errors: /* @__PURE__ */ new Set(),
|
328
|
+
defaultsCallbacks: [],
|
306
329
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
307
330
|
path: [],
|
308
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",
|