@@ -272,6 +272,18 @@ 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" )
284+ || ( type_name. contains ( "Nullable" ) && !type_name. contains ( "NonNull" ) )
285+ }
286+
275287struct ShopifyFunctionCodeGenerator ;
276288
277289impl CodeGenerator for ShopifyFunctionCodeGenerator {
@@ -496,35 +508,76 @@ impl CodeGenerator for ShopifyFunctionCodeGenerator {
496508 ) -> Vec < syn:: ItemImpl > {
497509 let name_ident = names:: type_ident ( input_object_type_definition. name ( ) ) ;
498510
511+ // Conditionally serialize fields based on GraphQL schema nullability
512+ // Nullable fields (Option<T>) are only serialized if Some(_)
513+ // Required fields are always serialized
514+
499515 let field_statements: Vec < syn:: Stmt > = input_object_type_definition
500516 . input_field_definitions ( )
501517 . iter ( )
502518 . flat_map ( |ivd| {
503519 let field_name_ident = names:: field_ident ( ivd. name ( ) ) ;
504520 let field_name_lit_str = syn:: LitStr :: new ( ivd. name ( ) , Span :: mixed_site ( ) ) ;
505521
506- vec ! [
522+ // Check if this field is nullable in the GraphQL schema
523+ if is_input_field_nullable ( ivd) {
524+ // For nullable fields, only serialize if Some(_)
525+ vec ! [ parse_quote! {
526+ if let :: std:: option:: Option :: Some ( ref value) = self . #field_name_ident {
527+ context. write_utf8_str( #field_name_lit_str) ?;
528+ value. serialize( context) ?;
529+ }
530+ } ]
531+ } else {
532+ // For required fields, always serialize
533+ vec ! [
534+ parse_quote! {
535+ context. write_utf8_str( #field_name_lit_str) ?;
536+ } ,
537+ parse_quote! {
538+ self . #field_name_ident. serialize( context) ?;
539+ } ,
540+ ]
541+ }
542+ } )
543+ . collect ( ) ;
544+
545+ // Generate field counting statements for dynamic field count calculation
546+ let field_count_statements: Vec < syn:: Stmt > = input_object_type_definition
547+ . input_field_definitions ( )
548+ . iter ( )
549+ . map ( |ivd| {
550+ let field_name_ident = names:: field_ident ( ivd. name ( ) ) ;
551+
552+ if is_input_field_nullable ( ivd) {
553+ // For nullable fields, count only if Some(_)
507554 parse_quote ! {
508- context. write_utf8_str( #field_name_lit_str) ?;
509- } ,
555+ if let :: std:: option:: Option :: Some ( _) = self . #field_name_ident {
556+ field_count += 1 ;
557+ }
558+ }
559+ } else {
560+ // For required fields, always count
510561 parse_quote ! {
511- self . #field_name_ident . serialize ( context ) ? ;
512- } ,
513- ]
562+ field_count += 1 ;
563+ }
564+ }
514565 } )
515566 . collect ( ) ;
516567
517- let num_fields = input_object_type_definition. input_field_definitions ( ) . len ( ) ;
518-
519568 let serialize_impl = parse_quote ! {
520569 impl shopify_function:: wasm_api:: Serialize for #name_ident {
521570 fn serialize( & self , context: & mut shopify_function:: wasm_api:: Context ) -> :: std:: result:: Result <( ) , shopify_function:: wasm_api:: write:: Error > {
571+ // Calculate dynamic field count based on non-null fields
572+ let mut field_count = 0usize ;
573+ #( #field_count_statements) *
574+
522575 context. write_object(
523576 |context| {
524577 #( #field_statements) *
525578 :: std:: result:: Result :: Ok ( ( ) )
526579 } ,
527- #num_fields ,
580+ field_count ,
528581 )
529582 }
530583 }
0 commit comments