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.
Files changed (58) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +112 -0
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.cts +93 -16
  5. package/dist/index.d.mts +93 -16
  6. package/dist/index.mjs +1 -1
  7. package/dist/jlib.js +3 -3
  8. package/index.ts +10 -1
  9. package/package.json +11 -8
  10. package/src/Draft.ts +1 -1
  11. package/src/Keyword.ts +36 -10
  12. package/src/SchemaNode.ts +75 -16
  13. package/src/compileSchema.ts +53 -4
  14. package/src/errors/errors.ts +4 -1
  15. package/src/keywords/$defs.ts +34 -8
  16. package/src/keywords/$ref.ts +12 -0
  17. package/src/keywords/additionalProperties.ts +19 -8
  18. package/src/keywords/allOf.ts +44 -18
  19. package/src/keywords/anyOf.ts +38 -19
  20. package/src/keywords/contains.ts +21 -9
  21. package/src/keywords/dependencies.ts +37 -17
  22. package/src/keywords/dependentRequired.ts +56 -38
  23. package/src/keywords/dependentSchemas.ts +37 -13
  24. package/src/keywords/deprecated.ts +32 -8
  25. package/src/keywords/enum.ts +30 -8
  26. package/src/keywords/exclusiveMaximum.ts +21 -2
  27. package/src/keywords/exclusiveMinimum.ts +22 -3
  28. package/src/keywords/format.ts +21 -2
  29. package/src/keywords/ifthenelse.ts +49 -5
  30. package/src/keywords/items.ts +27 -13
  31. package/src/keywords/maxItems.ts +22 -2
  32. package/src/keywords/maxLength.ts +30 -9
  33. package/src/keywords/maxProperties.ts +30 -9
  34. package/src/keywords/maximum.ts +28 -8
  35. package/src/keywords/minItems.ts +30 -9
  36. package/src/keywords/minLength.ts +30 -9
  37. package/src/keywords/minProperties.ts +26 -5
  38. package/src/keywords/minimum.ts +32 -13
  39. package/src/keywords/multipleOf.ts +33 -12
  40. package/src/keywords/not.ts +23 -10
  41. package/src/keywords/oneOf.ts +29 -9
  42. package/src/keywords/pattern.ts +35 -9
  43. package/src/keywords/properties.ts +35 -12
  44. package/src/keywords/propertyDependencies.test.ts +180 -0
  45. package/src/keywords/propertyDependencies.ts +173 -0
  46. package/src/keywords/propertyNames.ts +26 -14
  47. package/src/keywords/required.ts +31 -8
  48. package/src/keywords/type.ts +53 -16
  49. package/src/keywords/unevaluatedItems.ts +24 -8
  50. package/src/keywords/unevaluatedProperties.ts +24 -7
  51. package/src/keywords/uniqueItems.ts +23 -4
  52. package/src/mergeNode.ts +9 -4
  53. package/src/settings.ts +2 -1
  54. package/src/types.ts +1 -1
  55. package/src/utils/isListOfStrings.ts +3 -0
  56. package/src/validate.test.ts +0 -2
  57. package/src/validateNode.ts +2 -2
  58. package/src/validateSchema.test.ts +312 -0
@@ -1,26 +1,39 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
- import { SchemaNode } from "../types";
2
+ import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
3
3
  import { validateNode } from "../validateNode";
4
4
 
5
- export const notKeyword: Keyword = {
6
- id: "not",
7
- keyword: "not",
5
+ const KEYWORD = "not";
6
+
7
+ export const notKeyword: Keyword<"not"> = {
8
+ id: KEYWORD,
9
+ keyword: KEYWORD,
8
10
  parse: parseNot,
9
- addValidate: (node) => node.not != null,
11
+ addValidate: (node) => node[KEYWORD] != null,
10
12
  validate: validateNot
11
13
  };
12
14
 
13
15
  export function parseNot(node: SchemaNode) {
14
16
  const { schema, evaluationPath, schemaLocation } = node;
15
- if (schema.not != null) {
16
- node.not = node.compileSchema(schema.not, `${evaluationPath}/not`, `${schemaLocation}/not`);
17
+ const not = schema[KEYWORD];
18
+ if (not == null) {
19
+ return;
20
+ }
21
+ if (!isJsonSchema(not) && !isBooleanSchema(not)) {
22
+ return node.createError("schema-error", {
23
+ pointer: `${schemaLocation}/${KEYWORD}`,
24
+ schema,
25
+ value: not,
26
+ message: `Keyword '${KEYWORD}' must be a valid JSON Schema - received '${typeof not}'`
27
+ });
17
28
  }
29
+ node[KEYWORD] = node.compileSchema(schema[KEYWORD], `${evaluationPath}/not`, `${schemaLocation}/not`);
30
+ return node[KEYWORD].schemaValidation;
18
31
  }
19
32
 
20
- function validateNot({ node, data, pointer, path }: JsonSchemaValidatorParams) {
33
+ function validateNot({ node, data, pointer, path }: JsonSchemaValidatorParams<"not">) {
21
34
  const { schema } = node;
22
35
  // not has been tested in addValidate
23
- if (validateNode(node.not!, data, pointer, path).length === 0) {
24
- return node.createError("not-error", { value: data, not: schema.not, pointer, schema });
36
+ if (validateNode(node[KEYWORD]!, data, pointer, path).length === 0) {
37
+ return node.createError("not-error", { value: data, not: schema[KEYWORD], pointer, schema });
25
38
  }
26
39
  }
@@ -3,7 +3,8 @@ import {
3
3
  JsonSchemaReducerParams,
4
4
  JsonSchemaValidatorParams,
5
5
  ValidationPath,
6
- ValidationReturnType
6
+ ValidationReturnType,
7
+ ValidationAnnotation
7
8
  } from "../Keyword";
8
9
  import { isSchemaNode, SchemaNode } from "../types";
9
10
  import settings from "../settings";
@@ -13,15 +14,16 @@ import { isObject } from "../utils/isObject";
13
14
  import { validateNode } from "../validateNode";
14
15
  import { joinDynamicId } from "../SchemaNode";
15
16
 
17
+ const KEYWORD = "oneOf";
16
18
  const { DECLARATOR_ONEOF } = settings;
17
19
 
18
20
  export const oneOfKeyword: Keyword = {
19
- id: "oneOf",
20
- keyword: "oneOf",
21
+ id: KEYWORD,
22
+ keyword: KEYWORD,
21
23
  parse: parseOneOf,
22
- addReduce: (node) => node.oneOf != null,
24
+ addReduce: (node) => node[KEYWORD] != null,
23
25
  reduce: reduceOneOf,
24
- addValidate: (node) => node.oneOf != null,
26
+ addValidate: (node) => node[KEYWORD] != null,
25
27
  validate: oneOfValidator
26
28
  };
27
29
 
@@ -37,11 +39,29 @@ export const oneOfFuzzyKeyword: Keyword = {
37
39
 
38
40
  export function parseOneOf(node: SchemaNode) {
39
41
  const { schema, evaluationPath, schemaLocation } = node;
40
- if (Array.isArray(schema.oneOf) && schema.oneOf.length) {
41
- node.oneOf = schema.oneOf.map((s, index) =>
42
- node.compileSchema(s, `${evaluationPath}/oneOf/${index}`, `${schemaLocation}/oneOf/${index}`)
43
- );
42
+ if (schema[KEYWORD] == null) {
43
+ return;
44
+ }
45
+ if (!Array.isArray(schema[KEYWORD])) {
46
+ return node.createError("schema-error", {
47
+ pointer: schemaLocation,
48
+ schema,
49
+ value: schema[KEYWORD],
50
+ message: `Keyword '${KEYWORD}' must be an array - received '${typeof schema[KEYWORD]}'`
51
+ });
52
+ }
53
+ if (schema[KEYWORD].length === 0) {
54
+ return;
44
55
  }
56
+
57
+ node[KEYWORD] = schema[KEYWORD].map((s, index) =>
58
+ node.compileSchema(s, `${evaluationPath}/${KEYWORD}/${index}`, `${schemaLocation}/${KEYWORD}/${index}`)
59
+ );
60
+
61
+ return node[KEYWORD].reduce((errors, node) => {
62
+ if (node.schemaValidation) errors.push(...node.schemaValidation);
63
+ return errors;
64
+ }, [] as ValidationAnnotation[]);
45
65
  }
46
66
 
47
67
  function reduceOneOf({ node, data, pointer, path }: Omit<JsonSchemaReducerParams, "key">) {
@@ -1,22 +1,49 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { SchemaNode } from "../SchemaNode";
2
3
  import settings from "../settings";
3
4
 
5
+ const KEYWORD = "pattern";
4
6
  const { REGEX_FLAGS } = settings;
5
7
 
6
- export const patternKeyword: Keyword = {
7
- id: "pattern",
8
- keyword: "pattern",
9
- addValidate: ({ schema }) => typeof schema.pattern === "string",
8
+ export const patternKeyword: Keyword<"pattern"> = {
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
11
+ parse: parsePattern,
12
+ addValidate: (node) => node[KEYWORD] != null,
10
13
  validate: validatePattern
11
14
  };
12
15
 
13
- function validatePattern({ node, data, pointer = "#" }: JsonSchemaValidatorParams) {
14
- const { schema } = node;
16
+ function parsePattern(node: SchemaNode) {
17
+ const pattern = node.schema[KEYWORD];
18
+ if (pattern == null) {
19
+ return;
20
+ }
21
+ if (typeof pattern !== "string") {
22
+ return node.createError("schema-error", {
23
+ pointer: node.schemaLocation,
24
+ schema: node.schema,
25
+ value: pattern,
26
+ message: `Keyword 'pattern' must be a string - received '${typeof pattern}'`
27
+ });
28
+ }
29
+ try {
30
+ node[KEYWORD] = new RegExp(pattern, node.schema.regexFlags ?? REGEX_FLAGS);
31
+ } catch (e) {
32
+ return node.createError("schema-error", {
33
+ pointer: node.schemaLocation,
34
+ schema: node.schema,
35
+ value: pattern,
36
+ message: (e as Error).message
37
+ });
38
+ }
39
+ }
40
+
41
+ function validatePattern({ node, data, pointer = "#" }: JsonSchemaValidatorParams<"pattern">) {
15
42
  if (typeof data !== "string") {
16
43
  return;
17
44
  }
18
- const pattern = new RegExp(schema.pattern, schema.regexFlags ?? REGEX_FLAGS);
19
- if (pattern.test(data) === false) {
45
+ if (node.pattern.test(data) === false) {
46
+ const { schema } = node;
20
47
  return node.createError("pattern-error", {
21
48
  pattern: schema.pattern,
22
49
  description: schema.patternExample || schema.pattern,
@@ -26,5 +53,4 @@ function validatePattern({ node, data, pointer = "#" }: JsonSchemaValidatorParam
26
53
  pointer
27
54
  });
28
55
  }
29
- return undefined;
30
56
  }
@@ -1,6 +1,12 @@
1
1
  import { getValue } from "../utils/getValue";
2
- import { SchemaNode } from "../types";
3
- import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
2
+ import { JsonError, SchemaNode } from "../types";
3
+ import {
4
+ Keyword,
5
+ JsonSchemaResolverParams,
6
+ JsonSchemaValidatorParams,
7
+ ValidationReturnType,
8
+ ValidationAnnotation
9
+ } from "../Keyword";
4
10
  import { isObject } from "../utils/isObject";
5
11
  import { validateNode } from "../validateNode";
6
12
 
@@ -20,18 +26,35 @@ function propertyResolver({ node, key }: JsonSchemaResolverParams) {
20
26
 
21
27
  export function parseProperties(node: SchemaNode) {
22
28
  const { schema, evaluationPath, schemaLocation } = node;
23
- if (schema.properties) {
24
- const parsedProperties: Record<string, SchemaNode> = {};
25
- Object.keys(schema.properties).forEach((propertyName) => {
26
- const propertyNode = node.compileSchema(
27
- schema.properties[propertyName],
28
- `${evaluationPath}/properties/${propertyName}`,
29
- `${schemaLocation}/properties/${propertyName}`
30
- );
31
- parsedProperties[propertyName] = propertyNode;
29
+ if (schema.properties == null) {
30
+ return;
31
+ }
32
+
33
+ if (schema.properties && !isObject(schema.properties)) {
34
+ return node.createError("schema-error", {
35
+ pointer: schemaLocation,
36
+ schema,
37
+ value: undefined,
38
+ message: "keyword `properties` must be of type `object`"
32
39
  });
33
- node.properties = parsedProperties;
34
40
  }
41
+
42
+ const errors: ValidationAnnotation[] = [];
43
+ const parsedProperties: Record<string, SchemaNode> = {};
44
+ Object.keys(schema.properties).forEach((propertyName) => {
45
+ const propertyNode = node.compileSchema(
46
+ schema.properties[propertyName],
47
+ `${evaluationPath}/properties/${propertyName}`,
48
+ `${schemaLocation}/properties/${propertyName}`
49
+ );
50
+ parsedProperties[propertyName] = propertyNode;
51
+ if (parsedProperties[propertyName].schemaValidation) {
52
+ errors.push(...parsedProperties[propertyName].schemaValidation);
53
+ }
54
+ });
55
+ node.properties = parsedProperties;
56
+
57
+ return errors;
35
58
  }
36
59
 
37
60
  function validateProperties({ node, data, pointer, path }: JsonSchemaValidatorParams) {
@@ -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, JsonSchema, 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: "propertyNames",
9
- keyword: "propertyNames",
10
+ id: KEYWORD,
11
+ keyword: KEYWORD,
10
12
  parse: parsePropertyNames,
11
- addValidate: ({ schema }) => schema.propertyNames != null,
13
+ addValidate: (node) => node.schema[KEYWORD] != null,
12
14
  validate: validatePropertyNames
13
15
  };
14
16
 
15
17
  export function parsePropertyNames(node: SchemaNode) {
16
- const { propertyNames } = node.schema;
18
+ const propertyNames = node.schema[KEYWORD];
17
19
  if (propertyNames == null) {
18
20
  return;
19
21
  }
20
- if (isObject(propertyNames)) {
21
- node.propertyNames = node.compileSchema(
22
- propertyNames,
23
- `${node.evaluationPath}/propertyNames`,
24
- `${node.schemaLocation}/propertyNames`
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.propertyNames === true) {
61
+ if (schema[KEYWORD] === true) {
50
62
  return undefined;
51
63
  }
52
64
 
53
- const propertyNames = node.propertyNames;
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}/prop`, path);
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", {