json-schema-library 11.0.5 → 11.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/.mocharc.js +1 -0
- package/CHANGELOG.md +17 -0
- package/README.md +120 -0
- package/bowtie/.editorconfig +21 -0
- package/bowtie/.prettierrc +6 -0
- package/bowtie/BOWTIE.md +54 -0
- package/bowtie/Dockerfile +6 -0
- package/bowtie/bowtie-api.ts +101 -0
- package/bowtie/bowtie.test.ts +76 -0
- package/bowtie/bowtie.ts +156 -0
- package/bowtie/package.json +11 -0
- package/bowtie/tsconfig.json +12 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +39 -470
- package/dist/index.d.mts +39 -470
- package/dist/index.mjs +1 -1
- package/dist/jlib.js +2 -13
- package/dist/remotes/index.cjs +1 -0
- package/dist/remotes/index.d.cts +7 -0
- package/dist/remotes/index.d.mts +7 -0
- package/dist/remotes/index.mjs +1 -0
- package/dist/types-B2wwNWyo.d.cts +513 -0
- package/dist/types-BhTU1l2h.d.mts +513 -0
- package/index.ts +10 -4
- package/package.json +14 -8
- package/src/Keyword.ts +37 -12
- package/src/SchemaNode.ts +84 -16
- package/src/compileSchema.ts +56 -4
- package/src/draft04/keywords/$ref.ts +22 -14
- package/src/draft04/keywords/exclusiveMaximum.ts +14 -0
- package/src/draft04/keywords/exclusiveMinimum.ts +14 -0
- package/src/draft04/validateSchema.test.ts +20 -0
- package/src/draft06/keywords/$ref.ts +15 -5
- package/src/draft2019-09/keywords/$ref.test.ts +3 -1
- package/src/draft2019-09/keywords/$ref.ts +40 -16
- package/src/draft2019-09/keywords/additionalItems.ts +33 -10
- package/src/draft2019-09/keywords/items.ts +32 -10
- package/src/draft2019-09/keywords/unevaluatedItems.ts +19 -6
- package/src/draft2019-09/methods/getData.ts +1 -1
- package/src/draft2019-09/validateSchema.test.ts +28 -0
- package/src/errors/errors.ts +8 -1
- package/src/formats/formats.ts +35 -28
- package/src/formats/hyperjump.d.ts +172 -0
- package/src/keywords/$defs.ts +34 -8
- package/src/keywords/$ref.ts +59 -13
- 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 +34 -11
- 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/methods/getData.ts +1 -1
- 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 +6 -3
- package/src/validateSchema.test.ts +312 -0
- package/tsconfig.json +11 -4
- package/tsconfig.test.json +9 -2
- package/tsdown.config.ts +1 -1
- package/Dockerfile +0 -6
- package/bowtie_jlib.js +0 -140
- package/remotes/draft04.json +0 -150
- package/remotes/draft06.json +0 -155
- package/remotes/draft07.json +0 -155
- package/remotes/draft2019-09.json +0 -42
- package/remotes/draft2019-09_meta_applicator.json +0 -53
- package/remotes/draft2019-09_meta_content.json +0 -14
- package/remotes/draft2019-09_meta_core.json +0 -54
- package/remotes/draft2019-09_meta_format.json +0 -11
- package/remotes/draft2019-09_meta_meta-data.json +0 -34
- package/remotes/draft2019-09_meta_validation.json +0 -95
- package/remotes/draft2020-12.json +0 -55
- package/remotes/draft2020-12_meta_applicator.json +0 -45
- package/remotes/draft2020-12_meta_content.json +0 -14
- package/remotes/draft2020-12_meta_core.json +0 -48
- package/remotes/draft2020-12_meta_format_annotation.json +0 -11
- package/remotes/draft2020-12_meta_format_assertion.json +0 -11
- package/remotes/draft2020-12_meta_meta_data.json +0 -34
- package/remotes/draft2020-12_meta_unevaluated.json +0 -12
- package/remotes/draft2020-12_meta_validation.json +0 -87
- package/remotes/index.ts +0 -48
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { strict as assert } from "assert";
|
|
2
|
+
import { compileSchema } from "../compileSchema";
|
|
3
|
+
import { draft2020 } from "../draft2020";
|
|
4
|
+
import { extendDraft } from "../Draft";
|
|
5
|
+
import { propertyDependenciesKeyword } from "./propertyDependencies";
|
|
6
|
+
import { isSchemaNode } from "../SchemaNode";
|
|
7
|
+
|
|
8
|
+
const drafts = [
|
|
9
|
+
extendDraft(draft2020, {
|
|
10
|
+
keywords: [propertyDependenciesKeyword]
|
|
11
|
+
})
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
describe("keyword : propertyDependencies : validate", () => {
|
|
15
|
+
it("should return error if schema at matching property+value is invalid", () => {
|
|
16
|
+
const node = compileSchema(
|
|
17
|
+
{
|
|
18
|
+
type: "array",
|
|
19
|
+
items: {
|
|
20
|
+
propertyDependencies: {
|
|
21
|
+
propertyName: {
|
|
22
|
+
propertyValue: {
|
|
23
|
+
$ref: "#/$defs/object"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
$defs: {
|
|
29
|
+
object: {
|
|
30
|
+
type: "object",
|
|
31
|
+
required: ["propertyName", "test"],
|
|
32
|
+
properties: {
|
|
33
|
+
propertyName: { type: "string" },
|
|
34
|
+
test: { type: "string" }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{ drafts }
|
|
40
|
+
);
|
|
41
|
+
const { errors } = node.validate([{ propertyName: "propertyValue", test: 123 }]);
|
|
42
|
+
assert.equal(errors.length, 1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should return all errors for schemata at matching property+value", () => {
|
|
46
|
+
const node = compileSchema(
|
|
47
|
+
{
|
|
48
|
+
type: "array",
|
|
49
|
+
items: {
|
|
50
|
+
propertyDependencies: {
|
|
51
|
+
propertyName: {
|
|
52
|
+
propertyValue: {
|
|
53
|
+
$ref: "#/$defs/object"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
type: {
|
|
57
|
+
headline: {
|
|
58
|
+
$ref: "#/$defs/object"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
$defs: {
|
|
64
|
+
object: {
|
|
65
|
+
type: "object",
|
|
66
|
+
required: ["propertyName", "type", "test"],
|
|
67
|
+
properties: {
|
|
68
|
+
propertyName: { type: "string" },
|
|
69
|
+
type: { type: "string" },
|
|
70
|
+
test: { type: "number" }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{ drafts }
|
|
76
|
+
);
|
|
77
|
+
const { errors } = node.validate([{ propertyName: "propertyValue", type: "headline", test: "123" }]);
|
|
78
|
+
assert.equal(errors.length, 2);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should be valid for valid schema matching property+value", () => {
|
|
82
|
+
const node = compileSchema(
|
|
83
|
+
{
|
|
84
|
+
type: "array",
|
|
85
|
+
items: {
|
|
86
|
+
propertyDependencies: {
|
|
87
|
+
propertyName: {
|
|
88
|
+
propertyValue: {
|
|
89
|
+
$ref: "#/$defs/object"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
$defs: {
|
|
95
|
+
object: {
|
|
96
|
+
type: "object",
|
|
97
|
+
required: ["propertyName", "type", "test"],
|
|
98
|
+
properties: {
|
|
99
|
+
propertyName: { type: "string" },
|
|
100
|
+
type: { type: "string" },
|
|
101
|
+
test: { type: "number" }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{ drafts }
|
|
107
|
+
);
|
|
108
|
+
const { errors } = node.validate([{ propertyName: "propertyValue", type: "headline", test: 123 }]);
|
|
109
|
+
assert.equal(errors.length, 0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should be valid for valid schema matching property+number", () => {
|
|
113
|
+
const node = compileSchema(
|
|
114
|
+
{
|
|
115
|
+
type: "array",
|
|
116
|
+
items: {
|
|
117
|
+
propertyDependencies: {
|
|
118
|
+
test: {
|
|
119
|
+
"123": {
|
|
120
|
+
$ref: "#/$defs/object"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
$defs: {
|
|
126
|
+
object: {
|
|
127
|
+
type: "object",
|
|
128
|
+
required: ["propertyName", "type", "test"],
|
|
129
|
+
properties: {
|
|
130
|
+
propertyName: { type: "string" },
|
|
131
|
+
type: { type: "string" },
|
|
132
|
+
test: { type: "number" }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{ drafts }
|
|
138
|
+
);
|
|
139
|
+
const { errors } = node.validate([{ propertyName: "propertyValue", type: "headline", test: 123 }]);
|
|
140
|
+
assert.equal(errors.length, 0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("keyword : propertyDependencies : validate", () => {
|
|
145
|
+
it("should return reduced schema of matching property+value", () => {
|
|
146
|
+
const node = compileSchema(
|
|
147
|
+
{
|
|
148
|
+
type: "array",
|
|
149
|
+
items: {
|
|
150
|
+
properties: {
|
|
151
|
+
id: { type: "string" }
|
|
152
|
+
},
|
|
153
|
+
propertyDependencies: {
|
|
154
|
+
propertyName: {
|
|
155
|
+
propertyValue: {
|
|
156
|
+
$ref: "#/$defs/object"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
$defs: {
|
|
162
|
+
object: {
|
|
163
|
+
type: "object",
|
|
164
|
+
required: ["propertyName", "test"],
|
|
165
|
+
properties: {
|
|
166
|
+
propertyName: { type: "string" },
|
|
167
|
+
test: { type: "string" }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{ drafts }
|
|
173
|
+
);
|
|
174
|
+
const reducedNode = node.getNode("#/0", [{ propertyName: "propertyValue", test: 123 }])?.node;
|
|
175
|
+
assert(isSchemaNode(reducedNode));
|
|
176
|
+
assert(reducedNode.schema.propertyDependencies == null);
|
|
177
|
+
assert(reducedNode.schema.properties.id);
|
|
178
|
+
assert(reducedNode.schema.properties.propertyName);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Keyword,
|
|
3
|
+
JsonSchemaValidatorParams,
|
|
4
|
+
JsonSchemaReducerParams,
|
|
5
|
+
ValidationReturnType,
|
|
6
|
+
ValidationAnnotation
|
|
7
|
+
} from "../Keyword";
|
|
8
|
+
import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
|
|
9
|
+
import { hasProperty } from "../utils/hasProperty";
|
|
10
|
+
import { isObject } from "../utils/isObject";
|
|
11
|
+
import { mergeSchema } from "../utils/mergeSchema";
|
|
12
|
+
import sanitizeErrors from "../utils/sanitizeErrors";
|
|
13
|
+
import { validateNode } from "../validateNode";
|
|
14
|
+
|
|
15
|
+
const KEYWORD = "propertyDependencies";
|
|
16
|
+
|
|
17
|
+
function findMatchingSchemata(node: SchemaNode, data: Record<string, unknown>) {
|
|
18
|
+
const dependentProperties = node[KEYWORD];
|
|
19
|
+
if (dependentProperties == null) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const dependentPropertyNames = Object.keys(dependentProperties);
|
|
23
|
+
const matchingSchemata: { property: string; value: string; node: SchemaNode }[] = [];
|
|
24
|
+
for (const propertyName of dependentPropertyNames) {
|
|
25
|
+
if (hasProperty(data, propertyName)) {
|
|
26
|
+
const value = data[propertyName];
|
|
27
|
+
if (dependentProperties[propertyName][value as string]) {
|
|
28
|
+
matchingSchemata.push({
|
|
29
|
+
property: propertyName,
|
|
30
|
+
value: `${value}`,
|
|
31
|
+
node: dependentProperties[propertyName][value as string]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return matchingSchemata;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @experimental `propertyDependencies` to resolve schema by nested name and value
|
|
41
|
+
* @reference https://docs.google.com/presentation/d/1ajXlCQcsjjiMLsluFIILR7sN5aDRBnfqQ9DLbcFbqjI/mobilepresent?slide=id.p
|
|
42
|
+
*
|
|
43
|
+
* - matching schemas are resolved and validiated
|
|
44
|
+
* - multiple matching schemas are resolved and validiated
|
|
45
|
+
* - ignores keyword if no schema is matched
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* {
|
|
49
|
+
* type: "object",
|
|
50
|
+
* propertyDependencies: {
|
|
51
|
+
* propertyName: {
|
|
52
|
+
* propertyValue: { $ref: "#/$defs/schema" }
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* matches
|
|
58
|
+
*
|
|
59
|
+
* {
|
|
60
|
+
* "propertyName": "propertyValue",
|
|
61
|
+
* "otherData": 123
|
|
62
|
+
* } with "#/$defs/schema"
|
|
63
|
+
*/
|
|
64
|
+
export const propertyDependenciesKeyword: Keyword = {
|
|
65
|
+
id: KEYWORD,
|
|
66
|
+
keyword: KEYWORD,
|
|
67
|
+
parse: parsePropertyDependencies,
|
|
68
|
+
addValidate: (node) => node[KEYWORD] != null,
|
|
69
|
+
validate: validatePropertyDependencies,
|
|
70
|
+
addReduce: (node) => node[KEYWORD] != null,
|
|
71
|
+
reduce: reducePropertyDependencies
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function parsePropertyDependencies(node: SchemaNode) {
|
|
75
|
+
const propertyDependencies = node.schema[KEYWORD];
|
|
76
|
+
if (!isObject(propertyDependencies)) {
|
|
77
|
+
return node.createError("schema-error", {
|
|
78
|
+
pointer: `${node.schemaLocation}/${KEYWORD}`,
|
|
79
|
+
schema: node.schema,
|
|
80
|
+
value: propertyDependencies,
|
|
81
|
+
message: `Keyword '${KEYWORD}' must be an object - received '${typeof propertyDependencies}'`
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const parsed: Record<string, Record<string, SchemaNode>> = {};
|
|
85
|
+
const errors: ValidationAnnotation[] = [];
|
|
86
|
+
Object.keys(propertyDependencies).map((propertyName) => {
|
|
87
|
+
const values = propertyDependencies[propertyName];
|
|
88
|
+
if (!isObject(values)) {
|
|
89
|
+
errors.push(
|
|
90
|
+
node.createError("schema-error", {
|
|
91
|
+
pointer: `${node.schemaLocation}/${KEYWORD}/${propertyName}`,
|
|
92
|
+
schema: node.schema,
|
|
93
|
+
value: propertyDependencies,
|
|
94
|
+
message: `Keyword '${KEYWORD}[string]' must be an object - received '${typeof propertyDependencies}'`
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
Object.keys(values).forEach((value) => {
|
|
100
|
+
const schema = values[value];
|
|
101
|
+
if (!(isJsonSchema(schema) || isBooleanSchema(schema))) {
|
|
102
|
+
errors.push(
|
|
103
|
+
node.createError("schema-error", {
|
|
104
|
+
pointer: `${node.schemaLocation}/${KEYWORD}/${propertyName}/${value}`,
|
|
105
|
+
schema: node.schema,
|
|
106
|
+
value: schema,
|
|
107
|
+
message: `Keyword '${KEYWORD}[string][string]' must be a valid JSON Schema - received '${typeof schema}'`
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
parsed[propertyName] = parsed[propertyName] ?? {};
|
|
113
|
+
parsed[propertyName][value] = node.compileSchema(
|
|
114
|
+
schema,
|
|
115
|
+
`${node.evaluationPath}/${KEYWORD}/${propertyName}/${value}`,
|
|
116
|
+
`${node.schemaLocation}/${KEYWORD}/${propertyName}/${value}`
|
|
117
|
+
);
|
|
118
|
+
if (parsed[propertyName][value].schemaValidation) {
|
|
119
|
+
errors.push(...parsed[propertyName][value].schemaValidation);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
node[KEYWORD] = parsed;
|
|
124
|
+
return errors;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function validatePropertyDependencies({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
|
|
128
|
+
if (!isObject(data)) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
const matchingSchemata = findMatchingSchemata(node, data);
|
|
132
|
+
if (matchingSchemata == null || matchingSchemata.length === 0) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
const errors: ValidationReturnType[] = [];
|
|
136
|
+
for (const match of matchingSchemata) {
|
|
137
|
+
const result = validateNode(match.node, data, pointer, path);
|
|
138
|
+
errors.push(result);
|
|
139
|
+
}
|
|
140
|
+
return sanitizeErrors(errors);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function reducePropertyDependencies({ node, data, key, pointer, path }: JsonSchemaReducerParams) {
|
|
144
|
+
if (!isObject(data)) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const matchingSchemata = findMatchingSchemata(node, data);
|
|
148
|
+
if (matchingSchemata == null || matchingSchemata.length === 0) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let mergedSchema = {};
|
|
153
|
+
let dynamicId = "";
|
|
154
|
+
for (const match of matchingSchemata) {
|
|
155
|
+
const { node: schemaNode } = match.node.reduceNode(data, { key, pointer, path });
|
|
156
|
+
if (schemaNode) {
|
|
157
|
+
const nestedDynamicId = schemaNode.dynamicId?.replace(node.dynamicId, "") ?? "";
|
|
158
|
+
const localDynamicId =
|
|
159
|
+
nestedDynamicId === "" ? `propertyDependencies/${match.property}/${match.value}` : nestedDynamicId;
|
|
160
|
+
dynamicId += `${dynamicId === "" ? "" : ","}${localDynamicId}`;
|
|
161
|
+
|
|
162
|
+
const schema = mergeSchema(match.node.schema, schemaNode.schema);
|
|
163
|
+
mergedSchema = mergeSchema(mergedSchema, schema, "propertyDependencies");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return node.compileSchema(
|
|
168
|
+
mergedSchema,
|
|
169
|
+
`${node.evaluationPath}/${dynamicId}`,
|
|
170
|
+
node.schemaLocation,
|
|
171
|
+
`${node.schemaLocation}(${dynamicId})`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
@@ -1,29 +1,41 @@
|
|
|
1
|
-
import { JsonError } from "../types";
|
|
1
|
+
import { isBooleanSchema, isJsonSchema, JsonError } from "../types";
|
|
2
2
|
import { isObject } from "../utils/isObject";
|
|
3
3
|
import { SchemaNode } from "../types";
|
|
4
4
|
import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
|
|
5
5
|
import { validateNode } from "../validateNode";
|
|
6
6
|
|
|
7
|
+
const KEYWORD = "propertyNames";
|
|
8
|
+
|
|
7
9
|
export const propertyNamesKeyword: Keyword = {
|
|
8
|
-
id:
|
|
9
|
-
keyword:
|
|
10
|
+
id: KEYWORD,
|
|
11
|
+
keyword: KEYWORD,
|
|
10
12
|
parse: parsePropertyNames,
|
|
11
|
-
addValidate: (
|
|
13
|
+
addValidate: (node) => node.schema[KEYWORD] != null,
|
|
12
14
|
validate: validatePropertyNames
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export function parsePropertyNames(node: SchemaNode) {
|
|
16
|
-
const
|
|
18
|
+
const propertyNames = node.schema[KEYWORD];
|
|
17
19
|
if (propertyNames == null) {
|
|
18
20
|
return;
|
|
19
21
|
}
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
if (!(isJsonSchema(propertyNames) || isBooleanSchema(propertyNames))) {
|
|
23
|
+
return node.createError("schema-error", {
|
|
24
|
+
pointer: `${node.schemaLocation}/${KEYWORD}`,
|
|
25
|
+
schema: node.schema,
|
|
26
|
+
value: propertyNames,
|
|
27
|
+
message: `Keyword '${KEYWORD}' must be a valid JSON Schema - received '${typeof propertyNames}'`
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (isBooleanSchema(propertyNames)) {
|
|
31
|
+
return;
|
|
26
32
|
}
|
|
33
|
+
node.propertyNames = node.compileSchema(
|
|
34
|
+
propertyNames,
|
|
35
|
+
`${node.evaluationPath}/propertyNames`,
|
|
36
|
+
`${node.schemaLocation}/propertyNames`
|
|
37
|
+
);
|
|
38
|
+
return node.schemaValidation;
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
function validatePropertyNames({ node, data, pointer, path }: JsonSchemaValidatorParams) {
|
|
@@ -46,11 +58,11 @@ function validatePropertyNames({ node, data, pointer, path }: JsonSchemaValidato
|
|
|
46
58
|
});
|
|
47
59
|
}
|
|
48
60
|
|
|
49
|
-
if (schema
|
|
61
|
+
if (schema[KEYWORD] === true) {
|
|
50
62
|
return undefined;
|
|
51
63
|
}
|
|
52
64
|
|
|
53
|
-
const propertyNames = node
|
|
65
|
+
const propertyNames = node[KEYWORD];
|
|
54
66
|
if (!isObject(propertyNames)) {
|
|
55
67
|
// ignore invalid schema
|
|
56
68
|
return undefined;
|
|
@@ -59,7 +71,7 @@ function validatePropertyNames({ node, data, pointer, path }: JsonSchemaValidato
|
|
|
59
71
|
const errors: JsonError[] = [];
|
|
60
72
|
const properties = Object.keys(data);
|
|
61
73
|
properties.forEach((prop) => {
|
|
62
|
-
const validationResult = validateNode(propertyNames, prop, `${pointer}
|
|
74
|
+
const validationResult = validateNode(propertyNames, prop, `${pointer}/${prop}`, path);
|
|
63
75
|
if (validationResult.length > 0) {
|
|
64
76
|
errors.push(
|
|
65
77
|
node.createError("invalid-property-name-error", {
|
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) {
|