diff --git a/jsDoc/dataParser/classic/array/example.ts b/jsDoc/dataParser/classic/array/example.ts index 383a5bdf2..553c28655 100644 --- a/jsDoc/dataParser/classic/array/example.ts +++ b/jsDoc/dataParser/classic/array/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: string[] } -const withCheckers = DP.array(DP.number(), { - checkers: [DP.checkerArrayMin(1), DP.checkerArrayMax(3)], -}); +const withCheckers = DP.array(DP.number()) + .addChecker(DP.checkerArrayMin(1), DP.checkerArrayMax(3)); const nested = DP.array(DP.array(DP.boolean())); const nestedResult = nested.parse([[true, false]]); diff --git a/jsDoc/dataParser/classic/array/index.md b/jsDoc/dataParser/classic/array/index.md index 8aa03090f..264286d62 100644 --- a/jsDoc/dataParser/classic/array/index.md +++ b/jsDoc/dataParser/classic/array/index.md @@ -7,7 +7,7 @@ Creates a data parser for arrays of a given element parser. Validates that the input is an array and validates each element with the provided parser. ```ts -{@include dataParser/classic/array/example.ts[3,15]} +{@include dataParser/classic/array/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/array diff --git a/jsDoc/dataParser/classic/base/clone/example.ts b/jsDoc/dataParser/classic/base/clone/example.ts index e948c6d5c..fe85af6c6 100644 --- a/jsDoc/dataParser/classic/base/clone/example.ts +++ b/jsDoc/dataParser/classic/base/clone/example.ts @@ -8,5 +8,5 @@ const withMin = DP.string() const withMinClone = withMin.clone(); -const coerceNumber = DP.coerce.number(); +const coerceNumber = DP.coercer(DP.number()); const coerceNumberClone = coerceNumber.clone(); diff --git a/jsDoc/dataParser/classic/base/parse/example.ts b/jsDoc/dataParser/classic/base/parse/example.ts index 3bf487f21..049debd6c 100644 --- a/jsDoc/dataParser/classic/base/parse/example.ts +++ b/jsDoc/dataParser/classic/base/parse/example.ts @@ -8,16 +8,16 @@ if (E.isRight(result)) { // value: string } -const resultWithError = DP.string({ - checkers: [DP.checkerStringMin(3)], -}).parse("ok"); +const resultWithError = DP.string() + .addChecker(DP.checkerStringMin(3)) + .parse("ok"); if (E.isLeft(resultWithError)) { const error = unwrap(resultWithError); // error: DP.DataParserError } -const numberSchema = DP.coerce.number(); +const numberSchema = DP.coercer(DP.number()); const numberResult = numberSchema.parse("42"); if (E.isRight(numberResult)) { const value = unwrap(numberResult); diff --git a/jsDoc/dataParser/classic/base/parseOrThrow/example.ts b/jsDoc/dataParser/classic/base/parseOrThrow/example.ts index a22ab0eb4..44e09a377 100644 --- a/jsDoc/dataParser/classic/base/parseOrThrow/example.ts +++ b/jsDoc/dataParser/classic/base/parseOrThrow/example.ts @@ -1,8 +1,7 @@ import { DP } from "@scripts"; -const stringSchema = DP.string({ - checkers: [DP.checkerStringMin(3)], -}); +const stringSchema = DP.string() + .addChecker(DP.checkerStringMin(3)); const value = stringSchema.parseOrThrow("DuploJS"); // value: string diff --git a/jsDoc/dataParser/classic/base/parseOrThrow/index.md b/jsDoc/dataParser/classic/base/parseOrThrow/index.md index a33aeea8d..300e05c9f 100644 --- a/jsDoc/dataParser/classic/base/parseOrThrow/index.md +++ b/jsDoc/dataParser/classic/base/parseOrThrow/index.md @@ -6,7 +6,7 @@ The parseOrThrow() method runs a data parser synchronously and returns the parse It executes the parser, applies all registered checkers, and never mutates the input. ```ts -{@include dataParser/classic/base/parseOrThrow/example.ts[3,23]} +{@include dataParser/classic/base/parseOrThrow/example.ts[3,15]} ``` @namespace DP diff --git a/jsDoc/dataParser/classic/bigint/example.ts b/jsDoc/dataParser/classic/bigint/example.ts index cbb19a70e..50e332af0 100644 --- a/jsDoc/dataParser/classic/bigint/example.ts +++ b/jsDoc/dataParser/classic/bigint/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: bigint } -const withCheckers = DP.bigint({ - checkers: [DP.checkerBigIntMin(BigInt(1)), DP.checkerBigIntMax(BigInt(10))], -}); +const withCheckers = DP.bigint() + .addChecker(DP.checkerBigIntMin(BigInt(1)), DP.checkerBigIntMax(BigInt(10))); -const coerceParser = DP.coerce.bigint(); +const coerceParser = DP.coercer(DP.bigint()); const coerceResult = coerceParser.parse("42"); diff --git a/jsDoc/dataParser/classic/bigint/index.md b/jsDoc/dataParser/classic/bigint/index.md index 4bbbaed5d..72b716bda 100644 --- a/jsDoc/dataParser/classic/bigint/index.md +++ b/jsDoc/dataParser/classic/bigint/index.md @@ -7,7 +7,7 @@ Creates a data parser for bigint values. Validates that the input is a bigint, optionally applies coerce, and runs the configured checkers. ```ts -{@include dataParser/classic/bigint/example.ts[3,15]} +{@include dataParser/classic/bigint/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/bigint diff --git a/jsDoc/dataParser/classic/boolean/example.ts b/jsDoc/dataParser/classic/boolean/example.ts index 5ac649303..cb55ee825 100644 --- a/jsDoc/dataParser/classic/boolean/example.ts +++ b/jsDoc/dataParser/classic/boolean/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: boolean } -const onlyTrue = DP.boolean({ - checkers: [DP.checkerRefine((value) => value === true)], -}); +const onlyTrue = DP.boolean() + .addChecker(DP.checkerRefine((value) => value === true)); -const coerceParser = DP.coerce.boolean(); +const coerceParser = DP.coercer(DP.boolean()); const coerceResult = coerceParser.parse("false"); diff --git a/jsDoc/dataParser/classic/boolean/index.md b/jsDoc/dataParser/classic/boolean/index.md index 17e30bb3a..2e4773b8a 100644 --- a/jsDoc/dataParser/classic/boolean/index.md +++ b/jsDoc/dataParser/classic/boolean/index.md @@ -7,7 +7,7 @@ Creates a data parser for boolean values. Validates that the input is a boolean, optionally applies coerce, and runs the configured checkers. ```ts -{@include dataParser/classic/boolean/example.ts[3,15]} +{@include dataParser/classic/boolean/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/boolean diff --git a/jsDoc/dataParser/classic/checkerTimeMax/example.ts b/jsDoc/dataParser/classic/checkerTimeMax/example.ts index 0247dd968..c65423523 100644 --- a/jsDoc/dataParser/classic/checkerTimeMax/example.ts +++ b/jsDoc/dataParser/classic/checkerTimeMax/example.ts @@ -1,8 +1,7 @@ import { D, DP } from "@scripts"; -const parser = DP.time({ - checkers: [DP.checkerTimeMax(D.createTime(2, "minute"))], -}); +const parser = DP.time() + .addChecker(DP.checkerTimeMax(D.createTime(2, "minute"))); const valid = parser.parse("time1500+"); // valid: Error | Success diff --git a/jsDoc/dataParser/classic/checkerTimeMax/index.md b/jsDoc/dataParser/classic/checkerTimeMax/index.md index 50aab4898..f4b23b924 100644 --- a/jsDoc/dataParser/classic/checkerTimeMax/index.md +++ b/jsDoc/dataParser/classic/checkerTimeMax/index.md @@ -5,7 +5,7 @@ Signature: `checkerTimeMax(max, definition?)` → `DataParserCheckerTimeMax` The checker passes when parsed value is less than or equal to `max`. ```ts -{@include dataParser/classic/checkerTimeMax/example.ts[3,13]} +{@include dataParser/classic/checkerTimeMax/example.ts[3,10]} ``` @remarks diff --git a/jsDoc/dataParser/classic/checkerTimeMin/example.ts b/jsDoc/dataParser/classic/checkerTimeMin/example.ts index 4923f0de6..45fe22f35 100644 --- a/jsDoc/dataParser/classic/checkerTimeMin/example.ts +++ b/jsDoc/dataParser/classic/checkerTimeMin/example.ts @@ -1,8 +1,7 @@ import { D, DP } from "@scripts"; -const parser = DP.time({ - checkers: [DP.checkerTimeMin(D.createTime(1, "minute"))], -}); +const parser = DP.time() + .addChecker(DP.checkerTimeMin(D.createTime(1, "minute"))); const valid = parser.parse("time1500+"); // valid: Error | Success diff --git a/jsDoc/dataParser/classic/checkerTimeMin/index.md b/jsDoc/dataParser/classic/checkerTimeMin/index.md index 2bfb6f6f4..8ab987239 100644 --- a/jsDoc/dataParser/classic/checkerTimeMin/index.md +++ b/jsDoc/dataParser/classic/checkerTimeMin/index.md @@ -5,7 +5,7 @@ Signature: `checkerTimeMin(min, definition?)` → `DataParserCheckerTimeMin` The checker passes when parsed value is greater than or equal to `min`. ```ts -{@include dataParser/classic/checkerTimeMin/example.ts[3,13]} +{@include dataParser/classic/checkerTimeMin/example.ts[3,10]} ``` @remarks diff --git a/jsDoc/dataParser/classic/coercer/example.ts b/jsDoc/dataParser/classic/coercer/example.ts new file mode 100644 index 000000000..f31a0e0f9 --- /dev/null +++ b/jsDoc/dataParser/classic/coercer/example.ts @@ -0,0 +1,23 @@ +import { DP, E, unwrap } from "@scripts"; + +const parser = DP.coercer(DP.number()); +const result = parser.parse("42"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: number +} + +const withCheckers = DP.coercer( + DP.string(), +).addChecker(DP.checkerStringMin(3)); + +const checkedResult = withCheckers.parse(42); + +const complex = DP.object({ + name: DP.coercer(DP.string()), + age: DP.coercer(DP.number()), +}); +const complexResult = complex.parse({ + name: 123, + age: "42", +}); diff --git a/jsDoc/dataParser/classic/coercer/index.md b/jsDoc/dataParser/classic/coercer/index.md new file mode 100644 index 000000000..36e91c0bb --- /dev/null +++ b/jsDoc/dataParser/classic/coercer/index.md @@ -0,0 +1,18 @@ +Creates a classic coercer parser around another DataParser. + +Signature: `DP.coercer(inner, definition?)` -> `DataParserCoercer` + +This parser does not represent a data type by itself. It represents a parsing action, like `DP.pipe(...)` or `DP.lazy(...)`: it receives an input, transforms it with the transformer registered for the inner parser kind, then gives the transformed value to the inner parser. + +```ts +{@include dataParser/classic/coercer/example.ts[3,23]} +``` + +@remarks +- Parsed output is always the output of the inner parser. +- Accepted input is widened with the values supported by the inner parser coercion transformer. +- If no transformer is registered for the inner parser kind, the coercer still runs the inner parser with the original value. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DP diff --git a/jsDoc/dataParser/classic/coercer/transformers/example.ts b/jsDoc/dataParser/classic/coercer/transformers/example.ts new file mode 100644 index 000000000..ee88f677b --- /dev/null +++ b/jsDoc/dataParser/classic/coercer/transformers/example.ts @@ -0,0 +1,13 @@ +import { DP } from "@scripts"; + +const numberTransformer = DP.DataParserCoercer.transformers.get( + DP.numberKind, +); + +DP.DataParserCoercer.transformers.set( + DP.stringKind, + (value) => String(value), +); + +const parser = DP.coercer(DP.string()); +const result = parser.parse(42); diff --git a/jsDoc/dataParser/classic/coercer/transformers/index.md b/jsDoc/dataParser/classic/coercer/transformers/index.md new file mode 100644 index 000000000..a3f70abcf --- /dev/null +++ b/jsDoc/dataParser/classic/coercer/transformers/index.md @@ -0,0 +1,18 @@ +Defines the transformer map used by classic coercer parsers. + +Signature: `DP.DataParserCoercer.transformers` -> `Map` + +The map links a DataParser kind to the function that prepares raw input before the inner parser runs. This is what makes `DP.coercer(DP.number())` accept values like numeric strings while still returning the output of `DP.number()`. + +```ts +{@include dataParser/classic/coercer/transformers/example.ts[3,13]} +``` + +@remarks +- This property is the source of truth for the parser kinds that support coercion. +- A coercer memoizes the transformer it resolves during its first parse. +- Changing this map is an advanced extension point. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DP diff --git a/jsDoc/dataParser/classic/date/example.ts b/jsDoc/dataParser/classic/date/example.ts index 159be2dce..7d2d77d60 100644 --- a/jsDoc/dataParser/classic/date/example.ts +++ b/jsDoc/dataParser/classic/date/example.ts @@ -7,12 +7,11 @@ if (E.isRight(result)) { // value: TheDate } -const withCheckers = DP.date({ - checkers: [DP.checkerRefine((value) => value.getUTCFullYear() >= 2024)], -}); +const withCheckers = DP.date() + .addChecker(DP.checkerRefine((value) => value.getUTCFullYear() >= 2024)); const checked = withCheckers.parse("date1704067200000+"); // checked: E.Error | E.Success -const coerceParser = DP.coerce.date(); +const coerceParser = DP.coercer(DP.date()); const coerceResult = coerceParser.parse("2024-01-01T00:00:00.000Z"); // coerceResult: E.Error | E.Success diff --git a/jsDoc/dataParser/classic/date/index.md b/jsDoc/dataParser/classic/date/index.md index f92e20f41..62fd45ed3 100644 --- a/jsDoc/dataParser/classic/date/index.md +++ b/jsDoc/dataParser/classic/date/index.md @@ -6,12 +6,12 @@ The parser accepts `TheDate`, `SerializedTheDate`, and native `Date`. With `coerce: true`, safe timestamps and parsable date strings are also supported. ```ts -{@include dataParser/classic/date/example.ts[3,18]} +{@include dataParser/classic/date/example.ts[3,17]} ``` @remarks - Parsed output is always `TheDate`. -- Use `DP.coerce.date()` when you want coercion enabled by default. +- Use `DP.coercer(DP.date())` when you want coercion enabled by default. @see https://utils.duplojs.dev/en/v1/api/dataParser/date diff --git a/jsDoc/dataParser/classic/empty/example.ts b/jsDoc/dataParser/classic/empty/example.ts index 1b2f192d4..f0ad0585d 100644 --- a/jsDoc/dataParser/classic/empty/example.ts +++ b/jsDoc/dataParser/classic/empty/example.ts @@ -6,9 +6,8 @@ if (E.isRight(result)) { // E.Success } -const withCheckers = DP.empty({ - checkers: [DP.checkerRefine((value) => value === undefined)], -}); +const withCheckers = DP.empty() + .addChecker(DP.checkerRefine((value) => value === undefined)); -const coerceParser = DP.coerce.empty(); +const coerceParser = DP.coercer(DP.empty()); const coerceResult = coerceParser.parse("undefined"); diff --git a/jsDoc/dataParser/classic/empty/index.md b/jsDoc/dataParser/classic/empty/index.md index a71a2453a..df76ca439 100644 --- a/jsDoc/dataParser/classic/empty/index.md +++ b/jsDoc/dataParser/classic/empty/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts undefined. Accepts undefined (or the string "undefined" when coerce is enabled) and rejects other inputs. ```ts -{@include dataParser/classic/empty/example.ts[3,14]} +{@include dataParser/classic/empty/example.ts[3,13]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/empty diff --git a/jsDoc/dataParser/classic/nil/example.ts b/jsDoc/dataParser/classic/nil/example.ts index a3fca6356..3c5087dc9 100644 --- a/jsDoc/dataParser/classic/nil/example.ts +++ b/jsDoc/dataParser/classic/nil/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: null } -const withCheckers = DP.nil({ - checkers: [DP.checkerRefine((value) => value === null)], -}); +const withCheckers = DP.nil() + .addChecker(DP.checkerRefine((value) => value === null)); -const coerceParser = DP.coerce.nil(); +const coerceParser = DP.coercer(DP.nil()); const coerceResult = coerceParser.parse("null"); diff --git a/jsDoc/dataParser/classic/nil/index.md b/jsDoc/dataParser/classic/nil/index.md index 73f1399ac..647aec3e3 100644 --- a/jsDoc/dataParser/classic/nil/index.md +++ b/jsDoc/dataParser/classic/nil/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts null. Accepts null (or the string "null" when coerce is enabled) and rejects other inputs. ```ts -{@include dataParser/classic/nil/example.ts[3,15]} +{@include dataParser/classic/nil/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/nil diff --git a/jsDoc/dataParser/classic/nullable/example.ts b/jsDoc/dataParser/classic/nullable/example.ts index 59935b32b..376e5b0a3 100644 --- a/jsDoc/dataParser/classic/nullable/example.ts +++ b/jsDoc/dataParser/classic/nullable/example.ts @@ -10,6 +10,5 @@ if (E.isRight(result)) { const withCoalescing = DP.nullable(DP.number(), { coalescingValue: 0 }); const coalesced = withCoalescing.parse(null); -const withCheckers = DP.nullable(DP.boolean(), { - checkers: [DP.checkerRefine((value) => value !== null)], -}); +const withCheckers = DP.nullable(DP.boolean()) + .addChecker(DP.checkerRefine((value) => value !== null)); diff --git a/jsDoc/dataParser/classic/nullable/index.md b/jsDoc/dataParser/classic/nullable/index.md index b55a9e110..4deb00ae2 100644 --- a/jsDoc/dataParser/classic/nullable/index.md +++ b/jsDoc/dataParser/classic/nullable/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts null or the inner parser output. Returns null (or a coalescing value) when input is null, otherwise parses with the inner parser. ```ts -{@include dataParser/classic/nullable/example.ts[3,15]} +{@include dataParser/classic/nullable/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/nullable diff --git a/jsDoc/dataParser/classic/number/example.ts b/jsDoc/dataParser/classic/number/example.ts index a36c504e6..1ff13f0dd 100644 --- a/jsDoc/dataParser/classic/number/example.ts +++ b/jsDoc/dataParser/classic/number/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: number } -const withCheckers = DP.number({ - checkers: [DP.checkerNumberMin(0), DP.checkerInt()], -}); +const withCheckers = DP.number() + .addChecker(DP.checkerNumberMin(0), DP.checkerInt()); -const coerceParser = DP.coerce.number(); +const coerceParser = DP.coercer(DP.number()); const coerceResult = coerceParser.parse("42"); diff --git a/jsDoc/dataParser/classic/number/index.md b/jsDoc/dataParser/classic/number/index.md index 01d23cfbe..1fc959e07 100644 --- a/jsDoc/dataParser/classic/number/index.md +++ b/jsDoc/dataParser/classic/number/index.md @@ -7,7 +7,7 @@ Creates a data parser for numbers. Validates that the input is a finite number, optionally applies coerce, and runs the configured checkers. `NaN`, `Infinity`, and `-Infinity` are rejected. ```ts -{@include dataParser/classic/number/example.ts[3,15]} +{@include dataParser/classic/number/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/number diff --git a/jsDoc/dataParser/classic/optional/example.ts b/jsDoc/dataParser/classic/optional/example.ts index 51d65f717..95aeda0dd 100644 --- a/jsDoc/dataParser/classic/optional/example.ts +++ b/jsDoc/dataParser/classic/optional/example.ts @@ -10,6 +10,5 @@ if (E.isRight(result)) { const withCoalescing = DP.optional(DP.number(), { coalescingValue: 0 }); const coalesced = withCoalescing.parse(undefined); -const withCheckers = DP.optional(DP.number(), { - checkers: [DP.checkerRefine((value) => value !== 13)], -}); +const withCheckers = DP.optional(DP.number()) + .addChecker(DP.checkerRefine((value) => value !== 13)); diff --git a/jsDoc/dataParser/classic/optional/index.md b/jsDoc/dataParser/classic/optional/index.md index 115c44aa1..4eca5b01e 100644 --- a/jsDoc/dataParser/classic/optional/index.md +++ b/jsDoc/dataParser/classic/optional/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts undefined or the inner parser output. Returns undefined (or a coalescing value) when input is undefined, otherwise parses with the inner parser. ```ts -{@include dataParser/classic/optional/example.ts[3,15]} +{@include dataParser/classic/optional/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/optional diff --git a/jsDoc/dataParser/classic/pipe/example.ts b/jsDoc/dataParser/classic/pipe/example.ts index aed9a29d6..848ea76a8 100644 --- a/jsDoc/dataParser/classic/pipe/example.ts +++ b/jsDoc/dataParser/classic/pipe/example.ts @@ -1,7 +1,7 @@ import { DP, E, unwrap } from "@scripts"; const schema = DP.pipe( - DP.coerce.number(), + DP.coercer(DP.number()), DP.transform( DP.number(), (value) => value + 1, diff --git a/jsDoc/dataParser/classic/record/example.ts b/jsDoc/dataParser/classic/record/example.ts index 2b7549537..b721b2af2 100644 --- a/jsDoc/dataParser/classic/record/example.ts +++ b/jsDoc/dataParser/classic/record/example.ts @@ -17,6 +17,5 @@ const strictResult = strictKeys.parse({ yPos: false, }); -const withCheckers = DP.record(DP.string(), DP.string(), { - checkers: [DP.checkerRefine((value) => Object.keys(value).length > 0)], -}); +const withCheckers = DP.record(DP.string(), DP.string()) + .addChecker(DP.checkerRefine((value) => Object.keys(value).length > 0)); diff --git a/jsDoc/dataParser/classic/record/index.md b/jsDoc/dataParser/classic/record/index.md index 79f81edc1..97a29ec4e 100644 --- a/jsDoc/dataParser/classic/record/index.md +++ b/jsDoc/dataParser/classic/record/index.md @@ -7,7 +7,7 @@ Creates a data parser for records with key and value parsers. Validates that the input is an object and parses each key and value with the provided parsers. ```ts -{@include dataParser/classic/record/example.ts[3,22]} +{@include dataParser/classic/record/example.ts[3,21]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/record diff --git a/jsDoc/dataParser/classic/string/example.ts b/jsDoc/dataParser/classic/string/example.ts index 96e6f8c47..eb23dbe99 100644 --- a/jsDoc/dataParser/classic/string/example.ts +++ b/jsDoc/dataParser/classic/string/example.ts @@ -7,9 +7,8 @@ if (E.isRight(result)) { // value: string } -const withCheckers = DP.string({ - checkers: [DP.checkerStringMin(3), DP.checkerStringMax(10)], -}); +const withCheckers = DP.string() + .addChecker(DP.checkerStringMin(3), DP.checkerStringMax(10)); -const coerceParser = DP.coerce.string(); +const coerceParser = DP.coercer(DP.string()); const coerceResult = coerceParser.parse(123); diff --git a/jsDoc/dataParser/classic/string/index.md b/jsDoc/dataParser/classic/string/index.md index 89b1bb1dc..a1f15dbbd 100644 --- a/jsDoc/dataParser/classic/string/index.md +++ b/jsDoc/dataParser/classic/string/index.md @@ -7,7 +7,7 @@ Creates a data parser for strings. Validates that the input is a string, optionally applies coerce, and runs the configured checkers. ```ts -{@include dataParser/classic/string/example.ts[3,15]} +{@include dataParser/classic/string/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/string diff --git a/jsDoc/dataParser/classic/templateLiteral/example.ts b/jsDoc/dataParser/classic/templateLiteral/example.ts index b310d8823..37db80266 100644 --- a/jsDoc/dataParser/classic/templateLiteral/example.ts +++ b/jsDoc/dataParser/classic/templateLiteral/example.ts @@ -10,6 +10,5 @@ if (E.isRight(result)) { const orderParser = DP.templateLiteral(["order-", DP.literal("vip"), "-", DP.number()]); const orderResult = orderParser.parse("order-vip-12"); -const withCheckers = DP.templateLiteral(["id-", DP.number()], { - checkers: [DP.checkerRefine((value) => value.endsWith("0"))], -}); +const withCheckers = DP.templateLiteral(["id-", DP.number()]) + .addChecker(DP.checkerRefine((value) => value.endsWith("0"))); diff --git a/jsDoc/dataParser/classic/templateLiteral/index.md b/jsDoc/dataParser/classic/templateLiteral/index.md index e079434d8..1d45395ff 100644 --- a/jsDoc/dataParser/classic/templateLiteral/index.md +++ b/jsDoc/dataParser/classic/templateLiteral/index.md @@ -7,7 +7,7 @@ Creates a data parser for deterministic template literal strings. Validates that the input matches the provided template literal shape and pattern. Nested sub-parsers must not include their own checkers; add template-level `checkerRefine` rules when needed. ```ts -{@include dataParser/classic/templateLiteral/example.ts[3,15]} +{@include dataParser/classic/templateLiteral/example.ts[3,14]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/templateLiteral diff --git a/jsDoc/dataParser/classic/time/example.ts b/jsDoc/dataParser/classic/time/example.ts index a9538940e..df52d85cd 100644 --- a/jsDoc/dataParser/classic/time/example.ts +++ b/jsDoc/dataParser/classic/time/example.ts @@ -7,12 +7,11 @@ if (E.isRight(result)) { // value: TheTime } -const withCheckers = DP.time({ - checkers: [DP.checkerRefine((value) => value.toNative() !== 0)], -}); +const withCheckers = DP.time() + .addChecker(DP.checkerRefine((value) => value.toNative() !== 0)); const checked = withCheckers.parse("time1000+"); // checked: E.Error | E.Success -const coerceParser = DP.coerce.time(); +const coerceParser = DP.coercer(DP.time()); const coerceResult = coerceParser.parse("10:20:00"); // coerceResult: E.Error | E.Success diff --git a/jsDoc/dataParser/classic/time/index.md b/jsDoc/dataParser/classic/time/index.md index e89d665f4..29b48ac33 100644 --- a/jsDoc/dataParser/classic/time/index.md +++ b/jsDoc/dataParser/classic/time/index.md @@ -6,12 +6,12 @@ The parser accepts `TheTime`, `SerializedTheTime`, and safe numeric time values. With `coerce: true`, ISO-like time strings are also supported. ```ts -{@include dataParser/classic/time/example.ts[3,18]} +{@include dataParser/classic/time/example.ts[3,17]} ``` @remarks - Parsed output is always `TheTime`. -- Use `DP.coerce.time()` when you want string coercion enabled by default. +- Use `DP.coercer(DP.time())` when you want string coercion enabled by default. @see https://utils.duplojs.dev/en/v1/api/dataParser/time diff --git a/jsDoc/dataParser/classic/union/example.ts b/jsDoc/dataParser/classic/union/example.ts index 1607d6cb6..fa63d4c43 100644 --- a/jsDoc/dataParser/classic/union/example.ts +++ b/jsDoc/dataParser/classic/union/example.ts @@ -11,6 +11,5 @@ const literals = DP.union([DP.literal("on"), DP.literal("off")]); const literalResult = literals.parse("off"); const withCheckers = DP.union( - [DP.string(), DP.coerce.number()], - { checkers: [DP.checkerRefine((value) => value !== "forbidden")] }, -); + [DP.string(), DP.coercer(DP.number())], +).addChecker(DP.checkerRefine((value) => value !== "forbidden")); diff --git a/jsDoc/dataParser/classic/union/index.md b/jsDoc/dataParser/classic/union/index.md index 1a59b5216..b5730cf28 100644 --- a/jsDoc/dataParser/classic/union/index.md +++ b/jsDoc/dataParser/classic/union/index.md @@ -7,7 +7,7 @@ Creates a data parser that accepts one of multiple parsers. Tries each option in order until one succeeds, then returns its output. ```ts -{@include dataParser/classic/union/example.ts[3,16]} +{@include dataParser/classic/union/example.ts[3,15]} ``` @see https://utils.duplojs.dev/en/v1/api/dataParser/union diff --git a/jsDoc/dataParser/extended/base/array/example.ts b/jsDoc/dataParser/extended/base/array/example.ts index 257a36c60..7cab743d6 100644 --- a/jsDoc/dataParser/extended/base/array/example.ts +++ b/jsDoc/dataParser/extended/base/array/example.ts @@ -7,9 +7,9 @@ if (E.isRight(result)) { // value: string[] } -const withCheckers = DPE.number().array({ - checkers: [DP.checkerArrayMin(1)], -}); +const withCheckers = DPE.number() + .array() + .addChecker(DP.checkerArrayMin(1)); const nested = DPE.string().array().array(); const nestedResult = nested.parse([["a"], ["b"]]); diff --git a/jsDoc/dataParser/extended/base/coerce/example.ts b/jsDoc/dataParser/extended/base/coerce/example.ts new file mode 100644 index 000000000..0814fc098 --- /dev/null +++ b/jsDoc/dataParser/extended/base/coerce/example.ts @@ -0,0 +1,29 @@ +import { DPE, E, unwrap } from "@scripts"; + +const numberParser = DPE.number() + .min(1) + .coerce(); +const numberResult = numberParser.parse("42"); +if (E.isRight(numberResult)) { + const value = unwrap(numberResult); + // value: number +} + +const booleanParser = DPE.boolean().coerce(); +const booleanResult = booleanParser.parse("true"); + +const userParser = DPE.object({ + name: DPE.string() + .min(1) + .coerce(), + active: DPE.boolean().coerce(), + createdAt: DPE.date().coerce(), +}); +const userResult = userParser.parse({ + name: 123, + active: 1, + createdAt: "2024-01-01T00:00:00.000Z", +}); + +const passThrough = DPE.object({ id: DPE.number() }).coerce(); +const passThroughResult = passThrough.parse({ id: 1 }); diff --git a/jsDoc/dataParser/extended/base/coerce/index.md b/jsDoc/dataParser/extended/base/coerce/index.md new file mode 100644 index 000000000..f5030acd7 --- /dev/null +++ b/jsDoc/dataParser/extended/base/coerce/index.md @@ -0,0 +1,19 @@ +Wraps the current extended parser in a coercer parser. + +**Supported call styles:** +- Method: `dataParser.coerce()` -> returns a coercer parser + +Keeps the current parser as the inner parser. When a coercion transformer is registered for the inner parser kind, input is transformed before validation; otherwise the original input is passed to the inner parser. + +```ts +{@include dataParser/extended/base/coerce/example.ts[3,25]} +``` + +@remarks +- Parsed output is always the output of the current parser. +- Accepted input is widened only for parser kinds registered in `DPE.DataParserCoercerExtended.transformers`. +- Call this method after parser-specific methods like `.min(...)`, `.max(...)`, `.regex(...)`, or `.int()` so the coercer keeps those checks. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DPE diff --git a/jsDoc/dataParser/extended/base/nullable/example.ts b/jsDoc/dataParser/extended/base/nullable/example.ts index 6aac28d0e..c7a3338cc 100644 --- a/jsDoc/dataParser/extended/base/nullable/example.ts +++ b/jsDoc/dataParser/extended/base/nullable/example.ts @@ -10,6 +10,6 @@ if (E.isRight(result)) { const withCoalescing = DPE.number().nullable({ coalescingValue: 0 }); const coalesced = withCoalescing.parse(null); -const withCheckers = DPE.boolean().nullable({ - checkers: [DP.checkerRefine((value) => value !== null)], -}); +const withCheckers = DPE.boolean() + .nullable() + .addChecker(DP.checkerRefine((value) => value !== null)); diff --git a/jsDoc/dataParser/extended/base/optional/example.ts b/jsDoc/dataParser/extended/base/optional/example.ts index 22590f3fd..1fb850798 100644 --- a/jsDoc/dataParser/extended/base/optional/example.ts +++ b/jsDoc/dataParser/extended/base/optional/example.ts @@ -10,6 +10,6 @@ if (E.isRight(result)) { const withCoalescing = DPE.number().optional({ coalescingValue: 0 }); const coalesced = withCoalescing.parse(undefined); -const withCheckers = DPE.number().optional({ - checkers: [DP.checkerRefine((value) => value !== 13)], -}); +const withCheckers = DPE.number() + .optional() + .addChecker(DP.checkerRefine((value) => value !== 13)); diff --git a/jsDoc/dataParser/extended/base/or/example.ts b/jsDoc/dataParser/extended/base/or/example.ts index 992a24820..5fc3186ca 100644 --- a/jsDoc/dataParser/extended/base/or/example.ts +++ b/jsDoc/dataParser/extended/base/or/example.ts @@ -10,6 +10,6 @@ if (E.isRight(result)) { const literals = DPE.literal("on").or(DPE.literal("off")); const literalResult = literals.parse("off"); -const withCheckers = DPE.string().or(DPE.coerce.number(), { - checkers: [DP.checkerRefine((value) => value !== "forbidden")], -}); +const withCheckers = DPE.string() + .or(DPE.number().coerce()) + .addChecker(DP.checkerRefine((value) => value !== "forbidden")); diff --git a/jsDoc/dataParser/extended/base/pipe/example.ts b/jsDoc/dataParser/extended/base/pipe/example.ts index 8afc57a6e..35384adc9 100644 --- a/jsDoc/dataParser/extended/base/pipe/example.ts +++ b/jsDoc/dataParser/extended/base/pipe/example.ts @@ -1,6 +1,6 @@ import { DPE, DP, E, unwrap } from "@scripts"; -const parser = DPE.string().pipe(DPE.coerce.number()); +const parser = DPE.string().pipe(DPE.number().coerce()); const result = parser.parse("42"); if (E.isRight(result)) { const value = unwrap(result); diff --git a/jsDoc/dataParser/extended/bigint/example.ts b/jsDoc/dataParser/extended/bigint/example.ts index 1d70e9d75..2d9b5c10a 100644 --- a/jsDoc/dataParser/extended/bigint/example.ts +++ b/jsDoc/dataParser/extended/bigint/example.ts @@ -7,7 +7,7 @@ if (E.isRight(result)) { // value: bigint } -const coerceParser = DPE.coerce.bigint(); +const coerceParser = DPE.bigint().coerce(); const coerceResult = coerceParser.parse("42"); const onlySmall = DPE.bigint().max(3n); diff --git a/jsDoc/dataParser/extended/boolean/example.ts b/jsDoc/dataParser/extended/boolean/example.ts index 6f489aef4..0f9286838 100644 --- a/jsDoc/dataParser/extended/boolean/example.ts +++ b/jsDoc/dataParser/extended/boolean/example.ts @@ -7,7 +7,7 @@ if (E.isRight(result)) { // value: boolean } -const coerceParser = DPE.coerce.boolean(); +const coerceParser = DPE.boolean().coerce(); const coerceResult = coerceParser.parse("false"); const optionalBool = DPE.boolean().optional(); diff --git a/jsDoc/dataParser/extended/coercer/example.ts b/jsDoc/dataParser/extended/coercer/example.ts new file mode 100644 index 000000000..7f82dc378 --- /dev/null +++ b/jsDoc/dataParser/extended/coercer/example.ts @@ -0,0 +1,25 @@ +import { DPE, E, unwrap } from "@scripts"; + +const parser = DPE.number() + .min(4) + .max(10) + .coerce(); +const result = parser.parse("7"); +if (E.isRight(result)) { + const value = unwrap(result); + // value: number +} + +const direct = DPE.coercer(DPE.boolean()); +const directResult = direct.parse("true"); + +const complex = DPE.object({ + name: DPE.string().coerce(), + age: DPE.number() + .min(0) + .coerce(), +}); +const complexResult = complex.parse({ + name: 123, + age: "42", +}); diff --git a/jsDoc/dataParser/extended/coercer/index.md b/jsDoc/dataParser/extended/coercer/index.md new file mode 100644 index 000000000..aaafcddda --- /dev/null +++ b/jsDoc/dataParser/extended/coercer/index.md @@ -0,0 +1,19 @@ +Creates an extended coercer parser around another DataParser. + +Signature: `DPE.coercer(inner, definition?)` -> `DataParserCoercerExtended` + +This parser does not represent a data type by itself. It represents a parsing action, like `DPE.pipe(...)` or `DPE.lazy(...)`: it receives an input, transforms it with the transformer registered for the inner parser kind, then gives the transformed value to the inner parser. + +```ts +{@include dataParser/extended/coercer/example.ts[3,25]} +``` + +@remarks +- Parsed output is always the output of the inner parser. +- Accepted input is widened with the values supported by the inner parser coercion transformer. +- Prefer the fluent `.coerce()` method on extended parsers when you need parser-specific methods before coercion. +- Use `DPE.coercer(parser)` when you need the direct constructor form. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DPE diff --git a/jsDoc/dataParser/extended/coercer/transformers/example.ts b/jsDoc/dataParser/extended/coercer/transformers/example.ts new file mode 100644 index 000000000..c303bd389 --- /dev/null +++ b/jsDoc/dataParser/extended/coercer/transformers/example.ts @@ -0,0 +1,17 @@ +import { DPE, DP } from "@scripts"; + +const numberTransformer = DPE.DataParserCoercerExtended.transformers.get( + DP.numberKind, +); + +DPE.DataParserCoercerExtended.transformers.set( + DP.stringKind, + (value) => String(value), +); + +const parser = DPE.string().coerce(); +const result = parser.parse(42); + +if (numberTransformer) { + DPE.DataParserCoercerExtended.transformers.set(DP.numberKind, numberTransformer); +} diff --git a/jsDoc/dataParser/extended/coercer/transformers/index.md b/jsDoc/dataParser/extended/coercer/transformers/index.md new file mode 100644 index 000000000..40edfdc7b --- /dev/null +++ b/jsDoc/dataParser/extended/coercer/transformers/index.md @@ -0,0 +1,18 @@ +Defines the transformer map used by extended coercer parsers. + +Signature: `DPE.DataParserCoercerExtended.transformers` -> `Map` + +This map is shared with the classic coercer. It links a DataParser kind to the function that prepares raw input before the inner parser runs. The fluent `.coerce()` method is available on all extended parsers, but only registered kinds widen their accepted input with a transformer. + +```ts +{@include dataParser/extended/coercer/transformers/example.ts[3,16]} +``` + +@remarks +- This property is the source of truth for the parser kinds that support coercion. +- A coercer memoizes the transformer it resolves during its first parse. +- Changing this map is an advanced extension point. + +@see https://utils.duplojs.dev/en/v1/api/dataParser/coercer + +@namespace DPE diff --git a/jsDoc/dataParser/extended/date/example.ts b/jsDoc/dataParser/extended/date/example.ts index 9d6c74d99..056754f46 100644 --- a/jsDoc/dataParser/extended/date/example.ts +++ b/jsDoc/dataParser/extended/date/example.ts @@ -7,7 +7,7 @@ if (E.isRight(result)) { // value: TheDate } -const coerceParser = DPE.coerce.date(); +const coerceParser = DPE.date().coerce(); const coerceResult = coerceParser.parse("2024-01-01T00:00:00.000Z"); // coerceResult: E.Error | E.Success diff --git a/jsDoc/dataParser/extended/date/index.md b/jsDoc/dataParser/extended/date/index.md index ccc41c326..6bd772fc9 100644 --- a/jsDoc/dataParser/extended/date/index.md +++ b/jsDoc/dataParser/extended/date/index.md @@ -10,7 +10,7 @@ This parser extends classic `DP.date(...)` behavior and keeps the extended chain @remarks - Parsed output is always `TheDate`. -- `DPE.coerce.date()` enables coercion by default. +- `DPE.date().coerce()` enables coercion after date-specific configuration. @see https://utils.duplojs.dev/en/v1/api/dataParser/date diff --git a/jsDoc/dataParser/extended/empty/example.ts b/jsDoc/dataParser/extended/empty/example.ts index 3265a7ad2..9f008cd87 100644 --- a/jsDoc/dataParser/extended/empty/example.ts +++ b/jsDoc/dataParser/extended/empty/example.ts @@ -6,7 +6,7 @@ if (E.isRight(result)) { // E.Success } -const coerceParser = DPE.coerce.empty(); +const coerceParser = DPE.empty().coerce(); const coerceResult = coerceParser.parse("undefined"); const optionalEmpty = DPE.empty().optional(); diff --git a/jsDoc/dataParser/extended/nil/example.ts b/jsDoc/dataParser/extended/nil/example.ts index 4d8d2fd01..434f91ac0 100644 --- a/jsDoc/dataParser/extended/nil/example.ts +++ b/jsDoc/dataParser/extended/nil/example.ts @@ -7,7 +7,7 @@ if (E.isRight(result)) { // value: null } -const coerceParser = DPE.coerce.nil(); +const coerceParser = DPE.nil().coerce(); const coerceResult = coerceParser.parse("null"); const nullableNil = DPE.nil().nullable(); diff --git a/jsDoc/dataParser/extended/number/example.ts b/jsDoc/dataParser/extended/number/example.ts index 9b977cb9b..9eecfb306 100644 --- a/jsDoc/dataParser/extended/number/example.ts +++ b/jsDoc/dataParser/extended/number/example.ts @@ -10,7 +10,7 @@ if (E.isRight(result)) { // value: number } -const coerceParser = DPE.coerce.number(); +const coerceParser = DPE.number().coerce(); const coerceResult = coerceParser.parse("42"); const intOnly = DPE.number().int(); diff --git a/jsDoc/dataParser/extended/pipe/example.ts b/jsDoc/dataParser/extended/pipe/example.ts index e8aac0dfd..7b60e626f 100644 --- a/jsDoc/dataParser/extended/pipe/example.ts +++ b/jsDoc/dataParser/extended/pipe/example.ts @@ -1,6 +1,6 @@ import { DPE, E, unwrap } from "@scripts"; -const parser = DPE.pipe(DPE.string(), DPE.coerce.number()); +const parser = DPE.pipe(DPE.string(), DPE.number().coerce()); const result = parser.parse("42"); if (E.isRight(result)) { const value = unwrap(result); diff --git a/jsDoc/dataParser/extended/string/example.ts b/jsDoc/dataParser/extended/string/example.ts index e441019f5..3ac8f1b21 100644 --- a/jsDoc/dataParser/extended/string/example.ts +++ b/jsDoc/dataParser/extended/string/example.ts @@ -10,5 +10,7 @@ if (E.isRight(result)) { const withRegex = DPE.string().regex(/^[A-Z][a-z]+$/); const regexResult = withRegex.parse("Duplo"); -const coerceParser = DPE.coerce.string().min(2); +const coerceParser = DPE.string() + .min(2) + .coerce(); const coerceResult = coerceParser.parse(123); diff --git a/jsDoc/dataParser/extended/time/example.ts b/jsDoc/dataParser/extended/time/example.ts index c5852ed7a..5e8602bac 100644 --- a/jsDoc/dataParser/extended/time/example.ts +++ b/jsDoc/dataParser/extended/time/example.ts @@ -12,7 +12,7 @@ if (E.isRight(result)) { // value: TheTime } -const coerceParser = DPE.coerce.time(); +const coerceParser = DPE.time().coerce(); const coerceResult = coerceParser.parse("10:20:00"); // E.Error | E.Success diff --git a/jsDoc/dataParser/extended/time/index.md b/jsDoc/dataParser/extended/time/index.md index d30f08339..5ddbf5be2 100644 --- a/jsDoc/dataParser/extended/time/index.md +++ b/jsDoc/dataParser/extended/time/index.md @@ -10,7 +10,7 @@ This parser extends the classic time parser behavior and adds fluent methods lik @remarks - `.min(...)` and `.max(...)` expect `TheTime` values. -- `DPE.coerce.time()` enables the same coercion flow as classic parser mode. +- `DPE.time().coerce()` enables the same coercion flow after time-specific configuration. @see https://utils.duplojs.dev/en/v1/api/dataParser/time diff --git a/scripts/clean/toMapDataParser.ts b/scripts/clean/toMapDataParser.ts index 3fc57da6a..b395931af 100644 --- a/scripts/clean/toMapDataParser.ts +++ b/scripts/clean/toMapDataParser.ts @@ -3,8 +3,8 @@ import * as DPattern from "../pattern"; import { constrainedTypeKind, constraintHandlerKind, constraintsSetHandlerKind, type ConstraintHandler, type ConstraintsSetHandler, type GetConstraint, type GetConstraints } from "./constraint"; import { newTypeHandlerKind, newTypeKind } from "./newType"; import { primitiveHandlerKind, type PrimitiveHandler } from "./primitive"; -import { type EntityPropertyDefinition, entityPropertyUnionKind, entityPropertyIdentifierKind, entityPropertyStructureKind, entityPropertyArrayKind, entityPropertyNullableKind, entityPropertyDefinitionToDataParser, type EntityProperty } from "./entity"; -import { hasSomeKinds, keyWrappedValue } from "@scripts/common"; +import { type EntityPropertyDefinition, entityPropertyUnionKind, entityPropertyIdentifierKind, entityPropertyStructureKind, entityPropertyArrayKind, entityPropertyNullableKind, entityPropertyDefinitionToDataParser, type EntityInputRawProperty, type EntityProperty } from "./entity"; +import { hasSomeKinds, type IsNever, keyWrappedValue } from "@scripts/common"; type ToMapDataParserInput = ( | ConstraintHandler @@ -25,6 +25,24 @@ type OutputDataParser< ? EntityProperty : never; +type InputDataParser< + GenericInput extends ToMapDataParserInput, +> = GenericInput extends ConstraintHandler + ? IsNever extends true + ? InferredValue + : InferredInput + : GenericInput extends ConstraintsSetHandler + ? IsNever extends true + ? InferredValue + : InferredInput + : GenericInput extends PrimitiveHandler + ? IsNever extends true + ? InferredValue + : InferredInput + : GenericInput extends EntityPropertyDefinition + ? EntityInputRawProperty + : never; + interface ToMapDataParserParams { coerce?: boolean; } @@ -34,13 +52,14 @@ interface ToMapDataParserParams { */ export function toMapDataParser< GenericInput extends ToMapDataParserInput, - GenericOutput extends OutputDataParser, + GenericOutputDataParser extends OutputDataParser = OutputDataParser, + GenericInputDataParser extends InputDataParser = InputDataParser, >( input: GenericInput, params?: ToMapDataParserParams, ): DDataParser.DataParser< - NoInfer, - unknown + NoInfer, + NoInfer >; export function toMapDataParser( @@ -68,29 +87,7 @@ export function toMapDataParser( ); } - const dataParser = (primitiveHandlerKind.has(input) - ? input.internal.dataParser.clone() - : input.internal.dataParser.clone()) as DDataParser.DataParsers; - - if ( - params?.coerce - && hasSomeKinds( - dataParser, - [ - DDataParser.stringKind, - DDataParser.numberKind, - DDataParser.bigIntKind, - DDataParser.bigIntKind, - DDataParser.booleanKind, - DDataParser.dateKind, - DDataParser.timeKind, - DDataParser.emptyKind, - DDataParser.nilKind, - ], - ) - ) { - (dataParser.definition.coerce as any) = true; - } + const dataParser = input.internal.dataParser.clone() as DDataParser.DataParsers; const valueContainer = DPattern.match(input) .when( @@ -114,7 +111,9 @@ export function toMapDataParser( .exhaustive(); return DDataParser.transform( - dataParser, + params?.coerce + ? DDataParser.coercer(dataParser) + : dataParser, (value) => ({ ...valueContainer, [keyWrappedValue]: value, diff --git a/scripts/dataParser/base.ts b/scripts/dataParser/base.ts index ec0422bd8..87e5d5f9a 100644 --- a/scripts/dataParser/base.ts +++ b/scripts/dataParser/base.ts @@ -75,6 +75,7 @@ export abstract class DataParserBase< prepareDefinition( ...args: never[] ): DataParserDefinition; + readonly specificKindHandler: DCommon.KindHandler; } ); diff --git a/scripts/dataParser/extended/base.ts b/scripts/dataParser/extended/base.ts index 7e1aa3af0..695d8da22 100644 --- a/scripts/dataParser/extended/base.ts +++ b/scripts/dataParser/extended/base.ts @@ -2,9 +2,9 @@ import * as DCommon from "@scripts/common"; import { createDataParserKind } from "../kind"; import { DataParserBase, type DataParser, type DataParserDefinition } from "../base"; import * as dataParsers from "../parsers"; -import { type DataParserError } from "../error"; -import { type DataParserChecker, type DataParserCheckerBase, type DataParserCheckerDefinition } from "../baseChecker"; -import { type Output, type PrepareDataParserDefinition, type DataParserExtendedBaseInit, type MergeDefinition, type Input, type AddCheckersToDefinition } from "../types"; +import type { DataParserError } from "../error"; +import type { DataParserChecker, DataParserCheckerBase, DataParserCheckerDefinition } from "../baseChecker"; +import type { Output, PrepareDataParserDefinition, DataParserExtendedBaseInit, MergeDefinition, Input, AddCheckersToDefinition } from "../types"; export const dataParserExtendedKind = createDataParserKind("extended"); @@ -391,6 +391,20 @@ export abstract class DataParserBaseExtended< ); } + /** + * {@include dataParser/extended/base/coerce/index.md} + */ + public coerce< + GenericThis extends this = this, + >(): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + { inner: GenericThis } + > + > { + return DataParserCoercerExtended.create(this) as never; + } + public static initExtended< GenericConstructor extends DCommon.SimplifyTopLevel>, >( @@ -1121,6 +1135,86 @@ export class DataParserErrorHandlerExtended< } } +export class DataParserCoercerExtended< + GenericDefinition extends dataParsers.DataParserDefinitionCoercer = dataParsers.DataParserDefinitionCoercer, +> extends DataParserBaseExtended.initExtended(dataParsers.DataParserCoercer)< + GenericDefinition, + Output>, + Input> + > { + public get classConstructor() { + return this.checkConstructor(DataParserCoercerExtended); + } + + /** + * {@include dataParser/extended/coercer/transformers/index.md} + */ + public static transformers = dataParsers.DataParserCoercer.transformers; + + public declare addChecker: < + GenericChecker extends readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + >( + ...args: DCommon.FixDeepFunctionInfer< + readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + GenericChecker + > + ) => DataParserCoercerExtended< + AddCheckersToDefinition< + GenericDefinition, + GenericChecker + > + >; + + public declare refine: ( + theFunction: (input: Output) => boolean, + definition?: Partial< + Omit + >, + ) => DataParserCoercerExtended< + AddCheckersToDefinition< + GenericDefinition, + readonly [dataParsers.CheckerRefineImplementation>] + > + >; + + /** + * {@include dataParser/extended/coercer/index.md} + */ + public static override create< + GenericDataParser extends DataParser, + const GenericDefinition extends PrepareDataParserDefinition< + dataParsers.DataParserDefinitionCoercer< + Output + >, + "inner" + > = never, + >( + inner: GenericDataParser, + definition?: DCommon.FixDeepFunctionInfer< + PrepareDataParserDefinition< + dataParsers.DataParserDefinitionCoercer< + Output + >, + "inner" + >, + GenericDefinition + >, + ): DataParserCoercerExtended< + MergeDefinition< + dataParsers.DataParserDefinitionCoercer, + DCommon.NeverCoalescing & { inner: GenericDataParser } + > + > { + return new DataParserCoercerExtended(this.prepareDefinition(inner, definition)) as never; + } +} + export interface DataParserExtended< GenericOutput extends unknown = unknown, GenericInput extends unknown = GenericOutput, diff --git a/scripts/dataParser/extended/bigint.ts b/scripts/dataParser/extended/bigint.ts index 0abac2d99..1a85ad300 100644 --- a/scripts/dataParser/extended/bigint.ts +++ b/scripts/dataParser/extended/bigint.ts @@ -1,8 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserBigIntExtended< GenericDefinition extends dataParsers.DataParserDefinitionBigInt = dataParsers.DataParserDefinitionBigInt, diff --git a/scripts/dataParser/extended/boolean.ts b/scripts/dataParser/extended/boolean.ts index c6b071d58..957933c5f 100644 --- a/scripts/dataParser/extended/boolean.ts +++ b/scripts/dataParser/extended/boolean.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserBooleanExtended< GenericDefinition extends dataParsers.DataParserDefinitionBoolean = dataParsers.DataParserDefinitionBoolean, diff --git a/scripts/dataParser/extended/coerce/bigint.ts b/scripts/dataParser/extended/coerce/bigint.ts index f77767684..abf6a9dd4 100644 --- a/scripts/dataParser/extended/coerce/bigint.ts +++ b/scripts/dataParser/extended/coerce/bigint.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.bigint().coerce()` instead. + */ export function bigint< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionBigInt, diff --git a/scripts/dataParser/extended/coerce/boolean.ts b/scripts/dataParser/extended/coerce/boolean.ts index da75358c4..4f3bb9222 100644 --- a/scripts/dataParser/extended/coerce/boolean.ts +++ b/scripts/dataParser/extended/coerce/boolean.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.boolean().coerce()` instead. + */ export function boolean< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionBoolean, @@ -27,3 +30,4 @@ export function boolean< coerce: true, }); } + diff --git a/scripts/dataParser/extended/coerce/date.ts b/scripts/dataParser/extended/coerce/date.ts index 10b29e2e5..e9064f9a4 100644 --- a/scripts/dataParser/extended/coerce/date.ts +++ b/scripts/dataParser/extended/coerce/date.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.date().coerce()` instead. + */ export function date< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionDate, @@ -27,3 +30,4 @@ export function date< coerce: true, }); } + diff --git a/scripts/dataParser/extended/coerce/empty.ts b/scripts/dataParser/extended/coerce/empty.ts index 4ef510f64..4a2ba9563 100644 --- a/scripts/dataParser/extended/coerce/empty.ts +++ b/scripts/dataParser/extended/coerce/empty.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.empty().coerce()` instead. + */ export function empty< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionEmpty, @@ -27,3 +30,4 @@ export function empty< coerce: true, }); } + diff --git a/scripts/dataParser/extended/coerce/nil.ts b/scripts/dataParser/extended/coerce/nil.ts index 5fc3dcd85..2d3dacb37 100644 --- a/scripts/dataParser/extended/coerce/nil.ts +++ b/scripts/dataParser/extended/coerce/nil.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.nil().coerce()` instead. + */ export function nil< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionNil, diff --git a/scripts/dataParser/extended/coerce/number.ts b/scripts/dataParser/extended/coerce/number.ts index 37ac7a5aa..b1e3ac42e 100644 --- a/scripts/dataParser/extended/coerce/number.ts +++ b/scripts/dataParser/extended/coerce/number.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.number().coerce()` instead. + */ export function number< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionNumber, diff --git a/scripts/dataParser/extended/coerce/string.ts b/scripts/dataParser/extended/coerce/string.ts index 9f058838f..018180fdc 100644 --- a/scripts/dataParser/extended/coerce/string.ts +++ b/scripts/dataParser/extended/coerce/string.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.string().coerce()` instead. + */ export function string< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionString, diff --git a/scripts/dataParser/extended/coerce/time.ts b/scripts/dataParser/extended/coerce/time.ts index e1a872125..cc3dd21b7 100644 --- a/scripts/dataParser/extended/coerce/time.ts +++ b/scripts/dataParser/extended/coerce/time.ts @@ -1,8 +1,11 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import type * as dataParsers from "../../parsers"; import * as dataParsersExtended from ".."; +/** + * @deprecated Use `dataParsersExtended.time().coerce()` instead. + */ export function time< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionTime, diff --git a/scripts/dataParser/extended/coercer.ts b/scripts/dataParser/extended/coercer.ts new file mode 100644 index 000000000..789fae14b --- /dev/null +++ b/scripts/dataParser/extended/coercer.ts @@ -0,0 +1,7 @@ +import { detachObjectMethod } from "@scripts/common"; +import { DataParserCoercerExtended } from "./base"; + +/** + * {@include dataParser/extended/coercer/index.md} + */ +export const coercer = detachObjectMethod(DataParserCoercerExtended, "create"); diff --git a/scripts/dataParser/extended/date.ts b/scripts/dataParser/extended/date.ts index 2295ed1c5..b203d9584 100644 --- a/scripts/dataParser/extended/date.ts +++ b/scripts/dataParser/extended/date.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserDateExtended< GenericDefinition extends dataParsers.DataParserDefinitionDate = dataParsers.DataParserDefinitionDate, diff --git a/scripts/dataParser/extended/empty.ts b/scripts/dataParser/extended/empty.ts index 96ed55aa5..f18d2d35b 100644 --- a/scripts/dataParser/extended/empty.ts +++ b/scripts/dataParser/extended/empty.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserEmptyExtended< GenericDefinition extends dataParsers.DataParserDefinitionEmpty = dataParsers.DataParserDefinitionEmpty, diff --git a/scripts/dataParser/extended/index.ts b/scripts/dataParser/extended/index.ts index 55eb79ea6..1b0656ff9 100644 --- a/scripts/dataParser/extended/index.ts +++ b/scripts/dataParser/extended/index.ts @@ -1,5 +1,3 @@ -export * as coerce from "./coerce"; - export * from "./string"; export * from "./array"; export * from "./bigint"; @@ -23,6 +21,12 @@ export * from "./tuple"; export * from "./unknown"; export * from "./recover"; export * from "./errorHandler"; +export * from "./coercer"; + +/** + * @deprecated Use `DPE.().coerce()` when the method exists, or `DPE.coercer(...)`. + */ +export * as coerce from "./coerce"; export * from "../error"; export { type DataParser } from "../base"; diff --git a/scripts/dataParser/extended/literal.ts b/scripts/dataParser/extended/literal.ts index 4ae52688b..00414a609 100644 --- a/scripts/dataParser/extended/literal.ts +++ b/scripts/dataParser/extended/literal.ts @@ -1,8 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserLiteralExtended< GenericDefinition extends dataParsers.DataParserDefinitionLiteral = dataParsers.DataParserDefinitionLiteral, diff --git a/scripts/dataParser/extended/nil.ts b/scripts/dataParser/extended/nil.ts index e4c5ddd2e..a4477ad01 100644 --- a/scripts/dataParser/extended/nil.ts +++ b/scripts/dataParser/extended/nil.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserNilExtended< GenericDefinition extends dataParsers.DataParserDefinitionNil = dataParsers.DataParserDefinitionNil, diff --git a/scripts/dataParser/extended/number.ts b/scripts/dataParser/extended/number.ts index e4393002a..cb1b9137e 100644 --- a/scripts/dataParser/extended/number.ts +++ b/scripts/dataParser/extended/number.ts @@ -1,8 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserNumberExtended< GenericDefinition extends dataParsers.DataParserDefinitionNumber = dataParsers.DataParserDefinitionNumber, diff --git a/scripts/dataParser/extended/object.ts b/scripts/dataParser/extended/object.ts index 700403596..f6dd2d01b 100644 --- a/scripts/dataParser/extended/object.ts +++ b/scripts/dataParser/extended/object.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type Adaptor, type FixDeepFunctionInfer, type NeverCoalescing, type SimplifyTopLevel } from "@scripts/common"; import { type AssignObjects } from "@scripts/object"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserObjectExtended< GenericDefinition extends dataParsers.DataParserDefinitionObject = dataParsers.DataParserDefinitionObject, diff --git a/scripts/dataParser/extended/string.ts b/scripts/dataParser/extended/string.ts index afd4e8230..6ff744544 100644 --- a/scripts/dataParser/extended/string.ts +++ b/scripts/dataParser/extended/string.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserStringExtended< GenericDefinition extends dataParsers.DataParserDefinitionString = dataParsers.DataParserDefinitionString, diff --git a/scripts/dataParser/extended/templateLiteral.ts b/scripts/dataParser/extended/templateLiteral.ts index e2b213717..406ab812b 100644 --- a/scripts/dataParser/extended/templateLiteral.ts +++ b/scripts/dataParser/extended/templateLiteral.ts @@ -1,8 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserTemplateLiteralExtended< GenericDefinition extends dataParsers.DataParserDefinitionTemplateLiteral diff --git a/scripts/dataParser/extended/time.ts b/scripts/dataParser/extended/time.ts index ed50d58c4..1d83acc8d 100644 --- a/scripts/dataParser/extended/time.ts +++ b/scripts/dataParser/extended/time.ts @@ -1,8 +1,8 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; import { type TheTime } from "@scripts/date"; export class DataParserTimeExtended< diff --git a/scripts/dataParser/extended/unknown.ts b/scripts/dataParser/extended/unknown.ts index b3e4d6f1d..4a738e274 100644 --- a/scripts/dataParser/extended/unknown.ts +++ b/scripts/dataParser/extended/unknown.ts @@ -1,9 +1,9 @@ import { detachObjectMethod, type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; import { DataParserBaseExtended } from "./base"; -import { type AddCheckersToDefinition, type Output, type MergeDefinition, type PrepareDataParserDefinition, type Input } from "../types"; +import type { AddCheckersToDefinition, Output, MergeDefinition, PrepareDataParserDefinition, Input } from "../types"; import * as dataParsers from "../parsers"; -import { type DataParserChecker } from "../baseChecker"; +import type { DataParserChecker } from "../baseChecker"; export class DataParserUnknownExtended< GenericDefinition extends dataParsers.DataParserDefinitionUnknown = dataParsers.DataParserDefinitionUnknown, diff --git a/scripts/dataParser/parsers/bigint/index.ts b/scripts/dataParser/parsers/bigint/index.ts index d911fa4b6..c26dac752 100644 --- a/scripts/dataParser/parsers/bigint/index.ts +++ b/scripts/dataParser/parsers/bigint/index.ts @@ -12,6 +12,10 @@ export type DataParserBigIntCheckers = GetEligibleChecker; export interface DataParserDefinitionBigInt extends DataParserDefinition< DataParserBigIntCheckers > { + + /** + * @deprecated Use `DDataParser.coercer(DDataParser.bigint())` instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/boolean.ts b/scripts/dataParser/parsers/boolean.ts index b0d11c13e..992a288d4 100644 --- a/scripts/dataParser/parsers/boolean.ts +++ b/scripts/dataParser/parsers/boolean.ts @@ -10,6 +10,10 @@ export type DataParserBooleanCheckers = GetEligibleChecker; export interface DataParserDefinitionBoolean extends DataParserDefinition< DataParserBooleanCheckers > { + + /** + * @deprecated Use `DDataParser.coercer(DDataParser.boolean())` instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/coerce/bigint.ts b/scripts/dataParser/parsers/coerce/bigint.ts index 9ac299f90..c7ad39b20 100644 --- a/scripts/dataParser/parsers/coerce/bigint.ts +++ b/scripts/dataParser/parsers/coerce/bigint.ts @@ -1,7 +1,10 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.bigint())` instead. + */ export function bigint< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionBigInt, diff --git a/scripts/dataParser/parsers/coerce/boolean.ts b/scripts/dataParser/parsers/coerce/boolean.ts index d642b32ad..761db71c2 100644 --- a/scripts/dataParser/parsers/coerce/boolean.ts +++ b/scripts/dataParser/parsers/coerce/boolean.ts @@ -1,7 +1,10 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.boolean())` instead. + */ export function boolean< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionBoolean, diff --git a/scripts/dataParser/parsers/coerce/date.ts b/scripts/dataParser/parsers/coerce/date.ts index 127e2070a..757944ce9 100644 --- a/scripts/dataParser/parsers/coerce/date.ts +++ b/scripts/dataParser/parsers/coerce/date.ts @@ -1,7 +1,10 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.date())` instead. + */ export function date< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionDate, diff --git a/scripts/dataParser/parsers/coerce/empty.ts b/scripts/dataParser/parsers/coerce/empty.ts index e5cc32503..b4543898d 100644 --- a/scripts/dataParser/parsers/coerce/empty.ts +++ b/scripts/dataParser/parsers/coerce/empty.ts @@ -1,7 +1,10 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.empty())` instead. + */ export function empty< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionEmpty, diff --git a/scripts/dataParser/parsers/coerce/nil.ts b/scripts/dataParser/parsers/coerce/nil.ts index 9fe3c5476..42d0ff10a 100644 --- a/scripts/dataParser/parsers/coerce/nil.ts +++ b/scripts/dataParser/parsers/coerce/nil.ts @@ -1,7 +1,10 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.nil())` instead. + */ export function nil< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionNil, diff --git a/scripts/dataParser/parsers/coerce/number.ts b/scripts/dataParser/parsers/coerce/number.ts index 35074e748..2f9b3eaa6 100644 --- a/scripts/dataParser/parsers/coerce/number.ts +++ b/scripts/dataParser/parsers/coerce/number.ts @@ -1,7 +1,10 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.number())` instead. + */ export function number< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionNumber, @@ -26,3 +29,4 @@ export function number< coerce: true, }); } + diff --git a/scripts/dataParser/parsers/coerce/string.ts b/scripts/dataParser/parsers/coerce/string.ts index bfd56a581..d1f4e40d2 100644 --- a/scripts/dataParser/parsers/coerce/string.ts +++ b/scripts/dataParser/parsers/coerce/string.ts @@ -1,7 +1,10 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.string())` instead. + */ export function string< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionString, @@ -26,3 +29,4 @@ export function string< coerce: true, }); } + diff --git a/scripts/dataParser/parsers/coerce/time.ts b/scripts/dataParser/parsers/coerce/time.ts index ee91844a7..636ac596a 100644 --- a/scripts/dataParser/parsers/coerce/time.ts +++ b/scripts/dataParser/parsers/coerce/time.ts @@ -1,7 +1,10 @@ -import { type FixDeepFunctionInfer, type NeverCoalescing } from "@scripts/common"; -import { type MergeDefinition, type PrepareDataParserDefinition } from "../../types"; +import type { FixDeepFunctionInfer, NeverCoalescing } from "@scripts/common"; +import type { MergeDefinition, PrepareDataParserDefinition } from "@scripts/dataParser/types"; import * as dataParsers from ".."; +/** + * @deprecated Use `DP.coercer(DP.time())` instead. + */ export function time< const GenericDefinition extends PrepareDataParserDefinition< dataParsers.DataParserDefinitionTime, diff --git a/scripts/dataParser/parsers/coercer/index.ts b/scripts/dataParser/parsers/coercer/index.ts new file mode 100644 index 000000000..771723d34 --- /dev/null +++ b/scripts/dataParser/parsers/coercer/index.ts @@ -0,0 +1,205 @@ +import { detachObjectMethod, type FixDeepFunctionInfer, type Memoized, memo, type NeverCoalescing, type KindHandler, type AnyFunction, callThen, forward } from "@scripts/common"; +import { DataParserBase, type DataParser, type DataParserDefinition } from "../../base"; +import { createDataParserKind } from "../../kind"; +import { addIssue, type DataParserError } from "../../error"; +import type { DataParserChecker } from "../../baseChecker"; +import type { ApplyRefinementOfDefinition, AddCheckersToDefinition, GetEligibleChecker, Input, MergeDefinition, Output, PrepareDataParserDefinition } from "../../types"; +import * as coercerTransformers from "./transformers"; +import * as dataParsers from ".."; + +export interface ComputeInputDataParserCoercer< + GenericDataParser extends DataParser, +> { + bigint: GenericDataParser extends dataParsers.DataParserBigInt + ? string | number | boolean + : never; + boolean: GenericDataParser extends dataParsers.DataParserBoolean + ? string | number + : never; + empty: GenericDataParser extends dataParsers.DataParserEmpty + ? "undefined" + : never; + nil: GenericDataParser extends dataParsers.DataParserNil + ? "null" + : never; + number: GenericDataParser extends dataParsers.DataParserNumber + ? string | bigint | boolean | null + : never; + string: GenericDataParser extends dataParsers.DataParserString + ? number | bigint | boolean | symbol | null | undefined + : never; + date: GenericDataParser extends dataParsers.DataParserDate + ? string | number + : never; + time: GenericDataParser extends dataParsers.DataParserTime + ? string + : never; + templateLiteral: GenericDataParser extends dataParsers.DataParserTemplateLiteral + ? number | bigint | boolean + : never; +} + +export type DataParserCoercerInput< + GenericDataParser extends DataParser, +> = ComputeInputDataParserCoercer extends infer InferredCoercer + ? ( + | Input + | InferredCoercer[keyof InferredCoercer] + ) + : never; + +export type DataParserCoercerCheckers< + GenericInput extends unknown = unknown, +> = GetEligibleChecker; + +export interface DataParserDefinitionCoercer< + GenericOutput extends unknown = unknown, +> extends DataParserDefinition< + DataParserCoercerCheckers + > { + readonly inner: DataParser; + readonly transformer: Memoized; +} + +export const coercerKind = createDataParserKind("coercer"); + +export class DataParserCoercer< + GenericDefinition extends DataParserDefinitionCoercer = DataParserDefinitionCoercer, +> extends DataParserBase.init( + coercerKind, + )< + GenericDefinition, + ApplyRefinementOfDefinition< + Output, + GenericDefinition + >, + DataParserCoercerInput + > { + public get classConstructor() { + return this.checkConstructor(DataParserCoercer); + } + + public declare addChecker: < + GenericChecker extends readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + >( + ...args: FixDeepFunctionInfer< + readonly [ + DataParserChecker>, + ...DataParserChecker>[], + ], + GenericChecker + > + ) => DataParserCoercer< + AddCheckersToDefinition< + GenericDefinition, + GenericChecker + > + >; + + public static override execParse( + self: DataParserCoercer, + data: unknown, + error: DataParserError, + ): unknown { + try { + const transformedData = self.definition.transformer.value + ? self.definition.transformer.value(data) + : data; + + return callThen( + self.definition.inner.exec(transformedData, error), + forward, + (catchError) => addIssue( + error, + "successful coerce result", + catchError, + self.definition.errorMessage, + self, + ), + ); + } catch (catchError) { + return addIssue( + error, + "successful coerce result", + catchError, + self.definition.errorMessage, + self, + ); + } + } + + public static override dataParserIsAsynchronous(self: DataParserCoercer) { + return self.definition.inner.isAsynchronous(); + } + + /** + * {@include dataParser/classic/coercer/transformers/index.md} + */ + public static transformers = new Map([ + [dataParsers.numberKind, coercerTransformers.numberTransformer], + [dataParsers.stringKind, coercerTransformers.stringTransformer], + [dataParsers.booleanKind, coercerTransformers.booleanTransformer], + [dataParsers.dateKind, coercerTransformers.dateTransformer], + [dataParsers.timeKind, coercerTransformers.timeTransformer], + [dataParsers.bigIntKind, coercerTransformers.bigintTransformer], + [dataParsers.emptyKind, coercerTransformers.emptyTransformer], + [dataParsers.nilKind, coercerTransformers.nilTransformer], + [dataParsers.templateLiteralKind, coercerTransformers.templateLiteralTransformer], + ]); + + public static override prepareDefinition( + inner: DataParser, + definition?: Partial>, + ): DataParserDefinitionCoercer { + return { + ...definition, + inner, + transformer: memo( + () => DataParserCoercer.transformers.get( + inner.classConstructor.specificKindHandler, + ), + ), + checkers: definition?.checkers ?? [], + errorMessage: definition?.errorMessage, + }; + } + + /** + * {@include dataParser/classic/coercer/index.md} + */ + public static override create< + GenericDataParser extends DataParser, + const GenericDefinition extends PrepareDataParserDefinition< + DataParserDefinitionCoercer< + Output + >, + "inner" + > = never, + >( + inner: GenericDataParser, + definition?: FixDeepFunctionInfer< + PrepareDataParserDefinition< + DataParserDefinitionCoercer< + Output + >, + "inner" + >, + GenericDefinition + >, + ): DataParserCoercer< + MergeDefinition< + DataParserDefinitionCoercer, + NeverCoalescing & { inner: GenericDataParser } + > + > { + return new DataParserCoercer(this.prepareDefinition(inner, definition)) as never; + } +} + +/** + * {@include dataParser/classic/coercer/index.md} + */ +export const coercer = detachObjectMethod(DataParserCoercer, "create"); diff --git a/scripts/dataParser/parsers/coercer/transformers/bigint.ts b/scripts/dataParser/parsers/coercer/transformers/bigint.ts new file mode 100644 index 000000000..52f6f4e7b --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/bigint.ts @@ -0,0 +1,7 @@ +export function bigintTransformer(data: unknown) { + try { + return BigInt(data as never); + } catch { + return data; + } +} diff --git a/scripts/dataParser/parsers/coercer/transformers/boolean.ts b/scripts/dataParser/parsers/coercer/transformers/boolean.ts new file mode 100644 index 000000000..6f1d8654b --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/boolean.ts @@ -0,0 +1,14 @@ +export function booleanTransformer(data: unknown) { + if (typeof data === "string") { + const lower = data.trim().toLowerCase(); + + if (lower === "true" || lower === "false") { + return lower === "true"; + } + } else if (typeof data === "number" && (data === 0 || data === 1)) { + return data === 1; + } + + return data; +} + diff --git a/scripts/dataParser/parsers/coercer/transformers/date.ts b/scripts/dataParser/parsers/coercer/transformers/date.ts new file mode 100644 index 000000000..9ae366bba --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/date.ts @@ -0,0 +1,20 @@ +import * as DDate from "@scripts/date"; + +export function dateTransformer(data: unknown) { + if ( + typeof data === "number" + && DDate.isSafeTimestamp(data) + ) { + return DDate.TheDate.new(data); + } + + if (typeof data === "string") { + const date = new Date(data); + const timestamp = date.getTime(); + if (DDate.isSafeTimestamp(timestamp)) { + return DDate.TheDate.new(timestamp); + } + } + + return data; +} diff --git a/scripts/dataParser/parsers/coercer/transformers/empty.ts b/scripts/dataParser/parsers/coercer/transformers/empty.ts new file mode 100644 index 000000000..79bb827fe --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/empty.ts @@ -0,0 +1,5 @@ +export function emptyTransformer(data: unknown) { + return data === "undefined" + ? undefined + : data; +} diff --git a/scripts/dataParser/parsers/coercer/transformers/index.ts b/scripts/dataParser/parsers/coercer/transformers/index.ts new file mode 100644 index 000000000..f6d534bfa --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/index.ts @@ -0,0 +1,9 @@ +export * from "./bigint"; +export * from "./boolean"; +export * from "./date"; +export * from "./empty"; +export * from "./nil"; +export * from "./number"; +export * from "./string"; +export * from "./time"; +export * from "./templateLiteral"; diff --git a/scripts/dataParser/parsers/coercer/transformers/nil.ts b/scripts/dataParser/parsers/coercer/transformers/nil.ts new file mode 100644 index 000000000..99845836e --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/nil.ts @@ -0,0 +1,5 @@ +export function nilTransformer(data: unknown) { + return data === "null" + ? null + : data; +} diff --git a/scripts/dataParser/parsers/coercer/transformers/number.ts b/scripts/dataParser/parsers/coercer/transformers/number.ts new file mode 100644 index 000000000..8b91c5d64 --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/number.ts @@ -0,0 +1,7 @@ +export function numberTransformer(data: unknown) { + try { + return Number(data); + } catch { + return data; + } +} diff --git a/scripts/dataParser/parsers/coercer/transformers/string.ts b/scripts/dataParser/parsers/coercer/transformers/string.ts new file mode 100644 index 000000000..0f12e2bb7 --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/string.ts @@ -0,0 +1,7 @@ +export function stringTransformer(data: unknown) { + try { + return String(data); + } catch { + return data; + } +} diff --git a/scripts/dataParser/parsers/coercer/transformers/templateLiteral.ts b/scripts/dataParser/parsers/coercer/transformers/templateLiteral.ts new file mode 100644 index 000000000..eab685fb0 --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/templateLiteral.ts @@ -0,0 +1,11 @@ +export function templateLiteralTransformer(data: unknown) { + if ( + typeof data === "number" + || typeof data === "bigint" + || typeof data === "boolean" + ) { + return String(data); + } + + return data; +} diff --git a/scripts/dataParser/parsers/coercer/transformers/time.ts b/scripts/dataParser/parsers/coercer/transformers/time.ts new file mode 100644 index 000000000..a66c92b3e --- /dev/null +++ b/scripts/dataParser/parsers/coercer/transformers/time.ts @@ -0,0 +1,17 @@ +import * as DDate from "@scripts/date"; +import * as DEither from "@scripts/either"; + +export function timeTransformer(data: unknown) { + if ( + typeof data === "string" + && DDate.isoTimeRegex.test(data) + ) { + const result = DDate.createTime({ value: data }); + + if (DEither.isRight(result)) { + return DEither.unwrapRight(result); + } + } + + return data; +} diff --git a/scripts/dataParser/parsers/date.ts b/scripts/dataParser/parsers/date.ts index fea0e8ae0..0eb70d125 100644 --- a/scripts/dataParser/parsers/date.ts +++ b/scripts/dataParser/parsers/date.ts @@ -11,6 +11,10 @@ export type DataParserDateCheckers = GetEligibleChecker; export interface DataParserDefinitionDate extends DataParserDefinition< DataParserDateCheckers > { + + /** + * @deprecated Use `DDataParser.coercer(DDataParser.date())` instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/empty.ts b/scripts/dataParser/parsers/empty.ts index cf646f809..7d4961b52 100644 --- a/scripts/dataParser/parsers/empty.ts +++ b/scripts/dataParser/parsers/empty.ts @@ -10,6 +10,10 @@ export type DataParserEmptyCheckers = GetEligibleChecker; export interface DataParserDefinitionEmpty extends DataParserDefinition< DataParserEmptyCheckers > { + + /** + * @deprecated Use `DDataParser.coercer(DDataParser.empty())` instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/index.ts b/scripts/dataParser/parsers/index.ts index bbb2a8b68..846b6972a 100644 --- a/scripts/dataParser/parsers/index.ts +++ b/scripts/dataParser/parsers/index.ts @@ -1,4 +1,3 @@ -export * as coerce from "./coerce"; export * from "./string"; export * from "./number"; export * from "./array"; @@ -23,3 +22,9 @@ export * from "./tuple"; export * from "./union"; export * from "./unknown"; export * from "./errorHandler"; +export * from "./coercer"; + +/** + * @deprecated Use DP.coercer() + */ +export * as coerce from "./coerce"; diff --git a/scripts/dataParser/parsers/nil.ts b/scripts/dataParser/parsers/nil.ts index 3fe4bed93..a91a1e52f 100644 --- a/scripts/dataParser/parsers/nil.ts +++ b/scripts/dataParser/parsers/nil.ts @@ -10,6 +10,10 @@ export type DataParserNilCheckers = GetEligibleChecker; export interface DataParserDefinitionNil extends DataParserDefinition< DataParserNilCheckers > { + + /** + * @deprecated Use DDataParser.coercer(DDataParser.nil()) instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/nullable.ts b/scripts/dataParser/parsers/nullable.ts index a6adde7dd..0141db4e8 100644 --- a/scripts/dataParser/parsers/nullable.ts +++ b/scripts/dataParser/parsers/nullable.ts @@ -1,4 +1,4 @@ -import { detachObjectMethod, callThen, type FixDeepFunctionInfer, type IsEqual, type NeverCoalescing } from "@scripts/common"; +import { detachObjectMethod, type FixDeepFunctionInfer, type IsEqual, type NeverCoalescing } from "@scripts/common"; import { createDataParserKind } from "@scripts/dataParser/kind"; import { DataParserBase, type DataParser, type DataParserDefinition } from "../base"; import { type DataParserError } from "@scripts/dataParser/error"; diff --git a/scripts/dataParser/parsers/number/index.ts b/scripts/dataParser/parsers/number/index.ts index 7c37921a3..04305ae21 100644 --- a/scripts/dataParser/parsers/number/index.ts +++ b/scripts/dataParser/parsers/number/index.ts @@ -12,6 +12,10 @@ export type DataParserNumberCheckers = GetEligibleChecker; export interface DataParserDefinitionNumber extends DataParserDefinition< DataParserNumberCheckers > { + + /** + * @deprecated Use `DDataParser.coercer(DDataParser.number())` instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/string/index.ts b/scripts/dataParser/parsers/string/index.ts index 9118df59b..e3fe243db 100644 --- a/scripts/dataParser/parsers/string/index.ts +++ b/scripts/dataParser/parsers/string/index.ts @@ -12,6 +12,10 @@ export type DataParserStringCheckers = GetEligibleChecker; export interface DataParserDefinitionString extends DataParserDefinition< DataParserStringCheckers > { + + /** + * @deprecated Use `DDataParser.coercer(DDataParser.string())` instead. + */ readonly coerce: boolean; } diff --git a/scripts/dataParser/parsers/time/index.ts b/scripts/dataParser/parsers/time/index.ts index 6caba5806..56a578b31 100644 --- a/scripts/dataParser/parsers/time/index.ts +++ b/scripts/dataParser/parsers/time/index.ts @@ -14,6 +14,10 @@ export type DataParserTimeCheckers = GetEligibleChecker; export interface DataParserDefinitionTime extends DataParserDefinition< DataParserTimeCheckers > { + + /** + * @deprecated Use `DDataParser.coercer(DDataParser.time())` instead. + */ readonly coerce: boolean; } diff --git a/scripts/index.ts b/scripts/index.ts index f43d5267a..1d4390fc8 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -25,7 +25,14 @@ export * as DPattern from "./pattern"; export * as DP from "./dataParser"; export * as DDataParser from "./dataParser"; +/** + * @deprecated Use the corresponding extended DataParser and call `.coerce()`. + */ export * as DPC from "./dataParser/parsers/coerce"; + +/** + * @deprecated Use the corresponding extended DataParser and call `.coerce()`. + */ export * as DDataParserCoerce from "./dataParser/parsers/coerce"; export * as DPE from "./dataParser/extended"; diff --git a/tests/clean/toMapDataParser.test.ts b/tests/clean/toMapDataParser.test.ts index e4b6993db..c3d38f4c5 100644 --- a/tests/clean/toMapDataParser.test.ts +++ b/tests/clean/toMapDataParser.test.ts @@ -26,6 +26,12 @@ describe("toMapDataParser", () => { DClean.GetNewType, "strict" >; + + type CheckInput = ExpectType< + DDataParser.Input, + string, + "strict" + >; }); it("maps constraint handler to a data parser with constraint kind", () => { @@ -48,6 +54,12 @@ describe("toMapDataParser", () => { DClean.GetConstraint, "strict" >; + + type CheckInput = ExpectType< + DDataParser.Input, + string, + "strict" + >; }); it("maps constraints set handler to a data parser with constraint kinds", () => { @@ -75,6 +87,12 @@ describe("toMapDataParser", () => { DClean.GetConstraints, "strict" >; + + type CheckInput = ExpectType< + DDataParser.Input, + string, + "strict" + >; }); it("maps primitive handler to a data parser with wrapped value only", () => { @@ -95,6 +113,12 @@ describe("toMapDataParser", () => { DClean.String, "strict" >; + + type CheckInput = ExpectType< + DDataParser.Input, + string, + "strict" + >; }); it("supports coerce option on supported parsers", () => { @@ -146,4 +170,36 @@ describe("toMapDataParser", () => { tags: [], })).toStrictEqual(DEither.error(expect.any(Object))); }); + + it("infers entity property from wrapped new type handlers", () => { + const Label = DClean.createNewType("label", DPE.string().coerce()); + const Count = DClean.createNewType("count", DPE.number().coerce()); + const parser = DClean.toMapDataParser( + DClean.entityPropertyDefinitionTools.structure({ + label: Label, + count: DClean.entityPropertyDefinitionTools.nullable(Count), + tags: DClean.entityPropertyDefinitionTools.array(Label), + }), + ); + + type CheckInput = ExpectType< + DDataParser.Input, + { + readonly label: string | number | bigint | boolean | symbol | null | undefined; + readonly count: string | number | bigint | boolean | null; + readonly tags: readonly (string | number | bigint | boolean | symbol | null | undefined)[]; + }, + "strict" + >; + + type CheckOut = ExpectType< + DDataParser.Output, + { + readonly label: DClean.NewType<"label", string, never>; + readonly count: DClean.NewType<"count", number, never> | null; + readonly tags: readonly DClean.NewType<"label", string, never>[]; + }, + "strict" + >; + }); }); diff --git a/tests/dataParser/extended/bigint.test.ts b/tests/dataParser/extended/bigint.test.ts index cce1c9e8b..17ab97de5 100644 --- a/tests/dataParser/extended/bigint.test.ts +++ b/tests/dataParser/extended/bigint.test.ts @@ -59,6 +59,24 @@ describe("extended.bigint", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.bigint().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + bigint, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean | bigint, + "strict" + >; + + expect(parser.parse("5")).toStrictEqual(DEither.success(5n)); + }); + it("supports min/max helpers", () => { const parser = extended.bigint(); expect(parser.min(2n).parse(1n)).toStrictEqual( diff --git a/tests/dataParser/extended/boolean.test.ts b/tests/dataParser/extended/boolean.test.ts index a61f9cb85..4fe24abc1 100644 --- a/tests/dataParser/extended/boolean.test.ts +++ b/tests/dataParser/extended/boolean.test.ts @@ -59,6 +59,24 @@ describe("extended.boolean", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.boolean().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + boolean, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean, + "strict" + >; + + expect(parser.parse("true")).toStrictEqual(DEither.success(true)); + }); + it("coerces when enabled", () => { const parser = extended.boolean({ coerce: true }); expect(parser.parse("true")).toStrictEqual(DEither.success(true)); diff --git a/tests/dataParser/extended/coercer.test.ts b/tests/dataParser/extended/coercer.test.ts new file mode 100644 index 000000000..957d2ba1d --- /dev/null +++ b/tests/dataParser/extended/coercer.test.ts @@ -0,0 +1,79 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +const { extended } = DDataParser; + +describe("extended.coercer", () => { + it("preserves output and extends input from eligible extended parser", () => { + const parser = extended.coercer(extended.number()); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(parser.parse("42")).toStrictEqual(DEither.success(42)); + }); + + it("keeps the exact inner parser type when coerce is called after fluent helpers", () => { + const inner = extended.number().min(4).max(10); + const parser = inner.coerce(); + + type _CheckInner = ExpectType< + typeof parser.definition.inner, + typeof inner, + "strict" + >; + + expect(parser.definition.inner).toBe(inner); + expect(parser.parse("5")).toStrictEqual(DEither.success(5)); + expect(parser.parse("3")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("does not mutate the inner parser when it creates the coercer", () => { + const inner = extended.string().min(2); + const parser = inner.coerce(); + + expect(inner.parse(42)).toStrictEqual(DEither.error(expect.any(Object))); + expect(parser.parse(42)).toStrictEqual(DEither.success("42")); + }); + + it("runs coercer checkers after inner parser succeeds", () => { + const parser = extended.number() + .min(4) + .coerce() + .addChecker(DDataParser.checkerNumberMax(10)); + + expect(parser.parse("8")).toStrictEqual(DEither.success(8)); + expect(parser.parse("11")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("keeps coercer input unrefined when checker refines output", () => { + const parser = extended.number().coerce().addChecker( + DDataParser.checkerRefine( + (value): value is 42 => value === 42, + ), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + 42, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(parser.parse("42")).toStrictEqual(DEither.success(42)); + expect(parser.parse("41")).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/extended/date.test.ts b/tests/dataParser/extended/date.test.ts index 496312902..76a4c3dc8 100644 --- a/tests/dataParser/extended/date.test.ts +++ b/tests/dataParser/extended/date.test.ts @@ -63,6 +63,24 @@ describe("extended.date", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.date().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheDate, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | Date | DDate.TheDate, + "strict" + >; + + expect(parser.parse(1)).toStrictEqual(DEither.success(DDate.createOrThrow("date1+"))); + }); + it("supports refine helper", () => { const parser = extended.date().refine( (date) => DDate.greaterThan(date, DDate.createOrThrow(0)), diff --git a/tests/dataParser/extended/empty.test.ts b/tests/dataParser/extended/empty.test.ts index 2f775e0f1..b999c8e4f 100644 --- a/tests/dataParser/extended/empty.test.ts +++ b/tests/dataParser/extended/empty.test.ts @@ -59,6 +59,24 @@ describe("extended.empty", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.empty().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + undefined, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + undefined | "undefined", + "strict" + >; + + expect(parser.parse("undefined")).toStrictEqual(DEither.success(undefined)); + }); + it("coerces string when enabled", () => { const parser = extended.empty({ coerce: true }); expect(parser.parse("undefined")).toStrictEqual(DEither.success(undefined)); diff --git a/tests/dataParser/extended/nil.test.ts b/tests/dataParser/extended/nil.test.ts index 301546d9e..f87bef6c3 100644 --- a/tests/dataParser/extended/nil.test.ts +++ b/tests/dataParser/extended/nil.test.ts @@ -59,6 +59,24 @@ describe("extended.nil", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.nil().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + null, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + null | "null", + "strict" + >; + + expect(parser.parse("null")).toStrictEqual(DEither.success(null)); + }); + it("coerces string when enabled", () => { const parser = extended.nil({ coerce: true }); expect(parser.parse("null")).toStrictEqual(DEither.success(null)); diff --git a/tests/dataParser/extended/number.test.ts b/tests/dataParser/extended/number.test.ts index 64580731d..9b09d413d 100644 --- a/tests/dataParser/extended/number.test.ts +++ b/tests/dataParser/extended/number.test.ts @@ -59,6 +59,24 @@ describe("extended.number", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.number().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(parser.parse("42")).toStrictEqual(DEither.success(42)); + }); + it("supports min/max helpers", () => { const parser = extended.number(); diff --git a/tests/dataParser/extended/string.test.ts b/tests/dataParser/extended/string.test.ts index 2fdfb5841..18f4321e0 100644 --- a/tests/dataParser/extended/string.test.ts +++ b/tests/dataParser/extended/string.test.ts @@ -59,6 +59,24 @@ describe("extended.string", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.string().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + string, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | symbol | null | undefined, + "strict" + >; + + expect(parser.parse(42)).toStrictEqual(DEither.success("42")); + }); + it("supports min/max helpers", () => { const parser = extended.string(); const minParser = parser.min(3); diff --git a/tests/dataParser/extended/templateLiteral.test.ts b/tests/dataParser/extended/templateLiteral.test.ts index e986c35e2..acf2173d3 100644 --- a/tests/dataParser/extended/templateLiteral.test.ts +++ b/tests/dataParser/extended/templateLiteral.test.ts @@ -67,4 +67,22 @@ describe("extended.templateLiteral", () => { "strict" >; }); + + it("coerces values and preserves input and output types", () => { + const parser = extended.templateLiteral(["item-", extended.number()]).coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + `item-${number}`, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + number | bigint | boolean | `item-${number}`, + "strict" + >; + + expect(parser.parse("item-42")).toStrictEqual(DEither.success("item-42")); + }); }); diff --git a/tests/dataParser/extended/time.test.ts b/tests/dataParser/extended/time.test.ts index 69af7095b..fcacf42c7 100644 --- a/tests/dataParser/extended/time.test.ts +++ b/tests/dataParser/extended/time.test.ts @@ -63,6 +63,24 @@ describe("extended.time", () => { >; }); + it("coerces values and preserves input and output types", () => { + const parser = extended.time().coerce(); + + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheTime, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | DDate.TheTime, + "strict" + >; + + expect(parser.parse("01:02")).toStrictEqual(DEither.success(DDate.createTimeOrThrow("time3720000+"))); + }); + it("supports refine helper", () => { const parser = extended.time().refine( (time) => time.toNative() > 0, diff --git a/tests/dataParser/parsers/coerce/boolean.test.ts b/tests/dataParser/parsers/coerce/boolean.test.ts index 20ca9d89d..90cd1fea2 100644 --- a/tests/dataParser/parsers/coerce/boolean.test.ts +++ b/tests/dataParser/parsers/coerce/boolean.test.ts @@ -37,4 +37,9 @@ describe("coerce.boolean", () => { DEither.error(expect.any(Object)), ); }); + + it("coerces number", () => { + expect(DDataParser.coerce.boolean().parse(1)).toStrictEqual(DEither.success(true)); + expect(DDataParser.coerce.boolean().parse(0)).toStrictEqual(DEither.success(false)); + }); }); diff --git a/tests/dataParser/parsers/coerce/date.test.ts b/tests/dataParser/parsers/coerce/date.test.ts index e32079202..96d2234f5 100644 --- a/tests/dataParser/parsers/coerce/date.test.ts +++ b/tests/dataParser/parsers/coerce/date.test.ts @@ -27,12 +27,15 @@ describe("coerce.date", () => { void dataParser; }); - it("coerces number, Date and TheDate inputs", () => { + it("coerces number, string, Date and TheDate inputs", () => { const parser = DDataParser.coerce.date(); const nativeDate = new Date("2021-01-01T00:00:00.000Z"); const existing = DDate.create("2021-01-01"); + const expected = DDate.createOrThrow("date1609459200000+"); - expect(parser.parse(nativeDate)).toStrictEqual(DEither.success(DDate.createOrThrow("date1609459200000+"))); + expect(parser.parse(1609459200000)).toStrictEqual(DEither.success(expected)); + expect(parser.parse("2021-01-01")).toStrictEqual(DEither.success(expected)); + expect(parser.parse(nativeDate)).toStrictEqual(DEither.success(expected)); expect(parser.parse(existing)).toStrictEqual(DEither.success(existing)); }); @@ -42,10 +45,12 @@ describe("coerce.date", () => { const invalidDate = new Date(tooHigh); const invalidTheDate = `date${DDate.maxTimestamp}+` as DDate.SerializedTheDate; const invalidType = true; + const invalidString = "not-a-date"; expect(parser.parse(tooHigh)).toStrictEqual(DEither.error(expect.any(Object))); expect(parser.parse(invalidDate)).toStrictEqual(DEither.error(expect.any(Object))); expect(parser.parse(invalidTheDate)).toStrictEqual(DEither.error(expect.any(Object))); expect(parser.parse(invalidType)).toStrictEqual(DEither.error(expect.any(Object))); + expect(parser.parse(invalidString)).toStrictEqual(DEither.error(expect.any(Object))); }); }); diff --git a/tests/dataParser/parsers/coerce/number.test.ts b/tests/dataParser/parsers/coerce/number.test.ts index cb9fbc81d..9070440b9 100644 --- a/tests/dataParser/parsers/coerce/number.test.ts +++ b/tests/dataParser/parsers/coerce/number.test.ts @@ -36,4 +36,10 @@ describe("coerce.number", () => { DEither.error(expect.any(Object)), ); }); + + it("fails for symbol", () => { + expect(DDataParser.coerce.number({ errorMessage: "number.coerce" }).parse(Symbol("foo"))).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); }); diff --git a/tests/dataParser/parsers/coerce/time.test.ts b/tests/dataParser/parsers/coerce/time.test.ts index 7eb352075..9a55f8584 100644 --- a/tests/dataParser/parsers/coerce/time.test.ts +++ b/tests/dataParser/parsers/coerce/time.test.ts @@ -27,13 +27,16 @@ describe("coerce.time", () => { void dataParser; }); - it("coerces number, TheTime and ISO time inputs", () => { + it("coerces number, serialized string, ISO time string and TheTime inputs", () => { const parser = DDataParser.coerce.time(); const existing = DDate.createTime(1, "minute"); + const serialized = "time3720000+" as DDate.SerializedTheTime; + const expected = DDate.createTimeOrThrow(serialized); expect(parser.parse(1)).toStrictEqual((DEither.success(DDate.createTime(1, "millisecond")))); expect(parser.parse(-1)).toStrictEqual(DEither.success(DDate.createTime(-1, "millisecond"))); - expect(parser.parse("01:02")).toStrictEqual(DEither.success(DDate.createTimeOrThrow("time3720000+"))); + expect(parser.parse(serialized)).toStrictEqual(DEither.success(expected)); + expect(parser.parse("01:02")).toStrictEqual(DEither.success(expected)); expect(parser.parse(existing)).toStrictEqual(DEither.success(existing)); }); diff --git a/tests/dataParser/parsers/coercer/index.test.ts b/tests/dataParser/parsers/coercer/index.test.ts new file mode 100644 index 000000000..f2833467d --- /dev/null +++ b/tests/dataParser/parsers/coercer/index.test.ts @@ -0,0 +1,227 @@ +import { DDate, DEither, DDataParser, type ExpectType } from "@scripts"; + +const dataParserCoerceTransformersEntries = [...DDataParser.DataParserCoercer.transformers.entries()]; + +function restoreDataParserCoerceTransformers() { + DDataParser.DataParserCoercer.transformers.clear(); + + dataParserCoerceTransformersEntries.map( + ([kind, transformer]) => DDataParser.DataParserCoercer.transformers.set(kind, transformer), + ); +} + +describe("DDataParser coercer", () => { + beforeEach(restoreDataParserCoerceTransformers); + afterEach(restoreDataParserCoerceTransformers); + + it("preserves output and extends input from eligible coerce parser", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + }); + + it("keeps coercer input unrefined when checker refines output", () => { + const schema = DDataParser.coercer(DDataParser.number()).addChecker( + DDataParser.checkerRefine( + (value): value is 42 => value === 42, + ), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + 42, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + }); + + it("runs checkers after inner parser succeeds", () => { + const schema = DDataParser.coercer(DDataParser.number()).addChecker( + DDataParser.checkerNumberMin(10), + ); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + expect(schema.parse("5")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("coerces numeric boolean values through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.boolean()); + + expect(schema.parse(1)).toStrictEqual(DEither.success(true)); + expect(schema.parse(0)).toStrictEqual(DEither.success(false)); + expect(schema.parse(2)).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("coerces ISO time strings through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.time()); + + expect(schema.parse("01:02")).toStrictEqual( + DEither.success(DDate.createTimeOrThrow("time3720000+")), + ); + }); + + it("rejects ISO time strings when time creation fails through base coercer", () => { + const schema = DDataParser.coercer(DDataParser.time()); + const createTimeSpy = vi.spyOn(DDate, "createTime") + .mockReturnValueOnce(DEither.left("time-created-error", null) as never); + + try { + expect(schema.parse("01:02")).toStrictEqual(DEither.error(expect.any(Object))); + expect(createTimeSpy).toHaveBeenCalledWith({ value: "01:02" }); + } finally { + createTimeSpy.mockRestore(); + } + }); + + it("returns an error when no transformer is registered for inner parser kind", () => { + DDataParser.DataParserCoercer.transformers.delete(DDataParser.numberKind); + + expect(DDataParser.coercer(DDataParser.number()).parse("42")).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); + + it("keeps unsupported parser input and output unchanged", () => { + const schema = DDataParser.coercer(DDataParser.literal("ready")); + + type _CheckOut = ExpectType< + DDataParser.Output, + "ready", + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + "ready", + "strict" + >; + + expect(schema.parse("ready")).toStrictEqual(DEither.success("ready")); + expect(schema.parse("42")).toStrictEqual(DEither.error(expect.any(Object))); + }); + + it("returns an error when asynchronous inner parser rejects", async() => { + interface RejectedParserDefinition extends DDataParser.DataParserDefinition { + readonly rejectedError: Error; + } + + class RejectedParser extends DDataParser.DataParserBase.init( + DDataParser.dataParserKind, + )< + RejectedParserDefinition, + number, + number + > { + public get classConstructor() { + return this.checkConstructor(RejectedParser); + } + + public static override execParse( + self: RejectedParser, + data: unknown, + error: DDataParser.DataParserError, + ) { + void data; + void error; + return Promise.reject(self.definition.rejectedError); + } + + public static override dataParserIsAsynchronous(self: RejectedParser) { + void self; + return true; + } + + public static override prepareDefinition( + definition?: Partial, + ): RejectedParserDefinition { + return { + ...definition, + rejectedError: definition?.rejectedError ?? new Error("rejected"), + checkers: definition?.checkers ?? [], + errorMessage: definition?.errorMessage, + }; + } + + public static override create( + definition?: Partial, + ) { + return new RejectedParser(this.prepareDefinition(definition)); + } + } + + const rejectedError = new Error("coercer rejected"); + const schema = DDataParser.coercer(RejectedParser.create({ rejectedError })); + + await expect(schema.asyncParse(42)).resolves.toStrictEqual( + DEither.error( + expect.objectContaining({ + issues: [ + expect.objectContaining({ + expected: "successful coerce result", + data: rejectedError, + }), + ], + }), + ), + ); + }); + + it("reuses the transformer resolved during first parse", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + + DDataParser.DataParserCoercer.transformers.delete(DDataParser.numberKind); + + expect(schema.parse("43")).toStrictEqual(DEither.success(43)); + expect(DDataParser.coercer(DDataParser.number()).parse("44")).toStrictEqual( + DEither.error(expect.any(Object)), + ); + }); + + it("keeps coercion synchronous when inner parser is synchronous", () => { + expect(DDataParser.coercer(DDataParser.number()).isAsynchronous()).toBe(false); + }); + + it("returns an error when transformer throws", () => { + const transformerError = new Error("transformer failed"); + DDataParser.DataParserCoercer.transformers.set( + DDataParser.numberKind, + () => { + throw transformerError; + }, + ); + + expect(DDataParser.coercer(DDataParser.number()).parse("42")).toStrictEqual( + DEither.error( + expect.objectContaining({ + issues: [ + expect.objectContaining({ + expected: "successful coerce result", + data: transformerError, + }), + ], + }), + ), + ); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/bigint.test.ts b/tests/dataParser/parsers/coercer/transformers/bigint.test.ts new file mode 100644 index 000000000..206895583 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/bigint.test.ts @@ -0,0 +1,38 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer bigint transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.bigint()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + bigint, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean | bigint, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.bigint()); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42n)); + expect(schema.parse(42)).toStrictEqual(DEither.success(42n)); + expect(schema.parse(true)).toStrictEqual(DEither.success(1n)); + }); + + it("fails when transformed value is not a bigint", () => { + const schema = DDataParser.coercer(DDataParser.bigint()); + + expect(schema.parse(1.5)).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/boolean.test.ts b/tests/dataParser/parsers/coercer/transformers/boolean.test.ts new file mode 100644 index 000000000..1ab9b88c1 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/boolean.test.ts @@ -0,0 +1,40 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer boolean transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.boolean()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + boolean, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | boolean, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.boolean()); + + expect(schema.parse(" true ")).toStrictEqual(DEither.success(true)); + expect(schema.parse("FALSE")).toStrictEqual(DEither.success(false)); + expect(schema.parse(1)).toStrictEqual(DEither.success(true)); + expect(schema.parse(0)).toStrictEqual(DEither.success(false)); + }); + + it("fails when transformed value is not a boolean", () => { + const schema = DDataParser.coercer(DDataParser.boolean()); + + expect(schema.parse("yes")).toStrictEqual(DEither.error(expect.any(Object))); + expect(schema.parse(2)).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/date.test.ts b/tests/dataParser/parsers/coercer/transformers/date.test.ts new file mode 100644 index 000000000..ade689816 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/date.test.ts @@ -0,0 +1,40 @@ +import { DDataParser, DDate, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer date transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.date()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheDate, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + DDate.TheDate | Date | string | number, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.date()); + + expect(schema.parse(1)).toStrictEqual(DEither.success(DDate.createOrThrow("date1+"))); + expect(schema.parse("2021-01-01T00:00:00.000Z")).toStrictEqual( + DEither.success(DDate.createOrThrow(new Date("2021-01-01T00:00:00.000Z"))), + ); + }); + + it("fails when transformed value is not a date", () => { + const schema = DDataParser.coercer(DDataParser.date()); + + expect(schema.parse("not-a-date")).toStrictEqual(DEither.error(expect.any(Object))); + expect(schema.parse(Number.POSITIVE_INFINITY)).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/empty.test.ts b/tests/dataParser/parsers/coercer/transformers/empty.test.ts new file mode 100644 index 000000000..723bf2eb9 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/empty.test.ts @@ -0,0 +1,36 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer empty transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.empty()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + undefined, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + undefined | "undefined", + "strict" + >; + }); + + it("coerces the undefined string through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.empty()); + + expect(schema.parse("undefined")).toStrictEqual(DEither.success(undefined)); + }); + + it("fails when transformed value is not undefined", () => { + const schema = DDataParser.coercer(DDataParser.empty()); + + expect(schema.parse("null")).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/nil.test.ts b/tests/dataParser/parsers/coercer/transformers/nil.test.ts new file mode 100644 index 000000000..b02cb1d3e --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/nil.test.ts @@ -0,0 +1,36 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer nil transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.nil()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + null, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + null | "null", + "strict" + >; + }); + + it("coerces the null string through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.nil()); + + expect(schema.parse("null")).toStrictEqual(DEither.success(null)); + }); + + it("fails when transformed value is not null", () => { + const schema = DDataParser.coercer(DDataParser.nil()); + + expect(schema.parse("undefined")).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/number.test.ts b/tests/dataParser/parsers/coercer/transformers/number.test.ts new file mode 100644 index 000000000..8ff0b99f8 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/number.test.ts @@ -0,0 +1,40 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer number transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.number()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + number, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | null, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + expect(schema.parse("42")).toStrictEqual(DEither.success(42)); + expect(schema.parse(42n)).toStrictEqual(DEither.success(42)); + expect(schema.parse(true)).toStrictEqual(DEither.success(1)); + expect(schema.parse(null)).toStrictEqual(DEither.success(0)); + }); + + it("fails when transformed value is not a finite number", () => { + const schema = DDataParser.coercer(DDataParser.number()); + + expect(schema.parse("not-a-number")).toStrictEqual(DEither.error(expect.any(Object))); + expect(schema.parse(Symbol("foo"))).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/string.test.ts b/tests/dataParser/parsers/coercer/transformers/string.test.ts new file mode 100644 index 000000000..cce24921e --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/string.test.ts @@ -0,0 +1,40 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer string transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.string()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + string, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + string | number | bigint | boolean | symbol | null | undefined, + "strict" + >; + }); + + it("coerces supported values through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.string()); + + expect(schema.parse(42)).toStrictEqual(DEither.success("42")); + expect(schema.parse(42n)).toStrictEqual(DEither.success("42")); + expect(schema.parse(true)).toStrictEqual(DEither.success("true")); + expect(schema.parse(null)).toStrictEqual(DEither.success("null")); + expect(schema.parse(undefined)).toStrictEqual(DEither.success("undefined")); + }); + + it("fails when transformed value is not a string", () => { + const schema = DDataParser.coercer(DDataParser.string()); + + expect(schema.parse(Object.create(null))).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/templateLiteral.test.ts b/tests/dataParser/parsers/coercer/transformers/templateLiteral.test.ts new file mode 100644 index 000000000..5a5e60249 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/templateLiteral.test.ts @@ -0,0 +1,47 @@ +import { DDataParser, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer template literal transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer( + DDataParser.templateLiteral([DDataParser.boolean()]), + ).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + `${boolean}`, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + `${boolean}` | number | bigint | boolean, + "strict" + >; + }); + + it("coerces supported primitive values through the coercer", () => { + const booleanSchema = DDataParser.coercer( + DDataParser.templateLiteral([DDataParser.boolean()]), + ); + const numberSchema = DDataParser.coercer( + DDataParser.templateLiteral([DDataParser.number()]), + ); + + expect(booleanSchema.parse(false)).toStrictEqual(DEither.success("false")); + expect(numberSchema.parse(42)).toStrictEqual(DEither.success("42")); + expect(numberSchema.parse(42n)).toStrictEqual(DEither.success("42")); + }); + + it("fails when transformed value does not match the template literal", () => { + const schema = DDataParser.coercer( + DDataParser.templateLiteral(["id-", DDataParser.number()]), + ); + + expect(schema.parse(42)).toStrictEqual(DEither.error(expect.any(Object))); + }); +}); diff --git a/tests/dataParser/parsers/coercer/transformers/time.test.ts b/tests/dataParser/parsers/coercer/transformers/time.test.ts new file mode 100644 index 000000000..5e8f7e179 --- /dev/null +++ b/tests/dataParser/parsers/coercer/transformers/time.test.ts @@ -0,0 +1,38 @@ +import { DDataParser, DDate, DEither, type ExpectType } from "@scripts"; + +describe("DDataParser coercer time transformer", () => { + it("infers coerced input, output, and checker refine value types", () => { + const schema = DDataParser.coercer(DDataParser.time()).addChecker( + DDataParser.checkerRefine((value) => { + type _CheckRefineValue = ExpectType; + return true; + }), + ); + + type _CheckOut = ExpectType< + DDataParser.Output, + DDate.TheTime, + "strict" + >; + + type _CheckIn = ExpectType< + DDataParser.Input, + DDate.TheTime | number | string, + "strict" + >; + }); + + it("coerces ISO time strings through the coercer", () => { + const schema = DDataParser.coercer(DDataParser.time()); + + expect(schema.parse("01:02")).toStrictEqual( + DEither.success(DDate.createTimeOrThrow("time3720000+")), + ); + }); + + it("fails when transformed value is not a time", () => { + const schema = DDataParser.coercer(DDataParser.time()); + + expect(schema.parse("not-a-time")).toStrictEqual(DEither.error(expect.any(Object))); + }); +});