@hyperjump/json-schema 1.5.1 → 1.6.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.
- package/README.md +28 -6
- package/lib/context-uri.browser.js +1 -0
- package/lib/context-uri.js +4 -0
- package/lib/core.d.ts +1 -1
- package/lib/core.js +6 -0
- package/lib/fetch.js +7 -7
- package/lib/index.js +4 -0
- package/lib/keywords/additionalProperties.js +13 -15
- package/lib/keywords/conditional.js +65 -0
- package/lib/keywords/contains.js +2 -2
- package/lib/keywords/dependentRequired.js +1 -3
- package/lib/keywords/dependentSchemas.js +2 -5
- package/lib/keywords/itemPattern.js +88 -0
- package/lib/keywords/prefixItems.js +1 -5
- package/lib/nfa.js +95 -0
- package/lib/schema.js +13 -9
- package/package.json +8 -3
- package/stable/index.js +13 -10
- package/stable/meta/applicator.js +27 -1
- package/stable/meta/content.js +1 -1
- package/stable/meta/core.js +1 -1
- package/stable/meta/format-annotation.js +1 -1
- package/stable/meta/format-assertion.js +1 -1
- package/stable/meta/meta-data.js +1 -1
- package/stable/meta/unevaluated.js +1 -1
- package/stable/meta/validation.js +1 -1
- package/stable/validation.js +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,9 @@ A collection of modules for working with JSON Schemas.
|
|
|
18
18
|
* Provides utilities for working with annotations
|
|
19
19
|
|
|
20
20
|
## Install
|
|
21
|
-
Includes support for node.js (ES Modules, TypeScript) and browsers
|
|
21
|
+
Includes support for node.js/bun.js (ES Modules, TypeScript) and browsers (works
|
|
22
|
+
with CSP
|
|
23
|
+
[`unsafe-eval`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions)).
|
|
22
24
|
|
|
23
25
|
### Node.js
|
|
24
26
|
```bash
|
|
@@ -40,6 +42,16 @@ configuration.
|
|
|
40
42
|
]
|
|
41
43
|
```
|
|
42
44
|
|
|
45
|
+
### TypeScript
|
|
46
|
+
This package uses the package.json "exports" field. [TypeScript understands
|
|
47
|
+
"exports"](https://devblogs.microsoft.com/typescript/announcing-typescript-4-5-beta/#packagejson-exports-imports-and-self-referencing),
|
|
48
|
+
but you need to change a couple settings in your `tsconfig.json` for it to work.
|
|
49
|
+
|
|
50
|
+
```jsonc
|
|
51
|
+
"module": "Node16", // or "NodeNext"
|
|
52
|
+
"moduleResolution": "Node16", // or "NodeNext"
|
|
53
|
+
```
|
|
54
|
+
|
|
43
55
|
### Versioning
|
|
44
56
|
The API for this library is divided into two categories: Stable and
|
|
45
57
|
Experimental. The Stable API strictly follows semantic versioning, but the
|
|
@@ -96,20 +108,30 @@ const output2 = isString(42);
|
|
|
96
108
|
|
|
97
109
|
**Fetching schemas**
|
|
98
110
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
has an identifier with an http(s) scheme (**https**://example.com), it's not
|
|
102
|
-
allowed to reference schemas with a file scheme
|
|
103
|
-
(**file**:///path/to/my/schemas).
|
|
111
|
+
Schemas that are available on the web can be loaded automatically without
|
|
112
|
+
needing to load them manually.
|
|
104
113
|
|
|
105
114
|
```javascript
|
|
106
115
|
const output = await validate("http://example.com/schemas/string", "foo");
|
|
107
116
|
```
|
|
108
117
|
|
|
118
|
+
When running on the server, you can also load schemas directly from the
|
|
119
|
+
filesystem using `file:` URIs. When fetching from the file system, there are
|
|
120
|
+
limitations for security reasons. If your schema has an identifier with an
|
|
121
|
+
http(s) scheme (**https**://example.com), it's not allowed to reference schemas
|
|
122
|
+
with a file scheme (**file**:///path/to/my/schemas).
|
|
123
|
+
|
|
109
124
|
```javascript
|
|
110
125
|
const output = await validate(`file://${__dirname}/string.schema.json`, "foo");
|
|
111
126
|
```
|
|
112
127
|
|
|
128
|
+
If the schema URI is relative, the base URI in the browser is the browser
|
|
129
|
+
location and the base URI on the server is the current working directory.
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
const output = await validate(`./string.schema.json`, "foo");
|
|
133
|
+
```
|
|
134
|
+
|
|
113
135
|
**Media type plugins**
|
|
114
136
|
|
|
115
137
|
There is a plugin system for adding support for different media types. By
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const contextUri = () => document.location.toString();
|
package/lib/core.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export const addSchema: typeof add;
|
|
|
20
20
|
|
|
21
21
|
export type Validator = (value: unknown, outputFormat?: OutputFormat) => OutputUnit;
|
|
22
22
|
|
|
23
|
-
export type OutputFormat = "FLAG" | "BASIC" | "DETAILED" | "VERBOSE"
|
|
23
|
+
export type OutputFormat = "FLAG" | "BASIC" | "DETAILED" | "VERBOSE";
|
|
24
24
|
|
|
25
25
|
export type OutputUnit = {
|
|
26
26
|
keyword: string;
|
package/lib/core.js
CHANGED
|
@@ -90,9 +90,15 @@ subscribe("validate.metaValidate", async (message, schema) => {
|
|
|
90
90
|
const isDynamicRefEnabled = isExperimentalKeywordEnabled(dyanmicRefKeywordId);
|
|
91
91
|
setExperimentalKeywordEnabled(dyanmicRefKeywordId, true);
|
|
92
92
|
|
|
93
|
+
// itemPattern is experimental, but is necessary for meta-validation
|
|
94
|
+
const itemPatternKeywordId = "https://json-schema.org/keyword/itemPattern";
|
|
95
|
+
const isItemPatternEnabled = isExperimentalKeywordEnabled(itemPatternKeywordId);
|
|
96
|
+
setExperimentalKeywordEnabled(itemPatternKeywordId, true);
|
|
97
|
+
|
|
93
98
|
const compiledSchema = await compile(schema.dialectId);
|
|
94
99
|
metaValidators[schema.dialectId] = interpret(compiledSchema);
|
|
95
100
|
|
|
101
|
+
setExperimentalKeywordEnabled(itemPatternKeywordId, isItemPatternEnabled);
|
|
96
102
|
setExperimentalKeywordEnabled(dyanmicRefKeywordId, isDynamicRefEnabled);
|
|
97
103
|
}
|
|
98
104
|
|
package/lib/fetch.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
2
4
|
import { fetch, Response } from "undici";
|
|
3
|
-
import Url from "url";
|
|
4
5
|
import * as MediaTypes from "./media-types.js";
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
export default
|
|
8
|
+
export default (url, options) => {
|
|
8
9
|
if (url.startsWith("file://")) {
|
|
9
|
-
const filePath =
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const response = new Response(stream, {
|
|
10
|
+
const filePath = fileURLToPath(url);
|
|
11
|
+
const stream = createReadStream(filePath);
|
|
12
|
+
const response = new Response(Readable.toWeb(stream), {
|
|
13
13
|
headers: { "Content-Type": MediaTypes.getContentType(filePath) }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(response, "url", { value: url });
|
package/lib/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import additionalProperties from "./keywords/additionalProperties.js";
|
|
|
5
5
|
import allOf from "./keywords/allOf.js";
|
|
6
6
|
import anchor from "./keywords/anchor.js";
|
|
7
7
|
import anyOf from "./keywords/anyOf.js";
|
|
8
|
+
import conditional from "./keywords/conditional.js";
|
|
8
9
|
import const_ from "./keywords/const.js";
|
|
9
10
|
import contains from "./keywords/contains.js";
|
|
10
11
|
import comment from "./keywords/comment.js";
|
|
@@ -27,6 +28,7 @@ import exclusiveMinimum from "./keywords/exclusiveMinimum.js";
|
|
|
27
28
|
import format from "./keywords/format.js";
|
|
28
29
|
import id from "./keywords/id.js";
|
|
29
30
|
import if_ from "./keywords/if.js";
|
|
31
|
+
import itemPattern from "./keywords/itemPattern.js";
|
|
30
32
|
import items from "./keywords/items.js";
|
|
31
33
|
import maxContains from "./keywords/maxContains.js";
|
|
32
34
|
import maxItems from "./keywords/maxItems.js";
|
|
@@ -74,6 +76,7 @@ addKeyword(additionalProperties);
|
|
|
74
76
|
addKeyword(allOf);
|
|
75
77
|
addKeyword(anchor);
|
|
76
78
|
addKeyword(anyOf);
|
|
79
|
+
addKeyword(conditional);
|
|
77
80
|
addKeyword(const_);
|
|
78
81
|
addKeyword(contains);
|
|
79
82
|
addKeyword(comment);
|
|
@@ -120,6 +123,7 @@ addKeyword(readOnly);
|
|
|
120
123
|
addKeyword(ref);
|
|
121
124
|
addKeyword(requireAllExcept);
|
|
122
125
|
addKeyword(required);
|
|
126
|
+
addKeyword(itemPattern);
|
|
123
127
|
addKeyword(title);
|
|
124
128
|
addKeyword(then);
|
|
125
129
|
addKeyword(type);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { filter, every, pipe } from "@hyperjump/pact";
|
|
1
|
+
import { concat, join, empty, map, filter, every, pipe } from "@hyperjump/pact";
|
|
2
2
|
import * as Schema from "../schema.js";
|
|
3
3
|
import * as Instance from "../instance.js";
|
|
4
4
|
import { getKeywordName } from "../keywords.js";
|
|
@@ -8,26 +8,24 @@ import Validation from "./validation.js";
|
|
|
8
8
|
const id = "https://json-schema.org/keyword/additionalProperties";
|
|
9
9
|
|
|
10
10
|
const compile = async (schema, ast, parentSchema) => {
|
|
11
|
-
const patterns = [];
|
|
12
|
-
|
|
13
11
|
const propertiesKeyword = getKeywordName(schema.dialectId, "https://json-schema.org/keyword/properties");
|
|
14
12
|
const propertiesSchema = await Schema.step(propertiesKeyword, parentSchema);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
}
|
|
13
|
+
const propertyPatterns = Schema.typeOf(propertiesSchema, "object")
|
|
14
|
+
? map((propertyName) => "^" + regexEscape(propertyName) + "$", Schema.keys(propertiesSchema))
|
|
15
|
+
: empty();
|
|
20
16
|
|
|
21
17
|
const patternPropertiesKeyword = getKeywordName(schema.dialectId, "https://json-schema.org/keyword/patternProperties");
|
|
22
18
|
const patternProperties = await Schema.step(patternPropertiesKeyword, parentSchema);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const patternPropertyPatterns = Schema.typeOf(patternProperties, "object")
|
|
20
|
+
? Schema.keys(patternProperties)
|
|
21
|
+
: empty();
|
|
22
|
+
|
|
23
|
+
const pattern = pipe(
|
|
24
|
+
concat(propertyPatterns, patternPropertyPatterns),
|
|
25
|
+
join("|")
|
|
26
|
+
) || "(?!)";
|
|
26
27
|
|
|
27
|
-
return [
|
|
28
|
-
new RegExp(patterns.length > 0 ? patterns.join("|") : "(?!)", "u"),
|
|
29
|
-
await Validation.compile(schema, ast)
|
|
30
|
-
];
|
|
28
|
+
return [new RegExp(pattern, "u"), await Validation.compile(schema, ast)];
|
|
31
29
|
};
|
|
32
30
|
|
|
33
31
|
const regexEscape = (string) => string
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { pipe, asyncMap, asyncCollectArray } from "@hyperjump/pact";
|
|
2
|
+
import * as Schema from "../schema.js";
|
|
3
|
+
import Validation from "./validation.js";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const id = "https://json-schema.org/keyword/conditional";
|
|
7
|
+
const experimental = true;
|
|
8
|
+
|
|
9
|
+
const compile = (schema, ast) => pipe(
|
|
10
|
+
Schema.iter(schema),
|
|
11
|
+
schemaFlatten,
|
|
12
|
+
asyncMap((subSchema) => Validation.compile(subSchema, ast)),
|
|
13
|
+
asyncCollectArray
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const interpret = (conditional, instance, ast, dynamicAnchors, quiet) => {
|
|
17
|
+
for (let index = 0; index < conditional.length; index += 2) {
|
|
18
|
+
const isValid = Validation.interpret(conditional[index], instance, ast, dynamicAnchors, quiet);
|
|
19
|
+
if (index + 1 === conditional.length) {
|
|
20
|
+
return isValid;
|
|
21
|
+
} else if (isValid) {
|
|
22
|
+
return Validation.interpret(conditional[index + 1], instance, ast, dynamicAnchors, quiet);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const collectEvaluatedProperties = (conditional, instance, ast, dynamicAnchors) => {
|
|
30
|
+
for (let index = 0; index < conditional.length; index += 2) {
|
|
31
|
+
const unevaluatedProperties = Validation.collectEvaluatedProperties(conditional[index], instance, ast, dynamicAnchors);
|
|
32
|
+
if (index + 1 === conditional.length) {
|
|
33
|
+
return unevaluatedProperties;
|
|
34
|
+
} else if (unevaluatedProperties !== false) {
|
|
35
|
+
return Validation.collectEvaluatedProperties(conditional[index + 1], instance, ast, dynamicAnchors);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return new Set();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const collectEvaluatedItems = (conditional, instance, ast, dynamicAnchors) => {
|
|
43
|
+
for (let index = 0; index < conditional.length; index += 2) {
|
|
44
|
+
const unevaluatedItems = Validation.collectEvaluatedItems(conditional[index], instance, ast, dynamicAnchors);
|
|
45
|
+
if (index + 1 === conditional.length) {
|
|
46
|
+
return unevaluatedItems;
|
|
47
|
+
} else if (unevaluatedItems !== false) {
|
|
48
|
+
return Validation.collectEvaluatedItems(conditional[index + 1], instance, ast, dynamicAnchors);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new Set();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const schemaFlatten = async function* (iter, depth = 1) {
|
|
56
|
+
for await (const n of iter) {
|
|
57
|
+
if (depth > 0 && Schema.typeOf(n, "array")) {
|
|
58
|
+
yield* schemaFlatten(Schema.iter(n), depth - 1);
|
|
59
|
+
} else {
|
|
60
|
+
yield n;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default { id, experimental, compile, interpret, collectEvaluatedProperties, collectEvaluatedItems };
|
package/lib/keywords/contains.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pipe, map, filter,
|
|
1
|
+
import { pipe, map, filter, count, collectSet, zip, range } from "@hyperjump/pact";
|
|
2
2
|
import * as Schema from "../schema.js";
|
|
3
3
|
import * as Instance from "../instance.js";
|
|
4
4
|
import { getKeywordName } from "../keywords.js";
|
|
@@ -34,7 +34,7 @@ const interpret = ({ contains, minContains, maxContains }, instance, ast, dynami
|
|
|
34
34
|
const matches = pipe(
|
|
35
35
|
iterator,
|
|
36
36
|
filter((item) => Validation.interpret(contains, item, ast, dynamicAnchors, quiet)),
|
|
37
|
-
|
|
37
|
+
count
|
|
38
38
|
);
|
|
39
39
|
return matches >= minContains && matches <= maxContains;
|
|
40
40
|
};
|
|
@@ -12,10 +12,8 @@ const compile = (schema) => pipe(
|
|
|
12
12
|
);
|
|
13
13
|
|
|
14
14
|
const interpret = (dependentRequired, instance) => {
|
|
15
|
-
const value = Instance.value(instance);
|
|
16
|
-
|
|
17
15
|
return !Instance.typeOf(instance, "object") || dependentRequired.every(([propertyName, required]) => {
|
|
18
|
-
return !(propertyName
|
|
16
|
+
return !Instance.has(propertyName, instance) || required.every((key) => Instance.has(key, instance));
|
|
19
17
|
});
|
|
20
18
|
};
|
|
21
19
|
|
|
@@ -13,22 +13,19 @@ const compile = (schema, ast) => pipe(
|
|
|
13
13
|
);
|
|
14
14
|
|
|
15
15
|
const interpret = (dependentSchemas, instance, ast, dynamicAnchors, quiet) => {
|
|
16
|
-
const value = Instance.value(instance);
|
|
17
|
-
|
|
18
16
|
return !Instance.typeOf(instance, "object") || dependentSchemas.every(([propertyName, dependentSchema]) => {
|
|
19
|
-
return !(propertyName
|
|
17
|
+
return !Instance.has(propertyName, instance) || Validation.interpret(dependentSchema, instance, ast, dynamicAnchors, quiet);
|
|
20
18
|
});
|
|
21
19
|
};
|
|
22
20
|
|
|
23
21
|
const collectEvaluatedProperties = (dependentSchemas, instance, ast, dynamicAnchors) => {
|
|
24
|
-
const value = Instance.value(instance);
|
|
25
22
|
if (!Instance.typeOf(instance, "object")) {
|
|
26
23
|
return false;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
const evaluatedPropertyNames = new Set();
|
|
30
27
|
for (const [propertyName, dependentSchema] of dependentSchemas) {
|
|
31
|
-
if (propertyName
|
|
28
|
+
if (Instance.has(propertyName, instance)) {
|
|
32
29
|
const propertyNames = Validation.collectEvaluatedProperties(dependentSchema, instance, ast, dynamicAnchors);
|
|
33
30
|
if (propertyNames === false) {
|
|
34
31
|
return false;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as Schema from "../schema.js";
|
|
2
|
+
import * as Instance from "../instance.js";
|
|
3
|
+
import Validation from "./validation.js";
|
|
4
|
+
import { fromEpsilon, fromSchema, closure, zeroOrOne, oneOrMore, concat, union } from "../nfa.js";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const id = "https://json-schema.org/keyword/itemPattern";
|
|
8
|
+
const experimental = true;
|
|
9
|
+
|
|
10
|
+
const compile = async (schema, ast) => {
|
|
11
|
+
const groups = [[]];
|
|
12
|
+
let group = groups[0];
|
|
13
|
+
|
|
14
|
+
for await (const rule of Schema.iter(schema)) {
|
|
15
|
+
if (Schema.typeOf(rule, "string")) {
|
|
16
|
+
const operator = Schema.value(rule);
|
|
17
|
+
|
|
18
|
+
if (operator === "*") {
|
|
19
|
+
group.push(closure(group.pop()));
|
|
20
|
+
} else if (operator === "?") {
|
|
21
|
+
group.push(zeroOrOne(group.pop()));
|
|
22
|
+
} else if (operator === "+") {
|
|
23
|
+
group.push(oneOrMore(group.pop()));
|
|
24
|
+
} else if (operator === "|") {
|
|
25
|
+
group = [];
|
|
26
|
+
groups.push(group);
|
|
27
|
+
} else {
|
|
28
|
+
throw Error(`Unsupported pattern syntax: ${operator}`);
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
const node = Schema.typeOf(rule, "array")
|
|
32
|
+
? compile(rule, ast)
|
|
33
|
+
: fromSchema(await Validation.compile(rule, ast));
|
|
34
|
+
group.push(await node);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return Schema.length(schema) === 0 ? fromEpsilon() : groups
|
|
39
|
+
.map((group) => group.reduce(concat))
|
|
40
|
+
.reduce(union);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const interpret = (nfa, instance, ast, dynamicAnchors, quiet) => {
|
|
44
|
+
if (!Instance.typeOf(instance, "array")) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let currentStates = [];
|
|
49
|
+
addNextState(nfa.start, currentStates, []);
|
|
50
|
+
|
|
51
|
+
for (const item of Instance.iter(instance)) {
|
|
52
|
+
const nextStates = [];
|
|
53
|
+
|
|
54
|
+
for (const state of currentStates) {
|
|
55
|
+
const nextState = transition(state.transition, item, ast, dynamicAnchors, quiet);
|
|
56
|
+
if (nextState) {
|
|
57
|
+
addNextState(nextState, nextStates, []);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
currentStates = nextStates;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return Boolean(currentStates.find((s) => s.isEnd));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const addNextState = (state, nextStates, visited) => {
|
|
68
|
+
if (state.epsilonTransitions.length) {
|
|
69
|
+
for (const epsilonState of state.epsilonTransitions) {
|
|
70
|
+
if (!visited.find((visited) => visited === epsilonState)) {
|
|
71
|
+
visited.push(epsilonState);
|
|
72
|
+
addNextState(epsilonState, nextStates, visited);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
nextStates.push(state);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const transition = (transitions, instance, ast, dynamicAnchors, quiet) => {
|
|
81
|
+
for (const schema in transitions) {
|
|
82
|
+
if (Validation.interpret(schema, instance, ast, dynamicAnchors, quiet)) {
|
|
83
|
+
return transitions[schema];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default { id, experimental, compile, interpret };
|
|
@@ -13,11 +13,7 @@ const compile = (schema, ast) => pipe(
|
|
|
13
13
|
);
|
|
14
14
|
|
|
15
15
|
const interpret = (prefixItems, instance, ast, dynamicAnchors, quiet) => {
|
|
16
|
-
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return pipe(
|
|
16
|
+
return !Instance.typeOf(instance, "array") || pipe(
|
|
21
17
|
zip(prefixItems, Instance.iter(instance)),
|
|
22
18
|
take(Instance.length(instance)),
|
|
23
19
|
every(([prefixItem, item]) => Validation.interpret(prefixItem, item, ast, dynamicAnchors, quiet))
|
package/lib/nfa.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export const fromEpsilon = () => {
|
|
2
|
+
const start = createState(false);
|
|
3
|
+
const end = createState(true);
|
|
4
|
+
addEpsilonTransition(start, end);
|
|
5
|
+
|
|
6
|
+
return { start, end };
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const fromSchema = (schema) => {
|
|
10
|
+
const start = createState(false);
|
|
11
|
+
const end = createState(true);
|
|
12
|
+
addTransition(start, end, schema);
|
|
13
|
+
|
|
14
|
+
return { start, end };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const concat = (first, second) => {
|
|
18
|
+
if (first === undefined) {
|
|
19
|
+
return second;
|
|
20
|
+
}
|
|
21
|
+
addEpsilonTransition(first.end, second.start);
|
|
22
|
+
first.end.isEnd = false;
|
|
23
|
+
|
|
24
|
+
return { start: first.start, end: second.end };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const union = (first, second) => {
|
|
28
|
+
const start = createState(false);
|
|
29
|
+
addEpsilonTransition(start, first.start);
|
|
30
|
+
addEpsilonTransition(start, second.start);
|
|
31
|
+
|
|
32
|
+
const end = createState(true);
|
|
33
|
+
|
|
34
|
+
addEpsilonTransition(first.end, end);
|
|
35
|
+
first.end.isEnd = false;
|
|
36
|
+
addEpsilonTransition(second.end, end);
|
|
37
|
+
second.end.isEnd = false;
|
|
38
|
+
|
|
39
|
+
return { start, end };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const closure = (nfa) => {
|
|
43
|
+
const start = createState(false);
|
|
44
|
+
const end = createState(true);
|
|
45
|
+
|
|
46
|
+
addEpsilonTransition(start, end);
|
|
47
|
+
addEpsilonTransition(start, nfa.start);
|
|
48
|
+
|
|
49
|
+
addEpsilonTransition(nfa.end, end);
|
|
50
|
+
addEpsilonTransition(nfa.end, nfa.start);
|
|
51
|
+
nfa.end.isEnd = false;
|
|
52
|
+
|
|
53
|
+
return { start, end };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const zeroOrOne = (nfa) => {
|
|
57
|
+
const start = createState(false);
|
|
58
|
+
const end = createState(true);
|
|
59
|
+
|
|
60
|
+
addEpsilonTransition(start, end);
|
|
61
|
+
addEpsilonTransition(start, nfa.start);
|
|
62
|
+
|
|
63
|
+
addEpsilonTransition(nfa.end, end);
|
|
64
|
+
nfa.end.isEnd = false;
|
|
65
|
+
|
|
66
|
+
return { start, end };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const oneOrMore = (nfa) => {
|
|
70
|
+
const start = createState(false);
|
|
71
|
+
const end = createState(true);
|
|
72
|
+
|
|
73
|
+
addEpsilonTransition(start, nfa.start);
|
|
74
|
+
addEpsilonTransition(nfa.end, end);
|
|
75
|
+
addEpsilonTransition(nfa.end, nfa.start);
|
|
76
|
+
nfa.end.isEnd = false;
|
|
77
|
+
|
|
78
|
+
return { start, end };
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const addEpsilonTransition = (from, to) => {
|
|
82
|
+
from.epsilonTransitions.push(to);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const addTransition = (from, to, symbol) => {
|
|
86
|
+
from.transition[symbol] = to;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const createState = (isEnd) => {
|
|
90
|
+
return {
|
|
91
|
+
isEnd,
|
|
92
|
+
transition: {},
|
|
93
|
+
epsilonTransitions: []
|
|
94
|
+
};
|
|
95
|
+
};
|
package/lib/schema.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { nil as nilPointer, append as pointerAppend, get as pointerGet } from "@hyperjump/json-pointer";
|
|
2
2
|
import { toAbsoluteIri } from "@hyperjump/uri";
|
|
3
3
|
import { jsonTypeOf, resolveUri, uriFragment, pathRelative, jsonStringify } from "./common.js";
|
|
4
|
+
import { contextUri } from "./context-uri.js";
|
|
4
5
|
import fetch from "./fetch.js";
|
|
5
6
|
import { hasDialect, loadDialect, getKeywordName } from "./keywords.js";
|
|
6
7
|
import { parseResponse, acceptableMediaTypes } from "./media-types.js";
|
|
@@ -11,13 +12,14 @@ import * as Reference from "./reference.js";
|
|
|
11
12
|
const schemaStore = {};
|
|
12
13
|
const schemaStoreAlias = {};
|
|
13
14
|
|
|
14
|
-
const defaultDialectId = "https://json-schema.org/validation";
|
|
15
|
-
|
|
16
15
|
export const add = (schema, retrievalUri = undefined, contextDialectId = undefined) => {
|
|
17
16
|
schema = JSON.parse(JSON.stringify(schema));
|
|
18
17
|
|
|
19
18
|
// Dialect / JSON Schema Version
|
|
20
|
-
|
|
19
|
+
if ((typeof schema !== "object" || !("$schema" in schema)) && !contextDialectId) {
|
|
20
|
+
throw Error("Unable to determine a dialect for the schema. The dialect can be declared in a number of ways, but the recommended way is to use the '$schema' keyword in your schema.");
|
|
21
|
+
}
|
|
22
|
+
const dialectId = toAbsoluteIri(schema.$schema || contextDialectId);
|
|
21
23
|
delete schema.$schema;
|
|
22
24
|
|
|
23
25
|
if (!hasDialect(dialectId)) {
|
|
@@ -159,7 +161,7 @@ const nil = {
|
|
|
159
161
|
};
|
|
160
162
|
|
|
161
163
|
export const get = async (url, contextDoc = nil) => {
|
|
162
|
-
const resolvedUrl = resolveUri(url, contextDoc.id);
|
|
164
|
+
const resolvedUrl = resolveUri(url, contextDoc.id || contextUri());
|
|
163
165
|
const id = toAbsoluteIri(resolvedUrl);
|
|
164
166
|
const fragment = uriFragment(resolvedUrl);
|
|
165
167
|
|
|
@@ -173,9 +175,11 @@ export const get = async (url, contextDoc = nil) => {
|
|
|
173
175
|
const [schema, contextDialectId] = await parseResponse(response);
|
|
174
176
|
|
|
175
177
|
// Try to determine the dialect from the meta-schema if it isn't already known
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
if (schema.$schema || contextDialectId) {
|
|
179
|
+
const dialectId = toAbsoluteIri(schema.$schema || contextDialectId);
|
|
180
|
+
if (!hasDialect(dialectId) && !hasStoredSchema(dialectId)) {
|
|
181
|
+
await get(dialectId);
|
|
182
|
+
}
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
add(schema, id, contextDialectId);
|
|
@@ -204,7 +208,7 @@ const getAnchorPointer = (schema, fragment) => {
|
|
|
204
208
|
|
|
205
209
|
// Utility Functions
|
|
206
210
|
export const uri = (doc) => doc.id ? `${doc.id}#${encodeURI(doc.pointer)}` : undefined;
|
|
207
|
-
export const value = (doc) =>
|
|
211
|
+
export const value = (doc) => doc.value;
|
|
208
212
|
export const has = (key, doc) => key in value(doc);
|
|
209
213
|
export const typeOf = (doc, type) => jsonTypeOf(value(doc), type);
|
|
210
214
|
|
|
@@ -272,7 +276,7 @@ export const toSchema = (schemaDoc, options = {}) => {
|
|
|
272
276
|
dynamicAnchors[pointer] = anchor;
|
|
273
277
|
}
|
|
274
278
|
|
|
275
|
-
const schema = JSON.parse(jsonStringify(schemaDoc.schema, (
|
|
279
|
+
const schema = JSON.parse(jsonStringify(schemaDoc.schema, (_key, value, pointer) => {
|
|
276
280
|
if (Reference.isReference(value)) {
|
|
277
281
|
const refValue = Reference.value(value);
|
|
278
282
|
if (fullOptions.includeEmbedded || !(idToken in refValue)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperjump/json-schema",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "A JSON Schema validator with support for custom keywords, vocabularies, and dialects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./stable/index.js",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"./bundle": "./bundle/index.js"
|
|
22
22
|
},
|
|
23
23
|
"browser": {
|
|
24
|
-
"./lib/fetch.js": "./lib/fetch.browser.js"
|
|
24
|
+
"./lib/fetch.js": "./lib/fetch.browser.js",
|
|
25
|
+
"./lib/context-uri.js": "./lib/context-uri.browser.js"
|
|
25
26
|
},
|
|
26
27
|
"scripts": {
|
|
27
28
|
"clean": "xargs -a .gitignore rm -rf",
|
|
@@ -31,10 +32,13 @@
|
|
|
31
32
|
"repository": "github:hyperjump-io/json-schema",
|
|
32
33
|
"keywords": [
|
|
33
34
|
"JSON Schema",
|
|
35
|
+
"json-schema",
|
|
36
|
+
"jsonschema",
|
|
34
37
|
"JSON",
|
|
35
38
|
"Schema",
|
|
36
39
|
"2020-12",
|
|
37
40
|
"2019-09",
|
|
41
|
+
"draft-07",
|
|
38
42
|
"draft-06",
|
|
39
43
|
"draft-04",
|
|
40
44
|
"vocabulary",
|
|
@@ -49,6 +53,7 @@
|
|
|
49
53
|
"devDependencies": {
|
|
50
54
|
"@types/chai": "*",
|
|
51
55
|
"@types/mocha": "*",
|
|
56
|
+
"@types/node": "^20.6.2",
|
|
52
57
|
"@typescript-eslint/eslint-plugin": "*",
|
|
53
58
|
"@typescript-eslint/parser": "*",
|
|
54
59
|
"chai": "*",
|
|
@@ -64,7 +69,7 @@
|
|
|
64
69
|
},
|
|
65
70
|
"dependencies": {
|
|
66
71
|
"@hyperjump/json-pointer": "^1.0.0",
|
|
67
|
-
"@hyperjump/pact": "^1.
|
|
72
|
+
"@hyperjump/pact": "^1.2.0",
|
|
68
73
|
"@hyperjump/uri": "^1.2.0",
|
|
69
74
|
"content-type": "^1.0.4",
|
|
70
75
|
"fastest-stable-stringify": "^2.0.2",
|
package/stable/index.js
CHANGED
|
@@ -34,7 +34,9 @@ defineVocabulary("https://json-schema.org/vocab/applicator", {
|
|
|
34
34
|
"if": "https://json-schema.org/keyword/if",
|
|
35
35
|
"then": "https://json-schema.org/keyword/then",
|
|
36
36
|
"else": "https://json-schema.org/keyword/else",
|
|
37
|
+
"conditional": "https://json-schema.org/keyword/conditional",
|
|
37
38
|
"items": "https://json-schema.org/keyword/items",
|
|
39
|
+
"itemPattern": "https://json-schema.org/keyword/itemPattern",
|
|
38
40
|
"not": "https://json-schema.org/keyword/not",
|
|
39
41
|
"oneOf": "https://json-schema.org/keyword/oneOf",
|
|
40
42
|
"patternProperties": "https://json-schema.org/keyword/patternProperties",
|
|
@@ -95,7 +97,8 @@ defineVocabulary("https://json-schema.org/vocab/unevaluated", {
|
|
|
95
97
|
"unevaluatedProperties": "https://json-schema.org/keyword/unevaluatedProperties"
|
|
96
98
|
});
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
const dialectId = "https://json-schema.org/validation";
|
|
101
|
+
loadDialect(dialectId, {
|
|
99
102
|
"https://json-schema.org/vocab/core": true,
|
|
100
103
|
"https://json-schema.org/vocab/applicator": true,
|
|
101
104
|
"https://json-schema.org/vocab/validation": true,
|
|
@@ -105,14 +108,14 @@ loadDialect("https://json-schema.org/validation", {
|
|
|
105
108
|
"https://json-schema.org/vocab/unevaluated": true
|
|
106
109
|
});
|
|
107
110
|
|
|
108
|
-
addSchema(metaSchema);
|
|
109
|
-
addSchema(coreMetaSchema);
|
|
110
|
-
addSchema(applicatorMetaSchema);
|
|
111
|
-
addSchema(validationMetaSchema);
|
|
112
|
-
addSchema(metaDataMetaSchema);
|
|
113
|
-
addSchema(formatAnnotationMetaSchema);
|
|
114
|
-
addSchema(formatAssertionMetaSchema);
|
|
115
|
-
addSchema(contentMetaSchema);
|
|
116
|
-
addSchema(unevaluatedMetaSchema);
|
|
111
|
+
addSchema(metaSchema, dialectId);
|
|
112
|
+
addSchema(coreMetaSchema, "https://json-schema.org/meta/core");
|
|
113
|
+
addSchema(applicatorMetaSchema, "https://json-schema.org/meta/applicator");
|
|
114
|
+
addSchema(validationMetaSchema, "https://json-schema.org/meta/validation");
|
|
115
|
+
addSchema(metaDataMetaSchema, "https://json-schema.org/meta/meta-data");
|
|
116
|
+
addSchema(formatAnnotationMetaSchema, "https://json-schema.org/meta/format-annotation");
|
|
117
|
+
addSchema(formatAssertionMetaSchema, "https://json-schema.org/meta/format-assertion");
|
|
118
|
+
addSchema(contentMetaSchema, "https://json-schema.org/meta/content");
|
|
119
|
+
addSchema(unevaluatedMetaSchema, "https://json-schema.org/meta/unevaluated");
|
|
117
120
|
|
|
118
121
|
export * from "../lib/index.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
"$
|
|
2
|
+
"$schema": "https://json-schema.org/validation",
|
|
3
3
|
"title": "Applicator vocabulary meta-schema",
|
|
4
4
|
|
|
5
5
|
"$dynamicAnchor": "meta",
|
|
@@ -8,6 +8,7 @@ export default {
|
|
|
8
8
|
"prefixItems": { "$ref": "#/$defs/schemaArray" },
|
|
9
9
|
"items": { "$dynamicRef": "meta" },
|
|
10
10
|
"contains": { "$dynamicRef": "meta" },
|
|
11
|
+
"itemPattern": { "$ref": "#/$defs/itemPattern" },
|
|
11
12
|
"additionalProperties": { "$dynamicRef": "meta" },
|
|
12
13
|
"properties": {
|
|
13
14
|
"type": "object",
|
|
@@ -33,6 +34,16 @@ export default {
|
|
|
33
34
|
"if": { "$dynamicRef": "meta" },
|
|
34
35
|
"then": { "$dynamicRef": "meta" },
|
|
35
36
|
"else": { "$dynamicRef": "meta" },
|
|
37
|
+
"conditional": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {
|
|
40
|
+
"if": { "type": "array" },
|
|
41
|
+
"then": {
|
|
42
|
+
"items": { "$dynamicRef": "meta" }
|
|
43
|
+
},
|
|
44
|
+
"else": { "$dynamicRef": "meta" }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
36
47
|
"allOf": { "$ref": "#/$defs/schemaArray" },
|
|
37
48
|
"anyOf": { "$ref": "#/$defs/schemaArray" },
|
|
38
49
|
"oneOf": { "$ref": "#/$defs/schemaArray" },
|
|
@@ -44,6 +55,21 @@ export default {
|
|
|
44
55
|
"type": "array",
|
|
45
56
|
"minItems": 1,
|
|
46
57
|
"items": { "$dynamicRef": "meta" }
|
|
58
|
+
},
|
|
59
|
+
"itemPattern": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"itemPattern": [
|
|
62
|
+
[
|
|
63
|
+
{
|
|
64
|
+
"if": { "type": "array" },
|
|
65
|
+
"then": { "$ref": "#/$defs/itemPattern" },
|
|
66
|
+
"else": { "$dynamicRef": "meta" }
|
|
67
|
+
},
|
|
68
|
+
{ "enum": ["?", "*", "+"] }, "?",
|
|
69
|
+
"|",
|
|
70
|
+
{ "const": "|" }
|
|
71
|
+
], "*"
|
|
72
|
+
]
|
|
47
73
|
}
|
|
48
74
|
}
|
|
49
75
|
};
|
package/stable/meta/content.js
CHANGED
package/stable/meta/core.js
CHANGED
package/stable/meta/meta-data.js
CHANGED
package/stable/validation.js
CHANGED