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
@@ -2,6 +2,9 @@ import { compileSchema } from "./compileSchema";
2
2
  import { strict as assert } from "assert";
3
3
  import { draftEditor } from "./draftEditor";
4
4
  import { SchemaNode } from "./SchemaNode";
5
+ import { draft04 } from "./draft04";
6
+ import { draft07 } from "./draft07";
7
+ import { draft2020 } from "./draft2020";
5
8
 
6
9
  describe("compileSchema vocabulary", () => {
7
10
  let root: SchemaNode;
@@ -33,6 +36,54 @@ describe("compileSchema vocabulary", () => {
33
36
  });
34
37
  });
35
38
 
39
+ describe("compileSchema draft-version", () => {
40
+ it("should select last draft", () => {
41
+ const node = compileSchema({}, { drafts: [draft04, draft07, draft2020] });
42
+ assert(node.getDraftVersion(), "draft-2020");
43
+ });
44
+
45
+ it("should select draft specified by $schema", () => {
46
+ const node = compileSchema(
47
+ { $schema: "http://json-schema.org/draft-07/schema#" },
48
+ { drafts: [draft04, draft07, draft2020] }
49
+ );
50
+ assert(node.getDraftVersion(), "draft-07");
51
+ });
52
+
53
+ it("should select fallback draft specified by `draft`", () => {
54
+ const node = compileSchema(
55
+ {},
56
+ {
57
+ draft: "http://json-schema.org/draft-07/schema#",
58
+ drafts: [draft04, draft07, draft2020]
59
+ }
60
+ );
61
+ assert(node.getDraftVersion(), "draft-07");
62
+ });
63
+
64
+ it("should prefer `$schema` over `draft` options", () => {
65
+ const node = compileSchema(
66
+ { $schema: "http://json-schema.org/draft-04/schema#" },
67
+ {
68
+ draft: "http://json-schema.org/draft-07/schema#",
69
+ drafts: [draft04, draft07, draft2020]
70
+ }
71
+ );
72
+ assert(node.getDraftVersion(), "draft-04");
73
+ });
74
+
75
+ it("should always fallback to last draft if nothing matches", () => {
76
+ const node = compileSchema(
77
+ { $schema: "http://json-schema.org/draft-A/schema#" },
78
+ {
79
+ draft: "http://json-schema.org/draft-B/schema#",
80
+ drafts: [draft04, draft07, draft2020]
81
+ }
82
+ );
83
+ assert(node.getDraftVersion(), "draft-2020");
84
+ });
85
+ });
86
+
36
87
  describe("compileSchema vocabulary", () => {
37
88
  it("should add remote schema on compile", () => {
38
89
  const remote = compileSchema({
@@ -150,6 +201,7 @@ describe("compileSchema `schemaLocation`", () => {
150
201
  $defs: { asset: { type: "string" } }
151
202
  });
152
203
 
204
+ assert(node.if && node.then && node.properties && node.$defs);
153
205
  assert.deepEqual(node.schemaLocation, "#");
154
206
  assert.deepEqual(node.if.schemaLocation, "#/if");
155
207
  assert.deepEqual(node.then.schemaLocation, "#/then");
@@ -25,14 +25,63 @@ import sanitizeErrors from "./utils/sanitizeErrors";
25
25
  const { REGEX_FLAGS } = settings;
26
26
 
27
27
  export type CompileOptions = {
28
+ /**
29
+ * List of drafts to support.
30
+ *
31
+ * Drafts are selected by testing the passed `schema.$schema` for a matching id, which
32
+ * is tested by each draft's `Draft.$schemaRegEx`. In case no draft matches `schema.$schema`
33
+ * the last draft in the list will be used.
34
+ *
35
+ * @default [draft04, draft06, draft07, draft2019, draft2020]
36
+ *
37
+ * @example
38
+ * import { draft04, draft07, draft2020 } from "json-schema-library"
39
+ * compileSchema({ $schema: "draft-04" }, { drafts: [draft04, draft07, draft2020] })
40
+ */
28
41
  drafts?: Draft[];
42
+ /**
43
+ * Fallback _draft_ version in case no _draft_ is specified by `schema.$schema`.
44
+ *
45
+ * Drafts are selected by given `schema.$schema` or the last draft from `drafts` as a fallback.
46
+ * Specifying `draft` will workthe same as a specifying `schema.$schema` in case no $schema is
47
+ * defined. When no match can be found, the last _draft_ from `drafts` will be used.
48
+ *
49
+ * @example
50
+ * // uses draft-04
51
+ * compileSchema({ $schema: "draft-04" }, { drafts: [draft04, draft07, draft2020] })
52
+ *
53
+ * // uses draft-2020-12
54
+ * compileSchema({}, { drafts: [draft04, draft07, draft2020] })
55
+ *
56
+ * // uses draft-07
57
+ * compileSchema({}, { draft: "draft-07", drafts: [draft04, draft07, draft2020] })
58
+
59
+ * // uses draft-04
60
+ * compileSchema({ $schema: "draft-04" }, { draft: "draft-07", drafts: [draft04, draft07, draft2020] })
61
+ *
62
+ * // uses draft-2020
63
+ * compileSchema({ $schema: "draft-04" }, { draft: "draft-07", drafts: [draft2020] })
64
+ */
65
+ draft?: string;
66
+ /**
67
+ * Set node and its remote schemata as remote schemata for this node and schema to resolve $ref
68
+ */
29
69
  remote?: SchemaNode;
70
+ /**
71
+ * Enables `format`-keyword assertions when this is set tor `true` or sets assertion as defined by
72
+ * the given meta-schema. Set to `false` to deactivate format validation.
73
+ *
74
+ * @default true
75
+ */
30
76
  formatAssertion?: boolean | "meta-schema" | undefined;
77
+ /** Set default options for all `node.getData` requests */
31
78
  getDataDefaultOptions?: TemplateOptions;
32
- /** set to true to throw an Errors on errors in input schema. Defaults to false */
79
+ /** Set to true to throw an Error on errors in input schema. Defaults to false */
33
80
  throwOnInvalidSchema?: boolean;
34
- /** set to true to collect unknown keywords of input schema in `node.schemaAnnotations`. Defaults to false */
81
+ /** Set to true to collect unknown keywords of input schema in `node.schemaAnnotations`. Defaults to false */
35
82
  withSchemaAnnotations?: boolean;
83
+ /** Set to true to throw an Error when encountering an unresolvable ref */
84
+ throwOnInvalidRef?: boolean;
36
85
  };
37
86
 
38
87
  const defaultDrafts: Draft[] = [draft04, draft06, draft07, draft2019, draft2020];
@@ -49,7 +98,7 @@ function getDraft(drafts: Draft[], $schema: string) {
49
98
  export function compileSchema(schema: JsonSchema | BooleanSchema, options: CompileOptions = {}) {
50
99
  let formatAssertion = options.formatAssertion ?? true;
51
100
  const drafts = options.drafts ?? defaultDrafts;
52
- const draft = getDraft(drafts, isJsonSchema(schema) ? schema.$schema : undefined);
101
+ const draft = getDraft(drafts, isJsonSchema(schema) ? (options.draft ?? schema.$schema) : undefined);
53
102
  const node: SchemaNode & { schemaErrors?: JsonError[]; schemaAnnotations: JsonAnnotation[] } = {
54
103
  evaluationPath: "#",
55
104
  lastIdPointer: "#",
@@ -69,6 +118,7 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
69
118
  ...copy(pick(draft, "methods", "keywords", "version", "formats", "errors")),
70
119
  getDataDefaultOptions: options.getDataDefaultOptions,
71
120
  withSchemaAnnotations: options.withSchemaAnnotations ?? false,
121
+ throwOnInvalidRef: options.throwOnInvalidRef ?? false,
72
122
  drafts
73
123
  },
74
124
  ...SchemaNodeMethods
@@ -4,7 +4,7 @@ import { isObject } from "../../utils/isObject";
4
4
  import { omit } from "../../utils/omit";
5
5
  import splitRef from "../../utils/splitRef";
6
6
  import { $refKeyword as draft06Keyword } from "../../draft06/keywords/$ref";
7
- import { SchemaNode } from "../../types";
7
+ import { isSchemaNode, JsonError, SchemaNode } from "../../types";
8
8
 
9
9
  export const $refKeyword: Keyword = {
10
10
  id: "$ref",
@@ -63,14 +63,9 @@ function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: string; pat
63
63
  return this;
64
64
  }
65
65
  const resolvedNode = getRef(this);
66
- // console.log("RESOLVE REF", node.schema, "resolved ref", node.$ref, "=>", resolvedNode.schema);
67
- if (resolvedNode != null) {
66
+ if (isSchemaNode(resolvedNode)) {
68
67
  path?.push({ pointer: pointer!, node: resolvedNode });
69
- // console.log("resolve ref", node.$ref, "=>", resolvedNode.schema, Object.keys(node.context.refs));
70
- } else {
71
- console.log("failed resolving", this.$ref, "from", Object.keys(this.context.refs));
72
68
  }
73
-
74
69
  return resolvedNode;
75
70
  }
76
71
 
@@ -81,7 +76,7 @@ function compileNext(referencedNode: SchemaNode, evaluationPath = referencedNode
81
76
  return referencedNode.compileSchema(referencedSchema, `${evaluationPath}/$ref`, referencedSchema.schemaLocation);
82
77
  }
83
78
 
84
- function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
79
+ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | JsonError | undefined {
85
80
  if ($ref == null) {
86
81
  return node;
87
82
  }
@@ -100,8 +95,12 @@ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
100
95
  // check for remote-host + pointer pair to switch rootSchema
101
96
  const fragments = splitRef($ref);
102
97
  if (fragments.length === 0) {
103
- // console.error("REF: INVALID", $ref);
104
- return undefined;
98
+ return node.createError("ref-error", {
99
+ ref: $ref,
100
+ pointer: node.evaluationPath,
101
+ schema: node.schema,
102
+ value: undefined
103
+ });
105
104
  }
106
105
 
107
106
  // resolve $ref as remote-host
@@ -111,8 +110,12 @@ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
111
110
  if (node.context.remotes[$ref]) {
112
111
  return compileNext(node.context.remotes[$ref], node.evaluationPath);
113
112
  }
114
- // console.error("REF: UNFOUND 1", $ref, Object.keys(node.context.remotes));
115
- return undefined;
113
+ return node.createError("ref-error", {
114
+ ref: $ref,
115
+ pointer: node.evaluationPath,
116
+ schema: node.schema,
117
+ value: undefined
118
+ });
116
119
  }
117
120
 
118
121
  if (fragments.length === 2) {
@@ -142,9 +145,14 @@ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
142
145
  }
143
146
  }
144
147
 
145
- // console.error("REF: UNFOUND 2", $ref);
148
+ // @todo returning error here breaks specs
146
149
  return undefined;
147
150
  }
148
151
 
149
- console.error("REF: INVALID", $ref);
152
+ return node.createError("ref-error", {
153
+ ref: $ref,
154
+ pointer: node.evaluationPath,
155
+ schema: node.schema,
156
+ value: undefined
157
+ });
150
158
  }
@@ -1,12 +1,26 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../../Keyword";
2
+ import { SchemaNode } from "../../SchemaNode";
2
3
 
3
4
  export const exclusiveMaximumKeyword: Keyword = {
4
5
  id: "exclusiveMaximum",
5
6
  keyword: "exclusiveMaximum",
7
+ parse,
6
8
  addValidate: ({ schema }) => schema.exclusiveMaximum === true || !isNaN(schema.maximum),
7
9
  validate: validateExclusiveMaximum
8
10
  };
9
11
 
12
+ function parse(node: SchemaNode) {
13
+ const { exclusiveMaximum } = node.schema;
14
+ if (exclusiveMaximum != null && !(typeof exclusiveMaximum === "number" || typeof exclusiveMaximum === "boolean")) {
15
+ return node.createError("schema-error", {
16
+ pointer: node.evaluationPath,
17
+ schema: node.schema,
18
+ value: undefined,
19
+ message: `Keyword 'exclusiveMaximum' must be a number - received '${typeof exclusiveMaximum}'`
20
+ });
21
+ }
22
+ }
23
+
10
24
  function validateExclusiveMaximum({ node, data, pointer }: JsonSchemaValidatorParams) {
11
25
  if (typeof data !== "number") {
12
26
  return undefined;
@@ -1,12 +1,26 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../../Keyword";
2
+ import { SchemaNode } from "../../SchemaNode";
2
3
 
3
4
  export const exclusiveMinimumKeyword: Keyword = {
4
5
  id: "exclusiveMinimum",
5
6
  keyword: "exclusiveMinimum",
7
+ parse,
6
8
  addValidate: ({ schema }) => schema.exclusiveMinimum === true || !isNaN(schema.minimum),
7
9
  validate: validateExclusiveMinimum
8
10
  };
9
11
 
12
+ function parse(node: SchemaNode) {
13
+ const { exclusiveMinimum } = node.schema;
14
+ if (exclusiveMinimum != null && !(typeof exclusiveMinimum === "number" || typeof exclusiveMinimum === "boolean")) {
15
+ return node.createError("schema-error", {
16
+ pointer: node.evaluationPath,
17
+ schema: node.schema,
18
+ value: undefined,
19
+ message: `Keyword 'exclusiveMinimum' must be a number - received '${typeof exclusiveMinimum}'`
20
+ });
21
+ }
22
+ }
23
+
10
24
  function validateExclusiveMinimum({ node, data, pointer }: JsonSchemaValidatorParams) {
11
25
  if (typeof data !== "number") {
12
26
  return undefined;
@@ -0,0 +1,20 @@
1
+ import { compileSchema as _compileSchema, type CompileOptions } from "../compileSchema";
2
+ import { strict as assert } from "assert";
3
+ import { draft04 } from "../draft04";
4
+ import { JsonSchema } from "../types";
5
+
6
+ const drafts = [draft04];
7
+ function compileSchema(schema: JsonSchema, options: CompileOptions = {}) {
8
+ return _compileSchema(schema, { ...options, drafts });
9
+ }
10
+
11
+ describe("validateSchema (4)", () => {
12
+ it("should error if `exclusiveMinimum` is not a number or boolean", () => {
13
+ const { schemaErrors } = compileSchema({ exclusiveMinimum: [] });
14
+ assert.equal(schemaErrors?.length, 1);
15
+ });
16
+ it("should error if `exclusiveMaximum` is not a number or boolean", () => {
17
+ const { schemaErrors } = compileSchema({ exclusiveMaximum: [] });
18
+ assert.equal(schemaErrors?.length, 1);
19
+ });
20
+ });
package/src/draft04.ts CHANGED
@@ -61,8 +61,8 @@ import { uniqueItemsKeyword } from "./keywords/uniqueItems";
61
61
  */
62
62
  export const draft04 = sanitizeKeywords({
63
63
  version: "draft-04",
64
- $schemaRegEx: "draft-04",
65
- $schema: "http://json-schema.org/draft-04/schema",
64
+ $schemaRegEx: "draft-?0?4",
65
+ $schema: "http://json-schema.org/draft-04/schema#",
66
66
  errors,
67
67
  formats,
68
68
  methods: {
@@ -1,6 +1,6 @@
1
1
  import { Keyword, JsonSchemaValidatorParams, ValidationPath } from "../../Keyword";
2
2
  import { resolveRef } from "../../keywords/$ref";
3
- import { SchemaNode } from "../../types";
3
+ import { isSchemaNode, SchemaNode } from "../../types";
4
4
  import { resolveUri } from "../../utils/resolveUri";
5
5
  import { validateNode } from "../../validateNode";
6
6
 
@@ -51,16 +51,26 @@ function parseRef(node: SchemaNode) {
51
51
 
52
52
  function validateRef({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
53
53
  const nextNode = resolveAllRefs(node, pointer, path);
54
- if (nextNode == null) {
55
- return undefined;
54
+ if (!isSchemaNode(nextNode)) {
55
+ return node.createError("ref-error", {
56
+ ref: node.schema.$ref,
57
+ pointer,
58
+ schema: node.schema,
59
+ value: data
60
+ });
56
61
  }
57
62
  return validateNode(nextNode, data, pointer, path);
58
63
  }
59
64
 
60
65
  function resolveAllRefs(node: SchemaNode, pointer: string, path: ValidationPath) {
61
66
  const nextNode = node.resolveRef({ pointer, path });
62
- if (nextNode == null) {
63
- return undefined;
67
+ if (!isSchemaNode(nextNode)) {
68
+ return node.createError("ref-error", {
69
+ ref: node.schema.$ref,
70
+ pointer,
71
+ schema: node.schema,
72
+ value: undefined
73
+ });
64
74
  }
65
75
  if (nextNode !== node && nextNode) {
66
76
  return resolveAllRefs(nextNode, pointer, path);
package/src/draft06.ts CHANGED
@@ -62,8 +62,8 @@ import { uniqueItemsKeyword } from "./keywords/uniqueItems";
62
62
  */
63
63
  export const draft06 = sanitizeKeywords({
64
64
  version: "draft-06",
65
- $schemaRegEx: "draft-06",
66
- $schema: "http://json-schema.org/draft-06/schema",
65
+ $schemaRegEx: "draft-?0?6",
66
+ $schema: "http://json-schema.org/draft-06/schema#",
67
67
  errors,
68
68
  formats,
69
69
  methods: {
package/src/draft07.ts CHANGED
@@ -53,8 +53,8 @@ import { uniqueItemsKeyword } from "./keywords/uniqueItems";
53
53
  */
54
54
  export const draft07 = sanitizeKeywords({
55
55
  version: "draft-07",
56
- $schemaRegEx: "draft-07",
57
- $schema: "http://json-schema.org/draft-07/schema",
56
+ $schemaRegEx: "draft-?0?7",
57
+ $schema: "http://json-schema.org/draft-07/schema#",
58
58
  errors,
59
59
  formats,
60
60
  methods: {
@@ -1,5 +1,6 @@
1
1
  import { strict as assert } from "assert";
2
2
  import { compileSchema } from "../../compileSchema";
3
+ import { isJsonError } from "../../types";
3
4
 
4
5
  describe("keyword : $ref : resolve", () => {
5
6
  it("should return undefined for missing reference", () => {
@@ -8,7 +9,8 @@ describe("keyword : $ref : resolve", () => {
8
9
  minLength: 1
9
10
  }).resolveRef();
10
11
 
11
- assert.deepEqual(node, undefined);
12
+ assert(isJsonError(node));
13
+ assert.deepEqual(node.code, "ref-error");
12
14
  });
13
15
 
14
16
  it("should resolve $ref from definitions", () => {
@@ -1,12 +1,10 @@
1
1
  import { Keyword, JsonSchemaValidatorParams, ValidationPath } from "../../Keyword";
2
2
  import { resolveUri } from "../../utils/resolveUri";
3
3
  import splitRef from "../../utils/splitRef";
4
- import { omit } from "../../utils/omit";
5
- import { isObject } from "../../utils/isObject";
6
4
  import { validateNode } from "../../validateNode";
7
- import { SchemaNode } from "../../types";
5
+ import { isSchemaNode, JsonError, SchemaNode } from "../../types";
8
6
  import { get, split } from "@sagold/json-pointer";
9
- import { reduceRef } from "../../keywords/$ref";
7
+ import { reduceRef, compileNext } from "../../keywords/$ref";
10
8
 
11
9
  export const $refKeyword: Keyword = {
12
10
  id: "$ref",
@@ -73,7 +71,9 @@ export function parseRef(node: SchemaNode) {
73
71
  export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: string; path?: ValidationPath } = {}) {
74
72
  if (this.schema.$recursiveRef) {
75
73
  const nextNode = resolveRecursiveRef(this, path ?? []);
76
- path?.push({ pointer: pointer!, node: nextNode! });
74
+ if (isSchemaNode(nextNode)) {
75
+ path?.push({ pointer: pointer!, node: nextNode! });
76
+ }
77
77
  return nextNode;
78
78
  }
79
79
 
@@ -82,10 +82,8 @@ export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: stri
82
82
  }
83
83
 
84
84
  const resolvedNode = getRef(this);
85
- if (resolvedNode != null) {
85
+ if (isSchemaNode(resolvedNode)) {
86
86
  path?.push({ pointer: pointer!, node: resolvedNode });
87
- } else {
88
- // console.log("failed resolving", node.$ref, "from", Object.keys(node.context.refs));
89
87
  }
90
88
  return resolvedNode;
91
89
  }
@@ -93,13 +91,18 @@ export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: stri
93
91
  function validateRef({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
94
92
  const nextNode = node.resolveRef({ pointer, path });
95
93
  if (nextNode != null) {
96
- // recursively resolveRef and validate
97
94
  return validateNode(nextNode, data, pointer, path);
98
95
  }
96
+ return node.createError("ref-error", {
97
+ ref: node.schema.$ref ?? node.schema.$recursiveRef,
98
+ pointer,
99
+ schema: node.schema,
100
+ value: data
101
+ });
99
102
  }
100
103
 
101
104
  // 1. https://json-schema.org/draft/2019-09/json-schema-core#scopes
102
- function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | undefined {
105
+ function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | JsonError {
103
106
  const history = path;
104
107
 
105
108
  // RESTRICT BY CHANGE IN BASE-URL
@@ -127,33 +130,30 @@ function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode
127
130
  return nextNode;
128
131
  }
129
132
 
130
- function compileNext(referencedNode: SchemaNode, evaluationPath = referencedNode.evaluationPath) {
131
- const referencedSchema = isObject(referencedNode.schema)
132
- ? omit(referencedNode.schema, "$id")
133
- : referencedNode.schema;
134
-
135
- return referencedNode.compileSchema(referencedSchema, `${evaluationPath}/$ref`, referencedNode.schemaLocation);
136
- }
137
-
138
- export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
133
+ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | JsonError {
139
134
  if ($ref == null) {
140
135
  return node;
141
136
  }
142
137
 
143
138
  // resolve $ref by json-evaluationPath
144
139
  if (node.context.refs[$ref]) {
145
- return compileNext(node.context.refs[$ref], node.evaluationPath);
140
+ return compileNext(node.context.refs[$ref], node);
146
141
  }
147
142
  // resolve $ref from $anchor
148
143
  if (node.context.anchors[$ref]) {
149
- return compileNext(node.context.anchors[$ref], node.evaluationPath);
144
+ return compileNext(node.context.anchors[$ref], node);
150
145
  }
151
146
 
152
147
  // check for remote-host + pointer pair to switch rootSchema
153
148
  const fragments = splitRef($ref);
154
149
  if (fragments.length === 0) {
155
150
  // console.error("REF: INVALID", $ref);
156
- return undefined;
151
+ return node.createError("ref-error", {
152
+ ref: $ref,
153
+ pointer: node.evaluationPath,
154
+ schema: node.schema,
155
+ value: undefined
156
+ });
157
157
  }
158
158
 
159
159
  // resolve $ref as remote-host
@@ -161,7 +161,7 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
161
161
  const $ref = fragments[0];
162
162
  // this is a reference to remote-host root node
163
163
  if (node.context.remotes[$ref]) {
164
- return compileNext(node.context.remotes[$ref], node.evaluationPath);
164
+ return compileNext(node.context.remotes[$ref], node);
165
165
  }
166
166
  if ($ref[0] === "#") {
167
167
  // @todo there is a bug joining multiple fragments to e.g. #/base#/examples/0
@@ -175,7 +175,12 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
175
175
  }
176
176
  }
177
177
  // console.error("REF: UNFOUND 1", $ref);
178
- return undefined;
178
+ return node.createError("ref-error", {
179
+ ref: $ref,
180
+ pointer: node.evaluationPath,
181
+ schema: node.schema,
182
+ value: undefined
183
+ });
179
184
  }
180
185
 
181
186
  if (fragments.length === 2) {
@@ -206,16 +211,25 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
206
211
  // @ts-expect-error random path
207
212
  currentNode = currentNode[property];
208
213
  if (currentNode == null) {
209
- console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
210
- return undefined;
214
+ // console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
215
+ return node.createError("ref-error", {
216
+ ref: $ref,
217
+ pointer: node.evaluationPath,
218
+ schema: node.schema,
219
+ value: undefined,
220
+ host: fragments[0],
221
+ local: fragments[1]
222
+ });
211
223
  }
212
224
  }
213
225
  return currentNode;
214
226
  }
215
-
216
- // console.error("REF: UNFOUND 2", $ref);
217
- return undefined;
218
227
  }
219
228
 
220
- console.error("REF: UNHANDLED", $ref);
229
+ return node.createError("ref-error", {
230
+ ref: $ref,
231
+ pointer: node.evaluationPath,
232
+ schema: node.schema,
233
+ value: undefined
234
+ });
221
235
  }
@@ -4,28 +4,51 @@ import { SchemaNode } from "../../types";
4
4
  import { getValue } from "../../utils/getValue";
5
5
  import { validateNode } from "../../validateNode";
6
6
 
7
+ const KEYWORD = "additionalItems";
8
+
7
9
  export const additionalItemsKeyword: Keyword = {
8
- id: "additionalItems",
9
- keyword: "additionalItems",
10
+ id: KEYWORD,
11
+ keyword: KEYWORD,
10
12
  order: -10,
11
13
  parse: parseAdditionalItems,
12
14
  addResolve: (node: SchemaNode) => node.items != null,
13
15
  resolve: additionalItemsResolver,
14
- addValidate: ({ schema }) =>
15
- schema.additionalItems != null && schema.additionalItems !== true && Array.isArray(schema.items),
16
+ addValidate: ({ schema }) => schema[KEYWORD] != null && schema[KEYWORD] !== true && Array.isArray(schema.items),
16
17
  validate: validateAdditionalItems
17
18
  };
18
19
 
19
20
  // must come as last resolver
20
21
  export function parseAdditionalItems(node: SchemaNode) {
21
22
  const { schema, evaluationPath, schemaLocation } = node;
22
- if ((isObject(schema.additionalItems) || schema.additionalItems === true) && Array.isArray(schema.items)) {
23
- node.items = node.compileSchema(
24
- schema.additionalItems,
25
- `${evaluationPath}/additionalItems`,
26
- `${schemaLocation}/additionalItems`
27
- );
23
+ if (schema.additionalItems == null || schema.additionalItems === false) {
24
+ return;
25
+ }
26
+
27
+ const additionalItems = schema[KEYWORD];
28
+
29
+ if (!(isObject(additionalItems) || additionalItems === true)) {
30
+ return node.createError("schema-error", {
31
+ pointer: evaluationPath,
32
+ schema,
33
+ value: undefined,
34
+ message: `Keyword '${KEYWORD}' must be an object or a boolean - received '${typeof additionalItems}'`
35
+ });
36
+ }
37
+
38
+ if (!Array.isArray(schema.items)) {
39
+ return node.createAnnotation("schema-error", {
40
+ pointer: evaluationPath,
41
+ schema,
42
+ value: undefined,
43
+ message: `Keyword '${KEYWORD}' is only evaluated with a given items-array`
44
+ });
28
45
  }
46
+
47
+ node.items = node.compileSchema(
48
+ schema.additionalItems,
49
+ `${evaluationPath}/additionalItems`,
50
+ `${schemaLocation}/additionalItems`
51
+ );
29
52
  }
30
53
 
31
54
  function additionalItemsResolver({ node, key, data }: JsonSchemaResolverParams) {