@cloudflare/cabidela 0.0.19 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +117 -6
- package/dist/index.d.mts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +104 -4
- package/dist/index.mjs +104 -4
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,19 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
+
## [0.1.2] - 2025-03-07
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- $merge support - see README for more information
|
10
|
+
|
11
|
+
## [0.1.1] - 2025-02-26
|
12
|
+
|
13
|
+
### Added
|
14
|
+
|
15
|
+
- Added support for $id, $ref and $defs - https://json-schema.org/understanding-json-schema/structuring
|
16
|
+
- Added support for not - https://json-schema.org/understanding-json-schema/reference/combining#not
|
17
|
+
|
5
18
|
## [0.0.19] - 2025-02-26
|
6
19
|
|
7
20
|
### Added
|
package/README.md
CHANGED
@@ -71,6 +71,8 @@ Cabidela takes a JSON-Schema and optional configuration flags:
|
|
71
71
|
- `applyDefaults`: boolean - If true, the validator will apply default values to the input object. Default is false.
|
72
72
|
- `errorMessages`: boolean - If true, the validator will use custom `errorMessage` messages from the schema. Default is false.
|
73
73
|
- `fullErrors`: boolean - If true, the validator will be more verbose when throwing errors for complex schemas (example: anyOf, oneOf's), set to false for shorter exceptions. Default is true.
|
74
|
+
- `useMerge`: boolean - Set to true if you want to use the `$merge` keyword. Default is false. See below for more information.
|
75
|
+
- `subSchema`: any[] - An optional array of sub-schemas that can be used with `$id` and `$ref`. See below for more information.
|
74
76
|
|
75
77
|
Returns a validation object.
|
76
78
|
|
@@ -180,9 +182,121 @@ example, using this schema:
|
|
180
182
|
- The payload `{ moon: 10}` will be modified to `{ sun: 9000, moon: 10 }`.
|
181
183
|
- The payload `{ saturn: 10}` will throw an error because no condition is met.
|
182
184
|
|
185
|
+
### $id, $ref, $defs
|
186
|
+
|
187
|
+
The keywords [$id](https://json-schema.org/understanding-json-schema/structuring#id), [$ref](https://json-schema.org/understanding-json-schema/structuring#dollarref) and [$defs](https://json-schema.org/understanding-json-schema/structuring#defs) can be used to build and maintain complex schemas where the reusable parts are defined in separate schemas.
|
188
|
+
|
189
|
+
The following is the main schema and a `customer` sub-schema that defines the `contacts` and `address` properties.
|
190
|
+
|
191
|
+
```js
|
192
|
+
import { Cabidela } from "@cloudflare/cabidela";
|
193
|
+
|
194
|
+
const schema = {
|
195
|
+
$id: "http://example.com/schemas/main",
|
196
|
+
type: "object",
|
197
|
+
properties: {
|
198
|
+
name: { type: "string" },
|
199
|
+
contacts: { $ref: "customer#/contacts" },
|
200
|
+
address: { $ref: "customer#/address" },
|
201
|
+
balance: { $ref: "$defs#/balance" },
|
202
|
+
},
|
203
|
+
required: ["name", "contacts", "address"],
|
204
|
+
"$defs": {
|
205
|
+
"balance": {
|
206
|
+
type: "object",
|
207
|
+
prope properties: {
|
208
|
+
currency: { type: "string" },
|
209
|
+
amount: { type: "number" },
|
210
|
+
},
|
211
|
+
}
|
212
|
+
}
|
213
|
+
};
|
214
|
+
|
215
|
+
const contactSchema = {
|
216
|
+
$id: "http://example.com/schemas/customer",
|
217
|
+
contacts: {
|
218
|
+
type: "object",
|
219
|
+
properties: {
|
220
|
+
email: { type: "string" },
|
221
|
+
phone: { type: "string" },
|
222
|
+
},
|
223
|
+
required: ["email", "phone"],
|
224
|
+
},
|
225
|
+
address: {
|
226
|
+
type: "object",
|
227
|
+
properties: {
|
228
|
+
street: { type: "string" },
|
229
|
+
city: { type: "string" },
|
230
|
+
zip: { type: "string" },
|
231
|
+
country: { type: "string" },
|
232
|
+
},
|
233
|
+
required: ["street", "city", "zip", "country"],
|
234
|
+
},
|
235
|
+
};
|
236
|
+
|
237
|
+
const cabidela = new Cabidela(schema, { subSchemas: [contactSchema] });
|
238
|
+
|
239
|
+
cabidela.validate({
|
240
|
+
name: "John",
|
241
|
+
contacts: {
|
242
|
+
email: "john@example.com",
|
243
|
+
phone: "+123456789",
|
244
|
+
},
|
245
|
+
address: {
|
246
|
+
street: "123 Main St",
|
247
|
+
city: "San Francisco",
|
248
|
+
zip: "94105",
|
249
|
+
country: "USA",
|
250
|
+
},
|
251
|
+
});
|
252
|
+
```
|
253
|
+
|
254
|
+
## Combined schemas and $merge
|
255
|
+
|
256
|
+
The standard way of combining and extending schemas is by using the [`allOf`](https://json-schema.org/understanding-json-schema/reference/combining#allOf) (AND), [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) (OR), [`oneOf`](https://json-schema.org/understanding-json-schema/reference/combining#oneOf) (XOR) and [`not`](https://json-schema.org/understanding-json-schema/reference/combining#not) keywords, all supported by this library.
|
257
|
+
|
258
|
+
Cabidela supports an additional keyword `$merge` (inspired by [Ajv](https://ajv.js.org/guide/combining-schemas.html#merge-and-patch-keywords)) that allows you to merge two objects. This is useful when you want to extend a schema with additional properties and `allOf`` is not enough.
|
259
|
+
|
260
|
+
Here's how it works:
|
261
|
+
|
262
|
+
```json
|
263
|
+
{
|
264
|
+
"$merge": {
|
265
|
+
"source": {
|
266
|
+
"type": "object",
|
267
|
+
"properties": { "p": { "type": "string" } },
|
268
|
+
"additionalProperties": false
|
269
|
+
},
|
270
|
+
"with": {
|
271
|
+
"properties": { "q": { "type": "number" } }
|
272
|
+
}
|
273
|
+
}
|
274
|
+
}
|
275
|
+
```
|
276
|
+
|
277
|
+
Resolves to:
|
278
|
+
|
279
|
+
```json
|
280
|
+
{
|
281
|
+
"type": "object",
|
282
|
+
"properties": {
|
283
|
+
"q": {
|
284
|
+
"type": "number"
|
285
|
+
}
|
286
|
+
},
|
287
|
+
"additionalProperties": false
|
288
|
+
}
|
289
|
+
```
|
290
|
+
|
291
|
+
To use `$merge` set the `useMerge` flag to true when creating the instance.
|
292
|
+
|
293
|
+
```js
|
294
|
+
new Cabidela(schema, { useMerge: true });
|
295
|
+
```
|
296
|
+
|
183
297
|
## Custom errors
|
184
298
|
|
185
|
-
If the new instance options has the
|
299
|
+
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.
|
186
300
|
|
187
301
|
```js
|
188
302
|
const schema = {
|
@@ -204,7 +318,7 @@ const payload = {
|
|
204
318
|
|
205
319
|
cabidela.validate(payload);
|
206
320
|
// throws "Error: prompt required"
|
207
|
-
|
321
|
+
```
|
208
322
|
|
209
323
|
## Tests
|
210
324
|
|
@@ -262,7 +376,7 @@ Here are some results:
|
|
262
376
|
59.75x faster than Ajv
|
263
377
|
|
264
378
|
Cabidela - benchmarks/80-big-ops.bench.js > allOf, two properties
|
265
|
-
|
379
|
+
1701.95x faster than Ajv
|
266
380
|
|
267
381
|
Cabidela - benchmarks/80-big-ops.bench.js > allOf, two objects
|
268
382
|
1307.04x faster than Ajv
|
@@ -285,10 +399,7 @@ npm run benchmark
|
|
285
399
|
Cabidela supports most of JSON Schema specification, and should be useful for many applications, but it's not complete. **Currently** we do not support:
|
286
400
|
|
287
401
|
- Multiple (array of) types `{ "type": ["number", "string"] }`
|
288
|
-
- Regular expressions
|
289
402
|
- Pattern properties
|
290
|
-
- `not`
|
291
403
|
- `dependentRequired`, `dependentSchemas`, `If-Then-Else`
|
292
|
-
- `$ref`, `$defs` and `$id`
|
293
404
|
|
294
405
|
yet.
|
package/dist/index.d.mts
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
type CabidelaOptions = {
|
2
2
|
applyDefaults?: boolean;
|
3
|
+
useMerge?: boolean;
|
3
4
|
errorMessages?: boolean;
|
4
5
|
fullErrors?: boolean;
|
6
|
+
subSchemas?: Array<any>;
|
5
7
|
};
|
6
8
|
type SchemaNavigation = {
|
7
9
|
path: Array<string>;
|
@@ -15,10 +17,13 @@ type SchemaNavigation = {
|
|
15
17
|
defaultsCallbacks: Array<any>;
|
16
18
|
};
|
17
19
|
declare class Cabidela {
|
18
|
-
schema
|
19
|
-
options
|
20
|
+
private schema;
|
21
|
+
private options;
|
22
|
+
private definitions;
|
20
23
|
constructor(schema: any, options?: CabidelaOptions);
|
21
24
|
setSchema(schema: any): void;
|
25
|
+
addSchema(subSchema: any, combine?: boolean): void;
|
26
|
+
getSchema(): any;
|
22
27
|
setOptions(options: CabidelaOptions): void;
|
23
28
|
throw(message: string, needle: SchemaNavigation): void;
|
24
29
|
parseAdditionalProperties(needle: SchemaNavigation, contextAdditionalProperties: any, contextEvaluatedProperties: Set<string>): number;
|
package/dist/index.d.ts
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
type CabidelaOptions = {
|
2
2
|
applyDefaults?: boolean;
|
3
|
+
useMerge?: boolean;
|
3
4
|
errorMessages?: boolean;
|
4
5
|
fullErrors?: boolean;
|
6
|
+
subSchemas?: Array<any>;
|
5
7
|
};
|
6
8
|
type SchemaNavigation = {
|
7
9
|
path: Array<string>;
|
@@ -15,10 +17,13 @@ type SchemaNavigation = {
|
|
15
17
|
defaultsCallbacks: Array<any>;
|
16
18
|
};
|
17
19
|
declare class Cabidela {
|
18
|
-
schema
|
19
|
-
options
|
20
|
+
private schema;
|
21
|
+
private options;
|
22
|
+
private definitions;
|
20
23
|
constructor(schema: any, options?: CabidelaOptions);
|
21
24
|
setSchema(schema: any): void;
|
25
|
+
addSchema(subSchema: any, combine?: boolean): void;
|
26
|
+
getSchema(): any;
|
22
27
|
setOptions(options: CabidelaOptions): void;
|
23
28
|
throw(message: string, needle: SchemaNavigation): void;
|
24
29
|
parseAdditionalProperties(needle: SchemaNavigation, contextAdditionalProperties: any, contextEvaluatedProperties: Set<string>): number;
|
package/dist/index.js
CHANGED
@@ -25,6 +25,52 @@ __export(index_exports, {
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
26
26
|
|
27
27
|
// src/helpers.ts
|
28
|
+
var parse$ref = (ref) => {
|
29
|
+
const parts = ref.split("#");
|
30
|
+
const $id = parts[0];
|
31
|
+
const $path = parts[1].split("/").filter((part) => part !== "");
|
32
|
+
return { $id, $path };
|
33
|
+
};
|
34
|
+
function deepMerge(target, source) {
|
35
|
+
const result = Array.isArray(target) && Array.isArray(source) ? target.concat(source) : { ...target, ...source };
|
36
|
+
for (const key of Object.keys(result)) {
|
37
|
+
result[key] = typeof target[key] == "object" && typeof source[key] == "object" ? deepMerge(target[key], source[key]) : structuredClone(result[key]);
|
38
|
+
}
|
39
|
+
return result;
|
40
|
+
}
|
41
|
+
var traverseSchema = (options, definitions, obj, cb) => {
|
42
|
+
let hits;
|
43
|
+
do {
|
44
|
+
hits = 0;
|
45
|
+
Object.keys(obj).forEach((key) => {
|
46
|
+
if (obj[key] !== null && typeof obj[key] === "object") {
|
47
|
+
traverseSchema(options, definitions, obj[key], (value) => {
|
48
|
+
hits++;
|
49
|
+
obj[key] = value;
|
50
|
+
});
|
51
|
+
if (options.useMerge && key === "$merge") {
|
52
|
+
const merge = deepMerge(obj[key].source, obj[key].with);
|
53
|
+
if (cb) {
|
54
|
+
cb(merge);
|
55
|
+
} else {
|
56
|
+
Object.assign(obj, merge);
|
57
|
+
delete obj[key];
|
58
|
+
}
|
59
|
+
}
|
60
|
+
} else {
|
61
|
+
if (key === "$ref") {
|
62
|
+
const { $id, $path } = parse$ref(obj[key]);
|
63
|
+
const { resolvedObject } = resolvePayload($path, definitions[$id]);
|
64
|
+
if (resolvedObject) {
|
65
|
+
cb(resolvedObject);
|
66
|
+
} else {
|
67
|
+
throw new Error(`Could not resolve '${obj[key]}' $ref`);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
});
|
72
|
+
} while (hits > 0);
|
73
|
+
};
|
28
74
|
var resolvePayload = (path, obj) => {
|
29
75
|
let resolvedObject = path.reduce(function(prev, curr) {
|
30
76
|
return prev ? prev[curr] : void 0;
|
@@ -32,7 +78,7 @@ var resolvePayload = (path, obj) => {
|
|
32
78
|
return { metadata: getMetaData(resolvedObject), resolvedObject };
|
33
79
|
};
|
34
80
|
var pathToString = (path) => {
|
35
|
-
return path.length == 0 ?
|
81
|
+
return path.length == 0 ? `/` : path.map((item) => `/${item}`).join("");
|
36
82
|
};
|
37
83
|
var getMetaData = (value) => {
|
38
84
|
let size = 0;
|
@@ -70,20 +116,52 @@ var getMetaData = (value) => {
|
|
70
116
|
var Cabidela = class {
|
71
117
|
schema;
|
72
118
|
options;
|
119
|
+
definitions = {};
|
73
120
|
constructor(schema, options) {
|
74
121
|
this.schema = schema;
|
75
122
|
this.options = {
|
76
123
|
fullErrors: true,
|
124
|
+
subSchemas: [],
|
77
125
|
applyDefaults: false,
|
78
126
|
errorMessages: false,
|
79
127
|
...options || {}
|
80
128
|
};
|
129
|
+
if (this.schema.hasOwnProperty("$defs")) {
|
130
|
+
this.definitions["$defs"] = this.schema["$defs"];
|
131
|
+
delete this.schema["$defs"];
|
132
|
+
}
|
133
|
+
if (this.options.subSchemas.length > 0) {
|
134
|
+
for (const subSchema of this.options.subSchemas) {
|
135
|
+
this.addSchema(subSchema, false);
|
136
|
+
}
|
137
|
+
}
|
138
|
+
if (this.options.useMerge || this.options.subSchemas.length > 0) {
|
139
|
+
traverseSchema(this.options, this.definitions, this.schema);
|
140
|
+
}
|
81
141
|
}
|
82
142
|
setSchema(schema) {
|
83
143
|
this.schema = schema;
|
84
144
|
}
|
145
|
+
addSchema(subSchema, combine = true) {
|
146
|
+
if (subSchema.hasOwnProperty("$id")) {
|
147
|
+
const url = URL.parse(subSchema["$id"]);
|
148
|
+
if (url) {
|
149
|
+
this.definitions[url.pathname.split("/").slice(-1)[0]] = subSchema;
|
150
|
+
} else {
|
151
|
+
throw new Error(
|
152
|
+
"subSchemas need a valid retrieval URI $id https://json-schema.org/understanding-json-schema/structuring#retrieval-uri"
|
153
|
+
);
|
154
|
+
}
|
155
|
+
} else {
|
156
|
+
throw new Error("subSchemas need $id https://json-schema.org/understanding-json-schema/structuring#id");
|
157
|
+
}
|
158
|
+
if (combine == true) traverseSchema(this.options, this.definitions, this.schema);
|
159
|
+
}
|
160
|
+
getSchema() {
|
161
|
+
return this.schema;
|
162
|
+
}
|
85
163
|
setOptions(options) {
|
86
|
-
this.options = options;
|
164
|
+
this.options = { ...this.options, ...options };
|
87
165
|
}
|
88
166
|
throw(message, needle) {
|
89
167
|
const error = `${message}${this.options.fullErrors && needle.absorvErrors !== true && needle.errors.size > 0 ? `: ${Array.from(needle.errors).join(", ")}` : ``}`;
|
@@ -109,7 +187,7 @@ var Cabidela = class {
|
|
109
187
|
} else {
|
110
188
|
for (let property of unevaluatedProperties) {
|
111
189
|
if (this.parseSubSchema({
|
112
|
-
path: [property.split("
|
190
|
+
path: [property.split("/").slice(-1)[0]],
|
113
191
|
schema: contextAdditionalProperties,
|
114
192
|
payload: resolvedObject,
|
115
193
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
@@ -175,7 +253,7 @@ var Cabidela = class {
|
|
175
253
|
if (new Set(needle.schema.required.map((r) => pathToString([...needle.path, r]))).difference(
|
176
254
|
needle.evaluatedProperties.union(localEvaluatedProperties)
|
177
255
|
).size > 0) {
|
178
|
-
this.throw(`required properties at '${pathToString(needle.path)}'
|
256
|
+
this.throw(`required properties at '${pathToString(needle.path)}' are '${needle.schema.required}'`, needle);
|
179
257
|
}
|
180
258
|
}
|
181
259
|
return matchCount ? true : false;
|
@@ -210,6 +288,20 @@ var Cabidela = class {
|
|
210
288
|
if (needle.schema == void 0) {
|
211
289
|
this.throw(`No schema for path '${pathToString(needle.path)}'`, needle);
|
212
290
|
}
|
291
|
+
if (needle.schema.hasOwnProperty("not")) {
|
292
|
+
let pass = false;
|
293
|
+
try {
|
294
|
+
this.parseSubSchema({
|
295
|
+
...needle,
|
296
|
+
schema: needle.schema.not
|
297
|
+
});
|
298
|
+
} catch (e) {
|
299
|
+
pass = true;
|
300
|
+
}
|
301
|
+
if (pass == false) {
|
302
|
+
this.throw(`not at '${pathToString(needle.path)}' not met`, needle);
|
303
|
+
}
|
304
|
+
}
|
213
305
|
if (needle.schema.hasOwnProperty("oneOf")) {
|
214
306
|
const rounds = this.parseList(needle.schema.oneOf, needle, (r) => r !== 1);
|
215
307
|
if (rounds !== 1) {
|
@@ -322,6 +414,14 @@ var Cabidela = class {
|
|
322
414
|
break;
|
323
415
|
}
|
324
416
|
}
|
417
|
+
if (needle.schema.hasOwnProperty("pattern")) {
|
418
|
+
let passes = false;
|
419
|
+
try {
|
420
|
+
if (new RegExp(needle.schema.pattern).test(resolvedObject)) passes = true;
|
421
|
+
} catch (e) {
|
422
|
+
}
|
423
|
+
if (!passes) this.throw(`'${pathToString(needle.path)}' failed test ${needle.schema.pattern} patttern`, needle);
|
424
|
+
}
|
325
425
|
if (needle.carryProperties) {
|
326
426
|
needle.evaluatedProperties.add(pathToString(needle.path));
|
327
427
|
}
|
package/dist/index.mjs
CHANGED
@@ -1,4 +1,50 @@
|
|
1
1
|
// src/helpers.ts
|
2
|
+
var parse$ref = (ref) => {
|
3
|
+
const parts = ref.split("#");
|
4
|
+
const $id = parts[0];
|
5
|
+
const $path = parts[1].split("/").filter((part) => part !== "");
|
6
|
+
return { $id, $path };
|
7
|
+
};
|
8
|
+
function deepMerge(target, source) {
|
9
|
+
const result = Array.isArray(target) && Array.isArray(source) ? target.concat(source) : { ...target, ...source };
|
10
|
+
for (const key of Object.keys(result)) {
|
11
|
+
result[key] = typeof target[key] == "object" && typeof source[key] == "object" ? deepMerge(target[key], source[key]) : structuredClone(result[key]);
|
12
|
+
}
|
13
|
+
return result;
|
14
|
+
}
|
15
|
+
var traverseSchema = (options, definitions, obj, cb) => {
|
16
|
+
let hits;
|
17
|
+
do {
|
18
|
+
hits = 0;
|
19
|
+
Object.keys(obj).forEach((key) => {
|
20
|
+
if (obj[key] !== null && typeof obj[key] === "object") {
|
21
|
+
traverseSchema(options, definitions, obj[key], (value) => {
|
22
|
+
hits++;
|
23
|
+
obj[key] = value;
|
24
|
+
});
|
25
|
+
if (options.useMerge && key === "$merge") {
|
26
|
+
const merge = deepMerge(obj[key].source, obj[key].with);
|
27
|
+
if (cb) {
|
28
|
+
cb(merge);
|
29
|
+
} else {
|
30
|
+
Object.assign(obj, merge);
|
31
|
+
delete obj[key];
|
32
|
+
}
|
33
|
+
}
|
34
|
+
} else {
|
35
|
+
if (key === "$ref") {
|
36
|
+
const { $id, $path } = parse$ref(obj[key]);
|
37
|
+
const { resolvedObject } = resolvePayload($path, definitions[$id]);
|
38
|
+
if (resolvedObject) {
|
39
|
+
cb(resolvedObject);
|
40
|
+
} else {
|
41
|
+
throw new Error(`Could not resolve '${obj[key]}' $ref`);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
});
|
46
|
+
} while (hits > 0);
|
47
|
+
};
|
2
48
|
var resolvePayload = (path, obj) => {
|
3
49
|
let resolvedObject = path.reduce(function(prev, curr) {
|
4
50
|
return prev ? prev[curr] : void 0;
|
@@ -6,7 +52,7 @@ var resolvePayload = (path, obj) => {
|
|
6
52
|
return { metadata: getMetaData(resolvedObject), resolvedObject };
|
7
53
|
};
|
8
54
|
var pathToString = (path) => {
|
9
|
-
return path.length == 0 ?
|
55
|
+
return path.length == 0 ? `/` : path.map((item) => `/${item}`).join("");
|
10
56
|
};
|
11
57
|
var getMetaData = (value) => {
|
12
58
|
let size = 0;
|
@@ -44,20 +90,52 @@ var getMetaData = (value) => {
|
|
44
90
|
var Cabidela = class {
|
45
91
|
schema;
|
46
92
|
options;
|
93
|
+
definitions = {};
|
47
94
|
constructor(schema, options) {
|
48
95
|
this.schema = schema;
|
49
96
|
this.options = {
|
50
97
|
fullErrors: true,
|
98
|
+
subSchemas: [],
|
51
99
|
applyDefaults: false,
|
52
100
|
errorMessages: false,
|
53
101
|
...options || {}
|
54
102
|
};
|
103
|
+
if (this.schema.hasOwnProperty("$defs")) {
|
104
|
+
this.definitions["$defs"] = this.schema["$defs"];
|
105
|
+
delete this.schema["$defs"];
|
106
|
+
}
|
107
|
+
if (this.options.subSchemas.length > 0) {
|
108
|
+
for (const subSchema of this.options.subSchemas) {
|
109
|
+
this.addSchema(subSchema, false);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
if (this.options.useMerge || this.options.subSchemas.length > 0) {
|
113
|
+
traverseSchema(this.options, this.definitions, this.schema);
|
114
|
+
}
|
55
115
|
}
|
56
116
|
setSchema(schema) {
|
57
117
|
this.schema = schema;
|
58
118
|
}
|
119
|
+
addSchema(subSchema, combine = true) {
|
120
|
+
if (subSchema.hasOwnProperty("$id")) {
|
121
|
+
const url = URL.parse(subSchema["$id"]);
|
122
|
+
if (url) {
|
123
|
+
this.definitions[url.pathname.split("/").slice(-1)[0]] = subSchema;
|
124
|
+
} else {
|
125
|
+
throw new Error(
|
126
|
+
"subSchemas need a valid retrieval URI $id https://json-schema.org/understanding-json-schema/structuring#retrieval-uri"
|
127
|
+
);
|
128
|
+
}
|
129
|
+
} else {
|
130
|
+
throw new Error("subSchemas need $id https://json-schema.org/understanding-json-schema/structuring#id");
|
131
|
+
}
|
132
|
+
if (combine == true) traverseSchema(this.options, this.definitions, this.schema);
|
133
|
+
}
|
134
|
+
getSchema() {
|
135
|
+
return this.schema;
|
136
|
+
}
|
59
137
|
setOptions(options) {
|
60
|
-
this.options = options;
|
138
|
+
this.options = { ...this.options, ...options };
|
61
139
|
}
|
62
140
|
throw(message, needle) {
|
63
141
|
const error = `${message}${this.options.fullErrors && needle.absorvErrors !== true && needle.errors.size > 0 ? `: ${Array.from(needle.errors).join(", ")}` : ``}`;
|
@@ -83,7 +161,7 @@ var Cabidela = class {
|
|
83
161
|
} else {
|
84
162
|
for (let property of unevaluatedProperties) {
|
85
163
|
if (this.parseSubSchema({
|
86
|
-
path: [property.split("
|
164
|
+
path: [property.split("/").slice(-1)[0]],
|
87
165
|
schema: contextAdditionalProperties,
|
88
166
|
payload: resolvedObject,
|
89
167
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
@@ -149,7 +227,7 @@ var Cabidela = class {
|
|
149
227
|
if (new Set(needle.schema.required.map((r) => pathToString([...needle.path, r]))).difference(
|
150
228
|
needle.evaluatedProperties.union(localEvaluatedProperties)
|
151
229
|
).size > 0) {
|
152
|
-
this.throw(`required properties at '${pathToString(needle.path)}'
|
230
|
+
this.throw(`required properties at '${pathToString(needle.path)}' are '${needle.schema.required}'`, needle);
|
153
231
|
}
|
154
232
|
}
|
155
233
|
return matchCount ? true : false;
|
@@ -184,6 +262,20 @@ var Cabidela = class {
|
|
184
262
|
if (needle.schema == void 0) {
|
185
263
|
this.throw(`No schema for path '${pathToString(needle.path)}'`, needle);
|
186
264
|
}
|
265
|
+
if (needle.schema.hasOwnProperty("not")) {
|
266
|
+
let pass = false;
|
267
|
+
try {
|
268
|
+
this.parseSubSchema({
|
269
|
+
...needle,
|
270
|
+
schema: needle.schema.not
|
271
|
+
});
|
272
|
+
} catch (e) {
|
273
|
+
pass = true;
|
274
|
+
}
|
275
|
+
if (pass == false) {
|
276
|
+
this.throw(`not at '${pathToString(needle.path)}' not met`, needle);
|
277
|
+
}
|
278
|
+
}
|
187
279
|
if (needle.schema.hasOwnProperty("oneOf")) {
|
188
280
|
const rounds = this.parseList(needle.schema.oneOf, needle, (r) => r !== 1);
|
189
281
|
if (rounds !== 1) {
|
@@ -296,6 +388,14 @@ var Cabidela = class {
|
|
296
388
|
break;
|
297
389
|
}
|
298
390
|
}
|
391
|
+
if (needle.schema.hasOwnProperty("pattern")) {
|
392
|
+
let passes = false;
|
393
|
+
try {
|
394
|
+
if (new RegExp(needle.schema.pattern).test(resolvedObject)) passes = true;
|
395
|
+
} catch (e) {
|
396
|
+
}
|
397
|
+
if (!passes) this.throw(`'${pathToString(needle.path)}' failed test ${needle.schema.pattern} patttern`, needle);
|
398
|
+
}
|
299
399
|
if (needle.carryProperties) {
|
300
400
|
needle.evaluatedProperties.add(pathToString(needle.path));
|
301
401
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@cloudflare/cabidela",
|
3
|
-
"version": "0.0
|
3
|
+
"version": "0.2.0",
|
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",
|
@@ -27,6 +27,9 @@
|
|
27
27
|
"README.md",
|
28
28
|
"CHANGELOG.md"
|
29
29
|
],
|
30
|
+
"prettier": {
|
31
|
+
"embeddedLanguageFormatting": "auto"
|
32
|
+
},
|
30
33
|
"repository": {
|
31
34
|
"type": "git",
|
32
35
|
"url": "https://github.com/cloudflare/cabidela.git"
|
@@ -39,6 +42,7 @@
|
|
39
42
|
"@vitest/ui": "^3.0.3",
|
40
43
|
"ajv": "^8.17.1",
|
41
44
|
"ajv-errors": "^3.0.0",
|
45
|
+
"ajv-merge-patch": "^5.0.1",
|
42
46
|
"tsup": "^8.3.6",
|
43
47
|
"typescript": "^5.7.3",
|
44
48
|
"vitest": "^3.0.3"
|