@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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ===========================================================================
|
|
3
|
+
// L8 src/json-schema/malformed-schema-error.ts
|
|
4
|
+
//
|
|
5
|
+
// UnsupportedKeywordError refuses a keyword NAME Luq cannot honour. This
|
|
6
|
+
// refuses a keyword VALUE the Draft-07 meta-schema does not allow, which until
|
|
7
|
+
// it is refused does not fail — it quietly enforces less than the document
|
|
8
|
+
// says. `{"type":"strig"}` drops the unknown name, so the type check vanishes
|
|
9
|
+
// and the field starts accepting null; `{"pattern":{"source":"^SKU-"}}` reaches
|
|
10
|
+
// the regex constructor as a stringified object and compiles to one matching
|
|
11
|
+
// almost anything; `{"additionalProperties":"false"}` loses the closed-object
|
|
12
|
+
// guarantee; `{"required":"name"}` builds and then throws a raw TypeError
|
|
13
|
+
// inside validate() on the first request. A validator that silently drops a
|
|
14
|
+
// constraint is worse than one that refuses the schema, so the refusal is a
|
|
15
|
+
// typed error carrying the keyword, what the meta-schema requires, and a
|
|
16
|
+
// rendering of what was found instead.
|
|
17
|
+
//
|
|
18
|
+
// The two refusals stay separate classes, neither extending the other, because
|
|
19
|
+
// they answer different questions: "Luq cannot honour this keyword" versus
|
|
20
|
+
// "this document is not valid Draft-07". The first is a limit of the library
|
|
21
|
+
// and the second is a bug in the document; a caller catching one must not
|
|
22
|
+
// silently catch the other.
|
|
23
|
+
// ===========================================================================
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.MalformedSchemaError = exports.SCHEMA_FORMS = void 0;
|
|
26
|
+
exports.renderReceivedValue = renderReceivedValue;
|
|
27
|
+
/**
|
|
28
|
+
* The two shapes §4.4 lets a schema take, worded once. Several keywords take a
|
|
29
|
+
* schema as their value — `properties` members, `additionalProperties`, every
|
|
30
|
+
* position of the tuple form of `items` — and each is read by a different
|
|
31
|
+
* module; sharing the phrase is what stops the same refusal being explained
|
|
32
|
+
* three different ways to the same caller.
|
|
33
|
+
*
|
|
34
|
+
* It is a fragment, so it composes: "the value ", `the schema under "a" `.
|
|
35
|
+
*/
|
|
36
|
+
exports.SCHEMA_FORMS = "must be an object or a boolean";
|
|
37
|
+
/**
|
|
38
|
+
* How much of the offending value a message may carry. A schema node is
|
|
39
|
+
* arbitrarily large, and an error whose message is a megabyte long is unusable
|
|
40
|
+
* in a log line.
|
|
41
|
+
*/
|
|
42
|
+
const RECEIVED_MAX_LENGTH = 80;
|
|
43
|
+
const TRUNCATION_MARKER = "...";
|
|
44
|
+
/**
|
|
45
|
+
* Renders an untrusted schema value for a message, in at most
|
|
46
|
+
* RECEIVED_MAX_LENGTH characters. The value comes from the same document that
|
|
47
|
+
* is already known to be malformed, so nothing about it can be assumed: it may
|
|
48
|
+
* be circular, hold a bigint, or be large enough to drown the message.
|
|
49
|
+
* Rendering must never be the thing that throws.
|
|
50
|
+
*/
|
|
51
|
+
function renderReceivedValue(value) {
|
|
52
|
+
if (value === undefined)
|
|
53
|
+
return "undefined";
|
|
54
|
+
if (typeof value === "function")
|
|
55
|
+
return "[function]";
|
|
56
|
+
if (typeof value === "symbol")
|
|
57
|
+
return "[symbol]";
|
|
58
|
+
return truncate(renderJson(value));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* JSON.stringify throws on a circular reference and on a bigint anywhere in
|
|
62
|
+
* the value, and returns undefined for a value it has no encoding for at all.
|
|
63
|
+
* Both outcomes become a description of the shape rather than a failure.
|
|
64
|
+
*/
|
|
65
|
+
function renderJson(value) {
|
|
66
|
+
try {
|
|
67
|
+
const rendered = JSON.stringify(value);
|
|
68
|
+
return rendered === undefined ? describeUnrenderable(value) : rendered;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return describeUnrenderable(value);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function describeUnrenderable(value) {
|
|
75
|
+
return Array.isArray(value)
|
|
76
|
+
? "[unrenderable array]"
|
|
77
|
+
: `[unrenderable ${typeof value}]`;
|
|
78
|
+
}
|
|
79
|
+
function truncate(rendered) {
|
|
80
|
+
if (rendered.length <= RECEIVED_MAX_LENGTH)
|
|
81
|
+
return rendered;
|
|
82
|
+
const kept = RECEIVED_MAX_LENGTH - TRUNCATION_MARKER.length;
|
|
83
|
+
return `${rendered.slice(0, kept)}${TRUNCATION_MARKER}`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Raised when a keyword's value violates the Draft-07 meta-schema. Thrown at
|
|
87
|
+
* build time, before a validator that would enforce less than the document
|
|
88
|
+
* states can exist.
|
|
89
|
+
*/
|
|
90
|
+
class MalformedSchemaError extends Error {
|
|
91
|
+
constructor(keyword, reason, value) {
|
|
92
|
+
const received = renderReceivedValue(value);
|
|
93
|
+
super(`JSON Schema keyword "${keyword}" has a value the Draft-07 meta-schema ` +
|
|
94
|
+
`does not allow: ${reason}. Received: ${received}.`);
|
|
95
|
+
this.name = "MalformedSchemaError";
|
|
96
|
+
this.keyword = keyword;
|
|
97
|
+
this.reason = reason;
|
|
98
|
+
this.received = received;
|
|
99
|
+
// Without this, `instanceof` fails when the package is compiled to ES5.
|
|
100
|
+
Object.setPrototypeOf(this, MalformedSchemaError.prototype);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.MalformedSchemaError = MalformedSchemaError;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L8 src/json-schema/malformed-schema-error.ts
|
|
3
|
+
//
|
|
4
|
+
// UnsupportedKeywordError refuses a keyword NAME Luq cannot honour. This
|
|
5
|
+
// refuses a keyword VALUE the Draft-07 meta-schema does not allow, which until
|
|
6
|
+
// it is refused does not fail — it quietly enforces less than the document
|
|
7
|
+
// says. `{"type":"strig"}` drops the unknown name, so the type check vanishes
|
|
8
|
+
// and the field starts accepting null; `{"pattern":{"source":"^SKU-"}}` reaches
|
|
9
|
+
// the regex constructor as a stringified object and compiles to one matching
|
|
10
|
+
// almost anything; `{"additionalProperties":"false"}` loses the closed-object
|
|
11
|
+
// guarantee; `{"required":"name"}` builds and then throws a raw TypeError
|
|
12
|
+
// inside validate() on the first request. A validator that silently drops a
|
|
13
|
+
// constraint is worse than one that refuses the schema, so the refusal is a
|
|
14
|
+
// typed error carrying the keyword, what the meta-schema requires, and a
|
|
15
|
+
// rendering of what was found instead.
|
|
16
|
+
//
|
|
17
|
+
// The two refusals stay separate classes, neither extending the other, because
|
|
18
|
+
// they answer different questions: "Luq cannot honour this keyword" versus
|
|
19
|
+
// "this document is not valid Draft-07". The first is a limit of the library
|
|
20
|
+
// and the second is a bug in the document; a caller catching one must not
|
|
21
|
+
// silently catch the other.
|
|
22
|
+
// ===========================================================================
|
|
23
|
+
/**
|
|
24
|
+
* The two shapes §4.4 lets a schema take, worded once. Several keywords take a
|
|
25
|
+
* schema as their value — `properties` members, `additionalProperties`, every
|
|
26
|
+
* position of the tuple form of `items` — and each is read by a different
|
|
27
|
+
* module; sharing the phrase is what stops the same refusal being explained
|
|
28
|
+
* three different ways to the same caller.
|
|
29
|
+
*
|
|
30
|
+
* It is a fragment, so it composes: "the value ", `the schema under "a" `.
|
|
31
|
+
*/
|
|
32
|
+
export const SCHEMA_FORMS = "must be an object or a boolean";
|
|
33
|
+
/**
|
|
34
|
+
* How much of the offending value a message may carry. A schema node is
|
|
35
|
+
* arbitrarily large, and an error whose message is a megabyte long is unusable
|
|
36
|
+
* in a log line.
|
|
37
|
+
*/
|
|
38
|
+
const RECEIVED_MAX_LENGTH = 80;
|
|
39
|
+
const TRUNCATION_MARKER = "...";
|
|
40
|
+
/**
|
|
41
|
+
* Renders an untrusted schema value for a message, in at most
|
|
42
|
+
* RECEIVED_MAX_LENGTH characters. The value comes from the same document that
|
|
43
|
+
* is already known to be malformed, so nothing about it can be assumed: it may
|
|
44
|
+
* be circular, hold a bigint, or be large enough to drown the message.
|
|
45
|
+
* Rendering must never be the thing that throws.
|
|
46
|
+
*/
|
|
47
|
+
export function renderReceivedValue(value) {
|
|
48
|
+
if (value === undefined)
|
|
49
|
+
return "undefined";
|
|
50
|
+
if (typeof value === "function")
|
|
51
|
+
return "[function]";
|
|
52
|
+
if (typeof value === "symbol")
|
|
53
|
+
return "[symbol]";
|
|
54
|
+
return truncate(renderJson(value));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* JSON.stringify throws on a circular reference and on a bigint anywhere in
|
|
58
|
+
* the value, and returns undefined for a value it has no encoding for at all.
|
|
59
|
+
* Both outcomes become a description of the shape rather than a failure.
|
|
60
|
+
*/
|
|
61
|
+
function renderJson(value) {
|
|
62
|
+
try {
|
|
63
|
+
const rendered = JSON.stringify(value);
|
|
64
|
+
return rendered === undefined ? describeUnrenderable(value) : rendered;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return describeUnrenderable(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function describeUnrenderable(value) {
|
|
71
|
+
return Array.isArray(value)
|
|
72
|
+
? "[unrenderable array]"
|
|
73
|
+
: `[unrenderable ${typeof value}]`;
|
|
74
|
+
}
|
|
75
|
+
function truncate(rendered) {
|
|
76
|
+
if (rendered.length <= RECEIVED_MAX_LENGTH)
|
|
77
|
+
return rendered;
|
|
78
|
+
const kept = RECEIVED_MAX_LENGTH - TRUNCATION_MARKER.length;
|
|
79
|
+
return `${rendered.slice(0, kept)}${TRUNCATION_MARKER}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Raised when a keyword's value violates the Draft-07 meta-schema. Thrown at
|
|
83
|
+
* build time, before a validator that would enforce less than the document
|
|
84
|
+
* states can exist.
|
|
85
|
+
*/
|
|
86
|
+
export class MalformedSchemaError extends Error {
|
|
87
|
+
constructor(keyword, reason, value) {
|
|
88
|
+
const received = renderReceivedValue(value);
|
|
89
|
+
super(`JSON Schema keyword "${keyword}" has a value the Draft-07 meta-schema ` +
|
|
90
|
+
`does not allow: ${reason}. Received: ${received}.`);
|
|
91
|
+
this.name = "MalformedSchemaError";
|
|
92
|
+
this.keyword = keyword;
|
|
93
|
+
this.reason = reason;
|
|
94
|
+
this.received = received;
|
|
95
|
+
// Without this, `instanceof` fails when the package is compiled to ES5.
|
|
96
|
+
Object.setPrototypeOf(this, MalformedSchemaError.prototype);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** The dialect this library implements, in the spelling the draft publishes. */
|
|
2
|
+
export declare const DRAFT07_DIALECT_URI = "http://json-schema.org/draft-07/schema#";
|
|
3
|
+
/**
|
|
4
|
+
* Raised when a document's root `$schema` names a dialect Luq does not
|
|
5
|
+
* implement. Thrown at build time, before a validator that would read the
|
|
6
|
+
* document under the wrong dialect's rules can exist.
|
|
7
|
+
*
|
|
8
|
+
* `reason` follows the convention the other two refusals use: a sentence
|
|
9
|
+
* fragment with no terminal punctuation, saying what the dialect does that
|
|
10
|
+
* Draft-07 does not, so that two refusals from two different dialects read as
|
|
11
|
+
* one library rather than as two unrelated messages.
|
|
12
|
+
*/
|
|
13
|
+
export declare class UnsupportedDialectError extends Error {
|
|
14
|
+
/** The `$schema` value the document wrote, verbatim. */
|
|
15
|
+
readonly declared: string;
|
|
16
|
+
/** The dialect Luq reads every document under. */
|
|
17
|
+
readonly implemented: string;
|
|
18
|
+
/** What that dialect does differently, or that it is unrecognised. */
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
constructor(declared: string, reason: string);
|
|
21
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ===========================================================================
|
|
3
|
+
// L8 src/json-schema/unsupported-dialect-error.ts
|
|
4
|
+
//
|
|
5
|
+
// The third refusal, and the only one with no keyword to hang on.
|
|
6
|
+
// UnsupportedKeywordError refuses a keyword NAME; MalformedSchemaError refuses
|
|
7
|
+
// a keyword VALUE. Both need the document to write something recognisable, and
|
|
8
|
+
// there is one divergence that writes nothing at all:
|
|
9
|
+
//
|
|
10
|
+
// {"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
11
|
+
// "$defs": {"name": {"type": "string"}},
|
|
12
|
+
// "properties": {"nick": {"$ref": "#/$defs/name", "minLength": 5}}}
|
|
13
|
+
//
|
|
14
|
+
// Every keyword there is a Draft-07 keyword spelled the Draft-07 way, so the
|
|
15
|
+
// keyword table sees nothing to refuse. The meaning is not Draft-07: §8.3 has
|
|
16
|
+
// `$ref` REPLACE the object it appears in, and from 2019-09 on `$ref` is an
|
|
17
|
+
// ordinary applicator whose siblings are applied. Read as Draft-07 the document
|
|
18
|
+
// above builds a validator that accepts `{"nick":"ab"}` — the `minLength` is
|
|
19
|
+
// gone, silently, and nothing in the result says a constraint went missing.
|
|
20
|
+
// `$schema` is the only signal the document offers, so it is the one read.
|
|
21
|
+
//
|
|
22
|
+
// It stays a separate class from the other two, neither extending the other,
|
|
23
|
+
// for the reason written in malformed-schema-error.ts: each answers a different
|
|
24
|
+
// question. "Luq cannot honour this keyword", "this document is not valid
|
|
25
|
+
// Draft-07", and now "this document is not Draft-07 AT ALL" are three distinct
|
|
26
|
+
// facts, and a caller catching one must not silently catch another.
|
|
27
|
+
// ===========================================================================
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.UnsupportedDialectError = exports.DRAFT07_DIALECT_URI = void 0;
|
|
30
|
+
/** The dialect this library implements, in the spelling the draft publishes. */
|
|
31
|
+
exports.DRAFT07_DIALECT_URI = "http://json-schema.org/draft-07/schema#";
|
|
32
|
+
/**
|
|
33
|
+
* Raised when a document's root `$schema` names a dialect Luq does not
|
|
34
|
+
* implement. Thrown at build time, before a validator that would read the
|
|
35
|
+
* document under the wrong dialect's rules can exist.
|
|
36
|
+
*
|
|
37
|
+
* `reason` follows the convention the other two refusals use: a sentence
|
|
38
|
+
* fragment with no terminal punctuation, saying what the dialect does that
|
|
39
|
+
* Draft-07 does not, so that two refusals from two different dialects read as
|
|
40
|
+
* one library rather than as two unrelated messages.
|
|
41
|
+
*/
|
|
42
|
+
class UnsupportedDialectError extends Error {
|
|
43
|
+
constructor(declared, reason) {
|
|
44
|
+
super(`JSON Schema dialect "${declared}" is not supported: ${reason}. Luq ` +
|
|
45
|
+
`implements Draft-07 (${exports.DRAFT07_DIALECT_URI}) and would read this ` +
|
|
46
|
+
"document under Draft-07 rules, which can enforce less than it " +
|
|
47
|
+
"states. Convert the document to Draft-07, or pass " +
|
|
48
|
+
"`{ assumeDraft07: true }` to take the Draft-07 reading deliberately.");
|
|
49
|
+
/** The dialect Luq reads every document under. */
|
|
50
|
+
this.implemented = exports.DRAFT07_DIALECT_URI;
|
|
51
|
+
this.name = "UnsupportedDialectError";
|
|
52
|
+
this.declared = declared;
|
|
53
|
+
this.reason = reason;
|
|
54
|
+
// Without this, `instanceof` fails when the package is compiled to ES5.
|
|
55
|
+
Object.setPrototypeOf(this, UnsupportedDialectError.prototype);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.UnsupportedDialectError = UnsupportedDialectError;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L8 src/json-schema/unsupported-dialect-error.ts
|
|
3
|
+
//
|
|
4
|
+
// The third refusal, and the only one with no keyword to hang on.
|
|
5
|
+
// UnsupportedKeywordError refuses a keyword NAME; MalformedSchemaError refuses
|
|
6
|
+
// a keyword VALUE. Both need the document to write something recognisable, and
|
|
7
|
+
// there is one divergence that writes nothing at all:
|
|
8
|
+
//
|
|
9
|
+
// {"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
10
|
+
// "$defs": {"name": {"type": "string"}},
|
|
11
|
+
// "properties": {"nick": {"$ref": "#/$defs/name", "minLength": 5}}}
|
|
12
|
+
//
|
|
13
|
+
// Every keyword there is a Draft-07 keyword spelled the Draft-07 way, so the
|
|
14
|
+
// keyword table sees nothing to refuse. The meaning is not Draft-07: §8.3 has
|
|
15
|
+
// `$ref` REPLACE the object it appears in, and from 2019-09 on `$ref` is an
|
|
16
|
+
// ordinary applicator whose siblings are applied. Read as Draft-07 the document
|
|
17
|
+
// above builds a validator that accepts `{"nick":"ab"}` — the `minLength` is
|
|
18
|
+
// gone, silently, and nothing in the result says a constraint went missing.
|
|
19
|
+
// `$schema` is the only signal the document offers, so it is the one read.
|
|
20
|
+
//
|
|
21
|
+
// It stays a separate class from the other two, neither extending the other,
|
|
22
|
+
// for the reason written in malformed-schema-error.ts: each answers a different
|
|
23
|
+
// question. "Luq cannot honour this keyword", "this document is not valid
|
|
24
|
+
// Draft-07", and now "this document is not Draft-07 AT ALL" are three distinct
|
|
25
|
+
// facts, and a caller catching one must not silently catch another.
|
|
26
|
+
// ===========================================================================
|
|
27
|
+
/** The dialect this library implements, in the spelling the draft publishes. */
|
|
28
|
+
export const DRAFT07_DIALECT_URI = "http://json-schema.org/draft-07/schema#";
|
|
29
|
+
/**
|
|
30
|
+
* Raised when a document's root `$schema` names a dialect Luq does not
|
|
31
|
+
* implement. Thrown at build time, before a validator that would read the
|
|
32
|
+
* document under the wrong dialect's rules can exist.
|
|
33
|
+
*
|
|
34
|
+
* `reason` follows the convention the other two refusals use: a sentence
|
|
35
|
+
* fragment with no terminal punctuation, saying what the dialect does that
|
|
36
|
+
* Draft-07 does not, so that two refusals from two different dialects read as
|
|
37
|
+
* one library rather than as two unrelated messages.
|
|
38
|
+
*/
|
|
39
|
+
export class UnsupportedDialectError extends Error {
|
|
40
|
+
constructor(declared, reason) {
|
|
41
|
+
super(`JSON Schema dialect "${declared}" is not supported: ${reason}. Luq ` +
|
|
42
|
+
`implements Draft-07 (${DRAFT07_DIALECT_URI}) and would read this ` +
|
|
43
|
+
"document under Draft-07 rules, which can enforce less than it " +
|
|
44
|
+
"states. Convert the document to Draft-07, or pass " +
|
|
45
|
+
"`{ assumeDraft07: true }` to take the Draft-07 reading deliberately.");
|
|
46
|
+
/** The dialect Luq reads every document under. */
|
|
47
|
+
this.implemented = DRAFT07_DIALECT_URI;
|
|
48
|
+
this.name = "UnsupportedDialectError";
|
|
49
|
+
this.declared = declared;
|
|
50
|
+
this.reason = reason;
|
|
51
|
+
// Without this, `instanceof` fails when the package is compiled to ES5.
|
|
52
|
+
Object.setPrototypeOf(this, UnsupportedDialectError.prototype);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -36,7 +36,10 @@ function describeAllowed(allowed) {
|
|
|
36
36
|
function areAllMembersPrimitive(allowed) {
|
|
37
37
|
return allowed.every((member) => member === null || typeof member !== "object");
|
|
38
38
|
}
|
|
39
|
-
/** One membership test, decided ONCE at build time and never re-decided.
|
|
39
|
+
/** One membership test, decided ONCE at build time and never re-decided. The
|
|
40
|
+
* list must already be a snapshot: the linear path CLOSES OVER it rather than
|
|
41
|
+
* copying it into a Set, so a caller-owned array reaching here would stay
|
|
42
|
+
* readable — and mutable — through the returned validator. */
|
|
40
43
|
function createMembershipTest(allowed) {
|
|
41
44
|
if (allowed.length > SET_MEMBERSHIP_THRESHOLD &&
|
|
42
45
|
areAllMembersPrimitive(allowed)) {
|
|
@@ -53,13 +56,24 @@ exports.oneOfPlugin = (0, plugin_definition_1.definePlugin)()({
|
|
|
53
56
|
if (!(0, types_1.isArray)(allowed) || allowed.length === 0) {
|
|
54
57
|
throw new plugin_definition_1.PluginArgumentError(ctx.pluginName, "allowed", allowed);
|
|
55
58
|
}
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
// A built validator is a SNAPSHOT of what it was built from, and this list
|
|
60
|
+
// arrives BY REFERENCE: reached through the `enum` keyword it is the very
|
|
61
|
+
// array inside the caller's schema document. Without this copy, appending
|
|
62
|
+
// to that array in place after build() — a config reload that mutates the
|
|
63
|
+
// document rather than replacing it — silently widens a validator that is
|
|
64
|
+
// already serving traffic. It would also do so only SOMETIMES: the Set
|
|
65
|
+
// path in createMembershipTest copies and the linear path does not, so the
|
|
66
|
+
// behaviour would flip at a member count the caller has no reason to know
|
|
67
|
+
// about. Copying here, at the one point where the caller's array enters
|
|
68
|
+
// the plugin, is what makes both paths and the reported `expected` agree.
|
|
69
|
+
const members = Object.freeze(allowed.slice());
|
|
70
|
+
const isMember = createMembershipTest(members);
|
|
71
|
+
const rendered = describeAllowed(members);
|
|
58
72
|
return (0, create_rule_1.check)({
|
|
59
73
|
code: ctx.code,
|
|
60
74
|
messageFactory: ctx.messageFactory,
|
|
61
75
|
severity: ctx.severity,
|
|
62
|
-
run: (value) => isMember(value) ? types_1.PASS : (0, types_1.fail)({ expected:
|
|
76
|
+
run: (value) => isMember(value) ? types_1.PASS : (0, types_1.fail)({ expected: members, actual: value }),
|
|
63
77
|
describe: () => `Value must be one of: ${rendered}`,
|
|
64
78
|
buildMessageContext: () => ({}),
|
|
65
79
|
});
|
|
@@ -33,7 +33,10 @@ function describeAllowed(allowed) {
|
|
|
33
33
|
function areAllMembersPrimitive(allowed) {
|
|
34
34
|
return allowed.every((member) => member === null || typeof member !== "object");
|
|
35
35
|
}
|
|
36
|
-
/** One membership test, decided ONCE at build time and never re-decided.
|
|
36
|
+
/** One membership test, decided ONCE at build time and never re-decided. The
|
|
37
|
+
* list must already be a snapshot: the linear path CLOSES OVER it rather than
|
|
38
|
+
* copying it into a Set, so a caller-owned array reaching here would stay
|
|
39
|
+
* readable — and mutable — through the returned validator. */
|
|
37
40
|
function createMembershipTest(allowed) {
|
|
38
41
|
if (allowed.length > SET_MEMBERSHIP_THRESHOLD &&
|
|
39
42
|
areAllMembersPrimitive(allowed)) {
|
|
@@ -50,13 +53,24 @@ export const oneOfPlugin = /*#__PURE__*/ definePlugin()({
|
|
|
50
53
|
if (!isArray(allowed) || allowed.length === 0) {
|
|
51
54
|
throw new PluginArgumentError(ctx.pluginName, "allowed", allowed);
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
// A built validator is a SNAPSHOT of what it was built from, and this list
|
|
57
|
+
// arrives BY REFERENCE: reached through the `enum` keyword it is the very
|
|
58
|
+
// array inside the caller's schema document. Without this copy, appending
|
|
59
|
+
// to that array in place after build() — a config reload that mutates the
|
|
60
|
+
// document rather than replacing it — silently widens a validator that is
|
|
61
|
+
// already serving traffic. It would also do so only SOMETIMES: the Set
|
|
62
|
+
// path in createMembershipTest copies and the linear path does not, so the
|
|
63
|
+
// behaviour would flip at a member count the caller has no reason to know
|
|
64
|
+
// about. Copying here, at the one point where the caller's array enters
|
|
65
|
+
// the plugin, is what makes both paths and the reported `expected` agree.
|
|
66
|
+
const members = Object.freeze(allowed.slice());
|
|
67
|
+
const isMember = createMembershipTest(members);
|
|
68
|
+
const rendered = describeAllowed(members);
|
|
55
69
|
return check({
|
|
56
70
|
code: ctx.code,
|
|
57
71
|
messageFactory: ctx.messageFactory,
|
|
58
72
|
severity: ctx.severity,
|
|
59
|
-
run: (value) => isMember(value) ? PASS : fail({ expected:
|
|
73
|
+
run: (value) => isMember(value) ? PASS : fail({ expected: members, actual: value }),
|
|
60
74
|
describe: () => `Value must be one of: ${rendered}`,
|
|
61
75
|
buildMessageContext: () => ({}),
|
|
62
76
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maroonedog/luq",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
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",
|
|
@@ -459,18 +459,20 @@
|
|
|
459
459
|
"typecheck:src": "tsc --noEmit -p tsconfig.json",
|
|
460
460
|
"typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json",
|
|
461
461
|
"typecheck:bench": "tsc --noEmit -p bench/tsconfig.json",
|
|
462
|
-
"generate": "npm run generate:sources && npm run generate:exports && npm run generate:lock",
|
|
462
|
+
"generate": "npm run generate:sources && npm run generate:exports && npm run generate:lock && npm run generate:issue-codes",
|
|
463
463
|
"generate:sources": "npm run generate:manifest && npm run generate:barrel && npm run generate:slot-catalog",
|
|
464
464
|
"generate:slot-catalog": "npx ts-node --project scripts/tsconfig.json scripts/generate-slot-catalog.ts",
|
|
465
465
|
"generate:manifest": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-manifest.ts",
|
|
466
466
|
"generate:barrel": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-barrel.ts",
|
|
467
467
|
"generate:exports": "npx ts-node --project scripts/tsconfig.json scripts/generate-package-exports.ts",
|
|
468
468
|
"generate:lock": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-catalog.ts",
|
|
469
|
+
"generate:issue-codes": "npx ts-node --project scripts/tsconfig.json scripts/generate-issue-code-catalog.ts",
|
|
469
470
|
"check:exports": "npx ts-node --project scripts/tsconfig.json scripts/check-exports.ts --mode=exact",
|
|
470
471
|
"check:catalog-lock": "npx ts-node --project scripts/tsconfig.json scripts/check-catalog-lock.ts",
|
|
472
|
+
"check:issue-code-lock": "npx ts-node --project scripts/tsconfig.json scripts/check-issue-code-lock.ts",
|
|
471
473
|
"check:plugin-isolation": "npx ts-node --project scripts/tsconfig.json scripts/check-plugin-isolation.ts",
|
|
472
474
|
"check:plugin-uniqueness": "npx ts-node --project scripts/tsconfig.json scripts/check-plugin-uniqueness.ts",
|
|
473
|
-
"check:catalog": "npm run check:plugin-uniqueness && npm run check:plugin-isolation && npm run check:exports && npm run check:catalog-lock",
|
|
475
|
+
"check:catalog": "npm run check:plugin-uniqueness && npm run check:plugin-isolation && npm run check:exports && npm run check:catalog-lock && npm run check:issue-code-lock",
|
|
474
476
|
"check:size": "npx ts-node --project scripts/tsconfig.json scripts/measure-bundle-size.ts",
|
|
475
477
|
"check:barrel-equivalence": "npx ts-node --project scripts/tsconfig.json scripts/check-barrel-equivalence.ts",
|
|
476
478
|
"check:no-dynamic-code": "npx ts-node --project scripts/tsconfig.json scripts/check-no-dynamic-code.ts",
|
|
@@ -493,6 +495,7 @@
|
|
|
493
495
|
"check:conformance-figures": "npx ts-node --project scripts/tsconfig.json scripts/check-conformance-figures.ts",
|
|
494
496
|
"bench:competitors": "npx ts-node --project bench/tsconfig.json bench/competitors/report-competitors.ts",
|
|
495
497
|
"bench:competitors:check": "npx ts-node --project bench/tsconfig.json bench/competitors/check-competitors.ts",
|
|
498
|
+
"bench:megamorphism": "npx ts-node --project bench/tsconfig.json bench/megamorphism/report-megamorphism.ts",
|
|
496
499
|
"lint:ox": "oxlint -c .oxlintrc.json src bench",
|
|
497
500
|
"lint:naming": "eslint \"src/**/*.ts\" \"bench/**/*.ts\"",
|
|
498
501
|
"generate:perf-figures": "npx ts-node --project scripts/tsconfig.json scripts/check-perf-figures.ts --write",
|
|
@@ -538,9 +541,9 @@
|
|
|
538
541
|
"ts-node": "^10.9.2",
|
|
539
542
|
"tsx": "^4.20.3",
|
|
540
543
|
"typescript": "^5.8.3",
|
|
541
|
-
"valibot": "^1.
|
|
544
|
+
"valibot": "^1.5.0",
|
|
542
545
|
"yup": "^1.7.1",
|
|
543
|
-
"zod": "^4.
|
|
546
|
+
"zod": "^4.6.2"
|
|
544
547
|
},
|
|
545
548
|
"sideEffects": false
|
|
546
549
|
}
|