@hyperjump/json-schema 1.1.2 → 1.2.1
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 +123 -43
- package/bundle/index.d.ts +18 -0
- package/bundle/index.js +269 -0
- package/lib/common.d.ts +4 -2
- package/lib/common.js +63 -0
- package/lib/instance.js +2 -2
- package/lib/media-types.js +1 -1
- package/lib/schema.js +31 -31
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -11,6 +11,9 @@ A collection of modules for working with JSON Schemas.
|
|
|
11
11
|
* Schemas can reference other schemas using a different dialect
|
|
12
12
|
* Work directly with schemas on the filesystem or HTTP
|
|
13
13
|
* Create custom keywords, vocabularies, and dialects
|
|
14
|
+
* Bundle multiple schemas into one document
|
|
15
|
+
* Uses the process defined in the 2020-12 specification but works with any
|
|
16
|
+
dialect.
|
|
14
17
|
* Provides utilities for building non-validation JSON Schema tooling
|
|
15
18
|
|
|
16
19
|
## Install
|
|
@@ -41,7 +44,12 @@ The API for this library is divided into two categories: Stable and
|
|
|
41
44
|
Experimental. The Stable API strictly follows semantic versioning, but the
|
|
42
45
|
Experimental API may have backward-incompatible changes between minor versions.
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
All experimental features are segregated into exports that include the word
|
|
48
|
+
"experimental" so you never accidentally depend on something that could change
|
|
49
|
+
or be removed in future releases.
|
|
50
|
+
|
|
51
|
+
## Validation
|
|
52
|
+
### Usage
|
|
45
53
|
This library supports many versions of JSON Schema. Use the pattern
|
|
46
54
|
`@hyperjump/json-schema/*` to import the version you need.
|
|
47
55
|
|
|
@@ -130,7 +138,7 @@ const isString = await validate(`file://${__dirname}/string.schema.yaml`);
|
|
|
130
138
|
const output = isString("foo");
|
|
131
139
|
```
|
|
132
140
|
|
|
133
|
-
**
|
|
141
|
+
**OpenAPI**
|
|
134
142
|
|
|
135
143
|
The OpenAPI 3.0 and 3.1 meta-schemas are pre-loaded and the OpenAPI JSON Schema
|
|
136
144
|
dialects for each of those versions is supported. A document with a Content-Type
|
|
@@ -155,7 +163,7 @@ const output = await validate("https://spec.openapis.org/oas/3.1/schema-base", o
|
|
|
155
163
|
const output = await validate(`file://${__dirname}/example.openapi.json#/components/schemas/foo`, 42);
|
|
156
164
|
```
|
|
157
165
|
|
|
158
|
-
|
|
166
|
+
### API
|
|
159
167
|
These are available from any of the exports that refer to a version of JSON
|
|
160
168
|
Schema, such as `@hyperjump/json-schema/draft-2020-12`.
|
|
161
169
|
|
|
@@ -178,12 +186,12 @@ Schema, such as `@hyperjump/json-schema/draft-2020-12`.
|
|
|
178
186
|
|
|
179
187
|
This error is thrown if the schema being compiled is found to be invalid.
|
|
180
188
|
The `output` field contains an `OutputUnit` with information about the
|
|
181
|
-
error. You can use the `
|
|
182
|
-
format that is returned in `output`.
|
|
183
|
-
* **
|
|
189
|
+
error. You can use the `setMetaSchemaOutputFormat` configuration to set the
|
|
190
|
+
output format that is returned in `output`.
|
|
191
|
+
* **setMetaSchemaOutputFormat**: (outputFormat: OutputFormat) => void
|
|
184
192
|
|
|
185
193
|
Set the output format used for validating schemas.
|
|
186
|
-
* **
|
|
194
|
+
* **getMetaSchemaOutputFormat**: () => OutputFormat
|
|
187
195
|
|
|
188
196
|
Get the output format used for validating schemas.
|
|
189
197
|
* **setShouldMetaValidate**: (isEnabled: boolean) => void
|
|
@@ -221,28 +229,92 @@ The following types are used in the above definitions
|
|
|
221
229
|
Given a filesystem path, return whether or not the file should be
|
|
222
230
|
considered a member of this media type.
|
|
223
231
|
|
|
224
|
-
##
|
|
225
|
-
The JSON Schema specification includes several features that are experimental in
|
|
226
|
-
nature including the Vocabulary System, Output Formats, and Annotations. This
|
|
227
|
-
implementation aims to support only the latest version of experimental features
|
|
228
|
-
as they evolve. There will not be a major version bump if there needs to be
|
|
229
|
-
backward incompatible changes to the Experimental API.
|
|
230
|
-
|
|
232
|
+
## Bundling
|
|
231
233
|
### Usage
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
You can bundle schemas with external references into single deliverable using
|
|
235
|
+
the official JSON Schema bundling process introduced in the 2020-12
|
|
236
|
+
specification. Given a schema with external references, any external schemas
|
|
237
|
+
will be embedded in the schema resulting in a Compound Schema Document with all
|
|
238
|
+
the schemas necessary to evaluate the given schema in one document.
|
|
239
|
+
|
|
240
|
+
The bundling process allows schemas to be embedded without needing to modify any
|
|
241
|
+
references which means you get the same output details whether you validate the
|
|
242
|
+
bundle or the original unbundled schemas.
|
|
235
243
|
|
|
236
244
|
```javascript
|
|
237
|
-
import {
|
|
245
|
+
import { addSchema } from "@hyperjump/json-schema/draft-2020-12";
|
|
246
|
+
import { bundle } from "@hyperjump/json-schema/bundle";
|
|
247
|
+
|
|
248
|
+
addSchema({
|
|
249
|
+
"$id": "https://example.com/main"
|
|
250
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
251
|
+
|
|
252
|
+
"type": "object",
|
|
253
|
+
"properties": {
|
|
254
|
+
"foo": { "$ref": "/string" }
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
addSchema({
|
|
259
|
+
"$id": "https://example.com/string",
|
|
260
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
261
|
+
|
|
262
|
+
"type": "string"
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const bundledSchema = await bundle("https://example.com/main"); // {
|
|
266
|
+
// "$id": "https://example.com/main",
|
|
267
|
+
// "$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
268
|
+
//
|
|
269
|
+
// "type": "object",
|
|
270
|
+
// "properties": {
|
|
271
|
+
// "foo": { "$ref": "/string" }
|
|
272
|
+
// },
|
|
273
|
+
//
|
|
274
|
+
// "$defs": {
|
|
275
|
+
// "https://example.com/main": {
|
|
276
|
+
// "$id": "https://example.com/main",
|
|
277
|
+
// "type": "string"
|
|
278
|
+
// }
|
|
279
|
+
// }
|
|
280
|
+
// }
|
|
238
281
|
```
|
|
239
282
|
|
|
283
|
+
### API
|
|
284
|
+
These are available from the `@hyperjump/json-schema/bundle` export.
|
|
285
|
+
|
|
286
|
+
* **bundle**: (uri: string, options: Options) => Promise<SchemaObject>
|
|
287
|
+
|
|
288
|
+
Create a bundled schema starting with the given schema. External schemas
|
|
289
|
+
will be fetched from the filesystem, the network, or internally as needed.
|
|
290
|
+
|
|
291
|
+
Options:
|
|
292
|
+
* alwaysIncludeDialect: boolean (default: false) -- Include dialect even
|
|
293
|
+
when it isn't strictly needed
|
|
294
|
+
* bundleMode: "flat" | "full" (default: "flat") -- When bundling schemas
|
|
295
|
+
that already contain bundled schemas, "flat" mode with remove nested
|
|
296
|
+
embedded schemas and put them all in the top level `$defs`. When using
|
|
297
|
+
"full" mode, it will keep the already embedded schemas around, which will
|
|
298
|
+
result in some embedded schema duplication.
|
|
299
|
+
* definitionNamingStrategy: "uri" | "uuid" (default: "uri") -- By default
|
|
300
|
+
the name used in definitions for embedded schemas will match the
|
|
301
|
+
identifier of the embedded schema. This naming is unlikely to collide
|
|
302
|
+
with actual definitions, but if you want to be sure, you can use the
|
|
303
|
+
"uuid" strategy instead to be sure you get a unique name.
|
|
304
|
+
* externalSchemas: string[] (default: []) -- A list of schemas URIs that
|
|
305
|
+
are available externally and should not be included in the bundle.
|
|
306
|
+
|
|
307
|
+
## Output Formats (Experimental)
|
|
308
|
+
### Usage
|
|
309
|
+
|
|
240
310
|
**Change the validation output format**
|
|
241
311
|
|
|
242
312
|
The `FLAG` output format isn't very informative. You can change the output
|
|
243
|
-
format used for validation to get more information.
|
|
313
|
+
format used for validation to get more information about failures.
|
|
244
314
|
|
|
245
315
|
```javascript
|
|
316
|
+
import { BASIC } from "@hyperjump/json-schema/experimental";
|
|
317
|
+
|
|
246
318
|
const output = await validate("https://example.com/schema1", 42, BASIC);
|
|
247
319
|
```
|
|
248
320
|
|
|
@@ -251,7 +323,10 @@ const output = await validate("https://example.com/schema1", 42, BASIC);
|
|
|
251
323
|
The output format used for validating schemas can be changed as well.
|
|
252
324
|
|
|
253
325
|
```javascript
|
|
254
|
-
|
|
326
|
+
import { validate, setMetaSchemaOutputFormat } from "@hyperjump/json-schema/draft-2020-12";
|
|
327
|
+
import { BASIC } from "@hyperjump/json-schema/experimental";
|
|
328
|
+
|
|
329
|
+
setMetaSchemaOutputFormat(BASIC);
|
|
255
330
|
try {
|
|
256
331
|
const output = await validate("https://example.com/invalid-schema");
|
|
257
332
|
} catch (error) {
|
|
@@ -259,8 +334,20 @@ try {
|
|
|
259
334
|
}
|
|
260
335
|
```
|
|
261
336
|
|
|
262
|
-
|
|
337
|
+
### API
|
|
338
|
+
**Type Definitions**
|
|
339
|
+
|
|
340
|
+
* **OutputFormat**: **FLAG** | **BASIC** | **DETAILED** | **VERBOSE**
|
|
341
|
+
|
|
342
|
+
In addition to the `FLAG` output format in the Stable API, the Experimental
|
|
343
|
+
API includes support for the `BASIC`, `DETAILED`, and `VERBOSE` formats as
|
|
344
|
+
specified in the 2019-09 specification (with some minor customizations).
|
|
345
|
+
This implementation doesn't include annotations or human readable error
|
|
346
|
+
messages. The output can be processed to create human readable error
|
|
347
|
+
messages as needed.
|
|
263
348
|
|
|
349
|
+
## Meta-Schemas, Keywords, Vocabularies, and Dialects (Experimental)
|
|
350
|
+
### Usage
|
|
264
351
|
In order to create and use a custom keyword, you need to define your keyword's
|
|
265
352
|
behavior, create a vocabulary that includes that keyword, and then create a
|
|
266
353
|
dialect that includes your vocabulary.
|
|
@@ -384,14 +471,6 @@ const output = await validate("https://example.com/schema1", 42); // Expect Inva
|
|
|
384
471
|
### API
|
|
385
472
|
These are available from the `@hyperjump/json-schema/experimental` export.
|
|
386
473
|
|
|
387
|
-
* **compile**: (schema: SchemaDocument) => Promise<CompiledSchema>
|
|
388
|
-
|
|
389
|
-
Return a compiled schema. This is useful if you're creating tooling for
|
|
390
|
-
something other than validation.
|
|
391
|
-
* **interpret**: (schema: CompiledSchema, instance: Instance, outputFormat: OutputFormat = BASIC) => OutputUnit
|
|
392
|
-
|
|
393
|
-
A curried function for validating an instance against a compiled schema.
|
|
394
|
-
This can be useful for creating custom output formats.
|
|
395
474
|
* **addKeyword**: (keywordHandler: Keyword) => void
|
|
396
475
|
|
|
397
476
|
Define a keyword for use in a vocabulary.
|
|
@@ -419,18 +498,6 @@ These are available from the `@hyperjump/json-schema/experimental` export.
|
|
|
419
498
|
A Keyword object that represents a "validate" operation. You would use this
|
|
420
499
|
for compiling and evaluating sub-schemas when defining a custom keyword.
|
|
421
500
|
|
|
422
|
-
**Type Definitions**
|
|
423
|
-
|
|
424
|
-
The following types are used in the above definitions
|
|
425
|
-
|
|
426
|
-
* **OutputFormat**: **FLAG** | **BASIC** | **DETAILED** | **VERBOSE**
|
|
427
|
-
|
|
428
|
-
In addition to the `FLAG` output format in the Stable API, the Experimental
|
|
429
|
-
API includes support for the `BASIC`, `DETAILED`, and `VERBOSE` formats as
|
|
430
|
-
specified in the 2019-09 specification (with some minor customizations).
|
|
431
|
-
This implementation doesn't include annotations or human readable error
|
|
432
|
-
messages. The output can be processed to create human readable error
|
|
433
|
-
messages as needed.
|
|
434
501
|
* **Keyword**: object
|
|
435
502
|
* id: string
|
|
436
503
|
|
|
@@ -459,7 +526,7 @@ The following types are used in the above definitions
|
|
|
459
526
|
If the keyword is an applicator, it will need to implements this
|
|
460
527
|
function for `unevaluatedItems` to work as expected.
|
|
461
528
|
|
|
462
|
-
### Schema
|
|
529
|
+
### Schema API
|
|
463
530
|
These functions are available from the
|
|
464
531
|
`@hyperjump/json-schema/schema/experimental` export.
|
|
465
532
|
|
|
@@ -518,7 +585,7 @@ The following types are used in the above definitions
|
|
|
518
585
|
* includeEmbedded: boolean (default: true) -- If false, embedded schemas
|
|
519
586
|
will be unbundled from the schema.
|
|
520
587
|
|
|
521
|
-
### Instance
|
|
588
|
+
### Instance API
|
|
522
589
|
These functions are available from the
|
|
523
590
|
`@hyperjump/json-schema/instance/experimental` export.
|
|
524
591
|
|
|
@@ -569,6 +636,19 @@ set of functions for working with InstanceDocuments.
|
|
|
569
636
|
|
|
570
637
|
Similar to `Array.prototype.length`.
|
|
571
638
|
|
|
639
|
+
## Low-level Utilities (Experimental)
|
|
640
|
+
### API
|
|
641
|
+
These are available from the `@hyperjump/json-schema/experimental` export.
|
|
642
|
+
|
|
643
|
+
* **compile**: (schema: SchemaDocument) => Promise<CompiledSchema>
|
|
644
|
+
|
|
645
|
+
Return a compiled schema. This is useful if you're creating tooling for
|
|
646
|
+
something other than validation.
|
|
647
|
+
* **interpret**: (schema: CompiledSchema, instance: Instance, outputFormat: OutputFormat = BASIC) => OutputUnit
|
|
648
|
+
|
|
649
|
+
A curried function for validating an instance against a compiled schema.
|
|
650
|
+
This can be useful for creating custom output formats.
|
|
651
|
+
|
|
572
652
|
## Contributing
|
|
573
653
|
|
|
574
654
|
### Tests
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SchemaObject } from "../lib/schema.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const bundle: <A = SchemaObject>(uri: string, options?: BundleOptions) => Promise<A>;
|
|
5
|
+
export const FULL: "full";
|
|
6
|
+
export const FLAT: "flat";
|
|
7
|
+
export const URI: "uri";
|
|
8
|
+
export const UUID: "uuid";
|
|
9
|
+
|
|
10
|
+
export type BundleOptions = {
|
|
11
|
+
alwaysIncludeDialect?: boolean;
|
|
12
|
+
bundleMode?: BundleMode;
|
|
13
|
+
definitionNamingStrategy?: DefinitionNamingStrategy;
|
|
14
|
+
externalSchemas?: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type BundleMode = "full" | "flat";
|
|
18
|
+
export type DefinitionNamingStrategy = "uri" | "uuid";
|
package/bundle/index.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { v4 as uuid } from "uuid";
|
|
2
|
+
import * as JsonPointer from "@hyperjump/json-pointer";
|
|
3
|
+
import { toAbsoluteUri } from "../lib/common.js";
|
|
4
|
+
import { compile } from "../lib/core.js";
|
|
5
|
+
import { getKeywordName, getKeyword } from "../lib/keywords.js";
|
|
6
|
+
import Validation from "../lib/keywords/validation.js";
|
|
7
|
+
import * as Schema from "../lib/schema.js";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const FULL = "full", FLAT = "flat";
|
|
11
|
+
export const URI = "uri", UUID = "uuid";
|
|
12
|
+
|
|
13
|
+
const defaultOptions = {
|
|
14
|
+
alwaysIncludeDialect: false,
|
|
15
|
+
bundleMode: FLAT,
|
|
16
|
+
definitionNamingStrategy: URI,
|
|
17
|
+
externalSchemas: []
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const bundle = async (url, options = {}) => {
|
|
21
|
+
loadKeywordSupport();
|
|
22
|
+
const fullOptions = { ...defaultOptions, ...options };
|
|
23
|
+
|
|
24
|
+
const schemaDoc = await Schema.get(url);
|
|
25
|
+
const externalIds = await collectExternalIds(url, fullOptions);
|
|
26
|
+
|
|
27
|
+
const bundled = Schema.toSchema(schemaDoc, {
|
|
28
|
+
includeEmbedded: fullOptions.bundleMode === FULL
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const bundlingLocation = "/" + getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/definitions");
|
|
32
|
+
if (JsonPointer.get(bundlingLocation, bundled) === undefined && externalIds.size > 0) {
|
|
33
|
+
JsonPointer.assign(bundlingLocation, bundled, {});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const uri of externalIds.values()) {
|
|
37
|
+
const externalSchema = await Schema.get(uri);
|
|
38
|
+
const embeddedSchema = Schema.toSchema(externalSchema, {
|
|
39
|
+
parentId: schemaDoc.id,
|
|
40
|
+
parentDialect: fullOptions.alwaysIncludeDialect ? "" : schemaDoc.dialectId,
|
|
41
|
+
includeEmbedded: fullOptions.bundleMode === FULL
|
|
42
|
+
});
|
|
43
|
+
let id;
|
|
44
|
+
if (fullOptions.definitionNamingStrategy === URI) {
|
|
45
|
+
const idToken = getKeywordName(externalSchema.dialectId, "https://json-schema.org/keyword/id")
|
|
46
|
+
|| getKeywordName(externalSchema.dialectId, "https://json-schema.org/keyword/draft-04/id");
|
|
47
|
+
id = embeddedSchema[idToken];
|
|
48
|
+
} else if (fullOptions.definitionNamingStrategy === UUID) {
|
|
49
|
+
id = uuid();
|
|
50
|
+
} else {
|
|
51
|
+
throw Error(`Unknown definition naming stragety: ${fullOptions.definitionNamingStrategy}`);
|
|
52
|
+
}
|
|
53
|
+
const pointer = JsonPointer.append(id, bundlingLocation);
|
|
54
|
+
JsonPointer.assign(pointer, bundled, embeddedSchema);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return bundled;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const collectExternalIds = async (uri, options) => {
|
|
61
|
+
const { ast, schemaUri } = await compile(uri);
|
|
62
|
+
const subSchemaUris = new Set();
|
|
63
|
+
Validation.collectExternalIds(schemaUri, subSchemaUris, ast, {});
|
|
64
|
+
const externalIds = new Set([...subSchemaUris]
|
|
65
|
+
.map(toAbsoluteUri)
|
|
66
|
+
.filter((uri) => !options.externalSchemas.includes(uri)));
|
|
67
|
+
externalIds.delete(toAbsoluteUri(schemaUri));
|
|
68
|
+
|
|
69
|
+
return externalIds;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
Validation.collectExternalIds = (schemaUri, externalIds, ast, dynamicAnchors) => {
|
|
73
|
+
const nodes = ast[schemaUri][2];
|
|
74
|
+
if (externalIds.has(schemaUri) || typeof nodes === "boolean") {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
externalIds.add(schemaUri);
|
|
78
|
+
|
|
79
|
+
const id = toAbsoluteUri(schemaUri);
|
|
80
|
+
for (const [keywordId, , keywordValue] of nodes) {
|
|
81
|
+
const keyword = getKeyword(keywordId);
|
|
82
|
+
|
|
83
|
+
if (keyword.collectExternalIds) {
|
|
84
|
+
keyword.collectExternalIds(keywordValue, externalIds, ast, {
|
|
85
|
+
...ast.metaData[id].dynamicAnchors, ...dynamicAnchors
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const loadKeywordSupport = () => {
|
|
92
|
+
// Stable
|
|
93
|
+
|
|
94
|
+
const additionalProperties = getKeyword("https://json-schema.org/keyword/additionalProperties");
|
|
95
|
+
if (additionalProperties) {
|
|
96
|
+
additionalProperties.collectExternalIds = ([, , additionalProperties], externalIds, ast, dynamicAnchors) => {
|
|
97
|
+
if (typeof additionalProperties === "string") {
|
|
98
|
+
Validation.collectExternalIds(additionalProperties, externalIds, ast, dynamicAnchors);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const allOf = getKeyword("https://json-schema.org/keyword/allOf");
|
|
104
|
+
if (allOf) {
|
|
105
|
+
allOf.collectExternalIds = (allOf, externalIds, ast, dynamicAnchors) => {
|
|
106
|
+
allOf.forEach((schemaUri) => Validation.collectExternalIds(schemaUri, externalIds, ast, dynamicAnchors));
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const anyOf = getKeyword("https://json-schema.org/keyword/anyOf");
|
|
111
|
+
if (anyOf) {
|
|
112
|
+
anyOf.collectExternalIds = (anyOf, externalIds, ast, dynamicAnchors) => {
|
|
113
|
+
anyOf.forEach((schemaUri) => Validation.collectExternalIds(schemaUri, externalIds, ast, dynamicAnchors));
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const contains = getKeyword("https://json-schema.org/keyword/contains");
|
|
118
|
+
if (contains) {
|
|
119
|
+
contains.collectExternalIds = ({ contains }, externalIds, ast, dynamicAnchors) => {
|
|
120
|
+
Validation.collectExternalIds(contains, externalIds, ast, dynamicAnchors);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const dependentSchemas = getKeyword("https://json-schema.org/keyword/dependentSchemas");
|
|
125
|
+
if (dependentSchemas) {
|
|
126
|
+
dependentSchemas.collectExternalIds = (dependentSchemas, externalIds, ast, dynamicAnchors) => {
|
|
127
|
+
Object.values(dependentSchemas).forEach(([, schemaUri]) => Validation.collectExternalIds(schemaUri, externalIds, ast, dynamicAnchors));
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const if_ = getKeyword("https://json-schema.org/keyword/if");
|
|
132
|
+
if (if_) {
|
|
133
|
+
if_.collectExternalIds = Validation.collectExternalIds;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const then = getKeyword("https://json-schema.org/keyword/then");
|
|
137
|
+
if (then) {
|
|
138
|
+
then.collectExternalIds = ([, then], externalIds, ast, dynamicAnchors) => {
|
|
139
|
+
Validation.collectExternalIds(then, externalIds, ast, dynamicAnchors);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const else_ = getKeyword("https://json-schema.org/keyword/else");
|
|
144
|
+
if (else_) {
|
|
145
|
+
else_.collectExternalIds = ([, elseSchema], externalIds, ast, dynamicAnchors) => {
|
|
146
|
+
Validation.collectExternalIds(elseSchema, externalIds, ast, dynamicAnchors);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const items = getKeyword("https://json-schema.org/keyword/items");
|
|
151
|
+
if (items) {
|
|
152
|
+
items.collectExternalIds = ([, items], externalIds, ast, dynamicAnchors) => {
|
|
153
|
+
Validation.collectExternalIds(items, externalIds, ast, dynamicAnchors);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const not = getKeyword("https://json-schema.org/keyword/not");
|
|
158
|
+
if (not) {
|
|
159
|
+
not.collectExternalIds = Validation.collectExternalIds;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const oneOf = getKeyword("https://json-schema.org/keyword/oneOf");
|
|
163
|
+
if (oneOf) {
|
|
164
|
+
oneOf.collectExternalIds = (oneOf, externalIds, ast, dynamicAnchors) => {
|
|
165
|
+
oneOf.forEach((schemaUri) => Validation.collectExternalIds(schemaUri, externalIds, ast, dynamicAnchors));
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const patternProperties = getKeyword("https://json-schema.org/keyword/patternProperties");
|
|
170
|
+
if (patternProperties) {
|
|
171
|
+
patternProperties.collectExternalIds = (patternProperties, externalIds, ast, dynamicAnchors) => {
|
|
172
|
+
patternProperties.forEach(([, schemaUri]) => Validation.collectExternalIds(schemaUri, externalIds, ast, dynamicAnchors));
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const prefixItems = getKeyword("https://json-schema.org/keyword/prefixItems");
|
|
177
|
+
if (prefixItems) {
|
|
178
|
+
prefixItems.collectExternalIds = (tupleItems, externalIds, ast, dynamicAnchors) => {
|
|
179
|
+
tupleItems.forEach((schemaUri) => Validation.collectExternalIds(schemaUri, externalIds, ast, dynamicAnchors));
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const properties = getKeyword("https://json-schema.org/keyword/properties");
|
|
184
|
+
if (properties) {
|
|
185
|
+
properties.collectExternalIds = (properties, externalIds, ast, dynamicAnchors) => {
|
|
186
|
+
Object.values(properties).forEach((schemaUri) => Validation.collectExternalIds(schemaUri, externalIds, ast, dynamicAnchors));
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const propertyNames = getKeyword("https://json-schema.org/keyword/propertyNames");
|
|
191
|
+
if (propertyNames) {
|
|
192
|
+
propertyNames.collectExternalIds = Validation.collectExternalIds;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const ref = getKeyword("https://json-schema.org/keyword/ref");
|
|
196
|
+
if (ref) {
|
|
197
|
+
ref.collectExternalIds = Validation.collectExternalIds;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const unevaluatedItems = getKeyword("https://json-schema.org/keyword/unevaluatedItems");
|
|
201
|
+
if (unevaluatedItems) {
|
|
202
|
+
unevaluatedItems.collectExternalIds = ([, unevaluatedItems], externalIds, ast, dynamicAnchors) => {
|
|
203
|
+
Validation.collectExternalIds(unevaluatedItems, externalIds, ast, dynamicAnchors);
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const unevaluatedProperties = getKeyword("https://json-schema.org/keyword/unevaluatedProperties");
|
|
208
|
+
if (unevaluatedProperties) {
|
|
209
|
+
unevaluatedProperties.collectExternalIds = ([, unevaluatedProperties], externalIds, ast, dynamicAnchors) => {
|
|
210
|
+
Validation.collectExternalIds(unevaluatedProperties, externalIds, ast, dynamicAnchors);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Draft-04
|
|
215
|
+
|
|
216
|
+
const additionalItems4 = getKeyword("https://json-schema.org/keyword/draft-04/additionalItems");
|
|
217
|
+
if (additionalItems4) {
|
|
218
|
+
additionalItems4.collectExternalIds = ([, additionalItems], externalIds, ast, dynamicAnchors) => {
|
|
219
|
+
if (typeof additionalItems === "string") {
|
|
220
|
+
Validation.collectExternalIds(additionalItems, externalIds, ast, dynamicAnchors);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const dependencies = getKeyword("https://json-schema.org/keyword/draft-04/dependencies");
|
|
226
|
+
if (dependencies) {
|
|
227
|
+
dependencies.collectExternalIds = (dependentSchemas, externalIds, ast, dynamicAnchors) => {
|
|
228
|
+
Object.values(dependentSchemas).forEach(([, dependency]) => {
|
|
229
|
+
if (typeof dependency === "string") {
|
|
230
|
+
Validation.collectExternalIds(dependency, externalIds, ast, dynamicAnchors);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const items4 = getKeyword("https://json-schema.org/keyword/draft-04/items");
|
|
237
|
+
if (items4) {
|
|
238
|
+
items4.collectExternalIds = (items, externalIds, ast, dynamicAnchors) => {
|
|
239
|
+
if (typeof items === "string") {
|
|
240
|
+
Validation.collectExternalIds(items, externalIds, ast, dynamicAnchors);
|
|
241
|
+
} else {
|
|
242
|
+
items.forEach((schemaUri) => Validation.collectExternalIds(schemaUri, externalIds, ast, dynamicAnchors));
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const ref4 = getKeyword("https://json-schema.org/keyword/draft-04/ref");
|
|
248
|
+
if (ref4) {
|
|
249
|
+
ref4.collectExternalIds = Validation.collectExternalIds;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Draft-06
|
|
253
|
+
|
|
254
|
+
const contains6 = getKeyword("https://json-schema.org/keyword/draft-06/contains");
|
|
255
|
+
if (contains6) {
|
|
256
|
+
contains6.collectExternalIds = (contains, externalIds, ast, dynamicAnchors) => {
|
|
257
|
+
Validation.collectExternalIds(contains, externalIds, ast, dynamicAnchors);
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Draft-2019-09
|
|
262
|
+
|
|
263
|
+
const contains19 = getKeyword("https://json-schema.org/keyword/draft-2019-09/contains");
|
|
264
|
+
if (contains19) {
|
|
265
|
+
contains19.collectExternalIds = ({ contains }, externalIds, ast, dynamicAnchors) => {
|
|
266
|
+
Validation.collectExternalIds(contains, externalIds, ast, dynamicAnchors);
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
};
|
package/lib/common.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type JsonType = "object" | "array" | "string" | "number" | "boolean" | "null";
|
|
2
2
|
export type JsonSchemaType = JsonType | "integer";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
export const pathRelative: (from: string, to: string) => string;
|
|
5
|
+
|
|
6
|
+
export type Replacer = (key: string, value: unknown) => unknown;
|
|
7
|
+
export const jsonStringify: (value: unknown, replacer?: Replacer, space?: string) => string;
|
package/lib/common.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveIri, toAbsoluteIri, parseIriReference } from "@hyperjump/uri";
|
|
2
|
+
import * as JsonPointer from "@hyperjump/json-pointer";
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
const isObject = (value) => typeof value === "object" && !Array.isArray(value) && value !== null;
|
|
@@ -86,3 +87,65 @@ export const pathRelative = (from, to) => {
|
|
|
86
87
|
|
|
87
88
|
return to.slice(toStart, to.length);
|
|
88
89
|
};
|
|
90
|
+
|
|
91
|
+
const defaultReplacer = (key, value) => value;
|
|
92
|
+
export const jsonStringify = (value, replacer = defaultReplacer, space = "") => {
|
|
93
|
+
return stringifyValue(value, replacer, space, "", JsonPointer.nil, 1);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const stringifyValue = (value, replacer, space, key, pointer, depth) => {
|
|
97
|
+
value = replacer(key, value, pointer);
|
|
98
|
+
let result;
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
result = stringifyArray(value, replacer, space, pointer, depth);
|
|
101
|
+
} else if (typeof value === "object" && value !== null) {
|
|
102
|
+
result = stringifyObject(value, replacer, space, pointer, depth);
|
|
103
|
+
} else {
|
|
104
|
+
result = JSON.stringify(value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const stringifyArray = (value, replacer, space, pointer, depth) => {
|
|
111
|
+
if (value.length === 0) {
|
|
112
|
+
return "[]";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const padding = space ? `\n${space.repeat(depth - 1)}` : "";
|
|
116
|
+
|
|
117
|
+
let result = "[" + padding + space;
|
|
118
|
+
for (let index = 0; index < value.length; index++) {
|
|
119
|
+
const indexPointer = JsonPointer.append(index, pointer);
|
|
120
|
+
const stringifiedValue = stringifyValue(value[index], replacer, space, String(index), indexPointer, depth + 1);
|
|
121
|
+
result += stringifiedValue === undefined ? "null" : stringifiedValue;
|
|
122
|
+
if (index + 1 < value.length) {
|
|
123
|
+
result += `,${padding}${space}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result + padding + "]";
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const stringifyObject = (value, replacer, space, pointer, depth) => {
|
|
130
|
+
const entries = Object.entries(value);
|
|
131
|
+
if (entries.length === 0) {
|
|
132
|
+
return "{}";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const padding = space ? `\n${space.repeat(depth - 1)}` : "";
|
|
136
|
+
const colonSpacing = space ? " " : "";
|
|
137
|
+
|
|
138
|
+
let result = "{" + padding + space;
|
|
139
|
+
for (let index = 0; index < entries.length; index++) {
|
|
140
|
+
const [key, value] = entries[index];
|
|
141
|
+
const keyPointer = JsonPointer.append(key, pointer);
|
|
142
|
+
const stringifiedValue = stringifyValue(value, replacer, space, key, keyPointer, depth + 1);
|
|
143
|
+
if (stringifiedValue !== undefined) {
|
|
144
|
+
result += JSON.stringify(key) + ":" + colonSpacing + stringifiedValue;
|
|
145
|
+
if (entries[index + 1]) {
|
|
146
|
+
result += `,${padding}${space}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return result + padding + "}";
|
|
151
|
+
};
|
package/lib/instance.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { append as pointerAppend } from "@hyperjump/json-pointer";
|
|
2
2
|
import curry from "just-curry-it";
|
|
3
3
|
import { toAbsoluteUri, jsonTypeOf } from "./common.js";
|
|
4
4
|
import * as Reference from "./reference.js";
|
|
@@ -27,7 +27,7 @@ export const typeOf = curry((doc, type) => jsonTypeOf(value(doc), type));
|
|
|
27
27
|
|
|
28
28
|
export const step = (key, doc) => ({
|
|
29
29
|
...doc,
|
|
30
|
-
pointer:
|
|
30
|
+
pointer: pointerAppend(key, doc.pointer),
|
|
31
31
|
value: value(doc)[key]
|
|
32
32
|
});
|
|
33
33
|
|
package/lib/media-types.js
CHANGED
|
@@ -7,7 +7,7 @@ export const addMediaTypePlugin = (contentType, plugin) => {
|
|
|
7
7
|
mediaTypePlugins[contentType] = plugin;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const parseResponse = (response) => {
|
|
11
11
|
const contentType = contentTypeParser.parse(response.headers.get("content-type"));
|
|
12
12
|
if (!(contentType.type in mediaTypePlugins)) {
|
|
13
13
|
throw Error(`${response.url} is not a schema. Found a document with media type: ${contentType.type}`);
|
package/lib/schema.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import curry from "just-curry-it";
|
|
2
2
|
import * as Pact from "@hyperjump/pact";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { jsonTypeOf, resolveUri, toAbsoluteUri, uriFragment, pathRelative } from "./common.js";
|
|
3
|
+
import { nil as nilPointer, append as pointerAppend, get as pointerGet } from "@hyperjump/json-pointer";
|
|
4
|
+
import { jsonTypeOf, resolveUri, toAbsoluteUri, uriFragment, pathRelative, jsonStringify } from "./common.js";
|
|
6
5
|
import fetch from "./fetch.js";
|
|
7
|
-
import
|
|
8
|
-
import
|
|
6
|
+
import { hasDialect, loadDialect, getKeywordName } from "./keywords.js";
|
|
7
|
+
import { parseResponse } from "./media-types.js";
|
|
9
8
|
import * as Reference from "./reference.js";
|
|
10
9
|
|
|
11
10
|
|
|
@@ -22,13 +21,13 @@ export const add = (schema, retrievalUri = undefined, contextDialectId = undefin
|
|
|
22
21
|
const dialectId = toAbsoluteUri(schema.$schema || contextDialectId || defaultDialectId);
|
|
23
22
|
delete schema.$schema;
|
|
24
23
|
|
|
25
|
-
if (!
|
|
24
|
+
if (!hasDialect(dialectId)) {
|
|
26
25
|
throw Error(`Encountered unknown dialect '${dialectId}'`);
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
// Identifiers
|
|
30
|
-
const idToken =
|
|
31
|
-
||
|
|
29
|
+
const idToken = getKeywordName(dialectId, "https://json-schema.org/keyword/id")
|
|
30
|
+
|| getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
|
|
32
31
|
if (retrievalUri === undefined && !(idToken in schema)) {
|
|
33
32
|
throw Error(`Unable to determine an identifier for the schema. Use the '${idToken}' keyword or pass a retrievalUri when loading the schema.`);
|
|
34
33
|
}
|
|
@@ -41,19 +40,19 @@ export const add = (schema, retrievalUri = undefined, contextDialectId = undefin
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
// Vocabulary
|
|
44
|
-
const vocabularyToken =
|
|
43
|
+
const vocabularyToken = getKeywordName(dialectId, "https://json-schema.org/keyword/vocabulary");
|
|
45
44
|
if (jsonTypeOf(schema[vocabularyToken], "object")) {
|
|
46
45
|
const allowUnknownKeywords = schema[vocabularyToken]["https://json-schema.org/draft/2019-09/vocab/core"]
|
|
47
46
|
|| schema[vocabularyToken]["https://json-schema.org/draft/2020-12/vocab/core"];
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
loadDialect(id, schema[vocabularyToken], allowUnknownKeywords);
|
|
50
49
|
delete schema[vocabularyToken];
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
const dynamicAnchors = {};
|
|
54
53
|
|
|
55
54
|
// Recursive anchor
|
|
56
|
-
const recursiveAnchorToken =
|
|
55
|
+
const recursiveAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2019-09/recursiveAnchor");
|
|
57
56
|
if (schema[recursiveAnchorToken] === true) {
|
|
58
57
|
dynamicAnchors[""] = `${id}#`;
|
|
59
58
|
}
|
|
@@ -64,7 +63,7 @@ export const add = (schema, retrievalUri = undefined, contextDialectId = undefin
|
|
|
64
63
|
schemaStore[id] = {
|
|
65
64
|
id: id,
|
|
66
65
|
dialectId: dialectId,
|
|
67
|
-
schema: processSchema(schema, id, dialectId,
|
|
66
|
+
schema: processSchema(schema, id, dialectId, nilPointer, anchors, dynamicAnchors),
|
|
68
67
|
anchors: anchors,
|
|
69
68
|
dynamicAnchors: dynamicAnchors,
|
|
70
69
|
validated: false
|
|
@@ -76,7 +75,7 @@ export const add = (schema, retrievalUri = undefined, contextDialectId = undefin
|
|
|
76
75
|
const processSchema = (subject, id, dialectId, pointer, anchors, dynamicAnchors) => {
|
|
77
76
|
if (jsonTypeOf(subject, "object")) {
|
|
78
77
|
// Legacy id
|
|
79
|
-
const legacyIdToken =
|
|
78
|
+
const legacyIdToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
|
|
80
79
|
if (typeof subject[legacyIdToken] === "string") {
|
|
81
80
|
if (subject[legacyIdToken][0] === "#") {
|
|
82
81
|
const anchor = decodeURIComponent(subject[legacyIdToken].slice(1));
|
|
@@ -92,7 +91,7 @@ const processSchema = (subject, id, dialectId, pointer, anchors, dynamicAnchors)
|
|
|
92
91
|
|
|
93
92
|
// Embedded Schema
|
|
94
93
|
const embeddedDialectId = typeof subject.$schema === "string" ? toAbsoluteUri(subject.$schema) : dialectId;
|
|
95
|
-
const idToken =
|
|
94
|
+
const idToken = getKeywordName(embeddedDialectId, "https://json-schema.org/keyword/id");
|
|
96
95
|
if (typeof subject[idToken] === "string") {
|
|
97
96
|
subject[idToken] = resolveUri(subject[idToken], id);
|
|
98
97
|
add(subject, undefined, dialectId);
|
|
@@ -100,37 +99,37 @@ const processSchema = (subject, id, dialectId, pointer, anchors, dynamicAnchors)
|
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
// Legacy dynamic anchor
|
|
103
|
-
const legacyDynamicAnchorToken =
|
|
102
|
+
const legacyDynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
|
|
104
103
|
if (typeof subject[legacyDynamicAnchorToken] === "string") {
|
|
105
104
|
dynamicAnchors[subject[legacyDynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
|
|
106
105
|
anchors[subject[legacyDynamicAnchorToken]] = pointer;
|
|
107
106
|
delete subject[legacyDynamicAnchorToken];
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
const dynamicAnchorToken =
|
|
109
|
+
const dynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/dynamicAnchor");
|
|
111
110
|
if (typeof subject[dynamicAnchorToken] === "string") {
|
|
112
111
|
dynamicAnchors[subject[dynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
|
|
113
112
|
delete subject[dynamicAnchorToken];
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
const anchorToken =
|
|
115
|
+
const anchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/anchor");
|
|
117
116
|
if (typeof subject[anchorToken] === "string") {
|
|
118
117
|
anchors[subject[anchorToken]] = pointer;
|
|
119
118
|
delete subject[anchorToken];
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
// Legacy $ref
|
|
123
|
-
const jrefToken =
|
|
122
|
+
const jrefToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/ref");
|
|
124
123
|
if (typeof subject[jrefToken] === "string") {
|
|
125
124
|
return Reference.cons(subject[jrefToken], subject);
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
for (const key in subject) {
|
|
129
|
-
subject[key] = processSchema(subject[key], id, dialectId,
|
|
128
|
+
subject[key] = processSchema(subject[key], id, dialectId, pointerAppend(key, pointer), anchors, dynamicAnchors);
|
|
130
129
|
}
|
|
131
130
|
} else if (Array.isArray(subject)) {
|
|
132
131
|
for (let index = 0; index < subject.length; index++) {
|
|
133
|
-
subject[index] = processSchema(subject[index], id, dialectId,
|
|
132
|
+
subject[index] = processSchema(subject[index], id, dialectId, pointerAppend(index, pointer), anchors, dynamicAnchors);
|
|
134
133
|
}
|
|
135
134
|
}
|
|
136
135
|
|
|
@@ -148,7 +147,7 @@ export const markValidated = (id) => {
|
|
|
148
147
|
const nil = {
|
|
149
148
|
id: undefined,
|
|
150
149
|
dialectId: undefined,
|
|
151
|
-
pointer:
|
|
150
|
+
pointer: nilPointer,
|
|
152
151
|
schema: undefined,
|
|
153
152
|
value: undefined,
|
|
154
153
|
anchors: {},
|
|
@@ -168,11 +167,11 @@ export const get = async (url, contextDoc = nil) => {
|
|
|
168
167
|
throw Error(`Failed to retrieve schema with id: ${id}`);
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
const [schema, contextDialectId] = await
|
|
170
|
+
const [schema, contextDialectId] = await parseResponse(response);
|
|
172
171
|
|
|
173
172
|
// Try to determine the dialect from the meta-schema if it isn't already known
|
|
174
173
|
const dialectId = toAbsoluteUri(schema.$schema || contextDialectId || defaultDialectId);
|
|
175
|
-
if (!
|
|
174
|
+
if (!hasDialect(dialectId) && !hasStoredSchema(dialectId)) {
|
|
176
175
|
await get(dialectId);
|
|
177
176
|
}
|
|
178
177
|
|
|
@@ -184,7 +183,7 @@ export const get = async (url, contextDoc = nil) => {
|
|
|
184
183
|
const doc = {
|
|
185
184
|
...storedSchema,
|
|
186
185
|
pointer: pointer,
|
|
187
|
-
value:
|
|
186
|
+
value: pointerGet(pointer, storedSchema.schema)
|
|
188
187
|
};
|
|
189
188
|
|
|
190
189
|
return followReferences(doc);
|
|
@@ -210,7 +209,7 @@ export const step = (key, doc) => {
|
|
|
210
209
|
const storedSchema = getStoredSchema(doc.id);
|
|
211
210
|
return followReferences({
|
|
212
211
|
...doc,
|
|
213
|
-
pointer:
|
|
212
|
+
pointer: pointerAppend(`${key}`, doc.pointer),
|
|
214
213
|
value: value(doc)[key],
|
|
215
214
|
validated: storedSchema.validated
|
|
216
215
|
});
|
|
@@ -241,11 +240,12 @@ const toSchemaDefaultOptions = {
|
|
|
241
240
|
export const toSchema = (schemaDoc, options = {}) => {
|
|
242
241
|
const fullOptions = { ...toSchemaDefaultOptions, ...options };
|
|
243
242
|
|
|
244
|
-
const idToken =
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
243
|
+
const idToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/id")
|
|
244
|
+
|| getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-04/id");
|
|
245
|
+
const anchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/anchor");
|
|
246
|
+
const dynamicAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/dynamicAnchor");
|
|
247
|
+
const legacyDynamicAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
|
|
248
|
+
const recursiveAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/recursiveAnchor");
|
|
249
249
|
|
|
250
250
|
const anchors = {};
|
|
251
251
|
for (const anchor in schemaDoc.anchors) {
|
|
@@ -260,7 +260,7 @@ export const toSchema = (schemaDoc, options = {}) => {
|
|
|
260
260
|
dynamicAnchors[pointer] = anchor;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
const schema = JSON.parse(
|
|
263
|
+
const schema = JSON.parse(jsonStringify(schemaDoc.schema, (key, value, pointer) => {
|
|
264
264
|
if (Reference.isReference(value)) {
|
|
265
265
|
const refValue = Reference.value(value);
|
|
266
266
|
if (fullOptions.includeEmbedded || !(idToken in refValue)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperjump/json-schema",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "A JSON Schema validator with support for custom keywords, vocabularies, and dialects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./stable/index.js",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"./openapi-3-1": "./openapi-3-1/index.js",
|
|
16
16
|
"./experimental": "./lib/experimental.js",
|
|
17
17
|
"./schema/experimental": "./lib/schema.js",
|
|
18
|
-
"./instance/experimental": "./lib/instance.js"
|
|
18
|
+
"./instance/experimental": "./lib/instance.js",
|
|
19
|
+
"./bundle": "./bundle/index.js"
|
|
19
20
|
},
|
|
20
21
|
"browser": {
|
|
21
22
|
"./lib/fetch.js": "./lib/fetch.browser.js"
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"scripts": {
|
|
24
25
|
"clean": "xargs -a .gitignore rm -rf",
|
|
25
26
|
"lint": "eslint lib stable draft-* openapi-*",
|
|
26
|
-
"test": "mocha 'lib/**/*.spec.ts' 'stable/**/*.spec.ts' 'draft-*/**/*.spec.ts' 'openapi-*/**/*.spec.ts'"
|
|
27
|
+
"test": "mocha 'lib/**/*.spec.ts' 'stable/**/*.spec.ts' 'draft-*/**/*.spec.ts' 'openapi-*/**/*.spec.ts' 'bundle/**/*.spec.ts'"
|
|
27
28
|
},
|
|
28
29
|
"repository": "github:hyperjump-io/json-schema",
|
|
29
30
|
"keywords": [
|
|
@@ -61,12 +62,12 @@
|
|
|
61
62
|
"yaml": "*"
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
|
-
"@hyperjump/json": "^
|
|
65
|
-
"@hyperjump/json-pointer": "^0.9.5",
|
|
65
|
+
"@hyperjump/json-pointer": "^1.0.0",
|
|
66
66
|
"@hyperjump/pact": "^0.2.4",
|
|
67
67
|
"@hyperjump/uri": "^1.0.0",
|
|
68
68
|
"content-type": "^1.0.4",
|
|
69
69
|
"fastest-stable-stringify": "^2.0.2",
|
|
70
|
-
"node-fetch": "^3.3.0"
|
|
70
|
+
"node-fetch": "^3.3.0",
|
|
71
|
+
"uuid": "^9.0.0"
|
|
71
72
|
}
|
|
72
73
|
}
|