@maroonedog/luq 2.4.4 → 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.
Files changed (40) hide show
  1. package/README.md +41 -3
  2. package/dist/json-schema/array-keyword-guards.d.ts +31 -0
  3. package/dist/json-schema/array-keyword-guards.js +120 -0
  4. package/dist/json-schema/array-keyword-guards.mjs +114 -0
  5. package/dist/json-schema/assert-object-keyword-values.d.ts +59 -0
  6. package/dist/json-schema/assert-object-keyword-values.js +149 -0
  7. package/dist/json-schema/assert-object-keyword-values.mjs +141 -0
  8. package/dist/json-schema/collect-definitions.d.ts +7 -0
  9. package/dist/json-schema/collect-definitions.js +9 -0
  10. package/dist/json-schema/collect-definitions.mjs +10 -1
  11. package/dist/json-schema/declare-additional-properties.d.ts +11 -1
  12. package/dist/json-schema/declare-additional-properties.js +13 -1
  13. package/dist/json-schema/declare-additional-properties.mjs +13 -1
  14. package/dist/json-schema/declare-object-keywords.d.ts +4 -0
  15. package/dist/json-schema/declare-object-keywords.js +11 -4
  16. package/dist/json-schema/declare-object-keywords.mjs +11 -4
  17. package/dist/json-schema/declare-required-properties.js +6 -0
  18. package/dist/json-schema/declare-required-properties.mjs +6 -0
  19. package/dist/json-schema/declare-value-keywords.d.ts +10 -1
  20. package/dist/json-schema/declare-value-keywords.js +43 -4
  21. package/dist/json-schema/declare-value-keywords.mjs +43 -4
  22. package/dist/json-schema/extensions/json-schema/index.d.ts +1 -1
  23. package/dist/json-schema/extensions/json-schema/index.js +2 -1
  24. package/dist/json-schema/extensions/json-schema/index.mjs +1 -1
  25. package/dist/json-schema/flatten-array-schema.d.ts +11 -1
  26. package/dist/json-schema/flatten-array-schema.js +20 -7
  27. package/dist/json-schema/flatten-array-schema.mjs +20 -7
  28. package/dist/json-schema/index.d.ts +1 -0
  29. package/dist/json-schema/index.js +3 -1
  30. package/dist/json-schema/index.mjs +1 -0
  31. package/dist/json-schema/keyword-map-string.js +30 -1
  32. package/dist/json-schema/keyword-map-string.mjs +30 -1
  33. package/dist/json-schema/keyword-map.js +2 -0
  34. package/dist/json-schema/keyword-map.mjs +2 -0
  35. package/dist/json-schema/malformed-schema-error.d.ts +38 -0
  36. package/dist/json-schema/malformed-schema-error.js +103 -0
  37. package/dist/json-schema/malformed-schema-error.mjs +98 -0
  38. package/dist/plugins/one-of/one-of.js +18 -4
  39. package/dist/plugins/one-of/one-of.mjs +18 -4
  40. package/package.json +3 -3
package/README.md CHANGED
@@ -50,10 +50,16 @@ an array wildcard gets a red squiggle, not a validator that passes everything.
50
50
 
51
51
  Two consequences worth knowing before you read further:
52
52
 
53
- - **Adoption is a patch, not a migration.** A path you did not declare is not
53
+ - **Adoption is a patch, and so is removal.** A path you did not declare is not
54
54
  validated, not required, and not read, so a partly-covered type is a normal
55
55
  state rather than a half-finished one. There is nothing global to migrate: no
56
- registry, no plugin installation, no shared configuration object.
56
+ registry, no plugin installation, no shared configuration object. Leaving is
57
+ the same size of change in the other direction — your types were never
58
+ authored here, so there is no generated file to delete and no inferred type to
59
+ replace by hand, and a consumer that takes a [Standard
60
+ Schema](https://luq.dev/standard-schema) does not change when you hand it a
61
+ different value. The rules themselves you would rewrite; that part is real,
62
+ and it is the same work whichever way you go.
57
63
  - **Every rule you can call is a plugin you imported by name**, so the bundle
58
64
  contains what you used and nothing else — an unimported plugin's method does
59
65
  not even typecheck. What each configuration costs is measured on every build:
@@ -95,7 +101,9 @@ somewhere.
95
101
  npm install @maroonedog/luq
96
102
  ```
97
103
 
98
- Zero runtime dependencies. TypeScript 5.0 or later.
104
+ Zero runtime dependencies. TypeScript 5.0 or later. Your `tsconfig.json` does
105
+ not need `strict` for any of the compile errors above, and they are checked with
106
+ it on as well.
99
107
 
100
108
  ## The whole API
101
109
 
@@ -139,6 +147,36 @@ if (!result.valid) {
139
147
  not validated, not required and not read, so covering a type partly is a normal
140
148
  state rather than a half-finished one.
141
149
 
150
+ ### `.strict()` — when you want the whole type covered
151
+
152
+ Partial cover is the default, so nothing tells you a field was forgotten. Add
153
+ `.strict()` before `.build()` and the compiler does: it asserts every leaf path
154
+ of your type has been declared, and if one is missing the returned object has
155
+ no `build()` and names what is missing.
156
+
157
+ ```ts
158
+ import { Builder } from "@maroonedog/luq";
159
+ import { requiredPlugin } from "@maroonedog/luq/plugins/required";
160
+
161
+ interface Pair {
162
+ readonly left: string;
163
+ readonly right: string;
164
+ }
165
+
166
+ export const pairs = Builder()
167
+ .use(requiredPlugin)
168
+ .for<Pair>()
169
+ .v("left", (b) => b.string.required())
170
+ .v("right", (b) => b.string.required())
171
+ .strict() // drop either .v() above and this line stops compiling
172
+ .build();
173
+ ```
174
+
175
+ It has no runtime effect and rejects no extra property at run time — it is a
176
+ statement about your declarations, not about the data. For refusing unknown
177
+ keys in the value, see
178
+ [objectAdditionalProperties](https://luq.dev/plugins).
179
+
142
180
  ### What a built validator gives you
143
181
 
144
182
  ```ts
@@ -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
+ }