@hyperjump/json-schema 1.11.0 → 1.12.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 (56) hide show
  1. package/README.md +24 -7
  2. package/annotations/annotated-instance.d.ts +0 -1
  3. package/annotations/annotated-instance.js +0 -7
  4. package/annotations/index.js +19 -3
  5. package/draft-04/additionalItems.js +4 -4
  6. package/draft-04/dependencies.js +2 -2
  7. package/draft-04/items.js +5 -5
  8. package/draft-06/contains.js +3 -2
  9. package/draft-2020-12/dynamicRef.js +5 -5
  10. package/lib/core.js +5 -4
  11. package/lib/experimental.d.ts +12 -3
  12. package/lib/experimental.js +1 -1
  13. package/lib/index.d.ts +2 -1
  14. package/lib/instance.d.ts +0 -2
  15. package/lib/instance.js +0 -2
  16. package/lib/keywords/additionalProperties.js +7 -5
  17. package/lib/keywords/allOf.js +7 -5
  18. package/lib/keywords/anyOf.js +4 -4
  19. package/lib/keywords/conditional.js +9 -9
  20. package/lib/keywords/contains.js +5 -5
  21. package/lib/keywords/contentEncoding.js +3 -7
  22. package/lib/keywords/contentMediaType.js +3 -7
  23. package/lib/keywords/contentSchema.js +3 -7
  24. package/lib/keywords/default.js +3 -7
  25. package/lib/keywords/dependentSchemas.js +7 -5
  26. package/lib/keywords/deprecated.js +3 -7
  27. package/lib/keywords/description.js +3 -7
  28. package/lib/keywords/dynamicRef.js +6 -4
  29. package/lib/keywords/else.js +13 -10
  30. package/lib/keywords/examples.js +3 -7
  31. package/lib/keywords/format.js +3 -7
  32. package/lib/keywords/if.js +6 -6
  33. package/lib/keywords/itemPattern.js +7 -7
  34. package/lib/keywords/items.js +7 -5
  35. package/lib/keywords/oneOf.js +6 -6
  36. package/lib/keywords/patternProperties.js +7 -5
  37. package/lib/keywords/prefixItems.js +7 -5
  38. package/lib/keywords/properties.js +7 -5
  39. package/lib/keywords/propertyDependencies.js +7 -5
  40. package/lib/keywords/propertyNames.js +5 -3
  41. package/lib/keywords/readOnly.js +3 -7
  42. package/lib/keywords/ref.js +3 -1
  43. package/lib/keywords/then.js +14 -12
  44. package/lib/keywords/title.js +3 -7
  45. package/lib/keywords/unevaluatedItems.js +11 -8
  46. package/lib/keywords/unevaluatedProperties.js +11 -7
  47. package/lib/keywords/unknown.js +3 -7
  48. package/lib/keywords/validation.js +83 -28
  49. package/lib/keywords/writeOnly.js +3 -7
  50. package/openapi-3-0/discriminator.js +3 -7
  51. package/openapi-3-0/example.js +3 -7
  52. package/openapi-3-0/externalDocs.js +3 -7
  53. package/openapi-3-0/index.d.ts +1 -1
  54. package/openapi-3-0/xml.js +3 -7
  55. package/package.json +1 -1
  56. package/lib/output.js +0 -41
@@ -1,3 +1,4 @@
1
+ import { FLAG } from "../index.js";
1
2
  import { Validation, canonicalUri } from "../experimental.js";
2
3
  import * as Instance from "../instance.js";
3
4
 
@@ -8,12 +9,13 @@ const compile = async (schema, ast, parentSchema) => {
8
9
  return [canonicalUri(parentSchema), await Validation.compile(schema, ast)];
9
10
  };
10
11
 
11
- const interpret = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnchors, quiet) => {
12
+ const interpret = ([schemaUrl, unevaluatedProperties], instance, context) => {
12
13
  if (Instance.typeOf(instance) !== "object") {
13
14
  return true;
14
15
  }
15
16
 
16
- const evaluatedPropertyNames = Validation.collectEvaluatedProperties(schemaUrl, instance, ast, dynamicAnchors, true);
17
+ const keywordContext = { ...context, errors: [], annotations: [], outputFormat: FLAG };
18
+ const evaluatedPropertyNames = Validation.collectEvaluatedProperties(schemaUrl, instance, keywordContext, true);
17
19
  if (evaluatedPropertyNames === false) {
18
20
  return true;
19
21
  }
@@ -21,7 +23,7 @@ const interpret = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnc
21
23
  let isValid = true;
22
24
  for (const [propertyNameNode, property] of Instance.entries(instance)) {
23
25
  const propertyName = Instance.value(propertyNameNode);
24
- if (!evaluatedPropertyNames.has(propertyName) && !Validation.interpret(unevaluatedProperties, property, ast, dynamicAnchors, quiet)) {
26
+ if (!evaluatedPropertyNames.has(propertyName) && !Validation.interpret(unevaluatedProperties, property, context)) {
25
27
  isValid = false;
26
28
  }
27
29
  }
@@ -29,12 +31,14 @@ const interpret = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnc
29
31
  return isValid;
30
32
  };
31
33
 
32
- const collectEvaluatedProperties = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnchors) => {
34
+ const simpleApplicator = true;
35
+
36
+ const collectEvaluatedProperties = ([schemaUrl, unevaluatedProperties], instance, context) => {
33
37
  if (Instance.typeOf(instance) !== "object") {
34
38
  return true;
35
39
  }
36
40
 
37
- const evaluatedPropertyNames = Validation.collectEvaluatedProperties(schemaUrl, instance, ast, dynamicAnchors, true);
41
+ const evaluatedPropertyNames = Validation.collectEvaluatedProperties(schemaUrl, instance, context, true);
38
42
 
39
43
  if (!evaluatedPropertyNames) {
40
44
  return false;
@@ -43,7 +47,7 @@ const collectEvaluatedProperties = ([schemaUrl, unevaluatedProperties], instance
43
47
  for (const [propertyNameNode, property] of Instance.entries(instance)) {
44
48
  const propertyName = Instance.value(propertyNameNode);
45
49
  if (!evaluatedPropertyNames.has(propertyName)) {
46
- if (!Validation.interpret(unevaluatedProperties, property, ast, dynamicAnchors, true)) {
50
+ if (!Validation.interpret(unevaluatedProperties, property, context)) {
47
51
  return false;
48
52
  }
49
53
 
@@ -54,4 +58,4 @@ const collectEvaluatedProperties = ([schemaUrl, unevaluatedProperties], instance
54
58
  return evaluatedPropertyNames;
55
59
  };
56
60
 
57
- export default { id, compile, interpret, collectEvaluatedProperties };
61
+ export default { id, compile, interpret, simpleApplicator, collectEvaluatedProperties };
@@ -1,5 +1,4 @@
1
1
  import * as Browser from "@hyperjump/browser";
2
- import * as Instance from "../../annotations/annotated-instance.js";
3
2
  import { pointerSegments } from "@hyperjump/json-pointer";
4
3
 
5
4
 
@@ -10,10 +9,7 @@ const compile = (schema) => {
10
9
  return [keywordName, Browser.value(schema)];
11
10
  };
12
11
 
13
- const interpret = ([keywordName, value], instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => {
14
- const keywordId = `${id}#${keywordName}`;
15
- Instance.setAnnotation(instance, keywordId, schemaLocation, value);
16
- return true;
17
- };
12
+ const interpret = () => true;
13
+ const annotation = ([, value]) => value;
18
14
 
19
- export default { id, compile, interpret };
15
+ export default { id, compile, interpret, annotation };
@@ -2,8 +2,10 @@ import { value, entries } from "@hyperjump/browser";
2
2
  import { pipe, asyncMap, asyncCollectArray } from "@hyperjump/pact";
3
3
  import { append as pointerAppend } from "@hyperjump/json-pointer";
4
4
  import { publishAsync } from "../pubsub.js";
5
+ import * as Instance from "../instance.js";
5
6
  import { toAbsoluteUri } from "../common.js";
6
- import { canonicalUri, getKeyword, getKeywordByName } from "../experimental.js";
7
+ import { canonicalUri, getKeyword, getKeywordByName, BASIC, DETAILED } from "../experimental.js";
8
+ import { FLAG } from "../index.js";
7
9
 
8
10
 
9
11
  const id = "https://json-schema.org/evaluation/validate";
@@ -43,48 +45,101 @@ const compile = async (schema, ast) => {
43
45
  return url;
44
46
  };
45
47
 
46
- const interpret = (url, instance, ast, dynamicAnchors, quiet = false) => {
48
+ const interpret = (url, instance, { ast, dynamicAnchors, errors, annotations, outputFormat }) => {
47
49
  dynamicAnchors = { ...ast.metaData[toAbsoluteUri(url)].dynamicAnchors, ...dynamicAnchors };
48
50
 
49
- let isSchemaValid = true;
50
51
  if (typeof ast[url] === "boolean") {
51
- isSchemaValid = ast[url];
52
+ if (ast[url]) {
53
+ return true;
54
+ } else {
55
+ errors.push({
56
+ keyword: id,
57
+ absoluteKeywordLocation: url,
58
+ instanceLocation: Instance.uri(instance)
59
+ });
60
+ return false;
61
+ }
52
62
  } else {
53
- for (const [keywordId, schemaUrl, keywordValue] of ast[url]) {
54
- instance.valid = getKeyword(keywordId).interpret(keywordValue, instance, ast, dynamicAnchors, quiet, url);
55
- if (!instance.valid) {
56
- if (!quiet) {
57
- instance.errors[schemaUrl] = keywordId;
58
- }
63
+ const schemaAnnotations = [];
64
+
65
+ let isSchemaValid = true;
66
+ for (const [keywordId, schemaUri, keywordValue] of ast[url]) {
67
+ const context = { ast, dynamicAnchors, errors: [], annotations: [], outputFormat };
68
+ const keywordHandler = getKeyword(keywordId);
69
+ const isKeywordValid = keywordHandler.interpret(keywordValue, instance, context);
70
+ if (!isKeywordValid) {
59
71
  isSchemaValid = false;
60
72
  }
73
+
74
+ switch (outputFormat) {
75
+ case FLAG:
76
+ break;
77
+ case BASIC:
78
+ if (!isKeywordValid) {
79
+ if (!keywordHandler.simpleApplicator) {
80
+ errors.push({
81
+ keyword: keywordId,
82
+ absoluteKeywordLocation: schemaUri,
83
+ instanceLocation: Instance.uri(instance)
84
+ });
85
+ }
86
+ errors.push(...context.errors);
87
+ } else {
88
+ if (keywordHandler.annotation) {
89
+ schemaAnnotations.push({
90
+ keyword: keywordId,
91
+ absoluteKeywordLocation: schemaUri,
92
+ instanceLocation: Instance.uri(instance),
93
+ annotation: keywordHandler.annotation(keywordValue)
94
+ });
95
+ }
96
+ schemaAnnotations.push(...context.annotations);
97
+ }
98
+ break;
99
+ case DETAILED: {
100
+ if (!isKeywordValid) {
101
+ const outputUnit = {
102
+ keyword: keywordId,
103
+ absoluteKeywordLocation: schemaUri,
104
+ instanceLocation: Instance.uri(instance)
105
+ };
106
+
107
+ errors.push(outputUnit);
108
+ if (context.errors.length > 0) {
109
+ outputUnit.errors = context.errors;
110
+ }
111
+ }
112
+ break;
113
+ }
114
+ default:
115
+ throw Error(`Unsupported output format '${outputFormat}'`);
116
+ }
61
117
  }
62
- }
63
118
 
64
- if (!isSchemaValid) {
65
- instance.errors[url] = id;
66
- }
119
+ if (outputFormat === BASIC && isSchemaValid) {
120
+ annotations.push(...schemaAnnotations);
121
+ }
67
122
 
68
- instance.valid = isSchemaValid;
69
- return isSchemaValid;
123
+ return isSchemaValid;
124
+ }
70
125
  };
71
126
 
72
127
  const emptyPropertyNames = new Set();
73
- const collectEvaluatedProperties = (url, instance, ast, dynamicAnchors, isTop = false) => {
74
- if (typeof ast[url] === "boolean") {
75
- return ast[url] ? new Set() : false;
128
+ const collectEvaluatedProperties = (url, instance, context, isTop = false) => {
129
+ if (typeof context.ast[url] === "boolean") {
130
+ return context.ast[url] ? emptyPropertyNames : false;
76
131
  }
77
132
 
78
133
  const accumulatedPropertyNames = new Set();
79
- for (const [keywordId, , keywordValue] of ast[url]) {
134
+ for (const [keywordId, , keywordValue] of context.ast[url]) {
80
135
  if (isTop && keywordId === "https://json-schema.org/keyword/unevaluatedProperties") {
81
136
  continue;
82
137
  }
83
138
 
84
139
  const keywordHandler = getKeyword(keywordId);
85
140
  const propertyNames = "collectEvaluatedProperties" in keywordHandler
86
- ? keywordHandler.collectEvaluatedProperties(keywordValue, instance, ast, dynamicAnchors, isTop)
87
- : keywordHandler.interpret(keywordValue, instance, ast, dynamicAnchors, true) && emptyPropertyNames;
141
+ ? keywordHandler.collectEvaluatedProperties(keywordValue, instance, context, isTop)
142
+ : keywordHandler.interpret(keywordValue, instance, context) && emptyPropertyNames;
88
143
 
89
144
  if (propertyNames === false) {
90
145
  return false;
@@ -97,21 +152,21 @@ const collectEvaluatedProperties = (url, instance, ast, dynamicAnchors, isTop =
97
152
  };
98
153
 
99
154
  const emptyItemIndexes = new Set();
100
- const collectEvaluatedItems = (url, instance, ast, dynamicAnchors, isTop = false) => {
101
- if (typeof ast[url] === "boolean") {
102
- return ast[url] ? new Set() : false;
155
+ const collectEvaluatedItems = (url, instance, context, isTop = false) => {
156
+ if (typeof context.ast[url] === "boolean") {
157
+ return context.ast[url] ? emptyItemIndexes : false;
103
158
  }
104
159
 
105
160
  const accumulatedItemIndexes = new Set();
106
- for (const [keywordId, , keywordValue] of ast[url]) {
161
+ for (const [keywordId, , keywordValue] of context.ast[url]) {
107
162
  if (isTop && keywordId === "https://json-schema.org/keyword/unevaluatedItems") {
108
163
  continue;
109
164
  }
110
165
 
111
166
  const keywordHandler = getKeyword(keywordId);
112
167
  const itemIndexes = "collectEvaluatedItems" in keywordHandler
113
- ? keywordHandler.collectEvaluatedItems(keywordValue, instance, ast, dynamicAnchors, isTop)
114
- : keywordHandler.interpret(keywordValue, instance, ast, dynamicAnchors, true) && emptyItemIndexes;
168
+ ? keywordHandler.collectEvaluatedItems(keywordValue, instance, context, isTop)
169
+ : keywordHandler.interpret(keywordValue, instance, context) && emptyItemIndexes;
115
170
 
116
171
  if (itemIndexes === false) {
117
172
  return false;
@@ -1,14 +1,10 @@
1
1
  import * as Browser from "@hyperjump/browser";
2
- import * as Instance from "../../annotations/annotated-instance.js";
3
2
 
4
3
 
5
4
  const id = "https://json-schema.org/keyword/writeOnly";
6
5
 
7
6
  const compile = (schema) => Browser.value(schema);
7
+ const interpret = () => true;
8
+ const annotation = (writeOnly) => writeOnly;
8
9
 
9
- const interpret = (writeOnly, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => {
10
- Instance.setAnnotation(instance, id, schemaLocation, writeOnly);
11
- return true;
12
- };
13
-
14
- export default { id, compile, interpret };
10
+ export default { id, compile, interpret, annotation };
@@ -1,14 +1,10 @@
1
1
  import * as Browser from "@hyperjump/browser";
2
- import * as Instance from "../annotations/annotated-instance.js";
3
2
 
4
3
 
5
4
  const id = "https://spec.openapis.org/oas/3.0/keyword/discriminator";
6
5
 
7
6
  const compile = (schema) => Browser.value(schema);
7
+ const interpret = () => true;
8
+ const annotation = (discriminator) => discriminator;
8
9
 
9
- const interpret = (discriminator, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => {
10
- Instance.setAnnotation(instance, id, schemaLocation, discriminator);
11
- return true;
12
- };
13
-
14
- export default { id, compile, interpret };
10
+ export default { id, compile, interpret, annotation };
@@ -1,14 +1,10 @@
1
1
  import * as Browser from "@hyperjump/browser";
2
- import * as Instance from "../annotations/annotated-instance.js";
3
2
 
4
3
 
5
4
  const id = "https://spec.openapis.org/oas/3.0/keyword/example";
6
5
 
7
6
  const compile = (schema) => Browser.value(schema);
7
+ const interpret = () => true;
8
+ const annotation = (example) => example;
8
9
 
9
- const interpret = (example, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => {
10
- Instance.setAnnotation(instance, id, schemaLocation, example);
11
- return true;
12
- };
13
-
14
- export default { id, compile, interpret };
10
+ export default { id, compile, interpret, annotation };
@@ -1,14 +1,10 @@
1
1
  import * as Browser from "@hyperjump/browser";
2
- import * as Instance from "../annotations/annotated-instance.js";
3
2
 
4
3
 
5
4
  const id = "https://spec.openapis.org/oas/3.0/keyword/externalDocs";
6
5
 
7
6
  const compile = (schema) => Browser.value(schema);
7
+ const interpret = () => true;
8
+ const annotation = (externalDocs) => externalDocs;
8
9
 
9
- const interpret = (externalDocs, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => {
10
- Instance.setAnnotation(instance, id, schemaLocation, externalDocs);
11
- return true;
12
- };
13
-
14
- export default { id, compile, interpret };
10
+ export default { id, compile, interpret, annotation };
@@ -57,7 +57,7 @@ type Xml = {
57
57
  wrapped?: boolean;
58
58
  };
59
59
 
60
- type OpenApi = {
60
+ export type OpenApi30 = {
61
61
  openapi: string;
62
62
  info: Info;
63
63
  externalDocs?: ExternalDocs;
@@ -1,14 +1,10 @@
1
1
  import * as Browser from "@hyperjump/browser";
2
- import * as Instance from "../annotations/annotated-instance.js";
3
2
 
4
3
 
5
4
  const id = "https://spec.openapis.org/oas/3.0/keyword/xml";
6
5
 
7
6
  const compile = (schema) => Browser.value(schema);
7
+ const interpret = () => true;
8
+ const annotation = (xml) => xml;
8
9
 
9
- const interpret = (xml, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => {
10
- Instance.setAnnotation(instance, id, schemaLocation, xml);
11
- return true;
12
- };
13
-
14
- export default { id, compile, interpret };
10
+ export default { id, compile, interpret, annotation };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperjump/json-schema",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "A JSON Schema validator with support for custom keywords, vocabularies, and dialects",
5
5
  "type": "module",
6
6
  "main": "./stable/index.js",
package/lib/output.js DELETED
@@ -1,41 +0,0 @@
1
- import * as Instance from "./instance.js";
2
-
3
-
4
- const outputFormats = {};
5
-
6
- export const toOutputFormat = (node, outputFormat) => {
7
- if (outputFormat in outputFormats) {
8
- return outputFormats[outputFormat](node);
9
- } else {
10
- throw Error(`The '${outputFormat}' error format is not supported`);
11
- }
12
- };
13
-
14
- outputFormats.FLAG = (instance) => {
15
- return { valid: instance.valid };
16
- };
17
-
18
- outputFormats.BASIC = (instance) => {
19
- const output = {
20
- valid: instance.valid
21
- };
22
-
23
- if (!instance.valid) {
24
- output.errors = [];
25
-
26
- for (const child of Instance.allNodes(instance)) {
27
- for (const [absoluteKeywordLocation, keyword] of Object.entries(child.errors).reverse()) {
28
- if (keyword !== "https://json-schema.org/evaluation/validate" && !child.valid) {
29
- output.errors.unshift({
30
- keyword,
31
- absoluteKeywordLocation,
32
- instanceLocation: Instance.uri(child),
33
- valid: child.valid
34
- });
35
- }
36
- }
37
- }
38
- }
39
-
40
- return output;
41
- };