@@ -272,6 +272,18 @@ pub fn typegen(
272
272
module. to_token_stream ( ) . into ( )
273
273
}
274
274
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
+
275
287
struct ShopifyFunctionCodeGenerator ;
276
288
277
289
impl CodeGenerator for ShopifyFunctionCodeGenerator {
@@ -496,35 +508,76 @@ impl CodeGenerator for ShopifyFunctionCodeGenerator {
496
508
) -> Vec < syn:: ItemImpl > {
497
509
let name_ident = names:: type_ident ( input_object_type_definition. name ( ) ) ;
498
510
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
+
499
515
let field_statements: Vec < syn:: Stmt > = input_object_type_definition
500
516
. input_field_definitions ( )
501
517
. iter ( )
502
518
. flat_map ( |ivd| {
503
519
let field_name_ident = names:: field_ident ( ivd. name ( ) ) ;
504
520
let field_name_lit_str = syn:: LitStr :: new ( ivd. name ( ) , Span :: mixed_site ( ) ) ;
505
521
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(_)
507
554
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
510
561
parse_quote ! {
511
- self . #field_name_ident . serialize ( context ) ? ;
512
- } ,
513
- ]
562
+ field_count += 1 ;
563
+ }
564
+ }
514
565
} )
515
566
. collect ( ) ;
516
567
517
- let num_fields = input_object_type_definition. input_field_definitions ( ) . len ( ) ;
518
-
519
568
let serialize_impl = parse_quote ! {
520
569
impl shopify_function:: wasm_api:: Serialize for #name_ident {
521
570
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
+
522
575
context. write_object(
523
576
|context| {
524
577
#( #field_statements) *
525
578
:: std:: result:: Result :: Ok ( ( ) )
526
579
} ,
527
- #num_fields ,
580
+ field_count ,
528
581
)
529
582
}
530
583
}
0 commit comments