@loomcli/validators 0.5.0 → 0.7.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/dist/bounds.d.ts +5 -3
- package/dist/bounds.js +22 -12
- package/dist/codes.d.ts +78 -0
- package/dist/codes.js +134 -0
- package/dist/create.d.ts +3 -1
- package/dist/create.js +35 -13
- package/dist/date.js +3 -1
- package/dist/faults.d.ts +18 -3
- package/dist/faults.js +17 -5
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/integer.js +4 -3
- package/dist/issue-code.d.ts +24 -0
- package/dist/issue-code.js +183 -0
- package/dist/issues.d.ts +2 -2
- package/dist/issues.js +3 -3
- package/dist/number.js +5 -4
- package/dist/one-of.js +46 -14
- package/dist/params.d.ts +54 -0
- package/dist/params.js +68 -0
- package/dist/path.js +25 -22
- package/dist/port.js +3 -3
- package/dist/rules.d.ts +32 -0
- package/dist/rules.js +86 -0
- package/dist/text.d.ts +12 -4
- package/dist/text.js +71 -32
- package/dist/url.js +23 -12
- package/dist/uuid.js +3 -3
- package/package.json +2 -2
package/dist/bounds.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@loomcli/core';
|
|
2
|
+
import type { BoundCodes } from './codes.js';
|
|
1
3
|
/** Inclusive bounds on a numeric factory, either or both absent. */
|
|
2
4
|
interface Bounds {
|
|
3
5
|
min: number | undefined;
|
|
@@ -15,13 +17,13 @@ declare function readBounds(options: unknown, rule: BoundRule): Bounds;
|
|
|
15
17
|
declare function withinBounds(value: number, { max, min }: Bounds): boolean;
|
|
16
18
|
/** The number a numeric token outputs, with `-0` read as `0`. */
|
|
17
19
|
declare function withoutNegativeZero(value: number): number;
|
|
18
|
-
/** The one
|
|
19
|
-
declare function
|
|
20
|
+
/** The one issue for a numeric configuration, under the code for the bounds it declares. */
|
|
21
|
+
declare function boundsIssue(codes: BoundCodes, { max, min }: Bounds): StandardSchemaV1.Issue;
|
|
20
22
|
/** The published schema for a numeric type, with each declared bound. */
|
|
21
23
|
declare function boundsSchema(type: 'integer' | 'number', { max, min }: Bounds): {
|
|
22
24
|
type: "integer" | "number";
|
|
23
25
|
minimum?: number;
|
|
24
26
|
maximum?: number;
|
|
25
27
|
};
|
|
26
|
-
export {
|
|
28
|
+
export { boundsIssue, boundsSchema, readBounds, withinBounds, withoutNegativeZero };
|
|
27
29
|
export type { Bounds };
|
package/dist/bounds.js
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { fault, readOptions } from './faults.js';
|
|
2
|
+
import { boundsOrder, boundValue } from './rules.js';
|
|
2
3
|
const zero = 0;
|
|
3
|
-
|
|
4
|
+
/** One bound a numeric factory declares, checked against its rule; a fault marks the bound. */
|
|
5
|
+
function boundOf(bound, name, value) {
|
|
6
|
+
const { call, rule } = bound;
|
|
4
7
|
if (value === undefined) {
|
|
5
8
|
return undefined;
|
|
6
9
|
}
|
|
7
10
|
if (typeof value !== 'number' || !rule.accepts(value)) {
|
|
8
|
-
throw fault(
|
|
11
|
+
throw fault(boundValue, { ...call, mark: `0.${name}` }, {
|
|
12
|
+
correction: rule.correction,
|
|
13
|
+
sentence: `${rule.factory}() ${name} is not ${rule.requirement}.`,
|
|
14
|
+
});
|
|
9
15
|
}
|
|
10
16
|
return value;
|
|
11
17
|
}
|
|
12
18
|
/** Reads a numeric factory's `min` and `max`, and faults on a bad bound or `min` above `max`. */
|
|
13
19
|
function readBounds(options, rule) {
|
|
14
20
|
const declared = readOptions(rule.factory, options);
|
|
15
|
-
const
|
|
16
|
-
const
|
|
21
|
+
const call = { arguments: [options], factory: rule.factory };
|
|
22
|
+
const min = boundOf({ call, rule }, 'min', declared.min);
|
|
23
|
+
const max = boundOf({ call, rule }, 'max', declared.max);
|
|
17
24
|
if (min !== undefined && max !== undefined && min > max) {
|
|
18
|
-
throw fault(
|
|
25
|
+
throw fault(boundsOrder, { ...call, mark: '0.min' }, {
|
|
26
|
+
correction: 'Supply a min at or below max.',
|
|
27
|
+
sentence: `${rule.factory}() min ${String(min)} is above max ${String(max)}.`,
|
|
28
|
+
});
|
|
19
29
|
}
|
|
20
30
|
return { max, min };
|
|
21
31
|
}
|
|
@@ -26,18 +36,18 @@ function withinBounds(value, { max, min }) {
|
|
|
26
36
|
function withoutNegativeZero(value) {
|
|
27
37
|
return value === zero ? zero : value;
|
|
28
38
|
}
|
|
29
|
-
/** The one
|
|
30
|
-
function
|
|
39
|
+
/** The one issue for a numeric configuration, under the code for the bounds it declares. */
|
|
40
|
+
function boundsIssue(codes, { max, min }) {
|
|
31
41
|
if (min !== undefined && max !== undefined) {
|
|
32
|
-
return
|
|
42
|
+
return codes.range.issue({ max, min });
|
|
33
43
|
}
|
|
34
44
|
if (min !== undefined) {
|
|
35
|
-
return
|
|
45
|
+
return codes.min.issue({ min });
|
|
36
46
|
}
|
|
37
47
|
if (max !== undefined) {
|
|
38
|
-
return
|
|
48
|
+
return codes.max.issue({ max });
|
|
39
49
|
}
|
|
40
|
-
return
|
|
50
|
+
return codes.unbounded.issue({});
|
|
41
51
|
}
|
|
42
52
|
/** The published schema for a numeric type, with each declared bound. */
|
|
43
53
|
function boundsSchema(type, { max, min }) {
|
|
@@ -47,4 +57,4 @@ function boundsSchema(type, { max, min }) {
|
|
|
47
57
|
...(max === undefined ? {} : { maximum: max }),
|
|
48
58
|
};
|
|
49
59
|
}
|
|
50
|
-
export {
|
|
60
|
+
export { boundsIssue, boundsSchema, readBounds, withinBounds, withoutNegativeZero };
|
package/dist/codes.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { IssueCode } from './issue-code.js';
|
|
2
|
+
import type { NoParams } from './params.js';
|
|
3
|
+
import type { PathKind } from './probe.js';
|
|
4
|
+
declare const textNonemptyIssue: IssueCode<Readonly<Record<string, never>>>;
|
|
5
|
+
declare const textMinLengthIssue: IssueCode<{
|
|
6
|
+
min: number;
|
|
7
|
+
}>;
|
|
8
|
+
declare const textMaxLengthIssue: IssueCode<{
|
|
9
|
+
max: number;
|
|
10
|
+
}>;
|
|
11
|
+
declare const textExactLengthIssue: IssueCode<{
|
|
12
|
+
length: number;
|
|
13
|
+
}>;
|
|
14
|
+
declare const textLengthRangeIssue: IssueCode<{
|
|
15
|
+
max: number;
|
|
16
|
+
min: number;
|
|
17
|
+
}>;
|
|
18
|
+
/** The author's own sentence for a `text()` pattern, which is the code's one blank. */
|
|
19
|
+
declare const textPatternIssue: IssueCode<{
|
|
20
|
+
message: string;
|
|
21
|
+
}>;
|
|
22
|
+
declare const integerIssue: IssueCode<Readonly<Record<string, never>>>;
|
|
23
|
+
declare const integerRangeIssue: IssueCode<{
|
|
24
|
+
max: number;
|
|
25
|
+
min: number;
|
|
26
|
+
}>;
|
|
27
|
+
declare const integerMinIssue: IssueCode<{
|
|
28
|
+
min: number;
|
|
29
|
+
}>;
|
|
30
|
+
declare const integerMaxIssue: IssueCode<{
|
|
31
|
+
max: number;
|
|
32
|
+
}>;
|
|
33
|
+
declare const numberIssue: IssueCode<Readonly<Record<string, never>>>;
|
|
34
|
+
declare const numberRangeIssue: IssueCode<{
|
|
35
|
+
max: number;
|
|
36
|
+
min: number;
|
|
37
|
+
}>;
|
|
38
|
+
declare const numberMinIssue: IssueCode<{
|
|
39
|
+
min: number;
|
|
40
|
+
}>;
|
|
41
|
+
declare const numberMaxIssue: IssueCode<{
|
|
42
|
+
max: number;
|
|
43
|
+
}>;
|
|
44
|
+
declare const portIssue: IssueCode<Readonly<Record<string, never>>>;
|
|
45
|
+
declare const oneOfIssue: IssueCode<{
|
|
46
|
+
values: readonly string[];
|
|
47
|
+
}>;
|
|
48
|
+
declare const urlIssue: IssueCode<Readonly<Record<string, never>>>;
|
|
49
|
+
declare const urlSchemeIssue: IssueCode<{
|
|
50
|
+
protocols: readonly string[];
|
|
51
|
+
}>;
|
|
52
|
+
declare const uuidIssue: IssueCode<Readonly<Record<string, never>>>;
|
|
53
|
+
declare const dateIssue: IssueCode<Readonly<Record<string, never>>>;
|
|
54
|
+
declare const pathIssue: IssueCode<Readonly<Record<string, never>>>;
|
|
55
|
+
declare const pathReadableIssue: IssueCode<{
|
|
56
|
+
kind: PathKind;
|
|
57
|
+
}>;
|
|
58
|
+
declare const pathWritableIssue: IssueCode<{
|
|
59
|
+
kind: PathKind;
|
|
60
|
+
}>;
|
|
61
|
+
/** The four codes a numeric factory rejects with, one for each way its bounds are declared. */
|
|
62
|
+
interface BoundCodes {
|
|
63
|
+
unbounded: IssueCode<NoParams>;
|
|
64
|
+
range: IssueCode<{
|
|
65
|
+
max: number;
|
|
66
|
+
min: number;
|
|
67
|
+
}>;
|
|
68
|
+
min: IssueCode<{
|
|
69
|
+
min: number;
|
|
70
|
+
}>;
|
|
71
|
+
max: IssueCode<{
|
|
72
|
+
max: number;
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
declare const integerCodes: BoundCodes;
|
|
76
|
+
declare const numberCodes: BoundCodes;
|
|
77
|
+
export { dateIssue, integerCodes, integerIssue, integerMaxIssue, integerMinIssue, integerRangeIssue, numberCodes, numberIssue, numberMaxIssue, numberMinIssue, numberRangeIssue, oneOfIssue, pathIssue, pathReadableIssue, pathWritableIssue, portIssue, textExactLengthIssue, textLengthRangeIssue, textMaxLengthIssue, textMinLengthIssue, textNonemptyIssue, textPatternIssue, urlIssue, urlSchemeIssue, uuidIssue, };
|
|
78
|
+
export type { BoundCodes };
|
package/dist/codes.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { issueCode } from './issue-code.js';
|
|
2
|
+
import { listing } from './issues.js';
|
|
3
|
+
import { isCount, isFiniteNumber, isSafeInteger, kindParams, lengthParams, maxParams, messageParams, minParams, noParams, protocolsParams, rangeParams, valuesParams, } from './params.js';
|
|
4
|
+
/**
|
|
5
|
+
* The catalog's issue codes, one for each sentence a catalog validator prints. Each code's
|
|
6
|
+
* parameters are that sentence's blanks and hold the rule's settings, never the rejected value.
|
|
7
|
+
*/
|
|
8
|
+
const oneCharacter = 1;
|
|
9
|
+
/** A count of characters, reading `character` for a count of 1. */
|
|
10
|
+
function characters(count) {
|
|
11
|
+
return count === oneCharacter ? '1 character' : `${String(count)} characters`;
|
|
12
|
+
}
|
|
13
|
+
/** The catalog's own code for a rule: the package name, `/`, and the rule name. */
|
|
14
|
+
function catalogCode(rule) {
|
|
15
|
+
return `@loomcli/validators/${rule}`;
|
|
16
|
+
}
|
|
17
|
+
const textNonemptyIssue = issueCode(catalogCode('text-nonempty'), {
|
|
18
|
+
message: () => 'Expected a nonempty value.',
|
|
19
|
+
schema: noParams,
|
|
20
|
+
});
|
|
21
|
+
const textMinLengthIssue = issueCode(catalogCode('text-min-length'), {
|
|
22
|
+
message: ({ min }) => `Expected at least ${characters(min)}.`,
|
|
23
|
+
schema: minParams(isCount),
|
|
24
|
+
});
|
|
25
|
+
const textMaxLengthIssue = issueCode(catalogCode('text-max-length'), {
|
|
26
|
+
message: ({ max }) => `Expected at most ${characters(max)}.`,
|
|
27
|
+
schema: maxParams(isCount),
|
|
28
|
+
});
|
|
29
|
+
const textExactLengthIssue = issueCode(catalogCode('text-exact-length'), {
|
|
30
|
+
message: ({ length }) => `Expected exactly ${characters(length)}.`,
|
|
31
|
+
schema: lengthParams,
|
|
32
|
+
});
|
|
33
|
+
const textLengthRangeIssue = issueCode(catalogCode('text-length-range'), {
|
|
34
|
+
message: ({ max, min }) => `Expected from ${String(min)} through ${characters(max)}.`,
|
|
35
|
+
schema: rangeParams(isCount),
|
|
36
|
+
});
|
|
37
|
+
/** The author's own sentence for a `text()` pattern, which is the code's one blank. */
|
|
38
|
+
const textPatternIssue = issueCode(catalogCode('text-pattern'), {
|
|
39
|
+
message: ({ message }) => message,
|
|
40
|
+
schema: messageParams,
|
|
41
|
+
});
|
|
42
|
+
const integerIssue = issueCode(catalogCode('integer'), {
|
|
43
|
+
message: () => 'Expected a whole number.',
|
|
44
|
+
schema: noParams,
|
|
45
|
+
});
|
|
46
|
+
const integerRangeIssue = issueCode(catalogCode('integer-range'), {
|
|
47
|
+
message: ({ max, min }) => `Expected a whole number from ${String(min)} through ${String(max)}.`,
|
|
48
|
+
schema: rangeParams(isSafeInteger),
|
|
49
|
+
});
|
|
50
|
+
const integerMinIssue = issueCode(catalogCode('integer-min'), {
|
|
51
|
+
message: ({ min }) => `Expected a whole number of at least ${String(min)}.`,
|
|
52
|
+
schema: minParams(isSafeInteger),
|
|
53
|
+
});
|
|
54
|
+
const integerMaxIssue = issueCode(catalogCode('integer-max'), {
|
|
55
|
+
message: ({ max }) => `Expected a whole number of at most ${String(max)}.`,
|
|
56
|
+
schema: maxParams(isSafeInteger),
|
|
57
|
+
});
|
|
58
|
+
const numberIssue = issueCode(catalogCode('number'), {
|
|
59
|
+
message: () => 'Expected a number.',
|
|
60
|
+
schema: noParams,
|
|
61
|
+
});
|
|
62
|
+
const numberRangeIssue = issueCode(catalogCode('number-range'), {
|
|
63
|
+
message: ({ max, min }) => `Expected a number from ${String(min)} through ${String(max)}.`,
|
|
64
|
+
schema: rangeParams(isFiniteNumber),
|
|
65
|
+
});
|
|
66
|
+
const numberMinIssue = issueCode(catalogCode('number-min'), {
|
|
67
|
+
message: ({ min }) => `Expected a number of at least ${String(min)}.`,
|
|
68
|
+
schema: minParams(isFiniteNumber),
|
|
69
|
+
});
|
|
70
|
+
const numberMaxIssue = issueCode(catalogCode('number-max'), {
|
|
71
|
+
message: ({ max }) => `Expected a number of at most ${String(max)}.`,
|
|
72
|
+
schema: maxParams(isFiniteNumber),
|
|
73
|
+
});
|
|
74
|
+
const portIssue = issueCode(catalogCode('port'), {
|
|
75
|
+
message: () => 'Expected a port number from 1 through 65535.',
|
|
76
|
+
schema: noParams,
|
|
77
|
+
});
|
|
78
|
+
const oneOfIssue = issueCode(catalogCode('one-of'), {
|
|
79
|
+
message: ({ values }) => `Expected one of: ${values.join(', ')}.`,
|
|
80
|
+
schema: valuesParams,
|
|
81
|
+
});
|
|
82
|
+
const urlIssue = issueCode(catalogCode('url'), {
|
|
83
|
+
message: () => 'Expected an absolute URL, such as https://example.com.',
|
|
84
|
+
schema: noParams,
|
|
85
|
+
});
|
|
86
|
+
const urlSchemeIssue = issueCode(catalogCode('url-scheme'), {
|
|
87
|
+
message: ({ protocols }) => `Expected an absolute URL with the scheme ${listing(protocols)}.`,
|
|
88
|
+
schema: protocolsParams,
|
|
89
|
+
});
|
|
90
|
+
const uuidIssue = issueCode(catalogCode('uuid'), {
|
|
91
|
+
message: () => 'Expected a UUID, such as 123e4567-e89b-12d3-a456-426614174000.',
|
|
92
|
+
schema: noParams,
|
|
93
|
+
});
|
|
94
|
+
const dateIssue = issueCode(catalogCode('date'), {
|
|
95
|
+
message: () => 'Expected a date as YYYY-MM-DD, such as 2026-09-25.',
|
|
96
|
+
schema: noParams,
|
|
97
|
+
});
|
|
98
|
+
const pathIssue = issueCode(catalogCode('path'), {
|
|
99
|
+
message: () => 'Expected a nonempty path with no NUL character.',
|
|
100
|
+
schema: noParams,
|
|
101
|
+
});
|
|
102
|
+
/** How each kind reads in a readable-path sentence. */
|
|
103
|
+
const readableKinds = {
|
|
104
|
+
any: 'file or directory',
|
|
105
|
+
directory: 'directory',
|
|
106
|
+
file: 'file',
|
|
107
|
+
};
|
|
108
|
+
/** How each kind reads in a writable-path sentence. */
|
|
109
|
+
const writableKinds = {
|
|
110
|
+
any: 'path',
|
|
111
|
+
directory: 'directory',
|
|
112
|
+
file: 'file',
|
|
113
|
+
};
|
|
114
|
+
const pathReadableIssue = issueCode(catalogCode('path-readable'), {
|
|
115
|
+
message: ({ kind }) => `Expected a readable ${readableKinds[kind]} that exists.`,
|
|
116
|
+
schema: kindParams,
|
|
117
|
+
});
|
|
118
|
+
const pathWritableIssue = issueCode(catalogCode('path-writable'), {
|
|
119
|
+
message: ({ kind }) => `Expected a writable ${writableKinds[kind]}, or a new ${writableKinds[kind]} in a writable directory.`,
|
|
120
|
+
schema: kindParams,
|
|
121
|
+
});
|
|
122
|
+
const integerCodes = {
|
|
123
|
+
max: integerMaxIssue,
|
|
124
|
+
min: integerMinIssue,
|
|
125
|
+
range: integerRangeIssue,
|
|
126
|
+
unbounded: integerIssue,
|
|
127
|
+
};
|
|
128
|
+
const numberCodes = {
|
|
129
|
+
max: numberMaxIssue,
|
|
130
|
+
min: numberMinIssue,
|
|
131
|
+
range: numberRangeIssue,
|
|
132
|
+
unbounded: numberIssue,
|
|
133
|
+
};
|
|
134
|
+
export { dateIssue, integerCodes, integerIssue, integerMaxIssue, integerMinIssue, integerRangeIssue, numberCodes, numberIssue, numberMaxIssue, numberMinIssue, numberRangeIssue, oneOfIssue, pathIssue, pathReadableIssue, pathWritableIssue, portIssue, textExactLengthIssue, textLengthRangeIssue, textMaxLengthIssue, textMinLengthIssue, textNonemptyIssue, textPatternIssue, urlIssue, urlSchemeIssue, uuidIssue, };
|
package/dist/create.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ interface ValidatorDefinition<Output> {
|
|
|
8
8
|
parse: (raw: string, context: ValidationContext) => ParseResult<Output> | Promise<ParseResult<Output>>;
|
|
9
9
|
inputSchema?: Readonly<Record<string, unknown>>;
|
|
10
10
|
}
|
|
11
|
+
/** The vendor every Standard Schema value this package builds reports. */
|
|
12
|
+
declare const vendor = "@loomcli/validators";
|
|
11
13
|
/**
|
|
12
14
|
* Builds a frozen Standard Schema value from the author's parse function.
|
|
13
15
|
* With an input schema it also publishes that schema through Standard JSON Schema.
|
|
@@ -16,5 +18,5 @@ declare function createValidator<Output>(definition: ValidatorDefinition<Output>
|
|
|
16
18
|
inputSchema: Readonly<Record<string, unknown>>;
|
|
17
19
|
}): Validator<Output>;
|
|
18
20
|
declare function createValidator<Output>(definition: ValidatorDefinition<Output>): StandardSchemaV1<string, Output>;
|
|
19
|
-
export { createValidator };
|
|
21
|
+
export { createValidator, vendor };
|
|
20
22
|
export type { ParseResult, Validator, ValidatorDefinition };
|
package/dist/create.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { DeclarationError, validationContext } from '@loomcli/core';
|
|
2
2
|
import { copyRecord, isPlainObject } from './data.js';
|
|
3
3
|
import { fault } from './faults.js';
|
|
4
|
+
import { contextOutsideRun, inputSchema, validatorDefinition } from './rules.js';
|
|
5
|
+
/** The vendor every Standard Schema value this package builds reports. */
|
|
4
6
|
const vendor = '@loomcli/validators';
|
|
5
7
|
const target = 'draft-2020-12';
|
|
6
8
|
const dialect = 'https://json-schema.org/draft/2020-12/schema';
|
|
9
|
+
/** A read of the context outside a run, which no declaration call stands for. */
|
|
7
10
|
function unavailable() {
|
|
8
|
-
throw new DeclarationError(
|
|
11
|
+
throw new DeclarationError(contextOutsideRun, {
|
|
12
|
+
correction: 'Call the validator through an Application run, or leave the context unread.',
|
|
13
|
+
sentence: 'This validator reads the validation context, which only exists during a Loom run.',
|
|
14
|
+
});
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* The context `parse` receives when core did not make the call.
|
|
@@ -50,26 +56,42 @@ function converter(schema) {
|
|
|
50
56
|
* undefined when it declares none.
|
|
51
57
|
*/
|
|
52
58
|
function checkDefinition(definition) {
|
|
59
|
+
const at = (mark) => ({ arguments: [definition], factory: 'createValidator', mark });
|
|
53
60
|
if (!isPlainObject(definition)) {
|
|
54
|
-
throw fault('
|
|
61
|
+
throw fault(validatorDefinition, at('0'), {
|
|
62
|
+
correction: 'Supply an object with a parse function.',
|
|
63
|
+
sentence: 'createValidator() definition is not a plain object.',
|
|
64
|
+
});
|
|
55
65
|
}
|
|
56
66
|
if (typeof definition.parse !== 'function') {
|
|
57
|
-
throw fault('
|
|
67
|
+
throw fault(validatorDefinition, at('parse' in definition ? '0.parse' : '0'), {
|
|
68
|
+
correction: 'Supply a function that validates one raw string.',
|
|
69
|
+
sentence: 'createValidator() parse is not a function.',
|
|
70
|
+
});
|
|
58
71
|
}
|
|
59
|
-
return declaredSchema(definition.inputSchema);
|
|
72
|
+
return declaredSchema(definition.inputSchema, at);
|
|
60
73
|
}
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The declared input schema, checked, copied, and frozen, or undefined when none is declared. `at`
|
|
76
|
+
* rebuilds the `createValidator()` call, marking the part at fault.
|
|
77
|
+
*/
|
|
78
|
+
function declaredSchema(declared, at) {
|
|
79
|
+
if (declared === undefined) {
|
|
64
80
|
return undefined;
|
|
65
81
|
}
|
|
66
|
-
if (!isPlainObject(
|
|
67
|
-
throw fault(
|
|
82
|
+
if (!isPlainObject(declared)) {
|
|
83
|
+
throw fault(inputSchema, at('0.inputSchema'), {
|
|
84
|
+
correction: 'Supply the JSON Schema as a plain object.',
|
|
85
|
+
sentence: 'createValidator() inputSchema is not a plain object.',
|
|
86
|
+
});
|
|
68
87
|
}
|
|
69
|
-
if (Object.hasOwn(
|
|
70
|
-
throw fault(
|
|
88
|
+
if (Object.hasOwn(declared, '$schema')) {
|
|
89
|
+
throw fault(inputSchema, at('0.inputSchema.$schema'), {
|
|
90
|
+
correction: 'Leave out $schema, which the validator publishes itself.',
|
|
91
|
+
sentence: 'createValidator() inputSchema declares its own $schema.',
|
|
92
|
+
});
|
|
71
93
|
}
|
|
72
|
-
return copyRecord(
|
|
94
|
+
return copyRecord(declared, true);
|
|
73
95
|
}
|
|
74
96
|
function createValidator(definition) {
|
|
75
97
|
const schema = checkDefinition(definition);
|
|
@@ -89,4 +111,4 @@ function createValidator(definition) {
|
|
|
89
111
|
}
|
|
90
112
|
return Object.freeze({ '~standard': Object.freeze({ ...props, jsonSchema: converter(schema) }) });
|
|
91
113
|
}
|
|
92
|
-
export { createValidator };
|
|
114
|
+
export { createValidator, vendor };
|
package/dist/date.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dateIssue } from './codes.js';
|
|
1
2
|
import { createValidator } from './create.js';
|
|
2
3
|
import { reject } from './issues.js';
|
|
3
4
|
const layout = /^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})$/u;
|
|
@@ -17,13 +18,14 @@ function isRealDay(year, month, day) {
|
|
|
17
18
|
}
|
|
18
19
|
/** A calendar date as `YYYY-MM-DD`, kept as the token because a date has no time or zone. */
|
|
19
20
|
function date() {
|
|
21
|
+
const issue = dateIssue.issue({});
|
|
20
22
|
return createValidator({
|
|
21
23
|
inputSchema: { type: 'string', format: 'date' },
|
|
22
24
|
parse: (raw) => {
|
|
23
25
|
const parts = layout.exec(raw)?.groups;
|
|
24
26
|
const real = parts !== undefined &&
|
|
25
27
|
isRealDay(Number(parts.year), Number(parts.month), Number(parts.day));
|
|
26
|
-
return real ? { value: raw } : reject(
|
|
28
|
+
return real ? { value: raw } : reject(issue);
|
|
27
29
|
},
|
|
28
30
|
});
|
|
29
31
|
}
|
package/dist/faults.d.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { DeclarationError } from '@loomcli/core';
|
|
2
|
+
import type { DiagnosticRule } from '@loomcli/core';
|
|
3
|
+
/** One factory call as a finding rebuilds it: the factory's name and the arguments it received. */
|
|
4
|
+
interface FactoryCall {
|
|
5
|
+
readonly factory: string;
|
|
6
|
+
readonly arguments: readonly unknown[];
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
|
-
* A factory argument that can never work.
|
|
4
|
-
*
|
|
9
|
+
* A factory argument that can never work, under the rule it breaks. The finding rebuilds the call
|
|
10
|
+
* and marks the argument or the key at fault, `mark` being its dotted path among the arguments,
|
|
11
|
+
* with a note when given. The sentence names the factory and the argument and states the problem,
|
|
12
|
+
* and the correction states the fix.
|
|
5
13
|
*/
|
|
6
|
-
declare function fault(
|
|
14
|
+
declare function fault(rule: DiagnosticRule, at: FactoryCall & {
|
|
15
|
+
readonly mark: string;
|
|
16
|
+
readonly note?: string;
|
|
17
|
+
}, parts: {
|
|
18
|
+
readonly sentence: string;
|
|
19
|
+
readonly correction: string;
|
|
20
|
+
}): DeclarationError;
|
|
7
21
|
/** A declared value as a fault message quotes it: a string in quotes, a primitive as printed. */
|
|
8
22
|
declare function quote(value: unknown): string;
|
|
9
23
|
/**
|
|
@@ -12,3 +26,4 @@ declare function quote(value: unknown): string;
|
|
|
12
26
|
*/
|
|
13
27
|
declare function readOptions(factory: string, options: unknown): Readonly<Record<string, unknown>>;
|
|
14
28
|
export { fault, quote, readOptions };
|
|
29
|
+
export type { FactoryCall };
|
package/dist/faults.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { DeclarationError } from '@loomcli/core';
|
|
2
2
|
import { isPlainObject } from './data.js';
|
|
3
|
+
import { factoryOptions } from './rules.js';
|
|
3
4
|
/**
|
|
4
|
-
* A factory argument that can never work.
|
|
5
|
-
*
|
|
5
|
+
* A factory argument that can never work, under the rule it breaks. The finding rebuilds the call
|
|
6
|
+
* and marks the argument or the key at fault, `mark` being its dotted path among the arguments,
|
|
7
|
+
* with a note when given. The sentence names the factory and the argument and states the problem,
|
|
8
|
+
* and the correction states the fix.
|
|
6
9
|
*/
|
|
7
|
-
function fault(
|
|
8
|
-
|
|
10
|
+
function fault(rule, at, parts) {
|
|
11
|
+
const { mark, note } = at;
|
|
12
|
+
const finding = { arguments: at.arguments, call: at.factory, mark };
|
|
13
|
+
return new DeclarationError(rule, {
|
|
14
|
+
correction: parts.correction,
|
|
15
|
+
findings: [note === undefined ? finding : { ...finding, note }],
|
|
16
|
+
sentence: parts.sentence,
|
|
17
|
+
});
|
|
9
18
|
}
|
|
10
19
|
/** A declared value as a fault message quotes it: a string in quotes, a primitive as printed. */
|
|
11
20
|
function quote(value) {
|
|
@@ -30,7 +39,10 @@ function readOptions(factory, options) {
|
|
|
30
39
|
return {};
|
|
31
40
|
}
|
|
32
41
|
if (!isPlainObject(options)) {
|
|
33
|
-
throw fault(
|
|
42
|
+
throw fault(factoryOptions, { arguments: [options], factory, mark: '0' }, {
|
|
43
|
+
correction: 'Supply an object or leave it out.',
|
|
44
|
+
sentence: `${factory}() options is not a plain object.`,
|
|
45
|
+
});
|
|
34
46
|
}
|
|
35
47
|
return options;
|
|
36
48
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export { dateIssue, integerIssue, integerMaxIssue, integerMinIssue, integerRangeIssue, numberIssue, numberMaxIssue, numberMinIssue, numberRangeIssue, oneOfIssue, pathIssue, pathReadableIssue, pathWritableIssue, portIssue, textExactLengthIssue, textLengthRangeIssue, textMaxLengthIssue, textMinLengthIssue, textNonemptyIssue, textPatternIssue, urlIssue, urlSchemeIssue, uuidIssue, } from './codes.js';
|
|
1
2
|
export { createValidator } from './create.js';
|
|
2
3
|
export { date } from './date.js';
|
|
3
4
|
export { integer } from './integer.js';
|
|
5
|
+
export { issueCode } from './issue-code.js';
|
|
4
6
|
export { number } from './number.js';
|
|
5
7
|
export { oneOf } from './one-of.js';
|
|
6
8
|
export { path } from './path.js';
|
|
@@ -9,3 +11,4 @@ export { text } from './text.js';
|
|
|
9
11
|
export { url } from './url.js';
|
|
10
12
|
export { uuid } from './uuid.js';
|
|
11
13
|
export type { ParseResult, Validator } from './create.js';
|
|
14
|
+
export type { IssueCode } from './issue-code.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export { dateIssue, integerIssue, integerMaxIssue, integerMinIssue, integerRangeIssue, numberIssue, numberMaxIssue, numberMinIssue, numberRangeIssue, oneOfIssue, pathIssue, pathReadableIssue, pathWritableIssue, portIssue, textExactLengthIssue, textLengthRangeIssue, textMaxLengthIssue, textMinLengthIssue, textNonemptyIssue, textPatternIssue, urlIssue, urlSchemeIssue, uuidIssue, } from './codes.js';
|
|
1
2
|
export { createValidator } from './create.js';
|
|
2
3
|
export { date } from './date.js';
|
|
3
4
|
export { integer } from './integer.js';
|
|
5
|
+
export { issueCode } from './issue-code.js';
|
|
4
6
|
export { number } from './number.js';
|
|
5
7
|
export { oneOf } from './one-of.js';
|
|
6
8
|
export { path } from './path.js';
|
package/dist/integer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boundsIssue, boundsSchema, readBounds, withinBounds, withoutNegativeZero, } from './bounds.js';
|
|
2
|
+
import { integerCodes } from './codes.js';
|
|
2
3
|
import { createValidator } from './create.js';
|
|
3
4
|
import { reject } from './issues.js';
|
|
4
5
|
const digits = /^-?[0-9]+$/u;
|
|
@@ -18,12 +19,12 @@ function integer(options) {
|
|
|
18
19
|
factory: 'integer',
|
|
19
20
|
requirement: 'a safe integer',
|
|
20
21
|
});
|
|
21
|
-
const
|
|
22
|
+
const issue = boundsIssue(integerCodes, bounds);
|
|
22
23
|
return createValidator({
|
|
23
24
|
inputSchema: boundsSchema('integer', bounds),
|
|
24
25
|
parse: (raw) => {
|
|
25
26
|
const value = readInteger(raw);
|
|
26
|
-
return value !== undefined && withinBounds(value, bounds) ? { value } : reject(
|
|
27
|
+
return value !== undefined && withinBounds(value, bounds) ? { value } : reject(issue);
|
|
27
28
|
},
|
|
28
29
|
});
|
|
29
30
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@loomcli/core';
|
|
2
|
+
/**
|
|
3
|
+
* One issue code a validator package declares: the code string, the schema its parameters pass,
|
|
4
|
+
* a builder for the issue a `parse` function returns, and a typed read of that issue.
|
|
5
|
+
*/
|
|
6
|
+
interface IssueCode<Params> {
|
|
7
|
+
readonly code: string;
|
|
8
|
+
readonly schema: StandardSchemaV1<Params>;
|
|
9
|
+
issue(this: void, params: Params): StandardSchemaV1.Issue;
|
|
10
|
+
read(this: void, issue: StandardSchemaV1.Issue): Params | undefined;
|
|
11
|
+
}
|
|
12
|
+
/** What `issueCode` declares beside the code: the parameter schema and the one sentence. */
|
|
13
|
+
interface IssueCodeConfig<Params> {
|
|
14
|
+
schema: StandardSchemaV1<Params>;
|
|
15
|
+
message: (params: Params) => string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Declares one issue code with its parameter schema and its one sentence, and returns a frozen
|
|
19
|
+
* descriptor. `issue` builds the rejection a `parse` function returns, and `read` recovers the
|
|
20
|
+
* typed parameters from an issue carrying the code.
|
|
21
|
+
*/
|
|
22
|
+
declare function issueCode<Params>(code: string, config: IssueCodeConfig<Params>): IssueCode<Params>;
|
|
23
|
+
export { issueCode };
|
|
24
|
+
export type { IssueCode, IssueCodeConfig };
|