json-schema-library 11.0.4 → 11.1.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 +11 -0
- package/README.md +112 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +93 -16
- package/dist/index.d.mts +93 -16
- package/dist/index.mjs +1 -1
- package/dist/jlib.js +3 -3
- package/index.ts +10 -1
- package/package.json +11 -8
- package/src/Draft.ts +1 -1
- package/src/Keyword.ts +36 -10
- package/src/SchemaNode.ts +75 -16
- package/src/compileSchema.ts +53 -4
- package/src/errors/errors.ts +4 -1
- package/src/keywords/$defs.ts +34 -8
- package/src/keywords/$ref.ts +12 -0
- package/src/keywords/additionalProperties.ts +19 -8
- package/src/keywords/allOf.ts +44 -18
- package/src/keywords/anyOf.ts +38 -19
- package/src/keywords/contains.ts +21 -9
- package/src/keywords/dependencies.ts +37 -17
- package/src/keywords/dependentRequired.ts +56 -38
- package/src/keywords/dependentSchemas.ts +37 -13
- package/src/keywords/deprecated.ts +32 -8
- package/src/keywords/enum.ts +30 -8
- package/src/keywords/exclusiveMaximum.ts +21 -2
- package/src/keywords/exclusiveMinimum.ts +22 -3
- package/src/keywords/format.ts +21 -2
- package/src/keywords/ifthenelse.ts +49 -5
- package/src/keywords/items.ts +27 -13
- package/src/keywords/maxItems.ts +22 -2
- package/src/keywords/maxLength.ts +30 -9
- package/src/keywords/maxProperties.ts +30 -9
- package/src/keywords/maximum.ts +28 -8
- package/src/keywords/minItems.ts +30 -9
- package/src/keywords/minLength.ts +30 -9
- package/src/keywords/minProperties.ts +26 -5
- package/src/keywords/minimum.ts +32 -13
- package/src/keywords/multipleOf.ts +33 -12
- package/src/keywords/not.ts +23 -10
- package/src/keywords/oneOf.ts +29 -9
- package/src/keywords/pattern.ts +35 -9
- package/src/keywords/properties.ts +35 -12
- package/src/keywords/propertyDependencies.test.ts +180 -0
- package/src/keywords/propertyDependencies.ts +173 -0
- package/src/keywords/propertyNames.ts +26 -14
- package/src/keywords/required.ts +31 -8
- package/src/keywords/type.ts +53 -16
- package/src/keywords/unevaluatedItems.ts +24 -8
- package/src/keywords/unevaluatedProperties.ts +24 -7
- package/src/keywords/uniqueItems.ts +23 -4
- package/src/mergeNode.ts +9 -4
- package/src/settings.ts +2 -1
- package/src/types.ts +1 -1
- package/src/utils/isListOfStrings.ts +3 -0
- package/src/validate.test.ts +0 -2
- package/src/validateNode.ts +2 -2
- package/src/validateSchema.test.ts +312 -0
package/src/keywords/required.ts
CHANGED
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
import { isObject } from "../utils/isObject";
|
|
2
2
|
import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
|
|
3
3
|
import { hasProperty } from "../utils/hasProperty";
|
|
4
|
+
import { SchemaNode } from "../SchemaNode";
|
|
5
|
+
import { isListOfStrings } from "../utils/isListOfStrings";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const KEYWORD = "required";
|
|
8
|
+
|
|
9
|
+
export const requiredKeyword: Keyword<"required"> = {
|
|
10
|
+
id: KEYWORD,
|
|
11
|
+
keyword: KEYWORD,
|
|
12
|
+
parse: parseRequired,
|
|
13
|
+
addValidate: (node) => node.required != null,
|
|
9
14
|
validate: validateRequired
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
function
|
|
13
|
-
const
|
|
17
|
+
function parseRequired(node: SchemaNode) {
|
|
18
|
+
const required = node.schema[KEYWORD];
|
|
19
|
+
if (required == null) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!isListOfStrings(required)) {
|
|
24
|
+
return node.createError("schema-error", {
|
|
25
|
+
pointer: `${node.schemaLocation}/${KEYWORD}`,
|
|
26
|
+
schema: node.schema,
|
|
27
|
+
value: required,
|
|
28
|
+
message: `Keyword '${KEYWORD}' must be a string[]`
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
node.required = required;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validateRequired({ node, data, pointer = "#" }: JsonSchemaValidatorParams<"required">) {
|
|
36
|
+
const { required } = node;
|
|
14
37
|
if (!isObject(data)) {
|
|
15
38
|
return undefined;
|
|
16
39
|
}
|
|
17
|
-
return
|
|
40
|
+
return required.map((property: string) => {
|
|
18
41
|
if (!hasProperty(data, property)) {
|
|
19
42
|
return node.createError("required-property-error", {
|
|
20
43
|
key: property,
|
|
21
44
|
pointer,
|
|
22
|
-
schema,
|
|
45
|
+
schema: node.schema,
|
|
23
46
|
value: data
|
|
24
47
|
});
|
|
25
48
|
}
|
package/src/keywords/type.ts
CHANGED
|
@@ -2,17 +2,58 @@ import { getTypeOf, JSType } from "../utils/getTypeOf";
|
|
|
2
2
|
import { SchemaNode } from "../types";
|
|
3
3
|
import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams } from "../Keyword";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const KEYWORD = "type";
|
|
6
|
+
const validTyes = ["null", "boolean", "number", "integer", "string", "object", "array"];
|
|
7
|
+
|
|
8
|
+
export const typeKeyword: Keyword<"type"> = {
|
|
9
|
+
id: KEYWORD,
|
|
10
|
+
keyword: KEYWORD,
|
|
11
|
+
parse: parseType,
|
|
12
|
+
addReduce: (node) => Array.isArray(node.type),
|
|
9
13
|
reduce: reduceType,
|
|
10
|
-
addValidate: (
|
|
14
|
+
addValidate: (node) => node.type != null,
|
|
11
15
|
validate: validateType
|
|
12
16
|
};
|
|
13
17
|
|
|
18
|
+
function parseType(node: SchemaNode) {
|
|
19
|
+
const type = node.schema[KEYWORD];
|
|
20
|
+
if (type == null) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (typeof type !== "string" && !Array.isArray(type)) {
|
|
24
|
+
return node.createError("schema-error", {
|
|
25
|
+
pointer: `${node.schemaLocation}/${KEYWORD}`,
|
|
26
|
+
schema: node.schema,
|
|
27
|
+
value: type,
|
|
28
|
+
message: `Keyword '${KEYWORD}' must be a string or a string[] - received ${typeof type}`
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (typeof type === "string" && !validTyes.includes(type)) {
|
|
32
|
+
return node.createError("schema-error", {
|
|
33
|
+
pointer: `${node.schemaLocation}/${KEYWORD}`,
|
|
34
|
+
schema: node.schema,
|
|
35
|
+
value: type,
|
|
36
|
+
message: `Keyword '${KEYWORD}' is not a valid JSON Schema type - received '${type}'. Expected one of ${validTyes.join(", ")}`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(type)) {
|
|
41
|
+
const invalidTypeIndex = type.findIndex((t) => !validTyes.includes(t));
|
|
42
|
+
if (invalidTypeIndex !== -1) {
|
|
43
|
+
return node.createError("schema-error", {
|
|
44
|
+
pointer: `${node.schemaLocation}/${KEYWORD}/${invalidTypeIndex}`,
|
|
45
|
+
schema: node.schema,
|
|
46
|
+
value: type[invalidTypeIndex],
|
|
47
|
+
message: `Keyword '${KEYWORD}' contains an invalid JSON Schema type: '${type[invalidTypeIndex]}'`
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
node[KEYWORD] = type;
|
|
53
|
+
}
|
|
54
|
+
|
|
14
55
|
function reduceType({ node, pointer, data }: JsonSchemaReducerParams): undefined | SchemaNode {
|
|
15
|
-
const dataType = getJsonSchemaType(data, node.
|
|
56
|
+
const dataType = getJsonSchemaType(data, node.type!);
|
|
16
57
|
if (dataType !== "undefined" && Array.isArray(node.schema.type) && node.schema.type.includes(dataType)) {
|
|
17
58
|
return node.compileSchema({ ...node.schema, pointer, type: dataType }, node.evaluationPath);
|
|
18
59
|
}
|
|
@@ -30,22 +71,18 @@ function getJsonSchemaType(value: unknown, expectedType: string | string[]): JST
|
|
|
30
71
|
return jsType;
|
|
31
72
|
}
|
|
32
73
|
|
|
33
|
-
function validateType({ node, data, pointer }: JsonSchemaValidatorParams) {
|
|
34
|
-
const
|
|
35
|
-
const dataType = getJsonSchemaType(data,
|
|
36
|
-
if (
|
|
37
|
-
data === undefined ||
|
|
38
|
-
schema.type === dataType ||
|
|
39
|
-
(Array.isArray(schema.type) && schema.type.includes(dataType))
|
|
40
|
-
) {
|
|
74
|
+
function validateType({ node, data, pointer }: JsonSchemaValidatorParams<"type">) {
|
|
75
|
+
const type = node[KEYWORD];
|
|
76
|
+
const dataType = getJsonSchemaType(data, type);
|
|
77
|
+
if (data === undefined || type === dataType || (Array.isArray(type) && type.includes(dataType))) {
|
|
41
78
|
return;
|
|
42
79
|
}
|
|
43
80
|
|
|
44
81
|
return node.createError("type-error", {
|
|
45
82
|
value: data,
|
|
46
83
|
received: dataType,
|
|
47
|
-
expected:
|
|
48
|
-
schema,
|
|
84
|
+
expected: type,
|
|
85
|
+
schema: node.schema,
|
|
49
86
|
pointer
|
|
50
87
|
});
|
|
51
88
|
}
|
|
@@ -1,31 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SchemaNode } from "../types";
|
|
1
|
+
import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
|
|
3
2
|
import { Keyword, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
|
|
4
3
|
import { validateNode } from "../validateNode";
|
|
5
4
|
import { isItemEvaluated } from "../isItemEvaluated";
|
|
6
5
|
|
|
6
|
+
const KEYWORD = "unevaluatedItems";
|
|
7
|
+
|
|
7
8
|
/**
|
|
8
9
|
* @draft >= 2019-09
|
|
9
10
|
* Similar to additionalItems, but can "see" into subschemas and across references
|
|
10
11
|
* https://json-schema.org/draft/2019-09/json-schema-core#rfc.section.9.3.1.3
|
|
11
12
|
*/
|
|
12
13
|
export const unevaluatedItemsKeyword: Keyword = {
|
|
13
|
-
id:
|
|
14
|
-
keyword:
|
|
14
|
+
id: KEYWORD,
|
|
15
|
+
keyword: KEYWORD,
|
|
15
16
|
parse: parseUnevaluatedItems,
|
|
16
|
-
addValidate: ({ schema }) => schema.unevaluatedItems != null,
|
|
17
|
+
addValidate: ({ schema }) => schema.unevaluatedItems != null, // currently we do not store boolean schema
|
|
17
18
|
validate: validateUnevaluatedItems
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export function parseUnevaluatedItems(node: SchemaNode) {
|
|
21
|
-
|
|
22
|
+
const unevaluatedItems = node.schema[KEYWORD];
|
|
23
|
+
if (unevaluatedItems == null) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!(isJsonSchema(unevaluatedItems) || isBooleanSchema(unevaluatedItems))) {
|
|
27
|
+
return node.createError("schema-error", {
|
|
28
|
+
pointer: `${node.schemaLocation}/${KEYWORD}`,
|
|
29
|
+
schema: node.schema,
|
|
30
|
+
value: unevaluatedItems,
|
|
31
|
+
message: `Keyword '${KEYWORD}' must be a valid JSON Schema - received '${typeof unevaluatedItems}'`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isBooleanSchema(unevaluatedItems)) {
|
|
22
36
|
return;
|
|
23
37
|
}
|
|
38
|
+
|
|
24
39
|
node.unevaluatedItems = node.compileSchema(
|
|
25
40
|
node.schema.unevaluatedItems,
|
|
26
|
-
`${node.evaluationPath}
|
|
27
|
-
`${node.schemaLocation}
|
|
41
|
+
`${node.evaluationPath}/${KEYWORD}`,
|
|
42
|
+
`${node.schemaLocation}/${KEYWORD}`
|
|
28
43
|
);
|
|
44
|
+
return node.unevaluatedItems.schemaValidation;
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
function validateUnevaluatedItems({ node, data, pointer, path }: JsonSchemaValidatorParams) {
|
|
@@ -1,26 +1,43 @@
|
|
|
1
1
|
import { isObject } from "../utils/isObject";
|
|
2
|
-
import { SchemaNode } from "../types";
|
|
2
|
+
import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
|
|
3
3
|
import { Keyword, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
|
|
4
4
|
import { validateNode } from "../validateNode";
|
|
5
5
|
import { isPropertyEvaluated } from "../isPropertyEvaluated";
|
|
6
6
|
|
|
7
|
+
const KEYWORD = "unevaluatedProperties";
|
|
8
|
+
|
|
7
9
|
export const unevaluatedPropertiesKeyword: Keyword = {
|
|
8
|
-
id:
|
|
9
|
-
keyword:
|
|
10
|
+
id: KEYWORD,
|
|
11
|
+
keyword: KEYWORD,
|
|
10
12
|
parse: parseUnevaluatedProperties,
|
|
11
|
-
addValidate: ({ schema }) => schema
|
|
13
|
+
addValidate: ({ schema }) => schema[KEYWORD] != null, // currently we do not store boolean schema
|
|
12
14
|
validate: validateUnevaluatedProperties
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export function parseUnevaluatedProperties(node: SchemaNode) {
|
|
16
|
-
|
|
18
|
+
const unevaluatedProperties = node.schema[KEYWORD];
|
|
19
|
+
if (unevaluatedProperties == null) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!(isJsonSchema(unevaluatedProperties) || isBooleanSchema(unevaluatedProperties))) {
|
|
23
|
+
return node.createError("schema-error", {
|
|
24
|
+
pointer: `${node.schemaLocation}/${KEYWORD}`,
|
|
25
|
+
schema: node.schema,
|
|
26
|
+
value: unevaluatedProperties,
|
|
27
|
+
message: `Keyword '${KEYWORD}' must be a valid JSON Schema - received '${typeof unevaluatedProperties}'`
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isBooleanSchema(unevaluatedProperties)) {
|
|
17
32
|
return;
|
|
18
33
|
}
|
|
34
|
+
|
|
19
35
|
node.unevaluatedProperties = node.compileSchema(
|
|
20
36
|
node.schema.unevaluatedProperties,
|
|
21
|
-
`${node.evaluationPath}
|
|
22
|
-
`${node.schemaLocation}
|
|
37
|
+
`${node.evaluationPath}/${KEYWORD}`,
|
|
38
|
+
`${node.schemaLocation}/${KEYWORD}`
|
|
23
39
|
);
|
|
40
|
+
return node.unevaluatedProperties.schemaValidation;
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
// @todo we should use collected annotation to evaluated unevaluate properties
|
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
import { JsonError } from "../types";
|
|
1
|
+
import { JsonError, SchemaNode } from "../types";
|
|
2
2
|
import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
|
|
3
3
|
import deepEqual from "fast-deep-equal";
|
|
4
4
|
|
|
5
|
+
const KEYWORD = "uniqueItems";
|
|
6
|
+
|
|
5
7
|
export const uniqueItemsKeyword: Keyword = {
|
|
6
|
-
id:
|
|
7
|
-
keyword:
|
|
8
|
-
|
|
8
|
+
id: KEYWORD,
|
|
9
|
+
keyword: KEYWORD,
|
|
10
|
+
parse: parseUniqueItems,
|
|
11
|
+
addValidate: ({ schema }) => schema[KEYWORD] === true,
|
|
9
12
|
validate: validateUniqueItems
|
|
10
13
|
};
|
|
11
14
|
|
|
15
|
+
function parseUniqueItems(node: SchemaNode) {
|
|
16
|
+
const uniqueItems = node.schema[KEYWORD];
|
|
17
|
+
if (uniqueItems == null || uniqueItems === false) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (typeof uniqueItems !== "boolean") {
|
|
21
|
+
return node.createError("schema-error", {
|
|
22
|
+
pointer: `${node.schemaLocation}/${KEYWORD}`,
|
|
23
|
+
schema: node.schema,
|
|
24
|
+
value: uniqueItems,
|
|
25
|
+
message: `Keyword '${KEYWORD}' must be a boolean - received ${typeof uniqueItems}`
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
node.uniqueItems = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
function validateUniqueItems({ node, data, pointer }: JsonSchemaValidatorParams) {
|
|
13
32
|
if (!Array.isArray(data)) {
|
|
14
33
|
return undefined;
|
package/src/mergeNode.ts
CHANGED
|
@@ -37,9 +37,12 @@ function mergeObjects(a?: Record<string, SchemaNode>, b?: Record<string, SchemaN
|
|
|
37
37
|
return object;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
function combineArrays<T>(a?: T[], b?: T[]): T[] | undefined {
|
|
41
|
+
if (a == null || b == null) {
|
|
42
|
+
return b || a;
|
|
43
|
+
}
|
|
44
|
+
return a.concat(b).filter((value, index, list) => list.indexOf(value) === index);
|
|
45
|
+
}
|
|
43
46
|
|
|
44
47
|
function mergePatternProperties(a?: SchemaNode["patternProperties"], b?: SchemaNode["patternProperties"]) {
|
|
45
48
|
if (a == null || b == null) {
|
|
@@ -92,6 +95,7 @@ export function mergeNode(a?: SchemaNode, b?: SchemaNode, ...omit: string[]): Sc
|
|
|
92
95
|
|
|
93
96
|
additionalProperties: mergeNode(a.additionalProperties, b.additionalProperties),
|
|
94
97
|
contains: mergeNode(a.contains, b.contains),
|
|
98
|
+
enum: combineArrays(a.enum, b.enum),
|
|
95
99
|
if: mergeNode(a.if, b.if),
|
|
96
100
|
then: mergeNode(a.then, b.then),
|
|
97
101
|
else: mergeNode(a.else, b.else),
|
|
@@ -101,7 +105,8 @@ export function mergeNode(a?: SchemaNode, b?: SchemaNode, ...omit: string[]): Sc
|
|
|
101
105
|
unevaluatedItems: mergeNode(a.unevaluatedItems, b.unevaluatedItems),
|
|
102
106
|
$defs: mergeObjects(a.$defs, b.$defs),
|
|
103
107
|
patternProperties: mergePatternProperties(a.patternProperties, b.patternProperties),
|
|
104
|
-
properties: mergeObjects(a.properties, b.properties)
|
|
108
|
+
properties: mergeObjects(a.properties, b.properties),
|
|
109
|
+
required: combineArrays(a.required, b.required)
|
|
105
110
|
};
|
|
106
111
|
|
|
107
112
|
// this removes any function that has no keyword associated on schema
|
package/src/settings.ts
CHANGED
package/src/types.ts
CHANGED
package/src/validate.test.ts
CHANGED
|
@@ -8,7 +8,6 @@ describe("compileSchema : validate", () => {
|
|
|
8
8
|
// note: boolean schema is already thoroughly tested by spec
|
|
9
9
|
describe("boolean schema", () => {
|
|
10
10
|
it("should fail if root schema is false", () => {
|
|
11
|
-
// @ts-expect-error boolean typ unsupported
|
|
12
11
|
const { errors } = compileSchema(false).validate("anything");
|
|
13
12
|
assert.deepEqual(errors.length, 1);
|
|
14
13
|
});
|
|
@@ -23,7 +22,6 @@ describe("compileSchema : validate", () => {
|
|
|
23
22
|
assert.deepEqual(errors.length, 1);
|
|
24
23
|
});
|
|
25
24
|
it("should succeed if root schema is true", () => {
|
|
26
|
-
// @ts-expect-error boolean typ unsupported
|
|
27
25
|
const { errors } = compileSchema(true).validate("anything");
|
|
28
26
|
assert.deepEqual(errors.length, 0);
|
|
29
27
|
});
|
package/src/validateNode.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BooleanSchema, JsonSchema, SchemaNode } from "./types";
|
|
2
|
-
import { ValidationPath, ValidationReturnType } from "./Keyword";
|
|
2
|
+
import { SchemaNodeWithRequired, ValidationPath, ValidationReturnType } from "./Keyword";
|
|
3
3
|
import sanitizeErrors from "./utils/sanitizeErrors";
|
|
4
4
|
|
|
5
5
|
export function validateNode(node: SchemaNode, data: unknown, pointer: string, path: ValidationPath) {
|
|
@@ -19,7 +19,7 @@ export function validateNode(node: SchemaNode, data: unknown, pointer: string, p
|
|
|
19
19
|
}
|
|
20
20
|
const errors: ValidationReturnType = [];
|
|
21
21
|
for (const validate of node.validators) {
|
|
22
|
-
const result = validate({ node
|
|
22
|
+
const result = validate({ node: node as SchemaNodeWithRequired<keyof SchemaNode>, data, pointer, path });
|
|
23
23
|
if (Array.isArray(result)) {
|
|
24
24
|
errors.push(...result);
|
|
25
25
|
} else if (result) {
|