@maroonedog/luq 2.4.0 → 2.4.2
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/dist/builder/compile-declarations.d.ts +7 -1
- package/dist/builder/compile-declarations.js +1 -0
- package/dist/builder/compile-declarations.mjs +1 -0
- package/dist/builder/create-field-builder.js +1 -1
- package/dist/builder/create-field-builder.mjs +1 -1
- package/dist/builder/create-plan-validator.d.ts +2 -1
- package/dist/builder/create-plan-validator.js +2 -2
- package/dist/builder/create-plan-validator.mjs +2 -2
- package/dist/runtime/create-validator.d.ts +12 -4
- package/dist/runtime/create-validator.js +22 -11
- package/dist/runtime/create-validator.mjs +21 -10
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -2
- package/dist/runtime/index.mjs +1 -1
- package/dist/standard-schema/emit-field-schema.js +10 -0
- package/dist/standard-schema/emit-field-schema.mjs +10 -0
- package/dist/standard-schema/plugin-keyword-map.d.ts +15 -2
- package/dist/standard-schema/plugin-keyword-map.js +11 -4
- package/dist/standard-schema/plugin-keyword-map.mjs +11 -4
- package/dist/types/global-config.d.ts +12 -0
- package/dist/types/global-config.js +2 -0
- package/dist/types/global-config.mjs +2 -0
- package/package.json +1 -1
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { ValidationPlan } from "../compile/validation-plan.types";
|
|
2
|
-
import type { GlobalConfig } from "../types/global-config";
|
|
2
|
+
import type { GlobalConfig, ResolvedGlobalConfig } from "../types/global-config";
|
|
3
3
|
import type { FieldEntry } from "./field-entry.types";
|
|
4
4
|
import type { FieldDeclaredCalls } from "./field-declared-calls.types";
|
|
5
5
|
/** What build() makes in one pass: the plan to run, and what was declared. */
|
|
6
6
|
export interface CompiledDeclarations {
|
|
7
7
|
readonly plan: ValidationPlan;
|
|
8
8
|
readonly declaredCalls: readonly FieldDeclaredCalls[];
|
|
9
|
+
/**
|
|
10
|
+
* Resolved once here, and carried out because the root short-circuit needs
|
|
11
|
+
* it too. That rejection happens before any rule, so it cannot read the
|
|
12
|
+
* config the way a plugin does — through RuleBuildContext.
|
|
13
|
+
*/
|
|
14
|
+
readonly config: ResolvedGlobalConfig;
|
|
9
15
|
}
|
|
10
16
|
export declare function compileDeclarations(entries: readonly FieldEntry[], configOverride: GlobalConfig | undefined): CompiledDeclarations;
|
|
@@ -27,7 +27,7 @@ const NO_ENTRIES = Object.freeze([]);
|
|
|
27
27
|
*/
|
|
28
28
|
function buildValidator(entries, configOverride) {
|
|
29
29
|
const compiled = (0, compile_declarations_1.compileDeclarations)(entries, configOverride);
|
|
30
|
-
const validator = (0, create_plan_validator_1.createPlanBackedValidator)(compiled.plan);
|
|
30
|
+
const validator = (0, create_plan_validator_1.createPlanBackedValidator)(compiled.plan, compiled.config);
|
|
31
31
|
(0, declared_calls_store_1.rememberDeclaredCalls)(validator, compiled.declaredCalls);
|
|
32
32
|
return validator;
|
|
33
33
|
}
|
|
@@ -24,7 +24,7 @@ const NO_ENTRIES = Object.freeze([]);
|
|
|
24
24
|
*/
|
|
25
25
|
function buildValidator(entries, configOverride) {
|
|
26
26
|
const compiled = compileDeclarations(entries, configOverride);
|
|
27
|
-
const validator = createPlanBackedValidator(compiled.plan);
|
|
27
|
+
const validator = createPlanBackedValidator(compiled.plan, compiled.config);
|
|
28
28
|
rememberDeclaredCalls(validator, compiled.declaredCalls);
|
|
29
29
|
return validator;
|
|
30
30
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ValidationPlan } from "../compile/validation-plan.types";
|
|
2
|
+
import type { ResolvedGlobalConfig } from "../types/global-config";
|
|
2
3
|
import type { PlanBackedValidator } from "./builder-surface.types";
|
|
3
|
-
export declare function createPlanBackedValidator(plan: ValidationPlan): PlanBackedValidator;
|
|
4
|
+
export declare function createPlanBackedValidator(plan: ValidationPlan, config: ResolvedGlobalConfig): PlanBackedValidator;
|
|
@@ -4,8 +4,8 @@ exports.createPlanBackedValidator = createPlanBackedValidator;
|
|
|
4
4
|
const create_field_validator_1 = require("../runtime/create-field-validator");
|
|
5
5
|
const create_validator_1 = require("../runtime/create-validator");
|
|
6
6
|
const create_subset_validator_1 = require("./create-subset-validator");
|
|
7
|
-
function createPlanBackedValidator(plan) {
|
|
8
|
-
const validator = (0, create_validator_1.createValidator)(plan);
|
|
7
|
+
function createPlanBackedValidator(plan, config) {
|
|
8
|
+
const validator = (0, create_validator_1.createValidator)(plan, config);
|
|
9
9
|
return Object.freeze({
|
|
10
10
|
validate: validator.validate,
|
|
11
11
|
parse: validator.parse,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createFieldValidator } from "../runtime/create-field-validator.mjs";
|
|
2
2
|
import { createValidator } from "../runtime/create-validator.mjs";
|
|
3
3
|
import { createSubsetValidator } from "./create-subset-validator.mjs";
|
|
4
|
-
export function createPlanBackedValidator(plan) {
|
|
5
|
-
const validator = createValidator(plan);
|
|
4
|
+
export function createPlanBackedValidator(plan, config) {
|
|
5
|
+
const validator = createValidator(plan, config);
|
|
6
6
|
return Object.freeze({
|
|
7
7
|
validate: validator.validate,
|
|
8
8
|
parse: validator.parse,
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { ValidationIssue } from "../types";
|
|
2
2
|
import type { ValidateOptions, ValidationResult } from "../types/validation-result.types";
|
|
3
3
|
import type { ValidationPlan } from "../compile/validation-plan.types";
|
|
4
|
+
import { type ResolvedGlobalConfig } from "../types/global-config";
|
|
4
5
|
/** The untyped pair. L6 puts the declared type back on top of it. */
|
|
5
6
|
export interface PlanValidator {
|
|
6
7
|
validate(value: unknown, options?: ValidateOptions): ValidationResult<unknown>;
|
|
7
8
|
parse(value: unknown, options?: ValidateOptions): ValidationResult<unknown>;
|
|
8
9
|
}
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The code an absent SUBJECT reports.
|
|
12
|
+
*
|
|
13
|
+
* The same spelling a missing FIELD reports, deliberately. It was "REQUIRED"
|
|
14
|
+
* — 1.x's SCREAMING_SNAKE, carried over with the wording — which meant a
|
|
15
|
+
* caller switching on issue.code handled `required` for a missing field and
|
|
16
|
+
* missed the root, where the only difference is how much of the value was
|
|
17
|
+
* absent.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ROOT_MISSING_CODE = "required";
|
|
12
20
|
/**
|
|
13
21
|
* Severity decides validity, and nothing else does. An `info` or `warning`
|
|
14
22
|
* issue is still an issue — the sink counted it and abortEarly saw it — but it
|
|
@@ -16,4 +24,4 @@ export declare const ROOT_MISSING_MESSAGE = "Value is required";
|
|
|
16
24
|
* an `issues` list at all.
|
|
17
25
|
*/
|
|
18
26
|
export declare function hasRejectingIssue(issues: readonly ValidationIssue[]): boolean;
|
|
19
|
-
export declare function createValidator(plan: ValidationPlan): PlanValidator;
|
|
27
|
+
export declare function createValidator(plan: ValidationPlan, config?: ResolvedGlobalConfig): PlanValidator;
|
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ROOT_MISSING_CODE = void 0;
|
|
4
4
|
exports.hasRejectingIssue = hasRejectingIssue;
|
|
5
5
|
exports.createValidator = createValidator;
|
|
6
|
+
const global_config_1 = require("../types/global-config");
|
|
6
7
|
const index_stack_1 = require("./index-stack");
|
|
7
8
|
const issue_sink_1 = require("./issue-sink");
|
|
8
9
|
const output_writer_1 = require("./output-writer");
|
|
9
10
|
const run_recursion_1 = require("./run-recursion");
|
|
10
11
|
const run_plan_1 = require("./run-plan");
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The code an absent SUBJECT reports.
|
|
14
|
+
*
|
|
15
|
+
* The same spelling a missing FIELD reports, deliberately. It was "REQUIRED"
|
|
16
|
+
* — 1.x's SCREAMING_SNAKE, carried over with the wording — which meant a
|
|
17
|
+
* caller switching on issue.code handled `required` for a missing field and
|
|
18
|
+
* missed the root, where the only difference is how much of the value was
|
|
19
|
+
* absent.
|
|
20
|
+
*/
|
|
21
|
+
exports.ROOT_MISSING_CODE = "required";
|
|
14
22
|
/**
|
|
15
23
|
* A null or undefined subject fails before the plan runs. Without it every
|
|
16
24
|
* reader would answer `undefined`, OPEN_PRESENCE would permit every absence,
|
|
17
25
|
* and `validate(null)` would report success for a schema that declares nothing
|
|
18
26
|
* required.
|
|
19
27
|
*/
|
|
20
|
-
function rejectMissingRoot() {
|
|
28
|
+
function rejectMissingRoot(config) {
|
|
21
29
|
return {
|
|
22
30
|
valid: false,
|
|
23
31
|
issues: Object.freeze([
|
|
24
32
|
Object.freeze({
|
|
25
33
|
path: "",
|
|
26
34
|
code: exports.ROOT_MISSING_CODE,
|
|
27
|
-
message:
|
|
35
|
+
message: config.rootMissingMessage,
|
|
36
|
+
// Not config.defaultSeverity. A warning here would let validate(null)
|
|
37
|
+
// report success for a schema that declares nothing required, which is
|
|
38
|
+
// the outcome this rejection exists to prevent.
|
|
28
39
|
severity: "error",
|
|
29
40
|
}),
|
|
30
41
|
]),
|
|
@@ -66,9 +77,9 @@ function nodeCanRecurse(node) {
|
|
|
66
77
|
return (node.elementFields.some((field) => field.recursion !== null) ||
|
|
67
78
|
node.nested.some(nodeCanRecurse));
|
|
68
79
|
}
|
|
69
|
-
function runRoot(plan, value, options, targets, shouldApplyTransforms, canRecurse) {
|
|
80
|
+
function runRoot(plan, config, value, options, targets, shouldApplyTransforms, canRecurse) {
|
|
70
81
|
if (value === null || value === undefined)
|
|
71
|
-
return rejectMissingRoot();
|
|
82
|
+
return rejectMissingRoot(config);
|
|
72
83
|
const sink = new issue_sink_1.IssueSink((0, issue_sink_1.resolveAbortPolicy)(options));
|
|
73
84
|
const output = (0, run_plan_1.runPlan)(plan, value, {
|
|
74
85
|
root: value,
|
|
@@ -89,12 +100,12 @@ function runRoot(plan, value, options, targets, shouldApplyTransforms, canRecurs
|
|
|
89
100
|
return { valid: false, issues };
|
|
90
101
|
return { valid: true, data: output, issues };
|
|
91
102
|
}
|
|
92
|
-
function createValidator(plan) {
|
|
103
|
+
function createValidator(plan, config = global_config_1.DEFAULT_GLOBAL_CONFIG) {
|
|
93
104
|
const parseTargets = (0, output_writer_1.createPlanWriteTargets)(plan);
|
|
94
105
|
const shouldWriteOutput = parseTargets !== null;
|
|
95
106
|
const canRecurse = planCanRecurse(plan);
|
|
96
107
|
return {
|
|
97
|
-
validate: (value, options) => runRoot(plan, value, options, output_writer_1.NO_WRITE_TARGETS, false, canRecurse),
|
|
98
|
-
parse: (value, options) => runRoot(plan, value, options, parseTargets ?? output_writer_1.NO_WRITE_TARGETS, shouldWriteOutput, canRecurse),
|
|
108
|
+
validate: (value, options) => runRoot(plan, config, value, options, output_writer_1.NO_WRITE_TARGETS, false, canRecurse),
|
|
109
|
+
parse: (value, options) => runRoot(plan, config, value, options, parseTargets ?? output_writer_1.NO_WRITE_TARGETS, shouldWriteOutput, canRecurse),
|
|
99
110
|
};
|
|
100
111
|
}
|
|
@@ -1,25 +1,36 @@
|
|
|
1
|
+
import { DEFAULT_GLOBAL_CONFIG, } from "../types/global-config.mjs";
|
|
1
2
|
import { IndexStack } from "./index-stack.mjs";
|
|
2
3
|
import { IssueSink, resolveAbortPolicy } from "./issue-sink.mjs";
|
|
3
4
|
import { NO_WRITE_TARGETS, createPlanWriteTargets } from "./output-writer.mjs";
|
|
4
5
|
import { createRecursionRunner } from "./run-recursion.mjs";
|
|
5
6
|
import { runPlan } from "./run-plan.mjs";
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The code an absent SUBJECT reports.
|
|
9
|
+
*
|
|
10
|
+
* The same spelling a missing FIELD reports, deliberately. It was "REQUIRED"
|
|
11
|
+
* — 1.x's SCREAMING_SNAKE, carried over with the wording — which meant a
|
|
12
|
+
* caller switching on issue.code handled `required` for a missing field and
|
|
13
|
+
* missed the root, where the only difference is how much of the value was
|
|
14
|
+
* absent.
|
|
15
|
+
*/
|
|
16
|
+
export const ROOT_MISSING_CODE = "required";
|
|
9
17
|
/**
|
|
10
18
|
* A null or undefined subject fails before the plan runs. Without it every
|
|
11
19
|
* reader would answer `undefined`, OPEN_PRESENCE would permit every absence,
|
|
12
20
|
* and `validate(null)` would report success for a schema that declares nothing
|
|
13
21
|
* required.
|
|
14
22
|
*/
|
|
15
|
-
function rejectMissingRoot() {
|
|
23
|
+
function rejectMissingRoot(config) {
|
|
16
24
|
return {
|
|
17
25
|
valid: false,
|
|
18
26
|
issues: Object.freeze([
|
|
19
27
|
Object.freeze({
|
|
20
28
|
path: "",
|
|
21
29
|
code: ROOT_MISSING_CODE,
|
|
22
|
-
message:
|
|
30
|
+
message: config.rootMissingMessage,
|
|
31
|
+
// Not config.defaultSeverity. A warning here would let validate(null)
|
|
32
|
+
// report success for a schema that declares nothing required, which is
|
|
33
|
+
// the outcome this rejection exists to prevent.
|
|
23
34
|
severity: "error",
|
|
24
35
|
}),
|
|
25
36
|
]),
|
|
@@ -61,9 +72,9 @@ function nodeCanRecurse(node) {
|
|
|
61
72
|
return (node.elementFields.some((field) => field.recursion !== null) ||
|
|
62
73
|
node.nested.some(nodeCanRecurse));
|
|
63
74
|
}
|
|
64
|
-
function runRoot(plan, value, options, targets, shouldApplyTransforms, canRecurse) {
|
|
75
|
+
function runRoot(plan, config, value, options, targets, shouldApplyTransforms, canRecurse) {
|
|
65
76
|
if (value === null || value === undefined)
|
|
66
|
-
return rejectMissingRoot();
|
|
77
|
+
return rejectMissingRoot(config);
|
|
67
78
|
const sink = new IssueSink(resolveAbortPolicy(options));
|
|
68
79
|
const output = runPlan(plan, value, {
|
|
69
80
|
root: value,
|
|
@@ -84,12 +95,12 @@ function runRoot(plan, value, options, targets, shouldApplyTransforms, canRecurs
|
|
|
84
95
|
return { valid: false, issues };
|
|
85
96
|
return { valid: true, data: output, issues };
|
|
86
97
|
}
|
|
87
|
-
export function createValidator(plan) {
|
|
98
|
+
export function createValidator(plan, config = DEFAULT_GLOBAL_CONFIG) {
|
|
88
99
|
const parseTargets = createPlanWriteTargets(plan);
|
|
89
100
|
const shouldWriteOutput = parseTargets !== null;
|
|
90
101
|
const canRecurse = planCanRecurse(plan);
|
|
91
102
|
return {
|
|
92
|
-
validate: (value, options) => runRoot(plan, value, options, NO_WRITE_TARGETS, false, canRecurse),
|
|
93
|
-
parse: (value, options) => runRoot(plan, value, options, parseTargets ?? NO_WRITE_TARGETS, shouldWriteOutput, canRecurse),
|
|
103
|
+
validate: (value, options) => runRoot(plan, config, value, options, NO_WRITE_TARGETS, false, canRecurse),
|
|
104
|
+
parse: (value, options) => runRoot(plan, config, value, options, parseTargets ?? NO_WRITE_TARGETS, shouldWriteOutput, canRecurse),
|
|
94
105
|
};
|
|
95
106
|
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { prefixIssuePaths, runPlan } from "./run-plan";
|
|
|
13
13
|
export { RECURSION_ABORT_POLICY, createRecursionRunner } from "./run-recursion";
|
|
14
14
|
export type { RecursionHost } from "./run-recursion";
|
|
15
15
|
export { BRANCH_ABORT_POLICY, createBranchExecutor } from "./run-branch";
|
|
16
|
-
export { ROOT_MISSING_CODE,
|
|
16
|
+
export { ROOT_MISSING_CODE, createValidator, hasRejectingIssue, } from "./create-validator";
|
|
17
17
|
export type { PlanValidator } from "./create-validator";
|
|
18
18
|
export { createFieldValidator } from "./create-field-validator";
|
|
19
19
|
export type { PlanFieldValidator } from "./create-field-validator";
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createFieldValidator = exports.hasRejectingIssue = exports.createValidator = exports.
|
|
3
|
+
exports.createFieldValidator = exports.hasRejectingIssue = exports.createValidator = exports.ROOT_MISSING_CODE = exports.createBranchExecutor = exports.BRANCH_ABORT_POLICY = exports.createRecursionRunner = exports.RECURSION_ABORT_POLICY = exports.runPlan = exports.prefixIssuePaths = exports.runArrayNodes = exports.writeFieldValue = exports.replaceElement = exports.createPlanWriteTargets = exports.createArrayWriteTargets = exports.NO_WRITE_TARGETS = exports.runField = exports.FIELD_VALUE_UNCHANGED = exports.decidePresence = exports.renderFallbackMessage = exports.createIssue = exports.joinIssuePath = exports.IndexStack = exports.resolveAbortPolicy = exports.IssueSink = exports.DEFAULT_ABORT_EARLY_ON_EACH_FIELD = exports.DEFAULT_ABORT_EARLY = exports.ARRAY_ELEMENTS_ABORT_ON_EACH_FIELD = void 0;
|
|
4
4
|
// ===========================================================================
|
|
5
5
|
// L5 src/runtime/index.ts — re-exports only. The engine: one plan in, issues
|
|
6
6
|
// and (for parse) a copy-on-write output out. Nothing is defined in this file.
|
|
@@ -41,7 +41,6 @@ Object.defineProperty(exports, "BRANCH_ABORT_POLICY", { enumerable: true, get: f
|
|
|
41
41
|
Object.defineProperty(exports, "createBranchExecutor", { enumerable: true, get: function () { return run_branch_1.createBranchExecutor; } });
|
|
42
42
|
var create_validator_1 = require("./create-validator");
|
|
43
43
|
Object.defineProperty(exports, "ROOT_MISSING_CODE", { enumerable: true, get: function () { return create_validator_1.ROOT_MISSING_CODE; } });
|
|
44
|
-
Object.defineProperty(exports, "ROOT_MISSING_MESSAGE", { enumerable: true, get: function () { return create_validator_1.ROOT_MISSING_MESSAGE; } });
|
|
45
44
|
Object.defineProperty(exports, "createValidator", { enumerable: true, get: function () { return create_validator_1.createValidator; } });
|
|
46
45
|
Object.defineProperty(exports, "hasRejectingIssue", { enumerable: true, get: function () { return create_validator_1.hasRejectingIssue; } });
|
|
47
46
|
var create_field_validator_1 = require("./create-field-validator");
|
package/dist/runtime/index.mjs
CHANGED
|
@@ -12,5 +12,5 @@ export { runArrayNodes } from "./run-array-node.mjs";
|
|
|
12
12
|
export { prefixIssuePaths, runPlan } from "./run-plan.mjs";
|
|
13
13
|
export { RECURSION_ABORT_POLICY, createRecursionRunner } from "./run-recursion.mjs";
|
|
14
14
|
export { BRANCH_ABORT_POLICY, createBranchExecutor } from "./run-branch.mjs";
|
|
15
|
-
export { ROOT_MISSING_CODE,
|
|
15
|
+
export { ROOT_MISSING_CODE, createValidator, hasRejectingIssue, } from "./create-validator.mjs";
|
|
16
16
|
export { createFieldValidator } from "./create-field-validator.mjs";
|
|
@@ -55,6 +55,16 @@ function emitFieldSchema(fieldPath, calls, policy) {
|
|
|
55
55
|
continue;
|
|
56
56
|
}
|
|
57
57
|
const keywords = toKeywords(call.args);
|
|
58
|
+
// undefined is the table saying it cannot express THESE arguments, which
|
|
59
|
+
// is the same answer as having no entry at all: refuse, rather than emit a
|
|
60
|
+
// schema missing a constraint the validator enforces. null is different —
|
|
61
|
+
// it means type or presence already carries the declaration.
|
|
62
|
+
if (keywords === undefined) {
|
|
63
|
+
if (policy === "throw") {
|
|
64
|
+
throw new unrepresentable_rule_error_1.UnrepresentableRuleError(fieldPath, call.pluginName, "no JSON Schema keyword expresses the arguments it was given");
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
58
68
|
if (keywords === null)
|
|
59
69
|
continue;
|
|
60
70
|
Object.assign(schema, keywords);
|
|
@@ -52,6 +52,16 @@ export function emitFieldSchema(fieldPath, calls, policy) {
|
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
54
54
|
const keywords = toKeywords(call.args);
|
|
55
|
+
// undefined is the table saying it cannot express THESE arguments, which
|
|
56
|
+
// is the same answer as having no entry at all: refuse, rather than emit a
|
|
57
|
+
// schema missing a constraint the validator enforces. null is different —
|
|
58
|
+
// it means type or presence already carries the declaration.
|
|
59
|
+
if (keywords === undefined) {
|
|
60
|
+
if (policy === "throw") {
|
|
61
|
+
throw new UnrepresentableRuleError(fieldPath, call.pluginName, "no JSON Schema keyword expresses the arguments it was given");
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
55
65
|
if (keywords === null)
|
|
56
66
|
continue;
|
|
57
67
|
Object.assign(schema, keywords);
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A keyword fragment made from the arguments. Three answers, and the
|
|
3
|
+
* difference between the last two is the point:
|
|
4
|
+
*
|
|
5
|
+
* an object the keywords this declaration means
|
|
6
|
+
* null it adds no keyword, and that is correct — type or presence
|
|
7
|
+
* already carries it
|
|
8
|
+
* undefined it cannot be expressed with these arguments, so the caller
|
|
9
|
+
* must refuse rather than emit a schema that quietly says
|
|
10
|
+
* something else
|
|
11
|
+
*
|
|
12
|
+
* Collapsing the last two into `null` is how a constraint disappears without
|
|
13
|
+
* a trace, which is the failure UnrepresentableRuleError exists to prevent.
|
|
14
|
+
*/
|
|
15
|
+
export type ToKeywords = (args: readonly unknown[]) => Record<string, unknown> | null | undefined;
|
|
3
16
|
export declare const PLUGIN_KEYWORDS: Readonly<Record<string, ToKeywords>>;
|
|
@@ -37,9 +37,14 @@ exports.PLUGIN_KEYWORDS = Object.freeze({
|
|
|
37
37
|
maxLength: numberAt(args, 0),
|
|
38
38
|
}),
|
|
39
39
|
// Draft-07's `pattern` is an ECMA-262 SOURCE string. Flags cannot be
|
|
40
|
-
// spelled there, and dropping them changes the meaning
|
|
41
|
-
//
|
|
42
|
-
|
|
40
|
+
// spelled there, and dropping them changes the meaning — `/^a.c$/i`
|
|
41
|
+
// accepts "ABC" and `{"pattern":"^a.c$"}` does not — so a flagged RegExp
|
|
42
|
+
// is unwritable rather than something to narrow quietly. Anything that is
|
|
43
|
+
// not a RegExp at all is unwritable for the same reason: refusing is the
|
|
44
|
+
// only answer that does not lose the constraint in silence.
|
|
45
|
+
stringPattern: (args) => args[0] instanceof RegExp && args[0].flags === ""
|
|
46
|
+
? { pattern: args[0].source }
|
|
47
|
+
: undefined,
|
|
43
48
|
stringContentEncoding: (args) => ({ contentEncoding: args[0] }),
|
|
44
49
|
stringContentMediaType: (args) => ({ contentMediaType: args[0] }),
|
|
45
50
|
// --- formats (names taken verbatim from the format map) ---------------
|
|
@@ -81,7 +86,9 @@ exports.PLUGIN_KEYWORDS = Object.freeze({
|
|
|
81
86
|
arrayUnique: () => ({ uniqueItems: true }),
|
|
82
87
|
// --- values -----------------------------------------------------------
|
|
83
88
|
literal: (args) => ({ const: args[0] }),
|
|
84
|
-
|
|
89
|
+
// `enum` needs the list itself. Given something else there is no list to
|
|
90
|
+
// write, and answering "no keyword" would drop the constraint.
|
|
91
|
+
oneOf: (args) => (Array.isArray(args[0]) ? { enum: args[0] } : undefined),
|
|
85
92
|
// --- handled by type or presence, adding no keyword -------------------
|
|
86
93
|
// numberInteger becomes `type: "integer"`, so it feeds the type decision.
|
|
87
94
|
numberInteger: () => null,
|
|
@@ -34,9 +34,14 @@ export const PLUGIN_KEYWORDS = Object.freeze({
|
|
|
34
34
|
maxLength: numberAt(args, 0),
|
|
35
35
|
}),
|
|
36
36
|
// Draft-07's `pattern` is an ECMA-262 SOURCE string. Flags cannot be
|
|
37
|
-
// spelled there, and dropping them changes the meaning
|
|
38
|
-
//
|
|
39
|
-
|
|
37
|
+
// spelled there, and dropping them changes the meaning — `/^a.c$/i`
|
|
38
|
+
// accepts "ABC" and `{"pattern":"^a.c$"}` does not — so a flagged RegExp
|
|
39
|
+
// is unwritable rather than something to narrow quietly. Anything that is
|
|
40
|
+
// not a RegExp at all is unwritable for the same reason: refusing is the
|
|
41
|
+
// only answer that does not lose the constraint in silence.
|
|
42
|
+
stringPattern: (args) => args[0] instanceof RegExp && args[0].flags === ""
|
|
43
|
+
? { pattern: args[0].source }
|
|
44
|
+
: undefined,
|
|
40
45
|
stringContentEncoding: (args) => ({ contentEncoding: args[0] }),
|
|
41
46
|
stringContentMediaType: (args) => ({ contentMediaType: args[0] }),
|
|
42
47
|
// --- formats (names taken verbatim from the format map) ---------------
|
|
@@ -78,7 +83,9 @@ export const PLUGIN_KEYWORDS = Object.freeze({
|
|
|
78
83
|
arrayUnique: () => ({ uniqueItems: true }),
|
|
79
84
|
// --- values -----------------------------------------------------------
|
|
80
85
|
literal: (args) => ({ const: args[0] }),
|
|
81
|
-
|
|
86
|
+
// `enum` needs the list itself. Given something else there is no list to
|
|
87
|
+
// write, and answering "no keyword" would drop the constraint.
|
|
88
|
+
oneOf: (args) => (Array.isArray(args[0]) ? { enum: args[0] } : undefined),
|
|
82
89
|
// --- handled by type or presence, adding no keyword -------------------
|
|
83
90
|
// numberInteger becomes `type: "integer"`, so it feeds the type decision.
|
|
84
91
|
numberInteger: () => null,
|
|
@@ -15,6 +15,17 @@ export interface GlobalConfig {
|
|
|
15
15
|
readonly customTransforms?: Readonly<Record<string, ValueTransform>>;
|
|
16
16
|
/** Severity a rule emits when neither the rule nor the call names one. */
|
|
17
17
|
readonly defaultSeverity?: IssueSeverity;
|
|
18
|
+
/**
|
|
19
|
+
* What `validate(null)` and `validate(undefined)` say.
|
|
20
|
+
*
|
|
21
|
+
* A subject that is absent fails before the plan runs, so there is no rule
|
|
22
|
+
* to carry a messageFactory and no call site to name one. This is where it
|
|
23
|
+
* is said instead. The severity is NOT settable with it: a warning here
|
|
24
|
+
* would let `validate(null)` report success for a schema that declares
|
|
25
|
+
* nothing required, which is the outcome the short-circuit exists to
|
|
26
|
+
* prevent.
|
|
27
|
+
*/
|
|
28
|
+
readonly rootMissingMessage?: string;
|
|
18
29
|
}
|
|
19
30
|
/** Every member present: this is what a plugin reads. */
|
|
20
31
|
export interface ResolvedGlobalConfig {
|
|
@@ -26,6 +37,7 @@ export interface ResolvedGlobalConfig {
|
|
|
26
37
|
readonly caseSensitive: boolean;
|
|
27
38
|
readonly customTransforms: Readonly<Record<string, ValueTransform>>;
|
|
28
39
|
readonly defaultSeverity: IssueSeverity;
|
|
40
|
+
readonly rootMissingMessage: string;
|
|
29
41
|
}
|
|
30
42
|
export declare const DEFAULT_GLOBAL_CONFIG: ResolvedGlobalConfig;
|
|
31
43
|
export declare function resolveGlobalConfig(overrides: GlobalConfig | undefined, base?: ResolvedGlobalConfig): ResolvedGlobalConfig;
|
|
@@ -14,6 +14,7 @@ exports.DEFAULT_GLOBAL_CONFIG = Object.freeze({
|
|
|
14
14
|
caseSensitive: true,
|
|
15
15
|
customTransforms: Object.freeze({}),
|
|
16
16
|
defaultSeverity: "error",
|
|
17
|
+
rootMissingMessage: "Value is required",
|
|
17
18
|
});
|
|
18
19
|
function resolveGlobalConfig(overrides, base = exports.DEFAULT_GLOBAL_CONFIG) {
|
|
19
20
|
if (overrides === undefined)
|
|
@@ -32,5 +33,6 @@ function resolveGlobalConfig(overrides, base = exports.DEFAULT_GLOBAL_CONFIG) {
|
|
|
32
33
|
caseSensitive: overrides.caseSensitive ?? base.caseSensitive,
|
|
33
34
|
customTransforms: overrides.customTransforms ?? base.customTransforms,
|
|
34
35
|
defaultSeverity: overrides.defaultSeverity ?? base.defaultSeverity,
|
|
36
|
+
rootMissingMessage: overrides.rootMissingMessage ?? base.rootMissingMessage,
|
|
35
37
|
});
|
|
36
38
|
}
|
|
@@ -10,6 +10,7 @@ export const DEFAULT_GLOBAL_CONFIG = Object.freeze({
|
|
|
10
10
|
caseSensitive: true,
|
|
11
11
|
customTransforms: Object.freeze({}),
|
|
12
12
|
defaultSeverity: "error",
|
|
13
|
+
rootMissingMessage: "Value is required",
|
|
13
14
|
});
|
|
14
15
|
export function resolveGlobalConfig(overrides, base = DEFAULT_GLOBAL_CONFIG) {
|
|
15
16
|
if (overrides === undefined)
|
|
@@ -28,5 +29,6 @@ export function resolveGlobalConfig(overrides, base = DEFAULT_GLOBAL_CONFIG) {
|
|
|
28
29
|
caseSensitive: overrides.caseSensitive ?? base.caseSensitive,
|
|
29
30
|
customTransforms: overrides.customTransforms ?? base.customTransforms,
|
|
30
31
|
defaultSeverity: overrides.defaultSeverity ?? base.defaultSeverity,
|
|
32
|
+
rootMissingMessage: overrides.rootMissingMessage ?? base.rootMissingMessage,
|
|
31
33
|
});
|
|
32
34
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maroonedog/luq",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "Universal Model & API Definition Platform - TypeScript validation library evolving into cross-language code generation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|