@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 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 = /* @__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
  };
@@ -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 = metadata.properties.difference(contextEvaluatedProperties);
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
- if (this.parseSubSchema({
148
+ const matches = this.parseSubSchema({
146
149
  ...needle,
147
150
  path: [...needle.path, property],
148
151
  schema: needle.schema.properties[property]
149
- })) {
150
- localEvaluatedProperties.add(property);
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).difference(needle.evaluatedProperties.union(localEvaluatedProperties)).size > 0) {
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
- rounds += this.parseSubSchema({
188
+ const matches = this.parseSubSchema({
182
189
  ...needle,
183
190
  schema: { type: needle.schema.type, ...list[option] },
184
- carryProperties: true,
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
- if (this.parseList(needle.schema.oneOf, needle) !== 1) {
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 ${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(",")}`,
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[needle.path.length - 1]);
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
- needle.path.reduce(function(prev, curr, index) {
317
- if (prev[curr] === void 0) {
318
- prev[curr] = {};
319
- }
320
- if (index == needle.path.length - 1) {
321
- prev[curr] = needle.schema.default;
322
- needle.evaluatedProperties.add(needle.path[needle.path.length - 1]);
323
- }
324
- return prev ? prev[curr] : void 0;
325
- }, 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
+ }
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 = /* @__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
  };
@@ -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 = metadata.properties.difference(contextEvaluatedProperties);
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
- if (this.parseSubSchema({
122
+ const matches = this.parseSubSchema({
120
123
  ...needle,
121
124
  path: [...needle.path, property],
122
125
  schema: needle.schema.properties[property]
123
- })) {
124
- localEvaluatedProperties.add(property);
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).difference(needle.evaluatedProperties.union(localEvaluatedProperties)).size > 0) {
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
- rounds += this.parseSubSchema({
162
+ const matches = this.parseSubSchema({
156
163
  ...needle,
157
164
  schema: { type: needle.schema.type, ...list[option] },
158
- carryProperties: true,
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
- if (this.parseList(needle.schema.oneOf, needle) !== 1) {
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 ${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(",")}`,
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[needle.path.length - 1]);
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
- needle.path.reduce(function(prev, curr, index) {
291
- if (prev[curr] === void 0) {
292
- prev[curr] = {};
293
- }
294
- if (index == needle.path.length - 1) {
295
- prev[curr] = needle.schema.default;
296
- needle.evaluatedProperties.add(needle.path[needle.path.length - 1]);
297
- }
298
- return prev ? prev[curr] : void 0;
299
- }, 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
+ }
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.18",
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",