Skip to content

Commit 254911c

Browse files
committed
proto skip null fields
1 parent da26163 commit 254911c

File tree

2 files changed

+90
-9
lines changed

2 files changed

+90
-9
lines changed

example_with_targets/src/tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,32 @@ fn test_target_b() -> Result<()> {
4242
assert_eq!(result, expected);
4343
Ok(())
4444
}
45+
46+
#[test]
47+
fn test_null_field_skipping() -> Result<()> {
48+
let test_function_none = |_input: crate::schema::target_a::Input| -> Result<crate::schema::FunctionTargetAResult> {
49+
Ok(crate::schema::FunctionTargetAResult {
50+
status: None // This should not appear in serialized output
51+
})
52+
};
53+
54+
let test_function_some = |_input: crate::schema::target_a::Input| -> Result<crate::schema::FunctionTargetAResult> {
55+
Ok(crate::schema::FunctionTargetAResult {
56+
status: Some(200) // This should appear in serialized output
57+
})
58+
};
59+
60+
let test_input = r#"{
61+
"id": "gid://shopify/Order/1234567890",
62+
"num": 123,
63+
"name": "test"
64+
}"#;
65+
66+
let result_none = run_function_with_input(test_function_none, test_input)?;
67+
let result_some = run_function_with_input(test_function_some, test_input)?;
68+
69+
assert_eq!(result_none.status, None);
70+
assert_eq!(result_some.status, Some(200));
71+
72+
Ok(())
73+
}

shopify_function_macro/src/lib.rs

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
275286
struct ShopifyFunctionCodeGenerator;
276287

277288
impl 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

Comments
 (0)