@hyperjump/json-schema 1.8.0 → 1.9.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 +57 -56
- package/annotations/annotated-instance.d.ts +4 -82
- package/annotations/annotated-instance.js +31 -33
- package/annotations/index.d.ts +7 -3
- package/annotations/index.js +8 -108
- package/draft-04/additionalItems.js +1 -1
- package/draft-04/items.js +1 -1
- package/lib/core.js +8 -50
- package/lib/experimental.d.ts +5 -7
- package/lib/experimental.js +1 -1
- package/lib/index.d.ts +5 -2
- package/lib/instance.d.ts +24 -23
- package/lib/instance.js +133 -38
- package/lib/keywords/additionalProperties.js +12 -7
- package/lib/keywords/allOf.js +7 -1
- package/lib/keywords/comment.js +4 -3
- package/lib/keywords/const.js +1 -1
- package/lib/keywords/contentEncoding.js +12 -2
- package/lib/keywords/contentMediaType.js +12 -2
- package/lib/keywords/contentSchema.js +8 -3
- package/lib/keywords/default.js +12 -2
- package/lib/keywords/dependentRequired.js +12 -3
- package/lib/keywords/dependentSchemas.js +12 -3
- package/lib/keywords/deprecated.js +12 -2
- package/lib/keywords/description.js +12 -2
- package/lib/keywords/enum.js +5 -2
- package/lib/keywords/examples.js +12 -2
- package/lib/keywords/format.js +12 -2
- package/lib/keywords/items.js +9 -6
- package/lib/keywords/maxContains.js +6 -2
- package/lib/keywords/maxItems.js +3 -1
- package/lib/keywords/maxLength.js +3 -1
- package/lib/keywords/minContains.js +6 -2
- package/lib/keywords/minItems.js +3 -1
- package/lib/keywords/minLength.js +3 -1
- package/lib/keywords/oneOf.js +0 -4
- package/lib/keywords/pattern.js +3 -1
- package/lib/keywords/patternProperties.js +18 -9
- package/lib/keywords/prefixItems.js +21 -6
- package/lib/keywords/properties.js +16 -7
- package/lib/keywords/propertyDependencies.js +18 -6
- package/lib/keywords/propertyNames.js +13 -5
- package/lib/keywords/readOnly.js +12 -2
- package/lib/keywords/title.js +12 -2
- package/lib/keywords/unevaluatedItems.js +14 -7
- package/lib/keywords/unevaluatedProperties.js +15 -8
- package/lib/keywords/uniqueItems.js +1 -1
- package/lib/keywords/unknown.js +12 -2
- package/lib/keywords/validation.js +24 -27
- package/lib/keywords/writeOnly.js +12 -2
- package/lib/output.js +43 -0
- package/openapi-3-0/discriminator.js +12 -2
- package/openapi-3-0/example.js +12 -2
- package/openapi-3-0/externalDocs.js +12 -2
- package/openapi-3-0/index.js +2 -2
- package/openapi-3-0/nullable.js +8 -2
- package/openapi-3-0/xml.js +12 -2
- package/openapi-3-1/index.js +2 -2
- package/package.json +3 -3
- package/bundle/README.md +0 -134
- package/lib/keywords/meta-data.js +0 -7
package/README.md
CHANGED
|
@@ -518,28 +518,23 @@ These are available from the `@hyperjump/json-schema/experimental` export.
|
|
|
518
518
|
is needed for compiling sub-schemas. The `parentSchema` parameter is
|
|
519
519
|
primarily useful for looking up the value of an adjacent keyword that
|
|
520
520
|
might effect this one.
|
|
521
|
-
* interpret: (compiledKeywordValue: any, instance:
|
|
521
|
+
* interpret: (compiledKeywordValue: any, instance: JsonNode, ast: AST, dynamicAnchors: object, quiet: boolean, schemaLocation: string) => boolean
|
|
522
522
|
|
|
523
523
|
This function takes the value returned by the `compile` function and
|
|
524
524
|
the instance value that is being validated and returns whether the
|
|
525
525
|
value is valid or not. The other parameters are only needed for
|
|
526
526
|
validating sub-schemas.
|
|
527
|
-
* collectEvaluatedProperties?: (compiledKeywordValue: any, instance:
|
|
527
|
+
* collectEvaluatedProperties?: (compiledKeywordValue: any, instance: JsonNode, ast: AST, dynamicAnchors: object) => Set\<string> | false
|
|
528
528
|
|
|
529
529
|
If the keyword is an applicator, it will need to implement this
|
|
530
530
|
function for `unevaluatedProperties` to work as expected.
|
|
531
|
-
* collectEvaluatedItems?: (compiledKeywordValue: A, instance:
|
|
531
|
+
* collectEvaluatedItems?: (compiledKeywordValue: A, instance: JsonNode, ast: AST, dynamicAnchors: object) => Set\<number> | false
|
|
532
532
|
|
|
533
533
|
If the keyword is an applicator, it will need to implement this
|
|
534
534
|
function for `unevaluatedItems` to work as expected.
|
|
535
535
|
* collectExternalIds?: (visited: Set\<string>, parentSchema: Browser, schema: Browser) => Set\<string>
|
|
536
536
|
If the keyword is an applicator, it will need to implement this
|
|
537
537
|
function to work properly with the [bundle](#bundling) feature.
|
|
538
|
-
* annotation?: (compiledKeywordValue: any) => any
|
|
539
|
-
|
|
540
|
-
If the keyword is an annotation, it will need to implement this
|
|
541
|
-
function to work with the [annotation](#annotations-experimental)
|
|
542
|
-
functions.
|
|
543
538
|
* **defineVocabulary**: (id: string, keywords: { [keyword: string]: string }) => void
|
|
544
539
|
|
|
545
540
|
Define a vocabulary that maps keyword name to keyword URIs defined using
|
|
@@ -607,68 +602,69 @@ These are available from the `@hyperjump/json-schema/experimental` export.
|
|
|
607
602
|
|
|
608
603
|
Return a compiled schema. This is useful if you're creating tooling for
|
|
609
604
|
something other than validation.
|
|
610
|
-
* **interpret**: (schema: CompiledSchema, instance:
|
|
605
|
+
* **interpret**: (schema: CompiledSchema, instance: JsonNode, outputFormat: OutputFormat = BASIC) => OutputUnit
|
|
611
606
|
|
|
612
607
|
A curried function for validating an instance against a compiled schema.
|
|
613
608
|
This can be useful for creating custom output formats.
|
|
614
609
|
|
|
615
|
-
* **OutputFormat**: **FLAG** | **BASIC**
|
|
610
|
+
* **OutputFormat**: **FLAG** | **BASIC**
|
|
616
611
|
|
|
617
612
|
In addition to the `FLAG` output format in the Stable API, the Experimental
|
|
618
|
-
API includes support for the `BASIC
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
messages as needed.
|
|
613
|
+
API includes support for the `BASIC` format as specified in the 2019-09
|
|
614
|
+
specification (with some minor customizations). This implementation doesn't
|
|
615
|
+
include annotations or human readable error messages. The output can be
|
|
616
|
+
processed to create human readable error messages as needed.
|
|
623
617
|
|
|
624
618
|
## Instance API (experimental)
|
|
625
619
|
|
|
626
620
|
These functions are available from the
|
|
627
621
|
`@hyperjump/json-schema/instance/experimental` export.
|
|
628
622
|
|
|
629
|
-
This library uses
|
|
630
|
-
|
|
631
|
-
set of functions for working with InstanceDocuments.
|
|
623
|
+
This library uses JsonNode objects to represent instances. You'll work with
|
|
624
|
+
these objects if you create a custom keyword.
|
|
632
625
|
|
|
633
626
|
This API uses generators to iterate over arrays and objects. If you like using
|
|
634
627
|
higher order functions like `map`/`filter`/`reduce`, see
|
|
635
628
|
[`@hyperjump/pact`](https://github.com/hyperjump-io/pact) for utilities for
|
|
636
629
|
working with generators and async generators.
|
|
637
630
|
|
|
638
|
-
* **
|
|
631
|
+
* **fromJs**: (value: any, uri?: string) => JsonNode
|
|
639
632
|
|
|
640
|
-
Construct
|
|
641
|
-
* **get**: (url: string,
|
|
633
|
+
Construct a JsonNode from a JavaScript value.
|
|
634
|
+
* **get**: (url: string, instance: JsonNode) => JsonNode
|
|
642
635
|
|
|
643
|
-
Apply a same-resource reference to a
|
|
644
|
-
* **uri**: (
|
|
636
|
+
Apply a same-resource reference to a JsonNode.
|
|
637
|
+
* **uri**: (instance: JsonNode) => string
|
|
645
638
|
|
|
646
|
-
Returns a URI for the value the
|
|
647
|
-
* **value**: (
|
|
639
|
+
Returns a URI for the value the JsonNode represents.
|
|
640
|
+
* **value**: (instance: JsonNode) => any
|
|
648
641
|
|
|
649
|
-
Returns the value the
|
|
650
|
-
* **has**: (key: string,
|
|
642
|
+
Returns the value the JsonNode represents.
|
|
643
|
+
* **has**: (key: string, instance: JsonNode) => boolean
|
|
651
644
|
|
|
652
|
-
|
|
653
|
-
|
|
645
|
+
Returns whether or not "key" is a property name in a JsonNode that
|
|
646
|
+
represents an object.
|
|
647
|
+
* **typeOf**: (instance: JsonNode) => string
|
|
654
648
|
|
|
655
|
-
|
|
656
|
-
|
|
649
|
+
The JSON type of the JsonNode. In addition to the standard JSON types,
|
|
650
|
+
there's also the `property` type that indicates a property name/value pair
|
|
651
|
+
in an object.
|
|
652
|
+
* **step**: (key: string, instance: JsonNode) => JsonType
|
|
657
653
|
|
|
658
|
-
Similar to `
|
|
659
|
-
* **iter**: (
|
|
654
|
+
Similar to indexing into a object or array using the `[]` operator.
|
|
655
|
+
* **iter**: (instance: JsonNode) => Generator\<JsonNode>
|
|
660
656
|
|
|
661
|
-
Iterate over the items in the array that the
|
|
662
|
-
* **entries**: (
|
|
657
|
+
Iterate over the items in the array that the JsonNode represents.
|
|
658
|
+
* **entries**: (instance: JsonNode) => Generator\<[JsonNode, JsonNode]>
|
|
663
659
|
|
|
664
|
-
Similar to `Object.entries`, but yields
|
|
665
|
-
* **values**: (
|
|
660
|
+
Similar to `Object.entries`, but yields JsonNodes for keys and values.
|
|
661
|
+
* **values**: (instance: JsonNode) => Generator\<JsonNode>
|
|
666
662
|
|
|
667
|
-
Similar to `Object.values`, but yields
|
|
668
|
-
* **keys**: (
|
|
663
|
+
Similar to `Object.values`, but yields JsonNodes for values.
|
|
664
|
+
* **keys**: (instance: JsonNode) => Generator\<JsonNode>
|
|
669
665
|
|
|
670
|
-
Similar to `Object.keys
|
|
671
|
-
* **length**: (
|
|
666
|
+
Similar to `Object.keys`, but yields JsonNodes for keys.
|
|
667
|
+
* **length**: (instance: JsonNode) => number
|
|
672
668
|
|
|
673
669
|
Similar to `Array.prototype.length`.
|
|
674
670
|
|
|
@@ -678,12 +674,13 @@ module provides utilities for working with JSON documents annotated with JSON
|
|
|
678
674
|
Schema.
|
|
679
675
|
|
|
680
676
|
### Usage
|
|
681
|
-
An annotated JSON document is represented as
|
|
682
|
-
|
|
683
|
-
|
|
677
|
+
An annotated JSON document is represented as a
|
|
678
|
+
(JsonNode)[#/instance-api-experimental] AST. You can use this AST to traverse
|
|
679
|
+
the data structure and get annotations for the values it represents.
|
|
684
680
|
|
|
685
681
|
```javascript
|
|
686
|
-
import {
|
|
682
|
+
import { registerSchema } from "@hyperjump/json-schema/draft/2020-12";
|
|
683
|
+
import { annotate } from "@hyperjump/json-schema/annotations/experimental";
|
|
687
684
|
import * as AnnotatedInstance from "@hyperjump/json-schema/annotated-instance/experimental";
|
|
688
685
|
|
|
689
686
|
|
|
@@ -736,13 +733,14 @@ const unknowns = AnnotatedInstance.annotation(instance, "unknown", dialectId); /
|
|
|
736
733
|
const types = AnnotatedInstance.annotation(instance, "type", dialectId); // => []
|
|
737
734
|
|
|
738
735
|
// Get the title of each of the properties in the object
|
|
739
|
-
for (const [
|
|
740
|
-
|
|
736
|
+
for (const [propertyNameNode, propertyInstance] of AnnotatedInstance.entries(instance)) {
|
|
737
|
+
const propertyName = AnnotatedInstance.value(propertyName);
|
|
738
|
+
console.log(propertyName, AnnotatedInstance.annotation(propertyInstance, "title", dialectId));
|
|
741
739
|
}
|
|
742
740
|
|
|
743
741
|
// List all locations in the instance that are deprecated
|
|
744
742
|
for (const deprecated of AnnotatedInstance.annotatedWith(instance, "deprecated", dialectId)) {
|
|
745
|
-
if (AnnotatedInstance.annotation(
|
|
743
|
+
if (AnnotatedInstance.annotation(deprecated, "deprecated", dialectId)[0]) {
|
|
746
744
|
logger.warn(`The value at '${deprecated.pointer}' has been deprecated.`); // => (Example) "WARN: The value at '/name' has been deprecated."
|
|
747
745
|
}
|
|
748
746
|
}
|
|
@@ -752,13 +750,18 @@ for (const deprecated of AnnotatedInstance.annotatedWith(instance, "deprecated",
|
|
|
752
750
|
These are available from the `@hyperjump/json-schema/annotations/experimental`
|
|
753
751
|
export.
|
|
754
752
|
|
|
755
|
-
* **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat =
|
|
753
|
+
* **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat = BASIC) => Promise\<JsonNode>
|
|
756
754
|
|
|
757
755
|
Annotate an instance using the given schema. The function is curried to
|
|
758
756
|
allow compiling the schema once and applying it to multiple instances. This
|
|
759
757
|
may throw an [InvalidSchemaError](#api) if there is a problem with the
|
|
760
758
|
schema or a ValidationError if the instance doesn't validate against the
|
|
761
759
|
schema.
|
|
760
|
+
* **interpret**: (compiledSchema: CompiledSchema, instance: JsonNode, outputFormat: OutputFormat = BASIC) => JsonNode
|
|
761
|
+
|
|
762
|
+
Annotate a JsonNode object rather than a plain JavaScript value. This might
|
|
763
|
+
be useful when building tools on top of the annotation functionality, but
|
|
764
|
+
you probably don't need it.
|
|
762
765
|
* **ValidationError**: Error & { output: OutputUnit }
|
|
763
766
|
The `output` field contains an `OutputUnit` with information about the
|
|
764
767
|
error.
|
|
@@ -769,15 +772,13 @@ These are available from the
|
|
|
769
772
|
following functions are available in addition to the functions available in the
|
|
770
773
|
[Instance API](#instance-api-experimental).
|
|
771
774
|
|
|
772
|
-
* **annotation**: (instance:
|
|
775
|
+
* **annotation**: (instance: JsonNode, keyword: string, dialect?: string): any[];
|
|
773
776
|
|
|
774
|
-
Get the annotations for a
|
|
775
|
-
|
|
776
|
-
* **annotatedWith**: (instance: AnnotatedInstance, keyword: string, dialectId?: string) => AnnotatedInstance[]
|
|
777
|
+
Get the annotations for a keyword for the value represented by the JsonNode.
|
|
778
|
+
* **annotatedWith**: (instance: JsonNode, keyword: string, dialect?: string): JsonNode[];
|
|
777
779
|
|
|
778
|
-
Get
|
|
779
|
-
|
|
780
|
-
* **annotate**: (instance: AnnotatedInstance, keywordId: string, value: any) => AnnotatedInstance
|
|
780
|
+
Get all JsonNodes that are annotated with the given keyword.
|
|
781
|
+
* **setAnnotation**: (instance: JsonNode, keywordId: string, value: any) => JsonNode
|
|
781
782
|
|
|
782
783
|
Add an annotation to an instance. This is used internally, you probably
|
|
783
784
|
don't need it.
|
|
@@ -1,83 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export const setAnnotation: (keywordUri: string, schemaLocation: string, value: string) => void;
|
|
2
|
+
export const annotation: <A>(instance: JsonNode, keyword: string, dialectUri?: string) => A[];
|
|
3
|
+
export const annotatedWith: (instance: JsonNode, keyword: string, dialectUri?: string) => JsonNode[];
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
export const annotate: (instance: AnnotatedJsonDocument, keyword: string, value: string) => AnnotatedJsonDocument;
|
|
6
|
-
export const annotation: <A>(instance: AnnotatedJsonDocument, keyword: string, dialectId?: string) => A[];
|
|
7
|
-
export const annotatedWith: (instance: AnnotatedJsonDocument, keyword: string, dialectId?: string) => AnnotatedJsonDocument[];
|
|
8
|
-
export const nil: AnnotatedJsonDocument<undefined>;
|
|
9
|
-
export const cons: (instance: Json, id?: string) => AnnotatedJsonDocument;
|
|
10
|
-
export const get: (uri: string, context?: AnnotatedJsonDocument) => AnnotatedJsonDocument;
|
|
11
|
-
export const uri: (doc: AnnotatedJsonDocument) => string;
|
|
12
|
-
export const value: <A extends Json>(doc: AnnotatedJsonDocument<A>) => A;
|
|
13
|
-
export const has: (key: string, doc: AnnotatedJsonDocument<JsonObject>) => boolean;
|
|
14
|
-
export const typeOf: (
|
|
15
|
-
(doc: AnnotatedJsonDocument, type: "null") => doc is AnnotatedJsonDocument<null>
|
|
16
|
-
) & (
|
|
17
|
-
(doc: AnnotatedJsonDocument, type: "boolean") => doc is AnnotatedJsonDocument<boolean>
|
|
18
|
-
) & (
|
|
19
|
-
(doc: AnnotatedJsonDocument, type: "object") => doc is AnnotatedJsonDocument<JsonObject>
|
|
20
|
-
) & (
|
|
21
|
-
(doc: AnnotatedJsonDocument, type: "array") => doc is AnnotatedJsonDocument<Json[]>
|
|
22
|
-
) & (
|
|
23
|
-
(doc: AnnotatedJsonDocument, type: "number" | "integer") => doc is AnnotatedJsonDocument<number>
|
|
24
|
-
) & (
|
|
25
|
-
(doc: AnnotatedJsonDocument, type: "string") => doc is AnnotatedJsonDocument<string>
|
|
26
|
-
) & (
|
|
27
|
-
(doc: AnnotatedJsonDocument, type: JsonType) => boolean
|
|
28
|
-
) & (
|
|
29
|
-
(doc: AnnotatedJsonDocument) => (type: JsonType) => boolean
|
|
30
|
-
);
|
|
31
|
-
export const step: (key: string, doc: AnnotatedJsonDocument<JsonObject | Json[]>) => AnnotatedJsonDocument<typeof doc.value>;
|
|
32
|
-
export const entries: (doc: AnnotatedJsonDocument<JsonObject>) => [string, AnnotatedJsonDocument][];
|
|
33
|
-
export const keys: (doc: AnnotatedJsonDocument<JsonObject>) => string[];
|
|
34
|
-
export const map: (
|
|
35
|
-
<A>(fn: MapFn<A>, doc: AnnotatedJsonDocument<Json[]>) => A[]
|
|
36
|
-
) & (
|
|
37
|
-
<A>(fn: MapFn<A>) => (doc: AnnotatedJsonDocument<Json[]>) => A[]
|
|
38
|
-
);
|
|
39
|
-
export const forEach: (
|
|
40
|
-
(fn: ForEachFn, doc: AnnotatedJsonDocument<Json[]>) => void
|
|
41
|
-
) & (
|
|
42
|
-
(fn: ForEachFn) => (doc: AnnotatedJsonDocument<Json[]>) => void
|
|
43
|
-
);
|
|
44
|
-
export const filter: (
|
|
45
|
-
(fn: FilterFn, doc: AnnotatedJsonDocument<Json[]>) => AnnotatedJsonDocument[]
|
|
46
|
-
) & (
|
|
47
|
-
(fn: FilterFn) => (doc: AnnotatedJsonDocument<Json[]>) => AnnotatedJsonDocument[]
|
|
48
|
-
);
|
|
49
|
-
export const reduce: (
|
|
50
|
-
<A>(fn: ReduceFn<A>, acc: A, doc: AnnotatedJsonDocument<Json[]>) => A
|
|
51
|
-
) & (
|
|
52
|
-
<A>(fn: ReduceFn<A>) => (acc: A, doc: AnnotatedJsonDocument<Json[]>) => A
|
|
53
|
-
) & (
|
|
54
|
-
<A>(fn: ReduceFn<A>) => (acc: A) => (doc: AnnotatedJsonDocument<Json[]>) => A
|
|
55
|
-
);
|
|
56
|
-
export const every: (
|
|
57
|
-
(fn: FilterFn, doc: AnnotatedJsonDocument<Json[]>) => boolean
|
|
58
|
-
) & (
|
|
59
|
-
(fn: FilterFn) => (doc: AnnotatedJsonDocument<Json[]>) => boolean
|
|
60
|
-
);
|
|
61
|
-
export const some: (
|
|
62
|
-
(fn: FilterFn, doc: AnnotatedJsonDocument<Json[]>) => boolean
|
|
63
|
-
) & (
|
|
64
|
-
(fn: FilterFn) => (doc: AnnotatedJsonDocument<Json[]>) => boolean
|
|
65
|
-
);
|
|
66
|
-
export const length: (doc: AnnotatedJsonDocument<Json[] | string>) => number;
|
|
67
|
-
|
|
68
|
-
type MapFn<A> = (element: AnnotatedJsonDocument, index: number) => A;
|
|
69
|
-
type ForEachFn = (element: AnnotatedJsonDocument, index: number) => void;
|
|
70
|
-
type FilterFn = (element: AnnotatedJsonDocument, index: number) => boolean;
|
|
71
|
-
type ReduceFn<A> = (accumulator: A, currentValue: AnnotatedJsonDocument, index: number) => A;
|
|
72
|
-
|
|
73
|
-
export type AnnotatedJsonDocument<A extends Json | undefined = Json> = {
|
|
74
|
-
id: string;
|
|
75
|
-
pointer: string;
|
|
76
|
-
instance: Json;
|
|
77
|
-
value: A;
|
|
78
|
-
annotations: {
|
|
79
|
-
[pointer: string]: {
|
|
80
|
-
[keyword: string]: unknown[]
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
};
|
|
5
|
+
export * from "../lib/instance.js";
|
|
@@ -1,50 +1,48 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as JsonPointer from "@hyperjump/json-pointer";
|
|
2
|
+
import * as Instance from "../lib/instance.js";
|
|
3
3
|
import { getKeywordId } from "../lib/keywords.js";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
const defaultDialectId = "https://json-schema.org/validation";
|
|
7
7
|
|
|
8
|
-
export const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
value: instance
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
export const annotation = (instance, keyword, dialectId = defaultDialectId) => {
|
|
17
|
-
const keywordId = getKeywordId(keyword, dialectId);
|
|
18
|
-
return instance.annotations[instance.pointer]?.[keywordId] || [];
|
|
8
|
+
export const setAnnotation = (node, keywordUri, schemaLocation, value) => {
|
|
9
|
+
if (!(keywordUri in node.annotations)) {
|
|
10
|
+
node.annotations[keywordUri] = {};
|
|
11
|
+
}
|
|
12
|
+
node.annotations[keywordUri][schemaLocation] = value;
|
|
19
13
|
};
|
|
20
14
|
|
|
21
|
-
export const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
export const annotation = (node, keyword, dialect = defaultDialectId) => {
|
|
16
|
+
const keywordUri = getKeywordId(keyword, dialect);
|
|
17
|
+
|
|
18
|
+
let currentNode = node.root;
|
|
19
|
+
const errors = Object.keys(node.root.errors);
|
|
20
|
+
for (let segment of JsonPointer.pointerSegments(node.pointer)) {
|
|
21
|
+
segment = segment === "-" && currentNode.typeOf() === "array" ? currentNode.length() : segment;
|
|
22
|
+
currentNode = Instance.step(segment, currentNode);
|
|
23
|
+
errors.push(...Object.keys(currentNode.errors));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const annotations = [];
|
|
27
|
+
for (const schemaLocation in node.annotations[keywordUri]) {
|
|
28
|
+
if (!errors.some((error) => schemaLocation.startsWith(error))) {
|
|
29
|
+
annotations.unshift(node.annotations[keywordUri][schemaLocation]);
|
|
33
30
|
}
|
|
34
|
-
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return annotations;
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
export const annotatedWith = (instance, keyword, dialectId = defaultDialectId) => {
|
|
38
|
-
const
|
|
37
|
+
const nodes = [];
|
|
39
38
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
instances.push(get(`#${instancePointer}`, instance));
|
|
39
|
+
for (const node of Instance.allNodes(instance)) {
|
|
40
|
+
if (annotation(node, keyword, dialectId).length > 0) {
|
|
41
|
+
nodes.push(node);
|
|
44
42
|
}
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
return
|
|
45
|
+
return nodes;
|
|
48
46
|
};
|
|
49
47
|
|
|
50
|
-
export * from "../lib/instance.js";
|
|
48
|
+
export * from "../lib/instance.js";
|
package/annotations/index.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { OutputFormat, OutputUnit } from "../lib/index.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { CompiledSchema } from "../lib/experimental.js";
|
|
3
|
+
import type { JsonNode } from "../lib/json-node.js";
|
|
4
|
+
import type { Json } from "@hyperjump/json-pointer";
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
export const annotate: (
|
|
6
|
-
(schemaUrl: string, value:
|
|
8
|
+
(schemaUrl: string, value: Json, outputFormat?: OutputFormat) => Promise<JsonNode>
|
|
7
9
|
) & (
|
|
8
10
|
(schemaUrl: string) => Promise<Annotator>
|
|
9
11
|
);
|
|
10
12
|
|
|
11
|
-
export type Annotator = (value:
|
|
13
|
+
export type Annotator = (value: Json, outputFormat?: OutputFormat) => JsonNode;
|
|
14
|
+
|
|
15
|
+
export const interpret: (compiledSchema: CompiledSchema, value: JsonNode, outputFormat?: OutputFormat) => JsonNode;
|
|
12
16
|
|
|
13
17
|
export class ValidationError extends Error {
|
|
14
18
|
public output: OutputUnit;
|
package/annotations/index.js
CHANGED
|
@@ -1,123 +1,23 @@
|
|
|
1
|
-
import { subscribe, unsubscribe } from "../lib/pubsub.js";
|
|
2
|
-
import * as Instance from "./annotated-instance.js";
|
|
3
1
|
import { ValidationError } from "./validation-error.js";
|
|
4
|
-
import { getSchema,
|
|
2
|
+
import { getSchema, compile, interpret as validate, BASIC } from "../lib/experimental.js";
|
|
3
|
+
import * as Instance from "../lib/instance.js";
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
export const annotate = async (schemaUri, json = undefined, outputFormat = undefined) => {
|
|
8
|
-
loadKeywordSupport();
|
|
9
7
|
const schema = await getSchema(schemaUri);
|
|
10
8
|
const compiled = await compile(schema);
|
|
11
|
-
const interpretAst = (json, outputFormat) => interpret(compiled, Instance.
|
|
9
|
+
const interpretAst = (json, outputFormat) => interpret(compiled, Instance.fromJs(json), outputFormat);
|
|
12
10
|
|
|
13
11
|
return json === undefined ? interpretAst : interpretAst(json, outputFormat);
|
|
14
12
|
};
|
|
15
13
|
|
|
16
|
-
const interpret = ({ ast, schemaUri }, instance, outputFormat = BASIC) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const result = validate({ ast, schemaUri }, instance, outputFormat);
|
|
22
|
-
if (!result.valid) {
|
|
23
|
-
throw new ValidationError(result);
|
|
24
|
-
}
|
|
25
|
-
} finally {
|
|
26
|
-
unsubscribe("result", subscriptionToken);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return output[0];
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const outputHandler = (output) => {
|
|
33
|
-
let isPassing = true;
|
|
34
|
-
const instanceStack = [];
|
|
35
|
-
|
|
36
|
-
return (message, resultNode) => {
|
|
37
|
-
if (message === "result.start") {
|
|
38
|
-
instanceStack.push(output[0]);
|
|
39
|
-
isPassing = true;
|
|
40
|
-
} else if (message === "result" && isPassing) {
|
|
41
|
-
output[0] = Instance.get(resultNode.instanceLocation, output[0]);
|
|
42
|
-
|
|
43
|
-
if (resultNode.valid) {
|
|
44
|
-
const keywordHandler = getKeyword(resultNode.keyword);
|
|
45
|
-
if (keywordHandler?.annotation) {
|
|
46
|
-
const annotation = keywordHandler.annotation(resultNode.ast);
|
|
47
|
-
output[0] = Instance.annotate(output[0], resultNode.keyword, annotation);
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
output[0] = instanceStack[instanceStack.length - 1];
|
|
51
|
-
isPassing = false;
|
|
52
|
-
}
|
|
53
|
-
} else if (message === "result.end") {
|
|
54
|
-
instanceStack.pop();
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const identity = (a) => a;
|
|
60
|
-
|
|
61
|
-
const loadKeywordSupport = () => {
|
|
62
|
-
const title = getKeyword("https://json-schema.org/keyword/title");
|
|
63
|
-
if (title) {
|
|
64
|
-
title.annotation = identity;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const description = getKeyword("https://json-schema.org/keyword/description");
|
|
68
|
-
if (description) {
|
|
69
|
-
description.annotation = identity;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const _default = getKeyword("https://json-schema.org/keyword/default");
|
|
73
|
-
if (_default) {
|
|
74
|
-
_default.annotation = identity;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const deprecated = getKeyword("https://json-schema.org/keyword/deprecated");
|
|
78
|
-
if (deprecated) {
|
|
79
|
-
deprecated.annotation = identity;
|
|
14
|
+
export const interpret = ({ ast, schemaUri }, instance, outputFormat = BASIC) => {
|
|
15
|
+
const result = validate({ ast, schemaUri }, instance, outputFormat);
|
|
16
|
+
if (!result.valid) {
|
|
17
|
+
throw new ValidationError(result);
|
|
80
18
|
}
|
|
81
19
|
|
|
82
|
-
|
|
83
|
-
if (readOnly) {
|
|
84
|
-
readOnly.annotation = identity;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const writeOnly = getKeyword("https://json-schema.org/keyword/writeOnly");
|
|
88
|
-
if (writeOnly) {
|
|
89
|
-
writeOnly.annotation = identity;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const examples = getKeyword("https://json-schema.org/keyword/examples");
|
|
93
|
-
if (examples) {
|
|
94
|
-
examples.annotation = identity;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const format = getKeyword("https://json-schema.org/keyword/format");
|
|
98
|
-
if (format) {
|
|
99
|
-
format.annotation = identity;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const contentMediaType = getKeyword("https://json-schema.org/keyword/contentMediaType");
|
|
103
|
-
if (contentMediaType) {
|
|
104
|
-
contentMediaType.annotation = identity;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const contentEncoding = getKeyword("https://json-schema.org/keyword/contentEncoding");
|
|
108
|
-
if (contentEncoding) {
|
|
109
|
-
contentEncoding.annotation = identity;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const contentSchema = getKeyword("https://json-schema.org/keyword/contentSchema");
|
|
113
|
-
if (contentSchema) {
|
|
114
|
-
contentSchema.annotation = identity;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const unknown = getKeyword("https://json-schema.org/keyword/unknown");
|
|
118
|
-
if (unknown) {
|
|
119
|
-
unknown.annotation = identity;
|
|
120
|
-
}
|
|
20
|
+
return instance;
|
|
121
21
|
};
|
|
122
22
|
|
|
123
23
|
export { ValidationError } from "./validation-error.js";
|
|
@@ -27,7 +27,7 @@ const interpret = ([numberOfItems, additionalItems], instance, ast, dynamicAncho
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const collectEvaluatedItems = (keywordValue, instance, ast, dynamicAnchors) => {
|
|
30
|
-
if (!interpret(keywordValue, instance, ast, dynamicAnchors)) {
|
|
30
|
+
if (!interpret(keywordValue, instance, ast, dynamicAnchors, true)) {
|
|
31
31
|
return false;
|
|
32
32
|
}
|
|
33
33
|
|
package/draft-04/items.js
CHANGED
|
@@ -35,7 +35,7 @@ const interpret = (items, instance, ast, dynamicAnchors, quiet) => {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const collectEvaluatedItems = (items, instance, ast, dynamicAnchors) => {
|
|
38
|
-
return interpret(items, instance, ast, dynamicAnchors) && (typeof items === "string"
|
|
38
|
+
return interpret(items, instance, ast, dynamicAnchors, true) && (typeof items === "string"
|
|
39
39
|
? collectSet(range(0, Instance.length(instance)))
|
|
40
40
|
: collectSet(range(0, items.length)));
|
|
41
41
|
};
|
package/lib/core.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import curry from "just-curry-it";
|
|
2
2
|
import { resolveIri, toAbsoluteIri } from "@hyperjump/uri";
|
|
3
|
-
import { subscribe
|
|
3
|
+
import { subscribe } from "./pubsub.js";
|
|
4
4
|
import {
|
|
5
5
|
setMetaSchemaOutputFormat,
|
|
6
6
|
getShouldValidateSchema,
|
|
@@ -11,15 +11,16 @@ import { InvalidSchemaError } from "./invalid-schema-error.js";
|
|
|
11
11
|
import { getSchema, registerSchema, unregisterSchema as schemaUnregister } from "./schema.js";
|
|
12
12
|
import { getKeywordName } from "./keywords.js";
|
|
13
13
|
import Validation from "./keywords/validation.js";
|
|
14
|
+
import { toOutputFormat } from "./output.js";
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
export const FLAG = "FLAG", BASIC = "BASIC"
|
|
17
|
+
export const FLAG = "FLAG", BASIC = "BASIC";
|
|
17
18
|
setMetaSchemaOutputFormat(FLAG);
|
|
18
19
|
|
|
19
20
|
export const validate = async (url, value = undefined, outputFormat = undefined) => {
|
|
20
21
|
const schema = await getSchema(url);
|
|
21
22
|
const compiled = await compile(schema);
|
|
22
|
-
const interpretAst = (value, outputFormat) => interpret(compiled, Instance.
|
|
23
|
+
const interpretAst = (value, outputFormat) => interpret(compiled, Instance.fromJs(value), outputFormat);
|
|
23
24
|
|
|
24
25
|
return value === undefined ? interpretAst : interpretAst(value, outputFormat);
|
|
25
26
|
};
|
|
@@ -30,54 +31,11 @@ export const compile = async (schema) => {
|
|
|
30
31
|
return { ast, schemaUri };
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
export const interpret = curry(({ ast, schemaUri },
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const output = [];
|
|
39
|
-
const subscriptionToken = subscribe("result", outputHandler(outputFormat, output));
|
|
40
|
-
try {
|
|
41
|
-
Validation.interpret(schemaUri, value, ast, {});
|
|
42
|
-
} finally {
|
|
43
|
-
unsubscribe("result", subscriptionToken);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return output[0];
|
|
34
|
+
export const interpret = curry(({ ast, schemaUri }, instance, outputFormat = FLAG) => {
|
|
35
|
+
Validation.interpret(schemaUri, instance, ast, {});
|
|
36
|
+
return toOutputFormat(instance, outputFormat);
|
|
47
37
|
});
|
|
48
38
|
|
|
49
|
-
const outputHandler = (outputFormat, output) => {
|
|
50
|
-
const resultStack = [];
|
|
51
|
-
|
|
52
|
-
return (message, keywordResult) => {
|
|
53
|
-
if (message === "result") {
|
|
54
|
-
const { keyword, absoluteKeywordLocation, instanceLocation, valid } = keywordResult;
|
|
55
|
-
const result = { keyword, absoluteKeywordLocation, instanceLocation, valid, errors: [] };
|
|
56
|
-
resultStack.push(result);
|
|
57
|
-
} else if (message === "result.start") {
|
|
58
|
-
resultStack.push(message);
|
|
59
|
-
} else if (message === "result.end") {
|
|
60
|
-
const result = resultStack.pop();
|
|
61
|
-
while (resultStack[resultStack.length - 1] !== "result.start") {
|
|
62
|
-
const topResult = resultStack.pop();
|
|
63
|
-
|
|
64
|
-
const errors = [topResult];
|
|
65
|
-
if (outputFormat === BASIC) {
|
|
66
|
-
errors.push(...topResult.errors);
|
|
67
|
-
delete topResult.errors;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (outputFormat === VERBOSE || (outputFormat !== FLAG && !topResult.valid)) {
|
|
71
|
-
result.errors.unshift(...errors);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
resultStack[resultStack.length - 1] = result;
|
|
75
|
-
|
|
76
|
-
output[0] = result;
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
|
|
81
39
|
const metaValidators = {};
|
|
82
40
|
subscribe("validate.metaValidate", async (_message, schema) => {
|
|
83
41
|
if (getShouldValidateSchema() && !schema.document.validated) {
|
|
@@ -91,7 +49,7 @@ subscribe("validate.metaValidate", async (_message, schema) => {
|
|
|
91
49
|
}
|
|
92
50
|
|
|
93
51
|
// Interpret
|
|
94
|
-
const schemaInstance = Instance.
|
|
52
|
+
const schemaInstance = Instance.fromJs(schema.document.root, schema.document.baseUri);
|
|
95
53
|
const metaResults = metaValidators[schema.document.dialectId](schemaInstance, getMetaSchemaOutputFormat());
|
|
96
54
|
if (!metaResults.valid) {
|
|
97
55
|
throw new InvalidSchemaError(metaResults);
|