@hyperjump/json-schema 1.6.6 → 1.7.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 (114) hide show
  1. package/README.md +247 -255
  2. package/annotations/annotated-instance.js +3 -3
  3. package/annotations/index.d.ts +7 -1
  4. package/annotations/index.js +3 -3
  5. package/bundle/index.d.ts +1 -5
  6. package/bundle/index.js +125 -121
  7. package/draft-04/additionalItems.js +6 -7
  8. package/draft-04/dependencies.js +5 -5
  9. package/draft-04/index.js +2 -2
  10. package/draft-04/items.js +5 -5
  11. package/draft-04/maximum.js +8 -8
  12. package/draft-04/minimum.js +8 -8
  13. package/draft-06/contains.js +2 -2
  14. package/draft-06/index.js +3 -2
  15. package/draft-07/index.js +3 -2
  16. package/draft-2019-09/index.js +9 -11
  17. package/draft-2020-12/dynamicRef.js +5 -5
  18. package/draft-2020-12/index.js +11 -13
  19. package/lib/common.d.ts +1 -1
  20. package/lib/common.js +44 -60
  21. package/lib/configuration.js +0 -6
  22. package/lib/core.js +32 -30
  23. package/lib/experimental.d.ts +75 -5
  24. package/lib/experimental.js +2 -2
  25. package/lib/index.d.ts +43 -11
  26. package/lib/index.js +11 -11
  27. package/lib/instance.d.ts +1 -17
  28. package/lib/instance.js +3 -3
  29. package/lib/keywords/additionalProperties.js +12 -13
  30. package/lib/keywords/allOf.js +3 -3
  31. package/lib/keywords/anyOf.js +3 -3
  32. package/lib/keywords/conditional.js +6 -7
  33. package/lib/keywords/const.js +2 -2
  34. package/lib/keywords/contains.js +14 -35
  35. package/lib/keywords/contentSchema.js +1 -1
  36. package/lib/keywords/definitions.js +2 -2
  37. package/lib/keywords/dependentRequired.js +4 -4
  38. package/lib/keywords/dependentSchemas.js +5 -5
  39. package/lib/keywords/dynamicRef.js +10 -5
  40. package/lib/keywords/else.js +5 -6
  41. package/lib/keywords/enum.js +4 -4
  42. package/lib/keywords/exclusiveMaximum.js +3 -3
  43. package/lib/keywords/exclusiveMinimum.js +3 -3
  44. package/lib/keywords/if.js +1 -1
  45. package/lib/keywords/itemPattern.js +17 -14
  46. package/lib/keywords/items.js +6 -7
  47. package/lib/keywords/maxItems.js +3 -3
  48. package/lib/keywords/maxLength.js +3 -3
  49. package/lib/keywords/maxProperties.js +3 -3
  50. package/lib/keywords/maximum.js +3 -3
  51. package/lib/keywords/meta-data.js +1 -1
  52. package/lib/keywords/minItems.js +3 -3
  53. package/lib/keywords/minLength.js +3 -3
  54. package/lib/keywords/minProperties.js +3 -3
  55. package/lib/keywords/minimum.js +3 -3
  56. package/lib/keywords/multipleOf.js +3 -3
  57. package/lib/keywords/not.js +1 -1
  58. package/lib/keywords/oneOf.js +3 -3
  59. package/lib/keywords/pattern.js +3 -3
  60. package/lib/keywords/patternProperties.js +5 -5
  61. package/lib/keywords/prefixItems.js +5 -5
  62. package/lib/keywords/properties.js +5 -5
  63. package/lib/keywords/propertyDependencies.js +6 -7
  64. package/lib/keywords/propertyNames.js +2 -2
  65. package/lib/keywords/ref.js +2 -7
  66. package/lib/keywords/requireAllExcept.js +8 -9
  67. package/lib/keywords/required.js +3 -3
  68. package/lib/keywords/then.js +5 -5
  69. package/lib/keywords/type.js +9 -3
  70. package/lib/keywords/unevaluatedItems.js +4 -4
  71. package/lib/keywords/unevaluatedProperties.js +4 -5
  72. package/lib/keywords/uniqueItems.js +3 -3
  73. package/lib/keywords/validation.js +11 -23
  74. package/lib/keywords.js +30 -4
  75. package/lib/openapi.js +19 -6
  76. package/lib/schema.js +235 -233
  77. package/openapi-3-0/index.js +5 -5
  78. package/openapi-3-0/type.js +13 -7
  79. package/openapi-3-1/index.d.ts +1 -1
  80. package/openapi-3-1/index.js +22 -21
  81. package/openapi-3-1/{schema-base/2022-10-07.js → schema-base.js} +12 -2
  82. package/openapi-3-1/schema-draft-04.js +33 -0
  83. package/openapi-3-1/schema-draft-06.js +33 -0
  84. package/openapi-3-1/schema-draft-07.js +33 -0
  85. package/openapi-3-1/schema-draft-2019-09.js +33 -0
  86. package/openapi-3-1/schema-draft-2020-12.js +33 -0
  87. package/package.json +14 -16
  88. package/stable/index.js +10 -10
  89. package/annotations/tests/applicators.json +0 -375
  90. package/annotations/tests/content.json +0 -57
  91. package/annotations/tests/core.json +0 -33
  92. package/annotations/tests/format.json +0 -20
  93. package/annotations/tests/meta-data.json +0 -128
  94. package/annotations/tests/unevaluated.json +0 -557
  95. package/annotations/tests/unknown.json +0 -87
  96. package/annotations/tests/validation.json +0 -328
  97. package/annotations/validation-error.d.ts +0 -8
  98. package/bundle/file.json +0 -57
  99. package/draft-2019-09/contains.js +0 -44
  100. package/lib/configuration.d.ts +0 -9
  101. package/lib/context-uri.browser.js +0 -1
  102. package/lib/context-uri.js +0 -4
  103. package/lib/core.d.ts +0 -48
  104. package/lib/fetch.browser.js +0 -1
  105. package/lib/fetch.js +0 -20
  106. package/lib/invalid-schema-error.d.ts +0 -8
  107. package/lib/keywords.d.ts +0 -19
  108. package/lib/media-types.d.ts +0 -11
  109. package/lib/media-types.js +0 -48
  110. package/lib/reference.d.ts +0 -11
  111. package/lib/reference.js +0 -11
  112. package/lib/schema.d.ts +0 -60
  113. /package/openapi-3-0/{schema/2021-09-28.js → schema.js} +0 -0
  114. /package/openapi-3-1/{schema/2022-10-07.js → schema.js} +0 -0
package/lib/schema.js CHANGED
@@ -1,48 +1,91 @@
1
- import { nil as nilPointer, append as pointerAppend, get as pointerGet } from "@hyperjump/json-pointer";
2
- import { toAbsoluteIri } from "@hyperjump/uri";
3
- import { jsonTypeOf, resolveUri, uriFragment, pathRelative, jsonStringify } from "./common.js";
4
- import { contextUri } from "./context-uri.js";
5
- import fetch from "./fetch.js";
6
- import { hasDialect, loadDialect, getKeywordName } from "./keywords.js";
7
- import { parseResponse, acceptableMediaTypes } from "./media-types.js";
8
- import * as Reference from "./reference.js";
1
+ import contentTypeParser from "content-type";
2
+ import { get as browserGet } from "@hyperjump/browser";
3
+ import { Reference } from "@hyperjump/browser/jref";
4
+ import { append } from "@hyperjump/json-pointer";
5
+ import { resolveIri, toAbsoluteIri, normalizeIri } from "@hyperjump/uri";
6
+ import { getKeywordName, loadDialect } from "./keywords.js";
7
+ import { uriFragment, jsonStringify, jsonTypeOf, toRelativeIri } from "./common.js";
8
+
9
+
10
+ export const schemaPlugin = {
11
+ parse: async (response) => {
12
+ const contentType = contentTypeParser.parse(response.headers.get("content-type") ?? "");
13
+ const contextDialectId = contentType.parameters.schema ?? contentType.parameters.profile;
14
+
15
+ return buildSchemaDocument(await response.json(), response.url, contextDialectId);
16
+ },
17
+ fileMatcher: (path) => path.endsWith(".schema.json")
18
+ };
19
+
20
+ const schemaRegistry = {};
9
21
 
22
+ export const getSchema = async (uri, browser = undefined) => {
23
+ if (!browser) {
24
+ const cache = {};
25
+
26
+ for (const uri in schemaRegistry) {
27
+ cache[uri] = schemaRegistry[uri];
28
+ }
10
29
 
11
- // Schema Management
12
- const schemaStore = {};
13
- const schemaStoreAlias = {};
30
+ browser = { _cache: cache };
31
+ }
32
+
33
+ const schema = await browserGet(uri, browser);
34
+ if (typeof schema.document.dialectId !== "string") {
35
+ throw Error(`The document at ${schema.document.baseUri} is not a schema.`);
36
+ }
14
37
 
15
- export const add = (schema, retrievalUri = undefined, contextDialectId = undefined) => {
16
- schema = JSON.parse(JSON.stringify(schema));
38
+ return schema;
39
+ };
40
+
41
+ export const registerSchema = (schema, retrievalUri, contextDialectId) => {
42
+ schema = structuredClone(schema);
43
+ const document = buildSchemaDocument(schema, retrievalUri, contextDialectId);
44
+
45
+ if (document.baseUri in schemaRegistry) {
46
+ throw Error(`A schema has already been registered for '${document.baseUri}. You can use 'unregisterSchema' to remove the old schema before registering the new one.`);
47
+ }
48
+
49
+ if (document.baseUri.startsWith("file:")) {
50
+ throw Error(`Registering a schema with a 'file:' URI scheme is not allowed: ${document.baseUri}`);
51
+ }
17
52
 
53
+ schemaRegistry[retrievalUri ? toAbsoluteIri(retrievalUri) : document.baseUri] = document;
54
+ };
55
+
56
+ export const unregisterSchema = (uri) => {
57
+ delete schemaRegistry[toAbsoluteIri(uri)];
58
+ };
59
+
60
+ export const buildSchemaDocument = (schema, id, dialectId, embedded = {}) => {
18
61
  // Dialect / JSON Schema Version
19
- if ((typeof schema !== "object" || !("$schema" in schema)) && !contextDialectId) {
20
- throw Error("Unable to determine a dialect for the schema. The dialect can be declared in a number of ways, but the recommended way is to use the '$schema' keyword in your schema.");
62
+ if (typeof schema.$schema === "string") {
63
+ dialectId = schema.$schema;
64
+ delete schema.$schema;
21
65
  }
22
- const dialectId = toAbsoluteIri(schema.$schema || contextDialectId);
23
- delete schema.$schema;
24
66
 
25
- if (!hasDialect(dialectId)) {
26
- throw Error(`Encountered unknown dialect '${dialectId}'`);
67
+ if (!dialectId) {
68
+ throw Error("Unable to determine a dialect for the schema. The dialect can be declared in a number of ways, but the recommended way is to use the '$schema' keyword in your schema.");
27
69
  }
70
+ dialectId = toAbsoluteIri(dialectId);
28
71
 
29
72
  // Identifiers
30
- const idToken = getKeywordName(dialectId, "https://json-schema.org/keyword/id")
31
- || getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
32
- if (retrievalUri === undefined && !(idToken in schema)) {
73
+ const legacyIdToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
74
+ const idToken = getKeywordName(dialectId, "https://json-schema.org/keyword/id") || legacyIdToken;
75
+ if (!schema[idToken] && !id) {
33
76
  throw Error(`Unable to determine an identifier for the schema. Use the '${idToken}' keyword or pass a retrievalUri when loading the schema.`);
34
77
  }
35
- const internalUrl = resolveUri(schema[idToken] || retrievalUri, retrievalUri);
36
- const id = toAbsoluteIri(internalUrl);
37
- delete schema[idToken];
38
- if (retrievalUri) {
39
- const externalId = toAbsoluteIri(retrievalUri);
40
- schemaStoreAlias[externalId] = id;
78
+ const resolvedId = resolveIri(schema[idToken] ?? "", id ?? "");
79
+ id = toAbsoluteIri(resolvedId);
80
+ if (legacyIdToken && resolvedId.length > id.length) {
81
+ schema[idToken] = "#" + uriFragment(resolvedId);
82
+ } else {
83
+ delete schema[idToken];
41
84
  }
42
85
 
43
86
  // Vocabulary
44
87
  const vocabularyToken = getKeywordName(dialectId, "https://json-schema.org/keyword/vocabulary");
45
- if (jsonTypeOf(schema[vocabularyToken], "object")) {
88
+ if (jsonTypeOf(schema[vocabularyToken]) === "object") {
46
89
  const allowUnknownKeywords = schema[vocabularyToken]["https://json-schema.org/draft/2019-09/vocab/core"]
47
90
  || schema[vocabularyToken]["https://json-schema.org/draft/2020-12/vocab/core"];
48
91
 
@@ -50,283 +93,242 @@ export const add = (schema, retrievalUri = undefined, contextDialectId = undefin
50
93
  delete schema[vocabularyToken];
51
94
  }
52
95
 
96
+ const anchors = { "": "" };
53
97
  const dynamicAnchors = {};
54
98
 
55
- // Recursive anchor
99
+ // Legacy Recursive anchor
56
100
  const recursiveAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2019-09/recursiveAnchor");
57
101
  if (schema[recursiveAnchorToken] === true) {
58
102
  dynamicAnchors[""] = `${id}#`;
59
103
  }
60
104
  delete schema[recursiveAnchorToken];
61
105
 
62
- // Store Schema
63
- const anchors = { "": "" };
64
- schemaStore[id] = {
65
- id: id,
106
+ embedded[id] = {
107
+ baseUri: id,
66
108
  dialectId: dialectId,
67
- schema: processSchema(schema, id, dialectId, nilPointer, anchors, dynamicAnchors),
109
+ root: processSchema(schema, id, dialectId, "", embedded, anchors, dynamicAnchors),
110
+ anchorLocation: (fragment) => {
111
+ if (fragment === undefined) {
112
+ return "";
113
+ }
114
+
115
+ fragment = decodeURI(fragment);
116
+ if (fragment[0] === "/") {
117
+ return fragment;
118
+ } else if (!(fragment in anchors)) {
119
+ throw Error(`No such anchor '${id}#${encodeURI(fragment)}'`);
120
+ } else {
121
+ return anchors[fragment];
122
+ }
123
+ },
68
124
  anchors: anchors,
69
125
  dynamicAnchors: dynamicAnchors,
70
- validated: false
126
+ embedded: embedded
71
127
  };
72
128
 
73
- return id;
129
+ return embedded[id];
74
130
  };
75
131
 
76
- const processSchema = (subject, id, dialectId, pointer, anchors, dynamicAnchors) => {
77
- if (jsonTypeOf(subject, "object")) {
132
+ const processSchema = (json, id, dialectId, cursor, embedded, anchors, dynamicAnchors) => {
133
+ if (jsonTypeOf(json) === "object") {
78
134
  // Embedded Schema
79
- const embeddedDialectId = typeof subject.$schema === "string" ? toAbsoluteIri(subject.$schema) : dialectId;
80
- if (!hasDialect(embeddedDialectId)) {
81
- throw Error(`Encountered unknown dialect '${embeddedDialectId}'`);
82
- }
83
-
135
+ const embeddedDialectId = typeof json.$schema === "string" ? toAbsoluteIri(json.$schema) : dialectId;
84
136
  const idToken = getKeywordName(embeddedDialectId, "https://json-schema.org/keyword/id");
85
- if (typeof subject[idToken] === "string") {
86
- subject[idToken] = resolveUri(subject[idToken], id);
87
- add(subject, undefined, dialectId);
88
- return Reference.cons(subject[idToken], subject);
137
+
138
+ if (typeof json[idToken] === "string") {
139
+ const embeddedId = toAbsoluteIri(resolveIri(json[idToken], id));
140
+ json[idToken] = embeddedId;
141
+ embedded[embeddedId] = buildSchemaDocument(json, embeddedId, embeddedDialectId, embedded);
142
+ return new Reference(embeddedId, {});
89
143
  }
90
144
 
91
145
  // Legacy id
92
146
  const legacyIdToken = getKeywordName(embeddedDialectId, "https://json-schema.org/keyword/draft-04/id");
93
- if (typeof subject[legacyIdToken] === "string") {
94
- if (subject[legacyIdToken][0] === "#") {
95
- const anchor = decodeURIComponent(subject[legacyIdToken].slice(1));
96
- anchors[anchor] = pointer;
147
+ if (typeof json[legacyIdToken] === "string") {
148
+ if (json[legacyIdToken][0] === "#") {
149
+ const anchor = decodeURIComponent(json[legacyIdToken].slice(1));
150
+ anchors[anchor] = cursor;
151
+ delete json[legacyIdToken];
97
152
  } else {
98
- delete subject[legacyIdToken].$schema;
99
- subject[legacyIdToken] = resolveUri(subject[legacyIdToken], id);
100
- add(subject, undefined, dialectId);
101
- return Reference.cons(subject[legacyIdToken], subject);
153
+ const embeddedId = toAbsoluteIri(resolveIri(json[legacyIdToken], id));
154
+ json[legacyIdToken] = embeddedId;
155
+ embedded[embeddedId] = buildSchemaDocument(json, embeddedId, embeddedDialectId, embedded);
156
+ return new Reference(embeddedId, {});
102
157
  }
103
- delete subject[legacyIdToken];
104
- }
105
-
106
- const dynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/dynamicAnchor");
107
- if (typeof subject[dynamicAnchorToken] === "string") {
108
- dynamicAnchors[subject[dynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
109
- delete subject[dynamicAnchorToken];
110
- }
111
-
112
- // Legacy dynamic anchor
113
- const legacyDynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
114
- if (typeof subject[legacyDynamicAnchorToken] === "string") {
115
- dynamicAnchors[subject[legacyDynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
116
- anchors[subject[legacyDynamicAnchorToken]] = pointer;
117
- delete subject[legacyDynamicAnchorToken];
118
- }
119
-
120
- const anchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/anchor");
121
- if (typeof subject[anchorToken] === "string") {
122
- anchors[subject[anchorToken]] = pointer;
123
- delete subject[anchorToken];
124
158
  }
125
159
 
126
160
  // Legacy $ref
127
161
  const jrefToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/ref");
128
- if (typeof subject[jrefToken] === "string") {
129
- return Reference.cons(subject[jrefToken], subject);
162
+ if (typeof json[jrefToken] === "string") {
163
+ return new Reference(json[jrefToken], json);
130
164
  }
131
165
 
132
- for (const key in subject) {
133
- subject[key] = processSchema(subject[key], id, dialectId, pointerAppend(key, pointer), anchors, dynamicAnchors);
134
- }
135
- } else if (Array.isArray(subject)) {
136
- for (let index = 0; index < subject.length; index++) {
137
- subject[index] = processSchema(subject[index], id, dialectId, pointerAppend(index, pointer), anchors, dynamicAnchors);
166
+ // Anchors
167
+ const anchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/anchor");
168
+ if (typeof json[anchorToken] === "string") {
169
+ anchors[json[anchorToken]] = cursor;
170
+ delete json[anchorToken];
138
171
  }
139
- }
140
-
141
- return subject;
142
- };
143
-
144
- const hasStoredSchema = (id) => id in schemaStore || id in schemaStoreAlias;
145
- const getStoredSchema = (id) => schemaStore[schemaStoreAlias[id]] || schemaStore[id];
146
-
147
- export const markValidated = (id) => {
148
- schemaStore[id].validated = true;
149
- };
150
-
151
- // Schema Retrieval
152
- const nil = {
153
- id: undefined,
154
- dialectId: undefined,
155
- pointer: nilPointer,
156
- schema: undefined,
157
- value: undefined,
158
- anchors: {},
159
- dynamicAnchors: {},
160
- validated: true
161
- };
162
-
163
- export const get = async (url, contextDoc = nil) => {
164
- const resolvedUrl = resolveUri(url, contextDoc.id || contextUri());
165
- const id = toAbsoluteIri(resolvedUrl);
166
- const fragment = uriFragment(resolvedUrl);
167
172
 
168
- if (!hasStoredSchema(id)) {
169
- const response = await fetch(id, { headers: { Accept: acceptableMediaTypes() } });
170
- if (response.status >= 400) {
171
- await response.text(); // Sometimes node hangs without this hack
172
- throw Error(`Failed to retrieve ${id} (${response.status} ${response.statusText})`);
173
+ // Dynamic Anchors
174
+ const dynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/dynamicAnchor");
175
+ if (typeof json[dynamicAnchorToken] === "string") {
176
+ dynamicAnchors[json[dynamicAnchorToken]] = `${id}#${encodeURI(cursor)}`;
177
+ delete json[dynamicAnchorToken];
173
178
  }
174
179
 
175
- const [schema, contextDialectId] = await parseResponse(response);
180
+ // Legacy dynamic anchor
181
+ const legacyDynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
182
+ if (typeof json[legacyDynamicAnchorToken] === "string") {
183
+ dynamicAnchors[json[legacyDynamicAnchorToken]] = `${id}#${encodeURI(cursor)}`;
184
+ anchors[json[legacyDynamicAnchorToken]] = cursor;
185
+ delete json[legacyDynamicAnchorToken];
186
+ }
176
187
 
177
- // Try to determine the dialect from the meta-schema if it isn't already known
178
- if (schema.$schema || contextDialectId) {
179
- const dialectId = toAbsoluteIri(schema.$schema || contextDialectId);
180
- if (!hasDialect(dialectId) && !hasStoredSchema(dialectId)) {
181
- await get(dialectId);
188
+ for (const key in json) {
189
+ // References
190
+ const referenceToken = getKeywordName(dialectId, "https://json-schema.org/keyword/ref");
191
+ if (key === referenceToken && typeof json[key] === "string") {
192
+ json[key] = new Reference(json[key], json[key]);
193
+ } else {
194
+ json[key] = processSchema(json[key], id, dialectId, append(key, cursor), embedded, anchors, dynamicAnchors);
182
195
  }
183
196
  }
184
-
185
- add(schema, id, contextDialectId);
186
- }
187
-
188
- const storedSchema = getStoredSchema(id);
189
- const pointer = fragment[0] === "/" ? fragment : getAnchorPointer(storedSchema, fragment);
190
- const doc = {
191
- ...storedSchema,
192
- pointer: pointer,
193
- value: pointerGet(pointer, storedSchema.schema)
194
- };
195
-
196
- return followReferences(doc);
197
- };
198
-
199
- const followReferences = (doc) => Reference.isReference(doc.value) ? get(Reference.href(doc.value), doc) : doc;
200
-
201
- const getAnchorPointer = (schema, fragment) => {
202
- if (!(fragment in schema.anchors)) {
203
- throw Error(`No such anchor '${encodeURI(schema.id)}#${encodeURI(fragment)}'`);
204
- }
205
-
206
- return schema.anchors[fragment];
207
- };
208
-
209
- // Utility Functions
210
- export const uri = (doc) => doc.id ? `${doc.id}#${encodeURI(doc.pointer)}` : undefined;
211
- export const value = (doc) => doc.value;
212
- export const has = (key, doc) => key in value(doc);
213
- export const typeOf = (doc, type) => jsonTypeOf(value(doc), type);
214
-
215
- export const step = (key, doc) => {
216
- const storedSchema = getStoredSchema(doc.id);
217
- return followReferences({
218
- ...doc,
219
- pointer: pointerAppend(`${key}`, doc.pointer),
220
- value: value(doc)[key],
221
- validated: storedSchema.validated
222
- });
223
- };
224
-
225
- export const iter = async function* (doc) {
226
- for (let index = 0; index < value(doc).length; index++) {
227
- yield step(index, doc);
228
- }
229
- };
230
-
231
- export const keys = function* (doc) {
232
- for (const key in value(doc)) {
233
- yield key;
234
- }
235
- };
236
-
237
- export const values = async function* (doc) {
238
- for (const key in value(doc)) {
239
- yield step(key, doc);
240
- }
241
- };
242
-
243
- export const entries = async function* (doc) {
244
- for (const key in value(doc)) {
245
- yield [key, await step(key, doc)];
197
+ } else if (Array.isArray(json)) {
198
+ for (let index = 0; index < json.length; index++) {
199
+ json[index] = processSchema(json[index], id, dialectId, append(index, cursor), embedded, anchors, dynamicAnchors);
200
+ }
246
201
  }
247
- };
248
-
249
- export const length = (doc) => value(doc).length;
250
202
 
251
- const toSchemaDefaultOptions = {
252
- parentId: "",
253
- parentDialect: "",
254
- includeEmbedded: true
203
+ return json;
255
204
  };
256
- export const toSchema = (schemaDoc, options = {}) => {
257
- const fullOptions = { ...toSchemaDefaultOptions, ...options };
258
205
 
259
- const idToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/id")
260
- || getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-04/id");
261
- const anchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/anchor");
262
- const legacyAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-04/id");
263
- const dynamicAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/dynamicAnchor");
264
- const legacyDynamicAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
265
- const recursiveAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/recursiveAnchor");
206
+ export const canonicalUri = (browser) => `${browser.document.baseUri}#${encodeURI(browser.cursor)}`;
266
207
 
208
+ export const toSchema = (browser, options = {}) => {
267
209
  const anchors = {};
268
- for (const anchor in schemaDoc.anchors) {
269
- if (anchor !== "" && !schemaDoc.dynamicAnchors[anchor]) {
270
- anchors[schemaDoc.anchors[anchor]] = anchor;
210
+ for (const anchor in browser.document.anchors) {
211
+ if (anchor !== "" && !browser.document.dynamicAnchors[anchor]) {
212
+ anchors[browser.document.anchors[anchor]] = anchor;
271
213
  }
272
214
  }
273
215
 
274
216
  const dynamicAnchors = {};
275
- for (const anchor in schemaDoc.dynamicAnchors) {
276
- const pointer = uriFragment(schemaDoc.dynamicAnchors[anchor]);
217
+ for (const anchor in browser.document.dynamicAnchors) {
218
+ const pointer = uriFragment(browser.document.dynamicAnchors[anchor]);
277
219
  dynamicAnchors[pointer] = anchor;
278
220
  }
279
221
 
280
- const schema = JSON.parse(jsonStringify(schemaDoc.schema, (_key, value, pointer) => {
281
- if (Reference.isReference(value)) {
282
- const refValue = Reference.value(value);
283
- if (fullOptions.includeEmbedded || !(idToken in refValue)) {
284
- return Reference.value(value);
222
+ const legacyIdToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-04/id");
223
+ const idToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/id") || legacyIdToken;
224
+ const anchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/anchor");
225
+ const legacyAnchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-04/id");
226
+ const dynamicAnchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/dynamicAnchor");
227
+ const legacyDynamicAnchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
228
+ const recursiveAnchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-2019-09/recursiveAnchor");
229
+ const refToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/ref");
230
+ const legacyRefToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-04/ref");
231
+
232
+ let schema = JSON.parse(jsonStringify(browser.document.root, (key, value, pointer) => {
233
+ if (value instanceof Reference) {
234
+ if (key === refToken) {
235
+ return value.href;
236
+ } else if (legacyIdToken) {
237
+ if (JSON.stringify(value.toJSON()) === "{}") {
238
+ return toSchema({ document: browser.document.embedded[toAbsoluteIri(value.href)] }, {
239
+ ...options,
240
+ contextDialectId: browser.document.dialectId,
241
+ selfIdentify: true,
242
+ contextUri: browser.document.baseUri
243
+ });
244
+ } else {
245
+ return { [legacyRefToken]: value.href };
246
+ }
247
+ } else if (options.includeEmbedded ?? true) {
248
+ return toSchema({ document: browser.document.embedded[toAbsoluteIri(value.href)] }, {
249
+ ...options,
250
+ contextDialectId: browser.document.dialectId,
251
+ selfIdentify: true,
252
+ contextUri: browser.document.baseUri
253
+ });
254
+ } else {
255
+ return;
285
256
  }
286
- } else {
257
+ }
258
+
259
+ if (jsonTypeOf(value) === "object") {
260
+ value = { ...value };
287
261
  if (pointer in anchors) {
288
262
  if (anchorToken) {
289
- value = { [anchorToken]: anchors[pointer], ...value };
263
+ value[anchorToken] = anchors[pointer];
290
264
  }
291
265
 
292
266
  // Legacy anchor
293
267
  if (legacyAnchorToken) {
294
- value = { [legacyAnchorToken]: `#${anchors[pointer]}`, ...value };
268
+ value[legacyAnchorToken] = `#${anchors[pointer]}`;
295
269
  }
296
270
  }
271
+
297
272
  if (pointer in dynamicAnchors) {
298
273
  if (dynamicAnchorToken) {
299
- value = { [dynamicAnchorToken]: dynamicAnchors[pointer], ...value };
274
+ value[dynamicAnchorToken] = dynamicAnchors[pointer];
300
275
  }
301
276
 
302
277
  // Legacy dynamic anchor
303
278
  if (legacyDynamicAnchorToken) {
304
- value = { [legacyDynamicAnchorToken]: dynamicAnchors[pointer], ...value };
279
+ value[legacyDynamicAnchorToken] = dynamicAnchors[pointer];
305
280
  }
306
281
 
307
282
  // Recursive anchor
308
283
  if (recursiveAnchorToken) {
309
- value = { [recursiveAnchorToken]: true, ...value };
284
+ value[recursiveAnchorToken] = true;
310
285
  }
311
286
  }
312
- return value;
313
287
  }
288
+
289
+ return value;
314
290
  }));
315
291
 
316
- const id = relativeUri(fullOptions.parentId, schemaDoc.id);
317
- const dialect = fullOptions.parentDialect === schemaDoc.dialectId ? "" : schemaDoc.dialectId;
318
- return {
319
- ...id && { [idToken]: id },
320
- ...dialect && { $schema: dialect },
321
- ...schema
322
- };
323
- };
292
+ const definitionsToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/definitions");
293
+ if (Reflect.ownKeys(schema[definitionsToken] ?? {}).length === 0) {
294
+ delete schema[definitionsToken];
295
+ }
324
296
 
325
- const relativeUri = (from, to) => {
326
- if (to.startsWith("file://")) {
327
- const pathToSchema = from.slice(7, from.lastIndexOf("/"));
328
- return from === "" ? "" : pathRelative(pathToSchema, to.slice(7));
329
- } else {
330
- return to;
297
+ // Self-identification
298
+ if (options.selfIdentify ?? false) {
299
+ if (options.contextUri) {
300
+ schema = {
301
+ [idToken]: toRelativeIri(normalizeIri(options.contextUri), browser.document.baseUri),
302
+ ...schema
303
+ };
304
+ } else {
305
+ schema = {
306
+ [idToken]: browser.document.baseUri,
307
+ ...schema
308
+ };
309
+ }
310
+ }
311
+
312
+ // $schema
313
+ switch (options.includeDialect ?? "auto") {
314
+ case "auto":
315
+ if (browser.document.dialectId === options.contextDialectId) {
316
+ break;
317
+ }
318
+ case "always":
319
+ schema = {
320
+ $schema: browser.document.dialectId,
321
+ ...schema
322
+ };
323
+ if (legacyIdToken) {
324
+ schema.$schema += "#";
325
+ }
326
+ break;
327
+ case "never":
328
+ break;
329
+ default:
330
+ throw Error(`Unsupported value ToSchemaOptions.includeDialect: '${options.includeDialect}'`);
331
331
  }
332
+
333
+ return schema;
332
334
  };
@@ -1,9 +1,9 @@
1
1
  import { addKeyword, defineVocabulary, loadDialect } from "../lib/keywords.js";
2
- import { addSchema } from "../lib/core.js";
2
+ import { registerSchema } from "../lib/index.js";
3
3
  import "../lib/openapi.js";
4
4
 
5
5
  import dialectSchema from "./dialect.js";
6
- import schema20210928 from "./schema/2021-09-28.js";
6
+ import schema from "./schema.js";
7
7
 
8
8
  import discriminator from "./discriminator.js";
9
9
  import example from "./example.js";
@@ -69,9 +69,9 @@ loadDialect("https://spec.openapis.org/oas/3.0/schema", {
69
69
  [jsonSchemaVersion]: true
70
70
  });
71
71
 
72
- addSchema(dialectSchema);
72
+ registerSchema(dialectSchema);
73
73
 
74
- addSchema(schema20210928, "https://spec.openapis.org/oas/3.0/schema");
75
- addSchema(schema20210928, "https://spec.openapis.org/oas/3.0/schema/latest");
74
+ registerSchema(schema, "https://spec.openapis.org/oas/3.0/schema");
75
+ registerSchema(schema, "https://spec.openapis.org/oas/3.0/schema/latest");
76
76
 
77
77
  export * from "../draft-04/index.js";
@@ -1,16 +1,22 @@
1
- import { getKeywordName } from "../lib/keywords.js";
2
- import * as Schema from "../lib/schema.js";
1
+ import * as Browser from "@hyperjump/browser";
3
2
  import * as Instance from "../lib/instance.js";
3
+ import { getKeywordName } from "../lib/experimental.js";
4
4
 
5
5
 
6
6
  const id = "https://spec.openapis.org/oas/3.0/keyword/type";
7
7
 
8
- const compile = async (schema, ast, parentSchema) => {
9
- const nullableKeyword = getKeywordName(schema.dialectId, "https://spec.openapis.org/oas/3.0/keyword/nullable");
10
- const nullable = await Schema.step(nullableKeyword, parentSchema);
11
- return Schema.value(nullable) === true ? ["null", Schema.value(schema)] : Schema.value(schema);
8
+ const compile = async (schema, _ast, parentSchema) => {
9
+ const nullableKeyword = getKeywordName(schema.document.dialectId, "https://spec.openapis.org/oas/3.0/keyword/nullable");
10
+ const nullable = await Browser.step(nullableKeyword, parentSchema);
11
+ return Browser.value(nullable) === true ? ["null", Browser.value(schema)] : Browser.value(schema);
12
12
  };
13
13
 
14
- const interpret = (type, instance) => typeof type === "string" ? Instance.typeOf(instance, type) : type.some(Instance.typeOf(instance));
14
+ const interpret = (type, instance) => typeof type === "string"
15
+ ? isTypeOf(instance)(type)
16
+ : type.some(isTypeOf(instance));
17
+
18
+ const isTypeOf = (instance) => (type) => type === "integer"
19
+ ? Instance.typeOf(instance) === "number" && Number.isInteger(Instance.value(instance))
20
+ : Instance.typeOf(instance) === type;
15
21
 
16
22
  export default { id, compile, interpret };
@@ -244,7 +244,7 @@ type Header = {
244
244
  description?: string;
245
245
  required?: boolean;
246
246
  deprecated?: boolean;
247
- schema?: OpenApi31;
247
+ schema?: OasSchema31;
248
248
  style?: "simple";
249
249
  explode?: boolean;
250
250
  content?: Content;