@raoh/core 0.9.0-dev.15.20261004151401.ge5893e0cc07d

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.
@@ -0,0 +1,153 @@
1
+ import { Chain, Decoder, type Run } from "./decoder.ts";
2
+ import { type Result } from "./issue.ts";
3
+ import type { Path } from "./path.ts";
4
+ import { ValueSet } from "./set.ts";
5
+ /** A decoder of lists, with the operations on lists. */
6
+ export declare class ListDecoder<E> extends Chain<E[]> {
7
+ #private;
8
+ constructor(run: Run<E[]>, element: Decoder<E>);
9
+ protected derive(run: Run<E[]>): this;
10
+ metaValue(value: E[]): unknown;
11
+ /** Gives `too_small.nonempty` where there is no element. */
12
+ nonempty(message?: string): this;
13
+ /** Gives `too_small` where there are fewer than `min` elements. */
14
+ minSize(min: number, message?: string): this;
15
+ /** Gives `too_big` where there are more than `max` elements. */
16
+ maxSize(max: number, message?: string): this;
17
+ /** Gives `invalid_size` unless there are exactly `size` elements. */
18
+ fixedSize(size: number, message?: string): this;
19
+ /**
20
+ * Gives `duplicate_element` where an element occurs more than once, compared as the value model
21
+ * compares them. `duplicates` lists each such element once, in the order of the occurrences that
22
+ * first make them duplicates.
23
+ */
24
+ unique(message?: string): this;
25
+ /** Gives `missing_element` unless `element` occurs, compared as the value model compares them. */
26
+ contains(element: E, message?: string): this;
27
+ /**
28
+ * Gives `missing_elements` unless every element of `elements` occurs; `missing` lists, in the
29
+ * order given, each that does not, as many times as it was given.
30
+ */
31
+ containsAll(elements: readonly E[], message?: string): this;
32
+ /** A decoder giving the set of the elements, compared as the value model compares them. */
33
+ toSet(): Decoder<ValueSet<E>>;
34
+ }
35
+ /** A decoder of a JSON array, each element read with `element` at the path of its index. */
36
+ export declare function list<E>(element: Decoder<E>): ListDecoder<E>;
37
+ /** A decoder of maps, with the operations on maps. */
38
+ export declare class DictDecoder<E> extends Chain<Map<string, E>> {
39
+ /** Gives `too_small.nonempty` where there is no member. */
40
+ nonempty(message?: string): this;
41
+ /** Gives `too_small` where there are fewer than `min` members. */
42
+ minSize(min: number, message?: string): this;
43
+ /** Gives `too_big` where there are more than `max` members. */
44
+ maxSize(max: number, message?: string): this;
45
+ /** Gives `invalid_size` unless there are exactly `size` members. */
46
+ fixedSize(size: number, message?: string): this;
47
+ }
48
+ /** A decoder of a JSON object, each member's value read with `value` at the path of its name, in a `Map` in member order. */
49
+ export declare function dict<E>(value: Decoder<E>): DictDecoder<E>;
50
+ /** A field of an object decoder: what it reads of the object, and the member it names, if one. */
51
+ export declare class Field<T> {
52
+ #private;
53
+ /** The member the field reads; `undefined` for a flat field, which reads the whole input. */
54
+ readonly name: string | undefined;
55
+ constructor(name: string | undefined, run: Run<T>);
56
+ /** Reads the field of `input`, which is the object at `path`. */
57
+ readAt(input: unknown, path: Path): Result<T>;
58
+ }
59
+ /**
60
+ * A required field: the member `name` read with `decoder` at the member's path, an absent member
61
+ * handed to it as absent. Where the input is not an object, `type_mismatch` (expected `object`)
62
+ * at the member's path.
63
+ */
64
+ export declare function field<T>(name: string, decoder: Decoder<T>): Field<T>;
65
+ /** An optional field: `undefined` where the member is absent or the input is not an object, and otherwise the member read with `decoder`. */
66
+ export declare function optionalField<T>(name: string, decoder: Decoder<T>): Field<T | undefined>;
67
+ /** Whether a member was absent, null, or present with a value. */
68
+ export type Presence<T> = {
69
+ readonly state: "absent";
70
+ } | {
71
+ readonly state: "null";
72
+ } | {
73
+ readonly state: "present";
74
+ readonly value: T;
75
+ };
76
+ /** The presence of a member that is not there. */
77
+ export declare const ABSENT: Presence<never>;
78
+ /** The presence of a member that is JSON null. */
79
+ export declare const NULL: Presence<never>;
80
+ /** The presence of a member that has a value. */
81
+ export declare function presentWith<T>(value: T): Presence<T>;
82
+ /**
83
+ * A field that tells absence from null: absent where the member is absent or the input is not an
84
+ * object, null where the member is JSON null, and otherwise the member read with `decoder`.
85
+ */
86
+ export declare function optionalNullableField<T>(name: string, decoder: Decoder<T>): Field<Presence<T>>;
87
+ /** A field that reads the whole input with `decoder`, not a member of it. */
88
+ export declare function flat<T>(decoder: Decoder<T>): Field<T>;
89
+ type Values<F extends readonly Field<unknown>[]> = {
90
+ -readonly [K in keyof F]: F[K] extends Field<infer T> ? T : never;
91
+ };
92
+ /**
93
+ * A decoder of objects, giving the values of its fields in the order they are declared. Every
94
+ * field is read and every failing one's issues given, in that order.
95
+ */
96
+ export declare class ObjectDecoder<T extends readonly unknown[]> extends Chain<T> {
97
+ #private;
98
+ constructor(fields: readonly Field<unknown>[]);
99
+ /**
100
+ * This decoder, giving `unknown_field` for every member of an object input that no field names.
101
+ *
102
+ * @throws {TypeError} where a field is flat, since the members it reads cannot be told
103
+ */
104
+ strict(): Decoder<T>;
105
+ }
106
+ /** A decoder of objects whose fields are `fields`, giving their values as a tuple, in order. */
107
+ export declare function object<const F extends readonly Field<unknown>[]>(...fields: F): ObjectDecoder<Values<F>>;
108
+ /**
109
+ * `inner`, and where the input is an object, `unknown_field` at every member not among `known`
110
+ * that `inner` has not already reported unknown, in member order, after `inner`'s issues. Nested,
111
+ * the innermost that does not know a member reports it, and only members every one knows are
112
+ * accepted.
113
+ */
114
+ export declare function strict<T>(inner: Decoder<T>, known: readonly string[]): Decoder<T>;
115
+ /** What `enumOf` and `literal` are made with, beside their values. */
116
+ export interface ReadWith {
117
+ /** The decoder the string is read with; `string()` when none is given. */
118
+ readonly string?: Decoder<string>;
119
+ /** The sentence of the issue the decoder gives itself. */
120
+ readonly message?: string;
121
+ }
122
+ /**
123
+ * A decoder of one of `symbols`: the string `options.string` reads, matched with A-Z read as a-z,
124
+ * given as the symbol is declared. Anything else gives `invalid_format.enum`, listing the symbols
125
+ * lower-cased, in code point order.
126
+ *
127
+ * @throws {RangeError} where two symbols are the same once A-Z are read as a-z
128
+ */
129
+ export declare function enumOf<const S extends readonly string[]>(symbols: S, options?: ReadWith): Decoder<S[number]>;
130
+ /** A decoder of the string `expected`, read with `options.string`; anything else gives `invalid_format.literal`. */
131
+ export declare function literal<const L extends string>(expected: L, options?: ReadWith): Decoder<L>;
132
+ type Variants = Readonly<Record<string, Decoder<unknown>>>;
133
+ type VariantValue<V extends Variants> = {
134
+ [K in keyof V]: V[K] extends Decoder<infer T> ? T : never;
135
+ }[keyof V];
136
+ /**
137
+ * A decoder of the variant the member `fieldName` names: the tag read as a string, and the whole
138
+ * input read with that variant. A tag that names none gives `not_allowed` at the tag's path, the
139
+ * variants' names in code point order.
140
+ */
141
+ export declare function discriminate<const V extends Variants>(fieldName: string, variants: V): Decoder<VariantValue<V>>;
142
+ /**
143
+ * As {@link discriminate}, the tag being what `tag` gives for the whole input, its issues given
144
+ * as they are; a tag that names no variant gives `not_allowed` at the path of `fieldName`.
145
+ */
146
+ export declare function discriminateBy<const V extends Variants>(fieldName: string, tag: Decoder<string>, variants: V): Decoder<VariantValue<V>>;
147
+ type Candidate<D> = D extends Decoder<infer T> ? T : never;
148
+ /**
149
+ * A decoder trying each candidate in turn on the same input, giving the first success. Where all
150
+ * fail, `one_of_failed` at the input's path, its `candidates` listing each one's issues by index.
151
+ */
152
+ export declare function oneOf<const D extends readonly Decoder<unknown>[]>(...candidates: D): Decoder<Candidate<D[number]>>;
153
+ export {};
@@ -0,0 +1,397 @@
1
+ // The decoders that build structure: lists, maps, objects and their fields, and the choices
2
+ // between decoders.
3
+ import { Chain, Decoder, decoder } from "./decoder.js";
4
+ import { elementsOf, isObject, kindOf, memberOf, membersOf } from "./input.js";
5
+ import { Issue, Issues, failed, ok } from "./issue.js";
6
+ import { compareCodePoints, includesSame, keyOf, same } from "./meta.js";
7
+ import { string } from "./scalars.js";
8
+ import { ValueSet } from "./set.js";
9
+ const REQUIRED = new Issue("required");
10
+ function typeMismatch(expected, input) {
11
+ return new Issue("type_mismatch", { meta: { expected, actual: kindOf(input) } });
12
+ }
13
+ /** What a decoder's step gives: absent or null is `required`, another kind than `kind` a `type_mismatch`. */
14
+ function present(input, path, kind, expected) {
15
+ if (input === undefined || input === null) {
16
+ return REQUIRED.under(path);
17
+ }
18
+ return kindOf(input) === kind ? undefined : typeMismatch(expected, input).under(path);
19
+ }
20
+ /** Every value's result, as one: the values where all succeeded, and otherwise every issue in order. */
21
+ function gathered(results) {
22
+ const issues = [];
23
+ const values = [];
24
+ for (const result of results) {
25
+ if (result.issues === undefined) {
26
+ values.push(result.value);
27
+ }
28
+ else {
29
+ issues.push(...result.issues);
30
+ }
31
+ }
32
+ return issues.length > 0 ? failed(issues) : ok(values);
33
+ }
34
+ /** A decoder of lists, with the operations on lists. */
35
+ export class ListDecoder extends Chain {
36
+ #element;
37
+ constructor(run, element) {
38
+ super(run);
39
+ this.#element = element;
40
+ }
41
+ derive(run) {
42
+ return new ListDecoder(run, this.#element);
43
+ }
44
+ metaValue(value) {
45
+ return value.map((element) => this.#element.metaValue(element));
46
+ }
47
+ /** Gives `too_small.nonempty` where there is no element. */
48
+ nonempty(message) {
49
+ return this.check((list) => list.length === 0 ? new Issue("too_small", { messageKey: "too_small.nonempty", meta: { min: 1, actual: 0 } }) : undefined, message);
50
+ }
51
+ /** Gives `too_small` where there are fewer than `min` elements. */
52
+ minSize(min, message) {
53
+ return this.check((list) => (list.length < min ? new Issue("too_small", { meta: { min, actual: list.length } }) : undefined), message);
54
+ }
55
+ /** Gives `too_big` where there are more than `max` elements. */
56
+ maxSize(max, message) {
57
+ return this.check((list) => (list.length > max ? new Issue("too_big", { meta: { max, actual: list.length } }) : undefined), message);
58
+ }
59
+ /** Gives `invalid_size` unless there are exactly `size` elements. */
60
+ fixedSize(size, message) {
61
+ return this.check((list) => (list.length !== size ? new Issue("invalid_size", { meta: { expected: size, actual: list.length } }) : undefined), message);
62
+ }
63
+ /**
64
+ * Gives `duplicate_element` where an element occurs more than once, compared as the value model
65
+ * compares them. `duplicates` lists each such element once, in the order of the occurrences that
66
+ * first make them duplicates.
67
+ */
68
+ unique(message) {
69
+ return this.check((list) => {
70
+ const duplicates = duplicatesIn(list);
71
+ return duplicates.length > 0
72
+ ? new Issue("duplicate_element", { meta: { duplicates: duplicates.map((d) => this.#element.metaValue(d)) } })
73
+ : undefined;
74
+ }, message);
75
+ }
76
+ /** Gives `missing_element` unless `element` occurs, compared as the value model compares them. */
77
+ contains(element, message) {
78
+ return this.check((list) => includesSame(list, element)
79
+ ? undefined
80
+ : new Issue("missing_element", { meta: { expected: this.#element.metaValue(element) } }), message);
81
+ }
82
+ /**
83
+ * Gives `missing_elements` unless every element of `elements` occurs; `missing` lists, in the
84
+ * order given, each that does not, as many times as it was given.
85
+ */
86
+ containsAll(elements, message) {
87
+ if (elements.length === 0) {
88
+ throw new RangeError("containsAll takes at least one element");
89
+ }
90
+ return this.check((list) => {
91
+ const missing = elements.filter((element) => !includesSame(list, element));
92
+ const held = (values) => values.map((value) => this.#element.metaValue(value));
93
+ return missing.length > 0
94
+ ? new Issue("missing_elements", { meta: { expected: held(elements), missing: held(missing) } })
95
+ : undefined;
96
+ }, message);
97
+ }
98
+ /** A decoder giving the set of the elements, compared as the value model compares them. */
99
+ toSet() {
100
+ return new Chain(this.convert((list) => ok(ValueSet.of(list))));
101
+ }
102
+ }
103
+ /** The elements that occur more than once, each once, in the order their second occurrences come in. */
104
+ function duplicatesIn(list) {
105
+ const duplicates = [];
106
+ const seen = [];
107
+ const seenKeys = new Set();
108
+ const reported = new Set();
109
+ for (const element of list) {
110
+ const key = keyOf(element);
111
+ if (key !== undefined) {
112
+ if (seenKeys.has(key) && !reported.has(key)) {
113
+ duplicates.push(element);
114
+ reported.add(key);
115
+ }
116
+ seenKeys.add(key);
117
+ continue;
118
+ }
119
+ if (seen.some((other) => same(other, element)) && !includesSame(duplicates, element)) {
120
+ duplicates.push(element);
121
+ }
122
+ seen.push(element);
123
+ }
124
+ return duplicates;
125
+ }
126
+ /** A decoder of a JSON array, each element read with `element` at the path of its index. */
127
+ export function list(element) {
128
+ return new ListDecoder((input, path) => {
129
+ const wrong = present(input, path, "array", "array");
130
+ if (wrong !== undefined) {
131
+ return failed(wrong);
132
+ }
133
+ return gathered(elementsOf(input).map((item, i) => element.decodeAt(item, path.child(i))));
134
+ }, element);
135
+ }
136
+ /** A decoder of maps, with the operations on maps. */
137
+ export class DictDecoder extends Chain {
138
+ /** Gives `too_small.nonempty` where there is no member. */
139
+ nonempty(message) {
140
+ return this.check((map) => map.size === 0 ? new Issue("too_small", { messageKey: "too_small.nonempty", meta: { min: 1, actual: 0 } }) : undefined, message);
141
+ }
142
+ /** Gives `too_small` where there are fewer than `min` members. */
143
+ minSize(min, message) {
144
+ return this.check((map) => (map.size < min ? new Issue("too_small", { meta: { min, actual: map.size } }) : undefined), message);
145
+ }
146
+ /** Gives `too_big` where there are more than `max` members. */
147
+ maxSize(max, message) {
148
+ return this.check((map) => (map.size > max ? new Issue("too_big", { meta: { max, actual: map.size } }) : undefined), message);
149
+ }
150
+ /** Gives `invalid_size` unless there are exactly `size` members. */
151
+ fixedSize(size, message) {
152
+ return this.check((map) => (map.size !== size ? new Issue("invalid_size", { meta: { expected: size, actual: map.size } }) : undefined), message);
153
+ }
154
+ }
155
+ /** A decoder of a JSON object, each member's value read with `value` at the path of its name, in a `Map` in member order. */
156
+ export function dict(value) {
157
+ return new DictDecoder((input, path) => {
158
+ const wrong = present(input, path, "object", "object");
159
+ if (wrong !== undefined) {
160
+ return failed(wrong);
161
+ }
162
+ const members = membersOf(input);
163
+ const read = gathered(members.map(([name, member]) => value.decodeAt(member, path.child(name))));
164
+ return read.issues === undefined ? ok(new Map(members.map(([name], i) => [name, read.value[i]]))) : read;
165
+ });
166
+ }
167
+ /** A field of an object decoder: what it reads of the object, and the member it names, if one. */
168
+ export class Field {
169
+ #run;
170
+ /** The member the field reads; `undefined` for a flat field, which reads the whole input. */
171
+ name;
172
+ constructor(name, run) {
173
+ this.name = name;
174
+ this.#run = run;
175
+ }
176
+ /** Reads the field of `input`, which is the object at `path`. */
177
+ readAt(input, path) {
178
+ return this.#run(input, path);
179
+ }
180
+ }
181
+ /**
182
+ * A required field: the member `name` read with `decoder` at the member's path, an absent member
183
+ * handed to it as absent. Where the input is not an object, `type_mismatch` (expected `object`)
184
+ * at the member's path.
185
+ */
186
+ export function field(name, decoder) {
187
+ return new Field(name, (input, path) => {
188
+ const at = path.child(name);
189
+ if (!isObject(input)) {
190
+ return failed(typeMismatch("object", input).under(at));
191
+ }
192
+ return decoder.decodeAt(memberOf(input, name), at);
193
+ });
194
+ }
195
+ /** An optional field: `undefined` where the member is absent or the input is not an object, and otherwise the member read with `decoder`. */
196
+ export function optionalField(name, decoder) {
197
+ return new Field(name, (input, path) => {
198
+ const member = isObject(input) ? memberOf(input, name) : undefined;
199
+ return member === undefined ? ok(undefined) : decoder.decodeAt(member, path.child(name));
200
+ });
201
+ }
202
+ /** The presence of a member that is not there. */
203
+ export const ABSENT = Object.freeze({ state: "absent" });
204
+ /** The presence of a member that is JSON null. */
205
+ export const NULL = Object.freeze({ state: "null" });
206
+ /** The presence of a member that has a value. */
207
+ export function presentWith(value) {
208
+ return { state: "present", value };
209
+ }
210
+ /**
211
+ * A field that tells absence from null: absent where the member is absent or the input is not an
212
+ * object, null where the member is JSON null, and otherwise the member read with `decoder`.
213
+ */
214
+ export function optionalNullableField(name, decoder) {
215
+ return new Field(name, (input, path) => {
216
+ const member = isObject(input) ? memberOf(input, name) : undefined;
217
+ if (member === undefined) {
218
+ return ok(ABSENT);
219
+ }
220
+ if (member === null) {
221
+ return ok(NULL);
222
+ }
223
+ const read = decoder.decodeAt(member, path.child(name));
224
+ return read.issues === undefined ? ok(presentWith(read.value)) : read;
225
+ });
226
+ }
227
+ /** A field that reads the whole input with `decoder`, not a member of it. */
228
+ export function flat(decoder) {
229
+ return new Field(undefined, (input, path) => decoder.decodeAt(input, path));
230
+ }
231
+ /**
232
+ * A decoder of objects, giving the values of its fields in the order they are declared. Every
233
+ * field is read and every failing one's issues given, in that order.
234
+ */
235
+ export class ObjectDecoder extends Chain {
236
+ #fields;
237
+ constructor(fields) {
238
+ super((input, path) => gathered(fields.map((f) => f.readAt(input, path))));
239
+ this.#fields = fields;
240
+ }
241
+ /**
242
+ * This decoder, giving `unknown_field` for every member of an object input that no field names.
243
+ *
244
+ * @throws {TypeError} where a field is flat, since the members it reads cannot be told
245
+ */
246
+ strict() {
247
+ const names = this.#fields.map((f) => {
248
+ if (f.name === undefined) {
249
+ throw new TypeError("a strict object cannot have a flat field");
250
+ }
251
+ return f.name;
252
+ });
253
+ return strict(this, names);
254
+ }
255
+ }
256
+ /** A decoder of objects whose fields are `fields`, giving their values as a tuple, in order. */
257
+ export function object(...fields) {
258
+ return new ObjectDecoder(fields);
259
+ }
260
+ /**
261
+ * `inner`, and where the input is an object, `unknown_field` at every member not among `known`
262
+ * that `inner` has not already reported unknown, in member order, after `inner`'s issues. Nested,
263
+ * the innermost that does not know a member reports it, and only members every one knows are
264
+ * accepted.
265
+ */
266
+ export function strict(inner, known) {
267
+ const knownNames = new Set(known);
268
+ return decoder((input, path) => {
269
+ const read = inner.decodeAt(input, path);
270
+ if (!isObject(input)) {
271
+ return read;
272
+ }
273
+ const before = read.issues?.list ?? [];
274
+ const unknown = [];
275
+ for (const [name] of membersOf(input)) {
276
+ if (knownNames.has(name)) {
277
+ continue;
278
+ }
279
+ const at = path.child(name);
280
+ if (before.some((issue) => issue.messageKey === "unknown_field" && issue.path.equals(at))) {
281
+ continue;
282
+ }
283
+ unknown.push(new Issue("unknown_field", { meta: { field: name } }).under(at));
284
+ }
285
+ return unknown.length === 0 ? read : failed([...before, ...unknown]);
286
+ });
287
+ }
288
+ /**
289
+ * A decoder of one of `symbols`: the string `options.string` reads, matched with A-Z read as a-z,
290
+ * given as the symbol is declared. Anything else gives `invalid_format.enum`, listing the symbols
291
+ * lower-cased, in code point order.
292
+ *
293
+ * @throws {RangeError} where two symbols are the same once A-Z are read as a-z
294
+ */
295
+ export function enumOf(symbols, options = {}) {
296
+ const byFolded = new Map();
297
+ for (const symbol of symbols) {
298
+ const folded = asciiLower(symbol);
299
+ if (byFolded.has(folded)) {
300
+ throw new RangeError(`${symbol} is the same symbol as ${byFolded.get(folded)} with A-Z read as a-z`);
301
+ }
302
+ byFolded.set(folded, symbol);
303
+ }
304
+ const allowed = [...byFolded.keys()].sort(compareCodePoints);
305
+ return chained(options.string ?? string(), options.message, (s) => {
306
+ const symbol = byFolded.get(asciiLower(s));
307
+ return symbol === undefined
308
+ ? failed(new Issue("invalid_format", { messageKey: "invalid_format.enum", meta: { allowed } }))
309
+ : ok(symbol);
310
+ });
311
+ }
312
+ /** A decoder of the string `expected`, read with `options.string`; anything else gives `invalid_format.literal`. */
313
+ export function literal(expected, options = {}) {
314
+ return chained(options.string ?? string(), options.message, (s) => s === expected
315
+ ? ok(expected)
316
+ : failed(new Issue("invalid_format", { messageKey: "invalid_format.literal", meta: { expected } })));
317
+ }
318
+ /** `first`, then `f` of its value, the issues `f` gives at the decoder's path with `message` as their sentence where one is given. */
319
+ function chained(first, message, f) {
320
+ return decoder((input, path) => {
321
+ const read = first.decodeAt(input, path);
322
+ if (read.issues !== undefined) {
323
+ return read;
324
+ }
325
+ const made = f(read.value);
326
+ if (made.issues === undefined) {
327
+ return made;
328
+ }
329
+ const issues = message === undefined ? made.issues.list : made.issues.list.map((issue) => issue.withMessage(message));
330
+ return failed(new Issues(issues).under(path));
331
+ });
332
+ }
333
+ function asciiLower(s) {
334
+ return s.replace(/[A-Z]+/g, (upper) => upper.toLowerCase());
335
+ }
336
+ function notAllowed(variants) {
337
+ return new Issue("not_allowed", { meta: { allowed: Object.keys(variants).sort(compareCodePoints) } });
338
+ }
339
+ function variantFor(variants, tag) {
340
+ return Object.prototype.hasOwnProperty.call(variants, tag) ? variants[tag] : undefined;
341
+ }
342
+ /**
343
+ * A decoder of the variant the member `fieldName` names: the tag read as a string, and the whole
344
+ * input read with that variant. A tag that names none gives `not_allowed` at the tag's path, the
345
+ * variants' names in code point order.
346
+ */
347
+ export function discriminate(fieldName, variants) {
348
+ const tag = field(fieldName, string());
349
+ return decoder((input, path) => {
350
+ const read = tag.readAt(input, path);
351
+ if (read.issues !== undefined) {
352
+ return read;
353
+ }
354
+ const variant = variantFor(variants, read.value);
355
+ if (variant === undefined) {
356
+ return failed(notAllowed(variants).under(path.child(fieldName)));
357
+ }
358
+ return variant.decodeAt(input, path);
359
+ });
360
+ }
361
+ /**
362
+ * As {@link discriminate}, the tag being what `tag` gives for the whole input, its issues given
363
+ * as they are; a tag that names no variant gives `not_allowed` at the path of `fieldName`.
364
+ */
365
+ export function discriminateBy(fieldName, tag, variants) {
366
+ return decoder((input, path) => {
367
+ const read = tag.decodeAt(input, path);
368
+ if (read.issues !== undefined) {
369
+ return read;
370
+ }
371
+ const variant = variantFor(variants, read.value);
372
+ if (variant === undefined) {
373
+ return failed(notAllowed(variants).under(path.child(fieldName)));
374
+ }
375
+ return variant.decodeAt(input, path);
376
+ });
377
+ }
378
+ /**
379
+ * A decoder trying each candidate in turn on the same input, giving the first success. Where all
380
+ * fail, `one_of_failed` at the input's path, its `candidates` listing each one's issues by index.
381
+ */
382
+ export function oneOf(...candidates) {
383
+ if (candidates.length === 0) {
384
+ throw new RangeError("oneOf takes at least one decoder");
385
+ }
386
+ return decoder((input, path) => {
387
+ const failures = [];
388
+ for (const [index, candidate] of candidates.entries()) {
389
+ const read = candidate.decodeAt(input, path);
390
+ if (read.issues === undefined) {
391
+ return read;
392
+ }
393
+ failures.push({ candidate: index, issues: read.issues });
394
+ }
395
+ return failed(new Issue("one_of_failed", { meta: { candidates: failures } }).under(path));
396
+ });
397
+ }
package/dist/text.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Whether `text` is in the ASCII lexical profile of RFC 5321's Mailbox the `email` operation
3
+ * reads: atoms of atext joined by single dots, of at most 64 octets, `@`, and labels joined by
4
+ * single dots, each at most 63 octets, the domain at most 255 and the whole at most 254.
5
+ */
6
+ export declare function isEmail(text: string): boolean;
7
+ /** Whether `text` is RFC 3986's IPv4address: four decimal numbers from 0 to 255, no leading zero. */
8
+ export declare function isIpv4(text: string): boolean;
9
+ /**
10
+ * Whether `text` is an IPv6 address as the `ipv6` operation reads one: RFC 3986's IPv6address,
11
+ * optionally followed by `%` and a zone ID (non-empty, without `%` or U+0000) where the address is
12
+ * link-local unicast (fe80::/10) or multicast with a scope from 1 to D.
13
+ */
14
+ export declare function isIpv6(text: string): boolean;
15
+ /** Whether `text` is a ULID in its canonical text form, of at most 128 bits. */
16
+ export declare function isUlid(text: string): boolean;
17
+ /** Whether `text` is a CUID of version 1. */
18
+ export declare function isCuid(text: string): boolean;
19
+ /** Whether `text` is a UUID's 32 hexadecimal digits grouped 8-4-4-4-12, in either case. */
20
+ export declare function isUuid(text: string): boolean;
21
+ /** The parts of a URI the `url` operation asks about. */
22
+ export interface UriParts {
23
+ readonly scheme: string;
24
+ /** The host, where the URI has an authority. */
25
+ readonly host: string | undefined;
26
+ }
27
+ /**
28
+ * The scheme and host of `text` where it is a URI as the URI production of RFC 3986 section 3
29
+ * derives it, and `undefined` where it is not: a relative reference is not one, and neither is an
30
+ * IPv6 host with a zone identifier.
31
+ */
32
+ export declare function uriParts(text: string): UriParts | undefined;