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