json-schema-library 10.5.4 → 11.0.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/README.md +128 -14
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +42 -19
- package/dist/index.d.mts +42 -19
- package/dist/index.mjs +1 -1
- package/dist/jlib.js +2 -2
- package/index.ts +8 -4
- package/package.json +6 -6
- package/src/Keyword.ts +8 -3
- package/src/SchemaNode.ts +46 -10
- package/src/compileSchema.validate.test.ts +89 -54
- package/src/draft04.ts +10 -8
- package/src/draft06.ts +10 -8
- package/src/draft07.ts +10 -8
- package/src/draft2019-09/keywords/additionalItems.ts +2 -2
- package/src/draft2019-09/keywords/items.ts +2 -2
- package/src/draft2019-09/keywords/unevaluatedItems.ts +12 -6
- package/src/draft2019.ts +10 -8
- package/src/draft2020.ts +8 -6
- package/src/errors/errors.ts +3 -1
- package/src/formats/formats.ts +2 -6
- package/src/keywords/additionalProperties.ts +2 -2
- package/src/keywords/allOf.ts +2 -2
- package/src/keywords/dependencies.ts +5 -6
- package/src/keywords/dependentRequired.ts +2 -2
- package/src/keywords/dependentSchemas.ts +4 -3
- package/src/keywords/deprecated.ts +18 -0
- package/src/keywords/items.ts +2 -2
- package/src/keywords/oneOf.test.ts +150 -15
- package/src/keywords/oneOf.ts +64 -4
- package/src/keywords/patternProperties.ts +2 -2
- package/src/keywords/prefixItems.ts +2 -11
- package/src/keywords/properties.ts +2 -2
- package/src/keywords/unevaluatedItems.ts +2 -2
- package/src/keywords/unevaluatedProperties.ts +2 -2
- package/src/methods/getData.test.ts +1779 -1781
- package/src/types.ts +32 -18
- package/src/utils/sanitizeErrors.ts +9 -8
- package/src/validateNode.ts +2 -2
package/src/draft2019.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import { $defsKeyword } from "./keywords/$defs";
|
|
2
|
+
import { $refKeyword } from "./draft2019-09/keywords/$ref";
|
|
1
3
|
import { additionalItemsKeyword } from "./draft2019-09/keywords/additionalItems";
|
|
2
4
|
import { additionalPropertiesKeyword } from "./keywords/additionalProperties";
|
|
3
5
|
import { allOfKeyword } from "./keywords/allOf";
|
|
4
6
|
import { anyOfKeyword } from "./keywords/anyOf";
|
|
5
7
|
import { constKeyword } from "./keywords/const";
|
|
6
8
|
import { containsKeyword } from "./keywords/contains";
|
|
7
|
-
import {
|
|
9
|
+
import { createSchema } from "./methods/createSchema";
|
|
8
10
|
import { dependenciesKeyword } from "./keywords/dependencies";
|
|
9
11
|
import { dependentRequiredKeyword } from "./keywords/dependentRequired";
|
|
10
12
|
import { dependentSchemasKeyword } from "./keywords/dependentSchemas";
|
|
13
|
+
import { deprecatedKeyword } from "./keywords/deprecated";
|
|
11
14
|
import { enumKeyword } from "./keywords/enum";
|
|
15
|
+
import { errors } from "./errors/errors";
|
|
12
16
|
import { exclusiveMaximumKeyword } from "./keywords/exclusiveMaximum";
|
|
13
17
|
import { exclusiveMinimumKeyword } from "./keywords/exclusiveMinimum";
|
|
14
18
|
import { formatKeyword } from "./keywords/format";
|
|
19
|
+
import { formats } from "./formats/formats";
|
|
20
|
+
import { getChildSelection } from "./draft2019-09/methods/getChildSelection";
|
|
21
|
+
import { getData } from "./draft2019-09/methods/getData";
|
|
15
22
|
import { ifKeyword } from "./keywords/ifthenelse";
|
|
16
23
|
import { itemsKeyword } from "./draft2019-09/keywords/items";
|
|
17
24
|
import { maximumKeyword } from "./keywords/maximum";
|
|
@@ -29,19 +36,13 @@ import { patternKeyword } from "./keywords/pattern";
|
|
|
29
36
|
import { patternPropertiesKeyword } from "./keywords/patternProperties";
|
|
30
37
|
import { propertiesKeyword } from "./keywords/properties";
|
|
31
38
|
import { propertyNamesKeyword } from "./keywords/propertyNames";
|
|
32
|
-
import { $refKeyword } from "./draft2019-09/keywords/$ref";
|
|
33
39
|
import { requiredKeyword } from "./keywords/required";
|
|
34
40
|
import { sanitizeKeywords } from "./Draft";
|
|
41
|
+
import { toDataNodes } from "./methods/toDataNodes";
|
|
35
42
|
import { typeKeyword } from "./keywords/type";
|
|
36
43
|
import { unevaluatedItemsKeyword } from "./draft2019-09/keywords/unevaluatedItems";
|
|
37
44
|
import { unevaluatedPropertiesKeyword } from "./keywords/unevaluatedProperties";
|
|
38
45
|
import { uniqueItemsKeyword } from "./keywords/uniqueItems";
|
|
39
|
-
import { getChildSelection } from "./draft2019-09/methods/getChildSelection";
|
|
40
|
-
import { getData } from "./draft2019-09/methods/getData";
|
|
41
|
-
import { toDataNodes } from "./methods/toDataNodes";
|
|
42
|
-
import { createSchema } from "./methods/createSchema";
|
|
43
|
-
import { errors } from "./errors/errors";
|
|
44
|
-
import { formats } from "./formats/formats";
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* @draft-2019 https://json-schema.org/draft/2019-09/release-notes
|
|
@@ -79,6 +80,7 @@ export const draft2019 = sanitizeKeywords({
|
|
|
79
80
|
dependenciesKeyword, // optional support for old draft-version
|
|
80
81
|
dependentRequiredKeyword, // draft-2019: new
|
|
81
82
|
dependentSchemasKeyword, // draft-2019: new
|
|
83
|
+
deprecatedKeyword,
|
|
82
84
|
enumKeyword,
|
|
83
85
|
exclusiveMaximumKeyword,
|
|
84
86
|
exclusiveMinimumKeyword,
|
package/src/draft2020.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
import { $defsKeyword } from "./keywords/$defs";
|
|
2
|
+
import { $refKeyword } from "./keywords/$ref";
|
|
1
3
|
import { additionalPropertiesKeyword } from "./keywords/additionalProperties";
|
|
2
4
|
import { allOfKeyword } from "./keywords/allOf";
|
|
3
5
|
import { anyOfKeyword } from "./keywords/anyOf";
|
|
4
6
|
import { constKeyword } from "./keywords/const";
|
|
5
7
|
import { containsKeyword } from "./keywords/contains";
|
|
6
|
-
import {
|
|
8
|
+
import { createSchema } from "./methods/createSchema";
|
|
7
9
|
import { dependenciesKeyword } from "./keywords/dependencies";
|
|
8
10
|
import { dependentRequiredKeyword } from "./keywords/dependentRequired";
|
|
9
11
|
import { dependentSchemasKeyword } from "./keywords/dependentSchemas";
|
|
10
|
-
import {
|
|
12
|
+
import { deprecatedKeyword } from "./keywords/deprecated";
|
|
11
13
|
import { enumKeyword } from "./keywords/enum";
|
|
14
|
+
import { errors } from "./errors/errors";
|
|
12
15
|
import { exclusiveMaximumKeyword } from "./keywords/exclusiveMaximum";
|
|
13
16
|
import { exclusiveMinimumKeyword } from "./keywords/exclusiveMinimum";
|
|
14
17
|
import { formatKeyword } from "./keywords/format";
|
|
18
|
+
import { formats } from "./formats/formats";
|
|
15
19
|
import { getChildSelection } from "./methods/getChildSelection";
|
|
16
20
|
import { getData } from "./methods/getData";
|
|
17
21
|
import { ifKeyword } from "./keywords/ifthenelse";
|
|
@@ -32,16 +36,13 @@ import { patternPropertiesKeyword } from "./keywords/patternProperties";
|
|
|
32
36
|
import { prefixItemsKeyword } from "./keywords/prefixItems";
|
|
33
37
|
import { propertiesKeyword } from "./keywords/properties";
|
|
34
38
|
import { propertyNamesKeyword } from "./keywords/propertyNames";
|
|
35
|
-
import { $refKeyword } from "./keywords/$ref";
|
|
36
39
|
import { requiredKeyword } from "./keywords/required";
|
|
37
40
|
import { sanitizeKeywords } from "./Draft";
|
|
41
|
+
import { toDataNodes } from "./methods/toDataNodes";
|
|
38
42
|
import { typeKeyword } from "./keywords/type";
|
|
39
43
|
import { unevaluatedItemsKeyword } from "./keywords/unevaluatedItems";
|
|
40
44
|
import { unevaluatedPropertiesKeyword } from "./keywords/unevaluatedProperties";
|
|
41
45
|
import { uniqueItemsKeyword } from "./keywords/uniqueItems";
|
|
42
|
-
import { createSchema } from "./methods/createSchema";
|
|
43
|
-
import { errors } from "./errors/errors";
|
|
44
|
-
import { formats } from "./formats/formats";
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* @draft-2020-12 https://json-schema.org/draft/2020-12/release-notes
|
|
@@ -90,6 +91,7 @@ export const draft2020 = sanitizeKeywords({
|
|
|
90
91
|
dependenciesKeyword,
|
|
91
92
|
dependentRequiredKeyword,
|
|
92
93
|
dependentSchemasKeyword,
|
|
94
|
+
deprecatedKeyword,
|
|
93
95
|
enumKeyword,
|
|
94
96
|
exclusiveMaximumKeyword,
|
|
95
97
|
exclusiveMinimumKeyword,
|
package/src/errors/errors.ts
CHANGED
|
@@ -71,5 +71,7 @@ export const errors = {
|
|
|
71
71
|
"unique-items-error":
|
|
72
72
|
"Items in array must be unique. Value `{{value}}` in `{{pointer}}` is a duplicate of {{duplicatePointer}}.",
|
|
73
73
|
"unknown-property-error": "Could not find a valid schema for property `{{pointer}}` within object",
|
|
74
|
-
"value-not-empty-error": "A value for `{{property}}` is required at `{{pointer}}`"
|
|
74
|
+
"value-not-empty-error": "A value for `{{property}}` is required at `{{pointer}}`",
|
|
75
|
+
// annotations
|
|
76
|
+
"deprecated-warning": "Value at `{{pointer}}` is deprecated"
|
|
75
77
|
};
|
package/src/formats/formats.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-control-regex */
|
|
2
2
|
import { getTypeOf } from "../utils/getTypeOf";
|
|
3
3
|
import validUrl from "valid-url";
|
|
4
|
-
import { JsonSchemaValidatorParams,
|
|
4
|
+
import { JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
|
|
5
5
|
import { parse as parseIdnEmail } from "smtp-address-parser";
|
|
6
|
-
import { JsonError } from "../types";
|
|
7
6
|
import settings from "../settings";
|
|
8
7
|
|
|
9
8
|
const { REGEX_FLAGS } = settings;
|
|
@@ -27,10 +26,7 @@ const isValidURITemplate =
|
|
|
27
26
|
const isValidDurationString = /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/;
|
|
28
27
|
|
|
29
28
|
// Default Json-Schema formats: date-time, email, hostname, ipv4, ipv6, uri, uriref
|
|
30
|
-
export const formats: Record<
|
|
31
|
-
string,
|
|
32
|
-
(options: JsonSchemaValidatorParams) => undefined | JsonError | ValidationResult[]
|
|
33
|
-
> = {
|
|
29
|
+
export const formats: Record<string, (options: JsonSchemaValidatorParams) => ValidationReturnType> = {
|
|
34
30
|
date: ({ node, pointer, data }) => {
|
|
35
31
|
const { schema } = node;
|
|
36
32
|
if (typeof data !== "string" || data === "") {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import settings from "../settings";
|
|
2
2
|
import { isObject } from "../utils/isObject";
|
|
3
|
-
import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams,
|
|
3
|
+
import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType} from "../Keyword";
|
|
4
4
|
import { SchemaNode } from "../types";
|
|
5
5
|
import { getValue } from "../utils/getValue";
|
|
6
6
|
import { validateNode } from "../validateNode";
|
|
@@ -65,7 +65,7 @@ function validateAdditionalProperty({ node, data, pointer = "#", path }: JsonSch
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
const { schema } = node;
|
|
68
|
-
const errors:
|
|
68
|
+
const errors: ValidationReturnType = [];
|
|
69
69
|
let receivedProperties = Object.keys(data).filter((prop) => settings.propertyBlacklist.includes(prop) === false);
|
|
70
70
|
const patternProperties = node.patternProperties;
|
|
71
71
|
if (Array.isArray(patternProperties)) {
|
package/src/keywords/allOf.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mergeSchema } from "../utils/mergeSchema";
|
|
2
|
-
import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams,
|
|
2
|
+
import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
|
|
3
3
|
import { SchemaNode } from "../types";
|
|
4
4
|
import { validateNode } from "../validateNode";
|
|
5
5
|
|
|
@@ -55,7 +55,7 @@ function validateAllOf({ node, data, pointer, path }: JsonSchemaValidatorParams)
|
|
|
55
55
|
if (!Array.isArray(node.allOf) || node.allOf.length === 0) {
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
const errors:
|
|
58
|
+
const errors: ValidationReturnType = [];
|
|
59
59
|
node.allOf.forEach((allOfNode) => {
|
|
60
60
|
errors.push(...validateNode(allOfNode, data, pointer, path));
|
|
61
61
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { isSchemaNode, SchemaNode } from "../types";
|
|
2
|
-
import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams,
|
|
2
|
+
import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
|
|
3
3
|
import { isObject } from "../utils/isObject";
|
|
4
4
|
import { mergeNode } from "../mergeNode";
|
|
5
5
|
import { hasProperty } from "../utils/hasProperty";
|
|
6
6
|
import { validateDependentRequired } from "./dependentRequired";
|
|
7
7
|
import { validateDependentSchemas } from "./dependentSchemas";
|
|
8
|
+
import sanitizeErrors from "../utils/sanitizeErrors";
|
|
8
9
|
|
|
9
10
|
export const dependenciesKeyword: Keyword = {
|
|
10
11
|
id: "dependencies",
|
|
@@ -125,15 +126,13 @@ function validateDependencies({ node, data, pointer, path }: JsonSchemaValidator
|
|
|
125
126
|
if (!isObject(data)) {
|
|
126
127
|
return undefined;
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
+
const errors: ValidationAnnotation[] = [];
|
|
129
130
|
if (node.dependentRequired) {
|
|
130
|
-
|
|
131
|
+
sanitizeErrors(validateDependentRequired({ node, data, pointer, path }), errors);
|
|
131
132
|
}
|
|
132
133
|
if (node.dependentSchemas) {
|
|
133
134
|
const schemaErrors = validateDependentSchemas({ node, data, pointer, path });
|
|
134
|
-
|
|
135
|
-
errors.push(...schemaErrors);
|
|
136
|
-
}
|
|
135
|
+
sanitizeErrors(schemaErrors, errors);
|
|
137
136
|
}
|
|
138
137
|
return errors;
|
|
139
138
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Keyword, JsonSchemaValidatorParams,
|
|
1
|
+
import { Keyword, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
|
|
2
2
|
import { JsonError, SchemaNode } from "../types";
|
|
3
3
|
import { isObject } from "../utils/isObject";
|
|
4
4
|
|
|
@@ -21,7 +21,7 @@ export function validateDependentRequired({
|
|
|
21
21
|
node,
|
|
22
22
|
data,
|
|
23
23
|
pointer = "#"
|
|
24
|
-
}: JsonSchemaValidatorParams):
|
|
24
|
+
}: JsonSchemaValidatorParams): ValidationReturnType {
|
|
25
25
|
if (!isObject(data)) {
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { mergeSchema } from "../utils/mergeSchema";
|
|
2
2
|
import { isObject } from "../utils/isObject";
|
|
3
3
|
import { isSchemaNode, SchemaNode, JsonSchema } from "../types";
|
|
4
|
-
import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams,
|
|
4
|
+
import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
|
|
5
5
|
import { validateNode } from "../validateNode";
|
|
6
|
+
import sanitizeErrors from "../utils/sanitizeErrors";
|
|
6
7
|
|
|
7
8
|
export const dependentSchemasKeyword: Keyword = {
|
|
8
9
|
id: "dependentSchemas",
|
|
@@ -78,7 +79,7 @@ export function validateDependentSchemas({ node, data, pointer, path }: JsonSche
|
|
|
78
79
|
if (!isObject(data) || dependentSchemas == null) {
|
|
79
80
|
return undefined;
|
|
80
81
|
}
|
|
81
|
-
const errors:
|
|
82
|
+
const errors: ValidationAnnotation[] = [];
|
|
82
83
|
Object.keys(data).forEach((property) => {
|
|
83
84
|
const dependencies = dependentSchemas[property];
|
|
84
85
|
// @draft >= 6 boolean schema
|
|
@@ -90,7 +91,7 @@ export function validateDependentSchemas({ node, data, pointer, path }: JsonSche
|
|
|
90
91
|
return;
|
|
91
92
|
}
|
|
92
93
|
if (isSchemaNode(dependencies)) {
|
|
93
|
-
|
|
94
|
+
sanitizeErrors(validateNode(dependencies, data, pointer, path), errors);
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
96
97
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
|
|
2
|
+
|
|
3
|
+
export const deprecatedKeyword: Keyword = {
|
|
4
|
+
id: "deprecated",
|
|
5
|
+
keyword: "deprecated",
|
|
6
|
+
addValidate: ({ schema }) => schema.deprecated === true,
|
|
7
|
+
validate: validateDeprecated
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function validateDeprecated({ node, data, pointer }: JsonSchemaValidatorParams) {
|
|
11
|
+
return [
|
|
12
|
+
node.createAnnotation("deprecated-warning", {
|
|
13
|
+
pointer,
|
|
14
|
+
schema: node.schema,
|
|
15
|
+
value: data
|
|
16
|
+
})
|
|
17
|
+
];
|
|
18
|
+
}
|
package/src/keywords/items.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams,
|
|
1
|
+
import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
|
|
2
2
|
import { SchemaNode } from "../types";
|
|
3
3
|
import { isObject } from "../utils/isObject";
|
|
4
4
|
import { validateNode } from "../validateNode";
|
|
@@ -52,7 +52,7 @@ function validateItems({ node, data, pointer = "#", path }: JsonSchemaValidatorP
|
|
|
52
52
|
return node.createError("invalid-data-error", { pointer, value: data, schema });
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const errors:
|
|
55
|
+
const errors: ValidationReturnType = [];
|
|
56
56
|
if (node.items) {
|
|
57
57
|
for (let i = schema.prefixItems?.length ?? 0; i < data.length; i += 1) {
|
|
58
58
|
const itemData = data[i];
|
|
@@ -3,6 +3,7 @@ import { compileSchema } from "../compileSchema";
|
|
|
3
3
|
import { isJsonError } from "../types";
|
|
4
4
|
import { reduceOneOfDeclarator, reduceOneOfFuzzy } from "./oneOf";
|
|
5
5
|
import settings from "../settings";
|
|
6
|
+
import { draftEditor } from "../draftEditor";
|
|
6
7
|
const DECLARATOR_ONEOF = settings.DECLARATOR_ONEOF;
|
|
7
8
|
|
|
8
9
|
describe("keyword : oneof : validate", () => {
|
|
@@ -38,7 +39,7 @@ describe("keyword : oneOf : reduce", () => {
|
|
|
38
39
|
]
|
|
39
40
|
}).reduceNode(111);
|
|
40
41
|
|
|
41
|
-
assert.deepEqual(node
|
|
42
|
+
assert.deepEqual(node?.schema, { type: "number", title: "A Number" });
|
|
42
43
|
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
|
|
43
44
|
});
|
|
44
45
|
|
|
@@ -72,7 +73,7 @@ describe("keyword : oneOf : reduce", () => {
|
|
|
72
73
|
oneOf: [{ required: ["a"], properties: { a: { type: "string" } } }, { $ref: "#/$defs/withData" }]
|
|
73
74
|
}).reduceNode({ b: 111 });
|
|
74
75
|
|
|
75
|
-
assert.deepEqual(node
|
|
76
|
+
assert.deepEqual(node?.schema, { required: ["b"], properties: { b: { type: "number" } } });
|
|
76
77
|
// @note that we override nested oneOfIndex
|
|
77
78
|
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
|
|
78
79
|
});
|
|
@@ -83,7 +84,7 @@ describe("keyword : oneOf : reduce", () => {
|
|
|
83
84
|
oneOf: [{ required: ["a"], properties: { a: false } }, { $ref: "#/$defs/withData" }]
|
|
84
85
|
}).reduceNode({ b: 111 });
|
|
85
86
|
|
|
86
|
-
assert.deepEqual(node
|
|
87
|
+
assert.deepEqual(node?.schema, { required: ["b"], properties: { b: true } });
|
|
87
88
|
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
|
|
88
89
|
});
|
|
89
90
|
|
|
@@ -101,7 +102,7 @@ describe("keyword : oneOf : reduce", () => {
|
|
|
101
102
|
]
|
|
102
103
|
}).reduceNode({ title: 4 });
|
|
103
104
|
|
|
104
|
-
assert.deepEqual(node
|
|
105
|
+
assert.deepEqual(node?.schema, { type: "object", properties: { title: { type: "number" } } });
|
|
105
106
|
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
|
|
106
107
|
});
|
|
107
108
|
|
|
@@ -119,7 +120,7 @@ describe("keyword : oneOf : reduce", () => {
|
|
|
119
120
|
]
|
|
120
121
|
}).reduceNode({ title: 4, test: 2 });
|
|
121
122
|
|
|
122
|
-
assert.deepEqual(node
|
|
123
|
+
assert.deepEqual(node?.schema, { type: "object", additionalProperties: { type: "number" } });
|
|
123
124
|
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
|
|
124
125
|
});
|
|
125
126
|
});
|
|
@@ -130,7 +131,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
|
|
|
130
131
|
oneOf: [{ type: "string" }, { type: "number" }, { type: "object" }]
|
|
131
132
|
});
|
|
132
133
|
const res = reduceOneOfFuzzy({ node, data: 4, pointer: "#", path: [] });
|
|
133
|
-
assert.deepEqual(res
|
|
134
|
+
assert.deepEqual(res?.schema, { type: "number" });
|
|
134
135
|
assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
|
|
135
136
|
});
|
|
136
137
|
|
|
@@ -142,7 +143,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
|
|
|
142
143
|
]
|
|
143
144
|
});
|
|
144
145
|
const res = reduceOneOfFuzzy({ node, data: "anasterixcame", pointer: "#", path: [] });
|
|
145
|
-
assert.deepEqual(res
|
|
146
|
+
assert.deepEqual(res?.schema, { type: "string", pattern: "asterix" });
|
|
146
147
|
assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
|
|
147
148
|
});
|
|
148
149
|
|
|
@@ -155,7 +156,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
|
|
|
155
156
|
oneOf: [{ $ref: "#/definitions/a" }, { $ref: "#/definitions/b" }]
|
|
156
157
|
});
|
|
157
158
|
const res = reduceOneOfFuzzy({ node, data: "anasterixcame", pointer: "#", path: [] });
|
|
158
|
-
assert.deepEqual(res
|
|
159
|
+
assert.deepEqual(res?.schema, { type: "string", pattern: "asterix" });
|
|
159
160
|
assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
|
|
160
161
|
});
|
|
161
162
|
|
|
@@ -169,7 +170,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
|
|
|
169
170
|
]
|
|
170
171
|
});
|
|
171
172
|
const res = reduceOneOfFuzzy({ node, data: { description: "..." }, pointer: "#", path: [] });
|
|
172
|
-
assert.deepEqual(res
|
|
173
|
+
assert.deepEqual(res?.schema, {
|
|
173
174
|
type: "object",
|
|
174
175
|
properties: { description: { type: "string" } }
|
|
175
176
|
});
|
|
@@ -183,7 +184,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
|
|
|
183
184
|
]
|
|
184
185
|
});
|
|
185
186
|
const res = reduceOneOfFuzzy({ node, data: { title: "asterix" }, pointer: "#", path: [] });
|
|
186
|
-
assert.deepEqual(res
|
|
187
|
+
assert.deepEqual(res?.schema, {
|
|
187
188
|
type: "object",
|
|
188
189
|
properties: { title: { type: "string" } }
|
|
189
190
|
});
|
|
@@ -199,7 +200,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
|
|
|
199
200
|
]
|
|
200
201
|
});
|
|
201
202
|
const res = reduceOneOfFuzzy({ node, data: { a: 0, b: 1 }, pointer: "#", path: [] });
|
|
202
|
-
assert.deepEqual(res
|
|
203
|
+
assert.deepEqual(res?.schema, {
|
|
203
204
|
type: "object",
|
|
204
205
|
properties: { a: t, b: t, c: t }
|
|
205
206
|
});
|
|
@@ -216,7 +217,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
|
|
|
216
217
|
]
|
|
217
218
|
});
|
|
218
219
|
const res = reduceOneOfFuzzy({ node, data: { a: true, b: 1 }, pointer: "#", path: [] });
|
|
219
|
-
assert.deepEqual(res
|
|
220
|
+
assert.deepEqual(res?.schema, {
|
|
220
221
|
type: "object",
|
|
221
222
|
properties: { a: { type: "boolean" }, b: t, d: t }
|
|
222
223
|
});
|
|
@@ -252,7 +253,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
|
|
|
252
253
|
node,
|
|
253
254
|
data: { type: "teaser", redirectUrl: "http://example.com/test/pay/article.html" }
|
|
254
255
|
});
|
|
255
|
-
assert.deepEqual(res
|
|
256
|
+
assert.deepEqual(res?.schema, {
|
|
256
257
|
type: "object",
|
|
257
258
|
properties: {
|
|
258
259
|
redirectUrl: { format: "url", type: "string" },
|
|
@@ -284,7 +285,7 @@ describe("keyword : oneof-property : reduce", () => {
|
|
|
284
285
|
]
|
|
285
286
|
});
|
|
286
287
|
const res = reduceOneOfDeclarator({ node, data: { name: "2", title: 123 }, pointer: "#", path: [] });
|
|
287
|
-
assert.deepEqual(res
|
|
288
|
+
assert.deepEqual(res?.schema, {
|
|
288
289
|
type: "object",
|
|
289
290
|
properties: {
|
|
290
291
|
name: { type: "string", pattern: "^2$" },
|
|
@@ -318,7 +319,7 @@ describe("keyword : oneof-property : reduce", () => {
|
|
|
318
319
|
pointer: "#",
|
|
319
320
|
path: []
|
|
320
321
|
});
|
|
321
|
-
assert.deepEqual(res
|
|
322
|
+
assert.deepEqual(res?.schema, {
|
|
322
323
|
type: "object",
|
|
323
324
|
properties: {
|
|
324
325
|
name: { type: "string", pattern: "^2$" },
|
|
@@ -377,3 +378,137 @@ describe("keyword : oneof-property : reduce", () => {
|
|
|
377
378
|
});
|
|
378
379
|
});
|
|
379
380
|
});
|
|
381
|
+
|
|
382
|
+
describe("keyword : oneof-fuzzy : validate", () => {
|
|
383
|
+
it("should return one-of-error oneOfProperty does not match", () => {
|
|
384
|
+
const node = compileSchema(
|
|
385
|
+
{
|
|
386
|
+
type: "array",
|
|
387
|
+
items: {
|
|
388
|
+
oneOfProperty: "id",
|
|
389
|
+
oneOf: [
|
|
390
|
+
{
|
|
391
|
+
type: "object",
|
|
392
|
+
required: ["id"],
|
|
393
|
+
properties: { id: { const: "one" } }
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
type: "object",
|
|
397
|
+
required: ["id"],
|
|
398
|
+
properties: { id: { const: "two" } }
|
|
399
|
+
}
|
|
400
|
+
]
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
{ drafts: [draftEditor] }
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
const { errors } = node.validate([{ id: "unknown" }]);
|
|
407
|
+
|
|
408
|
+
assert.equal(errors.length, 1);
|
|
409
|
+
assert.deepEqual(errors[0].code, "one-of-error");
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("should return validation errors of object identified by oneOfProperty", () => {
|
|
413
|
+
const node = compileSchema(
|
|
414
|
+
{
|
|
415
|
+
type: "array",
|
|
416
|
+
items: {
|
|
417
|
+
oneOfProperty: "id",
|
|
418
|
+
oneOf: [
|
|
419
|
+
{
|
|
420
|
+
type: "object",
|
|
421
|
+
required: ["id"],
|
|
422
|
+
properties: {
|
|
423
|
+
id: { const: "one" },
|
|
424
|
+
title: { type: "string" }
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
]
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
{ drafts: [draftEditor] }
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const { errors } = node.validate([{ id: "one", title: 123 }]);
|
|
434
|
+
|
|
435
|
+
assert.equal(errors.length, 1);
|
|
436
|
+
assert.deepEqual(errors[0].code, "type-error");
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// issue json-editor
|
|
440
|
+
it("should return unique-items error for failed oneOf item", () => {
|
|
441
|
+
const node = compileSchema(
|
|
442
|
+
{
|
|
443
|
+
type: "object",
|
|
444
|
+
required: ["main"],
|
|
445
|
+
properties: {
|
|
446
|
+
main: {
|
|
447
|
+
type: "array",
|
|
448
|
+
items: {
|
|
449
|
+
oneOfProperty: "type",
|
|
450
|
+
oneOf: [{ $ref: "#/$defs/parent" }]
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
$defs: {
|
|
455
|
+
parent: {
|
|
456
|
+
type: "object",
|
|
457
|
+
title: "Parent",
|
|
458
|
+
description:
|
|
459
|
+
"Adding a duplicate item to this list fails as uniqueItems=true in children. @todo correct error message",
|
|
460
|
+
required: ["type", "children"],
|
|
461
|
+
properties: {
|
|
462
|
+
type: {
|
|
463
|
+
options: { hidden: true },
|
|
464
|
+
type: "string",
|
|
465
|
+
const: "parent"
|
|
466
|
+
},
|
|
467
|
+
children: {
|
|
468
|
+
type: "array",
|
|
469
|
+
title: "Children",
|
|
470
|
+
uniqueItems: true,
|
|
471
|
+
items: {
|
|
472
|
+
oneOfProperty: "type",
|
|
473
|
+
oneOf: [
|
|
474
|
+
{
|
|
475
|
+
type: "object",
|
|
476
|
+
title: "Child: First",
|
|
477
|
+
required: ["type"],
|
|
478
|
+
properties: {
|
|
479
|
+
type: {
|
|
480
|
+
options: { hidden: true },
|
|
481
|
+
type: "string",
|
|
482
|
+
const: "one"
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
type: "object",
|
|
488
|
+
title: "Child: Second",
|
|
489
|
+
required: ["type"],
|
|
490
|
+
properties: {
|
|
491
|
+
type: {
|
|
492
|
+
options: { hidden: true },
|
|
493
|
+
type: "string",
|
|
494
|
+
const: "two"
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
{ drafts: [draftEditor] }
|
|
506
|
+
);
|
|
507
|
+
const { errors } = node.validate({
|
|
508
|
+
main: [{ type: "parent", children: [{ type: "one" }, { type: "one" }] }]
|
|
509
|
+
});
|
|
510
|
+
assert.equal(errors.length, 1);
|
|
511
|
+
assert.deepEqual(errors[0].data.pointer, "#/main/0/children/1");
|
|
512
|
+
assert.deepEqual(errors[0].code, "unique-items-error");
|
|
513
|
+
});
|
|
514
|
+
});
|
package/src/keywords/oneOf.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
JsonSchemaReducerParams,
|
|
4
4
|
JsonSchemaValidatorParams,
|
|
5
5
|
ValidationPath,
|
|
6
|
-
|
|
6
|
+
ValidationReturnType
|
|
7
7
|
} from "../Keyword";
|
|
8
8
|
import { isSchemaNode, SchemaNode } from "../types";
|
|
9
9
|
import settings from "../settings";
|
|
@@ -57,7 +57,7 @@ function reduceOneOf({ node, data, pointer, path }: Omit<JsonSchemaReducerParams
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const matches: { index: number; node: SchemaNode }[] = [];
|
|
60
|
-
const errors:
|
|
60
|
+
const errors: ValidationReturnType[] = [];
|
|
61
61
|
for (let i = 0; i < node.oneOf.length; i += 1) {
|
|
62
62
|
const validationErrors = validateNode(node.oneOf[i], data, pointer, path);
|
|
63
63
|
if (validationErrors.length === 0) {
|
|
@@ -106,7 +106,7 @@ export function reduceOneOfDeclarator({ node, data, pointer, path }: Omit<JsonSc
|
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const errors:
|
|
109
|
+
const errors: ValidationReturnType = [];
|
|
110
110
|
const oneOfProperty = node.schema[DECLARATOR_ONEOF];
|
|
111
111
|
const oneOfValue = getValue(data, oneOfProperty);
|
|
112
112
|
|
|
@@ -236,13 +236,73 @@ export function reduceOneOfFuzzy({ node, data, pointer, path }: Omit<JsonSchemaR
|
|
|
236
236
|
return oneOfResult;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
function validateFromDeclarator({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
|
|
240
|
+
const { oneOf, schema } = node;
|
|
241
|
+
if (!oneOf) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// with a declarator we only validate by a declarator to retrieve matches.
|
|
246
|
+
// - if a single match was found, we return validation errors if any
|
|
247
|
+
// - if no match was found we return a one-of-error
|
|
248
|
+
// - if multiples matches were found we return a multiple-one-of-error
|
|
249
|
+
const oneOfProperty = schema[DECLARATOR_ONEOF];
|
|
250
|
+
const oneOfValue = getValue(data, oneOfProperty);
|
|
251
|
+
const matches: { index: number; node: SchemaNode }[] = [];
|
|
252
|
+
const errors: ValidationReturnType = [];
|
|
253
|
+
for (const oneOfNode of oneOf) {
|
|
254
|
+
const { node: oneOfPropertyNode, error } = oneOfNode.getNodeChild(oneOfProperty, oneOfValue);
|
|
255
|
+
if (oneOfPropertyNode) {
|
|
256
|
+
const validationResult = validateNode(oneOfPropertyNode, oneOfValue, `${pointer}/${oneOfProperty}`, path);
|
|
257
|
+
if (validationResult.length > 0) {
|
|
258
|
+
errors.push(...validationResult);
|
|
259
|
+
} else {
|
|
260
|
+
matches.push({ index: oneOf.indexOf(oneOfNode), node: oneOfNode });
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
console.log(
|
|
264
|
+
`jlib oneOf error: failed getting schema for '${oneOfProperty}' to resolve ${DECLARATOR_ONEOF} in ${pointer}/oneOf/${oneOf.indexOf(oneOfNode)}`,
|
|
265
|
+
error
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (matches.length === 1) {
|
|
271
|
+
const match = matches[0];
|
|
272
|
+
match.node.oneOfIndex = match.index; // @evaluation-info
|
|
273
|
+
return validateNode(match.node, data, pointer, path);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (matches.length > 1) {
|
|
277
|
+
return node.createError("multiple-one-of-error", {
|
|
278
|
+
value: data,
|
|
279
|
+
pointer,
|
|
280
|
+
schema,
|
|
281
|
+
matches
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return node.createError("one-of-error", {
|
|
286
|
+
value: JSON.stringify(data),
|
|
287
|
+
pointer,
|
|
288
|
+
schema,
|
|
289
|
+
oneOf: schema.oneOf,
|
|
290
|
+
errors
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
239
294
|
function oneOfValidator({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
|
|
240
295
|
const { oneOf, schema } = node;
|
|
241
296
|
if (!oneOf) {
|
|
242
297
|
return;
|
|
243
298
|
}
|
|
299
|
+
|
|
300
|
+
if (schema[DECLARATOR_ONEOF]) {
|
|
301
|
+
return validateFromDeclarator({ node, data, pointer, path });
|
|
302
|
+
}
|
|
303
|
+
|
|
244
304
|
const matches: { index: number; node: SchemaNode }[] = [];
|
|
245
|
-
const errors:
|
|
305
|
+
const errors: ValidationReturnType = [];
|
|
246
306
|
for (let i = 0; i < oneOf.length; i += 1) {
|
|
247
307
|
const validationResult = validateNode(oneOf[i], data, pointer, path);
|
|
248
308
|
if (validationResult.length > 0) {
|