Skip to content

Commit dc17baa

Browse files
committed
Refactor bundling to not depend on keyword handlers
1 parent f4b7807 commit dc17baa

File tree

2 files changed

+39
-228
lines changed

2 files changed

+39
-228
lines changed

bundle/index.js

Lines changed: 39 additions & 227 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
import curry from "just-curry-it";
21
import { v4 as uuid } from "uuid";
3-
import * as Browser from "@hyperjump/browser";
2+
import { jrefTypeOf } from "@hyperjump/browser/jref";
43
import * as JsonPointer from "@hyperjump/json-pointer";
5-
import { asyncCollectSet, asyncFilter, asyncFlatten, asyncMap, pipe } from "@hyperjump/pact";
6-
import {
7-
Validation,
8-
canonicalUri, getSchema, toSchema,
9-
getKeywordName, getKeyword, getKeywordByName
10-
} from "../lib/experimental.js";
4+
import { resolveIri, toAbsoluteIri } from "@hyperjump/uri";
5+
import { getSchema, toSchema, getKeywordName } from "../lib/experimental.js";
116

127

138
export const URI = "uri", UUID = "uuid";
@@ -19,32 +14,36 @@ const defaultOptions = {
1914
};
2015

2116
export const bundle = async (url, options = {}) => {
22-
loadKeywordSupport();
2317
const fullOptions = { ...defaultOptions, ...options };
2418

2519
const mainSchema = await getSchema(url);
26-
const contextUri = mainSchema.document.baseUri;
27-
const contextDialectId = mainSchema.document.dialectId;
20+
fullOptions.contextUri = mainSchema.document.baseUri;
21+
fullOptions.contextDialectId = mainSchema.document.dialectId;
22+
2823
const bundled = toSchema(mainSchema);
24+
fullOptions.bundlingLocation = "/" + getKeywordName(fullOptions.contextDialectId, "https://json-schema.org/keyword/definitions");
25+
if (JsonPointer.get(fullOptions.bundlingLocation, bundled) === undefined) {
26+
JsonPointer.assign(fullOptions.bundlingLocation, bundled, {});
27+
}
2928

30-
const externalIds = await Validation.collectExternalIds(new Set(), mainSchema, mainSchema);
29+
return await doBundling(mainSchema.uri, bundled, fullOptions);
30+
};
3131

32-
// Bundle
33-
const bundlingLocation = "/" + getKeywordName(contextDialectId, "https://json-schema.org/keyword/definitions");
34-
if (JsonPointer.get(bundlingLocation, bundled) === undefined && externalIds.size > 0) {
35-
JsonPointer.assign(bundlingLocation, bundled, {});
36-
}
32+
const doBundling = async (schemaUri, bundled, fullOptions, contextSchema, visited = new Set()) => {
33+
visited.add(schemaUri);
3734

38-
for (const uri of externalIds) {
39-
if (fullOptions.externalSchemas.includes(uri)) {
35+
const schema = await getSchema(schemaUri, contextSchema);
36+
for (const reference of allReferences(schema.document.root)) {
37+
const uri = toAbsoluteIri(resolveIri(reference.href, schema.document.baseUri));
38+
if (visited.has(uri) || fullOptions.externalSchemas.includes(uri) || (uri in schema.document.embedded && !(uri in schema._cache))) {
4039
continue;
4140
}
4241

43-
const externalSchema = await getSchema(uri);
42+
const externalSchema = await getSchema(uri, contextSchema);
4443
const embeddedSchema = toSchema(externalSchema, {
45-
contextUri: externalSchema.document.baseUri.startsWith("file:") ? contextUri : undefined,
44+
contextUri: externalSchema.document.baseUri.startsWith("file:") ? fullOptions.contextUri : undefined,
4645
includeDialect: fullOptions.alwaysIncludeDialect ? "always" : "auto",
47-
contextDialectId: contextDialectId
46+
contextDialectId: fullOptions.contextDialectId
4847
});
4948
let id;
5049
if (fullOptions.definitionNamingStrategy === URI) {
@@ -56,216 +55,29 @@ export const bundle = async (url, options = {}) => {
5655
} else {
5756
throw Error(`Unknown definition naming stragety: ${fullOptions.definitionNamingStrategy}`);
5857
}
59-
const pointer = JsonPointer.append(id, bundlingLocation);
58+
const pointer = JsonPointer.append(id, fullOptions.bundlingLocation);
6059
JsonPointer.assign(pointer, bundled, embeddedSchema);
60+
61+
bundled = await doBundling(uri, bundled, fullOptions, schema, visited);
6162
}
6263

6364
return bundled;
6465
};
6566

66-
Validation.collectExternalIds = curry(async (visited, parentSchema, schema) => {
67-
const uri = canonicalUri(schema);
68-
if (visited.has(uri) || Browser.typeOf(schema) === "boolean") {
69-
return new Set();
70-
}
71-
72-
visited.add(uri);
73-
74-
const externalIds = await pipe(
75-
Browser.entries(schema),
76-
asyncMap(async ([keyword, keywordSchema]) => {
77-
const keywordHandler = getKeywordByName(keyword, schema.document.dialectId);
78-
79-
return "collectExternalIds" in keywordHandler
80-
? await keywordHandler.collectExternalIds(visited, schema, keywordSchema)
81-
: new Set();
82-
}),
83-
asyncFlatten,
84-
asyncCollectSet
85-
);
86-
87-
if (parentSchema.document.baseUri !== schema.document.baseUri
88-
&& (!(schema.document.baseUri in parentSchema.document.embedded) || schema.document.baseUri in parentSchema._cache)
89-
) {
90-
externalIds.add(schema.document.baseUri);
91-
}
92-
93-
return externalIds;
94-
});
95-
96-
const collectExternalIdsFromArrayOfSchemas = (visited, parentSchema, schema) => pipe(
97-
Browser.iter(schema),
98-
asyncMap(Validation.collectExternalIds(visited, parentSchema)),
99-
asyncFlatten,
100-
asyncCollectSet
101-
);
102-
103-
const collectExternalIdsFromObjectOfSchemas = async (visited, parentSchema, schema) => pipe(
104-
Browser.values(schema),
105-
asyncMap(Validation.collectExternalIds(visited, parentSchema)),
106-
asyncFlatten,
107-
asyncCollectSet
108-
);
109-
110-
const loadKeywordSupport = () => {
111-
// Stable
112-
113-
const additionalProperties = getKeyword("https://json-schema.org/keyword/additionalProperties");
114-
if (additionalProperties) {
115-
additionalProperties.collectExternalIds = Validation.collectExternalIds;
116-
}
117-
118-
const allOf = getKeyword("https://json-schema.org/keyword/allOf");
119-
if (allOf) {
120-
allOf.collectExternalIds = collectExternalIdsFromArrayOfSchemas;
121-
}
122-
123-
const anyOf = getKeyword("https://json-schema.org/keyword/anyOf");
124-
if (anyOf) {
125-
anyOf.collectExternalIds = collectExternalIdsFromArrayOfSchemas;
126-
}
127-
128-
const contains = getKeyword("https://json-schema.org/keyword/contains");
129-
if (contains) {
130-
contains.collectExternalIds = Validation.collectExternalIds;
131-
}
132-
133-
const dependentSchemas = getKeyword("https://json-schema.org/keyword/dependentSchemas");
134-
if (dependentSchemas) {
135-
dependentSchemas.collectExternalIds = collectExternalIdsFromObjectOfSchemas;
136-
}
137-
138-
const if_ = getKeyword("https://json-schema.org/keyword/if");
139-
if (if_) {
140-
if_.collectExternalIds = Validation.collectExternalIds;
141-
}
142-
143-
const then = getKeyword("https://json-schema.org/keyword/then");
144-
if (then) {
145-
then.collectExternalIds = Validation.collectExternalIds;
146-
}
147-
148-
const else_ = getKeyword("https://json-schema.org/keyword/else");
149-
if (else_) {
150-
else_.collectExternalIds = Validation.collectExternalIds;
151-
}
152-
153-
const items = getKeyword("https://json-schema.org/keyword/items");
154-
if (items) {
155-
items.collectExternalIds = Validation.collectExternalIds;
156-
}
157-
158-
const not = getKeyword("https://json-schema.org/keyword/not");
159-
if (not) {
160-
not.collectExternalIds = Validation.collectExternalIds;
161-
}
162-
163-
const oneOf = getKeyword("https://json-schema.org/keyword/oneOf");
164-
if (oneOf) {
165-
oneOf.collectExternalIds = collectExternalIdsFromArrayOfSchemas;
166-
}
167-
168-
const patternProperties = getKeyword("https://json-schema.org/keyword/patternProperties");
169-
if (patternProperties) {
170-
patternProperties.collectExternalIds = collectExternalIdsFromObjectOfSchemas;
171-
}
172-
173-
const prefixItems = getKeyword("https://json-schema.org/keyword/prefixItems");
174-
if (prefixItems) {
175-
prefixItems.collectExternalIds = collectExternalIdsFromArrayOfSchemas;
176-
}
177-
178-
const properties = getKeyword("https://json-schema.org/keyword/properties");
179-
if (properties) {
180-
properties.collectExternalIds = collectExternalIdsFromObjectOfSchemas;
181-
}
182-
183-
const propertyNames = getKeyword("https://json-schema.org/keyword/propertyNames");
184-
if (propertyNames) {
185-
propertyNames.collectExternalIds = Validation.collectExternalIds;
186-
}
187-
188-
const ref = getKeyword("https://json-schema.org/keyword/ref");
189-
if (ref) {
190-
ref.collectExternalIds = Validation.collectExternalIds;
191-
}
192-
193-
const unevaluatedItems = getKeyword("https://json-schema.org/keyword/unevaluatedItems");
194-
if (unevaluatedItems) {
195-
unevaluatedItems.collectExternalIds = Validation.collectExternalIds;
196-
}
197-
198-
const unevaluatedProperties = getKeyword("https://json-schema.org/keyword/unevaluatedProperties");
199-
if (unevaluatedProperties) {
200-
unevaluatedProperties.collectExternalIds = Validation.collectExternalIds;
201-
}
202-
203-
// Draft-04
204-
205-
const additionalItems4 = getKeyword("https://json-schema.org/keyword/draft-04/additionalItems");
206-
if (additionalItems4) {
207-
additionalItems4.collectExternalIds = Validation.collectExternalIds;
208-
}
209-
210-
const dependencies = getKeyword("https://json-schema.org/keyword/draft-04/dependencies");
211-
if (dependencies) {
212-
dependencies.collectExternalIds = (visited, parentSchema, schema) => pipe(
213-
Browser.values(schema),
214-
asyncFilter((subSchema) => Browser.typeOf(subSchema) === "object"),
215-
asyncMap(Validation.collectExternalIds(visited, parentSchema)),
216-
asyncFlatten,
217-
asyncCollectSet
218-
);
219-
}
220-
221-
const items4 = getKeyword("https://json-schema.org/keyword/draft-04/items");
222-
if (items4) {
223-
items4.collectExternalIds = (visited, parentSchema, schema) => Browser.typeOf(schema) === "array"
224-
? collectExternalIdsFromArrayOfSchemas(visited, parentSchema, schema)
225-
: Validation.collectExternalIds(visited, parentSchema, schema);
226-
}
227-
228-
// Draft-06
229-
230-
const contains6 = getKeyword("https://json-schema.org/keyword/draft-06/contains");
231-
if (contains6) {
232-
contains6.collectExternalIds = Validation.collectExternalIds;
233-
}
234-
235-
// Draft-2019-09
236-
237-
const contains19 = getKeyword("https://json-schema.org/keyword/draft-2019-09/contains");
238-
if (contains19) {
239-
contains19.collectExternalIds = Validation.collectExternalIds;
240-
}
241-
242-
// Extensions
243-
244-
const propertyDependencies = getKeyword("https://json-schema.org/keyword/propertyDependencies");
245-
if (propertyDependencies) {
246-
propertyDependencies.collectExternalIds = (visited, parentSchema, schema) => pipe(
247-
Browser.values(schema),
248-
asyncMap((mapping) => Browser.values(mapping)),
249-
asyncFlatten,
250-
asyncMap(Validation.collectExternalIds(visited, parentSchema)),
251-
asyncFlatten,
252-
asyncCollectSet
253-
);
254-
}
255-
256-
const conditional = getKeyword("https://json-schema.org/keyword/conditional");
257-
if (conditional) {
258-
conditional.collectExternalIds = collectExternalIdsFromArrayOfSchemas;
259-
}
260-
261-
const itemPattern = getKeyword("https://json-schema.org/keyword/itemPattern");
262-
if (itemPattern) {
263-
itemPattern.collectExternalIds = (visited, parentSchema, schema) => pipe(
264-
Browser.iter(schema),
265-
asyncFilter((item) => Browser.typeOf(item) === "object"),
266-
asyncMap(Validation.collectExternalIds(visited, parentSchema)),
267-
asyncFlatten,
268-
asyncCollectSet
269-
);
67+
const allReferences = function* (node) {
68+
switch (jrefTypeOf(node)) {
69+
case "object":
70+
for (const property in node) {
71+
yield* allReferences(node[property]);
72+
}
73+
break;
74+
case "array":
75+
for (const item of node) {
76+
yield* allReferences(item);
77+
}
78+
break;
79+
case "reference":
80+
yield node;
81+
break;
27082
}
27183
};

lib/experimental.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ export type Keyword<A> = {
7171
compile: (schema: Browser<SchemaDocument>, ast: AST, parentSchema: Browser<SchemaDocument>) => Promise<A>;
7272
interpret: (compiledKeywordValue: A, instance: JsonNode, context: ValidationContext) => boolean;
7373
simpleApplicator: boolean;
74-
collectExternalIds?: (visited: Set<string>, parentSchema: Browser<SchemaDocument>, schema: Browser<SchemaDocument>) => Promise<Set<string>>;
7574
annotation?: <B>(compiledKeywordValue: A, instance: JsonNode) => B | undefined;
7675
};
7776

0 commit comments

Comments
 (0)