@@ -45,31 +45,30 @@ export class ZodSchemaVisitor extends BaseSchemaVisitor {
4545
4646 initialEmit ( ) : string {
4747 return (
48- `\n${
49- [
50- new DeclarationBlock ( { } )
51- . asKind ( 'type' )
52- . withName ( 'Properties<T>' )
53- . withContent ( [ 'Required<{' , ' [K in keyof T]: z.ZodType<T[K], any, T[K]>;' , '}>' ] . join ( '\n' ) )
54- . string ,
55- // Unfortunately, zod doesn’t provide non-null defined any schema.
56- // This is a temporary hack until it is fixed.
57- // see: https://github.com/colinhacks/zod/issues/884
58- new DeclarationBlock ( { } ) . asKind ( 'type' ) . withName ( 'definedNonNullAny' ) . withContent ( '{}' ) . string ,
59- new DeclarationBlock ( { } )
60- . export ( )
61- . asKind ( 'const' )
62- . withName ( `isDefinedNonNullAny` )
63- . withContent ( `(v: any): v is definedNonNullAny => v !== undefined && v !== null` )
64- . string ,
65- new DeclarationBlock ( { } )
66- . export ( )
67- . asKind ( 'const' )
68- . withName ( `${ anySchema } ` )
69- . withContent ( `z.any().refine((v) => isDefinedNonNullAny(v))` )
70- . string ,
71- ...this . enumDeclarations ,
72- ] . join ( '\n' ) } `
48+ `\n${ [
49+ new DeclarationBlock ( { } )
50+ . asKind ( 'type' )
51+ . withName ( 'Properties<T>' )
52+ . withContent ( [ 'Required<{' , ' [K in keyof T]: z.ZodType<T[K], any, T[K]>;' , '}>' ] . join ( '\n' ) )
53+ . string ,
54+ // Unfortunately, zod doesn’t provide non-null defined any schema.
55+ // This is a temporary hack until it is fixed.
56+ // see: https://github.com/colinhacks/zod/issues/884
57+ new DeclarationBlock ( { } ) . asKind ( 'type' ) . withName ( 'definedNonNullAny' ) . withContent ( '{}' ) . string ,
58+ new DeclarationBlock ( { } )
59+ . export ( )
60+ . asKind ( 'const' )
61+ . withName ( `isDefinedNonNullAny` )
62+ . withContent ( `(v: any): v is definedNonNullAny => v !== undefined && v !== null` )
63+ . string ,
64+ new DeclarationBlock ( { } )
65+ . export ( )
66+ . asKind ( 'const' )
67+ . withName ( `${ anySchema } ` )
68+ . withContent ( `z.any().refine((v) => isDefinedNonNullAny(v))` )
69+ . string ,
70+ ...this . enumDeclarations ,
71+ ] . join ( '\n' ) } `
7372 ) ;
7473 }
7574
@@ -79,6 +78,13 @@ export class ZodSchemaVisitor extends BaseSchemaVisitor {
7978 const visitor = this . createVisitor ( 'input' ) ;
8079 const name = visitor . convertName ( node . name . value ) ;
8180 this . importTypes . push ( name ) ;
81+
82+ const hasOneOf = node . directives ?. some ( directive => directive ?. name . value === 'oneOf' ) ;
83+ if ( hasOneOf ) {
84+ const result = this . buildOneOfInputFields ( node . fields ?? [ ] , visitor , name )
85+ return result ;
86+ }
87+
8288 return this . buildInputFields ( node . fields ?? [ ] , visitor , name ) ;
8389 } ,
8490 } ;
@@ -276,13 +282,89 @@ export class ZodSchemaVisitor extends BaseSchemaVisitor {
276282 . string ;
277283 }
278284 }
285+
286+ protected buildOneOfInputFields (
287+ fields : readonly ( FieldDefinitionNode | InputValueDefinitionNode ) [ ] ,
288+ visitor : Visitor ,
289+ name : string ,
290+ ) {
291+ // FIXME: If I end up adding an explicit z.ZodUnion return type, make sure to handle schemaNamespacedImportName
292+ // Also type prefix and suffix
293+
294+ const typeName = visitor . prefixTypeNamespace ( name ) ;
295+ const union = generateFieldUnionZodSchema ( this . config , visitor , fields , 2 )
296+
297+ switch ( this . config . validationSchemaExportType ) {
298+ case 'const' :
299+ return new DeclarationBlock ( { } )
300+ . export ( )
301+ . asKind ( 'const' )
302+ . withName ( `${ typeName } Schema` )
303+ . withContent ( [ 'z.union([' , union , '])' ] . join ( '\n' ) )
304+ . string ;
305+
306+ case 'function' :
307+ default :
308+ return new DeclarationBlock ( { } )
309+ . export ( )
310+ . asKind ( 'function' )
311+ . withName ( `${ name } Schema(): z.ZodUnion<z.ZodUnionOptions>` )
312+ . withBlock ( [ indent ( `return z.union([` ) , `${ union } ` , indent ( '])' ) ] . join ( '\n' ) )
313+ . string ;
314+ }
315+ }
279316}
280317
281318function generateFieldZodSchema ( config : ValidationSchemaPluginConfig , visitor : Visitor , field : InputValueDefinitionNode | FieldDefinitionNode , indentCount : number ) : string {
282319 const gen = generateFieldTypeZodSchema ( config , visitor , field , field . type ) ;
283320 return indent ( `${ field . name . value } : ${ maybeLazy ( visitor , field . type , gen ) } ` , indentCount ) ;
284321}
285322
323+ function generateFieldUnionZodSchema ( config : ValidationSchemaPluginConfig , visitor : Visitor , fields : readonly ( FieldDefinitionNode | InputValueDefinitionNode ) [ ] , indentCount : number ) : string {
324+ const union = fields . map ( ( field ) => {
325+ const objectFields = fields . map ( ( nestedObjectField ) => {
326+ const isSameField = field . name === nestedObjectField . name ;
327+
328+ if ( ! isSameField ) {
329+ return indent ( `${ nestedObjectField . name . value } : z.never()` , indentCount + 1 ) ;
330+ }
331+
332+ if ( isListType ( nestedObjectField . type ) ) {
333+ const gen = generateFieldTypeZodSchema ( config , visitor , nestedObjectField , nestedObjectField . type . type , nestedObjectField . type ) ;
334+
335+ const maybeLazyGen = `z.array(${ maybeLazy ( visitor , nestedObjectField . type . type , gen ) } )` ;
336+ const appliedDirectivesGen = applyDirectives ( config , nestedObjectField , maybeLazyGen ) ;
337+
338+ return indent ( `${ nestedObjectField . name . value } : ${ appliedDirectivesGen } ` , 3 ) ;
339+ }
340+
341+ if ( isNonNullType ( nestedObjectField . type ) ) {
342+ const gen = generateFieldTypeZodSchema ( config , visitor , field , nestedObjectField . type . type , nestedObjectField . type ) ;
343+
344+ return indent ( `${ nestedObjectField . name . value } : ${ maybeLazy ( visitor , nestedObjectField . type . type , gen ) } ` , indentCount + 1 ) ;
345+ }
346+
347+ if ( isNamedType ( nestedObjectField . type ) ) {
348+ const gen = generateNameNodeZodSchema ( config , visitor , nestedObjectField . type . name ) ;
349+
350+ const appliedDirectivesGen = applyDirectives ( config , nestedObjectField , gen ) ;
351+
352+ if ( visitor . shouldEmitAsNotAllowEmptyString ( nestedObjectField . type . name . value ) )
353+ return indent ( `${ nestedObjectField . name . value } : ${ maybeLazy ( visitor , nestedObjectField . type , appliedDirectivesGen ) } .min(1)` , indentCount + 1 ) ;
354+
355+ return indent ( `${ nestedObjectField . name . value } : ${ maybeLazy ( visitor , nestedObjectField . type , appliedDirectivesGen ) } ` , indentCount + 1 ) ;
356+ }
357+
358+ console . warn ( 'unhandled type:' , field . type ) ;
359+ return '' ;
360+ } ) ;
361+
362+ return [ indent ( 'z.object({' , indentCount ) , objectFields . join ( ',\n' ) , indent ( '})' , indentCount ) ] . join ( '\n' ) ;
363+ } ) . join ( ',\n' ) ;
364+
365+ return union ;
366+ }
367+
286368function generateFieldTypeZodSchema ( config : ValidationSchemaPluginConfig , visitor : Visitor , field : InputValueDefinitionNode | FieldDefinitionNode , type : TypeNode , parentType ?: TypeNode ) : string {
287369 if ( isListType ( type ) ) {
288370 const gen = generateFieldTypeZodSchema ( config , visitor , field , type . type , type ) ;
0 commit comments