@@ -553,7 +553,7 @@ export class EndpointSnippetGenerator {
553553 ) ;
554554
555555 // Add body fields as keyword arguments
556- if ( request . body != null ) {
556+ if ( request . body != null && snippet . requestBody != null ) {
557557 switch ( request . body . type ) {
558558 case "bytes" :
559559 // Not supported in Ruby snippets yet
@@ -563,22 +563,45 @@ export class EndpointSnippetGenerator {
563563 } ) ;
564564 break ;
565565 case "typeReference" : {
566- // For typeReference bodies, we need to flatten the body fields into keyword arguments
567- // The Ruby SDK expects keyword args that get wrapped into the type by the method
568- const bodyRecord = this . context . getRecord ( snippet . requestBody ) ;
569- if ( bodyRecord != null ) {
570- // Get the type definition to understand the field names
571- const typeRef = request . body . value ;
572- if ( typeRef . type === "named" ) {
573- const namedType = this . context . resolveNamedType ( { typeId : typeRef . value } ) ;
574- if ( namedType != null ) {
575- // Convert the body record fields to keyword arguments
566+ const typeRef = request . body . value ;
567+
568+ // Check if this is a named type that we can resolve
569+ if ( typeRef . type === "named" ) {
570+ const namedType = this . context . resolveNamedType ( { typeId : typeRef . value } ) ;
571+ if ( namedType != null && namedType . type === "object" ) {
572+ // For objects, flatten the body fields into keyword arguments
573+ const bodyRecord = this . context . getRecord ( snippet . requestBody ) ;
574+ if ( bodyRecord != null ) {
576575 const bodyFields = this . getBodyFieldsAsKeywordArgs ( {
577576 namedType,
578577 bodyRecord
579578 } ) ;
580579 args . push ( ...bodyFields ) ;
581580 }
581+ } else if ( namedType != null ) {
582+ // For non-object named types (undiscriminated unions, aliases, etc.),
583+ // convert the entire body value and pass as a single 'request' keyword argument
584+ const bodyArgs = this . getBodyArgsForNonObjectType ( {
585+ namedType,
586+ typeRef,
587+ bodyValue : snippet . requestBody
588+ } ) ;
589+ args . push ( ...bodyArgs ) ;
590+ }
591+ } else {
592+ // For non-named type references (containers, primitives, etc.),
593+ // convert the body value directly
594+ const convertedValue = this . context . dynamicTypeLiteralMapper . convert ( {
595+ typeReference : typeRef ,
596+ value : snippet . requestBody
597+ } ) ;
598+ if ( ! ruby . TypeLiteral . isNop ( convertedValue ) ) {
599+ args . push (
600+ ruby . keywordArgument ( {
601+ name : "request" ,
602+ value : convertedValue
603+ } )
604+ ) ;
582605 }
583606 }
584607 break ;
@@ -591,6 +614,119 @@ export class EndpointSnippetGenerator {
591614 return args ;
592615 }
593616
617+ private getBodyArgsForNonObjectType ( {
618+ namedType,
619+ typeRef,
620+ bodyValue
621+ } : {
622+ namedType : FernIr . dynamic . NamedType ;
623+ typeRef : FernIr . dynamic . TypeReference ;
624+ bodyValue : unknown ;
625+ } ) : ruby . KeywordArgument [ ] {
626+ const args : ruby . KeywordArgument [ ] = [ ] ;
627+
628+ switch ( namedType . type ) {
629+ case "undiscriminatedUnion" : {
630+ // For undiscriminated unions, the body value should match one of the variants
631+ // Try to convert it and extract the fields as keyword arguments
632+ const bodyRecord = this . context . getRecord ( bodyValue ) ;
633+ if ( bodyRecord != null ) {
634+ // The body is an object - try to find a matching variant and extract its fields
635+ for ( const variant of namedType . types ) {
636+ if ( variant . type === "named" ) {
637+ const variantType = this . context . resolveNamedType ( { typeId : variant . value } ) ;
638+ if ( variantType != null && variantType . type === "object" ) {
639+ // Check if the body matches this variant's properties
640+ const variantProps = new Set ( variantType . properties . map ( ( p ) => p . name . wireValue ) ) ;
641+ const bodyKeys = Object . keys ( bodyRecord ) ;
642+ const allKeysMatch = bodyKeys . every ( ( key ) => variantProps . has ( key ) ) ;
643+ if ( allKeysMatch && bodyKeys . length > 0 ) {
644+ // This variant matches - flatten its fields
645+ const bodyFields = this . getBodyFieldsAsKeywordArgs ( {
646+ namedType : variantType ,
647+ bodyRecord
648+ } ) ;
649+ args . push ( ...bodyFields ) ;
650+ return args ;
651+ }
652+ }
653+ }
654+ }
655+ }
656+ // If we couldn't match a variant or extract fields, convert the whole value
657+ const convertedValue = this . context . dynamicTypeLiteralMapper . convert ( {
658+ typeReference : typeRef ,
659+ value : bodyValue
660+ } ) ;
661+ if ( ! ruby . TypeLiteral . isNop ( convertedValue ) ) {
662+ args . push (
663+ ruby . keywordArgument ( {
664+ name : "request" ,
665+ value : convertedValue
666+ } )
667+ ) ;
668+ }
669+ break ;
670+ }
671+ case "alias" : {
672+ // For aliases, check if the underlying type is an object we can flatten
673+ const aliasedType = namedType . typeReference ;
674+ if ( aliasedType . type === "named" ) {
675+ const resolvedAliasType = this . context . resolveNamedType ( { typeId : aliasedType . value } ) ;
676+ if ( resolvedAliasType != null && resolvedAliasType . type === "object" ) {
677+ const bodyRecord = this . context . getRecord ( bodyValue ) ;
678+ if ( bodyRecord != null ) {
679+ const bodyFields = this . getBodyFieldsAsKeywordArgs ( {
680+ namedType : resolvedAliasType ,
681+ bodyRecord
682+ } ) ;
683+ args . push ( ...bodyFields ) ;
684+ return args ;
685+ }
686+ }
687+ }
688+ // For non-object aliases (arrays, primitives, etc.), convert the whole value
689+ const convertedValue = this . context . dynamicTypeLiteralMapper . convert ( {
690+ typeReference : typeRef ,
691+ value : bodyValue
692+ } ) ;
693+ if ( ! ruby . TypeLiteral . isNop ( convertedValue ) ) {
694+ args . push (
695+ ruby . keywordArgument ( {
696+ name : "request" ,
697+ value : convertedValue
698+ } )
699+ ) ;
700+ }
701+ break ;
702+ }
703+ case "discriminatedUnion" :
704+ case "enum" : {
705+ // For discriminated unions and enums, convert the whole value
706+ const convertedValue = this . context . dynamicTypeLiteralMapper . convert ( {
707+ typeReference : typeRef ,
708+ value : bodyValue
709+ } ) ;
710+ if ( ! ruby . TypeLiteral . isNop ( convertedValue ) ) {
711+ args . push (
712+ ruby . keywordArgument ( {
713+ name : "request" ,
714+ value : convertedValue
715+ } )
716+ ) ;
717+ }
718+ break ;
719+ }
720+ case "object" :
721+ // This shouldn't happen as objects are handled separately
722+ break ;
723+ default :
724+ assertNever ( namedType ) ;
725+ }
726+
727+ return args ;
728+ }
729+
594730 private getBodyFieldsAsKeywordArgs ( {
595731 namedType,
596732 bodyRecord
0 commit comments