json-schema-library 11.1.0 → 11.3.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 (82) hide show
  1. package/.mocharc.js +1 -0
  2. package/CHANGELOG.md +11 -0
  3. package/README.md +21 -13
  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-jlib.ts +150 -0
  10. package/bowtie/bowtie.test.ts +267 -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 +66 -503
  15. package/dist/index.d.mts +66 -503
  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 +0 -3
  25. package/package.json +14 -8
  26. package/src/Draft.ts +1 -1
  27. package/src/Keyword.ts +2 -3
  28. package/src/SchemaNode.ts +9 -0
  29. package/src/compileSchema.test.ts +52 -0
  30. package/src/compileSchema.ts +53 -3
  31. package/src/draft04/keywords/$ref.ts +22 -14
  32. package/src/draft04/keywords/exclusiveMaximum.ts +14 -0
  33. package/src/draft04/keywords/exclusiveMinimum.ts +14 -0
  34. package/src/draft04/validateSchema.test.ts +20 -0
  35. package/src/draft04.ts +2 -2
  36. package/src/draft06/keywords/$ref.ts +15 -5
  37. package/src/draft06.ts +2 -2
  38. package/src/draft07.ts +2 -2
  39. package/src/draft2019-09/keywords/$ref.test.ts +3 -1
  40. package/src/draft2019-09/keywords/$ref.ts +44 -30
  41. package/src/draft2019-09/keywords/additionalItems.ts +33 -10
  42. package/src/draft2019-09/keywords/items.ts +32 -10
  43. package/src/draft2019-09/keywords/unevaluatedItems.ts +19 -6
  44. package/src/draft2019-09/methods/getData.ts +1 -1
  45. package/src/draft2019-09/validateSchema.test.ts +28 -0
  46. package/src/draft2019.ts +1 -1
  47. package/src/draft2020.ts +1 -1
  48. package/src/errors/errors.ts +4 -0
  49. package/src/formats/formats.ts +35 -28
  50. package/src/formats/hyperjump.d.ts +172 -0
  51. package/src/keywords/$ref.ts +50 -17
  52. package/src/keywords/oneOf.test.ts +3 -3
  53. package/src/keywords/properties.ts +1 -1
  54. package/src/keywords/propertyDependencies.ts +1 -1
  55. package/src/methods/getData.ts +1 -1
  56. package/src/settings.ts +27 -1
  57. package/src/validateNode.ts +4 -1
  58. package/tsconfig.json +11 -4
  59. package/tsconfig.test.json +9 -2
  60. package/tsdown.config.ts +1 -1
  61. package/Dockerfile +0 -6
  62. package/bowtie_jlib.js +0 -140
  63. package/remotes/draft04.json +0 -150
  64. package/remotes/draft06.json +0 -155
  65. package/remotes/draft07.json +0 -155
  66. package/remotes/draft2019-09.json +0 -42
  67. package/remotes/draft2019-09_meta_applicator.json +0 -53
  68. package/remotes/draft2019-09_meta_content.json +0 -14
  69. package/remotes/draft2019-09_meta_core.json +0 -54
  70. package/remotes/draft2019-09_meta_format.json +0 -11
  71. package/remotes/draft2019-09_meta_meta-data.json +0 -34
  72. package/remotes/draft2019-09_meta_validation.json +0 -95
  73. package/remotes/draft2020-12.json +0 -55
  74. package/remotes/draft2020-12_meta_applicator.json +0 -45
  75. package/remotes/draft2020-12_meta_content.json +0 -14
  76. package/remotes/draft2020-12_meta_core.json +0 -48
  77. package/remotes/draft2020-12_meta_format_annotation.json +0 -11
  78. package/remotes/draft2020-12_meta_format_assertion.json +0 -11
  79. package/remotes/draft2020-12_meta_meta_data.json +0 -34
  80. package/remotes/draft2020-12_meta_unevaluated.json +0 -12
  81. package/remotes/draft2020-12_meta_validation.json +0 -87
  82. package/remotes/index.ts +0 -48
@@ -3,11 +3,13 @@ import { SchemaNode } from "../../types";
3
3
  import { isObject } from "../../utils/isObject";
4
4
  import { validateNode } from "../../validateNode";
5
5
 
6
+ const KEYWORD = "items";
7
+
6
8
  export const itemsKeyword: Keyword = {
7
- id: "items",
8
- keyword: "items",
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
9
11
  parse: parseItems,
10
- addResolve: (node) => (node.prefixItems || node.items) != null,
12
+ addResolve: (node) => (node.prefixItems || node[KEYWORD]) != null,
11
13
  resolve: itemsResolver,
12
14
  addValidate: ({ schema }) => schema.items != null,
13
15
  validate: validateItems
@@ -24,18 +26,38 @@ function itemsResolver({ node, key }: JsonSchemaResolverParams) {
24
26
 
25
27
  export function parseItems(node: SchemaNode) {
26
28
  const { schema, evaluationPath } = node;
27
- if (isObject(schema.items)) {
29
+ const items = schema[KEYWORD];
30
+ if (items == null || typeof items === "boolean") {
31
+ return;
32
+ }
33
+
34
+ if (isObject(items)) {
28
35
  const propertyNode = node.compileSchema(
29
- schema.items,
30
- `${evaluationPath}/items`,
31
- `${node.schemaLocation}/items`
36
+ items,
37
+ `${evaluationPath}/${KEYWORD}`,
38
+ `${node.schemaLocation}/${KEYWORD}`
32
39
  );
33
40
  node.items = propertyNode;
34
- } else if (Array.isArray(schema.items)) {
35
- node.prefixItems = schema.items.map((itemSchema, index) =>
36
- node.compileSchema(itemSchema, `${evaluationPath}/items/${index}`, `${node.schemaLocation}/items/${index}`)
41
+ return;
42
+ }
43
+
44
+ if (Array.isArray(items)) {
45
+ node.prefixItems = items.map((itemSchema, index) =>
46
+ node.compileSchema(
47
+ itemSchema,
48
+ `${evaluationPath}/${KEYWORD}/${index}`,
49
+ `${node.schemaLocation}/${KEYWORD}/${index}`
50
+ )
37
51
  );
52
+ return;
38
53
  }
54
+
55
+ return node.createError("schema-error", {
56
+ pointer: evaluationPath,
57
+ schema,
58
+ value: undefined,
59
+ message: `Keyword '${KEYWORD}' must be an object or array - received '${typeof items}'`
60
+ });
39
61
  }
40
62
 
41
63
  function validateItems({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
@@ -3,27 +3,40 @@ import { SchemaNode } from "../../types";
3
3
  import { Keyword, JsonSchemaValidatorParams, ValidationReturnType } from "../../Keyword";
4
4
  import { validateNode } from "../../validateNode";
5
5
 
6
+ const KEYWORD = "unevaluatedItems";
7
+
6
8
  /**
7
9
  * @draft >= 2019-09
8
10
  * Similar to additionalItems, but can "see" into subschemas and across references
9
11
  * https://json-schema.org/draft/2019-09/json-schema-core#rfc.section.9.3.1.3
10
12
  */
11
13
  export const unevaluatedItemsKeyword: Keyword = {
12
- id: "unevaluatedItems",
13
- keyword: "unevaluatedItems",
14
+ id: KEYWORD,
15
+ keyword: KEYWORD,
14
16
  parse: parseUnevaluatedItems,
15
- addValidate: ({ schema }) => schema.unevaluatedItems != null,
17
+ addValidate: ({ schema }) => schema[KEYWORD] != null,
16
18
  validate: validateUnevaluatedItems
17
19
  };
18
20
 
19
21
  export function parseUnevaluatedItems(node: SchemaNode) {
20
- if (!isObject(node.schema.unevaluatedItems)) {
22
+ const { unevaluatedItems } = node.schema;
23
+ if (unevaluatedItems == null || typeof unevaluatedItems === "boolean") {
21
24
  return;
22
25
  }
26
+
27
+ if (!isObject(unevaluatedItems)) {
28
+ return node.createError("schema-error", {
29
+ pointer: node.evaluationPath,
30
+ schema: node.schema,
31
+ value: undefined,
32
+ message: `Keyword '${KEYWORD}' must be an object - received '${typeof unevaluatedItems}'`
33
+ });
34
+ }
35
+
23
36
  node.unevaluatedItems = node.compileSchema(
24
37
  node.schema.unevaluatedItems,
25
- `${node.evaluationPath}/unevaluatedItems`,
26
- `${node.schemaLocation}/unevaluatedItems`
38
+ `${node.evaluationPath}/${KEYWORD}`,
39
+ `${node.schemaLocation}/${KEYWORD}`
27
40
  );
28
41
  }
29
42
 
@@ -160,7 +160,7 @@ export function getData(node: SchemaNode, data?: unknown, opts?: TemplateOptions
160
160
  return defaultData;
161
161
  }
162
162
 
163
- if (resolvedNode && resolvedNode !== currentNode) {
163
+ if (isSchemaNode(resolvedNode)) {
164
164
  defaultData = resolvedNode.getData(defaultData, opts) ?? defaultData;
165
165
  currentNode = resolvedNode;
166
166
  }
@@ -0,0 +1,28 @@
1
+ import { compileSchema as _compileSchema, type CompileOptions } from "../compileSchema";
2
+ import { strict as assert } from "assert";
3
+ import { draft2019 } from "../draft2019";
4
+ import { JsonSchema } from "../types";
5
+
6
+ const drafts = [draft2019];
7
+ function compileSchema(schema: JsonSchema, options: CompileOptions = {}) {
8
+ return _compileSchema(schema, { ...options, drafts });
9
+ }
10
+
11
+ describe("validateSchema (2019-09)", () => {
12
+ it("should error if `additionalItems` is of an invalid type", () => {
13
+ const { schemaErrors } = compileSchema({ additionalItems: [] });
14
+ assert.equal(schemaErrors?.length, 1);
15
+ });
16
+ it("should create annotation for `additionalItems` where items-array is missing", () => {
17
+ const { schemaAnnotations } = compileSchema({ additionalItems: true });
18
+ assert.equal(schemaAnnotations?.length, 1);
19
+ });
20
+ it("should error if `items` is of an invalid type", () => {
21
+ const { schemaErrors } = compileSchema({ items: 999 });
22
+ assert.equal(schemaErrors?.length, 1);
23
+ });
24
+ it("should error if `unevaluatedItems` is not an object or boolean", () => {
25
+ const { schemaErrors } = compileSchema({ unevaluatedItems: [] });
26
+ assert.equal(schemaErrors?.length, 1);
27
+ });
28
+ });
package/src/draft2019.ts CHANGED
@@ -60,7 +60,7 @@ import { uniqueItemsKeyword } from "./keywords/uniqueItems";
60
60
  */
61
61
  export const draft2019 = sanitizeKeywords({
62
62
  version: "draft-2019-09",
63
- $schemaRegEx: "draft[/-]2019-09",
63
+ $schemaRegEx: "draft[/-]2019-?(09)?",
64
64
  $schema: "https://json-schema.org/draft/2019-09/schema",
65
65
  errors,
66
66
  formats,
package/src/draft2020.ts CHANGED
@@ -70,7 +70,7 @@ import { uniqueItemsKeyword } from "./keywords/uniqueItems";
70
70
  */
71
71
  export const draft2020 = sanitizeKeywords({
72
72
  version: "draft-2020-12",
73
- $schemaRegEx: "draft[/-]2020-12",
73
+ $schemaRegEx: "draft[/-]2020-?(12)?",
74
74
  $schema: "https://json-schema.org/draft/2020-12/schema",
75
75
  errors,
76
76
  formats,
@@ -19,12 +19,15 @@ export const errors = {
19
19
  "format-duration-error": "Value `{{value}}` at `{{pointer}}` is not a valid duration",
20
20
  "format-email-error": "Value `{{value}}` at `{{pointer}}` is not a valid email",
21
21
  "format-hostname-error": "Value `{{value}}` at `{{pointer}}` is not a valid hostname",
22
+ "format-idn-hostname-error": "Value `{{value}}` at `{{pointer}}` is not a valid idn hostname",
22
23
  "format-ipv4-error": "Value `{{value}}` at `{{pointer}}` is not a valid IPv4 address",
23
24
  "format-ipv4-leading-zero-error":
24
25
  "IPv4 addresses starting with zero are invalid, since they are interpreted as octals",
25
26
  "format-ipv6-error": "Value `{{value}}` at `{{pointer}}` is not a valid IPv6 address",
26
27
  "format-ipv6-leading-zero-error":
27
28
  "IPv6 addresses starting with zero are invalid, since they are interpreted as octals",
29
+ "format-iri-error": "Value `{{value}}` at `{{pointer}}` is not a valid iri",
30
+ "format-iri-reference-error": "Value `{{value}}` at `{{pointer}}` is not a valid iri-reference",
28
31
  "format-json-pointer-error": "Value `{{value}}` at `{{pointer}}` is not a valid json-pointer",
29
32
  "format-regex-error": "Value `{{value}}` at `{{pointer}}` is not a valid regular expression",
30
33
  "format-time-error": "Value `{{value}}` at `{{pointer}}` is not a valid time",
@@ -62,6 +65,7 @@ export const errors = {
62
65
  "pattern-error": "Value in `{{pointer}}` should match `{{description}}`, but received `{{received}}`",
63
66
  "pattern-properties-error":
64
67
  "Property `{{key}}` does not match any patterns in `{{pointer}}`. Valid patterns are: {{patterns}}",
68
+ "ref-error": "Could not resolve $ref '{{ref}}' from '{{pointer}}'",
65
69
  "required-property-error": "The required property `{{key}}` is missing at `{{pointer}}`",
66
70
  /** return schema-warning with createSchemaWarning:true when a valid, but undefined property was found */
67
71
  "schema-warning": "Failed retrieving a schema from '{{pointer}}' to key '{{key}}'",
@@ -1,16 +1,14 @@
1
1
  /* eslint-disable no-control-regex */
2
- import { getTypeOf } from "../utils/getTypeOf";
2
+ import { isAsciiIdn, isUri, isIdnEmail, isIri, isIriReference, isIdn } from "@hyperjump/json-schema-formats";
3
3
  import validUrl from "valid-url";
4
+ import { getTypeOf } from "../utils/getTypeOf";
4
5
  import { JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
5
- import { parse as parseIdnEmail } from "smtp-address-parser";
6
6
  import settings from "../settings";
7
7
 
8
8
  const { REGEX_FLAGS } = settings;
9
9
  const isValidIPV4 = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
10
10
  const isValidIPV6 =
11
11
  /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i;
12
- const isValidHostname =
13
- /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/;
14
12
  const matchDate = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
15
13
  const matchTime =
16
14
  /^(?<time>(?:([0-1]\d|2[0-3]):[0-5]\d:(?<second>[0-5]\d|60)))(?:\.\d+)?(?<offset>(?:z|[+-]([0-1]\d|2[0-3])(?::?[0-5]\d)?))$/i;
@@ -129,33 +127,30 @@ export const formats: Record<string, (options: JsonSchemaValidatorParams) => Val
129
127
  return undefined;
130
128
  },
131
129
 
132
- /**
133
- * @draft 7
134
- * [RFC6531] https://json-schema.org/draft-07/json-schema-validation.html#RFC6531
135
- */
136
- "idn-email": ({ node, pointer, data }) => {
130
+ hostname: ({ node, pointer, data }) => {
137
131
  const { schema } = node;
138
- if (typeof data !== "string" || data === "") {
132
+ if (typeof data !== "string" || isAsciiIdn(data)) {
139
133
  return undefined;
140
134
  }
141
- try {
142
- parseIdnEmail(data);
143
- return undefined;
144
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
145
- } catch (e) {
146
- return node.createError("format-email-error", { value: data, pointer, schema });
147
- }
135
+ return node.createError("format-hostname-error", { value: data, pointer, schema });
148
136
  },
149
137
 
150
- hostname: ({ node, pointer, data }) => {
151
- const { schema } = node;
152
- if (typeof data !== "string") {
138
+ "idn-hostname": ({ node, pointer, data }) => {
139
+ if (typeof data !== "string" || isIdn(data)) {
153
140
  return undefined;
154
141
  }
155
- if (isValidHostname.test(data)) {
142
+ return node.createError("format-idn-hostname-error", { value: data, pointer, schema: node.schema });
143
+ },
144
+
145
+ /**
146
+ * @draft 7
147
+ * [RFC6531] https://json-schema.org/draft-07/json-schema-validation.html#RFC6531
148
+ */
149
+ "idn-email": ({ node, pointer, data }) => {
150
+ if (typeof data !== "string" || data === "" || isIdnEmail(data)) {
156
151
  return undefined;
157
152
  }
158
- return node.createError("format-hostname-error", { value: data, pointer, schema });
153
+ return node.createError("format-email-error", { value: data, pointer, schema: node.schema });
159
154
  },
160
155
 
161
156
  ipv4: ({ node, pointer, data }) => {
@@ -188,6 +183,22 @@ export const formats: Record<string, (options: JsonSchemaValidatorParams) => Val
188
183
  return node.createError("format-ipv6-error", { value: data, pointer, schema });
189
184
  },
190
185
 
186
+ iri: ({ node, pointer, data }) => {
187
+ const { schema } = node;
188
+ if (typeof data !== "string" || data === "" || isIri(data)) {
189
+ return undefined;
190
+ }
191
+ return node.createError("format-iri-error", { value: data, pointer, schema });
192
+ },
193
+
194
+ "iri-reference": ({ node, pointer, data }) => {
195
+ const { schema } = node;
196
+ if (typeof data !== "string" || data === "" || isIriReference(data)) {
197
+ return undefined;
198
+ }
199
+ return node.createError("format-iri-reference-error", { value: data, pointer, schema });
200
+ },
201
+
191
202
  "json-pointer": ({ node, pointer, data }) => {
192
203
  const { schema } = node;
193
204
  if (typeof data !== "string" || data === "") {
@@ -281,14 +292,10 @@ export const formats: Record<string, (options: JsonSchemaValidatorParams) => Val
281
292
  },
282
293
 
283
294
  uri: ({ node, pointer, data }) => {
284
- const { schema } = node;
285
- if (typeof data !== "string" || data === "") {
286
- return undefined;
287
- }
288
- if (validUrl.isUri(data)) {
295
+ if (typeof data !== "string" || data === "" || isUri(data)) {
289
296
  return undefined;
290
297
  }
291
- return node.createError("format-uri-error", { value: data, pointer, schema });
298
+ return node.createError("format-uri-error", { value: data, pointer, schema: node.schema });
292
299
  },
293
300
 
294
301
  "uri-reference": ({ node, pointer, data }) => {
@@ -0,0 +1,172 @@
1
+ declare module "@hyperjump/json-schema-formats" {
2
+ /**
3
+ * The 'date' format. Validates that a string represents a date according to
4
+ * [RFC 3339, section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6).
5
+ *
6
+ * @see [JSON Schema Core, section 7.3.1](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.1)
7
+ */
8
+ export const isDate: (date: string) => boolean;
9
+
10
+ /**
11
+ * The 'time' format. Validates that a string represents a time according to
12
+ * [RFC 3339, section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6).
13
+ *
14
+ * **NOTE**: Leap seconds are only allowed on specific dates. Since there is no date
15
+ * in this context, leap seconds are never allowed.
16
+ *
17
+ * @see [JSON Schema Core, section 7.3.1](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.1)
18
+ */
19
+ export const isTime: (time: string) => boolean;
20
+
21
+ /**
22
+ * The 'date-time' format. Validates that a string represents a date-time
23
+ * according to [RFC 3339, section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6).
24
+ *
25
+ * @see [JSON Schema Core, section 7.3.1](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.1)
26
+ */
27
+ export const isDateTime: (dateTime: string) => boolean;
28
+
29
+ /**
30
+ * The 'duration' format. Validates that a string represents a duration
31
+ * according to [RFC 3339, Appendix A](https://www.rfc-editor.org/rfc/rfc3339.html#appendix-A).
32
+ *
33
+ * @see [JSON Schema Core, section 7.3.1](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.1)
34
+ */
35
+ export const isDuration: (duration: string) => boolean;
36
+
37
+ /**
38
+ * The 'email' format. Validates that a string represents an email as defined by
39
+ * the "Mailbox" ABNF rule in [RFC 5321, section 4.1.2](https://www.rfc-editor.org/rfc/rfc5321.html#section-4.1.2).
40
+ *
41
+ * @see [JSON Schema Core, section 7.3.2](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.2)
42
+ */
43
+ export const isEmail: (email: string) => boolean;
44
+
45
+ /**
46
+ * The 'idn-email' format. Validates that a string represents an email as
47
+ * defined by the "Mailbox" ABNF rule in [RFC 6531, section 3.3](https://www.rfc-editor.org/rfc/rfc6531.html#section-3.3).
48
+ *
49
+ * @see [JSON Schema Core, section 7.3.2](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.2)
50
+ */
51
+ export const isIdnEmail: (email: string) => boolean;
52
+
53
+ /**
54
+ * The 'hostname' format in draft-04 - draft-06. Validates that a string
55
+ * represents a hostname as defined by [RFC 1123, section 2.1](https://www.rfc-editor.org/rfc/rfc1123.html#section-2.1).
56
+ *
57
+ * **NOTE**: The 'hostname' format changed in draft-07. Use {@link isAsciiIdn} for
58
+ * draft-07 and later.
59
+ *
60
+ * @see [JSON Schema Core, section 7.3.3](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.3)
61
+ */
62
+ export const isHostname: (hostname: string) => boolean;
63
+
64
+ /**
65
+ * The 'hostname' format since draft-07. Validates that a string represents an
66
+ * IDNA2008 internationalized domain name consiting of only A-labels and NR-LDH
67
+ * labels as defined by [RFC 5890, section 2.3.2.1](https://www.rfc-editor.org/rfc/rfc5890.html#section-2.3.2.3).
68
+ *
69
+ * **NOTE**: The 'hostname' format changed in draft-07. Use {@link isHostname}
70
+ * for draft-06 and earlier.
71
+ *
72
+ * @see [JSON Schema Core, section 7.3.3](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.3)
73
+ */
74
+ export const isAsciiIdn: (hostname: string) => boolean;
75
+
76
+ /**
77
+ * The 'idn-hostname' format. Validates that a string represents an IDNA2008
78
+ * internationalized domain name as defined by [RFC 5890, section 2.3.2.1](https://www.rfc-editor.org/rfc/rfc5890.html#section-2.3.2.1).
79
+ *
80
+ * @see [JSON Schema Core, section 7.3.3](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.3)
81
+ */
82
+ export const isIdn: (hostname: string) => boolean;
83
+
84
+ /**
85
+ * The 'ipv4' format. Validates that a string represents an IPv4 address
86
+ * according to the "dotted-quad" ABNF syntax as defined in
87
+ * [RFC 2673, section 3.2](https://www.rfc-editor.org/rfc/rfc2673.html#section-3.2).
88
+ *
89
+ * @see [JSON Schema Core, section 7.3.4](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.4)
90
+ */
91
+ export const isIPv4: (ip: string) => boolean;
92
+
93
+ /**
94
+ * The 'ipv6' format. Validates that a string represents an IPv6 address as
95
+ * defined in [RFC 4291, section 2.2](https://www.rfc-editor.org/rfc/rfc4291.html#section-2.2).
96
+ *
97
+ * @see [JSON Schema Core, section 7.3.4](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.4)
98
+ */
99
+ export const isIPv6: (ip: string) => boolean;
100
+
101
+ /**
102
+ * The 'uri' format. Validates that a string represents a URI as defined by [RFC
103
+ * 3986](https://www.rfc-editor.org/rfc/rfc3986.html).
104
+ *
105
+ * @see [JSON Schema Core, section 7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.5)
106
+ */
107
+ export const isUri: (uri: string) => boolean;
108
+
109
+ /**
110
+ * The 'uri-reference' format. Validates that a string represents a URI
111
+ * Reference as defined by [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986.html).
112
+ *
113
+ * @see [JSON Schema Core, section 7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.5)
114
+ */
115
+ export const isUriReference: (uri: string) => boolean;
116
+
117
+ /**
118
+ * The 'iri' format. Validates that a string represents an IRI as defined by
119
+ * [RFC 3987](https://www.rfc-editor.org/rfc/rfc3987.html).
120
+ *
121
+ * @see [JSON Schema Core, section 7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.5)
122
+ */
123
+ export const isIri: (iri: string) => boolean;
124
+
125
+ /**
126
+ * The 'iri-reference' format. Validates that a string represents an IRI
127
+ * Reference as defined by [RFC 3987](https://www.rfc-editor.org/rfc/rfc3987.html).
128
+ *
129
+ * @see [JSON Schema Core, section 7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.5)
130
+ */
131
+ export const isIriReference: (iri: string) => boolean;
132
+
133
+ /**
134
+ * The 'uuid' format. Validates that a string represents a UUID address as
135
+ * defined by [RFC 4122](https://www.rfc-editor.org/rfc/rfc4122.html).
136
+ *
137
+ * @see [JSON Schema Core, section 7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.5)
138
+ */
139
+ export const isUuid: (uuid: string) => boolean;
140
+
141
+ /**
142
+ * The 'uri-template' format. Validates that a string represents a URI Template
143
+ * as defined by [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570.html).
144
+ *
145
+ * @see [JSON Schema Core, section 7.3.6](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.6)
146
+ */
147
+ export const isUriTemplate: (uriTemplate: string) => boolean;
148
+
149
+ /**
150
+ * The 'json-pointer' format. Validates that a string represents a JSON Pointer
151
+ * as defined by [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901.html).
152
+ *
153
+ * @see [JSON Schema Core, section 7.3.7](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.7)
154
+ */
155
+ export const isJsonPointer: (pointer: string) => boolean;
156
+
157
+ /**
158
+ * The 'relative-json-pointer' format. Validates that a string represents an IRI
159
+ * Reference as defined by [draft-bhutton-relative-json-pointer-00](https://datatracker.ietf.org/doc/html/draft-bhutton-relative-json-pointer-00).
160
+ *
161
+ * @see [JSON Schema Core, section 7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.5)
162
+ */
163
+ export const isRelativeJsonPointer: (pointer: string) => boolean;
164
+
165
+ /**
166
+ * The 'regex' format. Validates that a string represents a regular expression
167
+ * as defined by [ECMA-262](https://262.ecma-international.org/5.1/).
168
+ *
169
+ * @see [JSON Schema Core, section 7.3.8](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-7.3.8)
170
+ */
171
+ export const isRegex: (regex: string) => boolean;
172
+ }
@@ -1,4 +1,4 @@
1
- import { SchemaNode } from "../types";
1
+ import { isJsonError, isSchemaNode, JsonError, SchemaNode } from "../types";
2
2
  import { Keyword, JsonSchemaValidatorParams, ValidationPath, JsonSchemaReducerParams } from "../Keyword";
3
3
  import { resolveUri } from "../utils/resolveUri";
4
4
  import splitRef from "../utils/splitRef";
@@ -8,6 +8,7 @@ import { validateNode } from "../validateNode";
8
8
  import { get, split } from "@sagold/json-pointer";
9
9
  import { mergeNode } from "../mergeNode";
10
10
  import { pick } from "../utils/pick";
11
+ import settings from "src/settings";
11
12
 
12
13
  export const $refKeyword: Keyword = {
13
14
  id: "$ref",
@@ -93,7 +94,12 @@ export function reduceRef({ node, data, key, pointer, path }: JsonSchemaReducerP
93
94
 
94
95
  const resolvedNode = node.resolveRef({ pointer, path });
95
96
  if (resolvedNode == null) {
96
- return;
97
+ return node.createError("ref-error", {
98
+ ref: node.schema.$ref ?? node.schema.$dynamicRef,
99
+ pointer,
100
+ schema: node.schema,
101
+ value: data
102
+ });
97
103
  }
98
104
 
99
105
  if (resolvedNode.schemaLocation === node.schemaLocation) {
@@ -107,6 +113,9 @@ export function reduceRef({ node, data, key, pointer, path }: JsonSchemaReducerP
107
113
  export function resolveRef(this: SchemaNode, { pointer, path = [] }: { pointer?: string; path?: ValidationPath } = {}) {
108
114
  if (this.schema.$dynamicRef) {
109
115
  const nextNode = resolveRecursiveRef(this, path);
116
+ if (isJsonError(nextNode)) {
117
+ return nextNode;
118
+ }
110
119
  path.push({ pointer: pointer!, node: nextNode! });
111
120
  return nextNode;
112
121
  }
@@ -116,7 +125,7 @@ export function resolveRef(this: SchemaNode, { pointer, path = [] }: { pointer?:
116
125
  }
117
126
 
118
127
  const resolvedNode = getRef(this);
119
- if (resolvedNode != null) {
128
+ if (isSchemaNode(resolvedNode)) {
120
129
  path.push({ pointer: pointer!, node: resolvedNode });
121
130
  }
122
131
 
@@ -129,10 +138,16 @@ function validateRef({ node, data, pointer = "#", path }: JsonSchemaValidatorPar
129
138
  // recursively resolveRef and validate
130
139
  return validateNode(nextNode, data, pointer, path);
131
140
  }
141
+ return node.createError("ref-error", {
142
+ ref: node.schema.$ref ?? node.schema.$dynamicRef,
143
+ pointer,
144
+ schema: node.schema,
145
+ value: data
146
+ });
132
147
  }
133
148
 
134
149
  // 1. https://json-schema.org/draft/2019-09/json-schema-core#scopes
135
- function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | undefined {
150
+ function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | JsonError {
136
151
  const history = path;
137
152
  const refInCurrentScope = resolveUri(node.$id, node.schema.$dynamicRef);
138
153
 
@@ -163,14 +178,12 @@ function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode
163
178
  return getRef(node, refInCurrentScope);
164
179
  }
165
180
 
166
- const PROPERTIES_TO_MERGE = ["title", "description", "options", "readOnly", "writeOnly"];
167
-
168
- function compileNext(referencedNode: SchemaNode, sourceNode: SchemaNode) {
181
+ export function compileNext(referencedNode: SchemaNode, sourceNode: SchemaNode) {
169
182
  let referencedSchema = referencedNode.schema;
170
183
  if (isObject(referencedNode.schema)) {
171
184
  referencedSchema = {
172
185
  ...omit(referencedNode.schema, "$id"),
173
- ...pick(sourceNode.schema, ...PROPERTIES_TO_MERGE)
186
+ ...pick(sourceNode.schema, ...settings.PROPERTIES_TO_MERGE)
174
187
  };
175
188
  }
176
189
  return referencedNode.compileSchema(
@@ -180,7 +193,7 @@ function compileNext(referencedNode: SchemaNode, sourceNode: SchemaNode) {
180
193
  );
181
194
  }
182
195
 
183
- export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
196
+ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | JsonError {
184
197
  if ($ref == null) {
185
198
  return node;
186
199
  }
@@ -202,7 +215,12 @@ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefi
202
215
  // check for remote-host + pointer pair to switch rootSchema
203
216
  const fragments = splitRef($ref);
204
217
  if (fragments.length === 0) {
205
- return undefined;
218
+ return node.createError("ref-error", {
219
+ ref: $ref,
220
+ pointer: node.evaluationPath,
221
+ schema: node.schema,
222
+ value: undefined
223
+ });
206
224
  }
207
225
 
208
226
  // resolve $ref as remote-host
@@ -212,6 +230,7 @@ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefi
212
230
  if (node.context.remotes[$ref]) {
213
231
  return compileNext(node.context.remotes[$ref], node);
214
232
  }
233
+
215
234
  if ($ref[0] === "#") {
216
235
  // support refOfUnknownKeyword
217
236
  const rootSchema = node.context.rootNode.schema;
@@ -221,7 +240,12 @@ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefi
221
240
  }
222
241
  }
223
242
  // console.error("REF: UNFOUND 1", $ref);
224
- return undefined;
243
+ return node.createError("ref-error", {
244
+ ref: $ref,
245
+ pointer: node.evaluationPath,
246
+ schema: node.schema,
247
+ value: undefined
248
+ });
225
249
  }
226
250
 
227
251
  if (fragments.length === 2) {
@@ -252,16 +276,25 @@ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefi
252
276
  // @ts-expect-error random path
253
277
  currentNode = currentNode[property];
254
278
  if (currentNode == null) {
255
- console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
256
- return undefined;
279
+ // console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
280
+ return node.createError("ref-error", {
281
+ ref: $ref,
282
+ pointer: node.evaluationPath,
283
+ schema: node.schema,
284
+ value: undefined,
285
+ host: fragments[0],
286
+ local: fragments[1]
287
+ });
257
288
  }
258
289
  }
259
290
  return currentNode;
260
291
  }
261
-
262
- console.error("REF: UNFOUND 2", $ref);
263
- return undefined;
264
292
  }
265
293
 
266
- console.error("REF: UNHANDLED", $ref);
294
+ return node.createError("ref-error", {
295
+ ref: $ref,
296
+ pointer: node.evaluationPath,
297
+ schema: node.schema,
298
+ value: undefined
299
+ });
267
300
  }
@@ -483,7 +483,7 @@ describe("keyword : oneof-fuzzy : validate", () => {
483
483
  required: ["type", "children"],
484
484
  properties: {
485
485
  type: {
486
- options: { hidden: true },
486
+ "x-options": { hidden: true },
487
487
  type: "string",
488
488
  const: "parent"
489
489
  },
@@ -500,7 +500,7 @@ describe("keyword : oneof-fuzzy : validate", () => {
500
500
  required: ["type"],
501
501
  properties: {
502
502
  type: {
503
- options: { hidden: true },
503
+ "x-options": { hidden: true },
504
504
  type: "string",
505
505
  const: "one"
506
506
  }
@@ -512,7 +512,7 @@ describe("keyword : oneof-fuzzy : validate", () => {
512
512
  required: ["type"],
513
513
  properties: {
514
514
  type: {
515
- options: { hidden: true },
515
+ "x-options": { hidden: true },
516
516
  type: "string",
517
517
  const: "two"
518
518
  }