json-schema-library 11.0.5 → 11.2.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 (113) hide show
  1. package/.mocharc.js +1 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +120 -0
  4. package/bowtie/.editorconfig +21 -0
  5. package/bowtie/.prettierrc +6 -0
  6. package/bowtie/BOWTIE.md +54 -0
  7. package/bowtie/Dockerfile +6 -0
  8. package/bowtie/bowtie-api.ts +101 -0
  9. package/bowtie/bowtie.test.ts +76 -0
  10. package/bowtie/bowtie.ts +156 -0
  11. package/bowtie/package.json +11 -0
  12. package/bowtie/tsconfig.json +12 -0
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +39 -470
  15. package/dist/index.d.mts +39 -470
  16. package/dist/index.mjs +1 -1
  17. package/dist/jlib.js +2 -13
  18. package/dist/remotes/index.cjs +1 -0
  19. package/dist/remotes/index.d.cts +7 -0
  20. package/dist/remotes/index.d.mts +7 -0
  21. package/dist/remotes/index.mjs +1 -0
  22. package/dist/types-B2wwNWyo.d.cts +513 -0
  23. package/dist/types-BhTU1l2h.d.mts +513 -0
  24. package/index.ts +10 -4
  25. package/package.json +14 -8
  26. package/src/Keyword.ts +37 -12
  27. package/src/SchemaNode.ts +84 -16
  28. package/src/compileSchema.ts +56 -4
  29. package/src/draft04/keywords/$ref.ts +22 -14
  30. package/src/draft04/keywords/exclusiveMaximum.ts +14 -0
  31. package/src/draft04/keywords/exclusiveMinimum.ts +14 -0
  32. package/src/draft04/validateSchema.test.ts +20 -0
  33. package/src/draft06/keywords/$ref.ts +15 -5
  34. package/src/draft2019-09/keywords/$ref.test.ts +3 -1
  35. package/src/draft2019-09/keywords/$ref.ts +40 -16
  36. package/src/draft2019-09/keywords/additionalItems.ts +33 -10
  37. package/src/draft2019-09/keywords/items.ts +32 -10
  38. package/src/draft2019-09/keywords/unevaluatedItems.ts +19 -6
  39. package/src/draft2019-09/methods/getData.ts +1 -1
  40. package/src/draft2019-09/validateSchema.test.ts +28 -0
  41. package/src/errors/errors.ts +8 -1
  42. package/src/formats/formats.ts +35 -28
  43. package/src/formats/hyperjump.d.ts +172 -0
  44. package/src/keywords/$defs.ts +34 -8
  45. package/src/keywords/$ref.ts +59 -13
  46. package/src/keywords/additionalProperties.ts +19 -8
  47. package/src/keywords/allOf.ts +44 -18
  48. package/src/keywords/anyOf.ts +38 -19
  49. package/src/keywords/contains.ts +21 -9
  50. package/src/keywords/dependencies.ts +37 -17
  51. package/src/keywords/dependentRequired.ts +56 -38
  52. package/src/keywords/dependentSchemas.ts +37 -13
  53. package/src/keywords/deprecated.ts +32 -8
  54. package/src/keywords/enum.ts +30 -8
  55. package/src/keywords/exclusiveMaximum.ts +21 -2
  56. package/src/keywords/exclusiveMinimum.ts +22 -3
  57. package/src/keywords/format.ts +21 -2
  58. package/src/keywords/ifthenelse.ts +49 -5
  59. package/src/keywords/items.ts +27 -13
  60. package/src/keywords/maxItems.ts +22 -2
  61. package/src/keywords/maxLength.ts +30 -9
  62. package/src/keywords/maxProperties.ts +30 -9
  63. package/src/keywords/maximum.ts +28 -8
  64. package/src/keywords/minItems.ts +30 -9
  65. package/src/keywords/minLength.ts +30 -9
  66. package/src/keywords/minProperties.ts +26 -5
  67. package/src/keywords/minimum.ts +32 -13
  68. package/src/keywords/multipleOf.ts +33 -12
  69. package/src/keywords/not.ts +23 -10
  70. package/src/keywords/oneOf.ts +29 -9
  71. package/src/keywords/pattern.ts +35 -9
  72. package/src/keywords/properties.ts +34 -11
  73. package/src/keywords/propertyDependencies.test.ts +180 -0
  74. package/src/keywords/propertyDependencies.ts +173 -0
  75. package/src/keywords/propertyNames.ts +26 -14
  76. package/src/keywords/required.ts +31 -8
  77. package/src/keywords/type.ts +53 -16
  78. package/src/keywords/unevaluatedItems.ts +24 -8
  79. package/src/keywords/unevaluatedProperties.ts +24 -7
  80. package/src/keywords/uniqueItems.ts +23 -4
  81. package/src/mergeNode.ts +9 -4
  82. package/src/methods/getData.ts +1 -1
  83. package/src/settings.ts +2 -1
  84. package/src/types.ts +1 -1
  85. package/src/utils/isListOfStrings.ts +3 -0
  86. package/src/validate.test.ts +0 -2
  87. package/src/validateNode.ts +6 -3
  88. package/src/validateSchema.test.ts +312 -0
  89. package/tsconfig.json +11 -4
  90. package/tsconfig.test.json +9 -2
  91. package/tsdown.config.ts +1 -1
  92. package/Dockerfile +0 -6
  93. package/bowtie_jlib.js +0 -140
  94. package/remotes/draft04.json +0 -150
  95. package/remotes/draft06.json +0 -155
  96. package/remotes/draft07.json +0 -155
  97. package/remotes/draft2019-09.json +0 -42
  98. package/remotes/draft2019-09_meta_applicator.json +0 -53
  99. package/remotes/draft2019-09_meta_content.json +0 -14
  100. package/remotes/draft2019-09_meta_core.json +0 -54
  101. package/remotes/draft2019-09_meta_format.json +0 -11
  102. package/remotes/draft2019-09_meta_meta-data.json +0 -34
  103. package/remotes/draft2019-09_meta_validation.json +0 -95
  104. package/remotes/draft2020-12.json +0 -55
  105. package/remotes/draft2020-12_meta_applicator.json +0 -45
  106. package/remotes/draft2020-12_meta_content.json +0 -14
  107. package/remotes/draft2020-12_meta_core.json +0 -48
  108. package/remotes/draft2020-12_meta_format_annotation.json +0 -11
  109. package/remotes/draft2020-12_meta_format_assertion.json +0 -11
  110. package/remotes/draft2020-12_meta_meta_data.json +0 -34
  111. package/remotes/draft2020-12_meta_unevaluated.json +0 -12
  112. package/remotes/draft2020-12_meta_validation.json +0 -87
  113. package/remotes/index.ts +0 -48
package/.mocharc.js CHANGED
@@ -1,4 +1,5 @@
1
1
  process.env.TS_NODE_PROJECT = "./tsconfig.test.json";
2
+ process.env.JLIB_TEST_RUN = "true";
2
3
 
3
4
  module.exports = {
4
5
  "node-option": ["import=tsx"]
package/CHANGELOG.md CHANGED
@@ -1,8 +1,25 @@
1
1
  ## Changelog
2
2
 
3
+ ### v11.2.0
4
+
5
+ - introduced `throwOnInvalidRef` to abort validation when a $ref cannot be resolved
6
+ - added export of meta-schema for all drafts using `import "json-schema-library/remotes"`
7
+ - added support for format validations: `idn-hostname`, `iri`, `iri-reference`
8
+
9
+ ### v11.1.0
10
+
11
+ - introduced upcoming keyword `propertyDependenciesKeyword` to package export
12
+ - added support for keyword `deprecatedMessage`
13
+ - introduced schema validation
14
+
15
+ ### v11.0.4, v11.0.5
16
+
17
+ - removed enforcing packageManager as this failed build-jobs
18
+
3
19
  ### v11.0.3
4
20
 
5
21
  - fixed type of main input-schema to support boolean
22
+ - enforced yarn as a package manager
6
23
 
7
24
  ### v11.0.2
8
25
 
package/README.md CHANGED
@@ -58,6 +58,12 @@ type CompileOptions = {
58
58
  remote: SchemaNode;
59
59
  // if format-validations should create errors. Defaults to true
60
60
  formatAssertion: boolean | "meta-schema";
61
+ /** set to true to throw an Error on errors in input schema. Defaults to false */
62
+ throwOnInvalidSchema?: boolean;
63
+ /** set to true to collect unknown keywords of input schema in `node.schemaAnnotations`. Defaults to false */
64
+ withSchemaAnnotations?: boolean;
65
+ /** set to true to throw an Error when encountering an unresolvable ref */
66
+ throwOnInvalidRef?: boolean;
61
67
  // default options for all calls to node.getData()
62
68
  getDataDefaultOptions?: {
63
69
  // Add all properties (required and optional) to the generated data
@@ -94,6 +100,81 @@ compileSchema(mySchema, { getDataDefaultOptions: { addOptionalProps: true } });
94
100
  Details on _drafts_ are documented in [draft customization](#draft-customization).
95
101
  Details on `getDataDefaultOptions` are documented in [getData](#getData).
96
102
 
103
+ ### validate input schema
104
+
105
+ All JSON Schema passed to `compileSchema` are validated automatically. To retrieve any schema errors you can access the property `schemaErrors` of the main node:
106
+
107
+ ```ts
108
+ const root = compileSchema(mySchema);
109
+ const { schemaErrors } = root; // JsonError[]
110
+ ```
111
+
112
+ Use the option `throwOnInvalidSchema:true` of `compileSchema` to throw an Error for a input schema containing errors:
113
+
114
+ ```ts
115
+ const root = compileSchema({ properties: 123 }, { throwOnInvalidSchema: true });
116
+ // throws Error
117
+ ```
118
+
119
+ <details><summary>Example for schema validation errors</summary>
120
+
121
+ ---
122
+
123
+ ```ts
124
+ const { schemaErrors } = compileSchema({ $defs: 999 });
125
+ console.log(schemaErrors[0]);
126
+ {
127
+ type: 'error',
128
+ code: 'schema-error',
129
+ message: 'Invalid schema found at #: $defs must be an object - received: number',
130
+ data: {
131
+ pointer: '#',
132
+ schema: { '$defs': 999 },
133
+ value: 999,
134
+ message: '$defs must be an object - received: number'
135
+ }
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ </details>
142
+
143
+ To collect JSON Schema annotations for unused keywords you can opt in with option `withSchemaAnnotations`:
144
+
145
+ ```ts
146
+ const root = compileSchema(mySchema, { withSchemaAnnotations: true });
147
+ const { schemaAnnotations } = root; // JsonAnnotation[]
148
+ ```
149
+
150
+ This collects all JSON Schema keywords not part of the used draft and any custom keywords. Custom keywords starting with `x-` are allowed and thus will not create an annotation.
151
+
152
+ <details><summary>Example for validation annotations</summary>
153
+
154
+ ---
155
+
156
+ ```ts
157
+ const { schemaAnnotations } = compileSchema({ unknown: true }, { withSchemaAnnotations: true });
158
+ console.log(schemaAnnotations[0]);
159
+ {
160
+ type: "annotation",
161
+ code: "unknown-keyword-warning",
162
+ message: "Keyword 'unknown' is not a valid keyword to draft 'draft-2020-12'",
163
+ data: {
164
+ pointer: "#/unknown",
165
+ schema: {
166
+ unknown: true
167
+ },
168
+ value: "unknown",
169
+ draft: "draft-2020-12"
170
+ }
171
+ }
172
+ ```
173
+
174
+ ---
175
+
176
+ </details>
177
+
97
178
  ### SchemaNode
98
179
 
99
180
  `compileSchema` builds a tree where each sub-schema becomes its own SchemaNode. Every node in the tree offers the same set of methods.
@@ -1379,6 +1460,45 @@ const dependentSchemasKeyword = draft2020.keywords.find((f) => f.keyword === "de
1379
1460
 
1380
1461
  ## Keyword extensions
1381
1462
 
1463
+ ### propertyDependencies
1464
+
1465
+ Resolves an object-schema by `propertyName:propertyValue`
1466
+
1467
+ ```ts
1468
+ {
1469
+ type: "object",
1470
+ propertyDependencies: {
1471
+ propertyName: {
1472
+ propertyValue: {
1473
+ properties: {
1474
+ id: { type: "string" }
1475
+ }
1476
+ }
1477
+ }
1478
+ }
1479
+
1480
+ // matches and returns error for id
1481
+ {
1482
+ "propertyName": "propertyValue",
1483
+ "id": 123
1484
+ }
1485
+ ```
1486
+
1487
+ Note that this keyword is not added by default as it is not part yet of the JSONSchema spec. Add this keyword with:
1488
+
1489
+ ```ts
1490
+ import { compileSchema, draft2020, extendDraft, propertyDependenciesKeyword } from "json-schema-library";
1491
+
1492
+ const draft = extendDraft(draft2020, {
1493
+ keywords: [propertyDependenciesKeyword]
1494
+ });
1495
+
1496
+ const node = compileSchema({ propertyDependencies: {} }, { drafts: [draft] });
1497
+ ```
1498
+
1499
+ - Note: this keyword may replace `OneOfProperty`
1500
+ - Reference: https://docs.google.com/presentation/d/1ajXlCQcsjjiMLsluFIILR7sN5aDRBnfqQ9DLbcFbqjI/mobilepresent?slide=id.g3ae4fb2e16d_0_15
1501
+
1382
1502
  ### oneOfProperty
1383
1503
 
1384
1504
  For `oneOf` resolution, JSON Schema states that data is valid if it validates against exactly one of those sub-schemas. In some scenarios this is unwanted behaviour, as the actual `oneOf` schema is known and only validation errors of this exact sub-schema should be returned.
@@ -0,0 +1,21 @@
1
+ # EditorConfig helps developers define and maintain consistent
2
+ # coding styles between different editors and IDEs
3
+ # editorconfig.org
4
+
5
+ root = true
6
+
7
+ [*]
8
+ indent_style = space
9
+ indent_size = 4
10
+ end_of_line = lf
11
+ charset = utf-8
12
+ trim_trailing_whitespace = true
13
+ insert_final_newline = true
14
+
15
+ [*.md]
16
+ trim_trailing_whitespace = false
17
+ indent_size = 2
18
+
19
+ [package.json]
20
+ indent_style = space
21
+ indent_size = 2
@@ -0,0 +1,6 @@
1
+ {
2
+ "printWidth": 120,
3
+ "singleQuote": false,
4
+ "bracketSpacing": true,
5
+ "trailingComma": "none"
6
+ }
@@ -0,0 +1,54 @@
1
+ # install
2
+
3
+ brew install bowtie-json-schema/tap/bowtie
4
+ https://docs.bowtie.report/en/stable/implementers/
5
+
6
+ > docker build -t localhost/jlib .
7
+ > bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft2020-12 | bowtie summary --show failures
8
+ > bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft7 --fail-fast
9
+
10
+ # build
11
+
12
+ docker build -t localhost/jlib .
13
+
14
+ # test without bowtie
15
+
16
+ docker run --rm localhost/jlib
17
+ echo '{"cmd":"start","version":1}' | docker run -i jlib
18
+ echo '{"cmd":"run","seq":1,"case":{"schema":{"type": "string"},"tests":["valid",999]}}' | docker run -i jlib
19
+ echo '{"cmd":"dialect","dialect":"http://json-schema.org/draft-07/schema#"}' | docker run -i jlib
20
+ echo '{"cmd":"stop"}' | docker run -i jlib
21
+
22
+ # test with bowtie
23
+
24
+ > docker build -t localhost/jlib .
25
+ > then:
26
+
27
+ bowtie smoke -i localhost/jlib
28
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft7/type.json | bowtie summary --show failures
29
+
30
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft7 > draft7.json
31
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft2019-09 > draft2019-09.json
32
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft2020-12 > draft2020-12.json
33
+
34
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft7 | bowtie summary --show failures
35
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft2019-09 | bowtie summary --show failures
36
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft2020-12 | bowtie summary --show failures
37
+
38
+ **Fails**
39
+ bowtie suite $(bowtie filter-implementations | sed 's/^/-i /') https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft2020-12 >draft2020-12.json
40
+
41
+ bowtie suite $(bowtie filter-implementations | sed 's/^jlib/-i /') https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft2020-12 >draft2020-12.json
42
+
43
+ bowtie suite -i localhost/jlib draft7 | bowtie summary
44
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft7/type.json | bowtie summary --show failures
45
+ bowtie suite -i localhost/jlib https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/main/tests/draft7 | bowtie summary --show failures
46
+ bowtie suite -i localhost/jlib draft7 | bowtie summary --show failures
47
+ bowtie validate -i localhost/jlib draft7 | bowtie summary --show failures
48
+
49
+ **Does not finish**
50
+ bowtie run --dialect 7 -i localhost/jlib
51
+ bowtie validate -i localhost/jlib <(printf '{"type": "integer"}') <(printf 37) <(printf '"foo"')
52
+ bowtie run -i localhost/jlib draft7 | bowtie summary --show failures
53
+ bowtie run -i localhost/jlib -V '{"description": "test case 1", "schema": {}, "tests": [{"description": "a test", "instance": {}}]}'
54
+ bowtie run -i localhost/jlib -V --fail-fast
@@ -0,0 +1,6 @@
1
+ FROM node:24-alpine
2
+ COPY . /usr/app
3
+ WORKDIR /usr/app
4
+ RUN npm install --omit=dev
5
+ RUN npm -g install tsm
6
+ CMD ["tsm", "bowtie.ts"]
@@ -0,0 +1,101 @@
1
+ import readline from "readline/promises";
2
+ import { JsonSchema } from "src/types";
3
+
4
+ export type Dialect =
5
+ | "https://json-schema.org/draft/2020-12/schema"
6
+ | "https://json-schema.org/draft/2019-09/schema"
7
+ | "http://json-schema.org/draft-07/schema#"
8
+ | "http://json-schema.org/draft-06/schema#"
9
+ | "http://json-schema.org/draft-04/schema#"
10
+ | "http://json-schema.org/draft-03/schema#";
11
+
12
+ type StartCmd = {
13
+ cmd: "start";
14
+ version: number;
15
+ };
16
+
17
+ type StartCmdResponse = {
18
+ version: number;
19
+ implementation: {
20
+ /** library language */
21
+ language: "typescript" | "typescript";
22
+ /** library name */
23
+ name: string;
24
+ /** library version */
25
+ version: string;
26
+ homepage: string;
27
+ issues: string;
28
+ source: string;
29
+ dialects: Dialect[];
30
+ /** operating system platform */
31
+ os: string;
32
+ /** operating system version */
33
+ os_version: string;
34
+ /** Node.js version */
35
+ language_version: string;
36
+ };
37
+ };
38
+
39
+ type DialectCommand = {
40
+ cmd: "dialect";
41
+ dialect: Dialect;
42
+ };
43
+
44
+ type DialectCmdResponse = { ok: true };
45
+
46
+ type RunCmd = {
47
+ cmd: "run";
48
+ seq: number;
49
+ // dialect: Dialect;
50
+ case: {
51
+ schema: JsonSchema;
52
+ tests: {
53
+ description: string;
54
+ instance: unknown;
55
+ }[];
56
+ registry: Record<string, JsonSchema>;
57
+ };
58
+ };
59
+
60
+ export type RunCmdResponse = {
61
+ seq: number;
62
+ results: ({ valid: boolean } | ErrorResponse)[];
63
+ };
64
+
65
+ export type ErrorResponse = {
66
+ errored: true;
67
+ context: {
68
+ traceback?: string | undefined;
69
+ message: string | undefined;
70
+ };
71
+ };
72
+
73
+ type StopCommand = {
74
+ cmd: "stop";
75
+ };
76
+
77
+ export type Command = StartCmd | RunCmd | DialectCommand | StopCommand;
78
+ export type CommandResponse = RunCmdResponse | StartCmdResponse | DialectCmdResponse | ErrorResponse | undefined;
79
+
80
+ export type CommandMap = {
81
+ /** Start nxet test run, informing bowtie of suppored draft versions and general metadata */
82
+ start: (args: StartCmd) => Promise<StartCmdResponse>;
83
+ /** Set JSON Schema draft-version of following tests */
84
+ dialect: (args: DialectCommand) => Promise<DialectCmdResponse>;
85
+ /** Run test cases for a specfic schema */
86
+ run: (args: RunCmd) => Promise<RunCmdResponse>;
87
+ /** Finalize test run and exit container */
88
+ stop: (args: StopCommand, stdio?: readline.Interface) => Promise<void>;
89
+ };
90
+
91
+ export const createBowtieError = (message: string, stack?: string): ErrorResponse => ({
92
+ errored: true,
93
+ context: {
94
+ message: message,
95
+ traceback: stack
96
+ }
97
+ });
98
+
99
+ export function sendToBowtie(data?: Record<string, unknown>) {
100
+ console.log(JSON.stringify(data ?? createBowtieError("missing response")));
101
+ }
@@ -0,0 +1,76 @@
1
+ import { strict as assert } from "node:assert";
2
+ import { compileSchema } from "../src/compileSchema";
3
+ import { remotes } from "json-schema-library/remotes";
4
+ import { JsonSchema } from "../src/types";
5
+ import { runCommand } from "./bowtie";
6
+ import { ErrorResponse, RunCmdResponse } from "./bowtie-api";
7
+
8
+ const isRunCmdResponse = (value: unknown): value is RunCmdResponse =>
9
+ value != null && typeof value === "object" && "results" in value && Array.isArray(value.results);
10
+
11
+ const isErrorResponse = (value: unknown): value is ErrorResponse =>
12
+ value != null && typeof value === "object" && "errored" in value && value.errored === true;
13
+
14
+ const remote = compileSchema({ $id: "draft2020-12" });
15
+ remotes.map((schema: JsonSchema) => remote.addRemoteSchema(schema.$id ?? schema.id, schema));
16
+
17
+ describe("bowtie (draft7)", async () => {
18
+ before(async () => {
19
+ await runCommand({ cmd: "start", version: 1 });
20
+ await runCommand({ cmd: "dialect", dialect: "http://json-schema.org/draft-07/schema#" });
21
+ });
22
+ after(async () => runCommand({ cmd: "stop" }));
23
+
24
+ it("additionalItems as schema - additional items match schema", async () => {
25
+ const response = await runCommand({
26
+ cmd: "run",
27
+ seq: 1,
28
+ case: {
29
+ description: "additionalItems as schema",
30
+ schema: { items: [{}], additionalItems: { type: "integer" } },
31
+ tests: [{ description: "additional items match schema", instance: [null, 2, 3, 4], valid: true }]
32
+ }
33
+ });
34
+ assert(isRunCmdResponse(response));
35
+ assert(!isErrorResponse(response.results[0]));
36
+ assert.equal(response.results[0].valid, true);
37
+ });
38
+
39
+ it("additionalItems as schema - additional items do not match schema", async () => {
40
+ const response = await runCommand({
41
+ cmd: "run",
42
+ seq: 1,
43
+ case: {
44
+ description: "additionalItems as schema",
45
+ schema: { items: [{}], additionalItems: { type: "integer" } },
46
+ tests: [
47
+ { description: "additional items do not match schema", instance: [null, 2, 3, "foo"], valid: false }
48
+ ]
49
+ }
50
+ });
51
+ assert(isRunCmdResponse(response));
52
+ assert(!isErrorResponse(response.results[0]));
53
+ assert.equal(response.results[0].valid, false);
54
+ });
55
+ });
56
+
57
+ describe("bowtie (2020-12)", () => {
58
+ describe("validate definition against metaschema", () => {
59
+ const node = compileSchema(
60
+ {
61
+ $schema: "https://json-schema.org/draft/2020-12/schema",
62
+ $ref: "https://json-schema.org/draft/2020-12/schema"
63
+ },
64
+ { remote }
65
+ );
66
+
67
+ it("valid definition schema", () => {
68
+ const first = node.validate({ $defs: { foo: { type: "integer" } } });
69
+ assert.deepEqual(first.valid, true);
70
+ });
71
+ it("invalid definition schema", () => {
72
+ const second = node.validate({ $defs: { foo: { type: 1 } } });
73
+ assert.deepEqual(second.valid, false);
74
+ });
75
+ });
76
+ });
@@ -0,0 +1,156 @@
1
+ /* json-schema-library bowtie integration (test harness) */
2
+ import readline from "readline/promises";
3
+ import process from "node:process";
4
+ import os from "os";
5
+ import packageJson from "json-schema-library/package.json";
6
+ import { compileSchema, type SchemaNode } from "json-schema-library";
7
+ import { remotes } from "json-schema-library/remotes";
8
+ import {
9
+ createBowtieError,
10
+ type Command,
11
+ type CommandMap,
12
+ type CommandResponse,
13
+ type Dialect,
14
+ RunCmdResponse,
15
+ sendToBowtie
16
+ } from "./bowtie-api";
17
+
18
+ /** track command sequence and abort if something is off */
19
+ let state: "started" | "dialect" | "testing" | "stopped" = "stopped";
20
+ /** current JSON Schema draft version to test */
21
+ let dialect: Dialect;
22
+ let remote: SchemaNode;
23
+
24
+ const cmds: CommandMap = {
25
+ start: async (args) => {
26
+ console.assert(args.version === 1, { args });
27
+ console.assert(state === "stopped");
28
+ state = "started";
29
+ return {
30
+ version: 1,
31
+ implementation: {
32
+ language: "typescript",
33
+ name: "json-schema-library",
34
+ version: packageJson.version,
35
+ homepage: "https://github.com/sagold/json-schema-library",
36
+ issues: "https://github.com/sagold/json-schema-library/issues",
37
+ source: "https://github.com/sagold/json-schema-library",
38
+
39
+ dialects: [
40
+ "https://json-schema.org/draft/2020-12/schema",
41
+ "https://json-schema.org/draft/2019-09/schema",
42
+ "http://json-schema.org/draft-07/schema#",
43
+ "http://json-schema.org/draft-06/schema#",
44
+ "http://json-schema.org/draft-04/schema#"
45
+ ],
46
+ os: os.platform(),
47
+ os_version: os.release(),
48
+ language_version: process.version
49
+ }
50
+ };
51
+ },
52
+
53
+ dialect: async (args) => {
54
+ console.assert(state === "started");
55
+ state = "dialect";
56
+ dialect = args.dialect;
57
+
58
+ const node = compileSchema({ $schema: dialect });
59
+ remotes.forEach((schema) => {
60
+ node.addRemoteSchema(schema.$id ?? schema.id, schema);
61
+ });
62
+ remote = node;
63
+
64
+ return { ok: true };
65
+ },
66
+
67
+ run: async (args) => {
68
+ console.assert(state === "dialect" || state === "testing");
69
+ state = "testing";
70
+ const { schema, tests, registry } = args.case;
71
+ if (schema != null && typeof schema === "object") {
72
+ schema.$schema = dialect; // set draft version to non-boolean schema
73
+ }
74
+ // compile schema
75
+ const node = compileSchema(schema, { remote, formatAssertion: false });
76
+ // add remote schemata
77
+ for (const id in registry) {
78
+ node.addRemoteSchema(id, registry[id]);
79
+ }
80
+ // run test cases and collect results to be sent back to bowtie
81
+ const results: RunCmdResponse["results"] = tests.map((test) => {
82
+ try {
83
+ return { valid: node.validate(test.instance).valid };
84
+ } catch (e) {
85
+ return createBowtieError(e.message ?? e, e.stack);
86
+ }
87
+ });
88
+ // response to bowtie for run command
89
+ return { seq: args.seq, results: results };
90
+ },
91
+
92
+ stop: async (_, stdio) => {
93
+ console.assert(state === "testing");
94
+ state = "stopped";
95
+ if (process.env.JLIB_TEST_RUN !== "true") {
96
+ stdio?.close();
97
+ process.exit(0);
98
+ }
99
+ }
100
+ } as const;
101
+
102
+ export async function runCommand(request: Command, stdio?: readline.Interface) {
103
+ let response: CommandResponse = undefined;
104
+ switch (request.cmd) {
105
+ case "start":
106
+ response = await cmds.start(request);
107
+ break;
108
+ case "dialect":
109
+ response = await cmds.dialect(request);
110
+ break;
111
+ case "run":
112
+ try {
113
+ response = await cmds.run(request);
114
+ return response;
115
+ } catch (error) {
116
+ response = {
117
+ seq: request.seq,
118
+ ...createBowtieError((error as Error).message ?? error, (error as Error).stack)
119
+ };
120
+ }
121
+ break;
122
+ case "stop":
123
+ await cmds.stop(request, stdio);
124
+ break;
125
+ }
126
+ return response;
127
+ }
128
+
129
+ function isCommand(value: unknown): value is Command {
130
+ return value != null && typeof value === "object" && "cmd" in value && cmds[value.cmd as keyof typeof cmds] != null;
131
+ }
132
+
133
+ /** listen for commands to be sent to container */
134
+ async function waitForBowtieCommands() {
135
+ const stdio = readline.createInterface({
136
+ input: process.stdin,
137
+ output: process.stdout,
138
+ terminal: false
139
+ });
140
+ for await (const line of stdio) {
141
+ let response;
142
+ try {
143
+ const request = JSON.parse(line);
144
+ response = isCommand(request)
145
+ ? await runCommand(request, stdio)
146
+ : createBowtieError(`Unsupported command received: '${line}'`);
147
+ } catch (e) {
148
+ response = createBowtieError(`Invalid json received: '${line}': ${(e as Error)?.message}`);
149
+ }
150
+ sendToBowtie(response);
151
+ }
152
+ }
153
+
154
+ if (process.env.JLIB_TEST_RUN !== "true") {
155
+ waitForBowtieCommands();
156
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "bowtie-jlib",
3
+ "version": "1.0.0",
4
+ "scripts": {
5
+ "format": "prettier -w ."
6
+ },
7
+ "dependencies": {
8
+ "json-schema-library": "*",
9
+ "prettier": "^3.8.1"
10
+ }
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../tsconfig.test.json",
3
+ "compilerOptions": {
4
+ "baseUrl": "..",
5
+ "rootDir": "..",
6
+ "paths": {
7
+ "json-schema-library": ["./index.ts"],
8
+ "json-schema-library/remotes": ["./remotes/index.ts"],
9
+ "json-schema-library/package.json": ["./package.json"]
10
+ }
11
+ }
12
+ }