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.
Files changed (113) hide show
  1. package/.mocharc.js +1 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +120 -0
  4. package/bowtie/.editorconfig +21 -0
  5. package/bowtie/.prettierrc +6 -0
  6. package/bowtie/BOWTIE.md +54 -0
  7. package/bowtie/Dockerfile +6 -0
  8. package/bowtie/bowtie-api.ts +101 -0
  9. package/bowtie/bowtie.test.ts +76 -0
  10. package/bowtie/bowtie.ts +156 -0
  11. package/bowtie/package.json +11 -0
  12. package/bowtie/tsconfig.json +12 -0
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +39 -470
  15. package/dist/index.d.mts +39 -470
  16. package/dist/index.mjs +1 -1
  17. package/dist/jlib.js +2 -13
  18. package/dist/remotes/index.cjs +1 -0
  19. package/dist/remotes/index.d.cts +7 -0
  20. package/dist/remotes/index.d.mts +7 -0
  21. package/dist/remotes/index.mjs +1 -0
  22. package/dist/types-B2wwNWyo.d.cts +513 -0
  23. package/dist/types-BhTU1l2h.d.mts +513 -0
  24. package/index.ts +10 -4
  25. package/package.json +14 -8
  26. package/src/Keyword.ts +37 -12
  27. package/src/SchemaNode.ts +84 -16
  28. package/src/compileSchema.ts +56 -4
  29. package/src/draft04/keywords/$ref.ts +22 -14
  30. package/src/draft04/keywords/exclusiveMaximum.ts +14 -0
  31. package/src/draft04/keywords/exclusiveMinimum.ts +14 -0
  32. package/src/draft04/validateSchema.test.ts +20 -0
  33. package/src/draft06/keywords/$ref.ts +15 -5
  34. package/src/draft2019-09/keywords/$ref.test.ts +3 -1
  35. package/src/draft2019-09/keywords/$ref.ts +40 -16
  36. package/src/draft2019-09/keywords/additionalItems.ts +33 -10
  37. package/src/draft2019-09/keywords/items.ts +32 -10
  38. package/src/draft2019-09/keywords/unevaluatedItems.ts +19 -6
  39. package/src/draft2019-09/methods/getData.ts +1 -1
  40. package/src/draft2019-09/validateSchema.test.ts +28 -0
  41. package/src/errors/errors.ts +8 -1
  42. package/src/formats/formats.ts +35 -28
  43. package/src/formats/hyperjump.d.ts +172 -0
  44. package/src/keywords/$defs.ts +34 -8
  45. package/src/keywords/$ref.ts +59 -13
  46. package/src/keywords/additionalProperties.ts +19 -8
  47. package/src/keywords/allOf.ts +44 -18
  48. package/src/keywords/anyOf.ts +38 -19
  49. package/src/keywords/contains.ts +21 -9
  50. package/src/keywords/dependencies.ts +37 -17
  51. package/src/keywords/dependentRequired.ts +56 -38
  52. package/src/keywords/dependentSchemas.ts +37 -13
  53. package/src/keywords/deprecated.ts +32 -8
  54. package/src/keywords/enum.ts +30 -8
  55. package/src/keywords/exclusiveMaximum.ts +21 -2
  56. package/src/keywords/exclusiveMinimum.ts +22 -3
  57. package/src/keywords/format.ts +21 -2
  58. package/src/keywords/ifthenelse.ts +49 -5
  59. package/src/keywords/items.ts +27 -13
  60. package/src/keywords/maxItems.ts +22 -2
  61. package/src/keywords/maxLength.ts +30 -9
  62. package/src/keywords/maxProperties.ts +30 -9
  63. package/src/keywords/maximum.ts +28 -8
  64. package/src/keywords/minItems.ts +30 -9
  65. package/src/keywords/minLength.ts +30 -9
  66. package/src/keywords/minProperties.ts +26 -5
  67. package/src/keywords/minimum.ts +32 -13
  68. package/src/keywords/multipleOf.ts +33 -12
  69. package/src/keywords/not.ts +23 -10
  70. package/src/keywords/oneOf.ts +29 -9
  71. package/src/keywords/pattern.ts +35 -9
  72. package/src/keywords/properties.ts +34 -11
  73. package/src/keywords/propertyDependencies.test.ts +180 -0
  74. package/src/keywords/propertyDependencies.ts +173 -0
  75. package/src/keywords/propertyNames.ts +26 -14
  76. package/src/keywords/required.ts +31 -8
  77. package/src/keywords/type.ts +53 -16
  78. package/src/keywords/unevaluatedItems.ts +24 -8
  79. package/src/keywords/unevaluatedProperties.ts +24 -7
  80. package/src/keywords/uniqueItems.ts +23 -4
  81. package/src/mergeNode.ts +9 -4
  82. package/src/methods/getData.ts +1 -1
  83. package/src/settings.ts +2 -1
  84. package/src/types.ts +1 -1
  85. package/src/utils/isListOfStrings.ts +3 -0
  86. package/src/validate.test.ts +0 -2
  87. package/src/validateNode.ts +6 -3
  88. package/src/validateSchema.test.ts +312 -0
  89. package/tsconfig.json +11 -4
  90. package/tsconfig.test.json +9 -2
  91. package/tsdown.config.ts +1 -1
  92. package/Dockerfile +0 -6
  93. package/bowtie_jlib.js +0 -140
  94. package/remotes/draft04.json +0 -150
  95. package/remotes/draft06.json +0 -155
  96. package/remotes/draft07.json +0 -155
  97. package/remotes/draft2019-09.json +0 -42
  98. package/remotes/draft2019-09_meta_applicator.json +0 -53
  99. package/remotes/draft2019-09_meta_content.json +0 -14
  100. package/remotes/draft2019-09_meta_core.json +0 -54
  101. package/remotes/draft2019-09_meta_format.json +0 -11
  102. package/remotes/draft2019-09_meta_meta-data.json +0 -34
  103. package/remotes/draft2019-09_meta_validation.json +0 -95
  104. package/remotes/draft2020-12.json +0 -55
  105. package/remotes/draft2020-12_meta_applicator.json +0 -45
  106. package/remotes/draft2020-12_meta_content.json +0 -14
  107. package/remotes/draft2020-12_meta_core.json +0 -48
  108. package/remotes/draft2020-12_meta_format_annotation.json +0 -11
  109. package/remotes/draft2020-12_meta_format_assertion.json +0 -11
  110. package/remotes/draft2020-12_meta_meta_data.json +0 -34
  111. package/remotes/draft2020-12_meta_unevaluated.json +0 -12
  112. package/remotes/draft2020-12_meta_validation.json +0 -87
  113. package/remotes/index.ts +0 -48
@@ -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;
@@ -1,12 +1,31 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { SchemaNode } from "../SchemaNode";
3
+
4
+ const KEYWORD = "format";
2
5
 
3
6
  export const formatKeyword: Keyword = {
4
- id: "format",
5
- keyword: "format",
7
+ id: KEYWORD,
8
+ keyword: KEYWORD,
9
+ parse: parseFormat,
6
10
  addValidate: ({ schema }) => schema?.format != null,
7
11
  validate: validateFormat
8
12
  };
9
13
 
14
+ function parseFormat(node: SchemaNode) {
15
+ const format = node.schema[KEYWORD];
16
+ if (format == null) {
17
+ return;
18
+ }
19
+ if (typeof format !== "string") {
20
+ return node.createError("schema-error", {
21
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
22
+ schema: node.schema,
23
+ value: format,
24
+ message: `Keyword '${KEYWORD}' must be a string - received '${typeof format}'`
25
+ });
26
+ }
27
+ }
28
+
10
29
  function validateFormat(options: JsonSchemaValidatorParams) {
11
30
  const { node } = options;
12
31
  const formatValidator = node.context.formats[node.schema.format];
@@ -1,6 +1,6 @@
1
1
  import { mergeSchema } from "../utils/mergeSchema";
2
- import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams } from "../Keyword";
3
- import { SchemaNode } from "../types";
2
+ import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
3
+ import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
4
4
  import { validateNode } from "../validateNode";
5
5
 
6
6
  export const ifKeyword: Keyword = {
@@ -15,15 +15,59 @@ export const ifKeyword: Keyword = {
15
15
 
16
16
  export function parseIfThenElse(node: SchemaNode) {
17
17
  const { schema, evaluationPath } = node;
18
+ const errors: ValidationAnnotation[] = [];
18
19
  if (schema.if != null) {
19
- node.if = node.compileSchema(schema.if, `${evaluationPath}/if`);
20
+ if (isJsonSchema(schema.if) || isBooleanSchema(schema.if)) {
21
+ node.if = node.compileSchema(schema.if, `${evaluationPath}/if`);
22
+ if (node.if.schemaValidation) {
23
+ errors.push(...node.if.schemaValidation);
24
+ }
25
+ } else {
26
+ errors.push(
27
+ node.createError("schema-error", {
28
+ pointer: `${node.schemaLocation}/if`,
29
+ schema: node.schema,
30
+ value: schema.if,
31
+ message: `Keyword 'if' must be valid JSON Schema - received '${typeof schema.if}'`
32
+ })
33
+ );
34
+ }
20
35
  }
21
36
  if (schema.then != null) {
22
- node.then = node.compileSchema(schema.then, `${evaluationPath}/then`);
37
+ if (isJsonSchema(schema.then) || isBooleanSchema(schema.then)) {
38
+ node.then = node.compileSchema(schema.then, `${evaluationPath}/then`);
39
+ if (node.then.schemaValidation) {
40
+ errors.push(...node.then.schemaValidation);
41
+ }
42
+ } else {
43
+ errors.push(
44
+ node.createError("schema-error", {
45
+ pointer: `${node.schemaLocation}/then`,
46
+ schema: node.schema,
47
+ value: schema.then,
48
+ message: `Keyword 'then' must be valid JSON Schema - received '${typeof schema.then}'`
49
+ })
50
+ );
51
+ }
23
52
  }
24
53
  if (schema.else != null) {
25
- node.else = node.compileSchema(schema.else, `${evaluationPath}/else`);
54
+ if (isJsonSchema(schema.else) || isBooleanSchema(schema.else)) {
55
+ node.else = node.compileSchema(schema.else, `${evaluationPath}/else`);
56
+ if (node.else.schemaValidation) {
57
+ errors.push(...node.else.schemaValidation);
58
+ }
59
+ } else {
60
+ errors.push(
61
+ node.createError("schema-error", {
62
+ pointer: `${node.schemaLocation}/else`,
63
+ schema: node.schema,
64
+ value: schema.else,
65
+ message: `Keyword 'else' must be valid JSON Schema - received '${typeof schema.else}'`
66
+ })
67
+ );
68
+ }
26
69
  }
70
+ return errors;
27
71
  }
28
72
 
29
73
  function reduceIf({ node, data, pointer, path }: JsonSchemaReducerParams) {
@@ -1,15 +1,16 @@
1
1
  import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
2
- import { SchemaNode } from "../types";
3
- import { isObject } from "../utils/isObject";
2
+ import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
4
3
  import { validateNode } from "../validateNode";
5
4
 
5
+ const KEYWORD = "items";
6
+
6
7
  export const itemsKeyword: Keyword = {
7
- id: "items",
8
- keyword: "items",
8
+ id: KEYWORD,
9
+ keyword: KEYWORD,
9
10
  parse: parseItems,
10
- addResolve: (node) => node.items != null,
11
+ addResolve: (node) => node[KEYWORD] != null,
11
12
  resolve: itemsResolver,
12
- addValidate: ({ schema }) => schema.items != null,
13
+ addValidate: (node) => node[KEYWORD] != null,
13
14
  validate: validateItems
14
15
  };
15
16
 
@@ -23,14 +24,27 @@ function itemsResolver({ node, key }: JsonSchemaResolverParams) {
23
24
  }
24
25
 
25
26
  export function parseItems(node: SchemaNode) {
26
- const { schema, evaluationPath } = node;
27
- if (isObject(schema.items)) {
28
- const propertyNode = node.compileSchema(
29
- schema.items,
30
- `${evaluationPath}/items`,
31
- `${node.schemaLocation}/items`
27
+ const items = node.schema[KEYWORD];
28
+ if (items == null) {
29
+ return;
30
+ }
31
+ if (!(isJsonSchema(items) || isBooleanSchema(items))) {
32
+ return node.createError("schema-error", {
33
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
34
+ schema: node.schema,
35
+ value: items,
36
+ message: `Keyword '${KEYWORD}' must be a valid JSON Schema - received '${typeof items}'`
37
+ });
38
+ }
39
+
40
+ if (items !== true) {
41
+ // @todo remove skipping boolean schema
42
+ node[KEYWORD] = node.compileSchema(
43
+ items,
44
+ `${node.evaluationPath}/${KEYWORD}`,
45
+ `${node.schemaLocation}/${KEYWORD}`
32
46
  );
33
- node.items = propertyNode;
47
+ return node[KEYWORD].schemaValidation;
34
48
  }
35
49
  }
36
50
 
@@ -1,12 +1,32 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { isNumber, SchemaNode } from "../types";
3
+
4
+ const KEYWORD = "maxItems";
2
5
 
3
6
  export const maxItemsKeyword: Keyword = {
4
- id: "maxItems",
5
- keyword: "maxItems",
7
+ id: KEYWORD,
8
+ keyword: KEYWORD,
9
+ parse: parseMaxItems,
6
10
  addValidate: ({ schema }) => !isNaN(schema.maxItems),
7
11
  validate: validateMaxItems
8
12
  };
9
13
 
14
+ function parseMaxItems(node: SchemaNode) {
15
+ const max = node.schema[KEYWORD];
16
+ if (max == null) {
17
+ return;
18
+ }
19
+ if (!isNumber(max)) {
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
+ node[KEYWORD] = max;
28
+ }
29
+
10
30
  function validateMaxItems({ node, data, pointer }: JsonSchemaValidatorParams) {
11
31
  const { schema } = node;
12
32
  if (Array.isArray(data) && schema.maxItems < data.length) {
@@ -1,25 +1,46 @@
1
1
  import ucs2decode from "../utils/punycode.ucs2decode";
2
2
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
3
+ import { SchemaNode } from "../SchemaNode";
4
+ import { isNumber } from "../types";
3
5
 
4
- export const maxLengthKeyword: Keyword = {
5
- id: "maxLength",
6
- keyword: "maxLength",
7
- addValidate: ({ schema }) => !isNaN(schema.maxLength),
6
+ const KEYWORD = "maxLength";
7
+
8
+ export const maxLengthKeyword: Keyword<"maxLength"> = {
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
11
+ parse: parseMaxLength,
12
+ addValidate: (node) => node[KEYWORD] != null,
8
13
  validate: validateMaxLength
9
14
  };
10
15
 
11
- function validateMaxLength({ node, data, pointer = "#" }: JsonSchemaValidatorParams) {
16
+ function parseMaxLength(node: SchemaNode) {
17
+ const max = node.schema[KEYWORD];
18
+ if (max == null) {
19
+ return;
20
+ }
21
+ if (!isNumber(max)) {
22
+ return node.createError("schema-error", {
23
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
24
+ schema: node.schema,
25
+ value: max,
26
+ message: `Keyword '${KEYWORD}' must be a number - received '${typeof max}'`
27
+ });
28
+ }
29
+ node[KEYWORD] = max;
30
+ }
31
+
32
+ function validateMaxLength({ node, data, pointer = "#" }: JsonSchemaValidatorParams<"maxLength">) {
12
33
  if (typeof data !== "string") {
13
34
  return;
14
35
  }
15
- const { schema } = node;
36
+ const maxLength = node[KEYWORD];
16
37
  const length = ucs2decode(data).length;
17
- if (schema.maxLength < length) {
38
+ if (maxLength < length) {
18
39
  return node.createError("max-length-error", {
19
- maxLength: schema.maxLength,
40
+ maxLength: maxLength,
20
41
  length,
21
42
  pointer,
22
- schema,
43
+ schema: node.schema,
23
44
  value: data
24
45
  });
25
46
  }