@hyperjump/json-schema 1.6.7 → 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 +112 -156
- 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 +27 -14
- package/lib/openapi.js +19 -6
- package/lib/schema.js +236 -227
- package/openapi-3-0/index.js +5 -5
- package/openapi-3-0/type.js +13 -7
- 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 +11 -11
- package/stable/index.js +10 -10
- package/annotations/validation-error.d.ts +0 -8
- 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,44 +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 (
|
|
62
|
+
if (typeof schema.$schema === "string") {
|
|
63
|
+
dialectId = schema.$schema;
|
|
64
|
+
delete schema.$schema;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!dialectId) {
|
|
20
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.");
|
|
21
69
|
}
|
|
22
|
-
|
|
23
|
-
delete schema.$schema;
|
|
70
|
+
dialectId = toAbsoluteIri(dialectId);
|
|
24
71
|
|
|
25
72
|
// Identifiers
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
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) {
|
|
29
76
|
throw Error(`Unable to determine an identifier for the schema. Use the '${idToken}' keyword or pass a retrievalUri when loading the schema.`);
|
|
30
77
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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];
|
|
37
84
|
}
|
|
38
85
|
|
|
39
86
|
// Vocabulary
|
|
40
87
|
const vocabularyToken = getKeywordName(dialectId, "https://json-schema.org/keyword/vocabulary");
|
|
41
|
-
if (jsonTypeOf(schema[vocabularyToken]
|
|
88
|
+
if (jsonTypeOf(schema[vocabularyToken]) === "object") {
|
|
42
89
|
const allowUnknownKeywords = schema[vocabularyToken]["https://json-schema.org/draft/2019-09/vocab/core"]
|
|
43
90
|
|| schema[vocabularyToken]["https://json-schema.org/draft/2020-12/vocab/core"];
|
|
44
91
|
|
|
@@ -46,280 +93,242 @@ export const add = (schema, retrievalUri = undefined, contextDialectId = undefin
|
|
|
46
93
|
delete schema[vocabularyToken];
|
|
47
94
|
}
|
|
48
95
|
|
|
96
|
+
const anchors = { "": "" };
|
|
49
97
|
const dynamicAnchors = {};
|
|
50
98
|
|
|
51
|
-
// Recursive anchor
|
|
99
|
+
// Legacy Recursive anchor
|
|
52
100
|
const recursiveAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2019-09/recursiveAnchor");
|
|
53
101
|
if (schema[recursiveAnchorToken] === true) {
|
|
54
102
|
dynamicAnchors[""] = `${id}#`;
|
|
55
103
|
}
|
|
56
104
|
delete schema[recursiveAnchorToken];
|
|
57
105
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
schemaStore[id] = {
|
|
61
|
-
id: id,
|
|
106
|
+
embedded[id] = {
|
|
107
|
+
baseUri: id,
|
|
62
108
|
dialectId: dialectId,
|
|
63
|
-
|
|
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
|
+
},
|
|
64
124
|
anchors: anchors,
|
|
65
125
|
dynamicAnchors: dynamicAnchors,
|
|
66
|
-
|
|
126
|
+
embedded: embedded
|
|
67
127
|
};
|
|
68
128
|
|
|
69
|
-
return id;
|
|
129
|
+
return embedded[id];
|
|
70
130
|
};
|
|
71
131
|
|
|
72
|
-
const processSchema = (
|
|
73
|
-
if (jsonTypeOf(
|
|
132
|
+
const processSchema = (json, id, dialectId, cursor, embedded, anchors, dynamicAnchors) => {
|
|
133
|
+
if (jsonTypeOf(json) === "object") {
|
|
74
134
|
// Embedded Schema
|
|
75
|
-
const embeddedDialectId = typeof
|
|
76
|
-
|
|
135
|
+
const embeddedDialectId = typeof json.$schema === "string" ? toAbsoluteIri(json.$schema) : dialectId;
|
|
77
136
|
const idToken = getKeywordName(embeddedDialectId, "https://json-schema.org/keyword/id");
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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, {});
|
|
82
143
|
}
|
|
83
144
|
|
|
84
145
|
// Legacy id
|
|
85
146
|
const legacyIdToken = getKeywordName(embeddedDialectId, "https://json-schema.org/keyword/draft-04/id");
|
|
86
|
-
if (typeof
|
|
87
|
-
if (
|
|
88
|
-
const anchor = decodeURIComponent(
|
|
89
|
-
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];
|
|
90
152
|
} else {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
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, {});
|
|
95
157
|
}
|
|
96
|
-
delete subject[legacyIdToken];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const dynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/dynamicAnchor");
|
|
100
|
-
if (typeof subject[dynamicAnchorToken] === "string") {
|
|
101
|
-
dynamicAnchors[subject[dynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
|
|
102
|
-
delete subject[dynamicAnchorToken];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Legacy dynamic anchor
|
|
106
|
-
const legacyDynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
|
|
107
|
-
if (typeof subject[legacyDynamicAnchorToken] === "string") {
|
|
108
|
-
dynamicAnchors[subject[legacyDynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
|
|
109
|
-
anchors[subject[legacyDynamicAnchorToken]] = pointer;
|
|
110
|
-
delete subject[legacyDynamicAnchorToken];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const anchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/anchor");
|
|
114
|
-
if (typeof subject[anchorToken] === "string") {
|
|
115
|
-
anchors[subject[anchorToken]] = pointer;
|
|
116
|
-
delete subject[anchorToken];
|
|
117
158
|
}
|
|
118
159
|
|
|
119
160
|
// Legacy $ref
|
|
120
161
|
const jrefToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/ref");
|
|
121
|
-
if (typeof
|
|
122
|
-
return Reference
|
|
162
|
+
if (typeof json[jrefToken] === "string") {
|
|
163
|
+
return new Reference(json[jrefToken], json);
|
|
123
164
|
}
|
|
124
165
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
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];
|
|
131
171
|
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return subject;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const hasStoredSchema = (id) => id in schemaStore || id in schemaStoreAlias;
|
|
138
|
-
const getStoredSchema = (id) => schemaStore[schemaStoreAlias[id]] || schemaStore[id];
|
|
139
|
-
|
|
140
|
-
export const markValidated = (id) => {
|
|
141
|
-
schemaStore[id].validated = true;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// Schema Retrieval
|
|
145
|
-
const nil = {
|
|
146
|
-
id: undefined,
|
|
147
|
-
dialectId: undefined,
|
|
148
|
-
pointer: nilPointer,
|
|
149
|
-
schema: undefined,
|
|
150
|
-
value: undefined,
|
|
151
|
-
anchors: {},
|
|
152
|
-
dynamicAnchors: {},
|
|
153
|
-
validated: true
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
export const get = async (url, contextDoc = nil) => {
|
|
157
|
-
const resolvedUrl = resolveUri(url, contextDoc.id || contextUri());
|
|
158
|
-
const id = toAbsoluteIri(resolvedUrl);
|
|
159
|
-
const fragment = uriFragment(resolvedUrl);
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
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];
|
|
166
178
|
}
|
|
167
179
|
|
|
168
|
-
|
|
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
|
+
}
|
|
169
187
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
if (
|
|
174
|
-
|
|
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);
|
|
175
195
|
}
|
|
176
196
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const storedSchema = getStoredSchema(id);
|
|
182
|
-
const pointer = fragment[0] === "/" ? fragment : getAnchorPointer(storedSchema, fragment);
|
|
183
|
-
const doc = {
|
|
184
|
-
...storedSchema,
|
|
185
|
-
pointer: pointer,
|
|
186
|
-
value: pointerGet(pointer, storedSchema.schema)
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
return followReferences(doc);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const followReferences = (doc) => Reference.isReference(doc.value) ? get(Reference.href(doc.value), doc) : doc;
|
|
193
|
-
|
|
194
|
-
const getAnchorPointer = (schema, fragment) => {
|
|
195
|
-
if (!(fragment in schema.anchors)) {
|
|
196
|
-
throw Error(`No such anchor '${encodeURI(schema.id)}#${encodeURI(fragment)}'`);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return schema.anchors[fragment];
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// Utility Functions
|
|
203
|
-
export const uri = (doc) => doc.id ? `${doc.id}#${encodeURI(doc.pointer)}` : undefined;
|
|
204
|
-
export const value = (doc) => doc.value;
|
|
205
|
-
export const has = (key, doc) => key in value(doc);
|
|
206
|
-
export const typeOf = (doc, type) => jsonTypeOf(value(doc), type);
|
|
207
|
-
|
|
208
|
-
export const step = (key, doc) => {
|
|
209
|
-
const storedSchema = getStoredSchema(doc.id);
|
|
210
|
-
return followReferences({
|
|
211
|
-
...doc,
|
|
212
|
-
pointer: pointerAppend(`${key}`, doc.pointer),
|
|
213
|
-
value: value(doc)[key],
|
|
214
|
-
validated: storedSchema.validated
|
|
215
|
-
});
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
export const iter = async function* (doc) {
|
|
219
|
-
for (let index = 0; index < value(doc).length; index++) {
|
|
220
|
-
yield step(index, doc);
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
export const keys = function* (doc) {
|
|
225
|
-
for (const key in value(doc)) {
|
|
226
|
-
yield key;
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
export const values = async function* (doc) {
|
|
231
|
-
for (const key in value(doc)) {
|
|
232
|
-
yield step(key, doc);
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
export const entries = async function* (doc) {
|
|
237
|
-
for (const key in value(doc)) {
|
|
238
|
-
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
|
+
}
|
|
239
201
|
}
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
export const length = (doc) => value(doc).length;
|
|
243
202
|
|
|
244
|
-
|
|
245
|
-
parentId: "",
|
|
246
|
-
parentDialect: "",
|
|
247
|
-
includeEmbedded: true
|
|
203
|
+
return json;
|
|
248
204
|
};
|
|
249
|
-
export const toSchema = (schemaDoc, options = {}) => {
|
|
250
|
-
const fullOptions = { ...toSchemaDefaultOptions, ...options };
|
|
251
205
|
|
|
252
|
-
|
|
253
|
-
|| getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-04/id");
|
|
254
|
-
const anchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/anchor");
|
|
255
|
-
const legacyAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-04/id");
|
|
256
|
-
const dynamicAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/dynamicAnchor");
|
|
257
|
-
const legacyDynamicAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
|
|
258
|
-
const recursiveAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/recursiveAnchor");
|
|
206
|
+
export const canonicalUri = (browser) => `${browser.document.baseUri}#${encodeURI(browser.cursor)}`;
|
|
259
207
|
|
|
208
|
+
export const toSchema = (browser, options = {}) => {
|
|
260
209
|
const anchors = {};
|
|
261
|
-
for (const anchor in
|
|
262
|
-
if (anchor !== "" && !
|
|
263
|
-
anchors[
|
|
210
|
+
for (const anchor in browser.document.anchors) {
|
|
211
|
+
if (anchor !== "" && !browser.document.dynamicAnchors[anchor]) {
|
|
212
|
+
anchors[browser.document.anchors[anchor]] = anchor;
|
|
264
213
|
}
|
|
265
214
|
}
|
|
266
215
|
|
|
267
216
|
const dynamicAnchors = {};
|
|
268
|
-
for (const anchor in
|
|
269
|
-
const pointer = uriFragment(
|
|
217
|
+
for (const anchor in browser.document.dynamicAnchors) {
|
|
218
|
+
const pointer = uriFragment(browser.document.dynamicAnchors[anchor]);
|
|
270
219
|
dynamicAnchors[pointer] = anchor;
|
|
271
220
|
}
|
|
272
221
|
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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;
|
|
278
256
|
}
|
|
279
|
-
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (jsonTypeOf(value) === "object") {
|
|
260
|
+
value = { ...value };
|
|
280
261
|
if (pointer in anchors) {
|
|
281
262
|
if (anchorToken) {
|
|
282
|
-
value
|
|
263
|
+
value[anchorToken] = anchors[pointer];
|
|
283
264
|
}
|
|
284
265
|
|
|
285
266
|
// Legacy anchor
|
|
286
267
|
if (legacyAnchorToken) {
|
|
287
|
-
value
|
|
268
|
+
value[legacyAnchorToken] = `#${anchors[pointer]}`;
|
|
288
269
|
}
|
|
289
270
|
}
|
|
271
|
+
|
|
290
272
|
if (pointer in dynamicAnchors) {
|
|
291
273
|
if (dynamicAnchorToken) {
|
|
292
|
-
value
|
|
274
|
+
value[dynamicAnchorToken] = dynamicAnchors[pointer];
|
|
293
275
|
}
|
|
294
276
|
|
|
295
277
|
// Legacy dynamic anchor
|
|
296
278
|
if (legacyDynamicAnchorToken) {
|
|
297
|
-
value
|
|
279
|
+
value[legacyDynamicAnchorToken] = dynamicAnchors[pointer];
|
|
298
280
|
}
|
|
299
281
|
|
|
300
282
|
// Recursive anchor
|
|
301
283
|
if (recursiveAnchorToken) {
|
|
302
|
-
value
|
|
284
|
+
value[recursiveAnchorToken] = true;
|
|
303
285
|
}
|
|
304
286
|
}
|
|
305
|
-
return value;
|
|
306
287
|
}
|
|
288
|
+
|
|
289
|
+
return value;
|
|
307
290
|
}));
|
|
308
291
|
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
...dialect && { $schema: dialect },
|
|
314
|
-
...schema
|
|
315
|
-
};
|
|
316
|
-
};
|
|
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
|
+
}
|
|
317
296
|
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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}'`);
|
|
324
331
|
}
|
|
332
|
+
|
|
333
|
+
return schema;
|
|
325
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 };
|