json-schema-library 11.4.0 → 11.5.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 +6 -0
- package/README.md +29 -56
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/jlib.js +2 -2
- package/package.json +11 -1
- package/src/SchemaNode.ts +5 -2
- package/src/compileSchema.test.ts +24 -0
- package/src/compileSchema.ts +15 -2
- package/src/draftEditor.ts +1 -0
- package/src/keywords/type.test.ts +13 -0
- package/src/settings.ts +2 -0
- package/src/utils/mergeSchema.test.ts +37 -0
- package/src/utils/mergeSchema.ts +24 -3
- package/src/validateSchema.test.ts +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-schema-library",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.5.0",
|
|
4
4
|
"description": "Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation",
|
|
5
5
|
"types": "./dist/index.d.cts",
|
|
6
6
|
"exports": {
|
|
@@ -18,6 +18,16 @@
|
|
|
18
18
|
},
|
|
19
19
|
"./package.json": "./package.json"
|
|
20
20
|
},
|
|
21
|
+
"typesVersions": {
|
|
22
|
+
"*": {
|
|
23
|
+
"formats": [
|
|
24
|
+
"./dist/formats.d.cts"
|
|
25
|
+
],
|
|
26
|
+
"remotes": [
|
|
27
|
+
"./dist/remotes.d.cts"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
21
31
|
"scripts": {
|
|
22
32
|
"coverage": "nyc npm run test --reporter=lcov",
|
|
23
33
|
"dist": "tsdown -f esm -f cjs --minify --report --attw --unused; pnpm run dist:iife;",
|
package/src/SchemaNode.ts
CHANGED
|
@@ -41,7 +41,7 @@ import { getNode } from "./getNode";
|
|
|
41
41
|
import { getNodeChild } from "./getNodeChild";
|
|
42
42
|
import { DataNode } from "./methods/toDataNodes";
|
|
43
43
|
|
|
44
|
-
const { DYNAMIC_PROPERTIES, REGEX_FLAGS, DECLARATOR_ONEOF } = settings;
|
|
44
|
+
const { DYNAMIC_PROPERTIES, REGEX_FLAGS, DECLARATOR_ONEOF, VALID_ANNOTATION_KEYWORDS } = settings;
|
|
45
45
|
|
|
46
46
|
export function isSchemaNode(value: unknown): value is SchemaNode {
|
|
47
47
|
return isObject(value) && Array.isArray(value?.reducers) && Array.isArray(value?.resolvers);
|
|
@@ -624,7 +624,10 @@ export function addKeywords(node: SchemaNode) {
|
|
|
624
624
|
.map((keyword) => execKeyword(keyword, node));
|
|
625
625
|
|
|
626
626
|
keys.filter(
|
|
627
|
-
(key) =>
|
|
627
|
+
(key) =>
|
|
628
|
+
!key.startsWith("x-") &&
|
|
629
|
+
!VALID_ANNOTATION_KEYWORDS.includes(key) &&
|
|
630
|
+
node.context.keywords.find((keyword) => keyword.keyword === key) == null
|
|
628
631
|
).forEach((keyword) => {
|
|
629
632
|
errors.push(
|
|
630
633
|
node.createAnnotation("unknown-keyword-warning", {
|
|
@@ -84,6 +84,30 @@ describe("compileSchema draft-version", () => {
|
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
+
describe("compileSchema remotes", () => {
|
|
88
|
+
it("should add list of remotes to returned SchemaNode node", () => {
|
|
89
|
+
const node = compileSchema(
|
|
90
|
+
{
|
|
91
|
+
type: "object",
|
|
92
|
+
required: ["string", "number"],
|
|
93
|
+
properties: {
|
|
94
|
+
string: { $ref: "https://remote-a.com/schema" },
|
|
95
|
+
number: { $ref: "https://remote-b.com/schema" }
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
drafts: [draft2020],
|
|
100
|
+
remotes: [
|
|
101
|
+
{ $id: "https://remote-a.com/schema", type: "string", default: "a" },
|
|
102
|
+
{ $id: "https://remote-b.com/schema", type: "number", default: 9 }
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
const data = node.getData();
|
|
107
|
+
assert.deepEqual(data, { string: "a", number: 9 });
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
87
111
|
describe("compileSchema vocabulary", () => {
|
|
88
112
|
it("should add remote schema on compile", () => {
|
|
89
113
|
const remote = compileSchema({
|
package/src/compileSchema.ts
CHANGED
|
@@ -67,6 +67,7 @@ export type CompileOptions = {
|
|
|
67
67
|
* Set node and its remote schemata as remote schemata for this node and schema to resolve $ref
|
|
68
68
|
*/
|
|
69
69
|
remote?: SchemaNode;
|
|
70
|
+
remotes?: JsonSchema[];
|
|
70
71
|
/**
|
|
71
72
|
* Enables `format`-keyword assertions when this is set tor `true` or sets assertion as defined by
|
|
72
73
|
* the given meta-schema. Set to `false` to deactivate format validation.
|
|
@@ -94,6 +95,18 @@ function getDraft(drafts: Draft[], $schema: string) {
|
|
|
94
95
|
* node will be reused for each task, but will create a compiledNode for bound data.
|
|
95
96
|
*/
|
|
96
97
|
export function compileSchema(schema: JsonSchema | BooleanSchema, options: CompileOptions = {}) {
|
|
98
|
+
let remote = options.remote;
|
|
99
|
+
if (Array.isArray(options.remotes) && options.remotes.length > 0 && !options.remote) {
|
|
100
|
+
const remotes = [...options.remotes];
|
|
101
|
+
remotes.forEach((remote, index) => {
|
|
102
|
+
if (remote.$id == null) {
|
|
103
|
+
throw new Error(`required $id on remotes[${index}] is missing`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
remote = compileSchema(remotes.shift()!);
|
|
107
|
+
remotes.forEach((r) => remote?.addRemoteSchema(r.$id, r));
|
|
108
|
+
}
|
|
109
|
+
|
|
97
110
|
let formatAssertion = options.formatAssertion ?? true;
|
|
98
111
|
const drafts = options.drafts ?? defaultDrafts;
|
|
99
112
|
const draft = getDraft(drafts, isJsonSchema(schema) ? (options.draft ?? schema.$schema) : undefined);
|
|
@@ -110,7 +123,7 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
|
|
|
110
123
|
context: {
|
|
111
124
|
remotes: {},
|
|
112
125
|
dynamicAnchors: {},
|
|
113
|
-
...(
|
|
126
|
+
...(remote?.context ?? {}),
|
|
114
127
|
anchors: {},
|
|
115
128
|
refs: {},
|
|
116
129
|
...copy(pick(draft, "methods", "keywords", "version", "formats", "errors")),
|
|
@@ -125,7 +138,7 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
|
|
|
125
138
|
node.context.rootNode = node;
|
|
126
139
|
node.context.remotes[(isJsonSchema(schema) ? schema.$id : undefined) ?? "#"] = node;
|
|
127
140
|
|
|
128
|
-
if (
|
|
141
|
+
if (remote) {
|
|
129
142
|
const metaSchema = getRef(node, node.schema.$schema);
|
|
130
143
|
if (isSchemaNode(metaSchema) && metaSchema.schema.$vocabulary) {
|
|
131
144
|
const vocabs = Object.keys(metaSchema.schema.$vocabulary);
|
package/src/draftEditor.ts
CHANGED
|
@@ -20,3 +20,16 @@ describe("keyword : type : validation", () => {
|
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
describe("keyword : type : reduce", () => {
|
|
25
|
+
it("should reduce array-type to the last matching data-type", () => {
|
|
26
|
+
const { node } = compileSchema({ type: ["null", "integer"] }).reduceNode(123);
|
|
27
|
+
assert.equal(node?.type, "integer");
|
|
28
|
+
assert.equal(node?.schema.type, "integer");
|
|
29
|
+
});
|
|
30
|
+
it("should reduce array-type to the first matching data-type", () => {
|
|
31
|
+
const { node } = compileSchema({ type: ["null", "integer"] }).reduceNode(null);
|
|
32
|
+
assert.equal(node?.type, "null");
|
|
33
|
+
assert.equal(node?.schema.type, "null");
|
|
34
|
+
});
|
|
35
|
+
});
|
package/src/settings.ts
CHANGED
|
@@ -18,6 +18,8 @@ export default {
|
|
|
18
18
|
"propertyDependencies"
|
|
19
19
|
],
|
|
20
20
|
REGEX_FLAGS: "u",
|
|
21
|
+
/** additional keywords that should not produce an unknown-keyword-warning */
|
|
22
|
+
VALID_ANNOTATION_KEYWORDS: ["title", "description", "default"],
|
|
21
23
|
/**
|
|
22
24
|
* properties to keep from a $ref-schema when resolving a $ref (recursively)
|
|
23
25
|
* this allows to overwrite specified properties locally on a $ref-definition
|
|
@@ -78,4 +78,41 @@ describe("mergeSchema", () => {
|
|
|
78
78
|
);
|
|
79
79
|
assert.deepEqual(schema.items.anyOf, [{ type: "string" }, { type: "number" }]);
|
|
80
80
|
});
|
|
81
|
+
|
|
82
|
+
describe("type", () => {
|
|
83
|
+
it("should return last type if they conflict", () => {
|
|
84
|
+
const schema = mergeSchema({ type: "array" }, { type: "integer" });
|
|
85
|
+
assert.deepEqual(schema.type, "integer");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should not merge mixed data types with string first", () => {
|
|
89
|
+
const schema = mergeSchema({ type: "array" }, { type: ["array", "object"] });
|
|
90
|
+
assert.deepEqual(schema.type, "array");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should not merge mixed data types with string second", () => {
|
|
94
|
+
const schema = mergeSchema({ type: ["array", "object"] }, { type: "array" });
|
|
95
|
+
assert.deepEqual(schema.type, "array");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should merge mixed data types if they have no type in common", () => {
|
|
99
|
+
const schema = mergeSchema({ type: ["array", "object"] }, { type: "integer" });
|
|
100
|
+
assert.deepEqual(schema.type, ["array", "object", "integer"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should merge return last type if they do not match", () => {
|
|
104
|
+
const schema = mergeSchema({ type: "object" }, { type: "array" });
|
|
105
|
+
assert.deepEqual(schema.type, "array");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should merge array types", () => {
|
|
109
|
+
const schema = mergeSchema({ type: ["object"] }, { type: ["array"] });
|
|
110
|
+
assert.deepEqual(schema.type, ["object", "array"]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should merge array types without duplicated", () => {
|
|
114
|
+
const schema = mergeSchema({ type: ["integer", "object"] }, { type: ["object", "array"] });
|
|
115
|
+
assert.deepEqual(schema.type, ["integer", "object", "array"]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
81
118
|
});
|
package/src/utils/mergeSchema.ts
CHANGED
|
@@ -32,7 +32,10 @@ export function mergeSchema2(a: unknown, b: unknown, property?: string): unknown
|
|
|
32
32
|
return newObject;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const aIsArray = Array.isArray(a);
|
|
36
|
+
const bIsArray = Array.isArray(b);
|
|
37
|
+
|
|
38
|
+
if (aIsArray && bIsArray) {
|
|
36
39
|
if (property === "required" || property === "anyOf") {
|
|
37
40
|
return a.concat(b).filter((item, index, array) => array.indexOf(item) === index);
|
|
38
41
|
}
|
|
@@ -66,11 +69,29 @@ export function mergeSchema2(a: unknown, b: unknown, property?: string): unknown
|
|
|
66
69
|
return [...result, ...append].filter((item, index, array) => array.indexOf(item) === index);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
// mixed data-type for type keyword
|
|
73
|
+
if (property === "type" && (aIsArray || bIsArray)) {
|
|
74
|
+
// we merge to the specific type
|
|
75
|
+
if (aIsArray && a.includes(b)) {
|
|
76
|
+
return b;
|
|
77
|
+
}
|
|
78
|
+
if (bIsArray && b.includes(a)) {
|
|
79
|
+
return a;
|
|
80
|
+
}
|
|
81
|
+
// extend the types if they do not match
|
|
82
|
+
if (aIsArray) {
|
|
83
|
+
return [...a, b];
|
|
84
|
+
}
|
|
85
|
+
if (bIsArray) {
|
|
86
|
+
return [...b, a];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (bIsArray) {
|
|
70
91
|
return b;
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
if (
|
|
94
|
+
if (aIsArray) {
|
|
74
95
|
return a;
|
|
75
96
|
}
|
|
76
97
|
|
|
@@ -263,7 +263,7 @@ describe("validateSchema", () => {
|
|
|
263
263
|
assert.equal(schemaAnnotations.length, 1);
|
|
264
264
|
assert.equal(schemaAnnotations[0].data.pointer, "#/properties/headline/options");
|
|
265
265
|
});
|
|
266
|
-
it("should return
|
|
266
|
+
it("should not return unknown keywords starting with 'x-'", () => {
|
|
267
267
|
const { schemaAnnotations } = compileSchema({
|
|
268
268
|
properties: {
|
|
269
269
|
headline: {
|
|
@@ -274,6 +274,15 @@ describe("validateSchema", () => {
|
|
|
274
274
|
});
|
|
275
275
|
assert.equal(schemaAnnotations.length, 0);
|
|
276
276
|
});
|
|
277
|
+
it("should not return valid annotation keywords", () => {
|
|
278
|
+
const { schemaAnnotations } = compileSchema({
|
|
279
|
+
type: "string",
|
|
280
|
+
title: "input",
|
|
281
|
+
description: "any string",
|
|
282
|
+
default: "hey"
|
|
283
|
+
});
|
|
284
|
+
assert.equal(schemaAnnotations.length, 0);
|
|
285
|
+
});
|
|
277
286
|
it("should return removed keywords from old drafts as annotation", () => {
|
|
278
287
|
const { schemaAnnotations } = compileSchema({
|
|
279
288
|
properties: {
|