@@ -272,6 +272,17 @@ pub fn typegen(
272272 module. to_token_stream ( ) . into ( )
273273}
274274
275+ /// Helper function to determine if a GraphQL input field type is nullable
276+ /// Uses conservative detection to identify Optional fields from GraphQL schema
277+ fn is_input_field_nullable ( ivd : & impl InputValueDefinition ) -> bool {
278+ // Use std::any::type_name to get type information as a string
279+ let type_name = std:: any:: type_name_of_val ( & ivd. r#type ( ) ) ;
280+
281+ // only treat fields that are explicitly nullable as Option types
282+ // This prevents incorrectly wrapping required fields in Option<T>
283+ type_name. contains ( "Option" ) || ( type_name. contains ( "Nullable" ) && !type_name. contains ( "NonNull" ) )
284+ }
285+
275286struct ShopifyFunctionCodeGenerator ;
276287
277288impl CodeGenerator for ShopifyFunctionCodeGenerator {
@@ -496,35 +507,76 @@ impl CodeGenerator for ShopifyFunctionCodeGenerator {
496507 ) -> Vec < syn:: ItemImpl > {
497508 let name_ident = names:: type_ident ( input_object_type_definition. name ( ) ) ;
498509
510+ // Conditionally serialize fields based on GraphQL schema nullability
511+ // Nullable fields (Option<T>) are only serialized if Some(_)
512+ // Required fields are always serialized
513+
499514 let field_statements: Vec < syn:: Stmt > = input_object_type_definition
500515 . input_field_definitions ( )
501516 . iter ( )
502517 . flat_map ( |ivd| {
503518 let field_name_ident = names:: field_ident ( ivd. name ( ) ) ;
504519 let field_name_lit_str = syn:: LitStr :: new ( ivd. name ( ) , Span :: mixed_site ( ) ) ;
520+
521+ // Check if this field is nullable in the GraphQL schema
522+ if is_input_field_nullable ( ivd) {
523+ // For nullable fields, only serialize if Some(_)
524+ vec ! [ parse_quote! {
525+ if let :: std:: option:: Option :: Some ( ref value) = self . #field_name_ident {
526+ context. write_utf8_str( #field_name_lit_str) ?;
527+ value. serialize( context) ?;
528+ }
529+ } ]
530+ } else {
531+ // For required fields, always serialize
532+ vec ! [
533+ parse_quote! {
534+ context. write_utf8_str( #field_name_lit_str) ?;
535+ } ,
536+ parse_quote! {
537+ self . #field_name_ident. serialize( context) ?;
538+ } ,
539+ ]
540+ }
541+ } )
542+ . collect ( ) ;
505543
506- vec ! [
544+ // Generate field counting statements for dynamic field count calculation
545+ let field_count_statements: Vec < syn:: Stmt > = input_object_type_definition
546+ . input_field_definitions ( )
547+ . iter ( )
548+ . map ( |ivd| {
549+ let field_name_ident = names:: field_ident ( ivd. name ( ) ) ;
550+
551+ if is_input_field_nullable ( ivd) {
552+ // For nullable fields, count only if Some(_)
507553 parse_quote ! {
508- context. write_utf8_str( #field_name_lit_str) ?;
509- } ,
554+ if let :: std:: option:: Option :: Some ( _) = self . #field_name_ident {
555+ field_count += 1 ;
556+ }
557+ }
558+ } else {
559+ // For required fields, always count
510560 parse_quote ! {
511- self . #field_name_ident . serialize ( context ) ? ;
512- } ,
513- ]
561+ field_count += 1 ;
562+ }
563+ }
514564 } )
515565 . collect ( ) ;
516566
517- let num_fields = input_object_type_definition. input_field_definitions ( ) . len ( ) ;
518-
519567 let serialize_impl = parse_quote ! {
520568 impl shopify_function:: wasm_api:: Serialize for #name_ident {
521569 fn serialize( & self , context: & mut shopify_function:: wasm_api:: Context ) -> :: std:: result:: Result <( ) , shopify_function:: wasm_api:: write:: Error > {
570+ // Calculate dynamic field count based on non-null fields
571+ let mut field_count = 0usize ;
572+ #( #field_count_statements) *
573+
522574 context. write_object(
523575 |context| {
524576 #( #field_statements) *
525577 :: std:: result:: Result :: Ok ( ( ) )
526578 } ,
527- #num_fields ,
579+ field_count ,
528580 )
529581 }
530582 }
0 commit comments