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,45 +1,64 @@
1
1
  import { mergeSchema } from "../utils/mergeSchema";
2
- import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
3
3
  import { SchemaNode } from "../types";
4
4
  import { validateNode } from "../validateNode";
5
5
 
6
+ const KEYWORD = "anyOf";
7
+
6
8
  export const anyOfKeyword: Keyword = {
7
- id: "anyOf",
8
- keyword: "anyOf",
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
9
11
  parse: parseAnyOf,
10
- addReduce: (node) => node.anyOf != null,
12
+ addReduce: (node) => node[KEYWORD] != null,
11
13
  reduce: reduceAnyOf,
12
- addValidate: (node) => node.anyOf != null,
14
+ addValidate: (node) => node[KEYWORD] != null,
13
15
  validate: validateAnyOf
14
16
  };
15
17
 
16
18
  export function parseAnyOf(node: SchemaNode) {
17
19
  const { schema, evaluationPath, schemaLocation } = node;
18
- if (Array.isArray(schema.anyOf) && schema.anyOf.length) {
19
- node.anyOf = schema.anyOf.map((s, index) =>
20
- node.compileSchema(s, `${evaluationPath}/anyOf/${index}`, `${schemaLocation}/anyOf/${index}`)
21
- );
20
+ if (schema[KEYWORD] == null) {
21
+ return;
22
22
  }
23
+ if (!Array.isArray(schema[KEYWORD])) {
24
+ return node.createError("schema-error", {
25
+ pointer: schemaLocation,
26
+ schema,
27
+ value: schema[KEYWORD],
28
+ message: `Keyword '${KEYWORD}' must be an array - received '${typeof schema[KEYWORD]}'`
29
+ });
30
+ }
31
+ if (schema[KEYWORD].length === 0) {
32
+ return;
33
+ }
34
+ node[KEYWORD] = schema[KEYWORD].map((s, index) =>
35
+ node.compileSchema(s, `${evaluationPath}/${KEYWORD}/${index}`, `${schemaLocation}/${KEYWORD}/${index}`)
36
+ );
37
+
38
+ return node[KEYWORD].reduce((errors, node) => {
39
+ if (node.schemaValidation) errors.push(...node.schemaValidation);
40
+ return errors;
41
+ }, [] as ValidationAnnotation[]);
23
42
  }
24
43
 
25
44
  function reduceAnyOf({ node, data, pointer, path }: JsonSchemaReducerParams) {
26
- if (node.anyOf == null) {
45
+ if (node[KEYWORD] == null) {
27
46
  return;
28
47
  }
29
48
 
30
49
  let mergedSchema = {};
31
50
  let dynamicId = "";
32
- for (let i = 0; i < node.anyOf.length; i += 1) {
33
- if (validateNode(node.anyOf[i], data, pointer, path).length === 0) {
34
- const { node: schemaNode } = node.anyOf[i].reduceNode(data);
51
+ for (let i = 0; i < node[KEYWORD].length; i += 1) {
52
+ if (validateNode(node[KEYWORD][i], data, pointer, path).length === 0) {
53
+ const { node: schemaNode } = node[KEYWORD][i].reduceNode(data);
35
54
 
36
55
  if (schemaNode) {
37
56
  const nestedDynamicId = schemaNode.dynamicId?.replace(node.dynamicId, "") ?? "";
38
- const localDynamicId = nestedDynamicId === "" ? `anyOf/${i}` : nestedDynamicId;
57
+ const localDynamicId = nestedDynamicId === "" ? `${KEYWORD}/${i}` : nestedDynamicId;
39
58
  dynamicId += `${dynamicId === "" ? "" : ","}${localDynamicId}`;
40
59
 
41
- const schema = mergeSchema(node.anyOf[i].schema, schemaNode.schema);
42
- mergedSchema = mergeSchema(mergedSchema, schema, "anyOf");
60
+ const schema = mergeSchema(node[KEYWORD][i].schema, schemaNode.schema);
61
+ mergedSchema = mergeSchema(mergedSchema, schema, KEYWORD);
43
62
  }
44
63
  }
45
64
  }
@@ -52,13 +71,13 @@ function reduceAnyOf({ node, data, pointer, path }: JsonSchemaReducerParams) {
52
71
  }
53
72
 
54
73
  function validateAnyOf({ node, data, pointer, path }: JsonSchemaValidatorParams) {
55
- if (node.anyOf == null) {
74
+ if (node[KEYWORD] == null) {
56
75
  return;
57
76
  }
58
- for (const anyOf of node.anyOf) {
77
+ for (const anyOf of node[KEYWORD]) {
59
78
  if (validateNode(anyOf, data, pointer, path).length === 0) {
60
79
  return undefined;
61
80
  }
62
81
  }
63
- return node.createError("any-of-error", { pointer, schema: node.schema, value: data, anyOf: node.schema.anyOf });
82
+ return node.createError("any-of-error", { pointer, schema: node.schema, value: data, anyOf: node.schema[KEYWORD] });
64
83
  }
@@ -1,20 +1,22 @@
1
1
  import { isObject } from "../utils/isObject";
2
- import { SchemaNode } from "../types";
2
+ import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
3
3
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
4
4
  import { validateNode } from "../validateNode";
5
5
 
6
+ const KEYWORD = "contains";
7
+
6
8
  export const containsKeyword: Keyword = {
7
- id: "contains",
8
- keyword: "contains",
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
9
11
  parse: parseContains,
10
- addValidate: (node) => node.contains != null,
12
+ addValidate: (node) => node[KEYWORD] != null,
11
13
  validate: validateContains,
12
- addReduce: (node) => node.contains != null,
14
+ addReduce: (node) => node[KEYWORD] != null,
13
15
  reduce: ({ node }) => {
14
16
  return node.compileSchema(
15
17
  {
16
18
  items: {
17
- anyOf: [node.contains!.schema] // we tested for contains in addReduce
19
+ anyOf: [node[KEYWORD]!.schema] // we tested for contains in addReduce
18
20
  }
19
21
  },
20
22
  node.evaluationPath,
@@ -24,11 +26,21 @@ export const containsKeyword: Keyword = {
24
26
  };
25
27
 
26
28
  export function parseContains(node: SchemaNode) {
27
- const { schema, evaluationPath } = node;
28
- if (schema.contains == null) {
29
+ const contains = node.schema[KEYWORD];
30
+ if (contains == null) {
29
31
  return;
30
32
  }
31
- node.contains = node.compileSchema(schema.contains, `${evaluationPath}/contains`);
33
+ if (!(isJsonSchema(contains) || isBooleanSchema(contains))) {
34
+ return node.createError("schema-error", {
35
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
36
+ schema: node.schema,
37
+ value: contains,
38
+ message: `Keyword '${KEYWORD}' must be a valid JSON Schema - received '${typeof contains}'`
39
+ });
40
+ }
41
+
42
+ node[KEYWORD] = node.compileSchema(contains, `${node.evaluationPath}/${KEYWORD}`);
43
+ return node[KEYWORD].schemaValidation;
32
44
  }
33
45
 
34
46
  function validateContains({ node, data, pointer, path }: JsonSchemaValidatorParams) {
@@ -1,4 +1,4 @@
1
- import { isSchemaNode, SchemaNode } from "../types";
1
+ import { isBooleanSchema, isJsonSchema, isSchemaNode, SchemaNode } from "../types";
2
2
  import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
3
3
  import { isObject } from "../utils/isObject";
4
4
  import { mergeNode } from "../mergeNode";
@@ -6,38 +6,58 @@ import { hasProperty } from "../utils/hasProperty";
6
6
  import { validateDependentRequired } from "./dependentRequired";
7
7
  import { validateDependentSchemas } from "./dependentSchemas";
8
8
  import sanitizeErrors from "../utils/sanitizeErrors";
9
+ import { isListOfStrings } from "../utils/isListOfStrings";
10
+
11
+ const KEYWORD = "dependencies";
9
12
 
10
13
  export const dependenciesKeyword: Keyword = {
11
- id: "dependencies",
12
- keyword: "dependencies",
14
+ id: KEYWORD,
15
+ keyword: KEYWORD,
13
16
  parse: parseDependencies,
14
17
  order: -9,
15
- addReduce: (node) => node.schema.dependencies != null,
18
+ addReduce: (node) => node.schema[KEYWORD] != null, // because we remap this has to be tested on schema
16
19
  reduce: reduceDependencies,
17
- addValidate: (node) => node.schema.dependencies != null,
20
+ addValidate: (node) => node.schema[KEYWORD] != null, // because we remap this has to be tested on schema
18
21
  validate: validateDependencies
19
22
  };
20
23
 
21
24
  export function parseDependencies(node: SchemaNode) {
22
25
  const { dependencies } = node.schema;
23
26
  if (!isObject(dependencies)) {
24
- return;
27
+ return node.createError("schema-error", {
28
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
29
+ schema: node.schema,
30
+ value: dependencies,
31
+ message: `Keyword '${KEYWORD}' must be an object - received ${typeof dependencies}`
32
+ });
25
33
  }
26
- const schemas = Object.keys(dependencies);
27
- schemas.forEach((property) => {
34
+
35
+ const errors: ValidationAnnotation[] = [];
36
+ for (const property of Object.keys(dependencies)) {
28
37
  const schema = dependencies[property] as string[];
29
- if (isObject(schema) || typeof schema === "boolean") {
38
+ if (isJsonSchema(schema) || isBooleanSchema(schema)) {
30
39
  node.dependentSchemas = node.dependentSchemas ?? {};
31
40
  node.dependentSchemas[property] = node.compileSchema(
32
41
  schema,
33
- `${node.evaluationPath}/dependencies/${property}`,
34
- `${node.schemaLocation}/dependencies/${property}`
42
+ `${node.evaluationPath}/${KEYWORD}/${property}`,
43
+ `${node.schemaLocation}/${KEYWORD}/${property}`
35
44
  );
36
- } else {
45
+ } else if (isListOfStrings(schema)) {
37
46
  node.dependentRequired = node.dependentRequired ?? {};
38
47
  node.dependentRequired[property] = schema;
48
+ } else {
49
+ errors.push(
50
+ node.createError("schema-error", {
51
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
52
+ schema: node.schema,
53
+ value: dependencies,
54
+ message: `Keyword '${KEYWORD}[string]' must be JSON Schema or string[]`
55
+ })
56
+ );
39
57
  }
40
- });
58
+ }
59
+
60
+ return errors;
41
61
  }
42
62
 
43
63
  export function reduceDependencies({ node, data, key, pointer, path }: JsonSchemaReducerParams) {
@@ -66,7 +86,7 @@ export function reduceDependencies({ node, data, key, pointer, path }: JsonSchem
66
86
  required.push(...dependentRequired[propertyName]);
67
87
 
68
88
  // @dynamicId
69
- const localDynamicId = `dependencies/${propertyName}`;
89
+ const localDynamicId = `${KEYWORD}/${propertyName}`;
70
90
  dynamicId += `${dynamicId === "" ? "" : ","}${localDynamicId}`;
71
91
  });
72
92
  }
@@ -90,7 +110,7 @@ export function reduceDependencies({ node, data, key, pointer, path }: JsonSchem
90
110
  // but probably not how json-schema spec defines this behaviour (resolve only within sub-schema)
91
111
  const reducedDependency = { ...dependency, schema: { ...dependency.schema, required } }.reduceNode(data, {
92
112
  key,
93
- pointer: `${pointer}/dependencies/${propertyName}`,
113
+ pointer: `${pointer}/${KEYWORD}/${propertyName}`,
94
114
  path
95
115
  }).node as SchemaNode;
96
116
 
@@ -98,7 +118,7 @@ export function reduceDependencies({ node, data, key, pointer, path }: JsonSchem
98
118
 
99
119
  // @dynamicId
100
120
  const nestedDynamicId = reducedDependency.dynamicId?.replace(node.dynamicId, "") ?? "";
101
- const localDynamicId = nestedDynamicId === "" ? `dependencies/${propertyName}` : nestedDynamicId;
121
+ const localDynamicId = nestedDynamicId === "" ? `${KEYWORD}/${propertyName}` : nestedDynamicId;
102
122
  dynamicId += `${dynamicId === "" ? "" : ","}${localDynamicId}`;
103
123
  });
104
124
  }
@@ -113,7 +133,7 @@ export function reduceDependencies({ node, data, key, pointer, path }: JsonSchem
113
133
 
114
134
  required = workingNode.schema.required ? workingNode.schema.required.concat(...required) : required;
115
135
  required = required.filter((r: string, index: number, list: string[]) => list.indexOf(r) === index);
116
- workingNode = mergeNode(workingNode, workingNode, "dependencies") as SchemaNode;
136
+ workingNode = mergeNode(workingNode, workingNode, KEYWORD) as SchemaNode;
117
137
  return workingNode.compileSchema(
118
138
  { ...workingNode.schema, required },
119
139
  workingNode.evaluationPath,
@@ -1,20 +1,51 @@
1
- import { Keyword, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
1
+ import { Keyword, JsonSchemaValidatorParams, ValidationReturnType, ValidationAnnotation } from "../Keyword";
2
2
  import { JsonError, SchemaNode } from "../types";
3
+ import { isListOfStrings } from "../utils/isListOfStrings";
3
4
  import { isObject } from "../utils/isObject";
4
5
 
6
+ const KEYWORD = "dependentRequired";
7
+
5
8
  export const dependentRequiredKeyword: Keyword = {
6
- id: "dependentRequired",
7
- keyword: "dependentRequired",
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
8
11
  parse: parseDependentRequired,
9
- addValidate: (node) => isObject(node.schema.dependentRequired),
12
+ addValidate: (node) => node[KEYWORD] != null,
10
13
  validate: validateDependentRequired
11
14
  };
12
15
 
13
16
  export function parseDependentRequired(node: SchemaNode) {
14
- if (!isObject(node.schema.dependentRequired)) {
17
+ const { schema } = node;
18
+ if (schema[KEYWORD] == null) {
15
19
  return;
16
20
  }
17
- node.dependentRequired = (node.schema.dependentRequired as Record<string, string[]>) ?? {};
21
+ if (!isObject(schema[KEYWORD])) {
22
+ return node.createError("schema-error", {
23
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
24
+ schema,
25
+ value: schema[KEYWORD],
26
+ message: `Keyword '${KEYWORD}' must be an object - received '${typeof schema[KEYWORD]}'`
27
+ });
28
+ }
29
+
30
+ const errors: ValidationAnnotation[] = [];
31
+ node.dependentRequired = {};
32
+ for (const propertyName of Object.keys(schema[KEYWORD])) {
33
+ const list = schema[KEYWORD][propertyName];
34
+ if (isListOfStrings(list)) {
35
+ node.dependentRequired[propertyName] = list;
36
+ } else {
37
+ errors.push(
38
+ node.createError("schema-error", {
39
+ pointer: `${node.schemaLocation}/${KEYWORD}/${propertyName}`,
40
+ schema,
41
+ value: list,
42
+ message: `Keyword '${KEYWORD}[string]' must be a string[] - received '${typeof list}'`
43
+ })
44
+ );
45
+ }
46
+ }
47
+
48
+ return errors;
18
49
  }
19
50
 
20
51
  export function validateDependentRequired({
@@ -22,41 +53,28 @@ export function validateDependentRequired({
22
53
  data,
23
54
  pointer = "#"
24
55
  }: JsonSchemaValidatorParams): ValidationReturnType {
25
- if (!isObject(data)) {
56
+ const { dependentRequired } = node;
57
+ if (dependentRequired == null || !isObject(data)) {
26
58
  return undefined;
27
59
  }
28
- const { dependentRequired } = node;
29
60
  const errors: JsonError[] = [];
30
- if (dependentRequired) {
31
- Object.keys(data).forEach((property) => {
32
- const dependencies = dependentRequired[property];
33
- // @draft >= 6 boolean schema
34
- // @ts-expect-error boolean schema
35
- if (dependencies === true) {
36
- return;
37
- }
38
- // @ts-expect-error boolean schema
39
- if (dependencies === false) {
40
- // @ts-expect-error boolean schema
41
- errors.push(node.createError("missing-dependency-error", { pointer, schema, value: data }));
42
- return;
43
- }
44
- if (!Array.isArray(dependencies)) {
45
- return;
61
+ Object.keys(data).forEach((property) => {
62
+ const dependencies = dependentRequired[property];
63
+ if (!Array.isArray(dependencies)) {
64
+ return;
65
+ }
66
+ for (let i = 0, l = dependencies.length; i < l; i += 1) {
67
+ if (data[dependencies[i]] === undefined) {
68
+ errors.push(
69
+ node.createError("missing-dependency-error", {
70
+ missingProperty: dependencies[i],
71
+ pointer,
72
+ schema: node.schema,
73
+ value: data
74
+ })
75
+ );
46
76
  }
47
- for (let i = 0, l = dependencies.length; i < l; i += 1) {
48
- if (data[dependencies[i]] === undefined) {
49
- errors.push(
50
- node.createError("missing-dependency-error", {
51
- missingProperty: dependencies[i],
52
- pointer,
53
- schema: node.schema,
54
- value: data
55
- })
56
- );
57
- }
58
- }
59
- });
60
- }
77
+ }
78
+ });
61
79
  return errors;
62
80
  }
@@ -1,13 +1,15 @@
1
1
  import { mergeSchema } from "../utils/mergeSchema";
2
2
  import { isObject } from "../utils/isObject";
3
- import { isSchemaNode, SchemaNode, JsonSchema } from "../types";
3
+ import { isSchemaNode, SchemaNode, JsonSchema, isBooleanSchema } from "../types";
4
4
  import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
5
5
  import { validateNode } from "../validateNode";
6
6
  import sanitizeErrors from "../utils/sanitizeErrors";
7
7
 
8
+ const KEYWORD = "dependentSchemas";
9
+
8
10
  export const dependentSchemasKeyword: Keyword = {
9
- id: "dependentSchemas",
10
- keyword: "dependentSchemas",
11
+ id: KEYWORD,
12
+ keyword: KEYWORD,
11
13
  parse: parseDependentSchemas,
12
14
  addReduce: (node) => node.dependentSchemas != null,
13
15
  reduce: reduceDependentSchemas,
@@ -17,29 +19,51 @@ export const dependentSchemasKeyword: Keyword = {
17
19
 
18
20
  export function parseDependentSchemas(node: SchemaNode) {
19
21
  const { dependentSchemas } = node.schema;
20
- if (!isObject(dependentSchemas)) {
22
+ if (dependentSchemas == null) {
21
23
  return;
22
24
  }
25
+ if (!isObject(dependentSchemas)) {
26
+ return node.createError("schema-error", {
27
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
28
+ schema: node.schema,
29
+ value: dependentSchemas,
30
+ message: `Keyword '${KEYWORD}' must be an object - received '${typeof dependentSchemas}'`
31
+ });
32
+ }
23
33
 
24
- const schemas = Object.keys(dependentSchemas);
25
- if (schemas.length === 0) {
34
+ const dependentProperties = Object.keys(dependentSchemas);
35
+ if (dependentProperties.length === 0) {
26
36
  return;
27
37
  }
28
38
 
39
+ const errors: ValidationAnnotation[] = [];
29
40
  const parsedSchemas: Record<string, boolean | SchemaNode> = {};
30
- schemas.forEach((property) => {
41
+ for (const property of Object.keys(dependentSchemas)) {
31
42
  const schema = dependentSchemas[property];
32
43
  if (isObject(schema)) {
33
44
  parsedSchemas[property] = node.compileSchema(
34
45
  schema,
35
- `${node.evaluationPath}/dependentSchemas/${property}`,
36
- `${node.schemaLocation}/dependentSchemas/${property}`
46
+ `${node.evaluationPath}/${KEYWORD}/${property}`,
47
+ `${node.schemaLocation}/${KEYWORD}/${property}`
37
48
  );
38
- } else if (typeof schema === "boolean") {
49
+ if (parsedSchemas[property].schemaValidation) {
50
+ errors.push(...parsedSchemas[property].schemaValidation);
51
+ }
52
+ } else if (isBooleanSchema(schema)) {
39
53
  parsedSchemas[property] = schema;
54
+ } else {
55
+ errors.push(
56
+ node.createError("schema-error", {
57
+ pointer: `${node.schemaLocation}/${KEYWORD}/${property}`,
58
+ schema: node.schema,
59
+ value: schema,
60
+ message: `Keyword '${KEYWORD}[string]' must be a valid JSON Schema'`
61
+ })
62
+ );
40
63
  }
41
- });
64
+ }
42
65
  node.dependentSchemas = parsedSchemas;
66
+ return errors;
43
67
  }
44
68
 
45
69
  export function reduceDependentSchemas({ node, data }: JsonSchemaReducerParams) {
@@ -62,7 +86,7 @@ export function reduceDependentSchemas({ node, data }: JsonSchemaReducerParams)
62
86
  } else {
63
87
  mergedSchema.properties[propertyName] = dependentSchemas[propertyName];
64
88
  }
65
- dynamicId += `${added ? "," : ""}dependentSchemas/${propertyName}`;
89
+ dynamicId += `${added ? "," : ""}${KEYWORD}/${propertyName}`;
66
90
  added++;
67
91
  });
68
92
 
@@ -70,7 +94,7 @@ export function reduceDependentSchemas({ node, data }: JsonSchemaReducerParams)
70
94
  return node;
71
95
  }
72
96
 
73
- mergedSchema = mergeSchema(node.schema, mergedSchema, "dependentSchemas");
97
+ mergedSchema = mergeSchema(node.schema, mergedSchema, KEYWORD);
74
98
  return node.compileSchema(mergedSchema, node.evaluationPath, node.schemaLocation, `${dynamicId})`);
75
99
  }
76
100
 
@@ -1,18 +1,42 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { SchemaNode } from "../SchemaNode";
3
+
4
+ const KEYWORD = "deprecated";
2
5
 
3
6
  export const deprecatedKeyword: Keyword = {
4
- id: "deprecated",
5
- keyword: "deprecated",
6
- addValidate: ({ schema }) => schema.deprecated === true,
7
+ id: KEYWORD,
8
+ keyword: KEYWORD,
9
+ parse: parseDeprecated,
10
+ addValidate: (node) => node[KEYWORD] === true,
7
11
  validate: validateDeprecated
8
12
  };
9
13
 
14
+ function parseDeprecated(node: SchemaNode) {
15
+ const deprecated = node.schema[KEYWORD];
16
+ if (deprecated == null) {
17
+ return;
18
+ }
19
+ if (typeof deprecated !== "boolean") {
20
+ return node.createError("schema-error", {
21
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
22
+ schema: node.schema,
23
+ value: deprecated,
24
+ message: `Keyword '${KEYWORD}' must be a boolean - received '${typeof deprecated}'`
25
+ });
26
+ }
27
+ node[KEYWORD] = deprecated;
28
+ }
29
+
10
30
  function validateDeprecated({ node, data, pointer }: JsonSchemaValidatorParams) {
11
31
  return [
12
- node.createAnnotation("deprecated-warning", {
13
- pointer,
14
- schema: node.schema,
15
- value: data
16
- })
32
+ node.createAnnotation(
33
+ "deprecated-warning",
34
+ {
35
+ pointer,
36
+ schema: node.schema,
37
+ value: data
38
+ },
39
+ node.schema.deprecatedMessage
40
+ )
17
41
  ];
18
42
  }
@@ -1,30 +1,52 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { SchemaNode } from "../SchemaNode";
2
3
  import { getTypeOf } from "../utils/getTypeOf";
3
4
 
5
+ const KEYWORD = "enum";
6
+
4
7
  export const enumKeyword: Keyword = {
5
- id: "enum",
6
- keyword: "enum",
7
- addValidate: ({ schema }) => Array.isArray(schema.enum),
8
+ id: KEYWORD,
9
+ keyword: KEYWORD,
10
+ parse: parseEnum,
11
+ addValidate: (node) => node.enum != null,
8
12
  validate: validateEnum
9
13
  };
10
14
 
11
- function validateEnum({ node, data, pointer = "#" }: JsonSchemaValidatorParams) {
15
+ export function parseEnum(node: SchemaNode) {
12
16
  const { schema } = node;
17
+ if (schema[KEYWORD] == null) {
18
+ return;
19
+ }
20
+ if (!Array.isArray(schema[KEYWORD])) {
21
+ return node.createError("schema-error", {
22
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
23
+ schema,
24
+ value: schema[KEYWORD],
25
+ message: `Keyword '${KEYWORD}' must be an array - received '${typeof schema[KEYWORD]}'`
26
+ });
27
+ }
28
+ node.enum = schema[KEYWORD];
29
+ }
30
+
31
+ function validateEnum({ node, data, pointer = "#" }: JsonSchemaValidatorParams) {
32
+ if (node.enum == null) {
33
+ return;
34
+ }
13
35
  const type = getTypeOf(data);
14
36
  if (type === "object" || type === "array") {
15
37
  const valueStr = JSON.stringify(data);
16
- for (const e of schema.enum) {
38
+ for (const e of node.enum) {
17
39
  if (JSON.stringify(e) === valueStr) {
18
40
  return undefined;
19
41
  }
20
42
  }
21
- } else if (schema.enum.includes(data)) {
43
+ } else if (node.enum.includes(data)) {
22
44
  return undefined;
23
45
  }
24
46
  return node.createError("enum-error", {
25
47
  pointer,
26
- schema,
48
+ schema: node.schema,
27
49
  value: data,
28
- values: schema.enum
50
+ values: node.enum
29
51
  });
30
52
  }
@@ -1,12 +1,31 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { SchemaNode } from "../SchemaNode";
3
+
4
+ const KEYWORD = "exclusiveMaximum";
2
5
 
3
6
  export const exclusiveMaximumKeyword: Keyword = {
4
- id: "exclusiveMaximum",
5
- keyword: "exclusiveMaximum",
7
+ id: KEYWORD,
8
+ keyword: KEYWORD,
9
+ parse: parseExclusiveMaximum,
6
10
  addValidate: ({ schema }) => schema.exclusiveMaximum != null && !isNaN(parseInt(schema.exclusiveMaximum)),
7
11
  validate: validateExclusiveMaximum
8
12
  };
9
13
 
14
+ function parseExclusiveMaximum(node: SchemaNode) {
15
+ const max = node.schema[KEYWORD];
16
+ if (max == null) {
17
+ return;
18
+ }
19
+ if (typeof max !== "number") {
20
+ return node.createError("schema-error", {
21
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
22
+ schema: node.schema,
23
+ value: max,
24
+ message: `Keyword '${KEYWORD}' must be a number - received '${typeof max}'`
25
+ });
26
+ }
27
+ }
28
+
10
29
  function validateExclusiveMaximum({ node, data, pointer }: JsonSchemaValidatorParams) {
11
30
  if (typeof data !== "number") {
12
31
  return undefined;
@@ -1,12 +1,31 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { SchemaNode } from "../SchemaNode";
3
+
4
+ const KEYWORD = "exclusiveMinimum";
2
5
 
3
6
  export const exclusiveMinimumKeyword: Keyword = {
4
- id: "exclusiveMinimum",
5
- keyword: "exclusiveMinimum",
6
- addValidate: ({ schema }) => schema.exclusiveMinimum != null && !isNaN(parseInt(schema.exclusiveMinimum)),
7
+ id: KEYWORD,
8
+ keyword: KEYWORD,
9
+ parse: parseExclusiveMinimum,
10
+ addValidate: ({ schema }) => schema[KEYWORD] != null && !isNaN(parseInt(schema[KEYWORD])),
7
11
  validate: validateExclusiveMinimum
8
12
  };
9
13
 
14
+ function parseExclusiveMinimum(node: SchemaNode) {
15
+ const min = node.schema[KEYWORD];
16
+ if (min == null) {
17
+ return;
18
+ }
19
+ if (typeof min !== "number") {
20
+ return node.createError("schema-error", {
21
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
22
+ schema: node.schema,
23
+ value: min,
24
+ message: `Keyword '${KEYWORD}' must be a number - received '${typeof min}'`
25
+ });
26
+ }
27
+ }
28
+
10
29
  function validateExclusiveMinimum({ node, data, pointer }: JsonSchemaValidatorParams) {
11
30
  if (typeof data !== "number") {
12
31
  return undefined;