@maroonedog/luq 2.4.3 → 2.5.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 +199 -9
- package/dist/chain/field-chain.types.d.ts +11 -1
- package/dist/chain/index.d.ts +2 -0
- package/dist/chain/plugin-not-imported.types.d.ts +40 -0
- package/dist/chain/plugin-not-imported.types.js +2 -0
- package/dist/chain/plugin-not-imported.types.mjs +1 -0
- package/dist/chain/slot-catalog.generated.d.ts +265 -0
- package/dist/chain/slot-catalog.generated.js +4 -0
- package/dist/chain/slot-catalog.generated.mjs +3 -0
- package/dist/core/type-erasure.d.ts +20 -0
- package/dist/core/type-erasure.js +23 -0
- package/dist/core/type-erasure.mjs +22 -0
- package/dist/field-rule/use-field.d.ts +2 -1
- package/dist/field-rule/use-field.js +9 -2
- package/dist/field-rule/use-field.mjs +9 -2
- package/dist/json-schema/array-keyword-guards.d.ts +31 -0
- package/dist/json-schema/array-keyword-guards.js +120 -0
- package/dist/json-schema/array-keyword-guards.mjs +114 -0
- package/dist/json-schema/assert-object-keyword-values.d.ts +59 -0
- package/dist/json-schema/assert-object-keyword-values.js +149 -0
- package/dist/json-schema/assert-object-keyword-values.mjs +141 -0
- package/dist/json-schema/collect-definitions.d.ts +7 -0
- package/dist/json-schema/collect-definitions.js +9 -0
- package/dist/json-schema/collect-definitions.mjs +10 -1
- package/dist/json-schema/declare-additional-properties.d.ts +11 -1
- package/dist/json-schema/declare-additional-properties.js +13 -1
- package/dist/json-schema/declare-additional-properties.mjs +13 -1
- package/dist/json-schema/declare-object-keywords.d.ts +4 -0
- package/dist/json-schema/declare-object-keywords.js +11 -4
- package/dist/json-schema/declare-object-keywords.mjs +11 -4
- package/dist/json-schema/declare-required-properties.js +6 -0
- package/dist/json-schema/declare-required-properties.mjs +6 -0
- package/dist/json-schema/declare-value-keywords.d.ts +10 -1
- package/dist/json-schema/declare-value-keywords.js +43 -4
- package/dist/json-schema/declare-value-keywords.mjs +43 -4
- package/dist/json-schema/extensions/json-schema/index.d.ts +1 -1
- package/dist/json-schema/extensions/json-schema/index.js +2 -1
- package/dist/json-schema/extensions/json-schema/index.mjs +1 -1
- package/dist/json-schema/flatten-array-schema.d.ts +11 -1
- package/dist/json-schema/flatten-array-schema.js +20 -7
- package/dist/json-schema/flatten-array-schema.mjs +20 -7
- package/dist/json-schema/index.d.ts +1 -0
- package/dist/json-schema/index.js +3 -1
- package/dist/json-schema/index.mjs +1 -0
- package/dist/json-schema/keyword-map-string.js +30 -1
- package/dist/json-schema/keyword-map-string.mjs +30 -1
- package/dist/json-schema/keyword-map.js +2 -0
- package/dist/json-schema/keyword-map.mjs +2 -0
- package/dist/json-schema/malformed-schema-error.d.ts +38 -0
- package/dist/json-schema/malformed-schema-error.js +103 -0
- package/dist/json-schema/malformed-schema-error.mjs +98 -0
- package/dist/plugins/manifest.generated.d.ts +18 -0
- package/dist/plugins/manifest.generated.js +77 -77
- package/dist/plugins/manifest.generated.mjs +77 -77
- package/dist/plugins/one-of/one-of.js +18 -4
- package/dist/plugins/one-of/one-of.mjs +18 -4
- package/dist/schema-tooling/index.d.ts +11 -0
- package/dist/schema-tooling/index.js +12 -1
- package/dist/schema-tooling/index.mjs +10 -0
- package/package.json +8 -6
|
@@ -46,3 +46,23 @@ export declare function eraseBuilderSurface<T extends object>(assembled: object)
|
|
|
46
46
|
* result is still correct. Exactly one call site is allowed.
|
|
47
47
|
*/
|
|
48
48
|
export declare function eraseSchemaValidator<T>(planBacked: object): T;
|
|
49
|
+
/**
|
|
50
|
+
* Why: useField runs a rule's callback against the BUILDER's slots, and the
|
|
51
|
+
* two are typed on different bags — the rule's, and the builder's. The
|
|
52
|
+
* signature proves the relation between them (`keyof BRule extends keyof B`,
|
|
53
|
+
* i.e. the builder carries at least what the rule was minted from), but that
|
|
54
|
+
* is a statement about two type parameters and there is no way to write the
|
|
55
|
+
* value side so the compiler carries it through.
|
|
56
|
+
*
|
|
57
|
+
* It used to need no erasure because both sides named one bag `B` and a richer
|
|
58
|
+
* slot object was simply assignable to a leaner one. That stopped holding when
|
|
59
|
+
* a slot began carrying a member for every method it does NOT have: where the
|
|
60
|
+
* rule's bag reports PluginNotImported the builder's may have the real method,
|
|
61
|
+
* and those two are not assignable in either direction.
|
|
62
|
+
*
|
|
63
|
+
* Soundness depends on the subset relation the signature states. The callback
|
|
64
|
+
* can only call methods its own bag declares, every one of those keys is in
|
|
65
|
+
* the builder's bag, and a key in the builder's bag is a real method rather
|
|
66
|
+
* than the not-imported marker.
|
|
67
|
+
*/
|
|
68
|
+
export declare function eraseRuleDefineToBuilderSlots<T>(define: (slots: never) => T): (slots: unknown) => T;
|
|
@@ -11,6 +11,7 @@ exports.eraseAssembledRecord = eraseAssembledRecord;
|
|
|
11
11
|
exports.eraseChainSurface = eraseChainSurface;
|
|
12
12
|
exports.eraseBuilderSurface = eraseBuilderSurface;
|
|
13
13
|
exports.eraseSchemaValidator = eraseSchemaValidator;
|
|
14
|
+
exports.eraseRuleDefineToBuilderSlots = eraseRuleDefineToBuilderSlots;
|
|
14
15
|
/**
|
|
15
16
|
* Why: a record built up key by key has all its keys at run time but stays
|
|
16
17
|
* Record<string, unknown> statically. The generic on the calling side —
|
|
@@ -67,3 +68,25 @@ function eraseBuilderSurface(assembled) {
|
|
|
67
68
|
function eraseSchemaValidator(planBacked) {
|
|
68
69
|
return planBacked;
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Why: useField runs a rule's callback against the BUILDER's slots, and the
|
|
73
|
+
* two are typed on different bags — the rule's, and the builder's. The
|
|
74
|
+
* signature proves the relation between them (`keyof BRule extends keyof B`,
|
|
75
|
+
* i.e. the builder carries at least what the rule was minted from), but that
|
|
76
|
+
* is a statement about two type parameters and there is no way to write the
|
|
77
|
+
* value side so the compiler carries it through.
|
|
78
|
+
*
|
|
79
|
+
* It used to need no erasure because both sides named one bag `B` and a richer
|
|
80
|
+
* slot object was simply assignable to a leaner one. That stopped holding when
|
|
81
|
+
* a slot began carrying a member for every method it does NOT have: where the
|
|
82
|
+
* rule's bag reports PluginNotImported the builder's may have the real method,
|
|
83
|
+
* and those two are not assignable in either direction.
|
|
84
|
+
*
|
|
85
|
+
* Soundness depends on the subset relation the signature states. The callback
|
|
86
|
+
* can only call methods its own bag declares, every one of those keys is in
|
|
87
|
+
* the builder's bag, and a key in the builder's bag is a real method rather
|
|
88
|
+
* than the not-imported marker.
|
|
89
|
+
*/
|
|
90
|
+
function eraseRuleDefineToBuilderSlots(define) {
|
|
91
|
+
return define;
|
|
92
|
+
}
|
|
@@ -61,3 +61,25 @@ export function eraseBuilderSurface(assembled) {
|
|
|
61
61
|
export function eraseSchemaValidator(planBacked) {
|
|
62
62
|
return planBacked;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Why: useField runs a rule's callback against the BUILDER's slots, and the
|
|
66
|
+
* two are typed on different bags — the rule's, and the builder's. The
|
|
67
|
+
* signature proves the relation between them (`keyof BRule extends keyof B`,
|
|
68
|
+
* i.e. the builder carries at least what the rule was minted from), but that
|
|
69
|
+
* is a statement about two type parameters and there is no way to write the
|
|
70
|
+
* value side so the compiler carries it through.
|
|
71
|
+
*
|
|
72
|
+
* It used to need no erasure because both sides named one bag `B` and a richer
|
|
73
|
+
* slot object was simply assignable to a leaner one. That stopped holding when
|
|
74
|
+
* a slot began carrying a member for every method it does NOT have: where the
|
|
75
|
+
* rule's bag reports PluginNotImported the builder's may have the real method,
|
|
76
|
+
* and those two are not assignable in either direction.
|
|
77
|
+
*
|
|
78
|
+
* Soundness depends on the subset relation the signature states. The callback
|
|
79
|
+
* can only call methods its own bag declares, every one of those keys is in
|
|
80
|
+
* the builder's bag, and a key in the builder's bag is a real method rather
|
|
81
|
+
* than the not-imported marker.
|
|
82
|
+
*/
|
|
83
|
+
export function eraseRuleDefineToBuilderSlots(define) {
|
|
84
|
+
return define;
|
|
85
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FieldBuilder } from "../builder/field-builder.types";
|
|
2
2
|
import type { PluginBag } from "../chain/plugin-bag.types";
|
|
3
|
+
import type { FieldRuleNeedsPlugins } from "../chain/plugin-not-imported.types";
|
|
3
4
|
import type { FieldPath } from "../path/field-path.types";
|
|
4
5
|
import type { ValueAtPath } from "../path/value-at-path.types";
|
|
5
6
|
import type { FieldRule } from "./field-rule.types";
|
|
6
|
-
export declare function useField<T extends object, B extends PluginBag, TDeclared extends string, K extends FieldPath<T> & string>(builder: FieldBuilder<T, B, TDeclared
|
|
7
|
+
export declare function useField<T extends object, B extends PluginBag, BRule extends PluginBag, TDeclared extends string, K extends FieldPath<T> & string>(builder: FieldBuilder<T, B, TDeclared> & (keyof BRule extends keyof B ? unknown : FieldRuleNeedsPlugins<Exclude<keyof BRule, keyof B>>), path: K, rule: FieldRule<ValueAtPath<T, K>, BRule>): FieldBuilder<T, B, TDeclared | K>;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.useField = useField;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const type_erasure_1 = require("../core/type-erasure");
|
|
5
|
+
function useField(
|
|
6
|
+
// The gate rides on the BUILDER rather than on the rule, so a builder that
|
|
7
|
+
// is missing plugins fails at the argument that is actually short of them.
|
|
8
|
+
// When it carries what the rule needs the intersection is `& unknown` and
|
|
9
|
+
// changes nothing; when it does not, the builder is asked to be a type no
|
|
10
|
+
// builder is, and the error names the plugins it lacks.
|
|
11
|
+
builder, path, rule) {
|
|
12
|
+
return builder.v(path, (0, type_erasure_1.eraseRuleDefineToBuilderSlots)(rule.define), rule.fieldOptions);
|
|
6
13
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { eraseRuleDefineToBuilderSlots } from "../core/type-erasure.mjs";
|
|
2
|
+
export function useField(
|
|
3
|
+
// The gate rides on the BUILDER rather than on the rule, so a builder that
|
|
4
|
+
// is missing plugins fails at the argument that is actually short of them.
|
|
5
|
+
// When it carries what the rule needs the intersection is `& unknown` and
|
|
6
|
+
// changes nothing; when it does not, the builder is asked to be a type no
|
|
7
|
+
// builder is, and the error names the plugins it lacks.
|
|
8
|
+
builder, path, rule) {
|
|
9
|
+
return builder.v(path, eraseRuleDefineToBuilderSlots(rule.define), rule.fieldOptions);
|
|
3
10
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Draft07Schema, Draft07SchemaObject } from "./draft07.types";
|
|
2
|
+
/** The two shapes §9.3.1 permits `items` to take. */
|
|
3
|
+
export type ItemsValue = Draft07Schema | readonly Draft07Schema[];
|
|
4
|
+
/**
|
|
5
|
+
* Reads `items` after checking it against the meta-schema: the keyword's own
|
|
6
|
+
* value, or undefined when the keyword is absent, and a refusal when it is
|
|
7
|
+
* neither of the two forms §9.3.1 defines.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readCheckedItems(schema: Draft07SchemaObject): ItemsValue | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Reads `additionalItems` after checking it is a single schema: the keyword's
|
|
12
|
+
* own value, or undefined when the keyword is absent, and a refusal for
|
|
13
|
+
* anything §4.4 does not call a schema.
|
|
14
|
+
*
|
|
15
|
+
* It is read only where §6.4.2 gives the keyword effect, which is beside the
|
|
16
|
+
* tuple form of `items`; beside the single form the draft ignores it, no rest
|
|
17
|
+
* branch is built and this reader is never reached.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readCheckedAdditionalItems(schema: Draft07SchemaObject): Draft07Schema | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Reads `contains` after checking it is a single schema: the keyword's own
|
|
22
|
+
* value, or undefined when the keyword is absent, and a refusal for anything
|
|
23
|
+
* §4.4 does not call a schema. Unlike `items`, `contains` has no array form —
|
|
24
|
+
* a tuple there is a malformed value, not a second reading.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readCheckedContains(schema: Draft07SchemaObject): Draft07Schema | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Reads `uniqueItems` after checking it is a boolean: the keyword's own value,
|
|
29
|
+
* or undefined when the keyword is absent, and a refusal for anything else.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readCheckedUniqueItems(schema: Draft07SchemaObject): boolean | undefined;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readCheckedItems = readCheckedItems;
|
|
4
|
+
exports.readCheckedAdditionalItems = readCheckedAdditionalItems;
|
|
5
|
+
exports.readCheckedContains = readCheckedContains;
|
|
6
|
+
exports.readCheckedUniqueItems = readCheckedUniqueItems;
|
|
7
|
+
// ===========================================================================
|
|
8
|
+
// L8 src/json-schema/array-keyword-guards.ts — the meta-schema checks for
|
|
9
|
+
// `items`, `additionalItems`, `contains` and `uniqueItems`, read by
|
|
10
|
+
// flatten-array-schema.ts.
|
|
11
|
+
//
|
|
12
|
+
// Every one of these keywords decides whether a rule EXISTS, and each had a
|
|
13
|
+
// route where a value the meta-schema forbids produced no rule at all rather
|
|
14
|
+
// than an error. `items: 5` is not a schema, but the sub-schema walker takes it
|
|
15
|
+
// anyway and iterates its keys; a number has none, so the walker yields zero
|
|
16
|
+
// rules and every element passes — the document asked for a constraint and the
|
|
17
|
+
// built validator carries an always-true one in its place. `additionalItems: 5`
|
|
18
|
+
// takes that same route for the rest branch, so every element past the tuple
|
|
19
|
+
// goes unchecked. `contains: 5` is the same bug wearing a disguise: an
|
|
20
|
+
// always-true element branch matches the FIRST element, so the existence
|
|
21
|
+
// requirement collapses into "the array is non-empty" — the empty array is
|
|
22
|
+
// still refused, and that lawful-looking verdict is what makes the loss of the
|
|
23
|
+
// real constraint easy to miss. `uniqueItems` is honoured only on a strict
|
|
24
|
+
// `=== true`, so `uniqueItems: "yes"`, the shape a form encoding or a loose
|
|
25
|
+
// YAML loader produces, drops to nothing the same way.
|
|
26
|
+
//
|
|
27
|
+
// A validator that quietly enforces LESS than its document says cannot be
|
|
28
|
+
// detected downstream: it answers "valid" for exactly the inputs the schema
|
|
29
|
+
// meant to refuse, and nothing in the result says a constraint went missing.
|
|
30
|
+
// Build time is therefore the only place a caller can still act, so these
|
|
31
|
+
// read the value and refuse it there.
|
|
32
|
+
//
|
|
33
|
+
// What must survive the refusal, because Draft-07 defines it: `items` has TWO
|
|
34
|
+
// legal forms — one schema for every element, or an ARRAY of schemas where
|
|
35
|
+
// position i constrains element i (§9.3.1) — `additionalItems` and `contains`
|
|
36
|
+
// take the boolean form of a schema as readily as the object form (§4.4), and
|
|
37
|
+
// `uniqueItems: false` is a lawful no-op (§6.4.3), so only a NON-boolean is
|
|
38
|
+
// refused.
|
|
39
|
+
// ===========================================================================
|
|
40
|
+
const types_1 = require("../types");
|
|
41
|
+
const draft07_types_1 = require("./draft07.types");
|
|
42
|
+
const malformed_schema_error_1 = require("./malformed-schema-error");
|
|
43
|
+
const ITEMS_REASON = "the value must be a schema or an array of schemas";
|
|
44
|
+
const UNIQUE_ITEMS_REASON = "the value must be a boolean";
|
|
45
|
+
/**
|
|
46
|
+
* Reads `items` after checking it against the meta-schema: the keyword's own
|
|
47
|
+
* value, or undefined when the keyword is absent, and a refusal when it is
|
|
48
|
+
* neither of the two forms §9.3.1 defines.
|
|
49
|
+
*/
|
|
50
|
+
function readCheckedItems(schema) {
|
|
51
|
+
const items = schema.items;
|
|
52
|
+
if (items === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
if ((0, types_1.isArray)(items))
|
|
55
|
+
return checkTuplePositions(items);
|
|
56
|
+
if (!(0, draft07_types_1.isDraft07Schema)(items)) {
|
|
57
|
+
throw new malformed_schema_error_1.MalformedSchemaError("items", ITEMS_REASON, items);
|
|
58
|
+
}
|
|
59
|
+
return items;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Every position of the tuple form is a schema in its own right, so a bad one
|
|
63
|
+
* names its index: with the whole array rendered instead, a caller holding a
|
|
64
|
+
* long tuple is told only that something in it is wrong.
|
|
65
|
+
*/
|
|
66
|
+
function checkTuplePositions(positions) {
|
|
67
|
+
for (let index = 0; index < positions.length; index += 1) {
|
|
68
|
+
const position = positions[index];
|
|
69
|
+
if ((0, draft07_types_1.isDraft07Schema)(position))
|
|
70
|
+
continue;
|
|
71
|
+
throw new malformed_schema_error_1.MalformedSchemaError("items", `position ${String(index)} of the array form ${malformed_schema_error_1.SCHEMA_FORMS}`, position);
|
|
72
|
+
}
|
|
73
|
+
return positions;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Reads `additionalItems` after checking it is a single schema: the keyword's
|
|
77
|
+
* own value, or undefined when the keyword is absent, and a refusal for
|
|
78
|
+
* anything §4.4 does not call a schema.
|
|
79
|
+
*
|
|
80
|
+
* It is read only where §6.4.2 gives the keyword effect, which is beside the
|
|
81
|
+
* tuple form of `items`; beside the single form the draft ignores it, no rest
|
|
82
|
+
* branch is built and this reader is never reached.
|
|
83
|
+
*/
|
|
84
|
+
function readCheckedAdditionalItems(schema) {
|
|
85
|
+
const additionalItems = schema.additionalItems;
|
|
86
|
+
if (additionalItems === undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
if (!(0, draft07_types_1.isDraft07Schema)(additionalItems)) {
|
|
89
|
+
throw new malformed_schema_error_1.MalformedSchemaError("additionalItems", `the value ${malformed_schema_error_1.SCHEMA_FORMS}`, additionalItems);
|
|
90
|
+
}
|
|
91
|
+
return additionalItems;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Reads `contains` after checking it is a single schema: the keyword's own
|
|
95
|
+
* value, or undefined when the keyword is absent, and a refusal for anything
|
|
96
|
+
* §4.4 does not call a schema. Unlike `items`, `contains` has no array form —
|
|
97
|
+
* a tuple there is a malformed value, not a second reading.
|
|
98
|
+
*/
|
|
99
|
+
function readCheckedContains(schema) {
|
|
100
|
+
const contains = schema.contains;
|
|
101
|
+
if (contains === undefined)
|
|
102
|
+
return undefined;
|
|
103
|
+
if (!(0, draft07_types_1.isDraft07Schema)(contains)) {
|
|
104
|
+
throw new malformed_schema_error_1.MalformedSchemaError("contains", `the value ${malformed_schema_error_1.SCHEMA_FORMS}`, contains);
|
|
105
|
+
}
|
|
106
|
+
return contains;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Reads `uniqueItems` after checking it is a boolean: the keyword's own value,
|
|
110
|
+
* or undefined when the keyword is absent, and a refusal for anything else.
|
|
111
|
+
*/
|
|
112
|
+
function readCheckedUniqueItems(schema) {
|
|
113
|
+
const uniqueItems = schema.uniqueItems;
|
|
114
|
+
if (uniqueItems === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
if (typeof uniqueItems !== "boolean") {
|
|
117
|
+
throw new malformed_schema_error_1.MalformedSchemaError("uniqueItems", UNIQUE_ITEMS_REASON, uniqueItems);
|
|
118
|
+
}
|
|
119
|
+
return uniqueItems;
|
|
120
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L8 src/json-schema/array-keyword-guards.ts — the meta-schema checks for
|
|
3
|
+
// `items`, `additionalItems`, `contains` and `uniqueItems`, read by
|
|
4
|
+
// flatten-array-schema.ts.
|
|
5
|
+
//
|
|
6
|
+
// Every one of these keywords decides whether a rule EXISTS, and each had a
|
|
7
|
+
// route where a value the meta-schema forbids produced no rule at all rather
|
|
8
|
+
// than an error. `items: 5` is not a schema, but the sub-schema walker takes it
|
|
9
|
+
// anyway and iterates its keys; a number has none, so the walker yields zero
|
|
10
|
+
// rules and every element passes — the document asked for a constraint and the
|
|
11
|
+
// built validator carries an always-true one in its place. `additionalItems: 5`
|
|
12
|
+
// takes that same route for the rest branch, so every element past the tuple
|
|
13
|
+
// goes unchecked. `contains: 5` is the same bug wearing a disguise: an
|
|
14
|
+
// always-true element branch matches the FIRST element, so the existence
|
|
15
|
+
// requirement collapses into "the array is non-empty" — the empty array is
|
|
16
|
+
// still refused, and that lawful-looking verdict is what makes the loss of the
|
|
17
|
+
// real constraint easy to miss. `uniqueItems` is honoured only on a strict
|
|
18
|
+
// `=== true`, so `uniqueItems: "yes"`, the shape a form encoding or a loose
|
|
19
|
+
// YAML loader produces, drops to nothing the same way.
|
|
20
|
+
//
|
|
21
|
+
// A validator that quietly enforces LESS than its document says cannot be
|
|
22
|
+
// detected downstream: it answers "valid" for exactly the inputs the schema
|
|
23
|
+
// meant to refuse, and nothing in the result says a constraint went missing.
|
|
24
|
+
// Build time is therefore the only place a caller can still act, so these
|
|
25
|
+
// read the value and refuse it there.
|
|
26
|
+
//
|
|
27
|
+
// What must survive the refusal, because Draft-07 defines it: `items` has TWO
|
|
28
|
+
// legal forms — one schema for every element, or an ARRAY of schemas where
|
|
29
|
+
// position i constrains element i (§9.3.1) — `additionalItems` and `contains`
|
|
30
|
+
// take the boolean form of a schema as readily as the object form (§4.4), and
|
|
31
|
+
// `uniqueItems: false` is a lawful no-op (§6.4.3), so only a NON-boolean is
|
|
32
|
+
// refused.
|
|
33
|
+
// ===========================================================================
|
|
34
|
+
import { isArray } from "../types/index.mjs";
|
|
35
|
+
import { isDraft07Schema } from "./draft07.types.mjs";
|
|
36
|
+
import { MalformedSchemaError, SCHEMA_FORMS } from "./malformed-schema-error.mjs";
|
|
37
|
+
const ITEMS_REASON = "the value must be a schema or an array of schemas";
|
|
38
|
+
const UNIQUE_ITEMS_REASON = "the value must be a boolean";
|
|
39
|
+
/**
|
|
40
|
+
* Reads `items` after checking it against the meta-schema: the keyword's own
|
|
41
|
+
* value, or undefined when the keyword is absent, and a refusal when it is
|
|
42
|
+
* neither of the two forms §9.3.1 defines.
|
|
43
|
+
*/
|
|
44
|
+
export function readCheckedItems(schema) {
|
|
45
|
+
const items = schema.items;
|
|
46
|
+
if (items === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (isArray(items))
|
|
49
|
+
return checkTuplePositions(items);
|
|
50
|
+
if (!isDraft07Schema(items)) {
|
|
51
|
+
throw new MalformedSchemaError("items", ITEMS_REASON, items);
|
|
52
|
+
}
|
|
53
|
+
return items;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Every position of the tuple form is a schema in its own right, so a bad one
|
|
57
|
+
* names its index: with the whole array rendered instead, a caller holding a
|
|
58
|
+
* long tuple is told only that something in it is wrong.
|
|
59
|
+
*/
|
|
60
|
+
function checkTuplePositions(positions) {
|
|
61
|
+
for (let index = 0; index < positions.length; index += 1) {
|
|
62
|
+
const position = positions[index];
|
|
63
|
+
if (isDraft07Schema(position))
|
|
64
|
+
continue;
|
|
65
|
+
throw new MalformedSchemaError("items", `position ${String(index)} of the array form ${SCHEMA_FORMS}`, position);
|
|
66
|
+
}
|
|
67
|
+
return positions;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Reads `additionalItems` after checking it is a single schema: the keyword's
|
|
71
|
+
* own value, or undefined when the keyword is absent, and a refusal for
|
|
72
|
+
* anything §4.4 does not call a schema.
|
|
73
|
+
*
|
|
74
|
+
* It is read only where §6.4.2 gives the keyword effect, which is beside the
|
|
75
|
+
* tuple form of `items`; beside the single form the draft ignores it, no rest
|
|
76
|
+
* branch is built and this reader is never reached.
|
|
77
|
+
*/
|
|
78
|
+
export function readCheckedAdditionalItems(schema) {
|
|
79
|
+
const additionalItems = schema.additionalItems;
|
|
80
|
+
if (additionalItems === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (!isDraft07Schema(additionalItems)) {
|
|
83
|
+
throw new MalformedSchemaError("additionalItems", `the value ${SCHEMA_FORMS}`, additionalItems);
|
|
84
|
+
}
|
|
85
|
+
return additionalItems;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Reads `contains` after checking it is a single schema: the keyword's own
|
|
89
|
+
* value, or undefined when the keyword is absent, and a refusal for anything
|
|
90
|
+
* §4.4 does not call a schema. Unlike `items`, `contains` has no array form —
|
|
91
|
+
* a tuple there is a malformed value, not a second reading.
|
|
92
|
+
*/
|
|
93
|
+
export function readCheckedContains(schema) {
|
|
94
|
+
const contains = schema.contains;
|
|
95
|
+
if (contains === undefined)
|
|
96
|
+
return undefined;
|
|
97
|
+
if (!isDraft07Schema(contains)) {
|
|
98
|
+
throw new MalformedSchemaError("contains", `the value ${SCHEMA_FORMS}`, contains);
|
|
99
|
+
}
|
|
100
|
+
return contains;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Reads `uniqueItems` after checking it is a boolean: the keyword's own value,
|
|
104
|
+
* or undefined when the keyword is absent, and a refusal for anything else.
|
|
105
|
+
*/
|
|
106
|
+
export function readCheckedUniqueItems(schema) {
|
|
107
|
+
const uniqueItems = schema.uniqueItems;
|
|
108
|
+
if (uniqueItems === undefined)
|
|
109
|
+
return undefined;
|
|
110
|
+
if (typeof uniqueItems !== "boolean") {
|
|
111
|
+
throw new MalformedSchemaError("uniqueItems", UNIQUE_ITEMS_REASON, uniqueItems);
|
|
112
|
+
}
|
|
113
|
+
return uniqueItems;
|
|
114
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `required` is an array of property names (§6.5.3), and whatever the document
|
|
3
|
+
* put there is refused when it is anything else.
|
|
4
|
+
*
|
|
5
|
+
* UNIQUENESS IS NOT ENFORCED, although the meta-schema's `stringArray` asks
|
|
6
|
+
* for it. A repeated name asks for nothing the single name does not — the
|
|
7
|
+
* property is required either way, so the verdict is identical — and this
|
|
8
|
+
* check exists to stop a validator enforcing LESS than its document, not to
|
|
9
|
+
* lint a document that is enforced exactly as written. Refusing a duplicate
|
|
10
|
+
* would reject working schemas for no gain in correctness.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertRequiredIsNameList(value: unknown): void;
|
|
13
|
+
/**
|
|
14
|
+
* `properties` maps property names to schemas (§6.5.4), and a schema is an
|
|
15
|
+
* object or a boolean; the map itself and every member of it are refused when
|
|
16
|
+
* they are not. The key is named in the reason so the message points at the
|
|
17
|
+
* offending node rather than at the whole document.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertPropertiesIsSchemaMap(value: unknown): void;
|
|
20
|
+
/**
|
|
21
|
+
* `additionalProperties` is a schema, and §4.4 makes a boolean one (§6.5.6).
|
|
22
|
+
* Both of those forms pass through unchanged; only a value the meta-schema
|
|
23
|
+
* already forbids — a string, a number, an array, null — is refused.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertAdditionalPropertiesIsSchema(value: unknown): void;
|
|
26
|
+
/**
|
|
27
|
+
* `patternProperties` maps regular expressions to schemas (§6.5.5). The map
|
|
28
|
+
* itself and every member of it are refused when they are not schemas, and the
|
|
29
|
+
* pattern is named in the reason so the message points at the one offending
|
|
30
|
+
* entry rather than at a map that may hold a dozen.
|
|
31
|
+
*
|
|
32
|
+
* THE KEYS ARE DELIBERATELY NOT COMPILED HERE, although Draft-07 says each
|
|
33
|
+
* SHOULD be a valid ECMA-262 pattern. A key that will not compile is not the
|
|
34
|
+
* failure this module exists to stop: the plugin compiles every key while
|
|
35
|
+
* building its rule, so such a key already kills the build, loudly, before any
|
|
36
|
+
* validator exists — it is a SyntaxError rather than a typed refusal, but
|
|
37
|
+
* nothing is silently under-enforced. Compiling one here as well would put a
|
|
38
|
+
* SECOND `new RegExp` in the converter, which holds exactly one — the `pattern`
|
|
39
|
+
* keyword's, over the document's own source string — so that regex
|
|
40
|
+
* construction and its flag handling stay in a single place.
|
|
41
|
+
*/
|
|
42
|
+
export declare function assertPatternPropertiesIsSchemaMap(value: unknown): void;
|
|
43
|
+
/**
|
|
44
|
+
* `propertyNames` is ONE schema, applied to every property name (§6.5.8), and
|
|
45
|
+
* §4.4 makes a boolean one. An ARRAY is the shape of a schema list and is not
|
|
46
|
+
* itself a schema, so it is refused along with every primitive. An object
|
|
47
|
+
* stands whatever keywords it carries, because §4.3 has a schema ignore the
|
|
48
|
+
* keywords it does not recognise rather than be invalid for holding them.
|
|
49
|
+
*/
|
|
50
|
+
export declare function assertPropertyNamesIsSchema(value: unknown): void;
|
|
51
|
+
/**
|
|
52
|
+
* `dependencies` maps a property name to what its presence demands (§6.5.7),
|
|
53
|
+
* and the draft gives that two equally lawful forms under the same key: an
|
|
54
|
+
* ARRAY of property names, which makes those names required alongside it, or a
|
|
55
|
+
* SCHEMA the whole instance must then satisfy. Both are kept; only a value
|
|
56
|
+
* that is neither is refused, and the key is named so the message points at
|
|
57
|
+
* the one offending entry rather than at the whole map.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertDependenciesIsSchemaOrNameListMap(value: unknown): void;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertRequiredIsNameList = assertRequiredIsNameList;
|
|
4
|
+
exports.assertPropertiesIsSchemaMap = assertPropertiesIsSchemaMap;
|
|
5
|
+
exports.assertAdditionalPropertiesIsSchema = assertAdditionalPropertiesIsSchema;
|
|
6
|
+
exports.assertPatternPropertiesIsSchemaMap = assertPatternPropertiesIsSchemaMap;
|
|
7
|
+
exports.assertPropertyNamesIsSchema = assertPropertyNamesIsSchema;
|
|
8
|
+
exports.assertDependenciesIsSchemaOrNameListMap = assertDependenciesIsSchemaOrNameListMap;
|
|
9
|
+
// ===========================================================================
|
|
10
|
+
// L8 src/json-schema/assert-object-keyword-values.ts — the meta-schema check
|
|
11
|
+
// on the VALUES of the object-family keywords: `required`, `properties`,
|
|
12
|
+
// `additionalProperties`, `patternProperties`, `propertyNames` and
|
|
13
|
+
// `dependencies`.
|
|
14
|
+
//
|
|
15
|
+
// They live together because they fail together: every one is read in a place
|
|
16
|
+
// that trusts the declared type, and untyped JSON reaches all six. `required:
|
|
17
|
+
// "name"` is iterated as a string and declares one phantom child per
|
|
18
|
+
// CHARACTER; `properties: {a: null}` is read for a pointer; the remaining four
|
|
19
|
+
// take the sub-schema route with a value that is not a schema at all, and the
|
|
20
|
+
// walker finds no keys on a primitive, so it yields a branch holding no rules.
|
|
21
|
+
//
|
|
22
|
+
// That last outcome is the SILENT one, and it is the one that matters most: a
|
|
23
|
+
// document saying "no extra properties", or "every key beginning with a_ holds
|
|
24
|
+
// a string", compiles to a validator that accepts everything. It is what a
|
|
25
|
+
// config service serialising booleans as strings produces, and what a
|
|
26
|
+
// hand-edited document with a quoted sub-schema produces. The loud failures
|
|
27
|
+
// are loud in the wrong place — a raw TypeError read off null somewhere deep
|
|
28
|
+
// in the walker, naming neither the keyword nor the document.
|
|
29
|
+
//
|
|
30
|
+
// Refusal is therefore always at BUILD time, and every well-formed shape is
|
|
31
|
+
// untouched: a name list, a map of schemas (including the §4.4 boolean form),
|
|
32
|
+
// a single schema, and — under `dependencies` — either a schema or an array of
|
|
33
|
+
// property names, which §6.5.7 makes equally lawful under the same key.
|
|
34
|
+
// ===========================================================================
|
|
35
|
+
const types_1 = require("../types");
|
|
36
|
+
const draft07_types_1 = require("./draft07.types");
|
|
37
|
+
const malformed_schema_error_1 = require("./malformed-schema-error");
|
|
38
|
+
/**
|
|
39
|
+
* `required` is an array of property names (§6.5.3), and whatever the document
|
|
40
|
+
* put there is refused when it is anything else.
|
|
41
|
+
*
|
|
42
|
+
* UNIQUENESS IS NOT ENFORCED, although the meta-schema's `stringArray` asks
|
|
43
|
+
* for it. A repeated name asks for nothing the single name does not — the
|
|
44
|
+
* property is required either way, so the verdict is identical — and this
|
|
45
|
+
* check exists to stop a validator enforcing LESS than its document, not to
|
|
46
|
+
* lint a document that is enforced exactly as written. Refusing a duplicate
|
|
47
|
+
* would reject working schemas for no gain in correctness.
|
|
48
|
+
*/
|
|
49
|
+
function assertRequiredIsNameList(value) {
|
|
50
|
+
if (value === undefined || (0, types_1.isStringArray)(value))
|
|
51
|
+
return;
|
|
52
|
+
throw new malformed_schema_error_1.MalformedSchemaError("required", "the value must be an array of property names", value);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `properties` and `patternProperties` differ only in what their KEYS mean, so
|
|
56
|
+
* they are checked the same way: the map itself, then every member of it, and
|
|
57
|
+
* the offending key named in the reason so the message points at one entry
|
|
58
|
+
* rather than at a map that may hold a dozen. `dependencies` is not routed
|
|
59
|
+
* through here — §6.5.7 gives its members a second lawful form, which is a
|
|
60
|
+
* different member check and a different sentence.
|
|
61
|
+
*
|
|
62
|
+
* `mapReason` is the keyword's own, because "an object mapping property names
|
|
63
|
+
* to schemas" and "an object mapping regular expressions to schemas" are the
|
|
64
|
+
* only part of the requirement the two keywords do not share.
|
|
65
|
+
*/
|
|
66
|
+
function assertSchemaMap(keyword, mapReason, value) {
|
|
67
|
+
if (value === undefined)
|
|
68
|
+
return;
|
|
69
|
+
if (!(0, types_1.isPlainObject)(value)) {
|
|
70
|
+
throw new malformed_schema_error_1.MalformedSchemaError(keyword, mapReason, value);
|
|
71
|
+
}
|
|
72
|
+
for (const [key, member] of Object.entries(value)) {
|
|
73
|
+
if ((0, draft07_types_1.isDraft07Schema)(member))
|
|
74
|
+
continue;
|
|
75
|
+
throw new malformed_schema_error_1.MalformedSchemaError(keyword, `the schema under "${key}" ${malformed_schema_error_1.SCHEMA_FORMS}`, member);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* `properties` maps property names to schemas (§6.5.4), and a schema is an
|
|
80
|
+
* object or a boolean; the map itself and every member of it are refused when
|
|
81
|
+
* they are not. The key is named in the reason so the message points at the
|
|
82
|
+
* offending node rather than at the whole document.
|
|
83
|
+
*/
|
|
84
|
+
function assertPropertiesIsSchemaMap(value) {
|
|
85
|
+
assertSchemaMap("properties", "the value must be an object mapping property names to schemas", value);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* `additionalProperties` is a schema, and §4.4 makes a boolean one (§6.5.6).
|
|
89
|
+
* Both of those forms pass through unchanged; only a value the meta-schema
|
|
90
|
+
* already forbids — a string, a number, an array, null — is refused.
|
|
91
|
+
*/
|
|
92
|
+
function assertAdditionalPropertiesIsSchema(value) {
|
|
93
|
+
if (value === undefined || (0, draft07_types_1.isDraft07Schema)(value))
|
|
94
|
+
return;
|
|
95
|
+
throw new malformed_schema_error_1.MalformedSchemaError("additionalProperties", `the value ${malformed_schema_error_1.SCHEMA_FORMS}`, value);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* `patternProperties` maps regular expressions to schemas (§6.5.5). The map
|
|
99
|
+
* itself and every member of it are refused when they are not schemas, and the
|
|
100
|
+
* pattern is named in the reason so the message points at the one offending
|
|
101
|
+
* entry rather than at a map that may hold a dozen.
|
|
102
|
+
*
|
|
103
|
+
* THE KEYS ARE DELIBERATELY NOT COMPILED HERE, although Draft-07 says each
|
|
104
|
+
* SHOULD be a valid ECMA-262 pattern. A key that will not compile is not the
|
|
105
|
+
* failure this module exists to stop: the plugin compiles every key while
|
|
106
|
+
* building its rule, so such a key already kills the build, loudly, before any
|
|
107
|
+
* validator exists — it is a SyntaxError rather than a typed refusal, but
|
|
108
|
+
* nothing is silently under-enforced. Compiling one here as well would put a
|
|
109
|
+
* SECOND `new RegExp` in the converter, which holds exactly one — the `pattern`
|
|
110
|
+
* keyword's, over the document's own source string — so that regex
|
|
111
|
+
* construction and its flag handling stay in a single place.
|
|
112
|
+
*/
|
|
113
|
+
function assertPatternPropertiesIsSchemaMap(value) {
|
|
114
|
+
assertSchemaMap("patternProperties", "the value must be an object mapping regular expressions to schemas", value);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* `propertyNames` is ONE schema, applied to every property name (§6.5.8), and
|
|
118
|
+
* §4.4 makes a boolean one. An ARRAY is the shape of a schema list and is not
|
|
119
|
+
* itself a schema, so it is refused along with every primitive. An object
|
|
120
|
+
* stands whatever keywords it carries, because §4.3 has a schema ignore the
|
|
121
|
+
* keywords it does not recognise rather than be invalid for holding them.
|
|
122
|
+
*/
|
|
123
|
+
function assertPropertyNamesIsSchema(value) {
|
|
124
|
+
if (value === undefined || (0, draft07_types_1.isDraft07Schema)(value))
|
|
125
|
+
return;
|
|
126
|
+
throw new malformed_schema_error_1.MalformedSchemaError("propertyNames", `the value ${malformed_schema_error_1.SCHEMA_FORMS}`, value);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* `dependencies` maps a property name to what its presence demands (§6.5.7),
|
|
130
|
+
* and the draft gives that two equally lawful forms under the same key: an
|
|
131
|
+
* ARRAY of property names, which makes those names required alongside it, or a
|
|
132
|
+
* SCHEMA the whole instance must then satisfy. Both are kept; only a value
|
|
133
|
+
* that is neither is refused, and the key is named so the message points at
|
|
134
|
+
* the one offending entry rather than at the whole map.
|
|
135
|
+
*/
|
|
136
|
+
function assertDependenciesIsSchemaOrNameListMap(value) {
|
|
137
|
+
if (value === undefined)
|
|
138
|
+
return;
|
|
139
|
+
if (!(0, types_1.isPlainObject)(value)) {
|
|
140
|
+
throw new malformed_schema_error_1.MalformedSchemaError("dependencies", "the value must be an object mapping property names to schemas or to " +
|
|
141
|
+
"arrays of property names", value);
|
|
142
|
+
}
|
|
143
|
+
for (const [key, dependency] of Object.entries(value)) {
|
|
144
|
+
if ((0, types_1.isStringArray)(dependency) || (0, draft07_types_1.isDraft07Schema)(dependency))
|
|
145
|
+
continue;
|
|
146
|
+
throw new malformed_schema_error_1.MalformedSchemaError("dependencies", `the dependency under "${key}" ${malformed_schema_error_1.SCHEMA_FORMS}, or an array of ` +
|
|
147
|
+
"property names", dependency);
|
|
148
|
+
}
|
|
149
|
+
}
|