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
@@ -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: "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", {
@@ -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
- export const requiredKeyword: Keyword = {
6
- id: "required",
7
- keyword: "required",
8
- addValidate: ({ schema }) => Array.isArray(schema.required),
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 validateRequired({ node, data, pointer = "#" }: JsonSchemaValidatorParams) {
13
- const { schema } = node;
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 schema.required.map((property: string) => {
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
  }
@@ -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
- export const typeKeyword: Keyword = {
6
- id: "type",
7
- keyword: "type",
8
- addReduce: (node) => Array.isArray(node.schema.type),
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: ({ schema }) => schema.type != null,
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.schema.type);
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 schema = node.schema;
35
- const dataType = getJsonSchemaType(data, schema.type);
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: schema.type,
48
- schema,
84
+ expected: type,
85
+ schema: node.schema,
49
86
  pointer
50
87
  });
51
88
  }
@@ -1,31 +1,47 @@
1
- import { isObject } from "../utils/isObject";
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: "unevaluatedItems",
14
- keyword: "unevaluatedItems",
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
- if (!isObject(node.schema.unevaluatedItems)) {
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}/unevaluatedItems`,
27
- `${node.schemaLocation}/unevaluatedItems`
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) {