diff --git a/scripts/dataParser/parsers/record/findRecordRequiredKey.ts b/scripts/dataParser/parsers/record/findRecordRequiredKey.ts index 8cd05bd5..2c7dc762 100644 --- a/scripts/dataParser/parsers/record/findRecordRequiredKey.ts +++ b/scripts/dataParser/parsers/record/findRecordRequiredKey.ts @@ -15,7 +15,7 @@ import { nilKind } from "../nil"; export function findRecordRequiredKeyOnTemplateLiteralPart( templatePart: readonly TemplateLiteralParts[], -): string[] | null { +): readonly string[] { return pipe( templatePart, DArray.map( @@ -24,7 +24,7 @@ export function findRecordRequiredKeyOnTemplateLiteralPart( (value) => stringKind.has(value) || numberKind.has(value) || bigIntKind.has(value), - justReturn(null), + justReturn([]), ), DPattern.when( or([ @@ -40,12 +40,20 @@ export function findRecordRequiredKeyOnTemplateLiteralPart( isType("bigint"), (value) => `${value}n`, ), - String, + DString.to, ), ), DPattern.when( literalKind.has, - (value) => findRecordRequiredKey(value), + (value) => pipe( + value.definition.value, + DArray.map( + (element) => findRecordRequiredKeyOnTemplateLiteralPart( + [element], + ), + ), + DArray.flat, + ), ), DPattern.when( templateLiteralKind.has, @@ -72,11 +80,7 @@ export function findRecordRequiredKeyOnTemplateLiteralPart( [element], ), ), - DPattern.when( - DArray.notIncludes(null), - DArray.flat, - ), - DPattern.otherwise(justReturn(null)), + DArray.flat, ), ), DPattern.exhaustive, @@ -84,12 +88,8 @@ export function findRecordRequiredKeyOnTemplateLiteralPart( ), DArray.reduce( DArray.reduceFrom([""]), - ({ lastValue, element, exit, next }) => pipe( + ({ lastValue, element, next }) => pipe( element, - DPattern.when( - isType("null"), - justReturn(exit(null)), - ), DPattern.when( isType("string"), (element) => next( @@ -117,43 +117,23 @@ export function findRecordRequiredKeyOnTemplateLiteralPart( ); } -export function findRecordRequiredKey(keyParser: DataParserRecordKey): string[] | null { +export function findRecordRequiredKey(keyParser: DataParserRecordKey): readonly string[] { return pipe( keyParser, DPattern.when( (value) => stringKind.has(value) || numberKind.has(value), - justReturn(null), + justReturn([]), ), DPattern.when( literalKind.has, - (dataParser) => pipe( - dataParser.definition.value, - DArray.map( - innerPipe( - when( - isType("bigint"), - (value) => `${value}n`, - ), - String, - ), - ), - ), + (dataParser) => dataParser.definition.value, ), DPattern.when( unionKind.has, (dataParser) => pipe( dataParser.definition.options, DArray.map(findRecordRequiredKey), - DPattern.when( - DArray.includes(null), - justReturn(null), - ), - DPattern.otherwise( - innerPipe( - DArray.filter(isType("array")), - DArray.flat, - ), - ), + DArray.flat, ), ), DPattern.when( diff --git a/scripts/dataParser/parsers/record/index.ts b/scripts/dataParser/parsers/record/index.ts index 8b4f3213..d613fec1 100644 --- a/scripts/dataParser/parsers/record/index.ts +++ b/scripts/dataParser/parsers/record/index.ts @@ -1,16 +1,16 @@ -import { type NeverCoalescing, type Kind, type FixDeepFunctionInfer, type Adaptor, createOverride } from "@scripts/common"; +/* eslint-disable @typescript-eslint/prefer-for-of */ +import { type NeverCoalescing, type Kind, type FixDeepFunctionInfer, createOverride } from "@scripts/common"; import { type DataParserDefinition, type DataParser, dataParserInit, type Output, type Input, SymbolDataParserError, type DataParserChecker } from "../../base"; import { type AddCheckersToDefinition, type MergeDefinition } from "@scripts/dataParser/types"; import { popErrorPath, setErrorPath, SymbolDataParserErrorIssue } from "@scripts/dataParser/error"; import { type DataParserString } from "../string"; import { type DataParserTemplateLiteral } from "../templateLiteral"; -import { type DataParserLiteral } from "../literal"; +import { type DataParserDefinitionLiteral, type DataParserLiteral } from "../literal"; import { type DataParserDefinitionNumber, type DataParserNumber } from "../number"; import { type DataParserDefinitionUnion, type DataParserUnion } from "../union"; import { createDataParserKind } from "../../kind"; import { type CheckerRefineImplementation } from "../refine"; import { findRecordRequiredKey } from "./findRecordRequiredKey"; -import { type TemplateLiteralContainLargeType } from "@scripts/string"; import { type GetPropsWithValueExtends } from "@scripts/object"; export * from "./findRecordRequiredKey"; @@ -18,7 +18,12 @@ export * from "./findRecordRequiredKey"; export type DataParserRecordKey = ( | DataParserString | DataParserTemplateLiteral - | DataParserLiteral + | DataParserLiteral< + & DataParserDefinitionLiteral + & { + value: readonly string[]; + } + > | DataParserNumber< & DataParserDefinitionNumber & { @@ -55,7 +60,7 @@ export interface DataParserDefinitionRecord extends DataParserDefinition< > { readonly key: DataParserRecordKey; readonly value: DataParser; - readonly requireKey: string[] | null; + readonly requireKey: readonly string[]; } export const recordKind = createDataParserKind("record"); @@ -73,13 +78,7 @@ export type DataParserRecordShapeOutput< : never >, any -> extends infer InferredOutput extends Record - ? TemplateLiteralContainLargeType< - Adaptor - > extends true - ? Partial - : InferredOutput - : never; +>; export type DataParserRecordShapeInput< GenericDataParserKey extends DataParserRecordKey, @@ -94,13 +93,7 @@ export type DataParserRecordShapeInput< : never >, any -> extends infer InferredInput extends Record - ? TemplateLiteralContainLargeType< - Adaptor - > extends true - ? Partial - : InferredInput - : never; +>; type _DataParserRecord< GenericDefinition extends DataParserDefinitionRecord, @@ -217,11 +210,11 @@ export function record< return output; } - if ( - self.definition.requireKey - && self.definition.requireKey.length !== Object.keys(output).length - ) { - return SymbolDataParserErrorIssue; + for (let index = 0; index < self.definition.requireKey.length; index++) { + const requiredKey = self.definition.requireKey[index]!; + if (!(requiredKey in output)) { + return SymbolDataParserErrorIssue; + } } return output; @@ -270,11 +263,11 @@ export function record< return output; } - if ( - self.definition.requireKey - && self.definition.requireKey.length !== Object.keys(output).length - ) { - return SymbolDataParserErrorIssue; + for (let index = 0; index < self.definition.requireKey.length; index++) { + const requiredKey = self.definition.requireKey[index]!; + if (!(requiredKey in output)) { + return SymbolDataParserErrorIssue; + } } return output; diff --git a/scripts/string/types/pop.ts b/scripts/string/types/pop.ts index a1b92465..406f703a 100644 --- a/scripts/string/types/pop.ts +++ b/scripts/string/types/pop.ts @@ -1,16 +1,23 @@ import type { IsEqual } from "@scripts/common"; import type { TemplateLiteralContainLargeType } from "./templateLiteralContainLargeType"; +type _Pop< + GenericValue extends string, + GenericCount extends never[] = [], +> = IsEqual extends true + ? string + : GenericValue extends `${infer InferredFirst}${infer InferredRest}` + ? IsEqual extends true + ? "" + : _Pop extends infer InferredResult extends string + ? `${InferredFirst}${InferredResult}` + : never + : string; + export type Pop< GenericValue extends string, > = TemplateLiteralContainLargeType extends true ? string : IsEqual extends true ? "" - : GenericValue extends `${infer InferredFirst}${infer InferredRest}` - ? IsEqual extends true - ? "" - : Pop extends infer InferredResult extends string - ? `${InferredFirst}${InferredResult}` - : never - : string; + : _Pop; diff --git a/scripts/string/types/split.ts b/scripts/string/types/split.ts index f3443bf7..111428e0 100644 --- a/scripts/string/types/split.ts +++ b/scripts/string/types/split.ts @@ -2,11 +2,34 @@ import { type Or, type IsEqual } from "@scripts/common"; import { type Includes } from "./includes"; import { type TemplateLiteralContainLargeType } from "./templateLiteralContainLargeType"; -export type Split< +type _Split< GenericString extends string, GenericSeparator extends string, GenericLimit extends number = number, GenericLastResult extends string[] = [], +> = GenericString extends `${infer InferredBefore}${GenericSeparator}${infer InferredAfter}` + ? [...GenericLastResult, InferredBefore] extends infer InferredResult extends any[] + ? IsEqual extends true + ? InferredResult + : IsEqual extends true + ? Includes extends true + ? [...InferredResult, ...string[]] + : InferredResult + : IsEqual extends true + ? InferredResult + : _Split< + InferredAfter, + GenericSeparator, + GenericLimit, + InferredResult + > + : never + : [...GenericLastResult, GenericString]; + +export type Split< + GenericString extends string, + GenericSeparator extends string, + GenericLimit extends number = number, > = IsEqual extends true ? [] : Or<[ @@ -14,21 +37,8 @@ export type Split< TemplateLiteralContainLargeType, ]> extends true ? [string, ...string[]] - : GenericString extends `${infer InferredBefore}${GenericSeparator}${infer InferredAfter}` - ? [...GenericLastResult, InferredBefore] extends infer InferredResult extends any[] - ? IsEqual extends true - ? InferredResult - : IsEqual extends true - ? Includes extends true - ? [...InferredResult, ...string[]] - : InferredResult - : IsEqual extends true - ? InferredResult - : Split< - InferredAfter, - GenericSeparator, - GenericLimit, - InferredResult - > - : never - : [...GenericLastResult, GenericString]; + : _Split< + GenericString, + GenericSeparator, + GenericLimit + >; diff --git a/scripts/string/types/templateLiteralContainLargeType.ts b/scripts/string/types/templateLiteralContainLargeType.ts index 01116e9a..945e3e9a 100644 --- a/scripts/string/types/templateLiteralContainLargeType.ts +++ b/scripts/string/types/templateLiteralContainLargeType.ts @@ -1,19 +1,11 @@ -import { type IsEqual, type Or } from "@scripts/common"; - export type TemplateLiteralContainLargeType< GenericValue extends string, > = ( - GenericValue extends `${infer InferredFirst}${infer InferredLast}` - ? Or<[ - IsEqual, - IsEqual, - IsEqual, - ]> extends false - ? TemplateLiteralContainLargeType - : true - : GenericValue extends "" - ? false - : true + GenericValue extends string + ? {} extends Record + ? true + : false + : never ) extends false ? false : true; diff --git a/tests/dataParser/extended/record.test.ts b/tests/dataParser/extended/record.test.ts index 71451bad..b7c21c23 100644 --- a/tests/dataParser/extended/record.test.ts +++ b/tests/dataParser/extended/record.test.ts @@ -15,7 +15,7 @@ describe("extended.record", () => { type check = ExpectType< typeof result, - DEither.Error | DEither.Success>>, + DEither.Error | DEither.Success>, "strict" >; }); diff --git a/tests/dataParser/parsers/record/findRecordRequiredKey.test.ts b/tests/dataParser/parsers/record/findRecordRequiredKey.test.ts index fd31b5f1..db58145b 100644 --- a/tests/dataParser/parsers/record/findRecordRequiredKey.test.ts +++ b/tests/dataParser/parsers/record/findRecordRequiredKey.test.ts @@ -4,7 +4,7 @@ describe("findRecordRequiredKey", () => { it("returns null for string key parser", () => { const keyParser = DDataParser.string(); - expect(DDataParser.findRecordRequiredKey(keyParser)).toBeNull(); + expect(DDataParser.findRecordRequiredKey(keyParser)).toStrictEqual([]); }); it("returns all combinations for template literal key parser", () => { @@ -24,7 +24,7 @@ describe("findRecordRequiredKey", () => { }); it("returns literal options converted to strings", () => { - const keyParser = DDataParser.literal(["foo", 42, 12n]); + const keyParser = DDataParser.literal(["foo", "42", "12n"]); expect(DDataParser.findRecordRequiredKey(keyParser)).toStrictEqual([ "foo", @@ -36,7 +36,7 @@ describe("findRecordRequiredKey", () => { it("returns null for number key parser with coercion", () => { const keyParser = DDataParser.number({ coerce: true }); - expect(DDataParser.findRecordRequiredKey(keyParser)).toBeNull(); + expect(DDataParser.findRecordRequiredKey(keyParser)).toStrictEqual([]); }); it("flattens union when every option resolves to concrete keys", () => { @@ -64,7 +64,7 @@ describe("findRecordRequiredKey", () => { DDataParser.string(), ]); - expect(DDataParser.findRecordRequiredKey(keyParser)).toBeNull(); + expect(DDataParser.findRecordRequiredKey(keyParser)).toStrictEqual(["foo"]); }); }); @@ -108,19 +108,19 @@ describe("findRecordRequiredKeyOnTemplateLiteralPart", () => { it("handles string parser part", () => { const result = DDataParser.findRecordRequiredKeyOnTemplateLiteralPart([DDataParser.email()]); - expect(result).toBeNull(); + expect(result).toStrictEqual([]); }); it("handles number parser part", () => { const result = DDataParser.findRecordRequiredKeyOnTemplateLiteralPart([DDataParser.number()]); - expect(result).toBeNull(); + expect(result).toStrictEqual([]); }); it("handles bigint parser part", () => { const result = DDataParser.findRecordRequiredKeyOnTemplateLiteralPart([DDataParser.bigint()]); - expect(result).toBeNull(); + expect(result).toStrictEqual([]); }); it("handles boolean parser part", () => { @@ -172,7 +172,20 @@ describe("findRecordRequiredKeyOnTemplateLiteralPart", () => { ]), ]); - expect(result).toBeNull(); + expect(result).toStrictEqual(["union"]); + }); + + it("template literal with large type", () => { + expect( + DDataParser.findRecordRequiredKeyOnTemplateLiteralPart([ + "test-", + DDataParser.union([ + DDataParser.string(), + DDataParser.literal("one"), + ]), + "ok", + ]), + ).toStrictEqual(["test-oneok"]); }); it("computes every combination for nested template literal structure", () => { diff --git a/tests/dataParser/parsers/record/index.test.ts b/tests/dataParser/parsers/record/index.test.ts index 6d7ef878..87a085e6 100644 --- a/tests/dataParser/parsers/record/index.test.ts +++ b/tests/dataParser/parsers/record/index.test.ts @@ -12,13 +12,13 @@ describe("DDataParser record", () => { type _CheckOut = ExpectType< DDataParser.Output, - Partial>, + Record, "strict" >; type _CheckIn = ExpectType< DDataParser.Input, - Partial>, + Record, "strict" >; @@ -34,7 +34,7 @@ describe("DDataParser record", () => { type _Check = ExpectType< typeof result, | DEither.Error - | DEither.Success>>, + | DEither.Success>, "strict" >; }); @@ -179,7 +179,7 @@ describe("DDataParser record", () => { type _CheckOut = ExpectType< DDataParser.Output, - Partial>, + Record, "strict" >; }); @@ -192,22 +192,26 @@ describe("DDataParser record", () => { DDataParser.literal(["a", "b"]), DDataParser.string(), ]), - DDataParser.literal(23), + DDataParser.literal("23"), ]), DDataParser.number(), ); const input = { + 23: 1, "prefix-akey": 5, }; expect(schema.parse(input)).toStrictEqual(DEither.success(input)); - expect(schema.parse({})).toStrictEqual(DEither.success({})); - expect(schema.parse({ "prefix-c": 23 })).toStrictEqual(DEither.error(expect.any(Object))); + expect(schema.parse({ 23: 1 })).toStrictEqual(DEither.success({ 23: 1 })); + expect(schema.parse({ + "prefix-c": 23, + 23: 1, + })).toStrictEqual(DEither.error(expect.any(Object))); type _CheckOut = ExpectType< DDataParser.Output, - Partial>, + Record<`prefix-a${string}` | `prefix-b${string}` | "23", number>, "strict" >; }); @@ -233,7 +237,7 @@ describe("DDataParser record", () => { ]), ); - type ExpectedRecord = Partial>; + type ExpectedRecord = Record<`user-${"admin" | "guest"}-${string}`, "read" | "write" | number>; type _CheckOut = ExpectType< DDataParser.Output, @@ -250,6 +254,8 @@ describe("DDataParser record", () => { const validInput: ExpectedRecord = { "user-admin-us": "read", "user-guest-eu": "write", + "user-admin-eu": "write", + "user-guest-us": "write", "user-admin-fr": 1, }; @@ -429,7 +435,9 @@ describe("DDataParser record", () => { const validInput = { "user-admin-us": "read", + "user-admin-eu": "read", "user-guest-eu": "write", + "user-guest-us": "write", }; expect(await schema.asyncParse(validInput)).toStrictEqual(DEither.success(validInput));