diff --git a/example_with_targets/schema.graphql b/example_with_targets/schema.graphql index 3ce0104..13e90b1 100644 --- a/example_with_targets/schema.graphql +++ b/example_with_targets/schema.graphql @@ -131,6 +131,12 @@ input FunctionTargetAResult { status: Int } +input SerializationProbe { + optionalValue: Int + defaultedValue: Int! = 200 + requiredValue: Int! +} + """ The result of API target B. """ diff --git a/example_with_targets/src/tests.rs b/example_with_targets/src/tests.rs index 4de5b0a..42455d9 100644 --- a/example_with_targets/src/tests.rs +++ b/example_with_targets/src/tests.rs @@ -1,4 +1,5 @@ use super::*; +use shopify_function::wasm_api::{Context, Serialize}; use shopify_function::{run_function_with_input, Result}; #[test] @@ -42,3 +43,49 @@ fn test_target_b() -> Result<()> { assert_eq!(result, expected); Ok(()) } + +#[test] +fn test_input_object_serialization_omits_none_fields_and_keeps_required_fields() -> Result<()> { + let result = serialize_to_json(&crate::schema::SerializationProbe { + optional_value: None, + defaulted_value: None, + required_value: 1, + })?; + + assert_eq!(serde_json::json!({ "requiredValue": 1 }), result); + Ok(()) +} + +#[test] +fn test_input_object_serialization_includes_some_fields() -> Result<()> { + let result = serialize_to_json(&crate::schema::SerializationProbe { + optional_value: Some(200), + defaulted_value: Some(201), + required_value: 1, + })?; + + assert_eq!( + serde_json::json!({ "optionalValue": 200, "defaultedValue": 201, "requiredValue": 1 }), + result + ); + Ok(()) +} + +#[test] +fn test_one_of_input_object_serialization_writes_active_variant() -> Result<()> { + let result = serialize_to_json(&crate::schema::Operation::DoThis(crate::schema::This { + this_field: "this field".to_string(), + }))?; + + assert_eq!( + serde_json::json!({ "doThis": { "thisField": "this field" } }), + result + ); + Ok(()) +} + +fn serialize_to_json(value: &T) -> Result { + let mut context = Context::new_with_input(serde_json::json!({})); + value.serialize(&mut context)?; + Ok(context.finalize_output_and_return()?) +} diff --git a/shopify_function_macro/src/lib.rs b/shopify_function_macro/src/lib.rs index d959fa4..52ac74d 100644 --- a/shopify_function_macro/src/lib.rs +++ b/shopify_function_macro/src/lib.rs @@ -500,28 +500,53 @@ impl CodeGenerator for ShopifyFunctionCodeGenerator { let field_name_ident = names::field_ident(ivd.name()); let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site()); - vec![ - parse_quote! { - context.write_utf8_str(#field_name_lit_str)?; - }, - parse_quote! { - self.#field_name_ident.serialize(context)?; - }, - ] + if ivd.is_required() { + vec![ + parse_quote! { + context.write_utf8_str(#field_name_lit_str)?; + }, + parse_quote! { + self.#field_name_ident.serialize(context)?; + }, + ] + } else { + vec![parse_quote! { + if let ::std::option::Option::Some(value) = &self.#field_name_ident { + context.write_utf8_str(#field_name_lit_str)?; + value.serialize(context)?; + } + }] + } }) .collect(); - let num_fields = input_object_type_definition.input_field_definitions().len(); + let num_required_fields = input_object_type_definition + .input_field_definitions() + .iter() + .filter(|ivd| ivd.is_required()) + .count(); + + let optional_field_count_terms: Vec = input_object_type_definition + .input_field_definitions() + .iter() + .filter(|ivd| !ivd.is_required()) + .map(|ivd| { + let field_name_ident = names::field_ident(ivd.name()); + parse_quote! { ::std::primitive::usize::from(self.#field_name_ident.is_some()) } + }) + .collect(); let serialize_impl = parse_quote! { impl shopify_function::wasm_api::Serialize for #name_ident { fn serialize(&self, context: &mut shopify_function::wasm_api::Context) -> ::std::result::Result<(), shopify_function::wasm_api::write::Error> { + let field_count: ::std::primitive::usize = #num_required_fields #(+ #optional_field_count_terms)*; + context.write_object( |context| { #(#field_statements)* ::std::result::Result::Ok(()) }, - #num_fields, + field_count, ) } }