@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 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, [Cloudflare Workers](https://developers.cloudflare.com/workers/) compatible, dynamic JSON Schema validator.</em>
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
- # What is
13
+ ## What is
15
14
 
16
- Cabidela is a small, fast, eval-less, [Cloudflare Workers](https://developers.cloudflare.com/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.
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 applications we typically want to validate with the HTTP request, one payload at a time, and then we discard the validator.
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 on par or better than Ajv.
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 most use-cases, we think. However it's not complete. **Currently** we do not support:
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 = /* @__PURE__ */ new Set([]);
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 = new Set(Object.keys(value));
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
- needle.path,
98
- needle.payload
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
- if (this.parseSubSchema({
148
+ const matches = this.parseSubSchema({
153
149
  ...needle,
154
150
  path: [...needle.path, property],
155
151
  schema: needle.schema.properties[property]
156
- })) {
157
- localEvaluatedProperties.add(property);
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
- rounds += this.parseSubSchema({
188
+ const matches = this.parseSubSchema({
197
189
  ...needle,
198
190
  schema: { type: needle.schema.type, ...list[option] },
199
- carryProperties: true,
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
- if (this.parseList(needle.schema.oneOf, needle) !== 1) {
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 ${JSON.stringify(Array.from(metadata.types))}`,
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[needle.path.length - 1]);
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
- needle.path.reduce(function(prev, curr, index) {
349
- if (prev[curr] === void 0) {
350
- prev[curr] = {};
351
- }
352
- if (index == needle.path.length - 1) {
353
- prev[curr] = needle.schema.default;
354
- needle.evaluatedProperties.add(needle.path[needle.path.length - 1]);
355
- }
356
- return prev ? prev[curr] : void 0;
357
- }, needle.payload);
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 = /* @__PURE__ */ new Set([]);
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 = new Set(Object.keys(value));
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
- needle.path,
72
- needle.payload
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
- if (this.parseSubSchema({
122
+ const matches = this.parseSubSchema({
127
123
  ...needle,
128
124
  path: [...needle.path, property],
129
125
  schema: needle.schema.properties[property]
130
- })) {
131
- localEvaluatedProperties.add(property);
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
- rounds += this.parseSubSchema({
162
+ const matches = this.parseSubSchema({
171
163
  ...needle,
172
164
  schema: { type: needle.schema.type, ...list[option] },
173
- carryProperties: true,
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
- if (this.parseList(needle.schema.oneOf, needle) !== 1) {
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 ${JSON.stringify(Array.from(metadata.types))}`,
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[needle.path.length - 1]);
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
- needle.path.reduce(function(prev, curr, index) {
323
- if (prev[curr] === void 0) {
324
- prev[curr] = {};
325
- }
326
- if (index == needle.path.length - 1) {
327
- prev[curr] = needle.schema.default;
328
- needle.evaluatedProperties.add(needle.path[needle.path.length - 1]);
329
- }
330
- return prev ? prev[curr] : void 0;
331
- }, needle.payload);
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.17",
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",