@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.
- package/README.md +247 -255
- package/annotations/annotated-instance.js +3 -3
- package/annotations/index.d.ts +7 -1
- package/annotations/index.js +3 -3
- package/bundle/index.d.ts +1 -5
- package/bundle/index.js +125 -121
- package/draft-04/additionalItems.js +6 -7
- package/draft-04/dependencies.js +5 -5
- package/draft-04/index.js +2 -2
- package/draft-04/items.js +5 -5
- package/draft-04/maximum.js +8 -8
- package/draft-04/minimum.js +8 -8
- package/draft-06/contains.js +2 -2
- package/draft-06/index.js +3 -2
- package/draft-07/index.js +3 -2
- package/draft-2019-09/index.js +9 -11
- package/draft-2020-12/dynamicRef.js +5 -5
- package/draft-2020-12/index.js +11 -13
- package/lib/common.d.ts +1 -1
- package/lib/common.js +44 -60
- package/lib/configuration.js +0 -6
- package/lib/core.js +32 -30
- package/lib/experimental.d.ts +75 -5
- package/lib/experimental.js +2 -2
- package/lib/index.d.ts +43 -11
- package/lib/index.js +11 -11
- package/lib/instance.d.ts +1 -17
- package/lib/instance.js +3 -3
- package/lib/keywords/additionalProperties.js +12 -13
- package/lib/keywords/allOf.js +3 -3
- package/lib/keywords/anyOf.js +3 -3
- package/lib/keywords/conditional.js +6 -7
- package/lib/keywords/const.js +2 -2
- package/lib/keywords/contains.js +14 -35
- package/lib/keywords/contentSchema.js +1 -1
- package/lib/keywords/definitions.js +2 -2
- package/lib/keywords/dependentRequired.js +4 -4
- package/lib/keywords/dependentSchemas.js +5 -5
- package/lib/keywords/dynamicRef.js +10 -5
- package/lib/keywords/else.js +5 -6
- package/lib/keywords/enum.js +4 -4
- package/lib/keywords/exclusiveMaximum.js +3 -3
- package/lib/keywords/exclusiveMinimum.js +3 -3
- package/lib/keywords/if.js +1 -1
- package/lib/keywords/itemPattern.js +17 -14
- package/lib/keywords/items.js +6 -7
- package/lib/keywords/maxItems.js +3 -3
- package/lib/keywords/maxLength.js +3 -3
- package/lib/keywords/maxProperties.js +3 -3
- package/lib/keywords/maximum.js +3 -3
- package/lib/keywords/meta-data.js +1 -1
- package/lib/keywords/minItems.js +3 -3
- package/lib/keywords/minLength.js +3 -3
- package/lib/keywords/minProperties.js +3 -3
- package/lib/keywords/minimum.js +3 -3
- package/lib/keywords/multipleOf.js +3 -3
- package/lib/keywords/not.js +1 -1
- package/lib/keywords/oneOf.js +3 -3
- package/lib/keywords/pattern.js +3 -3
- package/lib/keywords/patternProperties.js +5 -5
- package/lib/keywords/prefixItems.js +5 -5
- package/lib/keywords/properties.js +5 -5
- package/lib/keywords/propertyDependencies.js +6 -7
- package/lib/keywords/propertyNames.js +2 -2
- package/lib/keywords/ref.js +2 -7
- package/lib/keywords/requireAllExcept.js +8 -9
- package/lib/keywords/required.js +3 -3
- package/lib/keywords/then.js +5 -5
- package/lib/keywords/type.js +9 -3
- package/lib/keywords/unevaluatedItems.js +4 -4
- package/lib/keywords/unevaluatedProperties.js +4 -5
- package/lib/keywords/uniqueItems.js +3 -3
- package/lib/keywords/validation.js +11 -23
- package/lib/keywords.js +30 -4
- package/lib/openapi.js +19 -6
- package/lib/schema.js +235 -233
- package/openapi-3-0/index.js +5 -5
- package/openapi-3-0/type.js +13 -7
- package/openapi-3-1/index.d.ts +1 -1
- package/openapi-3-1/index.js +22 -21
- package/openapi-3-1/{schema-base/2022-10-07.js → schema-base.js} +12 -2
- package/openapi-3-1/schema-draft-04.js +33 -0
- package/openapi-3-1/schema-draft-06.js +33 -0
- package/openapi-3-1/schema-draft-07.js +33 -0
- package/openapi-3-1/schema-draft-2019-09.js +33 -0
- package/openapi-3-1/schema-draft-2020-12.js +33 -0
- package/package.json +14 -16
- package/stable/index.js +10 -10
- package/annotations/tests/applicators.json +0 -375
- package/annotations/tests/content.json +0 -57
- package/annotations/tests/core.json +0 -33
- package/annotations/tests/format.json +0 -20
- package/annotations/tests/meta-data.json +0 -128
- package/annotations/tests/unevaluated.json +0 -557
- package/annotations/tests/unknown.json +0 -87
- package/annotations/tests/validation.json +0 -328
- package/annotations/validation-error.d.ts +0 -8
- package/bundle/file.json +0 -57
- package/draft-2019-09/contains.js +0 -44
- package/lib/configuration.d.ts +0 -9
- package/lib/context-uri.browser.js +0 -1
- package/lib/context-uri.js +0 -4
- package/lib/core.d.ts +0 -48
- package/lib/fetch.browser.js +0 -1
- package/lib/fetch.js +0 -20
- package/lib/invalid-schema-error.d.ts +0 -8
- package/lib/keywords.d.ts +0 -19
- package/lib/media-types.d.ts +0 -11
- package/lib/media-types.js +0 -48
- package/lib/reference.d.ts +0 -11
- package/lib/reference.js +0 -11
- package/lib/schema.d.ts +0 -60
- /package/openapi-3-0/{schema/2021-09-28.js → schema.js} +0 -0
- /package/openapi-3-1/{schema/2022-10-07.js → schema.js} +0 -0
package/lib/schema.js
CHANGED
|
@@ -1,48 +1,91 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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 (
|
|
20
|
-
|
|
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 (!
|
|
26
|
-
throw Error(
|
|
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
|
|
31
|
-
|
|
32
|
-
if (
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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]
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
schemaStore[id] = {
|
|
65
|
-
id: id,
|
|
106
|
+
embedded[id] = {
|
|
107
|
+
baseUri: id,
|
|
66
108
|
dialectId: dialectId,
|
|
67
|
-
|
|
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
|
-
|
|
126
|
+
embedded: embedded
|
|
71
127
|
};
|
|
72
128
|
|
|
73
|
-
return id;
|
|
129
|
+
return embedded[id];
|
|
74
130
|
};
|
|
75
131
|
|
|
76
|
-
const processSchema = (
|
|
77
|
-
if (jsonTypeOf(
|
|
132
|
+
const processSchema = (json, id, dialectId, cursor, embedded, anchors, dynamicAnchors) => {
|
|
133
|
+
if (jsonTypeOf(json) === "object") {
|
|
78
134
|
// Embedded Schema
|
|
79
|
-
const embeddedDialectId = typeof
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
94
|
-
if (
|
|
95
|
-
const anchor = decodeURIComponent(
|
|
96
|
-
anchors[anchor] =
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return Reference
|
|
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
|
|
129
|
-
return Reference
|
|
162
|
+
if (typeof json[jrefToken] === "string") {
|
|
163
|
+
return new Reference(json[jrefToken], json);
|
|
130
164
|
}
|
|
131
165
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
if (
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
269
|
-
if (anchor !== "" && !
|
|
270
|
-
anchors[
|
|
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
|
|
276
|
-
const pointer = uriFragment(
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (jsonTypeOf(value) === "object") {
|
|
260
|
+
value = { ...value };
|
|
287
261
|
if (pointer in anchors) {
|
|
288
262
|
if (anchorToken) {
|
|
289
|
-
value
|
|
263
|
+
value[anchorToken] = anchors[pointer];
|
|
290
264
|
}
|
|
291
265
|
|
|
292
266
|
// Legacy anchor
|
|
293
267
|
if (legacyAnchorToken) {
|
|
294
|
-
value
|
|
268
|
+
value[legacyAnchorToken] = `#${anchors[pointer]}`;
|
|
295
269
|
}
|
|
296
270
|
}
|
|
271
|
+
|
|
297
272
|
if (pointer in dynamicAnchors) {
|
|
298
273
|
if (dynamicAnchorToken) {
|
|
299
|
-
value
|
|
274
|
+
value[dynamicAnchorToken] = dynamicAnchors[pointer];
|
|
300
275
|
}
|
|
301
276
|
|
|
302
277
|
// Legacy dynamic anchor
|
|
303
278
|
if (legacyDynamicAnchorToken) {
|
|
304
|
-
value
|
|
279
|
+
value[legacyDynamicAnchorToken] = dynamicAnchors[pointer];
|
|
305
280
|
}
|
|
306
281
|
|
|
307
282
|
// Recursive anchor
|
|
308
283
|
if (recursiveAnchorToken) {
|
|
309
|
-
value
|
|
284
|
+
value[recursiveAnchorToken] = true;
|
|
310
285
|
}
|
|
311
286
|
}
|
|
312
|
-
return value;
|
|
313
287
|
}
|
|
288
|
+
|
|
289
|
+
return value;
|
|
314
290
|
}));
|
|
315
291
|
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
};
|
package/openapi-3-0/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { addKeyword, defineVocabulary, loadDialect } from "../lib/keywords.js";
|
|
2
|
-
import {
|
|
2
|
+
import { registerSchema } from "../lib/index.js";
|
|
3
3
|
import "../lib/openapi.js";
|
|
4
4
|
|
|
5
5
|
import dialectSchema from "./dialect.js";
|
|
6
|
-
import
|
|
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
|
-
|
|
72
|
+
registerSchema(dialectSchema);
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
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";
|
package/openapi-3-0/type.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import
|
|
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,
|
|
9
|
-
const nullableKeyword = getKeywordName(schema.dialectId, "https://spec.openapis.org/oas/3.0/keyword/nullable");
|
|
10
|
-
const nullable = await
|
|
11
|
-
return
|
|
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"
|
|
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 };
|