@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.
Files changed (61) hide show
  1. package/README.md +57 -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 +5 -7
  10. package/lib/experimental.js +1 -1
  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/output.js +43 -0
  52. package/openapi-3-0/discriminator.js +12 -2
  53. package/openapi-3-0/example.js +12 -2
  54. package/openapi-3-0/externalDocs.js +12 -2
  55. package/openapi-3-0/index.js +2 -2
  56. package/openapi-3-0/nullable.js +8 -2
  57. package/openapi-3-0/xml.js +12 -2
  58. package/openapi-3-1/index.js +2 -2
  59. package/package.json +3 -3
  60. package/bundle/README.md +0 -134
  61. 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
@@ -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: Instance, outputFormat: OutputFormat = BASIC) => OutputUnit
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** | **DETAILED** | **VERBOSE**
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`, `DETAILED`, and `VERBOSE` formats as
619
- specified in the 2019-09 specification (with some minor customizations).
620
- This implementation doesn't include annotations or human readable error
621
- messages. The output can be processed to create human readable error
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 InstanceDocument objects to represent a value in an instance.
630
- You'll work with these objects if you create a custom keyword. This module is a
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
- * **cons**: (instance: any, uri?: string) => InstanceDocument
631
+ * **fromJs**: (value: any, uri?: string) => JsonNode
639
632
 
640
- Construct an InstanceDocument from a value.
641
- * **get**: (url: string, contextDoc: InstanceDocument) => InstanceDocument
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 InstanceDocument.
644
- * **uri**: (doc: InstanceDocument) => string
636
+ Apply a same-resource reference to a JsonNode.
637
+ * **uri**: (instance: JsonNode) => string
645
638
 
646
- Returns a URI for the value the InstanceDocument represents.
647
- * **value**: (doc: InstanceDocument) => any
639
+ Returns a URI for the value the JsonNode represents.
640
+ * **value**: (instance: JsonNode) => any
648
641
 
649
- Returns the value the InstanceDocument represents.
650
- * **has**: (key: string, doc: InstanceDocument) => any
642
+ Returns the value the JsonNode represents.
643
+ * **has**: (key: string, instance: JsonNode) => boolean
651
644
 
652
- Similar to `key in instance`.
653
- * **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
654
648
 
655
- Determines if the JSON type of the given doc matches the given type.
656
- * **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
657
653
 
658
- Similar to `schema[key]`, but returns a InstanceDocument.
659
- * **iter**: (doc: InstanceDocument) => Generator\<InstanceDocument>
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 SchemaDocument represents.
662
- * **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]>
663
659
 
664
- Similar to `Object.entries`, but yields InstanceDocuments for values.
665
- * **values**: (doc: InstanceDocument) => Generator\<InstanceDocument>
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 InstanceDocuments for values.
668
- * **keys**: (doc: InstanceDocument) => Generator\<string>
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**: (doc: InstanceDocument) => number
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 an AnnotatedInstance object. This
682
- object is a wrapper around your JSON document with functions that allow you to
683
- 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.
684
680
 
685
681
  ```javascript
686
- 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";
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 [propertyName, propertyInstance] of AnnotatedInstance.entries(instance)) {
740
- 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));
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(instance, "deprecated", dialectId)[0]) {
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 = FLAG) => Promise\<AnnotatedInstance>
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: AnnotatedInstance, keyword: string, dialectId?: string) => any[]
775
+ * **annotation**: (instance: JsonNode, keyword: string, dialect?: string): any[];
773
776
 
774
- Get the annotations for a given keyword at the location represented by the
775
- instance object.
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 an array of instances for all the locations that are annotated with the
779
- given keyword.
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
- 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
  };
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, unsubscribe } from "./pubsub.js";
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", DETAILED = "DETAILED", VERBOSE = "VERBOSE";
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.cons(value), outputFormat);
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 }, value, outputFormat = FLAG) => {
34
- if (![FLAG, BASIC, DETAILED, VERBOSE].includes(outputFormat)) {
35
- throw Error(`The '${outputFormat}' error format is not supported`);
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.cons(schema.document.root, schema.document.baseUri);
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);