22
33Zod + Generate = Zen
44
5- Converts Go structs with go-validator validations to Zod schemas.
5+ Converts Go structs with [ go-validator] ( https://github.com/go-playground/validator ) validations to Zod schemas.
66
77Zen supports self-referential types and generic types. Other cyclic types (apart from self referential types) are not supported
88as they are not supported by zod itself.
@@ -34,7 +34,7 @@ type Tree struct {
3434fmt.Print (zen.StructToZodSchema (Tree{}))
3535
3636// We can also use create a converter and convert multiple types together
37- c := zen.NewConverter ( nil )
37+ c := zen.NewConverterWithOpts ( )
3838
3939// Generic types are also supported
4040type GenericPair [T any, U any] struct {
@@ -114,7 +114,7 @@ export type PairMapStringIntBool = z.infer<typeof PairMapStringIntBoolSchema>
114114- Then using go templates and passing these struct names as input, we generate go code that is later used to generate the zod schemas.
115115
116116` ` ` go .tmpl
117- converter : = zen .NewConverter (make (map [string ]zen .CustomFn ))
117+ converter : = zen .NewConverterWithOpts (make (map [string ]zen .CustomFn ))
118118
119119 {{range .TypesToGenerate }}
120120 converter .AddType (types .{{.}}{})
@@ -123,42 +123,6 @@ export type PairMapStringIntBool = z.infer<typeof PairMapStringIntBoolSchema>
123123 schema := converter .Export ()
124124```
125125
126- ## Custom Types
127-
128- We can pass type name mappings to custom conversion functions:
129-
130- ``` go
131- c := zen.NewConverter (map [string ]zen.CustomFn {
132- " github.com/shopspring/decimal.Decimal" : func (c *zen.Converter , t reflect.Type , v string , i int ) string {
133- // Shopspring's decimal type serialises to a string.
134- return " z.string()"
135- },
136- })
137-
138- c.Convert (User{
139- Money decimal.Decimal
140- })
141- ```
142-
143- Outputs:
144-
145- ``` typescript
146- export const UserSchema = z .object ({
147- Money: z .string (),
148- })
149- export type User = z .infer <typeof UserSchema >
150- ` ` `
151-
152- There are some custom types with tests in the "custom" directory.
153-
154- The function signature for custom type handlers is:
155-
156- ` ` ` go
157- func (c *Converter , t reflect .Type , validate string , indent int ) string
158- ` ` `
159-
160- We can use ` c ` to process nested types. Indent level is for passing to other converter APIs.
161-
162126## Supported validations
163127
164128### Network
@@ -248,6 +212,120 @@ We can use `c` to process nested types. Indent level is for passing to other con
248212
249213- required checks that the value is not default, but we are not implementing this check for numbers and booleans
250214
215+ ## Custom Tags
216+
217+ In addition to the [ go-validator] ( https://github.com/go-playground/validator ) tags supported out of the box, custom tags can also be implemented.
218+
219+ ``` go
220+ type SortParams struct {
221+ Order *string ` json:"order,omitempty" validate:"omitempty,oneof=asc desc"`
222+ Field *string ` json:"field,omitempty"`
223+ }
224+
225+ type Request struct {
226+ SortParams ` validate:"sortFields=title address age dob"`
227+ PaginationParams struct {
228+ Start *int ` json:"start,omitempty" validate:"omitempty,gt=0"`
229+ End *int ` json:"end,omitempty" validate:"omitempty,gt=0"`
230+ } ` validate:"pageParams"`
231+ Search *string ` json:"search,omitempty" validate:"identifier"`
232+ }
233+
234+ customTagHandlers := map [string ]zen.CustomFn {
235+ " identifier" : func (c *zen.Converter , t reflect.Type , validate string , indent int ) string {
236+ return " .refine((val) => !val || /^[a-z0-9_]*$/.test(val), 'Invalid search identifier')"
237+ },
238+ " pageParams" : func (c *zen.Converter , t reflect.Type , validate string , indent int ) string {
239+ return " .refine((val) => !val.start || !val.end || val.start < val.end, 'Start should be less than end')"
240+ },
241+ " sortFields" : func (c *zen.Converter , t reflect.Type , validate string , indent int ) string {
242+ sortFields := strings.Split (validate, " " )
243+ for i := range sortFields {
244+ sortFields[i] = fmt.Sprintf (" '%s '" , sortFields[i])
245+ }
246+ return fmt.Sprintf (" .extend({field: z.enum([%s ])})" , strings.Join (sortFields, " , " ))
247+ },
248+ }
249+ opt := zen.WithCustomTags (customTagHandlers)
250+ c := zen.NewConverterWithOpts (opt)
251+
252+ c.Convert (Request{})
253+ ```
254+
255+ Outputs:
256+
257+ ``` ts
258+ export const SortParamsSchema = z .object ({
259+ order: z .enum ([" asc" , " desc" ] as const ).optional (),
260+ field: z .string ().optional (),
261+ })
262+ export type SortParams = z .infer <typeof SortParamsSchema >
263+
264+ export const RequestSchema = z .object ({
265+ PaginationParams: z .object ({
266+ start: z .number ().gt (0 ).optional (),
267+ end: z .number ().gt (0 ).optional (),
268+ }).refine ((val ) => ! val .start || ! val .end || val .start < val .end , ' Start should be less than end' ),
269+ search: z .string ().refine ((val ) => ! val || / ^ [a-z0-9 _] * $ / .test (val ), ' Invalid search identifier' ).optional (),
270+ }).merge (SortParamsSchema .extend ({field: z .enum ([' title' , ' address' , ' age' , ' dob' ])}))
271+ export type Request = z .infer <typeof RequestSchema >
272+ ` ` `
273+
274+ The function signature for custom type handlers is:
275+
276+ ` ` ` go
277+ func (c *Converter , t reflect .Type , validate string , indent int ) string
278+ ` ` `
279+
280+ We can use ` c ` to process nested types. Indent level is for passing to other converter APIs.
281+
282+ ## Ignored Tags
283+
284+ To ensure safety, ` zen ` will panic if it encounters unknown validation tags. If these tags are intentional, they should be explicitly ignored.
285+
286+ ` ` ` go
287+ opt : = zen .WithIgnoreTags (" identifier" )
288+ c : = zen .NewConverterWithOpts (opt )
289+ ` ` `
290+
291+ ## Custom Types
292+
293+ We can pass type name mappings to custom conversion functions:
294+
295+ ` ` ` go
296+ customTypeHandlers : = map [string ]zen .CustomFn {
297+ " github.com/shopspring/decimal.Decimal" : func (c *zen .Converter , t reflect .Type , v string , indent int ) string {
298+ // Shopspring's decimal type serialises to a string.
299+ return " z.string()"
300+ },
301+ }
302+ opt := zen .WithCustomTypes (customTypeHandlers )
303+ c := zen .NewConverterWithOpts (opt )
304+
305+ c .Convert (User {
306+ Money decimal.Decimal
307+ })
308+ ```
309+
310+ Outputs:
311+
312+ ``` typescript
313+ export const UserSchema = z .object ({
314+ Money: z .string (),
315+ })
316+ export type User = z .infer <typeof UserSchema >
317+ ` ` `
318+
319+ There are some custom types with tests in the [custom](./custom) directory.
320+
321+ The function signature for custom type handlers is:
322+
323+ ` ` ` go
324+ func (c *Converter , t reflect .Type , validate string , indent int ) string
325+ ` ` `
326+
327+ We can use ` c ` to process nested types. Indent level is for passing to other converter APIs.
328+
251329## Caveats
252330
253331- Does not support cyclic types - it's a limitation of zod, but self-referential types are supported.
0 commit comments