@cloudflare/cabidela 0.0.19 → 0.1.1
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 +7 -0
- package/README.md +72 -6
- package/dist/index.d.mts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +82 -4
- package/dist/index.mjs +82 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
+
## [0.1.1] - 2025-02-26
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- Added support for $id, $ref and $defs - https://json-schema.org/understanding-json-schema/structuring
|
10
|
+
- Added support for not - https://json-schema.org/understanding-json-schema/reference/combining#not
|
11
|
+
|
5
12
|
## [0.0.19] - 2025-02-26
|
6
13
|
|
7
14
|
### Added
|
package/README.md
CHANGED
@@ -180,9 +180,78 @@ example, using this schema:
|
|
180
180
|
- The payload `{ moon: 10}` will be modified to `{ sun: 9000, moon: 10 }`.
|
181
181
|
- The payload `{ saturn: 10}` will throw an error because no condition is met.
|
182
182
|
|
183
|
+
### $id, $ref, $defs
|
184
|
+
|
185
|
+
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.
|
186
|
+
|
187
|
+
The following is the main schema and a `customer` sub-schema that defines the `contacts` and `address` properties.
|
188
|
+
|
189
|
+
```js
|
190
|
+
import { Cabidela } from "@cloudflare/cabidela";
|
191
|
+
|
192
|
+
const schema = {
|
193
|
+
$id: "http://example.com/schemas/main",
|
194
|
+
type: "object",
|
195
|
+
properties: {
|
196
|
+
name: { type: "string" },
|
197
|
+
contacts: { $ref: "customer#/contacts" },
|
198
|
+
address: { $ref: "customer#/address" },
|
199
|
+
balance: { $ref: "$defs#/balance" },
|
200
|
+
},
|
201
|
+
required: ["name", "contacts", "address"],
|
202
|
+
"$defs": {
|
203
|
+
"balance": {
|
204
|
+
type: "object",
|
205
|
+
prope properties: {
|
206
|
+
currency: { type: "string" },
|
207
|
+
amount: { type: "number" },
|
208
|
+
},
|
209
|
+
}
|
210
|
+
}
|
211
|
+
};
|
212
|
+
|
213
|
+
const contactSchema = {
|
214
|
+
$id: "http://example.com/schemas/customer",
|
215
|
+
contacts: {
|
216
|
+
type: "object",
|
217
|
+
properties: {
|
218
|
+
email: { type: "string" },
|
219
|
+
phone: { type: "string" },
|
220
|
+
},
|
221
|
+
required: ["email", "phone"],
|
222
|
+
},
|
223
|
+
address: {
|
224
|
+
type: "object",
|
225
|
+
properties: {
|
226
|
+
street: { type: "string" },
|
227
|
+
city: { type: "string" },
|
228
|
+
zip: { type: "string" },
|
229
|
+
country: { type: "string" },
|
230
|
+
},
|
231
|
+
required: ["street", "city", "zip", "country"],
|
232
|
+
},
|
233
|
+
};
|
234
|
+
|
235
|
+
const cabidela = new Cabidela(schema, { subSchemas: [contactSchema] });
|
236
|
+
|
237
|
+
cabidela.validate({
|
238
|
+
name: "John",
|
239
|
+
contacts: {
|
240
|
+
email: "john@example.com",
|
241
|
+
phone: "+123456789",
|
242
|
+
},
|
243
|
+
address: {
|
244
|
+
street: "123 Main St",
|
245
|
+
city: "San Francisco",
|
246
|
+
zip: "94105",
|
247
|
+
country: "USA",
|
248
|
+
},
|
249
|
+
});
|
250
|
+
```
|
251
|
+
|
183
252
|
## Custom errors
|
184
253
|
|
185
|
-
If the new instance options has the
|
254
|
+
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
255
|
|
187
256
|
```js
|
188
257
|
const schema = {
|
@@ -204,7 +273,7 @@ const payload = {
|
|
204
273
|
|
205
274
|
cabidela.validate(payload);
|
206
275
|
// throws "Error: prompt required"
|
207
|
-
|
276
|
+
```
|
208
277
|
|
209
278
|
## Tests
|
210
279
|
|
@@ -262,7 +331,7 @@ Here are some results:
|
|
262
331
|
59.75x faster than Ajv
|
263
332
|
|
264
333
|
Cabidela - benchmarks/80-big-ops.bench.js > allOf, two properties
|
265
|
-
|
334
|
+
1701.95x faster than Ajv
|
266
335
|
|
267
336
|
Cabidela - benchmarks/80-big-ops.bench.js > allOf, two objects
|
268
337
|
1307.04x faster than Ajv
|
@@ -285,10 +354,7 @@ npm run benchmark
|
|
285
354
|
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
355
|
|
287
356
|
- Multiple (array of) types `{ "type": ["number", "string"] }`
|
288
|
-
- Regular expressions
|
289
357
|
- Pattern properties
|
290
|
-
- `not`
|
291
358
|
- `dependentRequired`, `dependentSchemas`, `If-Then-Else`
|
292
|
-
- `$ref`, `$defs` and `$id`
|
293
359
|
|
294
360
|
yet.
|
package/dist/index.d.mts
CHANGED
@@ -2,6 +2,7 @@ type CabidelaOptions = {
|
|
2
2
|
applyDefaults?: boolean;
|
3
3
|
errorMessages?: boolean;
|
4
4
|
fullErrors?: boolean;
|
5
|
+
subSchemas?: Array<any>;
|
5
6
|
};
|
6
7
|
type SchemaNavigation = {
|
7
8
|
path: Array<string>;
|
@@ -15,10 +16,13 @@ type SchemaNavigation = {
|
|
15
16
|
defaultsCallbacks: Array<any>;
|
16
17
|
};
|
17
18
|
declare class Cabidela {
|
18
|
-
schema
|
19
|
-
options
|
19
|
+
private schema;
|
20
|
+
private options;
|
21
|
+
private definitions;
|
20
22
|
constructor(schema: any, options?: CabidelaOptions);
|
21
23
|
setSchema(schema: any): void;
|
24
|
+
addSchema(subSchema: any, combine?: boolean): void;
|
25
|
+
getSchema(): any;
|
22
26
|
setOptions(options: CabidelaOptions): void;
|
23
27
|
throw(message: string, needle: SchemaNavigation): void;
|
24
28
|
parseAdditionalProperties(needle: SchemaNavigation, contextAdditionalProperties: any, contextEvaluatedProperties: Set<string>): number;
|
package/dist/index.d.ts
CHANGED
@@ -2,6 +2,7 @@ type CabidelaOptions = {
|
|
2
2
|
applyDefaults?: boolean;
|
3
3
|
errorMessages?: boolean;
|
4
4
|
fullErrors?: boolean;
|
5
|
+
subSchemas?: Array<any>;
|
5
6
|
};
|
6
7
|
type SchemaNavigation = {
|
7
8
|
path: Array<string>;
|
@@ -15,10 +16,13 @@ type SchemaNavigation = {
|
|
15
16
|
defaultsCallbacks: Array<any>;
|
16
17
|
};
|
17
18
|
declare class Cabidela {
|
18
|
-
schema
|
19
|
-
options
|
19
|
+
private schema;
|
20
|
+
private options;
|
21
|
+
private definitions;
|
20
22
|
constructor(schema: any, options?: CabidelaOptions);
|
21
23
|
setSchema(schema: any): void;
|
24
|
+
addSchema(subSchema: any, combine?: boolean): void;
|
25
|
+
getSchema(): any;
|
22
26
|
setOptions(options: CabidelaOptions): void;
|
23
27
|
throw(message: string, needle: SchemaNavigation): void;
|
24
28
|
parseAdditionalProperties(needle: SchemaNavigation, contextAdditionalProperties: any, contextEvaluatedProperties: Set<string>): number;
|
package/dist/index.js
CHANGED
@@ -25,6 +25,32 @@ __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
|
+
var traverseSchema = (definitions, obj, cb = () => {
|
35
|
+
}) => {
|
36
|
+
Object.keys(obj).forEach((key) => {
|
37
|
+
if (obj[key] !== null && typeof obj[key] === "object") {
|
38
|
+
traverseSchema(definitions, obj[key], (value) => {
|
39
|
+
obj[key] = value;
|
40
|
+
});
|
41
|
+
} else {
|
42
|
+
if (key === "$ref") {
|
43
|
+
const { $id, $path } = parse$ref(obj[key]);
|
44
|
+
const { resolvedObject } = resolvePayload($path, definitions[$id]);
|
45
|
+
if (resolvedObject) {
|
46
|
+
cb(resolvedObject);
|
47
|
+
} else {
|
48
|
+
throw new Error(`Could not resolve '${obj[key]}' $ref`);
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
});
|
53
|
+
};
|
28
54
|
var resolvePayload = (path, obj) => {
|
29
55
|
let resolvedObject = path.reduce(function(prev, curr) {
|
30
56
|
return prev ? prev[curr] : void 0;
|
@@ -32,7 +58,7 @@ var resolvePayload = (path, obj) => {
|
|
32
58
|
return { metadata: getMetaData(resolvedObject), resolvedObject };
|
33
59
|
};
|
34
60
|
var pathToString = (path) => {
|
35
|
-
return path.length == 0 ?
|
61
|
+
return path.length == 0 ? `/` : path.map((item) => `/${item}`).join("");
|
36
62
|
};
|
37
63
|
var getMetaData = (value) => {
|
38
64
|
let size = 0;
|
@@ -70,20 +96,50 @@ var getMetaData = (value) => {
|
|
70
96
|
var Cabidela = class {
|
71
97
|
schema;
|
72
98
|
options;
|
99
|
+
definitions = {};
|
73
100
|
constructor(schema, options) {
|
74
101
|
this.schema = schema;
|
75
102
|
this.options = {
|
76
103
|
fullErrors: true,
|
104
|
+
subSchemas: [],
|
77
105
|
applyDefaults: false,
|
78
106
|
errorMessages: false,
|
79
107
|
...options || {}
|
80
108
|
};
|
109
|
+
if (this.schema.hasOwnProperty("$defs")) {
|
110
|
+
this.definitions["$defs"] = this.schema["$defs"];
|
111
|
+
delete this.schema["$defs"];
|
112
|
+
}
|
113
|
+
if (this.options.subSchemas.length > 0) {
|
114
|
+
for (const subSchema of this.options.subSchemas) {
|
115
|
+
this.addSchema(subSchema, false);
|
116
|
+
}
|
117
|
+
traverseSchema(this.definitions, this.schema);
|
118
|
+
}
|
81
119
|
}
|
82
120
|
setSchema(schema) {
|
83
121
|
this.schema = schema;
|
84
122
|
}
|
123
|
+
addSchema(subSchema, combine = true) {
|
124
|
+
if (subSchema.hasOwnProperty("$id")) {
|
125
|
+
const url = URL.parse(subSchema["$id"]);
|
126
|
+
if (url) {
|
127
|
+
this.definitions[url.pathname.split("/").slice(-1)[0]] = subSchema;
|
128
|
+
} else {
|
129
|
+
throw new Error(
|
130
|
+
"subSchemas need a valid retrieval URI $id https://json-schema.org/understanding-json-schema/structuring#retrieval-uri"
|
131
|
+
);
|
132
|
+
}
|
133
|
+
} else {
|
134
|
+
throw new Error("subSchemas need $id https://json-schema.org/understanding-json-schema/structuring#id");
|
135
|
+
}
|
136
|
+
if (combine == true) traverseSchema(this.definitions, this.schema);
|
137
|
+
}
|
138
|
+
getSchema() {
|
139
|
+
return this.schema;
|
140
|
+
}
|
85
141
|
setOptions(options) {
|
86
|
-
this.options = options;
|
142
|
+
this.options = { ...this.options, ...options };
|
87
143
|
}
|
88
144
|
throw(message, needle) {
|
89
145
|
const error = `${message}${this.options.fullErrors && needle.absorvErrors !== true && needle.errors.size > 0 ? `: ${Array.from(needle.errors).join(", ")}` : ``}`;
|
@@ -109,7 +165,7 @@ var Cabidela = class {
|
|
109
165
|
} else {
|
110
166
|
for (let property of unevaluatedProperties) {
|
111
167
|
if (this.parseSubSchema({
|
112
|
-
path: [property.split("
|
168
|
+
path: [property.split("/").slice(-1)[0]],
|
113
169
|
schema: contextAdditionalProperties,
|
114
170
|
payload: resolvedObject,
|
115
171
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
@@ -175,7 +231,7 @@ var Cabidela = class {
|
|
175
231
|
if (new Set(needle.schema.required.map((r) => pathToString([...needle.path, r]))).difference(
|
176
232
|
needle.evaluatedProperties.union(localEvaluatedProperties)
|
177
233
|
).size > 0) {
|
178
|
-
this.throw(`required properties at '${pathToString(needle.path)}'
|
234
|
+
this.throw(`required properties at '${pathToString(needle.path)}' are '${needle.schema.required}'`, needle);
|
179
235
|
}
|
180
236
|
}
|
181
237
|
return matchCount ? true : false;
|
@@ -210,6 +266,20 @@ var Cabidela = class {
|
|
210
266
|
if (needle.schema == void 0) {
|
211
267
|
this.throw(`No schema for path '${pathToString(needle.path)}'`, needle);
|
212
268
|
}
|
269
|
+
if (needle.schema.hasOwnProperty("not")) {
|
270
|
+
let pass = false;
|
271
|
+
try {
|
272
|
+
this.parseSubSchema({
|
273
|
+
...needle,
|
274
|
+
schema: needle.schema.not
|
275
|
+
});
|
276
|
+
} catch (e) {
|
277
|
+
pass = true;
|
278
|
+
}
|
279
|
+
if (pass == false) {
|
280
|
+
this.throw(`not at '${pathToString(needle.path)}' not met`, needle);
|
281
|
+
}
|
282
|
+
}
|
213
283
|
if (needle.schema.hasOwnProperty("oneOf")) {
|
214
284
|
const rounds = this.parseList(needle.schema.oneOf, needle, (r) => r !== 1);
|
215
285
|
if (rounds !== 1) {
|
@@ -322,6 +392,14 @@ var Cabidela = class {
|
|
322
392
|
break;
|
323
393
|
}
|
324
394
|
}
|
395
|
+
if (needle.schema.hasOwnProperty("pattern")) {
|
396
|
+
let passes = false;
|
397
|
+
try {
|
398
|
+
if (new RegExp(needle.schema.pattern).test(resolvedObject)) passes = true;
|
399
|
+
} catch (e) {
|
400
|
+
}
|
401
|
+
if (!passes) this.throw(`'${pathToString(needle.path)}' failed test ${needle.schema.pattern} patttern`, needle);
|
402
|
+
}
|
325
403
|
if (needle.carryProperties) {
|
326
404
|
needle.evaluatedProperties.add(pathToString(needle.path));
|
327
405
|
}
|
package/dist/index.mjs
CHANGED
@@ -1,4 +1,30 @@
|
|
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
|
+
var traverseSchema = (definitions, obj, cb = () => {
|
9
|
+
}) => {
|
10
|
+
Object.keys(obj).forEach((key) => {
|
11
|
+
if (obj[key] !== null && typeof obj[key] === "object") {
|
12
|
+
traverseSchema(definitions, obj[key], (value) => {
|
13
|
+
obj[key] = value;
|
14
|
+
});
|
15
|
+
} else {
|
16
|
+
if (key === "$ref") {
|
17
|
+
const { $id, $path } = parse$ref(obj[key]);
|
18
|
+
const { resolvedObject } = resolvePayload($path, definitions[$id]);
|
19
|
+
if (resolvedObject) {
|
20
|
+
cb(resolvedObject);
|
21
|
+
} else {
|
22
|
+
throw new Error(`Could not resolve '${obj[key]}' $ref`);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
});
|
27
|
+
};
|
2
28
|
var resolvePayload = (path, obj) => {
|
3
29
|
let resolvedObject = path.reduce(function(prev, curr) {
|
4
30
|
return prev ? prev[curr] : void 0;
|
@@ -6,7 +32,7 @@ var resolvePayload = (path, obj) => {
|
|
6
32
|
return { metadata: getMetaData(resolvedObject), resolvedObject };
|
7
33
|
};
|
8
34
|
var pathToString = (path) => {
|
9
|
-
return path.length == 0 ?
|
35
|
+
return path.length == 0 ? `/` : path.map((item) => `/${item}`).join("");
|
10
36
|
};
|
11
37
|
var getMetaData = (value) => {
|
12
38
|
let size = 0;
|
@@ -44,20 +70,50 @@ var getMetaData = (value) => {
|
|
44
70
|
var Cabidela = class {
|
45
71
|
schema;
|
46
72
|
options;
|
73
|
+
definitions = {};
|
47
74
|
constructor(schema, options) {
|
48
75
|
this.schema = schema;
|
49
76
|
this.options = {
|
50
77
|
fullErrors: true,
|
78
|
+
subSchemas: [],
|
51
79
|
applyDefaults: false,
|
52
80
|
errorMessages: false,
|
53
81
|
...options || {}
|
54
82
|
};
|
83
|
+
if (this.schema.hasOwnProperty("$defs")) {
|
84
|
+
this.definitions["$defs"] = this.schema["$defs"];
|
85
|
+
delete this.schema["$defs"];
|
86
|
+
}
|
87
|
+
if (this.options.subSchemas.length > 0) {
|
88
|
+
for (const subSchema of this.options.subSchemas) {
|
89
|
+
this.addSchema(subSchema, false);
|
90
|
+
}
|
91
|
+
traverseSchema(this.definitions, this.schema);
|
92
|
+
}
|
55
93
|
}
|
56
94
|
setSchema(schema) {
|
57
95
|
this.schema = schema;
|
58
96
|
}
|
97
|
+
addSchema(subSchema, combine = true) {
|
98
|
+
if (subSchema.hasOwnProperty("$id")) {
|
99
|
+
const url = URL.parse(subSchema["$id"]);
|
100
|
+
if (url) {
|
101
|
+
this.definitions[url.pathname.split("/").slice(-1)[0]] = subSchema;
|
102
|
+
} else {
|
103
|
+
throw new Error(
|
104
|
+
"subSchemas need a valid retrieval URI $id https://json-schema.org/understanding-json-schema/structuring#retrieval-uri"
|
105
|
+
);
|
106
|
+
}
|
107
|
+
} else {
|
108
|
+
throw new Error("subSchemas need $id https://json-schema.org/understanding-json-schema/structuring#id");
|
109
|
+
}
|
110
|
+
if (combine == true) traverseSchema(this.definitions, this.schema);
|
111
|
+
}
|
112
|
+
getSchema() {
|
113
|
+
return this.schema;
|
114
|
+
}
|
59
115
|
setOptions(options) {
|
60
|
-
this.options = options;
|
116
|
+
this.options = { ...this.options, ...options };
|
61
117
|
}
|
62
118
|
throw(message, needle) {
|
63
119
|
const error = `${message}${this.options.fullErrors && needle.absorvErrors !== true && needle.errors.size > 0 ? `: ${Array.from(needle.errors).join(", ")}` : ``}`;
|
@@ -83,7 +139,7 @@ var Cabidela = class {
|
|
83
139
|
} else {
|
84
140
|
for (let property of unevaluatedProperties) {
|
85
141
|
if (this.parseSubSchema({
|
86
|
-
path: [property.split("
|
142
|
+
path: [property.split("/").slice(-1)[0]],
|
87
143
|
schema: contextAdditionalProperties,
|
88
144
|
payload: resolvedObject,
|
89
145
|
evaluatedProperties: /* @__PURE__ */ new Set(),
|
@@ -149,7 +205,7 @@ var Cabidela = class {
|
|
149
205
|
if (new Set(needle.schema.required.map((r) => pathToString([...needle.path, r]))).difference(
|
150
206
|
needle.evaluatedProperties.union(localEvaluatedProperties)
|
151
207
|
).size > 0) {
|
152
|
-
this.throw(`required properties at '${pathToString(needle.path)}'
|
208
|
+
this.throw(`required properties at '${pathToString(needle.path)}' are '${needle.schema.required}'`, needle);
|
153
209
|
}
|
154
210
|
}
|
155
211
|
return matchCount ? true : false;
|
@@ -184,6 +240,20 @@ var Cabidela = class {
|
|
184
240
|
if (needle.schema == void 0) {
|
185
241
|
this.throw(`No schema for path '${pathToString(needle.path)}'`, needle);
|
186
242
|
}
|
243
|
+
if (needle.schema.hasOwnProperty("not")) {
|
244
|
+
let pass = false;
|
245
|
+
try {
|
246
|
+
this.parseSubSchema({
|
247
|
+
...needle,
|
248
|
+
schema: needle.schema.not
|
249
|
+
});
|
250
|
+
} catch (e) {
|
251
|
+
pass = true;
|
252
|
+
}
|
253
|
+
if (pass == false) {
|
254
|
+
this.throw(`not at '${pathToString(needle.path)}' not met`, needle);
|
255
|
+
}
|
256
|
+
}
|
187
257
|
if (needle.schema.hasOwnProperty("oneOf")) {
|
188
258
|
const rounds = this.parseList(needle.schema.oneOf, needle, (r) => r !== 1);
|
189
259
|
if (rounds !== 1) {
|
@@ -296,6 +366,14 @@ var Cabidela = class {
|
|
296
366
|
break;
|
297
367
|
}
|
298
368
|
}
|
369
|
+
if (needle.schema.hasOwnProperty("pattern")) {
|
370
|
+
let passes = false;
|
371
|
+
try {
|
372
|
+
if (new RegExp(needle.schema.pattern).test(resolvedObject)) passes = true;
|
373
|
+
} catch (e) {
|
374
|
+
}
|
375
|
+
if (!passes) this.throw(`'${pathToString(needle.path)}' failed test ${needle.schema.pattern} patttern`, needle);
|
376
|
+
}
|
299
377
|
if (needle.carryProperties) {
|
300
378
|
needle.evaluatedProperties.add(pathToString(needle.path));
|
301
379
|
}
|
package/package.json
CHANGED