@hyperjump/json-schema 1.7.3 → 1.9.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.
Files changed (63) hide show
  1. package/README.md +61 -56
  2. package/annotations/annotated-instance.d.ts +4 -82
  3. package/annotations/annotated-instance.js +31 -33
  4. package/annotations/index.d.ts +7 -3
  5. package/annotations/index.js +8 -108
  6. package/draft-04/additionalItems.js +1 -1
  7. package/draft-04/items.js +1 -1
  8. package/lib/core.js +8 -50
  9. package/lib/experimental.d.ts +6 -7
  10. package/lib/experimental.js +6 -2
  11. package/lib/index.d.ts +5 -2
  12. package/lib/instance.d.ts +24 -23
  13. package/lib/instance.js +133 -38
  14. package/lib/keywords/additionalProperties.js +12 -7
  15. package/lib/keywords/allOf.js +7 -1
  16. package/lib/keywords/comment.js +4 -3
  17. package/lib/keywords/const.js +1 -1
  18. package/lib/keywords/contentEncoding.js +12 -2
  19. package/lib/keywords/contentMediaType.js +12 -2
  20. package/lib/keywords/contentSchema.js +8 -3
  21. package/lib/keywords/default.js +12 -2
  22. package/lib/keywords/dependentRequired.js +12 -3
  23. package/lib/keywords/dependentSchemas.js +12 -3
  24. package/lib/keywords/deprecated.js +12 -2
  25. package/lib/keywords/description.js +12 -2
  26. package/lib/keywords/enum.js +5 -2
  27. package/lib/keywords/examples.js +12 -2
  28. package/lib/keywords/format.js +12 -2
  29. package/lib/keywords/items.js +9 -6
  30. package/lib/keywords/maxContains.js +6 -2
  31. package/lib/keywords/maxItems.js +3 -1
  32. package/lib/keywords/maxLength.js +3 -1
  33. package/lib/keywords/minContains.js +6 -2
  34. package/lib/keywords/minItems.js +3 -1
  35. package/lib/keywords/minLength.js +3 -1
  36. package/lib/keywords/oneOf.js +0 -4
  37. package/lib/keywords/pattern.js +3 -1
  38. package/lib/keywords/patternProperties.js +18 -9
  39. package/lib/keywords/prefixItems.js +21 -6
  40. package/lib/keywords/properties.js +16 -7
  41. package/lib/keywords/propertyDependencies.js +18 -6
  42. package/lib/keywords/propertyNames.js +13 -5
  43. package/lib/keywords/readOnly.js +12 -2
  44. package/lib/keywords/title.js +12 -2
  45. package/lib/keywords/unevaluatedItems.js +14 -7
  46. package/lib/keywords/unevaluatedProperties.js +15 -8
  47. package/lib/keywords/uniqueItems.js +1 -1
  48. package/lib/keywords/unknown.js +12 -2
  49. package/lib/keywords/validation.js +24 -27
  50. package/lib/keywords/writeOnly.js +12 -2
  51. package/lib/keywords.js +5 -0
  52. package/lib/output.js +43 -0
  53. package/lib/schema.js +4 -2
  54. package/openapi-3-0/discriminator.js +12 -2
  55. package/openapi-3-0/example.js +12 -2
  56. package/openapi-3-0/externalDocs.js +12 -2
  57. package/openapi-3-0/index.js +2 -2
  58. package/openapi-3-0/nullable.js +8 -2
  59. package/openapi-3-0/xml.js +12 -2
  60. package/openapi-3-1/index.js +2 -2
  61. package/package.json +3 -3
  62. package/bundle/README.md +0 -134
  63. 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: InstanceDocument, ast: AST, dynamicAnchors: object, quiet: boolean) => boolean
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: InstanceDocument, ast: AST, dynamicAnchors: object) => Set\<string> | false
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: InstanceDocument, ast: AST, dynamicAnchors: object) => Set\<number> | false
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
@@ -566,6 +561,10 @@ These are available from the `@hyperjump/json-schema/experimental` export.
566
561
  `$vocabulary` keyword in the meta-schema. The only time you would need to
567
562
  load a dialect manually is if you're creating a distinct version of JSON
568
563
  Schema rather than creating a dialect of an existing version of JSON Schema.
564
+ * **unloadDialect**: (dialectId: string) => void
565
+
566
+ Remove a dialect. You shouldn't need to use this function. It's called for
567
+ you when you call `unregisterSchema`.
569
568
  * **Validation**: Keyword
570
569
 
571
570
  A Keyword object that represents a "validate" operation. You would use this
@@ -603,68 +602,69 @@ These are available from the `@hyperjump/json-schema/experimental` export.
603
602
 
604
603
  Return a compiled schema. This is useful if you're creating tooling for
605
604
  something other than validation.
606
- * **interpret**: (schema: CompiledSchema, instance: Instance, outputFormat: OutputFormat = BASIC) => OutputUnit
605
+ * **interpret**: (schema: CompiledSchema, instance: JsonNode, outputFormat: OutputFormat = BASIC) => OutputUnit
607
606
 
608
607
  A curried function for validating an instance against a compiled schema.
609
608
  This can be useful for creating custom output formats.
610
609
 
611
- * **OutputFormat**: **FLAG** | **BASIC** | **DETAILED** | **VERBOSE**
610
+ * **OutputFormat**: **FLAG** | **BASIC**
612
611
 
613
612
  In addition to the `FLAG` output format in the Stable API, the Experimental
614
- API includes support for the `BASIC`, `DETAILED`, and `VERBOSE` formats as
615
- specified in the 2019-09 specification (with some minor customizations).
616
- This implementation doesn't include annotations or human readable error
617
- messages. The output can be processed to create human readable error
618
- 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.
619
617
 
620
618
  ## Instance API (experimental)
621
619
 
622
620
  These functions are available from the
623
621
  `@hyperjump/json-schema/instance/experimental` export.
624
622
 
625
- This library uses InstanceDocument objects to represent a value in an instance.
626
- You'll work with these objects if you create a custom keyword. This module is a
627
- 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.
628
625
 
629
626
  This API uses generators to iterate over arrays and objects. If you like using
630
627
  higher order functions like `map`/`filter`/`reduce`, see
631
628
  [`@hyperjump/pact`](https://github.com/hyperjump-io/pact) for utilities for
632
629
  working with generators and async generators.
633
630
 
634
- * **cons**: (instance: any, uri?: string) => InstanceDocument
631
+ * **fromJs**: (value: any, uri?: string) => JsonNode
635
632
 
636
- Construct an InstanceDocument from a value.
637
- * **get**: (url: string, contextDoc: InstanceDocument) => InstanceDocument
633
+ Construct a JsonNode from a JavaScript value.
634
+ * **get**: (url: string, instance: JsonNode) => JsonNode
638
635
 
639
- Apply a same-resource reference to a InstanceDocument.
640
- * **uri**: (doc: InstanceDocument) => string
636
+ Apply a same-resource reference to a JsonNode.
637
+ * **uri**: (instance: JsonNode) => string
641
638
 
642
- Returns a URI for the value the InstanceDocument represents.
643
- * **value**: (doc: InstanceDocument) => any
639
+ Returns a URI for the value the JsonNode represents.
640
+ * **value**: (instance: JsonNode) => any
644
641
 
645
- Returns the value the InstanceDocument represents.
646
- * **has**: (key: string, doc: InstanceDocument) => any
642
+ Returns the value the JsonNode represents.
643
+ * **has**: (key: string, instance: JsonNode) => boolean
647
644
 
648
- Similar to `key in instance`.
649
- * **typeOf**: (doc: InstanceDocument) => string
645
+ Returns whether or not "key" is a property name in a JsonNode that
646
+ represents an object.
647
+ * **typeOf**: (instance: JsonNode) => string
650
648
 
651
- Determines if the JSON type of the given doc matches the given type.
652
- * **step**: (key: string, doc: InstanceDocument) => InstanceDocument
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
653
653
 
654
- Similar to `schema[key]`, but returns a InstanceDocument.
655
- * **iter**: (doc: InstanceDocument) => Generator\<InstanceDocument>
654
+ Similar to indexing into a object or array using the `[]` operator.
655
+ * **iter**: (instance: JsonNode) => Generator\<JsonNode>
656
656
 
657
- Iterate over the items in the array that the SchemaDocument represents.
658
- * **entries**: (doc: InstanceDocument) => Generator\<[string, InstanceDocument]>
657
+ Iterate over the items in the array that the JsonNode represents.
658
+ * **entries**: (instance: JsonNode) => Generator\<[JsonNode, JsonNode]>
659
659
 
660
- Similar to `Object.entries`, but yields InstanceDocuments for values.
661
- * **values**: (doc: InstanceDocument) => Generator\<InstanceDocument>
660
+ Similar to `Object.entries`, but yields JsonNodes for keys and values.
661
+ * **values**: (instance: JsonNode) => Generator\<JsonNode>
662
662
 
663
- Similar to `Object.values`, but yields InstanceDocuments for values.
664
- * **keys**: (doc: InstanceDocument) => Generator\<string>
663
+ Similar to `Object.values`, but yields JsonNodes for values.
664
+ * **keys**: (instance: JsonNode) => Generator\<JsonNode>
665
665
 
666
- Similar to `Object.keys`.
667
- * **length**: (doc: InstanceDocument) => number
666
+ Similar to `Object.keys`, but yields JsonNodes for keys.
667
+ * **length**: (instance: JsonNode) => number
668
668
 
669
669
  Similar to `Array.prototype.length`.
670
670
 
@@ -674,12 +674,13 @@ module provides utilities for working with JSON documents annotated with JSON
674
674
  Schema.
675
675
 
676
676
  ### Usage
677
- An annotated JSON document is represented as an AnnotatedInstance object. This
678
- object is a wrapper around your JSON document with functions that allow you to
679
- traverse the data structure and get annotations for the values within.
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.
680
680
 
681
681
  ```javascript
682
- import { annotate, annotatedWith, registerSchema } from "@hyperjump/json-schema/annotations/experimental";
682
+ import { registerSchema } from "@hyperjump/json-schema/draft/2020-12";
683
+ import { annotate } from "@hyperjump/json-schema/annotations/experimental";
683
684
  import * as AnnotatedInstance from "@hyperjump/json-schema/annotated-instance/experimental";
684
685
 
685
686
 
@@ -732,13 +733,14 @@ const unknowns = AnnotatedInstance.annotation(instance, "unknown", dialectId); /
732
733
  const types = AnnotatedInstance.annotation(instance, "type", dialectId); // => []
733
734
 
734
735
  // Get the title of each of the properties in the object
735
- for (const [propertyName, propertyInstance] of AnnotatedInstance.entries(instance)) {
736
- console.log(propertyName, Instance.annotation(propertyInstance, "title", dialectId));
736
+ for (const [propertyNameNode, propertyInstance] of AnnotatedInstance.entries(instance)) {
737
+ const propertyName = AnnotatedInstance.value(propertyName);
738
+ console.log(propertyName, AnnotatedInstance.annotation(propertyInstance, "title", dialectId));
737
739
  }
738
740
 
739
741
  // List all locations in the instance that are deprecated
740
742
  for (const deprecated of AnnotatedInstance.annotatedWith(instance, "deprecated", dialectId)) {
741
- if (AnnotatedInstance.annotation(instance, "deprecated", dialectId)[0]) {
743
+ if (AnnotatedInstance.annotation(deprecated, "deprecated", dialectId)[0]) {
742
744
  logger.warn(`The value at '${deprecated.pointer}' has been deprecated.`); // => (Example) "WARN: The value at '/name' has been deprecated."
743
745
  }
744
746
  }
@@ -748,13 +750,18 @@ for (const deprecated of AnnotatedInstance.annotatedWith(instance, "deprecated",
748
750
  These are available from the `@hyperjump/json-schema/annotations/experimental`
749
751
  export.
750
752
 
751
- * **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat = FLAG) => Promise\<AnnotatedInstance>
753
+ * **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat = BASIC) => Promise\<JsonNode>
752
754
 
753
755
  Annotate an instance using the given schema. The function is curried to
754
756
  allow compiling the schema once and applying it to multiple instances. This
755
757
  may throw an [InvalidSchemaError](#api) if there is a problem with the
756
758
  schema or a ValidationError if the instance doesn't validate against the
757
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.
758
765
  * **ValidationError**: Error & { output: OutputUnit }
759
766
  The `output` field contains an `OutputUnit` with information about the
760
767
  error.
@@ -765,15 +772,13 @@ These are available from the
765
772
  following functions are available in addition to the functions available in the
766
773
  [Instance API](#instance-api-experimental).
767
774
 
768
- * **annotation**: (instance: AnnotatedInstance, keyword: string, dialectId?: string) => any[]
775
+ * **annotation**: (instance: JsonNode, keyword: string, dialect?: string): any[];
769
776
 
770
- Get the annotations for a given keyword at the location represented by the
771
- instance object.
772
- * **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[];
773
779
 
774
- Get an array of instances for all the locations that are annotated with the
775
- given keyword.
776
- * **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
777
782
 
778
783
  Add an annotation to an instance. This is used internally, you probably
779
784
  don't need it.
@@ -1,83 +1,5 @@
1
- import type { JsonType } from "../lib/common.js";
2
- import type { Json, JsonObject } from "@hyperjump/json-pointer";
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 { toAbsoluteIri } from "@hyperjump/uri";
2
- import { nil as nilInstance, get } from "../lib/instance.js";
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 nil = { ...nilInstance, annotations: {} }; // eslint-disable-line import/export
9
- export const cons = (instance, id = undefined) => ({ // eslint-disable-line import/export
10
- ...nil,
11
- id: id ? toAbsoluteIri(id) : "",
12
- instance,
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 annotate = (instance, keyword, value) => {
22
- return Object.freeze({
23
- ...instance,
24
- annotations: {
25
- ...instance.annotations,
26
- [instance.pointer]: {
27
- ...instance.annotations[instance.pointer],
28
- [keyword]: [
29
- value,
30
- ...instance.annotations[instance.pointer]?.[keyword] || []
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 instances = [];
37
+ const nodes = [];
39
38
 
40
- const keywordId = getKeywordId(keyword, dialectId);
41
- for (const instancePointer in instance.annotations) {
42
- if (keywordId in instance.annotations[instancePointer]) {
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 instances;
45
+ return nodes;
48
46
  };
49
47
 
50
- export * from "../lib/instance.js"; // eslint-disable-line import/export
48
+ export * from "../lib/instance.js";
@@ -1,14 +1,18 @@
1
1
  import type { OutputFormat, OutputUnit } from "../lib/index.js";
2
- import type { AnnotatedJsonDocument } from "./annotated-instance.js";
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: unknown, outputFormat?: OutputFormat) => Promise<Annotator>
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: unknown, outputFormat?: OutputFormat) => AnnotatedJsonDocument;
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;
@@ -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, getKeyword, compile, interpret as validate, BASIC } from "../lib/experimental.js";
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.cons(json), outputFormat);
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 output = [instance];
18
- const subscriptionToken = subscribe("result", outputHandler(output));
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
- const readOnly = getKeyword("https://json-schema.org/keyword/readOnly");
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
  };