diff --git a/CHANGELOG.md b/CHANGELOG.md index 4101aa6..782f063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Move to xUnit v3 (#162) +- Generate Zod v4 schemas (#168) ## [0.17.4.1] - 2025-09-19 diff --git a/TypeContractor.Tests/TypeScript/TypeScriptWriterTests.cs b/TypeContractor.Tests/TypeScript/TypeScriptWriterTests.cs index 8ba5c22..3109ba4 100644 --- a/TypeContractor.Tests/TypeScript/TypeScriptWriterTests.cs +++ b/TypeContractor.Tests/TypeScript/TypeScriptWriterTests.cs @@ -298,7 +298,7 @@ public void Writes_Zod_Schema_With_Enum_Reference() .And.Contain("import { z } from 'zod';") .And.Contain("import { ObsoleteEnum } from './ObsoleteEnum';") .And.Contain("export const TypeWithEnumSchema = z.object({") - .And.Contain(" status: z.nativeEnum(ObsoleteEnum),") + .And.Contain(" status: z.enum(ObsoleteEnum),") .And.Contain("});"); } @@ -318,7 +318,7 @@ public void Writes_Zod_Schema_With_Nullable_Enum() .And.Contain("import { z } from 'zod';") .And.Contain("import { ObsoleteEnum } from './ObsoleteEnum';") .And.Contain("export const TypeWithNullableEnumSchema = z.object({") - .And.Contain(" status: z.nativeEnum(ObsoleteEnum).nullable(),") + .And.Contain(" status: z.enum(ObsoleteEnum).nullable(),") .And.Contain("});"); } diff --git a/TypeContractor.sln b/TypeContractor.sln index 14878d3..c40aa6a 100644 --- a/TypeContractor.sln +++ b/TypeContractor.sln @@ -25,6 +25,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TypeContractor.Tool", "Type EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeContractor.Annotations", "TypeContractor.Annotations\TypeContractor.Annotations.csproj", "{8A6455DF-CE2F-44F7-B1BB-2C1640E1AEC3}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + tools\response.d.ts = tools\response.d.ts + tools\response.ts = tools\response.ts + tools\zod-errors.ts = tools\zod-errors.ts + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,6 +66,9 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {70D3E515-AB31-4EDB-886A-23ECD377E3AE} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BE873438-37D1-44EF-98BD-2A263351F7F2} EndGlobalSection diff --git a/TypeContractor/TypeScript/ZodSchemaWriter.cs b/TypeContractor/TypeScript/ZodSchemaWriter.cs index 83d9779..ad03fc1 100644 --- a/TypeContractor/TypeScript/ZodSchemaWriter.cs +++ b/TypeContractor/TypeScript/ZodSchemaWriter.cs @@ -69,12 +69,12 @@ private static string GetImportType(Type? innerSourceType, Type sourceType) private static string? GetZodOutputType(OutputProperty property, IEnumerable allTypes) { if (!property.IsBuiltin && property.SourceType.IsEnum) - return $"z.nativeEnum({property.SourceType.Name})"; + return $"z.enum({property.SourceType.Name})"; else if (!property.IsBuiltin && property.IsNullable && property.SourceType.IsGenericType) { var sourceType = TypeChecks.GetGenericType(property.SourceType); if (sourceType.IsEnum) - return $"z.nativeEnum({sourceType.Name}).nullable()"; + return $"z.enum({sourceType.Name}).nullable()"; } string? output; @@ -124,17 +124,17 @@ private static string GetImportType(Type? innerSourceType, Type sourceType) if (IsOfType(sourceType, typeof(string))) output = "z.string()"; else if (IsOfType(sourceType, typeof(Guid))) - output = "z.string().uuid()"; + output = "z.guid()"; else if (IsOfType(sourceType, typeof(int), typeof(long), typeof(float), typeof(double), typeof(short), typeof(byte), typeof(decimal))) output = "z.number()"; else if (IsOfType(sourceType, typeof(bool))) output = "z.boolean()"; else if (IsOfType(sourceType, typeof(DateTime), typeof(DateTimeOffset))) - output = "z.string().datetime({ offset: true })"; + output = "z.iso.datetime({ offset: true })"; else if (IsOfType(sourceType, typeof(DateOnly))) - output = "z.string().date()"; + output = "z.iso.date()"; else if (IsOfType(sourceType, typeof(TimeOnly))) - output = "z.string().time()"; + output = "z.iso.time()"; else if (IsOfType(sourceType, typeof(TimeSpan))) output = "z.string()"; // FIXME: Can assume some formatting here else if (TypeChecks.ImplementsIEnumerable(sourceType)) diff --git a/tools/zod-errors.ts b/tools/zod-errors.ts index bc5731d..9321264 100644 --- a/tools/zod-errors.ts +++ b/tools/zod-errors.ts @@ -3,7 +3,7 @@ const leftPad = (stringToPad: string, number: number = 1): string => { }; export type ErrorRoot = { - _errors: string[]; + errors: string[]; }; export type ErrorInstance = ErrorRoot & { [key: string]: ErrorInstance; @@ -15,13 +15,13 @@ export function formatErrors(input: ErrorRoot): any { const activeBreadcumb: string[] = []; function formatErrorsRecursive(input: any, activeBreadcumb: string[]) { - if (input['_errors'] && input['_errors'].length > 0) { - const errorsString = `- ${input['_errors'].join('\n')}\n`; + if (input['errors'] && input['errors'].length > 0) { + const errorsString = `- ${input['errors'].join('\n')}\n`; result += leftPad(errorsString, activeBreadcumb.length); } for (const key in input) { - if (key === '_errors') continue; + if (key === 'errors') continue; result += `${leftPad(key, activeBreadcumb.length)}:\n`;