diff --git a/example_with_targets/schema.graphql b/example_with_targets/schema.graphql index 32b9046..9505d2d 100644 --- a/example_with_targets/schema.graphql +++ b/example_with_targets/schema.graphql @@ -8,6 +8,11 @@ Only allow the field to be queried when targeting one of the specified targets. """ directive @restrictTarget(only: [String!]!) on FIELD_DEFINITION +""" +Requires that exactly one field must be supplied and that field must not be `null`. +""" +directive @oneOf on INPUT_OBJECT + """ Represents an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)-encoded date string. For example, September 7, 2019 is represented as `"2019-07-16"`. @@ -108,6 +113,20 @@ The result of API target B. """ input FunctionTargetBResult { name: String + operations: [Operation!]! +} + +input Operation @oneOf { + doThis: This + doThat: That +} + +input This { + thisField: String! +} + +input That { + thatField: Int! } """ diff --git a/example_with_targets/src/main.rs b/example_with_targets/src/main.rs index e1a6bb7..fa475d5 100644 --- a/example_with_targets/src/main.rs +++ b/example_with_targets/src/main.rs @@ -23,6 +23,12 @@ fn target_a(_input: schema::target_a::Input) -> Result Result { Ok(schema::FunctionTargetBResult { name: Some(format!("new name: \"{}\"", input.id())), + operations: vec![ + schema::Operation::DoThis(schema::This { + this_field: "this field".to_string(), + }), + schema::Operation::DoThat(schema::That { that_field: 42 }), + ], }) } diff --git a/example_with_targets/src/tests.rs b/example_with_targets/src/tests.rs index 2dc866e..4de5b0a 100644 --- a/example_with_targets/src/tests.rs +++ b/example_with_targets/src/tests.rs @@ -31,6 +31,12 @@ fn test_target_b() -> Result<()> { )?; let expected = crate::schema::FunctionTargetBResult { name: Some("new name: \"gid://shopify/Order/1234567890\"".to_string()), + operations: vec![ + crate::schema::Operation::DoThis(crate::schema::This { + this_field: "this field".to_string(), + }), + crate::schema::Operation::DoThat(crate::schema::That { that_field: 42 }), + ], }; assert_eq!(result, expected); diff --git a/integration_tests/tests/integration_test.rs b/integration_tests/tests/integration_test.rs index cc322e3..9d812b9 100644 --- a/integration_tests/tests/integration_test.rs +++ b/integration_tests/tests/integration_test.rs @@ -39,7 +39,19 @@ fn test_example_with_targets_target_b() -> Result<()> { assert_eq!( output, serde_json::json!({ - "name": "new name: \"gid://shopify/Order/1234567890\"" + "name": "new name: \"gid://shopify/Order/1234567890\"", + "operations": [ + { + "doThis": { + "thisField": "this field" + } + }, + { + "doThat": { + "thatField": 42 + } + } + ] }) ); Ok(()) diff --git a/shopify_function_macro/src/lib.rs b/shopify_function_macro/src/lib.rs index c8adf30..d8257f1 100644 --- a/shopify_function_macro/src/lib.rs +++ b/shopify_function_macro/src/lib.rs @@ -529,7 +529,27 @@ impl CodeGenerator for ShopifyFunctionCodeGenerator { } }; - vec![serialize_impl] + let field_values: Vec = input_object_type_definition + .input_field_definitions() + .iter() + .map(|ivd| { + let field_name_ident = names::field_ident(ivd.name()); + let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site()); + parse_quote! { #field_name_ident: shopify_function::wasm_api::Deserialize::deserialize(&value.get_obj_prop(#field_name_lit_str))? } + }) + .collect(); + + let deserialize_impl = parse_quote! { + impl shopify_function::wasm_api::Deserialize for #name_ident { + fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result { + Ok(Self { + #(#field_values),* + }) + } + } + }; + + vec![serialize_impl, deserialize_impl] } fn additional_impls_for_one_of_input_object( @@ -567,7 +587,47 @@ impl CodeGenerator for ShopifyFunctionCodeGenerator { } }; - vec![serialize_impl] + let deserialize_match_arms: Vec = input_object_type_definition + .input_field_definitions() + .iter() + .map(|ivd| { + let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site()); + let variant_ident = names::enum_variant_ident(ivd.name()); + + parse_quote! { + #field_name_lit_str => { + let value = shopify_function::wasm_api::Deserialize::deserialize(&field_value)?; + Ok(Self::#variant_ident(value)) + } + } + }) + .collect(); + + let deserialize_impl = parse_quote! { + impl shopify_function::wasm_api::Deserialize for #name_ident { + fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result { + let Some(obj_len) = value.obj_len() else { + return Err(shopify_function::wasm_api::read::Error::InvalidType); + }; + + if obj_len != 1 { + return Err(shopify_function::wasm_api::read::Error::InvalidType); + } + + let Some(field_name) = value.get_obj_key_at_index(0) else { + return Err(shopify_function::wasm_api::read::Error::InvalidType); + }; + let field_value = value.get_at_index(0); + + match field_name.as_str() { + #(#deserialize_match_arms)* + _ => Err(shopify_function::wasm_api::read::Error::InvalidType), + } + } + } + }; + + vec![serialize_impl, deserialize_impl] } fn attributes_for_enum(