diff --git a/integration/toJsonSchema/__snapshots__/index.test.ts.snap b/integration/toJsonSchema/__snapshots__/index.test.ts.snap index 407402a..33d145e 100644 --- a/integration/toJsonSchema/__snapshots__/index.test.ts.snap +++ b/integration/toJsonSchema/__snapshots__/index.test.ts.snap @@ -97,7 +97,7 @@ exports[`integration 1`] = ` "type": "string", }, "id": { - "pattern": "^(?:user\\-)(?:-?[0-9]+(?:\\.[0-9]+)?)(?:\\-db1)$", + "pattern": "^(?:user\\-)(?:(?:[+-]?(?:(?:[0-9]+(?:\\.[0-9]*)?)|(?:\\.[0-9]+))(?:[eE][+-]?[0-9]+)?)|(?:0(?:[xX][0-9a-fA-F]+|[bB][01]+|[oO][0-7]+)))(?:\\-db1)$", "type": "string", }, "identifiedDataParser": { diff --git a/package-lock.json b/package-lock.json index b41921a..c93b78a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,8 +38,8 @@ "node": ">=22.15.1" }, "peerDependencies": { - "@duplojs/server-utils": ">=0.4.0 < 1.0.0", - "@duplojs/utils": ">=1.9.1 <2.0.0" + "@duplojs/server-utils": ">=0.4.2 < 1.0.0", + "@duplojs/utils": ">=1.10.1 <2.0.0" } }, "docs": {}, @@ -984,9 +984,9 @@ } }, "node_modules/@duplojs/server-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@duplojs/server-utils/-/server-utils-0.4.0.tgz", - "integrity": "sha512-wpsBdc/Rz4NTWQ4UywF3qo1mfosZDSzj7kMzBT2FF+Jyn5MEyUEyor4DjSHYssjakiOHxqu5zWiVaopqJpznuQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@duplojs/server-utils/-/server-utils-0.4.2.tgz", + "integrity": "sha512-4WSDGzWpO4j/DmC66MjlAYC+V0VGVAgL6NHofJmfXa2IB5Cw/4FUIdzF3C2VvvI11QBVlKG6Bf6kYvo6qJMRiQ==", "license": "MIT", "peer": true, "workspaces": [ @@ -997,13 +997,13 @@ "node": ">=22.15.1" }, "peerDependencies": { - "@duplojs/utils": ">=1.9.1 <2.0.0" + "@duplojs/utils": ">=1.10.1 <2.0.0" } }, "node_modules/@duplojs/utils": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@duplojs/utils/-/utils-1.9.1.tgz", - "integrity": "sha512-dQGYiobo7ZSyk1N5G1e5Acjk7nxeKsh9TiGFaHxbBtS0xXfH1h2fnk2nqCkIdSU9mJw6BdNA05uX35AMYcvMPA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@duplojs/utils/-/utils-1.10.1.tgz", + "integrity": "sha512-hs4BERlse4gIn05Hn/VaB62iIr+UbLfE3B7Rq65VodEjEVIIX4hKC//ZhnB5jQgVDm6yjcsJO3reOFHFutZYBg==", "license": "MIT", "peer": true, "workspaces": [ diff --git a/package.json b/package.json index ed40fd5..57d2bff 100644 --- a/package.json +++ b/package.json @@ -86,8 +86,8 @@ "vitest": "4.1.8" }, "peerDependencies": { - "@duplojs/server-utils": ">=0.4.0 < 1.0.0", - "@duplojs/utils": ">=1.9.1 <2.0.0" + "@duplojs/server-utils": ">=0.4.2 < 1.0.0", + "@duplojs/utils": ">=1.10.1 <2.0.0" }, "dependencies": { "typescript": "5.9.2" diff --git a/scripts/finder/researcher/defaults/errorHandler.ts b/scripts/finder/researcher/defaults/errorHandler.ts new file mode 100644 index 0000000..ef25ed3 --- /dev/null +++ b/scripts/finder/researcher/defaults/errorHandler.ts @@ -0,0 +1,9 @@ +import { DP } from "@duplojs/utils"; +import { createResearcher } from "../create"; + +export const errorHandlerResearcher = createResearcher( + DP.errorHandlerKind.has, + (dataParser, params) => { + params.find(dataParser.definition.inner); + }, +); diff --git a/scripts/finder/researcher/defaults/index.ts b/scripts/finder/researcher/defaults/index.ts index 4554d48..2c17488 100644 --- a/scripts/finder/researcher/defaults/index.ts +++ b/scripts/finder/researcher/defaults/index.ts @@ -10,6 +10,7 @@ export * from "./templateLiteral"; export * from "./transform"; export * from "./tuple"; export * from "./union"; +export * from "./errorHandler"; import type { createResearcher } from "../create"; import { arrayResearcher } from "./array"; @@ -24,6 +25,7 @@ import { templateLiteralResearcher } from "./templateLiteral"; import { transformResearcher } from "./transform"; import { tupleResearcher } from "./tuple"; import { unionResearcher } from "./union"; +import { errorHandlerResearcher } from "./errorHandler"; export const defaultResearchers = [ arrayResearcher, @@ -38,4 +40,5 @@ export const defaultResearchers = [ transformResearcher, tupleResearcher, unionResearcher, + errorHandlerResearcher, ] as const satisfies readonly ReturnType[]; diff --git a/scripts/toDataParser/buildContext.ts b/scripts/toDataParser/buildContext.ts index 9cca5c8..9a35ebd 100644 --- a/scripts/toDataParser/buildContext.ts +++ b/scripts/toDataParser/buildContext.ts @@ -1,5 +1,5 @@ import { DP, E, unwrap } from "@duplojs/utils"; -import { createIdentifier, type createTransformer, transformer, type MapContext, type TransformerHook, type DataParserErrorEither, type DataParserNotSupportedEither, type DataParserGetDefinitionErrorEither, type ToTypescriptDataParserErrorEither, type ToTypescriptDataParserNotSupportedEither, type DependenciesContext } from "./dataParserTransformer"; +import { createIdentifier, type createTransformer, transformer, type MapContext, type TransformerHook, type DataParserErrorEither, type DataParserNotSupportedEither, type DataParserGetDefinitionErrorEither, type ToTypescriptDataParserErrorEither, type ToTypescriptDataParserNotSupportedEither, type DependenciesContext, type ToTypescriptCheckerErrorEither } from "./dataParserTransformer"; import type * as TST from "@scripts/toTypescript"; import { getRecursiveDataParser } from "@scripts/utils"; import { factory } from "typescript"; @@ -19,10 +19,10 @@ export interface BuildContextParams { readonly dataParserTransformers: readonly ReturnType[]; readonly checkerTransformers: readonly ReturnType[]; readonly typescriptTransformers: readonly ReturnType[]; + readonly typescriptCheckerRefiner?: readonly ReturnType[]; readonly context?: MapContext; readonly typescriptContext?: TST.MapContext; readonly importContext?: TST.MapImportContext; - readonly importMode?: ImportMode; readonly hooks?: readonly TransformerHook[]; readonly toTypescript?: { @@ -41,6 +41,7 @@ export function buildContext( | DataParserGetDefinitionErrorEither | ToTypescriptDataParserNotSupportedEither | ToTypescriptDataParserErrorEither + | ToTypescriptCheckerErrorEither ) { const context: MapContext = params.context ?? new Map(); const typescriptContext: TST.MapContext = params.typescriptContext ?? new Map(); diff --git a/scripts/toDataParser/checkerTransformer/create.ts b/scripts/toDataParser/checkerTransformer/create.ts index bc30551..4ba0846 100644 --- a/scripts/toDataParser/checkerTransformer/create.ts +++ b/scripts/toDataParser/checkerTransformer/create.ts @@ -1,13 +1,12 @@ -import type { CallExpression, ObjectLiteralExpression, PropertyAssignment } from "typescript"; +import type { CallExpression, Identifier, ObjectLiteralExpression, PropertyAssignment } from "typescript"; import { type DP, E } from "@duplojs/utils"; -import type { DServerDataParser } from "@duplojs/server-utils"; import type * as TST from "@scripts/toTypescript"; -export type CheckerTransformerSuccessEither = E.Right<"buildSuccess", CallExpression>; +export type CheckerTransformerSuccessEither = E.Right<"buildSuccess", CallExpression | Identifier>; -export type CheckerTransformerCheckerNotSupportedEither = E.Left<"checkerNotSupport", DP.DataParserChecker>; +export type CheckerTransformerCheckerNotSupportedEither = E.Left<"checkerNotSupport", DP.DataParserCheckers>; -export type CheckerTransformerBuildErrorEither = E.Left<"buildCheckerError", DP.DataParserChecker>; +export type CheckerTransformerBuildErrorEither = E.Left<"buildCheckerError", DP.DataParserCheckers>; export type CheckerTransformerEither = | CheckerTransformerSuccessEither @@ -18,7 +17,7 @@ export interface CheckerTransformerParams { readonly importContext: TST.MapImportContext; success( - result: CallExpression, + result: CallExpression | Identifier, ): CheckerTransformerSuccessEither; buildError(): CheckerTransformerBuildErrorEither; addImport(path: string, typeName: string, type?: "default" | "namespace" | "direct"): void; @@ -27,47 +26,21 @@ export interface CheckerTransformerParams { ): readonly [ObjectLiteralExpression] | readonly []; } -/** - * @deprecated - */ -export type DataParserCheckers = ( - | DP.DataParserChecker - | DP.DataParserCheckerArrayMax - | DP.DataParserCheckerArrayMin - | DP.DataParserCheckerBigIntMax - | DP.DataParserCheckerBigIntMin - | DP.DataParserCheckerNumberMax - | DP.DataParserCheckerNumberMin - | DP.DataParserCheckerInt - | DP.DataParserCheckerStringMax - | DP.DataParserCheckerStringMin - | DP.DataParserCheckerEmail - | DP.DataParserCheckerRegex - | DP.DataParserCheckerUrl - | DP.DataParserCheckerUuid - | DP.DataParserCheckerRefine - | DP.DataParserCheckerTimeMin - | DP.DataParserCheckerTimeMax - | DServerDataParser.DataParserCheckerFileExist - | DServerDataParser.DataParserCheckerFileMimeType - | DServerDataParser.DataParserCheckerFileSize -); - export type CheckerTransformerBuildFunction< - GenericChecker extends DataParserCheckers = DataParserCheckers, + GenericChecker extends DP.DataParserCheckers = DP.DataParserCheckers, > = ( checker: GenericChecker, params: CheckerTransformerParams, ) => CheckerTransformerEither; export function createCheckerTransformer< - GenericChecker extends DataParserCheckers, + GenericChecker extends DP.DataParserCheckers, >( - support: (checker: DataParserCheckers) => checker is GenericChecker, + support: (checker: DP.DataParserCheckers) => checker is GenericChecker, builder: CheckerTransformerBuildFunction, ) { return ( - checker: DataParserCheckers, + checker: DP.DataParserCheckers, params: CheckerTransformerParams, ): CheckerTransformerEither => support(checker) ? builder( diff --git a/scripts/toDataParser/checkerTransformer/transformer.ts b/scripts/toDataParser/checkerTransformer/transformer.ts index 337e361..d1ea8fb 100644 --- a/scripts/toDataParser/checkerTransformer/transformer.ts +++ b/scripts/toDataParser/checkerTransformer/transformer.ts @@ -47,28 +47,37 @@ export function checkerTransformer( }, }; - return A.reduce( - params.transformers, - A.reduceFrom( - E.left("checkerNotSupport", checker), - ), - ({ - element: functionBuilder, - lastValue, - next, - exit, - }) => { - const result = functionBuilder(checker, functionParams); + if (checker.definition.mapImportContextEntries) { + TST.applyMapImportContextEntries( + functionParams.addImport, + checker.definition.mapImportContextEntries, + ); + } - if (E.isLeft(result)) { - if (!E.hasInformation(result, "checkerNotSupport")) { - return exit(result); - } + return checker.definition.overrideCheckerTransformer + ? checker.definition.overrideCheckerTransformer(checker, functionParams) + : A.reduce( + params.transformers, + A.reduceFrom( + E.left("checkerNotSupport", checker), + ), + ({ + element: functionBuilder, + lastValue, + next, + exit, + }) => { + const result = functionBuilder(checker, functionParams); - return next(lastValue); - } + if (E.isLeft(result)) { + if (!E.hasInformation(result, "checkerNotSupport")) { + return exit(result); + } - return exit(result); - }, - ); + return next(lastValue); + } + + return exit(result); + }, + ); } diff --git a/scripts/toDataParser/dataParserTransformer/create.ts b/scripts/toDataParser/dataParserTransformer/create.ts index 0f385f3..e9326b3 100644 --- a/scripts/toDataParser/dataParserTransformer/create.ts +++ b/scripts/toDataParser/dataParserTransformer/create.ts @@ -13,6 +13,8 @@ export type ToTypescriptDataParserNotSupportedEither = E.Left<"toTypescriptDataP export type ToTypescriptDataParserErrorEither = E.Left<"toTypescriptBuildDataParserError", DP.DataParser>; +export type ToTypescriptCheckerErrorEither = E.Left<"toTypescriptBuildCheckerError", DP.DataParserChecker>; + export type DataParserGetDefinitionErrorEither = E.Left< "buildDataParserGetDefinitionError", { @@ -38,7 +40,8 @@ export type MaybeTransformerEither = | DataParserErrorEither | DataParserGetDefinitionErrorEither | ToTypescriptDataParserNotSupportedEither - | ToTypescriptDataParserErrorEither; + | ToTypescriptDataParserErrorEither + | ToTypescriptCheckerErrorEither; export interface TransformerParams { readonly dependencyIdentifier: Identifier; diff --git a/scripts/toDataParser/dataParserTransformer/defaults/errorHandler.ts b/scripts/toDataParser/dataParserTransformer/defaults/errorHandler.ts new file mode 100644 index 0000000..5450055 --- /dev/null +++ b/scripts/toDataParser/dataParserTransformer/defaults/errorHandler.ts @@ -0,0 +1,12 @@ +import { DP } from "@duplojs/utils"; +import { createTransformer } from "../create"; + +export const errorHandlerTransformer = createTransformer( + DP.errorHandlerKind.has, + ( + dataParser, + { + transformer, + }, + ) => transformer(dataParser.definition.inner), +); diff --git a/scripts/toDataParser/dataParserTransformer/defaults/index.ts b/scripts/toDataParser/dataParserTransformer/defaults/index.ts index 2b30fdc..a8f777d 100644 --- a/scripts/toDataParser/dataParserTransformer/defaults/index.ts +++ b/scripts/toDataParser/dataParserTransformer/defaults/index.ts @@ -21,6 +21,7 @@ export * from "./transform"; export * from "./tuple"; export * from "./union"; export * from "./unknown"; +export * from "./errorHandler"; import type { createTransformer } from "../create"; import { arrayTransformer } from "./array"; @@ -46,6 +47,7 @@ import { transformTransformer } from "./transform"; import { tupleTransformer } from "./tuple"; import { unionTransformer } from "./union"; import { unknownTransformer } from "./unknown"; +import { errorHandlerTransformer } from "./errorHandler"; export const defaultTransformers = [ arrayTransformer, @@ -71,4 +73,5 @@ export const defaultTransformers = [ tupleTransformer, unionTransformer, unknownTransformer, + errorHandlerTransformer, ] as const satisfies readonly ReturnType[]; diff --git a/scripts/toDataParser/dataParserTransformer/getDefinitionDataParser.ts b/scripts/toDataParser/dataParserTransformer/getDefinitionDataParser.ts index 11ec1dd..e113d06 100644 --- a/scripts/toDataParser/dataParserTransformer/getDefinitionDataParser.ts +++ b/scripts/toDataParser/dataParserTransformer/getDefinitionDataParser.ts @@ -1,6 +1,6 @@ import { A, type DP, E, pipe, when } from "@duplojs/utils"; import { checkerTransformer, type createCheckerTransformer } from "../checkerTransformer"; -import { type CallExpression, factory, type PropertyAssignment } from "typescript"; +import { type CallExpression, factory, type Identifier, type PropertyAssignment } from "typescript"; import type * as TST from "@scripts/toTypescript"; export interface getDefinitionDataParserParams { @@ -27,7 +27,7 @@ export function getDefinitionDataParser(params: getDefinitionDataParserParams) { if (A.minElements(params.dataParser.definition.checkers, 1)) { const checkers = A.reduce( params.dataParser.definition.checkers, - A.reduceFrom([]), + A.reduceFrom<(CallExpression | Identifier)[]>([]), ({ element, lastValue, nextPush, exit }) => pipe( checkerTransformer( element, diff --git a/scripts/toDataParser/dataParserTransformer/transformer.ts b/scripts/toDataParser/dataParserTransformer/transformer.ts index 4f645a1..cae0806 100644 --- a/scripts/toDataParser/dataParserTransformer/transformer.ts +++ b/scripts/toDataParser/dataParserTransformer/transformer.ts @@ -11,6 +11,7 @@ export interface TransformerFunctionParams { readonly dataParserTransformers: readonly ReturnType[]; readonly checkerTransformers: readonly ReturnType[]; readonly typescriptTransformers: readonly ReturnType[]; + readonly typescriptCheckerRefiner?: readonly ReturnType[]; readonly context: MapContext; readonly typescriptContext: TST.MapContext; readonly importContext: TST.MapImportContext; @@ -122,6 +123,13 @@ export function transformer( addImport: TST.createAddImport(params.importContext), }; + if (currentDataParser.definition.mapImportContextEntries) { + TST.applyMapImportContextEntries( + functionParams.addImport, + currentDataParser.definition.mapImportContextEntries, + ); + } + const result = currentDataParser.definition.overrideDataParserTransformer ? currentDataParser.definition.overrideDataParserTransformer( currentDataParser.addOverrideDataParserTransformer(null), @@ -141,7 +149,14 @@ export function transformer( const result = functionBuilder(currentDataParser, functionParams); if (E.isLeft(result)) { - if (!E.hasInformation(result, "dataParserNotSupport")) { + if ( + E.hasInformation(result, [ + "buildDataParserError", + "buildDataParserGetDefinitionError", + "toTypescriptBuildDataParserError", + "toTypescriptBuildCheckerError", + ]) + ) { return exit(result); } @@ -168,12 +183,13 @@ export function transformer( { identifier, transformers: params.typescriptTransformers, + checkerRefiner: params.typescriptCheckerRefiner, context: params.typescriptContext, importContext: params.importContext, ...params.toTypescript, }, ); - return E.matchInformationOtherwise( + return E.matchInformation( result, { buildDataParserError: (dataParser) => E.left( @@ -184,8 +200,12 @@ export function transformer( "toTypescriptDataParserNotSupport", dataParser, ), + buildCheckerError: (checker) => E.left( + "toTypescriptBuildCheckerError", + checker, + ), + success: () => factory.createIdentifier(identifier), }, - () => factory.createIdentifier(identifier), ); }); diff --git a/scripts/toDataParser/override.ts b/scripts/toDataParser/override.ts index 1680bdb..f26c562 100644 --- a/scripts/toDataParser/override.ts +++ b/scripts/toDataParser/override.ts @@ -1,63 +1,96 @@ -import { DataParserBase } from "@duplojs/utils/dataParser"; +import "@scripts/toTypescript/override"; +import { DataParserBase, DataParserCheckerBase } from "@duplojs/utils/dataParser"; import type { CallExpression, Identifier } from "typescript"; import type { TransformerBuildFunction } from "./dataParserTransformer"; +import { type CheckerTransformerBuildFunction } from "./checkerTransformer"; declare module "@duplojs/utils/dataParser" { interface DataParserBase { - /** - * @deprecated this method mutated the dataParser by adding an identifier - */ - setIdentifier(input: string): this; - addIdentifier(input: string): this; - /** * @deprecated this method mutated the dataParser by adding an override transformer */ - setOverrideDataParserTransformer( + setOverrideDataParserTransformer< + GenericSelf extends DataParserBase = this, + >( transformer: CallExpression | Identifier | TransformerBuildFunction | null, - ): this; - addOverrideDataParserTransformer( + ): GenericSelf; + addOverrideDataParserTransformer< + GenericSelf extends DataParserBase = this, + >( transformer: CallExpression | Identifier | TransformerBuildFunction | null, - ): this; + ): GenericSelf; } interface DataParserDefinition { - identifier?: string; overrideDataParserTransformer?: TransformerBuildFunction; } + + interface DataParserCheckerBase { + + /** + * @deprecated this method mutated the checker by adding an override refiner + */ + setOverrideCheckerTransformer< + GenericSelf extends DataParserCheckerBase = this, + >( + typeNode: CallExpression | Identifier | CheckerTransformerBuildFunction | null + ): GenericSelf; + addOverrideCheckerTransformer< + GenericSelf extends DataParserCheckerBase = this, + >( + typeNode: CallExpression | Identifier | CheckerTransformerBuildFunction | null + ): GenericSelf; + } + + interface DataParserCheckerDefinition { + overrideCheckerTransformer?: CheckerTransformerBuildFunction; + } } -DataParserBase.prototype.setIdentifier = function(this: DataParserBase, identifier) { - this.definition.identifier = identifier; +DataParserBase.prototype.setOverrideDataParserTransformer = function(this: DataParserBase, overrideTransformer) { + if (overrideTransformer) { + this.definition.overrideDataParserTransformer = typeof overrideTransformer === "function" + ? overrideTransformer + : (__, { success }) => success(overrideTransformer); + } else { + this.definition.overrideDataParserTransformer = undefined; + } - return this; + return this as never; }; -DataParserBase.prototype.addIdentifier = function(this: DataParserBase, identifier) { +DataParserBase.prototype.addOverrideDataParserTransformer = function(this: DataParserBase, overrideTransformer) { const newSchema = this.clone(); - newSchema.setIdentifier(identifier); + newSchema.setOverrideDataParserTransformer(overrideTransformer); - return newSchema; + return newSchema as never; }; -DataParserBase.prototype.setOverrideDataParserTransformer = function(this: DataParserBase, overrideTransformer) { +DataParserCheckerBase.prototype.setOverrideCheckerTransformer = function( + this: DataParserCheckerBase, + overrideTransformer, +) { if (overrideTransformer) { - this.definition.overrideDataParserTransformer = typeof overrideTransformer === "function" + this.definition.overrideCheckerTransformer = typeof overrideTransformer === "function" ? overrideTransformer : (__, { success }) => success(overrideTransformer); } else { - this.definition.overrideDataParserTransformer = undefined; + this.definition.overrideCheckerTransformer = undefined; } - return this; + return this as never; }; -DataParserBase.prototype.addOverrideDataParserTransformer = function(this: DataParserBase, overrideTransformer) { +DataParserCheckerBase.prototype.addOverrideCheckerTransformer = function( + this: DataParserCheckerBase, + overrideTransformer, +) { const newSchema = this.clone(); - newSchema.setOverrideDataParserTransformer(overrideTransformer); + newSchema.setOverrideCheckerTransformer(overrideTransformer); - return newSchema; + return newSchema as never; }; + diff --git a/scripts/toDataParser/render.ts b/scripts/toDataParser/render.ts index 7fb69bc..95265bb 100644 --- a/scripts/toDataParser/render.ts +++ b/scripts/toDataParser/render.ts @@ -1,5 +1,5 @@ import type * as TST from "@scripts/toTypescript"; -import { type DataParserNotSupportedEither, type DataParserGetDefinitionErrorEither, type DataParserErrorEither, type MapContext, type ToTypescriptDataParserErrorEither, type ToTypescriptDataParserNotSupportedEither } from "./dataParserTransformer"; +import { type DataParserNotSupportedEither, type DataParserGetDefinitionErrorEither, type DataParserErrorEither, type MapContext, type ToTypescriptDataParserErrorEither, type ToTypescriptDataParserNotSupportedEither, type ToTypescriptCheckerErrorEither } from "./dataParserTransformer"; import { createToDataParserKind } from "./kind"; import { buildContext, type BuildContextParams } from "./buildContext"; import { type DP, E, kindClass, unwrap } from "@duplojs/utils"; @@ -23,7 +23,11 @@ export class DataParserToDataParserTypeRenderError extends kindClass( ) { public constructor( public dataParser: DP.DataParser, - public error: ToTypescriptDataParserNotSupportedEither | ToTypescriptDataParserErrorEither, + public error: ( + | ToTypescriptDataParserNotSupportedEither + | ToTypescriptDataParserErrorEither + | ToTypescriptCheckerErrorEither + ), ) { super({}, "Error during the render of recursive dataParser type."); } @@ -46,17 +50,22 @@ export function render(dataParser: DP.DataParser, params: RenderParams) { }); if ( - E.hasInformation(result, "buildDataParserError") - || E.hasInformation(result, "dataParserNotSupport") - || E.hasInformation(result, "buildDataParserGetDefinitionError") + E.hasInformation(result, [ + "buildDataParserError", + "dataParserNotSupport", + "buildDataParserGetDefinitionError", + ]) ) { throw new DataParserToDataParserRenderError( dataParser, result, ); } else if ( - E.hasInformation(result, "toTypescriptBuildDataParserError") - || E.hasInformation(result, "toTypescriptDataParserNotSupport") + E.hasInformation(result, [ + "toTypescriptBuildDataParserError", + "toTypescriptBuildCheckerError", + "toTypescriptDataParserNotSupport", + ]) ) { throw new DataParserToDataParserTypeRenderError( dataParser, diff --git a/scripts/toJsonSchema/override.ts b/scripts/toJsonSchema/override.ts index b94eca7..497086e 100644 --- a/scripts/toJsonSchema/override.ts +++ b/scripts/toJsonSchema/override.ts @@ -7,18 +7,26 @@ declare module "@duplojs/utils/dataParser" { /** * @deprecated this method mutated the dataParser by adding an identifier */ - setIdentifier(input: string): this; - addIdentifier(input: string): this; + setIdentifier< + GenericSelf extends DataParserBase = this, + >(input: string): GenericSelf; + addIdentifier< + GenericSelf extends DataParserBase = this, + >(input: string): GenericSelf; /** * @deprecated this method mutated the dataParser by adding an override transformer */ - setOverrideJsonSchemaTransformer( + setOverrideJsonSchemaTransformer< + GenericSelf extends DataParserBase = this, + >( transformer: TransformerSuccess | TransformerBuildFunction | null - ): this; - addOverrideJsonSchemaTransformer( + ): GenericSelf; + addOverrideJsonSchemaTransformer< + GenericSelf extends DataParserBase = this, + >( transformer: TransformerSuccess | TransformerBuildFunction | null - ): this; + ): GenericSelf; } interface DataParserDefinition { @@ -50,7 +58,7 @@ DataParserBase.prototype.setOverrideJsonSchemaTransformer = function(this: DataP this.definition.overrideJsonSchemaTransformer = undefined; } - return this; + return this as never; }; DataParserBase.prototype.addOverrideJsonSchemaTransformer = function(this: DataParserBase, overrideTransformer) { @@ -58,5 +66,5 @@ DataParserBase.prototype.addOverrideJsonSchemaTransformer = function(this: DataP newSchema.setOverrideJsonSchemaTransformer(overrideTransformer); - return newSchema; + return newSchema as never; }; diff --git a/scripts/toJsonSchema/transformer/defaults/errorHandler.ts b/scripts/toJsonSchema/transformer/defaults/errorHandler.ts new file mode 100644 index 0000000..c6b6542 --- /dev/null +++ b/scripts/toJsonSchema/transformer/defaults/errorHandler.ts @@ -0,0 +1,10 @@ +import { DP } from "@duplojs/utils"; +import { createTransformer } from "../create"; + +export const errorHandlerTransformer = createTransformer( + DP.errorHandlerKind.has, + ( + schema, + { transformer }, + ) => transformer(schema.definition.inner), +); diff --git a/scripts/toJsonSchema/transformer/defaults/index.ts b/scripts/toJsonSchema/transformer/defaults/index.ts index 0e025f9..c2563c4 100644 --- a/scripts/toJsonSchema/transformer/defaults/index.ts +++ b/scripts/toJsonSchema/transformer/defaults/index.ts @@ -21,6 +21,7 @@ export * from "./tuple"; export * from "./union"; export * from "./unknown"; export * from "./time"; +export * from "./errorHandler"; import { type createTransformer } from "../create"; import { arrayTransformer } from "./array"; @@ -46,6 +47,7 @@ import { unionTransformer } from "./union"; import { unknownTransformer } from "./unknown"; import { timeTransformer } from "./time"; import { fileTransformer } from "./file"; +import { errorHandlerTransformer } from "./errorHandler"; export const defaultTransformers = [ arrayTransformer, @@ -71,4 +73,5 @@ export const defaultTransformers = [ unknownTransformer, timeTransformer, fileTransformer, + errorHandlerTransformer, ] as const satisfies readonly ReturnType[]; diff --git a/scripts/toJsonSchema/transformer/transformer.ts b/scripts/toJsonSchema/transformer/transformer.ts index cbf4846..0e5b42d 100644 --- a/scripts/toJsonSchema/transformer/transformer.ts +++ b/scripts/toJsonSchema/transformer/transformer.ts @@ -134,7 +134,7 @@ export function transformer( const result = functionBuilder(currentSchema, functionParams); if (E.isLeft(result)) { - if (unwrap(result) !== currentSchema) { + if (E.hasInformation(result, "buildDataParserError")) { return exit(result); } diff --git a/scripts/toTypescript/buildContext.ts b/scripts/toTypescript/buildContext.ts index 97e17dc..4ac9840 100644 --- a/scripts/toTypescript/buildContext.ts +++ b/scripts/toTypescript/buildContext.ts @@ -1,7 +1,8 @@ import { DP, E, unwrap } from "@duplojs/utils"; -import { createIdentifier, type createTransformer, transformer, type MapContext, type MapImportContext, type TransformerHook, type TransformerMode, type DataParserErrorEither, type DataParserNotSupportedEither } from "./transformer"; +import { createIdentifier, type createTransformer, transformer, type MapContext, type MapImportContext, type TransformerHook, type TransformerMode, type DataParserErrorEither, type DataParserNotSupportedEither } from "./dataParserTransformer"; import { getRecursiveDataParser } from "@scripts/utils"; import { factory, SyntaxKind } from "typescript"; +import { type CheckerRefinerBuildErrorEither, type createCheckerRefiner } from "./checkerRefiner"; export interface BuildedContext { readonly context: MapContext; @@ -11,6 +12,7 @@ export interface BuildedContext { export interface BuildContextParams { readonly identifier: string; readonly transformers: readonly ReturnType[]; + readonly checkerRefiner?: readonly ReturnType[]; readonly context?: MapContext; readonly mode?: TransformerMode; readonly hooks?: readonly TransformerHook[]; @@ -24,6 +26,7 @@ export function buildContext( | E.Success | DataParserNotSupportedEither | DataParserErrorEither + | CheckerRefinerBuildErrorEither ) { const context: MapContext = params.context ?? new Map(); const importContext: MapImportContext = params.importContext ?? new Map(); diff --git a/scripts/toTypescript/checkerRefiner/create.ts b/scripts/toTypescript/checkerRefiner/create.ts new file mode 100644 index 0000000..09e04f0 --- /dev/null +++ b/scripts/toTypescript/checkerRefiner/create.ts @@ -0,0 +1,49 @@ +import type { TypeNode } from "typescript"; +import { type DP, E } from "@duplojs/utils"; +import { type TransformerMode, type MapImportContext } from "../dataParserTransformer"; + +export type CheckerRefinerSuccessEither = E.Right<"buildSuccess", TypeNode>; + +export type CheckerRefinerCheckerNotSupportedEither = E.Left<"checkerNotSupport", DP.DataParserCheckers>; + +export type CheckerRefinerBuildErrorEither = E.Left<"buildCheckerError", DP.DataParserCheckers>; + +export type MaybeCheckerRefinerEither = + | CheckerRefinerSuccessEither + | CheckerRefinerCheckerNotSupportedEither + | CheckerRefinerBuildErrorEither; + +export interface CheckerRefinerParams { + readonly mode: TransformerMode; + readonly importContext: MapImportContext; + success(result: TypeNode): CheckerRefinerSuccessEither; + buildError(): CheckerRefinerBuildErrorEither; + addImport(path: string, typeName: string, type?: "default" | "namespace" | "direct"): void; +} + +export type CheckerRefinerBuildFunction< + GenericChecker extends DP.DataParserCheckers = DP.DataParserCheckers, +> = ( + checker: GenericChecker, + currentTypeNode: TypeNode, + params: CheckerRefinerParams, +) => MaybeCheckerRefinerEither; + +export function createCheckerRefiner< + GenericChecker extends DP.DataParserCheckers, +>( + support: (checker: DP.DataParserCheckers) => checker is GenericChecker, + builder: CheckerRefinerBuildFunction, +) { + return ( + checker: DP.DataParserCheckers, + currentTypeNode: TypeNode, + params: CheckerRefinerParams, + ): MaybeCheckerRefinerEither => support(checker) + ? builder( + checker as GenericChecker, + currentTypeNode, + params, + ) + : E.left("checkerNotSupport", checker); +} diff --git a/scripts/toTypescript/checkerRefiner/default/arrayMax.ts b/scripts/toTypescript/checkerRefiner/default/arrayMax.ts new file mode 100644 index 0000000..32a38e9 --- /dev/null +++ b/scripts/toTypescript/checkerRefiner/default/arrayMax.ts @@ -0,0 +1,21 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerRefiner } from "../create"; +import { factory } from "typescript"; + +export const arrayMaxCheckerRefiner = createCheckerRefiner( + DP.checkerArrayMaxKind.has, + (checker, currentTypeNode, { success, addImport }) => { + addImport("@duplojs/utils/array", "MaxElements", "direct"); + + return success( + factory.createTypeReferenceNode( + factory.createIdentifier("MaxElements"), + [ + factory.createLiteralTypeNode( + factory.createNumericLiteral(checker.definition.max.toString()), + ), + ], + ), + ); + }, +); diff --git a/scripts/toTypescript/checkerRefiner/default/arrayMin.ts b/scripts/toTypescript/checkerRefiner/default/arrayMin.ts new file mode 100644 index 0000000..dc075c9 --- /dev/null +++ b/scripts/toTypescript/checkerRefiner/default/arrayMin.ts @@ -0,0 +1,21 @@ +import { A, DP, pipe } from "@duplojs/utils"; +import { createCheckerRefiner } from "../create"; +import { factory, isArrayTypeNode } from "typescript"; + +export const arrayMinCheckerRefiner = createCheckerRefiner( + DP.checkerArrayMinKind.has, + (checker, currentTypeNode, { success, buildError }) => isArrayTypeNode(currentTypeNode) + ? success( + pipe( + Array.from({ length: checker.definition.min }), + A.fillAll(currentTypeNode.elementType), + (result) => factory.createTupleTypeNode([ + ...result, + factory.createRestTypeNode( + currentTypeNode, + ), + ]), + ), + ) + : buildError(), +); diff --git a/scripts/toTypescript/checkerRefiner/default/email.ts b/scripts/toTypescript/checkerRefiner/default/email.ts new file mode 100644 index 0000000..c5e3925 --- /dev/null +++ b/scripts/toTypescript/checkerRefiner/default/email.ts @@ -0,0 +1,38 @@ +import { DP } from "@duplojs/utils"; +import { createCheckerRefiner } from "../create"; +import { factory, SyntaxKind } from "typescript"; + +export const emailCheckerRefiner = createCheckerRefiner( + DP.checkerEmailKind.has, + (checker, currentTypeNode, { success }) => success( + factory.createTemplateLiteralType( + factory.createTemplateHead( + "", + "", + ), + [ + factory.createTemplateLiteralTypeSpan( + factory.createKeywordTypeNode(SyntaxKind.StringKeyword), + factory.createTemplateMiddle( + "@", + "@", + ), + ), + factory.createTemplateLiteralTypeSpan( + factory.createKeywordTypeNode(SyntaxKind.StringKeyword), + factory.createTemplateMiddle( + ".", + ".", + ), + ), + factory.createTemplateLiteralTypeSpan( + factory.createKeywordTypeNode(SyntaxKind.StringKeyword), + factory.createTemplateTail( + "", + "", + ), + ), + ], + ), + ), +); diff --git a/scripts/toTypescript/checkerRefiner/default/index.ts b/scripts/toTypescript/checkerRefiner/default/index.ts new file mode 100644 index 0000000..7a10fdb --- /dev/null +++ b/scripts/toTypescript/checkerRefiner/default/index.ts @@ -0,0 +1,14 @@ +import { type createCheckerRefiner } from "../create"; +import { arrayMaxCheckerRefiner } from "./arrayMax"; +import { arrayMinCheckerRefiner } from "./arrayMin"; +import { emailCheckerRefiner } from "./email"; + +export * from "./email"; +export * from "./arrayMin"; +export * from "./arrayMax"; + +export const defaultCheckerRefiners = [ + emailCheckerRefiner, + arrayMinCheckerRefiner, + arrayMaxCheckerRefiner, +] as const satisfies readonly ReturnType[]; diff --git a/scripts/toTypescript/checkerRefiner/index.ts b/scripts/toTypescript/checkerRefiner/index.ts new file mode 100644 index 0000000..475ef5e --- /dev/null +++ b/scripts/toTypescript/checkerRefiner/index.ts @@ -0,0 +1,3 @@ +export * from "./create"; +export * from "./refiner"; +export * from "./default"; diff --git a/scripts/toTypescript/checkerRefiner/refiner.ts b/scripts/toTypescript/checkerRefiner/refiner.ts new file mode 100644 index 0000000..f5308b7 --- /dev/null +++ b/scripts/toTypescript/checkerRefiner/refiner.ts @@ -0,0 +1,55 @@ +import { type MaybeCheckerRefinerEither, type CheckerRefinerParams, type createCheckerRefiner } from "./create"; +import { type TransformerMode, type MapImportContext, createAddImport } from "../dataParserTransformer"; +import { type TypeNode } from "typescript"; +import { A, type DP, E, pipe, whenElse } from "@duplojs/utils"; +import { applyMapImportContextEntries } from "../override"; + +export interface CheckerTransformerFunctionParams { + readonly mode: TransformerMode; + readonly refiners: readonly ReturnType[]; + readonly importContext: MapImportContext; +} + +export function checkerRefiner( + checker: DP.DataParserCheckers, + currentTypeNode: TypeNode, + params: CheckerTransformerFunctionParams, +) { + const functionParams: CheckerRefinerParams = { + mode: params.mode, + importContext: params.importContext, + success(result) { + return E.right("buildSuccess", result); + }, + buildError() { + return E.left("buildCheckerError", checker); + }, + addImport: createAddImport(params.importContext), + }; + + if (checker.definition.mapImportContextEntries) { + applyMapImportContextEntries( + functionParams.addImport, + checker.definition.mapImportContextEntries, + ); + } + + return checker.definition.overrideTypescriptRefiner + ? checker.definition.overrideTypescriptRefiner(checker, currentTypeNode, functionParams) + : A.reduce( + params.refiners, + A.reduceFrom(E.left("checkerNotSupport", checker)), + ({ element: functionBuilder, next, exit }) => pipe( + functionBuilder( + checker, + currentTypeNode, + functionParams, + ), + whenElse( + E.hasInformation(["buildSuccess", "buildCheckerError"]), + exit, + next, + ), + ), + ); +} diff --git a/scripts/toTypescript/transformer/addImport.ts b/scripts/toTypescript/dataParserTransformer/addImport.ts similarity index 100% rename from scripts/toTypescript/transformer/addImport.ts rename to scripts/toTypescript/dataParserTransformer/addImport.ts diff --git a/scripts/toTypescript/transformer/create.ts b/scripts/toTypescript/dataParserTransformer/create.ts similarity index 72% rename from scripts/toTypescript/transformer/create.ts rename to scripts/toTypescript/dataParserTransformer/create.ts index 65d81be..bd4de42 100644 --- a/scripts/toTypescript/transformer/create.ts +++ b/scripts/toTypescript/dataParserTransformer/create.ts @@ -1,30 +1,31 @@ import { type TypeAliasDeclaration, type TypeNode } from "typescript"; import { type DP, E } from "@duplojs/utils"; +import { type CheckerRefinerBuildErrorEither } from "../checkerRefiner"; -export type TransformerSuccessEither< -> = E.Right<"buildSuccess", TypeNode>; +export type TransformerSuccessEither = E.Right<"buildSuccess", TypeNode>; -export type DataParserNotSupportedEither< -> = E.Left<"dataParserNotSupport", DP.DataParser>; +export type DataParserNotSupportedEither = E.Left<"dataParserNotSupport", DP.DataParser>; -export type DataParserErrorEither< -> = E.Left<"buildDataParserError", DP.DataParser>; +export type DataParserErrorEither = E.Left<"buildDataParserError", DP.DataParser>; export type MapContext = Map; +export interface MapImportContextValue { + namespace?: string[]; + default?: string[]; + direct?: string[]; +} + export type MapImportContext = Map< string, - { - namespace?: string[]; - default?: string[]; - direct?: string[]; - } + MapImportContextValue >; export type MaybeTransformerEither = | TransformerSuccessEither | DataParserNotSupportedEither - | DataParserErrorEither; + | DataParserErrorEither + | CheckerRefinerBuildErrorEither; export type TransformerMode = "in" | "out"; diff --git a/scripts/toTypescript/transformer/createIdentifier.ts b/scripts/toTypescript/dataParserTransformer/createIdentifier.ts similarity index 100% rename from scripts/toTypescript/transformer/createIdentifier.ts rename to scripts/toTypescript/dataParserTransformer/createIdentifier.ts diff --git a/scripts/toTypescript/transformer/createImportDeclaration.ts b/scripts/toTypescript/dataParserTransformer/createImportDeclaration.ts similarity index 100% rename from scripts/toTypescript/transformer/createImportDeclaration.ts rename to scripts/toTypescript/dataParserTransformer/createImportDeclaration.ts diff --git a/scripts/toTypescript/transformer/defaults/array.ts b/scripts/toTypescript/dataParserTransformer/defaults/array.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/array.ts rename to scripts/toTypescript/dataParserTransformer/defaults/array.ts diff --git a/scripts/toTypescript/transformer/defaults/bigint.ts b/scripts/toTypescript/dataParserTransformer/defaults/bigint.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/bigint.ts rename to scripts/toTypescript/dataParserTransformer/defaults/bigint.ts diff --git a/scripts/toTypescript/transformer/defaults/boolean.ts b/scripts/toTypescript/dataParserTransformer/defaults/boolean.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/boolean.ts rename to scripts/toTypescript/dataParserTransformer/defaults/boolean.ts diff --git a/scripts/toTypescript/transformer/defaults/date.ts b/scripts/toTypescript/dataParserTransformer/defaults/date.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/date.ts rename to scripts/toTypescript/dataParserTransformer/defaults/date.ts diff --git a/scripts/toTypescript/transformer/defaults/empty.ts b/scripts/toTypescript/dataParserTransformer/defaults/empty.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/empty.ts rename to scripts/toTypescript/dataParserTransformer/defaults/empty.ts diff --git a/scripts/toTypescript/dataParserTransformer/defaults/errorHandler.ts b/scripts/toTypescript/dataParserTransformer/defaults/errorHandler.ts new file mode 100644 index 0000000..c6b6542 --- /dev/null +++ b/scripts/toTypescript/dataParserTransformer/defaults/errorHandler.ts @@ -0,0 +1,10 @@ +import { DP } from "@duplojs/utils"; +import { createTransformer } from "../create"; + +export const errorHandlerTransformer = createTransformer( + DP.errorHandlerKind.has, + ( + schema, + { transformer }, + ) => transformer(schema.definition.inner), +); diff --git a/scripts/toTypescript/transformer/defaults/file.ts b/scripts/toTypescript/dataParserTransformer/defaults/file.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/file.ts rename to scripts/toTypescript/dataParserTransformer/defaults/file.ts diff --git a/scripts/toTypescript/transformer/defaults/index.ts b/scripts/toTypescript/dataParserTransformer/defaults/index.ts similarity index 95% rename from scripts/toTypescript/transformer/defaults/index.ts rename to scripts/toTypescript/dataParserTransformer/defaults/index.ts index ea43cf0..a2c14a9 100644 --- a/scripts/toTypescript/transformer/defaults/index.ts +++ b/scripts/toTypescript/dataParserTransformer/defaults/index.ts @@ -21,6 +21,7 @@ export * from "./unknown"; export * from "./date"; export * from "./time"; export * from "./file"; +export * from "./errorHandler"; import { type createTransformer } from "../create"; import { arrayTransformer } from "./array"; @@ -46,6 +47,7 @@ import { unknownTransformer } from "./unknown"; import { dateTransformer } from "./date"; import { timeTransformer } from "./time"; import { fileTransformer } from "./file"; +import { errorHandlerTransformer } from "./errorHandler"; export const defaultTransformers = [ arrayTransformer, @@ -71,4 +73,5 @@ export const defaultTransformers = [ dateTransformer, timeTransformer, fileTransformer, + errorHandlerTransformer, ] as const satisfies readonly ReturnType[]; diff --git a/scripts/toTypescript/transformer/defaults/lazy.ts b/scripts/toTypescript/dataParserTransformer/defaults/lazy.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/lazy.ts rename to scripts/toTypescript/dataParserTransformer/defaults/lazy.ts diff --git a/scripts/toTypescript/transformer/defaults/literal.ts b/scripts/toTypescript/dataParserTransformer/defaults/literal.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/literal.ts rename to scripts/toTypescript/dataParserTransformer/defaults/literal.ts diff --git a/scripts/toTypescript/transformer/defaults/nil.ts b/scripts/toTypescript/dataParserTransformer/defaults/nil.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/nil.ts rename to scripts/toTypescript/dataParserTransformer/defaults/nil.ts diff --git a/scripts/toTypescript/transformer/defaults/nullable.ts b/scripts/toTypescript/dataParserTransformer/defaults/nullable.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/nullable.ts rename to scripts/toTypescript/dataParserTransformer/defaults/nullable.ts diff --git a/scripts/toTypescript/transformer/defaults/number.ts b/scripts/toTypescript/dataParserTransformer/defaults/number.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/number.ts rename to scripts/toTypescript/dataParserTransformer/defaults/number.ts diff --git a/scripts/toTypescript/transformer/defaults/object.ts b/scripts/toTypescript/dataParserTransformer/defaults/object.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/object.ts rename to scripts/toTypescript/dataParserTransformer/defaults/object.ts diff --git a/scripts/toTypescript/transformer/defaults/optional.ts b/scripts/toTypescript/dataParserTransformer/defaults/optional.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/optional.ts rename to scripts/toTypescript/dataParserTransformer/defaults/optional.ts diff --git a/scripts/toTypescript/transformer/defaults/pipe.ts b/scripts/toTypescript/dataParserTransformer/defaults/pipe.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/pipe.ts rename to scripts/toTypescript/dataParserTransformer/defaults/pipe.ts diff --git a/scripts/toTypescript/transformer/defaults/record.ts b/scripts/toTypescript/dataParserTransformer/defaults/record.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/record.ts rename to scripts/toTypescript/dataParserTransformer/defaults/record.ts diff --git a/scripts/toTypescript/transformer/defaults/recover.ts b/scripts/toTypescript/dataParserTransformer/defaults/recover.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/recover.ts rename to scripts/toTypescript/dataParserTransformer/defaults/recover.ts diff --git a/scripts/toTypescript/transformer/defaults/string.ts b/scripts/toTypescript/dataParserTransformer/defaults/string.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/string.ts rename to scripts/toTypescript/dataParserTransformer/defaults/string.ts diff --git a/scripts/toTypescript/transformer/defaults/templateLiteral.ts b/scripts/toTypescript/dataParserTransformer/defaults/templateLiteral.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/templateLiteral.ts rename to scripts/toTypescript/dataParserTransformer/defaults/templateLiteral.ts diff --git a/scripts/toTypescript/transformer/defaults/time.ts b/scripts/toTypescript/dataParserTransformer/defaults/time.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/time.ts rename to scripts/toTypescript/dataParserTransformer/defaults/time.ts diff --git a/scripts/toTypescript/transformer/defaults/transform.ts b/scripts/toTypescript/dataParserTransformer/defaults/transform.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/transform.ts rename to scripts/toTypescript/dataParserTransformer/defaults/transform.ts diff --git a/scripts/toTypescript/transformer/defaults/tuple.ts b/scripts/toTypescript/dataParserTransformer/defaults/tuple.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/tuple.ts rename to scripts/toTypescript/dataParserTransformer/defaults/tuple.ts diff --git a/scripts/toTypescript/transformer/defaults/union.ts b/scripts/toTypescript/dataParserTransformer/defaults/union.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/union.ts rename to scripts/toTypescript/dataParserTransformer/defaults/union.ts diff --git a/scripts/toTypescript/transformer/defaults/unknown.ts b/scripts/toTypescript/dataParserTransformer/defaults/unknown.ts similarity index 100% rename from scripts/toTypescript/transformer/defaults/unknown.ts rename to scripts/toTypescript/dataParserTransformer/defaults/unknown.ts diff --git a/scripts/toTypescript/transformer/hook.ts b/scripts/toTypescript/dataParserTransformer/hook.ts similarity index 100% rename from scripts/toTypescript/transformer/hook.ts rename to scripts/toTypescript/dataParserTransformer/hook.ts diff --git a/scripts/toTypescript/transformer/includesUndefinedTypeNode.ts b/scripts/toTypescript/dataParserTransformer/includesUndefinedTypeNode.ts similarity index 100% rename from scripts/toTypescript/transformer/includesUndefinedTypeNode.ts rename to scripts/toTypescript/dataParserTransformer/includesUndefinedTypeNode.ts diff --git a/scripts/toTypescript/transformer/index.ts b/scripts/toTypescript/dataParserTransformer/index.ts similarity index 100% rename from scripts/toTypescript/transformer/index.ts rename to scripts/toTypescript/dataParserTransformer/index.ts diff --git a/scripts/toTypescript/dataParserTransformer/transformer.ts b/scripts/toTypescript/dataParserTransformer/transformer.ts new file mode 100644 index 0000000..a03caa0 --- /dev/null +++ b/scripts/toTypescript/dataParserTransformer/transformer.ts @@ -0,0 +1,224 @@ +import { A, type DDataParser, E, unwrap, type DP, justExec, pipe, when, type AnyTuple, whenElse } from "@duplojs/utils"; +import type { MapContext, DataParserNotSupportedEither, TransformerParams, createTransformer, TransformerMode, DataParserErrorEither, MapImportContext, TransformerSuccessEither } from "./create"; +import { factory, SyntaxKind, type TypeNode } from "typescript"; +import type { TransformerHook } from "./hook"; +import { createAddImport } from "./addImport"; +import { createIdentifier } from "./createIdentifier"; +import { checkerRefiner, type CheckerRefinerBuildErrorEither, type CheckerTransformerFunctionParams, type createCheckerRefiner } from "../checkerRefiner"; +import { applyMapImportContextEntries } from "../override"; + +export interface TransformerFunctionParams { + readonly transformers: readonly ReturnType[]; + readonly checkerRefiner?: readonly ReturnType[]; + readonly context: MapContext; + readonly mode: TransformerMode; + readonly hooks: readonly TransformerHook[]; + readonly recursiveDataParsers: DDataParser.DataParser[]; + readonly importContext: MapImportContext; +} + +export function transformer( + dataParser: DP.DataParser, + params: TransformerFunctionParams, +) { + const currentDataParser = A.reduce( + params.hooks, + A.reduceFrom(dataParser), + ({ element: hook, lastValue, next, exit }) => { + const result = hook({ + schema: lastValue, + context: params.context, + importContext: params.importContext, + output: (action, schema) => ({ + schema, + action, + }), + }); + + if (result.action === "stop") { + return exit(result.schema); + } else { + return next(result.schema); + } + }, + ); + + const currentDeclaration = params.context.get(currentDataParser); + + if (currentDeclaration) { + return E.right( + "buildSuccess", + factory.createTypeReferenceNode( + currentDeclaration.name, + ), + ); + } + + const currentIdentifier = justExec(() => { + if ( + !A.includes(params.recursiveDataParsers, currentDataParser) + && !currentDataParser.definition.identifier + ) { + return undefined; + } + + const identifier = currentDataParser.definition.identifier !== undefined + ? createIdentifier(currentDataParser.definition.identifier) + : `RecursiveType${params.context.size}`; + + params.context.set( + currentDataParser, + factory.createTypeAliasDeclaration( + [factory.createToken(SyntaxKind.ExportKeyword)], + factory.createIdentifier(identifier), + undefined, + factory.createTypeReferenceNode( + identifier, + ), + ), + ); + + return identifier; + }); + + const functionParams: TransformerParams = { + success(result) { + return E.right("buildSuccess", result); + }, + transformer(schema) { + return transformer( + schema, + params, + ); + }, + context: params.context, + mode: params.mode, + buildError() { + return E.left("buildDataParserError"); + }, + importContext: params.importContext, + addImport: createAddImport(params.importContext), + }; + + if (currentDataParser.definition.mapImportContextEntries) { + applyMapImportContextEntries( + functionParams.addImport, + currentDataParser.definition.mapImportContextEntries, + ); + } + + const result = pipe( + currentDataParser.definition.overrideTypescriptTransformer + ? currentDataParser.definition.overrideTypescriptTransformer( + currentDataParser.addOverrideTypescriptTransformer(null), + functionParams, + ) + : A.reduce( + params.transformers, + A.reduceFrom( + E.left("dataParserNotSupport", currentDataParser), + ), + ({ + element: functionBuilder, + lastValue, + next, + exit, + }) => { + const result = functionBuilder(currentDataParser, functionParams); + + if (E.isLeft(result)) { + if (E.hasInformation(result, ["buildCheckerError", "buildDataParserError"])) { + return exit(result); + } + + return next(lastValue); + } + + return exit(result); + }, + ), + when( + E.hasInformation("buildSuccess"), + (resultTypeNode): TransformerSuccessEither | CheckerRefinerBuildErrorEither => { + if ( + !A.minElements(currentDataParser.definition.checkers, 1) + || !params.checkerRefiner + || !A.minElements(params.checkerRefiner, 1) + ) { + return resultTypeNode; + } + + const currentTypeNode = unwrap(resultTypeNode); + + const refinerFunctionParams: CheckerTransformerFunctionParams = { + mode: functionParams.mode, + importContext: functionParams.importContext, + refiners: params.checkerRefiner, + }; + return pipe( + currentDataParser.definition.checkers, + A.reduce( + A.reduceFrom>([currentTypeNode]), + ({ element: checker, next, exit, lastValue }) => pipe( + checkerRefiner( + checker, + currentTypeNode, + refinerFunctionParams, + ), + E.whenHasInformationOtherwise( + "buildSuccess", + (refinedTypeNode) => next([...lastValue, refinedTypeNode]), + whenElse( + E.hasInformation(["buildCheckerError"]), + exit, + () => next(lastValue), + ), + ), + ), + ), + (result) => { + if (E.hasInformation(result, "buildCheckerError")) { + return result; + } + + if (A.minElements(result, 2)) { + return E.right( + "buildSuccess", + factory.createIntersectionTypeNode(result), + ); + } + + return resultTypeNode; + }, + ); + }, + ), + ); + + if (E.isLeft(result)) { + return result; + } + + if (currentIdentifier) { + params.context.delete(currentDataParser); + + params.context.set( + currentDataParser, + factory.createTypeAliasDeclaration( + [factory.createToken(SyntaxKind.ExportKeyword)], + factory.createIdentifier(currentIdentifier), + undefined, + unwrap(result), + ), + ); + + return E.right( + "buildSuccess", + factory.createTypeReferenceNode( + currentIdentifier, + ), + ); + } + + return result; +} diff --git a/scripts/toTypescript/index.ts b/scripts/toTypescript/index.ts index 9d369e4..c5d76da 100644 --- a/scripts/toTypescript/index.ts +++ b/scripts/toTypescript/index.ts @@ -1,4 +1,5 @@ -export * from "./transformer"; +export * from "./dataParserTransformer"; +export * from "./checkerRefiner"; export * from "./override"; export * from "./render"; export * from "./kind"; diff --git a/scripts/toTypescript/override.ts b/scripts/toTypescript/override.ts index 2a66229..3961cd6 100644 --- a/scripts/toTypescript/override.ts +++ b/scripts/toTypescript/override.ts @@ -1,6 +1,27 @@ -import { DataParserBase } from "@duplojs/utils/dataParser"; +import { DataParserBase, DataParserCheckerBase } from "@duplojs/utils/dataParser"; import { type TypeNode } from "typescript"; -import { type TransformerBuildFunction } from "./transformer"; +import { type createAddImport, type MapImportContextValue, type TransformerBuildFunction } from "./dataParserTransformer"; +import { O, type AnyTuple } from "@duplojs/utils"; +import { type CheckerRefinerBuildFunction } from "./checkerRefiner"; + +export type MapImportContextEntry = [path: string, value: MapImportContextValue]; + +export function applyMapImportContextEntries( + addImport: ReturnType, + entries: AnyTuple, +) { + for (const [path, importContextValue] of entries) { + for (const [importType, importValues] of O.entries(importContextValue)) { + if (!importValues) { + continue; + } + + for (const importValue of importValues) { + addImport(path, importValue, importType); + } + } + } +} declare module "@duplojs/utils/dataParser" { interface DataParserBase { @@ -8,19 +29,66 @@ declare module "@duplojs/utils/dataParser" { /** * @deprecated this method mutated the dataParser by adding an identifier */ - setIdentifier(identifier: string): this; - addIdentifier(identifier: string): this; + setIdentifier< + GenericSelf extends DataParserBase = this, + >(identifier: string): GenericSelf; + addIdentifier< + GenericSelf extends DataParserBase = this, + >(identifier: string): GenericSelf; /** * @deprecated this method mutated the dataParser by adding an override transformer */ - setOverrideTypescriptTransformer(typeNode: TypeNode | TransformerBuildFunction | null): this; - addOverrideTypescriptTransformer(typeNode: TypeNode | TransformerBuildFunction | null): this; + setOverrideTypescriptTransformer< + GenericSelf extends DataParserBase = this, + >(typeNode: TypeNode | TransformerBuildFunction | null): GenericSelf; + addOverrideTypescriptTransformer< + GenericSelf extends DataParserBase = this, + >(typeNode: TypeNode | TransformerBuildFunction | null): GenericSelf; + + /** + * @deprecated this method mutated the dataParser by adding import + */ + setMapImportContextEntries< + GenericSelf extends DataParserBase = this, + >(...args: AnyTuple): GenericSelf; + addMapImportContextEntries< + GenericSelf extends DataParserBase = this, + >(...args: AnyTuple): GenericSelf; } interface DataParserDefinition { identifier?: string; overrideTypescriptTransformer?: TransformerBuildFunction; + mapImportContextEntries?: AnyTuple; + } + + interface DataParserCheckerBase { + + /** + * @deprecated this method mutated the checker by adding an override refiner + */ + setOverrideTypescriptRefiner< + GenericSelf extends DataParserCheckerBase = this, + >(typeNode: TypeNode | CheckerRefinerBuildFunction | null): GenericSelf; + addOverrideTypescriptRefiner< + GenericSelf extends DataParserCheckerBase = this, + >(typeNode: TypeNode | CheckerRefinerBuildFunction | null): GenericSelf; + + /** + * @deprecated this method mutated the checker by adding import + */ + setMapImportContextEntries< + GenericSelf extends DataParserCheckerBase = this, + >(...args: AnyTuple): GenericSelf; + addMapImportContextEntries< + GenericSelf extends DataParserCheckerBase = this, + >(...args: AnyTuple): GenericSelf; + } + + interface DataParserCheckerDefinition { + overrideTypescriptRefiner?: CheckerRefinerBuildFunction; + mapImportContextEntries?: AnyTuple; } } @@ -47,7 +115,7 @@ DataParserBase.prototype.setOverrideTypescriptTransformer = function(this: DataP this.definition.overrideTypescriptTransformer = undefined; } - return this; + return this as never; }; DataParserBase.prototype.addOverrideTypescriptTransformer = function(this: DataParserBase, overrideTransformer) { @@ -55,5 +123,59 @@ DataParserBase.prototype.addOverrideTypescriptTransformer = function(this: DataP newSchema.setOverrideTypescriptTransformer(overrideTransformer); - return newSchema; + return newSchema as never; +}; + +DataParserBase.prototype.setMapImportContextEntries = function(this: DataParserBase, ...importValues) { + this.definition.mapImportContextEntries = importValues; + + return this as never; +}; + +DataParserBase.prototype.addMapImportContextEntries = function(this: DataParserBase, ...importValues) { + const newSchema = this.clone(); + + newSchema.setMapImportContextEntries(...importValues); + + return newSchema as never; +}; + +DataParserCheckerBase.prototype.setOverrideTypescriptRefiner = function(this: DataParserCheckerBase, overrideRefiner) { + if (overrideRefiner) { + this.definition.overrideTypescriptRefiner = typeof overrideRefiner === "function" + ? overrideRefiner + : (__, ___, { success }) => success(overrideRefiner); + } else { + this.definition.overrideTypescriptRefiner = undefined; + } + + return this as never; +}; + +DataParserCheckerBase.prototype.addOverrideTypescriptRefiner = function(this: DataParserCheckerBase, overrideRefiner) { + const newSchema = this.clone(); + + newSchema.setOverrideTypescriptRefiner(overrideRefiner); + + return newSchema as never; +}; + +DataParserCheckerBase.prototype.setMapImportContextEntries = function( + this: DataParserCheckerBase, + ...mapImportContextEntries +) { + this.definition.mapImportContextEntries = mapImportContextEntries; + + return this as never; +}; + +DataParserCheckerBase.prototype.addMapImportContextEntries = function( + this: DataParserCheckerBase, + ...mapImportContextEntries +) { + const newSchema = this.clone(); + + newSchema.setMapImportContextEntries(...mapImportContextEntries); + + return newSchema as never; }; diff --git a/scripts/toTypescript/printer.ts b/scripts/toTypescript/printer.ts index fd1e27c..6c0545b 100644 --- a/scripts/toTypescript/printer.ts +++ b/scripts/toTypescript/printer.ts @@ -1,7 +1,7 @@ import { createPrinter, createSourceFile, EmitHint, ScriptKind, ScriptTarget } from "typescript"; import { type BuildedContext } from "./buildContext"; import { A, pipe, S } from "@duplojs/utils"; -import { createImportDeclaration } from "./transformer"; +import { createImportDeclaration } from "./dataParserTransformer"; export function printer(params: BuildedContext) { const sourceFile = createSourceFile("print.ts", "", ScriptTarget.Latest, false, ScriptKind.TS); diff --git a/scripts/toTypescript/render.ts b/scripts/toTypescript/render.ts index 1482ca5..e8e2b47 100644 --- a/scripts/toTypescript/render.ts +++ b/scripts/toTypescript/render.ts @@ -1,8 +1,9 @@ import { type DP, E, kindClass, unwrap } from "@duplojs/utils"; -import { type DataParserErrorEither, type DataParserNotSupportedEither } from "./transformer"; +import { type DataParserErrorEither, type DataParserNotSupportedEither } from "./dataParserTransformer"; import { createToTypescriptKind } from "./kind"; import { buildContext, type BuildContextParams } from "./buildContext"; import { printer } from "./printer"; +import { type CheckerRefinerBuildErrorEither } from "./checkerRefiner"; export interface RenderParams extends BuildContextParams { @@ -14,7 +15,7 @@ export class DataParserToTypescriptRenderError extends kindClass( ) { public constructor( public schema: DP.DataParser, - public error: DataParserNotSupportedEither | DataParserErrorEither, + public error: DataParserNotSupportedEither | DataParserErrorEither | CheckerRefinerBuildErrorEither, ) { super({}, "Error during the render of dataParser in typescript type."); } diff --git a/scripts/toTypescript/transformer/transformer.ts b/scripts/toTypescript/transformer/transformer.ts deleted file mode 100644 index 043d78e..0000000 --- a/scripts/toTypescript/transformer/transformer.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { A, type DDataParser, E, unwrap, type DP, justExec } from "@duplojs/utils"; -import type { MapContext, DataParserNotSupportedEither, TransformerParams, createTransformer, TransformerMode, DataParserErrorEither, MapImportContext } from "./create"; -import { factory, SyntaxKind } from "typescript"; -import type { TransformerHook } from "./hook"; -import { createAddImport } from "./addImport"; -import { createIdentifier } from "./createIdentifier"; - -export interface TransformerFunctionParams { - readonly transformers: readonly ReturnType[]; - readonly context: MapContext; - readonly mode: TransformerMode; - readonly hooks: readonly TransformerHook[]; - readonly recursiveDataParsers: DDataParser.DataParser[]; - readonly importContext: MapImportContext; -} -export function transformer( - schema: DP.DataParser, - params: TransformerFunctionParams, -) { - const currentSchema = A.reduce( - params.hooks, - A.reduceFrom(schema), - ({ element: hook, lastValue, next, exit }) => { - const result = hook({ - schema: lastValue, - context: params.context, - importContext: params.importContext, - output: (action, schema) => ({ - schema, - action, - }), - }); - if (result.action === "stop") { - return exit(result.schema); - } else { - return next(result.schema); - } - }, - ); - - const currentDeclaration = params.context.get(currentSchema); - - if (currentDeclaration) { - return E.right( - "buildSuccess", - factory.createTypeReferenceNode( - currentDeclaration.name, - ), - ); - } - - const currentIdentifier = justExec(() => { - if ( - !A.includes(params.recursiveDataParsers, currentSchema) - && !currentSchema.definition.identifier - ) { - return undefined; - } - - const identifier = currentSchema.definition.identifier !== undefined - ? createIdentifier(currentSchema.definition.identifier) - : `RecursiveType${params.context.size}`; - - params.context.set( - currentSchema, - factory.createTypeAliasDeclaration( - [factory.createToken(SyntaxKind.ExportKeyword)], - factory.createIdentifier(identifier), - undefined, - factory.createTypeReferenceNode( - identifier, - ), - ), - ); - - return identifier; - }); - - const functionParams: TransformerParams = { - success(result) { - return E.right("buildSuccess", result); - }, - transformer(schema) { - return transformer( - schema, - params, - ); - }, - context: params.context, - mode: params.mode, - buildError() { - return E.left("buildDataParserError"); - }, - importContext: params.importContext, - addImport: createAddImport(params.importContext), - }; - - const result = currentSchema.definition.overrideTypescriptTransformer - ? currentSchema.definition.overrideTypescriptTransformer( - currentSchema.addOverrideTypescriptTransformer(null), - functionParams, - ) - : A.reduce( - params.transformers, - A.reduceFrom( - E.left("dataParserNotSupport", currentSchema), - ), - ({ - element: functionBuilder, - lastValue, - next, - exit, - }) => { - const result = functionBuilder(currentSchema, functionParams); - - if (E.isLeft(result)) { - if (unwrap(result) !== currentSchema) { - return exit(result); - } - - return next(lastValue); - } - - return exit(result); - }, - ); - - if (E.isLeft(result)) { - return result; - } - - if (currentIdentifier) { - params.context.delete(currentSchema); - - params.context.set( - currentSchema, - factory.createTypeAliasDeclaration( - [factory.createToken(SyntaxKind.ExportKeyword)], - factory.createIdentifier(currentIdentifier), - undefined, - unwrap(result), - ), - ); - - return E.right( - "buildSuccess", - factory.createTypeReferenceNode( - currentIdentifier, - ), - ); - } - - return result; -} diff --git a/tests/finder/researcher/defaults/__snapshots__/errorHandler.test.ts.snap b/tests/finder/researcher/defaults/__snapshots__/errorHandler.test.ts.snap new file mode 100644 index 0000000..cf7c7fb --- /dev/null +++ b/tests/finder/researcher/defaults/__snapshots__/errorHandler.test.ts.snap @@ -0,0 +1,14 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`errorHandlerResearcher > finds the inner parser 1`] = ` +[ + { + "definition": { + "checkers": [], + "coerce": false, + "errorMessage": undefined, + }, + "isTarget": true, + }, +] +`; diff --git a/tests/finder/researcher/defaults/errorHandler.test.ts b/tests/finder/researcher/defaults/errorHandler.test.ts new file mode 100644 index 0000000..fd0bb35 --- /dev/null +++ b/tests/finder/researcher/defaults/errorHandler.test.ts @@ -0,0 +1,31 @@ +import { DP, DPE } from "@duplojs/utils"; +import { dataParserFinder } from "@scripts/finder"; +import { errorHandlerResearcher } from "@scripts/finder/researcher"; + +describe("errorHandlerResearcher", () => { + it("finds the inner parser", () => { + const target = DPE.boolean(); + const schema = DPE.errorHandler( + target, + DP.createErrorMessageTransformer(DP.booleanKind, () => "Expected boolean."), + ); + const result = dataParserFinder( + schema, + (dataParser) => dataParser === target, + { + researchers: [errorHandlerResearcher], + ignore: new Set(), + }, + ); + + expect( + result.map( + (dataParser) => ({ + isTarget: dataParser === target, + definition: dataParser.definition, + }), + ), + ).toMatchSnapshot(); + expect(result).toEqual([target]); + }); +}); diff --git a/tests/toDataParser/__snapshots__/render.test.ts.snap b/tests/toDataParser/__snapshots__/render.test.ts.snap index fc220ff..85a7cf9 100644 --- a/tests/toDataParser/__snapshots__/render.test.ts.snap +++ b/tests/toDataParser/__snapshots__/render.test.ts.snap @@ -1,5 +1,23 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`render > renders anonymous recursive dataParser in extended mode 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +import * as DPE from "@duplojs/utils/dataParserExtended"; + +export type RecursiveType0 = { + next: RecursiveType0; +}; + +export type $recursive0DataParser = RecursiveType0; + +export const recursive0DataParser: DPE.DataParserExtended<$recursive0DataParser, unknown> = DPE.object({ + next: DPE.lazy(() => recursive0DataParser) +}); + +export const recursiveExtendedNodeParserDataParser = recursive0DataParser;" +`; + exports[`render > renders anonymous recursive dataParser with a temporary const 1`] = ` "import * as DP from "@duplojs/utils/dataParser"; diff --git a/tests/toDataParser/checkerTransfomers/overrideDefinition.test.ts b/tests/toDataParser/checkerTransfomers/overrideDefinition.test.ts new file mode 100644 index 0000000..569fbab --- /dev/null +++ b/tests/toDataParser/checkerTransfomers/overrideDefinition.test.ts @@ -0,0 +1,48 @@ +import "@scripts/toDataParser/override"; +import { DP, E } from "@duplojs/utils"; +import { checkerTransformer, getCheckerDefinition } from "@scripts/toDataParser"; +import { factory } from "typescript"; + +describe("checker transformer override and definition", () => { + it("builds checker definition with error message", () => { + const checker = DP.checkerStringMin(1, { + errorMessage: "Too short.", + }); + + expect(getCheckerDefinition(checker)).toHaveLength(1); + }); + + it("applies map imports from checker definition", () => { + const checker = DP.checkerStringMin(1) + .addMapImportContextEntries(["module-a", { direct: ["checkerImport"] }]); + const importContext = new Map(); + + const result = checkerTransformer( + checker, + { + transformers: [(__, { success }) => success(factory.createIdentifier("checkerImport"))], + importContext, + }, + ); + + expect(E.isRight(result)).toBe(true); + expect(importContext.get("module-a")).toEqual({ direct: ["checkerImport"] }); + }); + + it("uses override checker transformer", () => { + const checker = DP.checkerStringMin(1) + .addOverrideCheckerTransformer(factory.createIdentifier("overrideChecker")); + + expect( + checkerTransformer( + checker, + { + transformers: [], + importContext: new Map(), + }, + ), + ).toStrictEqual( + E.right("buildSuccess", factory.createIdentifier("overrideChecker")), + ); + }); +}); diff --git a/tests/toDataParser/checkerTransfomers/utils.ts b/tests/toDataParser/checkerTransfomers/utils.ts index 5209fbb..db79124 100644 --- a/tests/toDataParser/checkerTransfomers/utils.ts +++ b/tests/toDataParser/checkerTransfomers/utils.ts @@ -1,6 +1,6 @@ -import { createPrinter, createSourceFile, EmitHint, ScriptKind, ScriptTarget, type CallExpression } from "typescript"; +import { createPrinter, createSourceFile, EmitHint, type Identifier, ScriptKind, ScriptTarget, type CallExpression } from "typescript"; -export function printExpression(expression: CallExpression) { +export function printExpression(expression: CallExpression | Identifier) { const printer = createPrinter(); const sourceFile = createSourceFile("test.ts", "", ScriptTarget.Latest, false, ScriptKind.TS); return printer.printNode(EmitHint.Unspecified, expression, sourceFile); diff --git a/tests/toDataParser/dataParserTransformer/__snapshots__/errorHandler.test.ts.snap b/tests/toDataParser/dataParserTransformer/__snapshots__/errorHandler.test.ts.snap new file mode 100644 index 0000000..3ceda08 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/__snapshots__/errorHandler.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`errorHandler > renders inner parser 1`] = ` +"import * as DP from "@duplojs/utils/dataParser"; + +export const errorHandlerParserDataParser = DP.string();" +`; diff --git a/tests/toDataParser/dataParserTransformer/definitionError.test.ts b/tests/toDataParser/dataParserTransformer/definitionError.test.ts new file mode 100644 index 0000000..f87dc95 --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/definitionError.test.ts @@ -0,0 +1,106 @@ +import { SDP } from "@duplojs/server-utils"; +import { DP, DPE, E } from "@duplojs/utils"; +import { + arrayTransformer, + bigIntTransformer, + booleanTransformer, + dateTransformer, + emptyTransformer, + fileTransformer, + lazyTransformer, + literalTransformer, + nilTransformer, + nullableTransformer, + numberTransformer, + objectTransformer, + optionalTransformer, + pipeTransformer, + recordTransformer, + stringTransformer, + templateLiteralTransformer, + timeTransformer, + transformTransformer, + tupleTransformer, + unionTransformer, + unknownTransformer, +} from "@scripts/toDataParser/dataParserTransformer/defaults"; +import { getDefinitionDataParser } from "@scripts/toDataParser"; +import { factory } from "typescript"; + +describe("default dataParser transformers definition error", () => { + const definitionErrorChecker = DP.checkerStringMin(1); + + const buildParams = (schema: DP.DataParser) => ({ + success: (result: any) => E.right("buildSuccess", result), + transformer: () => E.right("buildSuccess", factory.createIdentifier("inner")), + context: new Map(), + dependencyIdentifier: factory.createIdentifier("DP"), + buildError: () => E.left("buildDataParserError", schema), + importContext: new Map(), + getDefinition: () => E.left("buildDataParserGetDefinitionError", { + dataParser: schema, + error: E.left("buildCheckerError", schema.definition.checkers[0] ?? definitionErrorChecker), + }), + addImport: vi.fn(), + }) as any; + + it.each([ + ["array", arrayTransformer, DPE.array(DPE.string())], + ["bigint", bigIntTransformer, DPE.bigint()], + ["boolean", booleanTransformer, DPE.boolean()], + ["date", dateTransformer, DPE.date()], + ["empty", emptyTransformer, DPE.empty()], + ["file", fileTransformer, SDP.file()], + ["lazy", lazyTransformer, DPE.lazy(() => DPE.string())], + ["literal", literalTransformer, DPE.literal(["value"])], + ["nil", nilTransformer, DPE.nil()], + ["nullable", nullableTransformer, DPE.nullable(DPE.string())], + ["number", numberTransformer, DPE.number()], + ["object", objectTransformer, DPE.object({ value: DPE.string() })], + ["optional", optionalTransformer, DPE.optional(DPE.string())], + ["pipe", pipeTransformer, DPE.pipe(DPE.string(), DPE.number())], + ["record", recordTransformer, DPE.record(DPE.string(), DPE.number())], + ["string", stringTransformer, DPE.string()], + ["templateLiteral", templateLiteralTransformer, DPE.templateLiteral(["id-", DPE.string()])], + ["time", timeTransformer, DPE.time()], + ["transform", transformTransformer, DPE.transform(DPE.string(), (value) => value)], + ["tuple", tupleTransformer, DPE.tuple([DPE.string()], { rest: DPE.number() })], + ["union", unionTransformer, DPE.union([DPE.string(), DPE.number()])], + ["unknown", unknownTransformer, DPE.unknown()], + ])("returns definition error for %s", (__, transformer, schema) => { + expect(transformer(schema as any, buildParams(schema as DP.DataParser))).toStrictEqual( + E.left("buildDataParserGetDefinitionError", { + dataParser: schema, + error: E.left( + "buildCheckerError", + (schema as DP.DataParser).definition.checkers[0] ?? definitionErrorChecker, + ), + }), + ); + }); + + it("returns getDefinitionDataParser error when checker transformation fails", () => { + const dataParser = DPE.string({ + errorMessage: "Invalid string.", + checkers: [DP.checkerStringMin(1)], + }); + + expect( + getDefinitionDataParser({ + dataParser, + checkerTransformers: [ + (checker, { buildError }) => DP.checkerStringMinKind.has(checker) + ? buildError() + : E.left("checkerNotSupport", checker), + ], + importContext: new Map(), + customProperties: [], + }), + ).toStrictEqual( + E.left("buildDataParserGetDefinitionError", { + dataParser, + error: E.left("buildCheckerError", dataParser.definition.checkers[0]), + }), + ); + }); +}); diff --git a/tests/toDataParser/dataParserTransformer/errorHandler.test.ts b/tests/toDataParser/dataParserTransformer/errorHandler.test.ts new file mode 100644 index 0000000..37e013f --- /dev/null +++ b/tests/toDataParser/dataParserTransformer/errorHandler.test.ts @@ -0,0 +1,45 @@ +import { DP, DPE, E } from "@duplojs/utils"; +import { defaultTransformers, defaultCheckerTransformers, render } from "@scripts/toDataParser"; +import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; + +describe("errorHandler", () => { + it("renders inner parser", () => { + const inner = DPE.string(); + const schema = DPE.errorHandler( + inner, + DP.createErrorMessageTransformer(DP.stringKind, () => "Expected string."), + ); + const params = { + identifier: "errorHandlerParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }; + const result = render(schema, params); + + expect(result).toMatchSnapshot(); + expect(result).toBe(render(inner, params)); + }); + + it("fails when inner parser cannot be rendered", () => { + expect( + () => render( + DPE.errorHandler( + DPE.string(), + DP.createErrorMessageTransformer(DP.stringKind, () => "Expected string."), + ), + { + identifier: "errorHandlerParserError", + dataParserTransformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toDataParser/override.test.ts b/tests/toDataParser/override.test.ts index ce66e6b..37eb229 100644 --- a/tests/toDataParser/override.test.ts +++ b/tests/toDataParser/override.test.ts @@ -1,5 +1,5 @@ import "@scripts/toDataParser/override"; -import { asserts, DP, DPE, type ExpectType, forwardAsserts, justReturn } from "@duplojs/utils"; +import { type AnyTuple, asserts, DP, DPE, type ExpectType, forwardAsserts, justReturn } from "@duplojs/utils"; import { factory } from "typescript"; import { defaultCheckerTransformers, defaultTransformers, render } from "@scripts/toDataParser"; import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; @@ -47,6 +47,10 @@ describe("DP override", () => { ); expect(typeof schema.definition.overrideDataParserTransformer).toBe("function"); + + schema.setOverrideDataParserTransformer(null); + + expect(schema.definition.overrideDataParserTransformer).toBe(undefined); }); it("addOverrideDataParserTransformer", () => { @@ -105,6 +109,73 @@ describe("DP override", () => { expect(result).toContain("export const overrideDataParser = DP.number();"); }); + + it("uses static overrideDataParserTransformer in render", () => { + const schema = DPE.string().addOverrideDataParserTransformer( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier("DP"), + factory.createIdentifier("number"), + ), + undefined, + [], + ), + ); + + const result = render( + schema, + { + identifier: "override", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ); + + expect(result).toContain("export const overrideDataParser = DP.number();"); + }); + + it("sets checker override transformer", () => { + const checker = DP.checkerStringMin(1); + + checker.setOverrideCheckerTransformer(factory.createIdentifier("customChecker")); + + expect(typeof checker.definition.overrideCheckerTransformer).toBe("function"); + + checker.setOverrideCheckerTransformer(null); + + expect(checker.definition.overrideCheckerTransformer).toBe(undefined); + }); + + it("adds checker override transformer", () => { + const checker = DP.checkerStringMin(1); + const overrideTransformer: DataParserToDataParser.CheckerTransformerBuildFunction = ( + __, + { success }, + ) => success(factory.createIdentifier("customChecker")); + const newChecker = checker.addOverrideCheckerTransformer(overrideTransformer); + + expect(checker.definition.overrideCheckerTransformer).toBe(undefined); + expect(newChecker.definition.overrideCheckerTransformer).toBe(overrideTransformer); + }); + + it("uses static checker override transformer in render", () => { + const schema = DPE.string({ + checkers: [DP.checkerStringMin(1).addOverrideCheckerTransformer(factory.createIdentifier("customChecker"))], + }); + + const result = render( + schema, + { + identifier: "overrideChecker", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ); + + expect(result).toContain("checkers: [customChecker]"); + }); }); describe("DPE override", () => { @@ -209,6 +280,8 @@ describe("DPE override", () => { DataParserToJsonSchema.TransformerBuildFunction | undefined; readonly coerce: boolean; readonly checkers: readonly []; + readonly mapImportContextEntries?: + AnyTuple | undefined; }>, "strict" >; @@ -238,4 +311,30 @@ describe("DPE override", () => { expect(result).toContain("export const overrideDataParser = DP.number();"); }); + + it("uses map imports in render", () => { + const schema = DPE.string() + .addMapImportContextEntries([ + "module-a", + { + direct: ["directValue"], + default: ["defaultValue"], + namespace: ["NamespaceValue"], + }, + ]); + + const result = render( + schema, + { + identifier: "withImport", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ); + + expect(result).toContain("import * as NamespaceValue from \"module-a\";"); + expect(result).toContain("import defaultValue from \"module-a\";"); + expect(result).toContain("import { directValue } from \"module-a\";"); + }); }); diff --git a/tests/toDataParser/render.test.ts b/tests/toDataParser/render.test.ts index 297cc95..ec490ad 100644 --- a/tests/toDataParser/render.test.ts +++ b/tests/toDataParser/render.test.ts @@ -1,6 +1,16 @@ -import { DDate, DP, DPE } from "@duplojs/utils"; -import { defaultTransformers as tsDefaultTransformers } from "@scripts/toTypescript"; -import { defaultCheckerTransformers, defaultTransformers, render } from "@scripts/toDataParser"; +import { throws } from "node:assert/strict"; +import { DDate, DP, DPE, E } from "@duplojs/utils"; +import { + defaultCheckerRefiners, + defaultTransformers as tsDefaultTransformers, +} from "@scripts/toTypescript"; +import { + DataParserToDataParserRenderError, + DataParserToDataParserTypeRenderError, + defaultCheckerTransformers, + defaultTransformers, + render, +} from "@scripts/toDataParser"; describe("render", () => { it("renders anonymous recursive dataParser with a temporary const", () => { @@ -25,6 +35,29 @@ describe("render", () => { ).toMatchSnapshot(); }); + it("renders anonymous recursive dataParser in extended mode", () => { + interface RecursiveNode { + next: RecursiveNode; + } + + const schema: DPE.DataParser = DPE.object({ + next: DPE.lazy(() => schema), + }).contract(); + + expect( + render( + schema, + { + identifier: "recursiveExtendedNodeParser", + importMode: "extended", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + ).toMatchSnapshot(); + }); + it("renders named dependencies before their consumers", () => { const child = DPE.string().addIdentifier("childParser"); const schema = DPE.object({ child }); @@ -213,4 +246,171 @@ describe("render", () => { expect(identifier).toBe("UserParser"); expect(renderedIdentifier).toBe("userParserDataParser"); }); + + it("throws a render error when dataParser transformation fails", () => { + throws( + () => render( + DPE.string(), + { + identifier: "renderError", + dataParserTransformers: [(dataParser) => E.left("dataParserNotSupport", dataParser)], + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + }, + ), + (error) => { + expect(error).toBeInstanceOf(DataParserToDataParserRenderError); + expect( + E.hasInformation((error as DataParserToDataParserRenderError).error, "dataParserNotSupport"), + ).toBe(true); + + return true; + }, + ); + }); + + it("throws a type render error when recursive type transformation is not supported", () => { + interface RecursiveNode { + next: RecursiveNode; + } + + const schema: DPE.DataParser = DPE.object({ + next: DPE.lazy(() => schema), + }).contract(); + + throws( + () => render( + schema, + { + identifier: "recursiveNodeParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: [], + }, + ), + (error) => { + expect(error).toBeInstanceOf(DataParserToDataParserTypeRenderError); + expect( + E.hasInformation( + (error as DataParserToDataParserTypeRenderError).error, + "toTypescriptDataParserNotSupport", + ), + ).toBe(true); + + return true; + }, + ); + }); + + it("throws a type render error when recursive type transformation fails", () => { + interface RecursiveNode { + next: RecursiveNode; + } + + const schema: DPE.DataParser = DPE.object({ + next: DPE.lazy(() => schema), + }).contract(); + + throws( + () => render( + schema, + { + identifier: "recursiveNodeParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: [(__, { buildError }) => buildError()], + }, + ), + (error) => { + expect(error).toBeInstanceOf(DataParserToDataParserTypeRenderError); + expect( + E.hasInformation( + (error as DataParserToDataParserTypeRenderError).error, + "toTypescriptBuildDataParserError", + ), + ).toBe(true); + + return true; + }, + ); + }); + + it("throws a type render error when recursive checker refinement fails", () => { + interface RecursiveNode { + next: RecursiveNode; + } + + const schema: DPE.DataParser = DPE.object({ + next: DPE.lazy(() => schema), + }, { + checkers: [DP.checkerRefine(() => true)], + }).contract(); + + throws( + () => render( + schema, + { + identifier: "recursiveNodeParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + typescriptCheckerRefiner: [ + (__, ___, { buildError }) => buildError(), + ...defaultCheckerRefiners, + ], + }, + ), + (error) => { + expect(error).toBeInstanceOf(DataParserToDataParserTypeRenderError); + expect( + E.hasInformation( + (error as DataParserToDataParserTypeRenderError).error, + "toTypescriptBuildCheckerError", + ), + ).toBe(true); + + return true; + }, + ); + }); + + it("runs dataParser transformer hooks", () => { + const stopHook = vi.fn(({ output }) => output("stop", DPE.string())); + const shouldNotRun = vi.fn(({ output, dataParser }) => output("next", dataParser)); + + const result = render( + DPE.number(), + { + identifier: "hookStopParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + hooks: [stopHook, shouldNotRun], + }, + ); + + expect(stopHook).toHaveBeenCalledTimes(1); + expect(shouldNotRun).not.toHaveBeenCalled(); + expect(result).toContain("DP.string()"); + }); + + it("chains dataParser transformer hooks", () => { + const useLessHook = vi.fn(({ output, dataParser }) => output("next", dataParser)); + const replaceHook = vi.fn(({ output }) => output("stop", DPE.string())); + + const result = render( + DPE.number(), + { + identifier: "hookNextParser", + dataParserTransformers: defaultTransformers, + checkerTransformers: defaultCheckerTransformers, + typescriptTransformers: tsDefaultTransformers, + hooks: [useLessHook, replaceHook], + }, + ); + + expect(useLessHook).toHaveBeenCalledTimes(1); + expect(replaceHook).toHaveBeenCalledTimes(1); + expect(result).toContain("DP.string()"); + }); }); diff --git a/tests/toJsonSchema/__snapshots__/render.test.ts.snap b/tests/toJsonSchema/__snapshots__/render.test.ts.snap new file mode 100644 index 0000000..b7a7933 --- /dev/null +++ b/tests/toJsonSchema/__snapshots__/render.test.ts.snap @@ -0,0 +1,31 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`render > renders jsonSchema202012 1`] = ` +{ + "$defs": { + "StringSchema": { + "type": "string", + }, + }, + "$ref": "#/$defs/StringSchema", + "$schema": "jsonSchema202012", +} +`; + +exports[`render > reuses identified schema references 1`] = ` +{ + "properties": { + "other": { + "$ref": "#/definitions/ReferencedSchema", + }, + "value": { + "$ref": "#/definitions/ReferencedSchema", + }, + }, + "required": [ + "value", + "other", + ], + "type": "object", +} +`; diff --git a/tests/toJsonSchema/render.test.ts b/tests/toJsonSchema/render.test.ts new file mode 100644 index 0000000..2e2b667 --- /dev/null +++ b/tests/toJsonSchema/render.test.ts @@ -0,0 +1,163 @@ +import { throws } from "node:assert/strict"; +import { DP, DPE, E } from "@duplojs/utils"; +import { + DataParserToJsonSchemaRenderError, + defaultTransformers, + render, +} from "@scripts/toJsonSchema"; + +describe("render", () => { + it("renders jsonSchema202012", () => { + expect( + render( + DPE.string(), + { + identifier: "StringSchema", + transformers: defaultTransformers, + version: "jsonSchema202012", + }, + ), + ).toMatchSnapshot(); + }); + + it("throws when transformation is not supported", () => { + throws( + () => render( + DPE.string(), + { + identifier: "UnsupportedSchema", + transformers: [], + version: "jsonSchema7", + }, + ), + (error) => { + expect(error).toBeInstanceOf(DataParserToJsonSchemaRenderError); + expect( + E.hasInformation((error as DataParserToJsonSchemaRenderError).error, "dataParserNotSupport"), + ).toBe(true); + + return true; + }, + ); + }); + + it("throws when transformation fails", () => { + throws( + () => render( + DPE.string(), + { + identifier: "ErrorSchema", + transformers: [(schema) => E.left("buildDataParserError", schema)], + version: "jsonSchema7", + }, + ), + (error) => { + expect(error).toBeInstanceOf(DataParserToJsonSchemaRenderError); + expect( + E.hasInformation((error as DataParserToJsonSchemaRenderError).error, "buildDataParserError"), + ).toBe(true); + + return true; + }, + ); + }); + + it("runs transformer hooks", () => { + const stopHook = vi.fn(({ output }) => output("stop", DPE.string())); + const shouldNotRun = vi.fn(({ output, schema }) => output("next", schema)); + + const result = render( + DPE.number(), + { + identifier: "HookStopSchema", + transformers: defaultTransformers, + hooks: [stopHook, shouldNotRun], + version: "jsonSchema7", + }, + ); + + expect(stopHook).toHaveBeenCalledTimes(1); + expect(shouldNotRun).not.toHaveBeenCalled(); + expect(result.definitions.HookStopSchema).toStrictEqual({ type: "string" }); + }); + + it("chains transformer hooks", () => { + const useLessHook = vi.fn(({ output, schema }) => output("next", schema)); + const replaceHook = vi.fn(({ output }) => output("stop", DPE.string())); + + const result = render( + DPE.number(), + { + identifier: "HookNextSchema", + transformers: defaultTransformers, + hooks: [useLessHook, replaceHook], + version: "jsonSchema7", + }, + ); + + expect(useLessHook).toHaveBeenCalledTimes(1); + expect(replaceHook).toHaveBeenCalledTimes(1); + expect(result.definitions.HookNextSchema).toStrictEqual({ type: "string" }); + }); + + it("reuses identified schema references", () => { + const schema = DPE.object({ + value: DPE.string(), + }).addIdentifier("ReferencedSchema"); + + expect( + render( + DPE.object({ + value: schema, + other: schema, + }), + { + identifier: "ObjectSchema", + transformers: defaultTransformers, + version: "jsonSchema7", + }, + ).definitions.ObjectSchema, + ).toMatchSnapshot(); + }); + + it("keeps existing identifier definition", () => { + const schema = DP.string().addIdentifier("StringSchema"); + + expect( + render( + schema, + { + identifier: "StringSchema", + transformers: defaultTransformers, + version: "jsonSchema7", + }, + ).definitions.StringSchema, + ).toStrictEqual({ type: "string" }); + }); + + it("coverage: ignores context entries without schema", () => { + const context = new Map([ + [ + DPE.string(), + { + name: "NoSchema", + isOptional: false, + }, + ], + ]); + + expect( + render( + DPE.number(), + { + identifier: "NumberSchema", + transformers: defaultTransformers, + context, + version: "jsonSchema7", + }, + ).definitions, + ).toStrictEqual({ + NumberSchema: { type: "number" }, + }); + }); +}); diff --git a/tests/toJsonSchema/transformers/__snapshots__/errorHandler.test.ts.snap b/tests/toJsonSchema/transformers/__snapshots__/errorHandler.test.ts.snap new file mode 100644 index 0000000..e5b8a8e --- /dev/null +++ b/tests/toJsonSchema/transformers/__snapshots__/errorHandler.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`errorHandler > renders inner schema 1`] = ` +{ + "$ref": "#/definitions/ErrorHandlerSchema", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ErrorHandlerSchema": { + "type": "number", + }, + }, +} +`; diff --git a/tests/toJsonSchema/transformers/__snapshots__/templateLiteral.test.ts.snap b/tests/toJsonSchema/transformers/__snapshots__/templateLiteral.test.ts.snap index bb36841..9ef4e3b 100644 --- a/tests/toJsonSchema/transformers/__snapshots__/templateLiteral.test.ts.snap +++ b/tests/toJsonSchema/transformers/__snapshots__/templateLiteral.test.ts.snap @@ -6,7 +6,7 @@ exports[`templateLiteral > builds pattern 1`] = ` "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "TemplateSchema": { - "pattern": "^(?:id\\-)(?:-?[0-9]+(?:\\.[0-9]+)?)(?:\\-ok)$", + "pattern": "^(?:id\\-)(?:(?:[+-]?(?:(?:[0-9]+(?:\\.[0-9]*)?)|(?:\\.[0-9]+))(?:[eE][+-]?[0-9]+)?)|(?:0(?:[xX][0-9a-fA-F]+|[bB][01]+|[oO][0-7]+)))(?:\\-ok)$", "type": "string", }, }, diff --git a/tests/toJsonSchema/transformers/errorHandler.test.ts b/tests/toJsonSchema/transformers/errorHandler.test.ts new file mode 100644 index 0000000..1466e57 --- /dev/null +++ b/tests/toJsonSchema/transformers/errorHandler.test.ts @@ -0,0 +1,50 @@ +import { render, defaultTransformers } from "@scripts/toJsonSchema"; +import { errorHandlerTransformer } from "@scripts/toJsonSchema/transformer/defaults"; +import { DP, DPE, E } from "@duplojs/utils"; + +describe("errorHandler", () => { + it("renders inner schema", () => { + const inner = DPE.number(); + const schema = DPE.errorHandler( + inner, + DP.createErrorMessageTransformer(DP.numberKind, () => "Expected number."), + ); + const params = { + identifier: "ErrorHandlerSchema", + transformers: defaultTransformers, + mode: "out" as const, + version: "jsonSchema7" as const, + }; + const result = render(schema, params); + + expect(result).toMatchSnapshot(); + expect(result).toStrictEqual(render(inner, params)); + }); + + it("returns left when inner transform fails", () => { + const schema = DPE.errorHandler( + DPE.string(), + DP.createErrorMessageTransformer(DP.stringKind, () => "Expected string."), + ); + expect(errorHandlerTransformer( + schema, + { + mode: "out", + context: new Map(), + version: "jsonSchema7", + transformer: () => E.left("dataParserNotSupport", schema.definition.inner), + success(result, isOptional = false) { + return E.right("buildSuccess", { + schema: result, + isOptional, + }); + }, + buildError() { + return E.left("buildDataParserError", schema); + }, + }, + )).toStrictEqual( + E.left("dataParserNotSupport", schema.definition.inner), + ); + }); +}); diff --git a/tests/toTypescript/checkerRefiner/__snapshots__/arrayMax.test.ts.snap b/tests/toTypescript/checkerRefiner/__snapshots__/arrayMax.test.ts.snap new file mode 100644 index 0000000..a503823 --- /dev/null +++ b/tests/toTypescript/checkerRefiner/__snapshots__/arrayMax.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`array max > string array 1`] = ` +"import { MaxElements } from "@duplojs/utils/array"; + +export type ArrayString = string[] & MaxElements<10>;" +`; diff --git a/tests/toTypescript/checkerRefiner/__snapshots__/arrayMin.test.ts.snap b/tests/toTypescript/checkerRefiner/__snapshots__/arrayMin.test.ts.snap new file mode 100644 index 0000000..cbf8b93 --- /dev/null +++ b/tests/toTypescript/checkerRefiner/__snapshots__/arrayMin.test.ts.snap @@ -0,0 +1,10 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`array min > string array 1`] = ` +"export type ArrayString = string[] & [ + string, + string, + string, + ...string[] +];" +`; diff --git a/tests/toTypescript/checkerRefiner/__snapshots__/email.test.ts.snap b/tests/toTypescript/checkerRefiner/__snapshots__/email.test.ts.snap new file mode 100644 index 0000000..ff0afe7 --- /dev/null +++ b/tests/toTypescript/checkerRefiner/__snapshots__/email.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`string email > basic 1`] = `"export type Test = string & \`\${string}@\${string}.\${string}\`;"`; diff --git a/tests/toTypescript/checkerRefiner/arrayMax.test.ts b/tests/toTypescript/checkerRefiner/arrayMax.test.ts new file mode 100644 index 0000000..68539ed --- /dev/null +++ b/tests/toTypescript/checkerRefiner/arrayMax.test.ts @@ -0,0 +1,18 @@ +import { render, defaultTransformers, defaultCheckerRefiners } from "@scripts/toTypescript"; +import { DP, DPE } from "@duplojs/utils"; + +describe("array max", () => { + it("string array", () => { + expect( + render( + DPE.array(DPE.string()).addChecker(DP.checkerArrayMax(10)), + { + identifier: "ArrayString", + transformers: defaultTransformers, + checkerRefiner: defaultCheckerRefiners, + mode: "out", + }, + ), + ).toMatchSnapshot(); + }); +}); diff --git a/tests/toTypescript/checkerRefiner/arrayMin.test.ts b/tests/toTypescript/checkerRefiner/arrayMin.test.ts new file mode 100644 index 0000000..96bc8a1 --- /dev/null +++ b/tests/toTypescript/checkerRefiner/arrayMin.test.ts @@ -0,0 +1,32 @@ +import { render, defaultTransformers, defaultCheckerRefiners, DataParserToTypescriptRenderError } from "@scripts/toTypescript"; +import { DP, DPE } from "@duplojs/utils"; + +describe("array min", () => { + it("string array", () => { + expect( + render( + DPE.array(DPE.string()).addChecker(DP.checkerArrayMin(3)), + { + identifier: "ArrayString", + transformers: defaultTransformers, + checkerRefiner: defaultCheckerRefiners, + mode: "out", + }, + ), + ).toMatchSnapshot(); + }); + + it("current type node is not array", () => { + expect( + () => render( + DPE.string().addChecker(DP.checkerArrayMin(3) as never), + { + identifier: "ArrayString", + transformers: defaultTransformers, + checkerRefiner: defaultCheckerRefiners, + mode: "out", + }, + ), + ).toThrow(DataParserToTypescriptRenderError); + }); +}); diff --git a/tests/toTypescript/checkerRefiner/email.test.ts b/tests/toTypescript/checkerRefiner/email.test.ts new file mode 100644 index 0000000..31f4ad6 --- /dev/null +++ b/tests/toTypescript/checkerRefiner/email.test.ts @@ -0,0 +1,36 @@ +import { render, defaultTransformers, defaultCheckerRefiners } from "@scripts/toTypescript"; +import { DP, DPE, E } from "@duplojs/utils"; + +describe("string email", () => { + it("basic", () => { + const schema = DPE.email(); + + expect( + render( + schema, + { + identifier: "Test", + transformers: defaultTransformers, + checkerRefiner: defaultCheckerRefiners, + mode: "out", + }, + ), + ).toMatchSnapshot(); + }); + + it("keeps base type when checker refiner does not support checker", () => { + expect( + render( + DPE.string({ + checkers: [DP.checkerEmail()], + }), + { + identifier: "UnsupportedEmailRefiner", + transformers: defaultTransformers, + checkerRefiner: [(checker) => E.left("checkerNotSupport", checker)], + mode: "out", + }, + ), + ).toBe("export type UnsupportedEmailRefiner = string;"); + }); +}); diff --git a/tests/toTypescript/transfomers/__snapshots__/array.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/array.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/array.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/array.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/bigint.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/bigint.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/bigint.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/bigint.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/boolean.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/boolean.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/boolean.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/boolean.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/date.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/date.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/date.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/date.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/empty.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/empty.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/empty.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/empty.test.ts.snap diff --git a/tests/toTypescript/dataParserTransformer/__snapshots__/errorHandler.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/errorHandler.test.ts.snap new file mode 100644 index 0000000..5986b49 --- /dev/null +++ b/tests/toTypescript/dataParserTransformer/__snapshots__/errorHandler.test.ts.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`errorHandler > renders inner parser type in mode in and out 1`] = `"export type ErrorHandlerIn = string;"`; + +exports[`errorHandler > renders inner parser type in mode in and out 2`] = `"export type ErrorHandlerOut = string;"`; diff --git a/tests/toTypescript/transfomers/__snapshots__/file.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/file.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/file.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/file.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/lazy.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/lazy.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/lazy.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/lazy.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/literal.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/literal.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/literal.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/literal.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/nil.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/nil.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/nil.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/nil.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/nullable.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/nullable.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/nullable.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/nullable.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/number.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/number.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/number.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/number.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/object.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/object.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/object.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/object.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/optional.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/optional.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/optional.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/optional.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/pipe.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/pipe.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/pipe.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/pipe.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/record.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/record.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/record.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/record.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/recover.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/recover.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/recover.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/recover.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/string.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/string.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/string.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/string.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/templateLiteral.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/templateLiteral.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/templateLiteral.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/templateLiteral.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/time.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/time.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/time.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/time.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/transform.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/transform.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/transform.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/transform.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/tuple.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/tuple.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/tuple.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/tuple.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/union.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/union.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/union.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/union.test.ts.snap diff --git a/tests/toTypescript/transfomers/__snapshots__/unknown.test.ts.snap b/tests/toTypescript/dataParserTransformer/__snapshots__/unknown.test.ts.snap similarity index 100% rename from tests/toTypescript/transfomers/__snapshots__/unknown.test.ts.snap rename to tests/toTypescript/dataParserTransformer/__snapshots__/unknown.test.ts.snap diff --git a/tests/toTypescript/transfomers/array.test.ts b/tests/toTypescript/dataParserTransformer/array.test.ts similarity index 100% rename from tests/toTypescript/transfomers/array.test.ts rename to tests/toTypescript/dataParserTransformer/array.test.ts diff --git a/tests/toTypescript/transfomers/bigint.test.ts b/tests/toTypescript/dataParserTransformer/bigint.test.ts similarity index 100% rename from tests/toTypescript/transfomers/bigint.test.ts rename to tests/toTypescript/dataParserTransformer/bigint.test.ts diff --git a/tests/toTypescript/transfomers/boolean.test.ts b/tests/toTypescript/dataParserTransformer/boolean.test.ts similarity index 100% rename from tests/toTypescript/transfomers/boolean.test.ts rename to tests/toTypescript/dataParserTransformer/boolean.test.ts diff --git a/tests/toTypescript/transfomers/date.test.ts b/tests/toTypescript/dataParserTransformer/date.test.ts similarity index 100% rename from tests/toTypescript/transfomers/date.test.ts rename to tests/toTypescript/dataParserTransformer/date.test.ts diff --git a/tests/toTypescript/transfomers/empty.test.ts b/tests/toTypescript/dataParserTransformer/empty.test.ts similarity index 100% rename from tests/toTypescript/transfomers/empty.test.ts rename to tests/toTypescript/dataParserTransformer/empty.test.ts diff --git a/tests/toTypescript/dataParserTransformer/errorHandler.test.ts b/tests/toTypescript/dataParserTransformer/errorHandler.test.ts new file mode 100644 index 0000000..7416ee1 --- /dev/null +++ b/tests/toTypescript/dataParserTransformer/errorHandler.test.ts @@ -0,0 +1,50 @@ +import { render, defaultTransformers } from "@scripts/toTypescript"; +import { DP, DPE, E } from "@duplojs/utils"; + +describe("errorHandler", () => { + it("renders inner parser type in mode in and out", () => { + const inner = DPE.string(); + const schema = DPE.errorHandler( + inner, + DP.createErrorMessageTransformer(DP.stringKind, () => "Expected string."), + ); + const inputParams = { + identifier: "ErrorHandlerIn", + transformers: defaultTransformers, + mode: "in" as const, + }; + const outputParams = { + identifier: "ErrorHandlerOut", + transformers: defaultTransformers, + mode: "out" as const, + }; + const inputResult = render(schema, inputParams); + const outputResult = render(schema, outputParams); + + expect(inputResult).toMatchSnapshot(); + expect(inputResult).toBe(render(inner, inputParams)); + expect(outputResult).toMatchSnapshot(); + expect(outputResult).toBe(render(inner, outputParams)); + }); + + it("fails when inner parser cannot be rendered", () => { + expect( + () => render( + DPE.errorHandler( + DPE.string(), + DP.createErrorMessageTransformer(DP.stringKind, () => "Expected string."), + ), + { + identifier: "ErrorHandlerError", + transformers: [ + ((dataParser, { buildError }) => DP.stringKind.has(dataParser) + ? buildError() + : E.left("dataParserNotSupport", dataParser)), + ...defaultTransformers, + ], + mode: "out", + }, + ), + ).toThrow(); + }); +}); diff --git a/tests/toTypescript/transfomers/file.test.ts b/tests/toTypescript/dataParserTransformer/file.test.ts similarity index 100% rename from tests/toTypescript/transfomers/file.test.ts rename to tests/toTypescript/dataParserTransformer/file.test.ts diff --git a/tests/toTypescript/transfomers/lazy.test.ts b/tests/toTypescript/dataParserTransformer/lazy.test.ts similarity index 100% rename from tests/toTypescript/transfomers/lazy.test.ts rename to tests/toTypescript/dataParserTransformer/lazy.test.ts diff --git a/tests/toTypescript/transfomers/literal.test.ts b/tests/toTypescript/dataParserTransformer/literal.test.ts similarity index 100% rename from tests/toTypescript/transfomers/literal.test.ts rename to tests/toTypescript/dataParserTransformer/literal.test.ts diff --git a/tests/toTypescript/transfomers/nil.test.ts b/tests/toTypescript/dataParserTransformer/nil.test.ts similarity index 100% rename from tests/toTypescript/transfomers/nil.test.ts rename to tests/toTypescript/dataParserTransformer/nil.test.ts diff --git a/tests/toTypescript/transfomers/nullable.test.ts b/tests/toTypescript/dataParserTransformer/nullable.test.ts similarity index 100% rename from tests/toTypescript/transfomers/nullable.test.ts rename to tests/toTypescript/dataParserTransformer/nullable.test.ts diff --git a/tests/toTypescript/transfomers/number.test.ts b/tests/toTypescript/dataParserTransformer/number.test.ts similarity index 100% rename from tests/toTypescript/transfomers/number.test.ts rename to tests/toTypescript/dataParserTransformer/number.test.ts diff --git a/tests/toTypescript/transfomers/object.test.ts b/tests/toTypescript/dataParserTransformer/object.test.ts similarity index 100% rename from tests/toTypescript/transfomers/object.test.ts rename to tests/toTypescript/dataParserTransformer/object.test.ts diff --git a/tests/toTypescript/transfomers/optional.test.ts b/tests/toTypescript/dataParserTransformer/optional.test.ts similarity index 100% rename from tests/toTypescript/transfomers/optional.test.ts rename to tests/toTypescript/dataParserTransformer/optional.test.ts diff --git a/tests/toTypescript/transfomers/pipe.test.ts b/tests/toTypescript/dataParserTransformer/pipe.test.ts similarity index 100% rename from tests/toTypescript/transfomers/pipe.test.ts rename to tests/toTypescript/dataParserTransformer/pipe.test.ts diff --git a/tests/toTypescript/transfomers/record.test.ts b/tests/toTypescript/dataParserTransformer/record.test.ts similarity index 100% rename from tests/toTypescript/transfomers/record.test.ts rename to tests/toTypescript/dataParserTransformer/record.test.ts diff --git a/tests/toTypescript/transfomers/recover.test.ts b/tests/toTypescript/dataParserTransformer/recover.test.ts similarity index 100% rename from tests/toTypescript/transfomers/recover.test.ts rename to tests/toTypescript/dataParserTransformer/recover.test.ts diff --git a/tests/toTypescript/transfomers/string.test.ts b/tests/toTypescript/dataParserTransformer/string.test.ts similarity index 100% rename from tests/toTypescript/transfomers/string.test.ts rename to tests/toTypescript/dataParserTransformer/string.test.ts diff --git a/tests/toTypescript/transfomers/templateLiteral.test.ts b/tests/toTypescript/dataParserTransformer/templateLiteral.test.ts similarity index 100% rename from tests/toTypescript/transfomers/templateLiteral.test.ts rename to tests/toTypescript/dataParserTransformer/templateLiteral.test.ts diff --git a/tests/toTypescript/transfomers/time.test.ts b/tests/toTypescript/dataParserTransformer/time.test.ts similarity index 100% rename from tests/toTypescript/transfomers/time.test.ts rename to tests/toTypescript/dataParserTransformer/time.test.ts diff --git a/tests/toTypescript/transfomers/transform.test.ts b/tests/toTypescript/dataParserTransformer/transform.test.ts similarity index 100% rename from tests/toTypescript/transfomers/transform.test.ts rename to tests/toTypescript/dataParserTransformer/transform.test.ts diff --git a/tests/toTypescript/transfomers/tuple.test.ts b/tests/toTypescript/dataParserTransformer/tuple.test.ts similarity index 100% rename from tests/toTypescript/transfomers/tuple.test.ts rename to tests/toTypescript/dataParserTransformer/tuple.test.ts diff --git a/tests/toTypescript/transfomers/union.test.ts b/tests/toTypescript/dataParserTransformer/union.test.ts similarity index 100% rename from tests/toTypescript/transfomers/union.test.ts rename to tests/toTypescript/dataParserTransformer/union.test.ts diff --git a/tests/toTypescript/transfomers/unknown.test.ts b/tests/toTypescript/dataParserTransformer/unknown.test.ts similarity index 100% rename from tests/toTypescript/transfomers/unknown.test.ts rename to tests/toTypescript/dataParserTransformer/unknown.test.ts diff --git a/tests/toTypescript/override.test.ts b/tests/toTypescript/override.test.ts index 5e63aed..6d6fcdb 100644 --- a/tests/toTypescript/override.test.ts +++ b/tests/toTypescript/override.test.ts @@ -1,7 +1,9 @@ import "@scripts/toTypescript/override"; import { asserts, DP, DPE, forwardAsserts, justReturn } from "@duplojs/utils"; -import { factory } from "typescript"; -import { type TransformerBuildFunction } from "@scripts/toTypescript/transformer"; +import { factory, SyntaxKind } from "typescript"; +import { type TransformerBuildFunction } from "@scripts/toTypescript/dataParserTransformer"; +import { defaultCheckerRefiners, defaultTransformers, render } from "@scripts/toTypescript"; +import { type CheckerRefinerBuildFunction } from "@scripts/toTypescript/checkerRefiner"; describe("DP override", () => { it("setIdentifier", () => { @@ -39,6 +41,10 @@ describe("DP override", () => { schema.setOverrideTypescriptTransformer(factory.createTypeReferenceNode("test")); expect(typeof schema.definition.overrideTypescriptTransformer).toBe("function"); + + schema.setOverrideTypescriptTransformer(null); + + expect(schema.definition.overrideTypescriptTransformer).toBe(undefined); }); it("addOverrideTypeNode", () => { @@ -60,6 +66,148 @@ describe("DP override", () => { expect(newSchemaWithoutIdentifier.definition.overrideTypescriptTransformer).toBe(overrideTransformer); }); + + it("setMapImportContextEntries", () => { + const schema = DP.string(); + + schema.setMapImportContextEntries( + [ + "module-a", + { + direct: ["directValue"], + default: ["defaultValue"], + namespace: ["NamespaceValue"], + }, + ], + [ + "module-b", + { + direct: undefined, + default: undefined, + namespace: undefined, + }, + ], + ); + + expect(schema.definition.mapImportContextEntries).toEqual([ + [ + "module-a", + { + direct: ["directValue"], + default: ["defaultValue"], + namespace: ["NamespaceValue"], + }, + ], + [ + "module-b", + { + direct: undefined, + default: undefined, + namespace: undefined, + }, + ], + ]); + }); + + it("addMapImportContextEntries", () => { + const schema = DP.string(); + const newSchema = schema.addMapImportContextEntries( + ["module-a", { direct: ["directValue"] }], + ); + + expect(schema.definition.mapImportContextEntries).toBe(undefined); + expect(newSchema.definition.mapImportContextEntries).toEqual([["module-a", { direct: ["directValue"] }]]); + }); + + it("uses static override and map imports in render", () => { + const schema = DP.string() + .addOverrideTypescriptTransformer(factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword)) + .addMapImportContextEntries( + [ + "module-a", + { + direct: ["directValue"], + default: ["defaultValue"], + namespace: ["NamespaceValue"], + }, + ], + [ + "module-b", + { + direct: undefined, + default: undefined, + namespace: undefined, + }, + ], + ); + + const result = render( + schema, + { + identifier: "OverrideWithImports", + transformers: defaultTransformers, + mode: "out", + }, + ); + + expect(result).toContain("import * as NamespaceValue from \"module-a\";"); + expect(result).toContain("import defaultValue from \"module-a\";"); + expect(result).toContain("import { directValue } from \"module-a\";"); + expect(result).toContain("export type OverrideWithImports = boolean;"); + }); + + it("sets checker override refiner and map imports", () => { + const checker = DP.checkerStringMin(1); + + checker.setOverrideTypescriptRefiner(factory.createTypeReferenceNode("StaticRefinement")); + checker.setMapImportContextEntries(["module-c", { direct: ["CheckerImport"] }]); + + expect(typeof checker.definition.overrideTypescriptRefiner).toBe("function"); + expect(checker.definition.mapImportContextEntries).toEqual([["module-c", { direct: ["CheckerImport"] }]]); + + checker.setOverrideTypescriptRefiner(null); + + expect(checker.definition.overrideTypescriptRefiner).toBe(undefined); + }); + + it("adds checker override refiner and map imports", () => { + const checker = DP.checkerStringMin(1); + const overrideRefiner: CheckerRefinerBuildFunction = ( + __, + ___, + { success }, + ) => success(factory.createTypeReferenceNode("AddedRefinement")); + const newChecker = checker + .addOverrideTypescriptRefiner(overrideRefiner) + .addMapImportContextEntries(["module-c", { direct: ["CheckerImport"] }]); + + expect(checker.definition.overrideTypescriptRefiner).toBe(undefined); + expect(checker.definition.mapImportContextEntries).toBe(undefined); + expect(newChecker.definition.overrideTypescriptRefiner).toBe(overrideRefiner); + expect(newChecker.definition.mapImportContextEntries).toEqual([["module-c", { direct: ["CheckerImport"] }]]); + }); + + it("uses checker override refiner in render", () => { + const schema = DP.string({ + checkers: [ + DP.checkerStringMin(1) + .addOverrideTypescriptRefiner(factory.createTypeReferenceNode("StaticRefinement")) + .addMapImportContextEntries(["module-c", { direct: ["CheckerImport"] }]), + ], + }); + + expect( + render( + schema, + { + identifier: "CheckerOverride", + transformers: defaultTransformers, + checkerRefiner: defaultCheckerRefiners, + mode: "out", + }, + ), + ).toContain("StaticRefinement"); + }); }); describe("DPE override", () => { diff --git a/tests/toTypescript/render.test.ts b/tests/toTypescript/render.test.ts index 55d3682..8614072 100644 --- a/tests/toTypescript/render.test.ts +++ b/tests/toTypescript/render.test.ts @@ -1,5 +1,5 @@ -import { DPE } from "@duplojs/utils"; -import { defaultTransformers, render } from "@scripts/toTypescript"; +import { DPE, E } from "@duplojs/utils"; +import { buildContext, defaultTransformers, render } from "@scripts/toTypescript"; describe("render", () => { it("normalizes the render identifier into a type identifier", () => { @@ -17,4 +17,57 @@ describe("render", () => { expect(identifier).toBe("userType"); expect(renderedIdentifier).toBe("UserType"); }); + + it("coverage: uses provided contexts", () => { + expect( + render( + DPE.string(), + { + identifier: "WithContexts", + transformers: defaultTransformers, + context: new Map(), + importContext: new Map(), + }, + ), + ).toBe("export type WithContexts = string;"); + }); + + it("coverage: uses provided context with default import context", () => { + expect( + render( + DPE.string(), + { + identifier: "WithContextOnly", + transformers: defaultTransformers, + context: new Map(), + }, + ), + ).toBe("export type WithContextOnly = string;"); + }); + + it("coverage: uses default context with provided import context", () => { + expect( + render( + DPE.string(), + { + identifier: "WithImportContextOnly", + transformers: defaultTransformers, + importContext: new Map(), + }, + ), + ).toBe("export type WithImportContextOnly = string;"); + }); + + it("coverage: builds context with default maps", () => { + expect(E.isRight( + buildContext( + DPE.string(), + { + identifier: "DefaultMaps", + transformers: defaultTransformers, + mode: "out", + }, + ), + )).toBe(true); + }); }); diff --git a/tests/utils/getRecursiveDataParser.test.ts b/tests/utils/getRecursiveDataParser.test.ts new file mode 100644 index 0000000..99137ca --- /dev/null +++ b/tests/utils/getRecursiveDataParser.test.ts @@ -0,0 +1,10 @@ +import { DPE } from "@duplojs/utils"; +import { getRecursiveDataParser } from "@scripts/utils"; + +describe("getRecursiveDataParser", () => { + it("detects self recursive lazy parser", () => { + const schema: DPE.DataParser = DPE.lazy(() => schema); + + expect(getRecursiveDataParser(schema)).toEqual([schema]); + }); +}); diff --git a/vitest.config.js b/vitest.config.js index 3f14171..edeedbc 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -15,10 +15,7 @@ export default defineConfig({ reporter: ["text", "json", "html", "json-summary"], reportsDirectory: "coverage", include: [ - "scripts/toTypescript/transformer/defaults", - "scripts/toJsonSchema/transformer/defaults", - "scripts/toDataParser/checkerTransformer/defaults", - "scripts/toDataParser/dataParserTransformer/defaults" + "scripts" ], exclude: [ "**/*.test.ts",