@maroonedog/luq 2.4.4 → 2.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 +64 -10
- package/dist/chain/slot-type-guard.js +20 -6
- package/dist/chain/slot-type-guard.mjs +20 -6
- package/dist/compile/resolve-presence.js +36 -5
- package/dist/compile/resolve-presence.mjs +36 -5
- 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/assert-supported-dialect.d.ts +37 -0
- package/dist/json-schema/assert-supported-dialect.js +113 -0
- package/dist/json-schema/assert-supported-dialect.mjs +109 -0
- package/dist/json-schema/build-from-schema.d.ts +22 -3
- package/dist/json-schema/build-from-schema.js +27 -5
- package/dist/json-schema/build-from-schema.mjs +27 -5
- 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 +2 -2
- package/dist/json-schema/extensions/json-schema/index.js +4 -1
- package/dist/json-schema/extensions/json-schema/index.mjs +1 -1
- package/dist/json-schema/extensions/json-schema/json-schema.d.ts +2 -2
- package/dist/json-schema/extensions/json-schema/json-schema.js +3 -0
- package/dist/json-schema/extensions/json-schema/json-schema.mjs +4 -1
- package/dist/json-schema/extensions/json-schema-full-feature/index.d.ts +3 -1
- package/dist/json-schema/extensions/json-schema-full-feature/index.js +18 -1
- package/dist/json-schema/extensions/json-schema-full-feature/index.mjs +11 -0
- package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.d.ts +9 -1
- package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.js +9 -2
- package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.mjs +9 -2
- 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 +4 -0
- package/dist/json-schema/index.js +9 -1
- package/dist/json-schema/index.mjs +3 -0
- package/dist/json-schema/keyword-map-core.js +3 -1
- package/dist/json-schema/keyword-map-core.mjs +3 -1
- 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 +5 -0
- package/dist/json-schema/keyword-map.mjs +5 -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/json-schema/unsupported-dialect-error.d.ts +21 -0
- package/dist/json-schema/unsupported-dialect-error.js +58 -0
- package/dist/json-schema/unsupported-dialect-error.mjs +54 -0
- package/dist/plugins/one-of/one-of.js +18 -4
- package/dist/plugins/one-of/one-of.mjs +18 -4
- package/package.json +8 -5
|
@@ -2,7 +2,7 @@ import type { Rule } from "../../../plugin-kit/compiled-rule";
|
|
|
2
2
|
import { type MessageContextExtra, type TypeName } from "../../../types";
|
|
3
3
|
import type { RuleBuildContext } from "../../../plugin-kit/rule-build-context";
|
|
4
4
|
import type { Unchanged } from "../../../plugin-kit/marker.types";
|
|
5
|
-
import { type JsonSchemaBag } from "../../index";
|
|
5
|
+
import { type DialectOptions, type JsonSchemaBag } from "../../index";
|
|
6
6
|
/** The label the whole document carries inside the composite it becomes. */
|
|
7
7
|
export declare const SCHEMA_BRANCH_LABEL = "schema";
|
|
8
8
|
/**
|
|
@@ -17,7 +17,7 @@ export declare const SCHEMA_BRANCH_LABEL = "schema";
|
|
|
17
17
|
* The caller fetches, reads from disk, or bundles — whichever is right for
|
|
18
18
|
* their deployment — and hands over what they already have.
|
|
19
19
|
*/
|
|
20
|
-
export interface JsonSchemaOptions {
|
|
20
|
+
export interface JsonSchemaOptions extends DialectOptions {
|
|
21
21
|
readonly externalDocuments?: Readonly<Record<string, unknown>> | undefined;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
@@ -66,6 +66,9 @@ const NO_EXTERNAL_DOCUMENTS = Object.freeze({});
|
|
|
66
66
|
function collectDocumentRules(ctx, document, bag, options = {}) {
|
|
67
67
|
if (!(0, index_1.isDraft07Schema)(document))
|
|
68
68
|
throw new index_1.NotASchemaError(document);
|
|
69
|
+
// Before any keyword is read, and in the same place the function front door
|
|
70
|
+
// reads it: the dialect decides what the keywords below MEAN.
|
|
71
|
+
(0, index_1.assertSupportedDialect)(document, options);
|
|
69
72
|
const root = document;
|
|
70
73
|
const scope = (0, index_1.createDocumentScope)(root, options.externalDocuments ?? NO_EXTERNAL_DOCUMENTS);
|
|
71
74
|
// The seed's `chain` is a ChainBuildContext, whose three members a
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
import { branch, composite } from "../../../plugin-kit/create-rule.mjs";
|
|
39
39
|
import { definePlugin } from "../../../plugin-kit/plugin-definition.mjs";
|
|
40
40
|
import { PASS } from "../../../types/index.mjs";
|
|
41
|
-
import { NotASchemaError, collectSubSchemaRules, createStructuralContext, createDocumentScope, isDraft07Schema, resolveSchemaNodeInScope, } from "../../index.mjs";
|
|
41
|
+
import { NotASchemaError, assertSupportedDialect, collectSubSchemaRules, createStructuralContext, createDocumentScope, isDraft07Schema, resolveSchemaNodeInScope, } from "../../index.mjs";
|
|
42
42
|
/** Every slot: a JSON Schema document constrains a value of any shape. */
|
|
43
43
|
const SCHEMA_SLOTS = [
|
|
44
44
|
"string",
|
|
@@ -62,6 +62,9 @@ const NO_EXTERNAL_DOCUMENTS = Object.freeze({});
|
|
|
62
62
|
export function collectDocumentRules(ctx, document, bag, options = {}) {
|
|
63
63
|
if (!isDraft07Schema(document))
|
|
64
64
|
throw new NotASchemaError(document);
|
|
65
|
+
// Before any keyword is read, and in the same place the function front door
|
|
66
|
+
// reads it: the dialect decides what the keywords below MEAN.
|
|
67
|
+
assertSupportedDialect(document, options);
|
|
65
68
|
const root = document;
|
|
66
69
|
const scope = createDocumentScope(root, options.externalDocuments ?? NO_EXTERNAL_DOCUMENTS);
|
|
67
70
|
// The seed's `chain` is a ChainBuildContext, whose three members a
|
|
@@ -2,4 +2,6 @@ export { fromJsonSchema, jsonSchemaFullFeaturePlugin, } from "./json-schema-full
|
|
|
2
2
|
export { jsonSchemaBag } from "./bundled-plugins";
|
|
3
3
|
export { UNBOUND_BUNDLED_PLUGIN_NAMES, findStaleUnboundDeclarations, findUnbundledBoundPlugins, findUnexplainedBundledPlugins, listBundledPluginNames, } from "./bundle-coverage";
|
|
4
4
|
export type { JsonSchemaOptions } from "../json-schema";
|
|
5
|
-
export
|
|
5
|
+
export { DRAFT07_DIALECT_URI, UnsupportedDialectError, } from "../../unsupported-dialect-error";
|
|
6
|
+
export { MalformedSchemaError, NotASchemaError, RefResolutionError, UnsupportedKeywordError, } from "../../index";
|
|
7
|
+
export type { DialectOptions, JsonSchemaBag } from "../../index";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listBundledPluginNames = exports.findUnexplainedBundledPlugins = exports.findUnbundledBoundPlugins = exports.findStaleUnboundDeclarations = exports.UNBOUND_BUNDLED_PLUGIN_NAMES = exports.jsonSchemaBag = exports.jsonSchemaFullFeaturePlugin = exports.fromJsonSchema = void 0;
|
|
3
|
+
exports.UnsupportedKeywordError = exports.RefResolutionError = exports.NotASchemaError = exports.MalformedSchemaError = exports.UnsupportedDialectError = exports.DRAFT07_DIALECT_URI = exports.listBundledPluginNames = exports.findUnexplainedBundledPlugins = exports.findUnbundledBoundPlugins = exports.findStaleUnboundDeclarations = exports.UNBOUND_BUNDLED_PLUGIN_NAMES = exports.jsonSchemaBag = exports.jsonSchemaFullFeaturePlugin = exports.fromJsonSchema = void 0;
|
|
4
4
|
// ===========================================================================
|
|
5
5
|
// The public subpath `@maroonedog/luq/plugins/jsonSchemaFullFeature`.
|
|
6
6
|
// Re-exports only.
|
|
@@ -23,3 +23,20 @@ Object.defineProperty(exports, "findStaleUnboundDeclarations", { enumerable: tru
|
|
|
23
23
|
Object.defineProperty(exports, "findUnbundledBoundPlugins", { enumerable: true, get: function () { return bundle_coverage_1.findUnbundledBoundPlugins; } });
|
|
24
24
|
Object.defineProperty(exports, "findUnexplainedBundledPlugins", { enumerable: true, get: function () { return bundle_coverage_1.findUnexplainedBundledPlugins; } });
|
|
25
25
|
Object.defineProperty(exports, "listBundledPluginNames", { enumerable: true, get: function () { return bundle_coverage_1.listBundledPluginNames; } });
|
|
26
|
+
// Every error `fromJsonSchema` can throw, so a caller can tell them apart by
|
|
27
|
+
// class rather than by reading the message.
|
|
28
|
+
//
|
|
29
|
+
// This subpath is the one-import route and the one the documentation points at,
|
|
30
|
+
// which made it the worst place to be missing them: a document that is not
|
|
31
|
+
// Draft-07 at all, one the meta-schema forbids, one carrying a keyword Luq
|
|
32
|
+
// cannot honour and one that is not a schema are four different things to do
|
|
33
|
+
// about it, and a caller who can only match on message text has to re-derive
|
|
34
|
+
// that distinction from prose that is free to change.
|
|
35
|
+
var unsupported_dialect_error_1 = require("../../unsupported-dialect-error");
|
|
36
|
+
Object.defineProperty(exports, "DRAFT07_DIALECT_URI", { enumerable: true, get: function () { return unsupported_dialect_error_1.DRAFT07_DIALECT_URI; } });
|
|
37
|
+
Object.defineProperty(exports, "UnsupportedDialectError", { enumerable: true, get: function () { return unsupported_dialect_error_1.UnsupportedDialectError; } });
|
|
38
|
+
var index_1 = require("../../index");
|
|
39
|
+
Object.defineProperty(exports, "MalformedSchemaError", { enumerable: true, get: function () { return index_1.MalformedSchemaError; } });
|
|
40
|
+
Object.defineProperty(exports, "NotASchemaError", { enumerable: true, get: function () { return index_1.NotASchemaError; } });
|
|
41
|
+
Object.defineProperty(exports, "RefResolutionError", { enumerable: true, get: function () { return index_1.RefResolutionError; } });
|
|
42
|
+
Object.defineProperty(exports, "UnsupportedKeywordError", { enumerable: true, get: function () { return index_1.UnsupportedKeywordError; } });
|
|
@@ -12,3 +12,14 @@
|
|
|
12
12
|
export { fromJsonSchema, jsonSchemaFullFeaturePlugin, } from "./json-schema-full-feature.mjs";
|
|
13
13
|
export { jsonSchemaBag } from "./bundled-plugins.mjs";
|
|
14
14
|
export { UNBOUND_BUNDLED_PLUGIN_NAMES, findStaleUnboundDeclarations, findUnbundledBoundPlugins, findUnexplainedBundledPlugins, listBundledPluginNames, } from "./bundle-coverage.mjs";
|
|
15
|
+
// Every error `fromJsonSchema` can throw, so a caller can tell them apart by
|
|
16
|
+
// class rather than by reading the message.
|
|
17
|
+
//
|
|
18
|
+
// This subpath is the one-import route and the one the documentation points at,
|
|
19
|
+
// which made it the worst place to be missing them: a document that is not
|
|
20
|
+
// Draft-07 at all, one the meta-schema forbids, one carrying a keyword Luq
|
|
21
|
+
// cannot honour and one that is not a schema are four different things to do
|
|
22
|
+
// about it, and a caller who can only match on message text has to re-derive
|
|
23
|
+
// that distinction from prose that is free to change.
|
|
24
|
+
export { DRAFT07_DIALECT_URI, UnsupportedDialectError, } from "../../unsupported-dialect-error.mjs";
|
|
25
|
+
export { MalformedSchemaError, NotASchemaError, RefResolutionError, UnsupportedKeywordError, } from "../../index.mjs";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MessageContextExtra, TypeName } from "../../../types";
|
|
2
2
|
import type { Unchanged } from "../../../plugin-kit/marker.types";
|
|
3
3
|
import type { JsonSchemaOptions } from "../json-schema";
|
|
4
|
+
import type { DialectOptions } from "../../index";
|
|
4
5
|
import type { GlobalConfig } from "../../../types/global-config";
|
|
5
6
|
export declare const jsonSchemaFullFeaturePlugin: import("../../../plugin-kit/plugin-definition").PluginDefinition<"jsonSchemaFullFeature", "jsonSchemaFullFeature", readonly TypeName[], {
|
|
6
7
|
args: readonly [document: unknown, options?: JsonSchemaOptions];
|
|
@@ -17,8 +18,15 @@ export declare const jsonSchemaFullFeaturePlugin: import("../../../plugin-kit/pl
|
|
|
17
18
|
* NO declared path is checked — see build-from-schema.ts, which owns that
|
|
18
19
|
* escape hatch and its single overload.
|
|
19
20
|
*
|
|
21
|
+
* THE INPUT LIMIT IS DRAFT-07. A document whose root `$schema` names 2019-09 or
|
|
22
|
+
* 2020-12 is refused with an `UnsupportedDialectError` rather than read as
|
|
23
|
+
* Draft-07, because the dialects disagree about what unchanged keywords mean;
|
|
24
|
+
* a document with no `$schema` is read as Draft-07 as before. Pass
|
|
25
|
+
* `{ assumeDraft07: true }` as the third argument to take the Draft-07 reading
|
|
26
|
+
* deliberately. build-from-schema.ts states the divergence in full.
|
|
27
|
+
*
|
|
20
28
|
* The return type is INFERRED rather than written: naming `Validator<T>` would
|
|
21
29
|
* mean importing src/builder, which tier `extension` forbids. The inferred type
|
|
22
30
|
* is that same `Validator<T>` and the type test asserts it.
|
|
23
31
|
*/
|
|
24
|
-
export declare function fromJsonSchema<T extends object = Record<string, unknown>>(schema: unknown, config?: GlobalConfig): import("../../..").Validator<T, T>;
|
|
32
|
+
export declare function fromJsonSchema<T extends object = Record<string, unknown>>(schema: unknown, config?: GlobalConfig, options?: DialectOptions): import("../../..").Validator<T, T>;
|
|
@@ -57,10 +57,17 @@ exports.jsonSchemaFullFeaturePlugin = (0, plugin_definition_1.definePlugin)()({
|
|
|
57
57
|
* NO declared path is checked — see build-from-schema.ts, which owns that
|
|
58
58
|
* escape hatch and its single overload.
|
|
59
59
|
*
|
|
60
|
+
* THE INPUT LIMIT IS DRAFT-07. A document whose root `$schema` names 2019-09 or
|
|
61
|
+
* 2020-12 is refused with an `UnsupportedDialectError` rather than read as
|
|
62
|
+
* Draft-07, because the dialects disagree about what unchanged keywords mean;
|
|
63
|
+
* a document with no `$schema` is read as Draft-07 as before. Pass
|
|
64
|
+
* `{ assumeDraft07: true }` as the third argument to take the Draft-07 reading
|
|
65
|
+
* deliberately. build-from-schema.ts states the divergence in full.
|
|
66
|
+
*
|
|
60
67
|
* The return type is INFERRED rather than written: naming `Validator<T>` would
|
|
61
68
|
* mean importing src/builder, which tier `extension` forbids. The inferred type
|
|
62
69
|
* is that same `Validator<T>` and the type test asserts it.
|
|
63
70
|
*/
|
|
64
|
-
function fromJsonSchema(schema, config) {
|
|
65
|
-
return (0, index_1.fromJsonSchema)(bundled_plugins_1.jsonSchemaBag, schema, config);
|
|
71
|
+
function fromJsonSchema(schema, config, options) {
|
|
72
|
+
return (0, index_1.fromJsonSchema)(bundled_plugins_1.jsonSchemaBag, schema, config, options);
|
|
66
73
|
}
|
|
@@ -53,10 +53,17 @@ export const jsonSchemaFullFeaturePlugin = /*#__PURE__*/ definePlugin()({
|
|
|
53
53
|
* NO declared path is checked — see build-from-schema.ts, which owns that
|
|
54
54
|
* escape hatch and its single overload.
|
|
55
55
|
*
|
|
56
|
+
* THE INPUT LIMIT IS DRAFT-07. A document whose root `$schema` names 2019-09 or
|
|
57
|
+
* 2020-12 is refused with an `UnsupportedDialectError` rather than read as
|
|
58
|
+
* Draft-07, because the dialects disagree about what unchanged keywords mean;
|
|
59
|
+
* a document with no `$schema` is read as Draft-07 as before. Pass
|
|
60
|
+
* `{ assumeDraft07: true }` as the third argument to take the Draft-07 reading
|
|
61
|
+
* deliberately. build-from-schema.ts states the divergence in full.
|
|
62
|
+
*
|
|
56
63
|
* The return type is INFERRED rather than written: naming `Validator<T>` would
|
|
57
64
|
* mean importing src/builder, which tier `extension` forbids. The inferred type
|
|
58
65
|
* is that same `Validator<T>` and the type test asserts it.
|
|
59
66
|
*/
|
|
60
|
-
export function fromJsonSchema(schema, config) {
|
|
61
|
-
return convertWithBag(jsonSchemaBag, schema, config);
|
|
67
|
+
export function fromJsonSchema(schema, config, options) {
|
|
68
|
+
return convertWithBag(jsonSchemaBag, schema, config, options);
|
|
62
69
|
}
|
|
@@ -6,6 +6,10 @@ export declare const EACH_STEP = "[*]";
|
|
|
6
6
|
/**
|
|
7
7
|
* The single form only. The tuple form gets no child path: element 0 and
|
|
8
8
|
* element 1 obey DIFFERENT schemas, and one declared `[*]` cannot say that.
|
|
9
|
+
*
|
|
10
|
+
* Reading through readCheckedItems is what stops a non-schema value here from
|
|
11
|
+
* becoming a child declaration whose sub-schema yields no rules at all; see
|
|
12
|
+
* array-keyword-guards.ts.
|
|
9
13
|
*/
|
|
10
14
|
export declare function readItemChildren(schema: Draft07SchemaObject): readonly ChildSchema[];
|
|
11
15
|
export declare function composeTupleItems(schema: Draft07SchemaObject, context: StructuralContext): readonly Rule[];
|
|
@@ -13,6 +17,10 @@ export declare function composeTupleItems(schema: Draft07SchemaObject, context:
|
|
|
13
17
|
* `contains` is EXISTENTIAL: at least one element matches. arrayContains owns
|
|
14
18
|
* that reduction, and its default bounds (min 1, no max) are exactly the
|
|
15
19
|
* draft's, so this is one call and no arithmetic.
|
|
20
|
+
*
|
|
21
|
+
* An element branch holding no rules would satisfy the existence check on the
|
|
22
|
+
* first element and turn the keyword into "the array is non-empty", so the
|
|
23
|
+
* value is read through array-keyword-guards.ts.
|
|
16
24
|
*/
|
|
17
25
|
export declare function composeContains(schema: Draft07SchemaObject, context: StructuralContext): readonly Rule[];
|
|
18
26
|
/**
|
|
@@ -20,6 +28,8 @@ export declare function composeContains(schema: Draft07SchemaObject, context: St
|
|
|
20
28
|
* and `maxItems` to `maxLength`; writing "minItems" as the method name does not
|
|
21
29
|
* compile (keyword-map-array.ts), which is the C2 regression made impossible.
|
|
22
30
|
* `uniqueItems: false` never reaches its binding: §6.4.3 gives it no effect and
|
|
23
|
-
* the binding's value type is `true`.
|
|
31
|
+
* the binding's value type is `true`. That is why the value is read through
|
|
32
|
+
* readCheckedUniqueItems: with a bare `=== true`, a non-boolean would take the
|
|
33
|
+
* same silent route as the lawful `false` and lose the constraint entirely.
|
|
24
34
|
*/
|
|
25
35
|
export declare function declareArrayRules(schema: Draft07SchemaObject, context: StructuralContext): readonly Rule[];
|
|
@@ -33,6 +33,7 @@ const create_chain_node_1 = require("../chain/create-chain-node");
|
|
|
33
33
|
const create_field_slots_1 = require("../chain/create-field-slots");
|
|
34
34
|
const create_rule_1 = require("../plugin-kit/create-rule");
|
|
35
35
|
const apply_keyword_binding_1 = require("./apply-keyword-binding");
|
|
36
|
+
const array_keyword_guards_1 = require("./array-keyword-guards");
|
|
36
37
|
const keyword_map_array_1 = require("./keyword-map-array");
|
|
37
38
|
const types_1 = require("../types");
|
|
38
39
|
const collect_definitions_1 = require("./collect-definitions");
|
|
@@ -47,9 +48,13 @@ function isTupleItems(items) {
|
|
|
47
48
|
/**
|
|
48
49
|
* The single form only. The tuple form gets no child path: element 0 and
|
|
49
50
|
* element 1 obey DIFFERENT schemas, and one declared `[*]` cannot say that.
|
|
51
|
+
*
|
|
52
|
+
* Reading through readCheckedItems is what stops a non-schema value here from
|
|
53
|
+
* becoming a child declaration whose sub-schema yields no rules at all; see
|
|
54
|
+
* array-keyword-guards.ts.
|
|
50
55
|
*/
|
|
51
56
|
function readItemChildren(schema) {
|
|
52
|
-
const items = schema
|
|
57
|
+
const items = (0, array_keyword_guards_1.readCheckedItems)(schema);
|
|
53
58
|
if (items === undefined || isTupleItems(items))
|
|
54
59
|
return NO_CHILDREN;
|
|
55
60
|
return Object.freeze([{ step: exports.EACH_STEP, schema: items, isRequired: false }]);
|
|
@@ -58,7 +63,9 @@ function readItemChildren(schema) {
|
|
|
58
63
|
* Element i against position i, extra elements against `additionalItems`.
|
|
59
64
|
* A non-array passes, a short array passes, and `additionalItems: false`
|
|
60
65
|
* becomes the schema that matches nothing (collect-definitions), so the
|
|
61
|
-
* "no extra elements" case needs no fourth branch kind.
|
|
66
|
+
* "no extra elements" case needs no fourth branch kind. A rest branch built
|
|
67
|
+
* from a non-schema would carry no rules and check none of those elements,
|
|
68
|
+
* which is why the value is read through array-keyword-guards.ts.
|
|
62
69
|
*/
|
|
63
70
|
function applyPositionally(runners, positionCount, hasRest) {
|
|
64
71
|
return (value, ctx) => {
|
|
@@ -85,11 +92,11 @@ function describePosition(index) {
|
|
|
85
92
|
: `Element ${String(index)} does not match the schema declared for it`;
|
|
86
93
|
}
|
|
87
94
|
function composeTupleItems(schema, context) {
|
|
88
|
-
const items = schema
|
|
95
|
+
const items = (0, array_keyword_guards_1.readCheckedItems)(schema);
|
|
89
96
|
if (items === undefined || !isTupleItems(items))
|
|
90
97
|
return NO_RULES;
|
|
91
98
|
const branches = items.map((position, index) => context.toBranch(`position:${String(index)}`, position));
|
|
92
|
-
const rest = schema
|
|
99
|
+
const rest = (0, array_keyword_guards_1.readCheckedAdditionalItems)(schema);
|
|
93
100
|
const hasRest = rest !== undefined;
|
|
94
101
|
if (rest !== undefined)
|
|
95
102
|
branches.push(context.toBranch("rest", rest));
|
|
@@ -109,9 +116,13 @@ function composeTupleItems(schema, context) {
|
|
|
109
116
|
* `contains` is EXISTENTIAL: at least one element matches. arrayContains owns
|
|
110
117
|
* that reduction, and its default bounds (min 1, no max) are exactly the
|
|
111
118
|
* draft's, so this is one call and no arithmetic.
|
|
119
|
+
*
|
|
120
|
+
* An element branch holding no rules would satisfy the existence check on the
|
|
121
|
+
* first element and turn the keyword into "the array is non-empty", so the
|
|
122
|
+
* value is read through array-keyword-guards.ts.
|
|
112
123
|
*/
|
|
113
124
|
function composeContains(schema, context) {
|
|
114
|
-
const contains = schema
|
|
125
|
+
const contains = (0, array_keyword_guards_1.readCheckedContains)(schema);
|
|
115
126
|
if (contains === undefined)
|
|
116
127
|
return NO_RULES;
|
|
117
128
|
const element = context.collectSubSchemaRules((0, collect_definitions_1.toSchemaObject)(contains));
|
|
@@ -124,7 +135,9 @@ function composeContains(schema, context) {
|
|
|
124
135
|
* and `maxItems` to `maxLength`; writing "minItems" as the method name does not
|
|
125
136
|
* compile (keyword-map-array.ts), which is the C2 regression made impossible.
|
|
126
137
|
* `uniqueItems: false` never reaches its binding: §6.4.3 gives it no effect and
|
|
127
|
-
* the binding's value type is `true`.
|
|
138
|
+
* the binding's value type is `true`. That is why the value is read through
|
|
139
|
+
* readCheckedUniqueItems: with a bare `=== true`, a non-boolean would take the
|
|
140
|
+
* same silent route as the lawful `false` and lose the constraint entirely.
|
|
128
141
|
*/
|
|
129
142
|
function declareArrayRules(schema, context) {
|
|
130
143
|
let chain = (0, create_field_slots_1.createFieldSlots)(context.bag, context.build).array;
|
|
@@ -134,7 +147,7 @@ function declareArrayRules(schema, context) {
|
|
|
134
147
|
if (schema.maxItems !== undefined) {
|
|
135
148
|
chain = (0, apply_keyword_binding_1.applyKeywordBinding)(chain, keyword_map_array_1.maxItemsBinding, schema.maxItems);
|
|
136
149
|
}
|
|
137
|
-
if (schema
|
|
150
|
+
if ((0, array_keyword_guards_1.readCheckedUniqueItems)(schema) === true) {
|
|
138
151
|
chain = (0, apply_keyword_binding_1.applyKeywordBinding)(chain, keyword_map_array_1.uniqueItemsBinding, true);
|
|
139
152
|
}
|
|
140
153
|
return (0, create_chain_node_1.readChainRules)(chain) ?? NO_RULES;
|
|
@@ -26,6 +26,7 @@ import { readChainRules } from "../chain/create-chain-node.mjs";
|
|
|
26
26
|
import { createFieldSlots } from "../chain/create-field-slots.mjs";
|
|
27
27
|
import { composite } from "../plugin-kit/create-rule.mjs";
|
|
28
28
|
import { applyKeywordBinding } from "./apply-keyword-binding.mjs";
|
|
29
|
+
import { readCheckedAdditionalItems, readCheckedContains, readCheckedItems, readCheckedUniqueItems, } from "./array-keyword-guards.mjs";
|
|
29
30
|
import { maxItemsBinding, minItemsBinding, uniqueItemsBinding, } from "./keyword-map-array.mjs";
|
|
30
31
|
import { PASS, fail, isArray, isNumber } from "../types/index.mjs";
|
|
31
32
|
import { toSchemaObject } from "./collect-definitions.mjs";
|
|
@@ -40,9 +41,13 @@ function isTupleItems(items) {
|
|
|
40
41
|
/**
|
|
41
42
|
* The single form only. The tuple form gets no child path: element 0 and
|
|
42
43
|
* element 1 obey DIFFERENT schemas, and one declared `[*]` cannot say that.
|
|
44
|
+
*
|
|
45
|
+
* Reading through readCheckedItems is what stops a non-schema value here from
|
|
46
|
+
* becoming a child declaration whose sub-schema yields no rules at all; see
|
|
47
|
+
* array-keyword-guards.ts.
|
|
43
48
|
*/
|
|
44
49
|
export function readItemChildren(schema) {
|
|
45
|
-
const items = schema
|
|
50
|
+
const items = readCheckedItems(schema);
|
|
46
51
|
if (items === undefined || isTupleItems(items))
|
|
47
52
|
return NO_CHILDREN;
|
|
48
53
|
return Object.freeze([{ step: EACH_STEP, schema: items, isRequired: false }]);
|
|
@@ -51,7 +56,9 @@ export function readItemChildren(schema) {
|
|
|
51
56
|
* Element i against position i, extra elements against `additionalItems`.
|
|
52
57
|
* A non-array passes, a short array passes, and `additionalItems: false`
|
|
53
58
|
* becomes the schema that matches nothing (collect-definitions), so the
|
|
54
|
-
* "no extra elements" case needs no fourth branch kind.
|
|
59
|
+
* "no extra elements" case needs no fourth branch kind. A rest branch built
|
|
60
|
+
* from a non-schema would carry no rules and check none of those elements,
|
|
61
|
+
* which is why the value is read through array-keyword-guards.ts.
|
|
55
62
|
*/
|
|
56
63
|
function applyPositionally(runners, positionCount, hasRest) {
|
|
57
64
|
return (value, ctx) => {
|
|
@@ -78,11 +85,11 @@ function describePosition(index) {
|
|
|
78
85
|
: `Element ${String(index)} does not match the schema declared for it`;
|
|
79
86
|
}
|
|
80
87
|
export function composeTupleItems(schema, context) {
|
|
81
|
-
const items = schema
|
|
88
|
+
const items = readCheckedItems(schema);
|
|
82
89
|
if (items === undefined || !isTupleItems(items))
|
|
83
90
|
return NO_RULES;
|
|
84
91
|
const branches = items.map((position, index) => context.toBranch(`position:${String(index)}`, position));
|
|
85
|
-
const rest = schema
|
|
92
|
+
const rest = readCheckedAdditionalItems(schema);
|
|
86
93
|
const hasRest = rest !== undefined;
|
|
87
94
|
if (rest !== undefined)
|
|
88
95
|
branches.push(context.toBranch("rest", rest));
|
|
@@ -102,9 +109,13 @@ export function composeTupleItems(schema, context) {
|
|
|
102
109
|
* `contains` is EXISTENTIAL: at least one element matches. arrayContains owns
|
|
103
110
|
* that reduction, and its default bounds (min 1, no max) are exactly the
|
|
104
111
|
* draft's, so this is one call and no arithmetic.
|
|
112
|
+
*
|
|
113
|
+
* An element branch holding no rules would satisfy the existence check on the
|
|
114
|
+
* first element and turn the keyword into "the array is non-empty", so the
|
|
115
|
+
* value is read through array-keyword-guards.ts.
|
|
105
116
|
*/
|
|
106
117
|
export function composeContains(schema, context) {
|
|
107
|
-
const contains = schema
|
|
118
|
+
const contains = readCheckedContains(schema);
|
|
108
119
|
if (contains === undefined)
|
|
109
120
|
return NO_RULES;
|
|
110
121
|
const element = context.collectSubSchemaRules(toSchemaObject(contains));
|
|
@@ -117,7 +128,9 @@ export function composeContains(schema, context) {
|
|
|
117
128
|
* and `maxItems` to `maxLength`; writing "minItems" as the method name does not
|
|
118
129
|
* compile (keyword-map-array.ts), which is the C2 regression made impossible.
|
|
119
130
|
* `uniqueItems: false` never reaches its binding: §6.4.3 gives it no effect and
|
|
120
|
-
* the binding's value type is `true`.
|
|
131
|
+
* the binding's value type is `true`. That is why the value is read through
|
|
132
|
+
* readCheckedUniqueItems: with a bare `=== true`, a non-boolean would take the
|
|
133
|
+
* same silent route as the lawful `false` and lose the constraint entirely.
|
|
121
134
|
*/
|
|
122
135
|
export function declareArrayRules(schema, context) {
|
|
123
136
|
let chain = createFieldSlots(context.bag, context.build).array;
|
|
@@ -127,7 +140,7 @@ export function declareArrayRules(schema, context) {
|
|
|
127
140
|
if (schema.maxItems !== undefined) {
|
|
128
141
|
chain = applyKeywordBinding(chain, maxItemsBinding, schema.maxItems);
|
|
129
142
|
}
|
|
130
|
-
if (schema
|
|
143
|
+
if (readCheckedUniqueItems(schema) === true) {
|
|
131
144
|
chain = applyKeywordBinding(chain, uniqueItemsBinding, true);
|
|
132
145
|
}
|
|
133
146
|
return readChainRules(chain) ?? NO_RULES;
|
|
@@ -17,6 +17,10 @@ export { createDocumentScope, createLocalScope } from "./ref-scope";
|
|
|
17
17
|
export type { SchemaRegistry } from "./schema-registry";
|
|
18
18
|
export { createSchemaRegistry } from "./schema-registry";
|
|
19
19
|
export { UnsupportedKeywordError } from "./unsupported-keyword-error";
|
|
20
|
+
export { MalformedSchemaError } from "./malformed-schema-error";
|
|
21
|
+
export { DRAFT07_DIALECT_URI, UnsupportedDialectError, } from "./unsupported-dialect-error";
|
|
22
|
+
export { NON_DRAFT07_DIALECTS, assertSupportedDialect, } from "./assert-supported-dialect";
|
|
23
|
+
export type { DialectOptions } from "./assert-supported-dialect";
|
|
20
24
|
export { isDraft07Schema, isSchemaObject } from "./draft07.types";
|
|
21
25
|
export type { Draft07Schema, Draft07SchemaObject, Draft07TypeKeyword, } from "./draft07.types";
|
|
22
26
|
export { assertKeywordSupported, countKeywordHandlings, findKeywordHandling, isDraft07Keyword, listBoundPluginNames, listDraft07Keywords, } from "./keyword-map";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listFormatNames = exports.findFormatHandling = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Keyword = exports.findKeywordHandling = exports.countKeywordHandlings = exports.assertKeywordSupported = exports.isSchemaObject = exports.isDraft07Schema = exports.UnsupportedKeywordError = exports.createSchemaRegistry = exports.createLocalScope = exports.createDocumentScope = exports.resolveRefInScope = exports.resolveRef = exports.isResolvableRef = exports.RefResolutionError = exports.declarePresenceRules = exports.declareRequiredProperties = exports.createStructuralContext = exports.toSchemaBranch = exports.collectSubSchemaRules = exports.readChildSchemas = exports.isStructuralKeyword = exports.expandSchemaRules = exports.STRUCTURAL_EXPANSIONS = exports.joinDeclaredPath = exports.flattenSchema = exports.toSchemaObject = exports.resolveSchemaNodeInScope = exports.resolveSchemaNode = exports.readRefPointer = exports.isDefinitionContainer = exports.collectDefinitionNames = exports.DEFINITION_CONTAINERS = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.NotASchemaError = void 0;
|
|
3
|
+
exports.listFormatNames = exports.findFormatHandling = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Keyword = exports.findKeywordHandling = exports.countKeywordHandlings = exports.assertKeywordSupported = exports.isSchemaObject = exports.isDraft07Schema = exports.assertSupportedDialect = exports.NON_DRAFT07_DIALECTS = exports.UnsupportedDialectError = exports.DRAFT07_DIALECT_URI = exports.MalformedSchemaError = exports.UnsupportedKeywordError = exports.createSchemaRegistry = exports.createLocalScope = exports.createDocumentScope = exports.resolveRefInScope = exports.resolveRef = exports.isResolvableRef = exports.RefResolutionError = exports.declarePresenceRules = exports.declareRequiredProperties = exports.createStructuralContext = exports.toSchemaBranch = exports.collectSubSchemaRules = exports.readChildSchemas = exports.isStructuralKeyword = exports.expandSchemaRules = exports.STRUCTURAL_EXPANSIONS = exports.joinDeclaredPath = exports.flattenSchema = exports.toSchemaObject = exports.resolveSchemaNodeInScope = exports.resolveSchemaNode = exports.readRefPointer = exports.isDefinitionContainer = exports.collectDefinitionNames = exports.DEFINITION_CONTAINERS = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.NotASchemaError = void 0;
|
|
4
4
|
// ===========================================================================
|
|
5
5
|
// L8 src/json-schema/index.ts — re-exports only. Nothing is defined here.
|
|
6
6
|
//
|
|
@@ -50,6 +50,14 @@ var schema_registry_1 = require("./schema-registry");
|
|
|
50
50
|
Object.defineProperty(exports, "createSchemaRegistry", { enumerable: true, get: function () { return schema_registry_1.createSchemaRegistry; } });
|
|
51
51
|
var unsupported_keyword_error_1 = require("./unsupported-keyword-error");
|
|
52
52
|
Object.defineProperty(exports, "UnsupportedKeywordError", { enumerable: true, get: function () { return unsupported_keyword_error_1.UnsupportedKeywordError; } });
|
|
53
|
+
var malformed_schema_error_1 = require("./malformed-schema-error");
|
|
54
|
+
Object.defineProperty(exports, "MalformedSchemaError", { enumerable: true, get: function () { return malformed_schema_error_1.MalformedSchemaError; } });
|
|
55
|
+
var unsupported_dialect_error_1 = require("./unsupported-dialect-error");
|
|
56
|
+
Object.defineProperty(exports, "DRAFT07_DIALECT_URI", { enumerable: true, get: function () { return unsupported_dialect_error_1.DRAFT07_DIALECT_URI; } });
|
|
57
|
+
Object.defineProperty(exports, "UnsupportedDialectError", { enumerable: true, get: function () { return unsupported_dialect_error_1.UnsupportedDialectError; } });
|
|
58
|
+
var assert_supported_dialect_1 = require("./assert-supported-dialect");
|
|
59
|
+
Object.defineProperty(exports, "NON_DRAFT07_DIALECTS", { enumerable: true, get: function () { return assert_supported_dialect_1.NON_DRAFT07_DIALECTS; } });
|
|
60
|
+
Object.defineProperty(exports, "assertSupportedDialect", { enumerable: true, get: function () { return assert_supported_dialect_1.assertSupportedDialect; } });
|
|
53
61
|
var draft07_types_1 = require("./draft07.types");
|
|
54
62
|
Object.defineProperty(exports, "isDraft07Schema", { enumerable: true, get: function () { return draft07_types_1.isDraft07Schema; } });
|
|
55
63
|
Object.defineProperty(exports, "isSchemaObject", { enumerable: true, get: function () { return draft07_types_1.isSchemaObject; } });
|
|
@@ -17,6 +17,9 @@ export { isResolvableRef, resolveRef, resolveRefInScope } from "./resolve-ref.mj
|
|
|
17
17
|
export { createDocumentScope, createLocalScope } from "./ref-scope.mjs";
|
|
18
18
|
export { createSchemaRegistry } from "./schema-registry.mjs";
|
|
19
19
|
export { UnsupportedKeywordError } from "./unsupported-keyword-error.mjs";
|
|
20
|
+
export { MalformedSchemaError } from "./malformed-schema-error.mjs";
|
|
21
|
+
export { DRAFT07_DIALECT_URI, UnsupportedDialectError, } from "./unsupported-dialect-error.mjs";
|
|
22
|
+
export { NON_DRAFT07_DIALECTS, assertSupportedDialect, } from "./assert-supported-dialect.mjs";
|
|
20
23
|
export { isDraft07Schema, isSchemaObject } from "./draft07.types.mjs";
|
|
21
24
|
export { assertKeywordSupported, countKeywordHandlings, findKeywordHandling, isDraft07Keyword, listBoundPluginNames, listDraft07Keywords, } from "./keyword-map.mjs";
|
|
22
25
|
export { findFormatHandling, listFormatNames } from "./format-map.mjs";
|
|
@@ -25,7 +25,9 @@ const ANNOTATION_ONLY = "an annotation with no validation effect in Draft-07";
|
|
|
25
25
|
*/
|
|
26
26
|
exports.constBinding = (0, bind_keyword_1.bindKeyword)("any", "literal", literal_1.literalPlugin, (v) => [v]);
|
|
27
27
|
exports.coreKeywordMap = {
|
|
28
|
-
$schema: (0, bind_keyword_1.unsupported)("read
|
|
28
|
+
$schema: (0, bind_keyword_1.unsupported)("read at the document ROOT by assert-supported-dialect and then " +
|
|
29
|
+
"discarded: Luq implements Draft-07 and never switches dialect, so a " +
|
|
30
|
+
"root `$schema` naming another one is refused rather than ignored"),
|
|
29
31
|
$id: (0, bind_keyword_1.unsupported)("no base-URI resolution; resolve-ref accepts local $ref only"),
|
|
30
32
|
$comment: (0, bind_keyword_1.unsupported)(ANNOTATION_ONLY),
|
|
31
33
|
$ref: (0, bind_keyword_1.structural)("resolved by resolve-ref before any keyword is read"),
|
|
@@ -22,7 +22,9 @@ const ANNOTATION_ONLY = "an annotation with no validation effect in Draft-07";
|
|
|
22
22
|
*/
|
|
23
23
|
export const constBinding = bindKeyword("any", "literal", literalPlugin, (v) => [v]);
|
|
24
24
|
export const coreKeywordMap = {
|
|
25
|
-
$schema: unsupported("read
|
|
25
|
+
$schema: unsupported("read at the document ROOT by assert-supported-dialect and then " +
|
|
26
|
+
"discarded: Luq implements Draft-07 and never switches dialect, so a " +
|
|
27
|
+
"root `$schema` naming another one is refused rather than ignored"),
|
|
26
28
|
$id: unsupported("no base-URI resolution; resolve-ref accepts local $ref only"),
|
|
27
29
|
$comment: unsupported(ANNOTATION_ONLY),
|
|
28
30
|
$ref: structural("resolved by resolve-ref before any keyword is read"),
|
|
@@ -10,7 +10,9 @@ exports.stringKeywordMap = exports.contentMediaTypeBinding = exports.contentEnco
|
|
|
10
10
|
// table in this repository — 1.x shipped three of them and they disagreed
|
|
11
11
|
// (docs/legacy-spec/json-schema-mapping.md records the disagreements).
|
|
12
12
|
// ===========================================================================
|
|
13
|
+
const types_1 = require("../types");
|
|
13
14
|
const bind_keyword_1 = require("./bind-keyword");
|
|
15
|
+
const malformed_schema_error_1 = require("./malformed-schema-error");
|
|
14
16
|
const string_min_1 = require("../plugins/string-min");
|
|
15
17
|
const string_max_1 = require("../plugins/string-max");
|
|
16
18
|
const string_pattern_1 = require("../plugins/string-pattern");
|
|
@@ -18,13 +20,40 @@ const string_content_encoding_1 = require("../plugins/string-content-encoding");
|
|
|
18
20
|
const string_content_media_type_1 = require("../plugins/string-content-media-type");
|
|
19
21
|
exports.minLengthBinding = (0, bind_keyword_1.bindKeyword)("string", "min", string_min_1.stringMinPlugin, (v) => [v]);
|
|
20
22
|
exports.maxLengthBinding = (0, bind_keyword_1.bindKeyword)("string", "max", string_max_1.stringMaxPlugin, (v) => [v]);
|
|
23
|
+
/**
|
|
24
|
+
* Compiles the document's source, refusing anything the meta-schema forbids.
|
|
25
|
+
*
|
|
26
|
+
* The parameter is `unknown` although the keyword's value type is `string`,
|
|
27
|
+
* and that is the whole point: compiling is a TOTAL operation on JSON values —
|
|
28
|
+
* every one of them has a string form — so a number becomes a literal digit
|
|
29
|
+
* pattern and an object becomes one that matches almost every string. Both
|
|
30
|
+
* then reach stringPattern as a perfectly valid RegExp, leaving its
|
|
31
|
+
* non-RegExp guard nothing to fire on. The refusal has to be here, before the
|
|
32
|
+
* value is turned into something well formed.
|
|
33
|
+
*
|
|
34
|
+
* A string that is not a valid ECMA-262 pattern is the same failure wearing a
|
|
35
|
+
* different coat: the compile step throws a SyntaxError naming neither the
|
|
36
|
+
* keyword nor the document, so it is caught and re-raised as the typed refusal
|
|
37
|
+
* every other malformed value gets.
|
|
38
|
+
*/
|
|
39
|
+
function compilePattern(source) {
|
|
40
|
+
if (!(0, types_1.isString)(source)) {
|
|
41
|
+
throw new malformed_schema_error_1.MalformedSchemaError("pattern", "the value must be a string", source);
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return new RegExp(source);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new malformed_schema_error_1.MalformedSchemaError("pattern", "the value must be a valid ECMA-262 regular expression", source);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
21
50
|
/**
|
|
22
51
|
* Draft-07 spells `pattern` as an ECMA-262 SOURCE string; the plugin accepts a
|
|
23
52
|
* RegExp and only a RegExp, because 1.x's `new RegExp(source)` at the call site
|
|
24
53
|
* dropped flags silently and gave the caller no compile-time check. Compiling
|
|
25
54
|
* the schema's string therefore happens HERE, once, and nowhere else.
|
|
26
55
|
*/
|
|
27
|
-
exports.patternBinding = (0, bind_keyword_1.bindKeyword)("string", "pattern", string_pattern_1.stringPatternPlugin, (v) => [
|
|
56
|
+
exports.patternBinding = (0, bind_keyword_1.bindKeyword)("string", "pattern", string_pattern_1.stringPatternPlugin, (v) => [compilePattern(v)]);
|
|
28
57
|
/**
|
|
29
58
|
* The value type is `ContentEncodingName`, not `string`: an encoding the
|
|
30
59
|
* plugin does not know must be refused by the converter rather than accepted
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// table in this repository — 1.x shipped three of them and they disagreed
|
|
8
8
|
// (docs/legacy-spec/json-schema-mapping.md records the disagreements).
|
|
9
9
|
// ===========================================================================
|
|
10
|
+
import { isString } from "../types/index.mjs";
|
|
10
11
|
import { bindKeyword, structural } from "./bind-keyword.mjs";
|
|
12
|
+
import { MalformedSchemaError } from "./malformed-schema-error.mjs";
|
|
11
13
|
import { stringMinPlugin } from "../plugins/string-min/index.mjs";
|
|
12
14
|
import { stringMaxPlugin } from "../plugins/string-max/index.mjs";
|
|
13
15
|
import { stringPatternPlugin } from "../plugins/string-pattern/index.mjs";
|
|
@@ -15,13 +17,40 @@ import { stringContentEncodingPlugin, } from "../plugins/string-content-encoding
|
|
|
15
17
|
import { stringContentMediaTypePlugin } from "../plugins/string-content-media-type/index.mjs";
|
|
16
18
|
export const minLengthBinding = bindKeyword("string", "min", stringMinPlugin, (v) => [v]);
|
|
17
19
|
export const maxLengthBinding = bindKeyword("string", "max", stringMaxPlugin, (v) => [v]);
|
|
20
|
+
/**
|
|
21
|
+
* Compiles the document's source, refusing anything the meta-schema forbids.
|
|
22
|
+
*
|
|
23
|
+
* The parameter is `unknown` although the keyword's value type is `string`,
|
|
24
|
+
* and that is the whole point: compiling is a TOTAL operation on JSON values —
|
|
25
|
+
* every one of them has a string form — so a number becomes a literal digit
|
|
26
|
+
* pattern and an object becomes one that matches almost every string. Both
|
|
27
|
+
* then reach stringPattern as a perfectly valid RegExp, leaving its
|
|
28
|
+
* non-RegExp guard nothing to fire on. The refusal has to be here, before the
|
|
29
|
+
* value is turned into something well formed.
|
|
30
|
+
*
|
|
31
|
+
* A string that is not a valid ECMA-262 pattern is the same failure wearing a
|
|
32
|
+
* different coat: the compile step throws a SyntaxError naming neither the
|
|
33
|
+
* keyword nor the document, so it is caught and re-raised as the typed refusal
|
|
34
|
+
* every other malformed value gets.
|
|
35
|
+
*/
|
|
36
|
+
function compilePattern(source) {
|
|
37
|
+
if (!isString(source)) {
|
|
38
|
+
throw new MalformedSchemaError("pattern", "the value must be a string", source);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
return new RegExp(source);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new MalformedSchemaError("pattern", "the value must be a valid ECMA-262 regular expression", source);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
18
47
|
/**
|
|
19
48
|
* Draft-07 spells `pattern` as an ECMA-262 SOURCE string; the plugin accepts a
|
|
20
49
|
* RegExp and only a RegExp, because 1.x's `new RegExp(source)` at the call site
|
|
21
50
|
* dropped flags silently and gave the caller no compile-time check. Compiling
|
|
22
51
|
* the schema's string therefore happens HERE, once, and nowhere else.
|
|
23
52
|
*/
|
|
24
|
-
export const patternBinding = bindKeyword("string", "pattern", stringPatternPlugin, (v) => [
|
|
53
|
+
export const patternBinding = bindKeyword("string", "pattern", stringPatternPlugin, (v) => [compilePattern(v)]);
|
|
25
54
|
/**
|
|
26
55
|
* The value type is `ContentEncodingName`, not `string`: an encoding the
|
|
27
56
|
* plugin does not know must be refused by the converter rather than accepted
|
|
@@ -32,9 +32,14 @@ exports.NON_DRAFT07_KEYWORDS = {
|
|
|
32
32
|
$defs: "2019-09 spelling of `definitions`; resolve-ref follows it anyway",
|
|
33
33
|
$anchor: "2019-09 anchors; resolve-ref resolves JSON Pointers only",
|
|
34
34
|
$dynamicRef: "2020-12 dynamic references",
|
|
35
|
+
$dynamicAnchor: "2020-12 dynamic anchors; the target half of $dynamicRef",
|
|
35
36
|
$recursiveRef: "2019-09 recursive references",
|
|
37
|
+
$recursiveAnchor: "2019-09 recursive anchors; the target half of $recursiveRef",
|
|
38
|
+
$vocabulary: "2019-09 vocabulary declaration; Luq has one fixed vocabulary",
|
|
36
39
|
dependentRequired: "2019-09 split of `dependencies`; use `dependencies`",
|
|
37
40
|
dependentSchemas: "2019-09 split of `dependencies`; use `dependencies`",
|
|
41
|
+
minContains: "2019-09 bound on `contains`; Draft-07's is fixed at one",
|
|
42
|
+
maxContains: "2019-09 bound on `contains`; Draft-07 states no maximum",
|
|
38
43
|
unevaluatedItems: "2019-09; no annotation-collection model in Luq",
|
|
39
44
|
unevaluatedProperties: "2019-09; no annotation-collection model in Luq",
|
|
40
45
|
contentSchema: "2019-09 companion to contentMediaType",
|
|
@@ -23,9 +23,14 @@ export const NON_DRAFT07_KEYWORDS = {
|
|
|
23
23
|
$defs: "2019-09 spelling of `definitions`; resolve-ref follows it anyway",
|
|
24
24
|
$anchor: "2019-09 anchors; resolve-ref resolves JSON Pointers only",
|
|
25
25
|
$dynamicRef: "2020-12 dynamic references",
|
|
26
|
+
$dynamicAnchor: "2020-12 dynamic anchors; the target half of $dynamicRef",
|
|
26
27
|
$recursiveRef: "2019-09 recursive references",
|
|
28
|
+
$recursiveAnchor: "2019-09 recursive anchors; the target half of $recursiveRef",
|
|
29
|
+
$vocabulary: "2019-09 vocabulary declaration; Luq has one fixed vocabulary",
|
|
27
30
|
dependentRequired: "2019-09 split of `dependencies`; use `dependencies`",
|
|
28
31
|
dependentSchemas: "2019-09 split of `dependencies`; use `dependencies`",
|
|
32
|
+
minContains: "2019-09 bound on `contains`; Draft-07's is fixed at one",
|
|
33
|
+
maxContains: "2019-09 bound on `contains`; Draft-07 states no maximum",
|
|
29
34
|
unevaluatedItems: "2019-09; no annotation-collection model in Luq",
|
|
30
35
|
unevaluatedProperties: "2019-09; no annotation-collection model in Luq",
|
|
31
36
|
contentSchema: "2019-09 companion to contentMediaType",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two shapes §4.4 lets a schema take, worded once. Several keywords take a
|
|
3
|
+
* schema as their value — `properties` members, `additionalProperties`, every
|
|
4
|
+
* position of the tuple form of `items` — and each is read by a different
|
|
5
|
+
* module; sharing the phrase is what stops the same refusal being explained
|
|
6
|
+
* three different ways to the same caller.
|
|
7
|
+
*
|
|
8
|
+
* It is a fragment, so it composes: "the value ", `the schema under "a" `.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SCHEMA_FORMS = "must be an object or a boolean";
|
|
11
|
+
/**
|
|
12
|
+
* Renders an untrusted schema value for a message, in at most
|
|
13
|
+
* RECEIVED_MAX_LENGTH characters. The value comes from the same document that
|
|
14
|
+
* is already known to be malformed, so nothing about it can be assumed: it may
|
|
15
|
+
* be circular, hold a bigint, or be large enough to drown the message.
|
|
16
|
+
* Rendering must never be the thing that throws.
|
|
17
|
+
*/
|
|
18
|
+
export declare function renderReceivedValue(value: unknown): string;
|
|
19
|
+
/**
|
|
20
|
+
* Raised when a keyword's value violates the Draft-07 meta-schema. Thrown at
|
|
21
|
+
* build time, before a validator that would enforce less than the document
|
|
22
|
+
* states can exist.
|
|
23
|
+
*/
|
|
24
|
+
export declare class MalformedSchemaError extends Error {
|
|
25
|
+
/** The keyword whose value is malformed, for instance "type". */
|
|
26
|
+
readonly keyword: string;
|
|
27
|
+
/**
|
|
28
|
+
* What the meta-schema REQUIRES, written as a sentence fragment with no
|
|
29
|
+
* terminal punctuation — the message supplies it. Every keyword phrases it
|
|
30
|
+
* the same way, "<the thing> must be <the shape>", so that a caller reading
|
|
31
|
+
* two refusals from two different keywords reads one library rather than
|
|
32
|
+
* three; what was found instead is carried separately, in `received`.
|
|
33
|
+
*/
|
|
34
|
+
readonly reason: string;
|
|
35
|
+
/** The offending value, rendered short and safe by renderReceivedValue. */
|
|
36
|
+
readonly received: string;
|
|
37
|
+
constructor(keyword: string, reason: string, value: unknown);
|
|
38
|
+
}
|