1use std::collections::HashMap;
2
3use bluejay_core::{
4 definition::{
5 EnumTypeDefinition, EnumValueDefinition, InputObjectTypeDefinition, InputValueDefinition,
6 },
7 AsIter,
8};
9use bluejay_typegen_codegen::{
10 generate_schema, names, CodeGenerator, ExecutableStruct, Input as BluejayInput,
11 KnownCustomScalarType, WrappedExecutableType,
12};
13use convert_case::{Case, Casing};
14use proc_macro2::Span;
15use quote::{format_ident, quote, ToTokens};
16use syn::{parse_macro_input, parse_quote, FnArg};
17
18fn extract_shopify_function_return_type(ast: &syn::ItemFn) -> Result<&syn::Ident, syn::Error> {
19 use syn::*;
20
21 let ReturnType::Type(_arrow, ty) = &ast.sig.output else {
22 return Err(Error::new_spanned(
23 &ast.sig,
24 "Shopify Functions require an explicit return type",
25 ));
26 };
27 let Type::Path(path) = ty.as_ref() else {
28 return Err(Error::new_spanned(
29 &ast.sig,
30 "Shopify Functions must return a Result",
31 ));
32 };
33 let result = path.path.segments.last().unwrap();
34 if result.ident != "Result" {
35 return Err(Error::new_spanned(
36 result,
37 "Shopify Functions must return a Result",
38 ));
39 }
40 let PathArguments::AngleBracketed(generics) = &result.arguments else {
41 return Err(Error::new_spanned(
42 result,
43 "Shopify Function Result is missing generic arguments",
44 ));
45 };
46 if generics.args.len() != 1 {
47 return Err(Error::new_spanned(
48 generics,
49 "Shopify Function Result takes exactly one generic argument",
50 ));
51 }
52 let GenericArgument::Type(ty) = generics.args.first().unwrap() else {
53 return Err(Error::new_spanned(
54 generics,
55 "Shopify Function Result expects a type",
56 ));
57 };
58 let Type::Path(path) = ty else {
59 return Err(Error::new_spanned(
60 result,
61 "Unexpected result type for Shopify Function Result",
62 ));
63 };
64 Ok(&path.path.segments.last().as_ref().unwrap().ident)
65}
66
67#[proc_macro_attribute]
70pub fn shopify_function(
71 attr: proc_macro::TokenStream,
72 item: proc_macro::TokenStream,
73) -> proc_macro::TokenStream {
74 let ast = parse_macro_input!(item as syn::ItemFn);
75 if !attr.is_empty() {
76 return quote! {compile_error!("Shopify functions don't accept attributes");}.into();
77 }
78
79 let function_name = &ast.sig.ident;
80 let function_name_string = function_name.to_string();
81 let export_function_name = format_ident!("{}_export", function_name);
82
83 if ast.sig.inputs.len() != 1 {
84 return quote! {compile_error!("Shopify functions need exactly one input parameter");}
85 .into();
86 }
87
88 let input_type = match &ast.sig.inputs.first().unwrap() {
89 FnArg::Typed(input) => input.ty.as_ref(),
90 FnArg::Receiver(_) => {
91 return quote! {compile_error!("Shopify functions can't have a receiver");}.into()
92 }
93 };
94
95 if let Err(error) = extract_shopify_function_return_type(&ast) {
96 return error.to_compile_error().into();
97 }
98
99 quote! {
100 #[export_name = #function_name_string]
101 pub extern "C" fn #export_function_name() {
102 shopify_function::wasm_api::init_panic_handler();
103 let mut context = shopify_function::wasm_api::Context::new();
104 let root_value = context.input_get().expect("Failed to get input");
105 let mut input: #input_type = shopify_function::wasm_api::Deserialize::deserialize(&root_value).expect("Failed to deserialize input");
106 let result = #function_name(input).expect("Failed to call function");
107 shopify_function::wasm_api::Serialize::serialize(&result, &mut context).expect("Failed to serialize output");
108 }
109
110 #ast
111 }
112 .into()
113}
114
115const DEFAULT_EXTERN_ENUMS: &[&str] = &["LanguageCode", "CountryCode", "CurrencyCode"];
116
117mod kw {
118 syn::custom_keyword!(input_stream);
119 syn::custom_keyword!(output_stream);
120}
121
122#[proc_macro_attribute]
187pub fn typegen(
188 attr: proc_macro::TokenStream,
189 item: proc_macro::TokenStream,
190) -> proc_macro::TokenStream {
191 let mut input = syn::parse_macro_input!(attr as BluejayInput);
192 let mut module = syn::parse_macro_input!(item as syn::ItemMod);
193
194 if let Some(borrow) = input.borrow.as_ref() {
195 if borrow.value() {
196 let error = syn::Error::new_spanned(
197 borrow,
198 "`borrow` attribute must be `false` or omitted for Shopify Functions",
199 );
200 return error.to_compile_error().into();
201 }
202 }
203
204 if input.enums_as_str.is_empty() {
205 let enums_as_str = DEFAULT_EXTERN_ENUMS
206 .iter()
207 .map(|enum_name| syn::LitStr::new(enum_name, Span::mixed_site()))
208 .collect::<Vec<_>>();
209 input.enums_as_str = syn::parse_quote! { #(#enums_as_str),* };
210 }
211
212 let string_known_custom_scalar_type = KnownCustomScalarType {
213 type_for_borrowed: None, type_for_owned: syn::parse_quote! { ::std::string::String },
215 };
216
217 let known_custom_scalar_types = HashMap::from([
218 (String::from("Id"), string_known_custom_scalar_type.clone()),
219 (String::from("Url"), string_known_custom_scalar_type.clone()),
220 (
221 String::from("Handle"),
222 string_known_custom_scalar_type.clone(),
223 ),
224 (
225 String::from("Date"),
226 string_known_custom_scalar_type.clone(),
227 ),
228 (
229 String::from("DateTime"),
230 string_known_custom_scalar_type.clone(),
231 ),
232 (
233 String::from("DateTimeWithoutTimezone"),
234 string_known_custom_scalar_type.clone(),
235 ),
236 (
237 String::from("TimeWithoutTimezone"),
238 string_known_custom_scalar_type.clone(),
239 ),
240 (
241 String::from("Void"),
242 KnownCustomScalarType {
243 type_for_borrowed: None,
244 type_for_owned: syn::parse_quote! { () },
245 },
246 ),
247 (
248 String::from("Json"),
249 KnownCustomScalarType {
250 type_for_borrowed: None,
251 type_for_owned: syn::parse_quote! { ::shopify_function::scalars::JsonValue },
252 },
253 ),
254 (
255 String::from("Decimal"),
256 KnownCustomScalarType {
257 type_for_borrowed: None,
258 type_for_owned: syn::parse_quote! { ::shopify_function::scalars::Decimal },
259 },
260 ),
261 ]);
262
263 if let Err(error) = generate_schema(
264 input,
265 &mut module,
266 known_custom_scalar_types,
267 ShopifyFunctionCodeGenerator,
268 ) {
269 return error.to_compile_error().into();
270 }
271
272 module.to_token_stream().into()
273}
274
275struct ShopifyFunctionCodeGenerator;
276
277impl CodeGenerator for ShopifyFunctionCodeGenerator {
278 fn fields_for_executable_struct(
279 &self,
280 executable_struct: &bluejay_typegen_codegen::ExecutableStruct,
281 ) -> syn::Fields {
282 let once_cell_fields: Vec<syn::Field> = executable_struct
283 .fields()
284 .iter()
285 .map(|field| {
286 let field_name_ident = names::field_ident(field.graphql_name());
287 let field_type = Self::type_for_field(executable_struct, field.r#type(), false);
288
289 parse_quote! {
290 #field_name_ident: ::std::cell::OnceCell<#field_type>
291 }
292 })
293 .collect();
294
295 let fields_named: syn::FieldsNamed = parse_quote! {
296 {
297 __wasm_value: shopify_function::wasm_api::Value,
298 #(#once_cell_fields),*
299 }
300 };
301 fields_named.into()
302 }
303
304 fn additional_impls_for_executable_struct(
305 &self,
306 executable_struct: &bluejay_typegen_codegen::ExecutableStruct,
307 ) -> Vec<syn::ItemImpl> {
308 let name_ident = names::type_ident(executable_struct.parent_name());
309
310 let once_cell_field_values: Vec<syn::FieldValue> = executable_struct
311 .fields()
312 .iter()
313 .map(|field| {
314 let field_name_ident = names::field_ident(field.graphql_name());
315
316 parse_quote! {
317 #field_name_ident: ::std::cell::OnceCell::new()
318 }
319 })
320 .collect();
321
322 let deserialize_impl = parse_quote! {
323 impl shopify_function::wasm_api::Deserialize for #name_ident {
324 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
325 Ok(Self {
326 __wasm_value: *value,
327 #(#once_cell_field_values),*
328 })
329 }
330 }
331 };
332
333 let accessors: Vec<syn::ImplItemFn> = executable_struct
334 .fields()
335 .iter()
336 .map(|field| {
337 let field_name_ident = names::field_ident(field.graphql_name());
338 let field_name_lit_str = syn::LitStr::new(field.graphql_name(), Span::mixed_site());
339 let field_type = Self::type_for_field(executable_struct, field.r#type(), true);
340
341 let properly_referenced_value =
342 Self::reference_variable_for_type(field.r#type(), &format_ident!("value_ref"));
343
344 let description: Option<syn::Attribute> = field.description().map(|description| {
345 let description_lit_str = syn::LitStr::new(description, Span::mixed_site());
346 parse_quote! { #[doc = #description_lit_str] }
347 });
348
349 parse_quote! {
350 #description
351 pub fn #field_name_ident(&self) -> #field_type {
352 static INTERNED_FIELD_NAME: shopify_function::wasm_api::CachedInternedStringId = shopify_function::wasm_api::CachedInternedStringId::new(#field_name_lit_str, );
353 let interned_string_id = INTERNED_FIELD_NAME.load();
354
355 let value = self.#field_name_ident.get_or_init(|| {
356 let value = self.__wasm_value.get_interned_obj_prop(interned_string_id);
357 shopify_function::wasm_api::Deserialize::deserialize(&value).unwrap()
358 });
359 let value_ref = &value;
360 #properly_referenced_value
361 }
362 }
363 })
364 .collect();
365
366 let accessor_impl = parse_quote! {
367 impl #name_ident {
368 #(#accessors)*
369 }
370 };
371
372 vec![deserialize_impl, accessor_impl]
373 }
374
375 fn additional_impls_for_executable_enum(
376 &self,
377 executable_enum: &bluejay_typegen_codegen::ExecutableEnum,
378 ) -> Vec<syn::ItemImpl> {
379 let name_ident = names::type_ident(executable_enum.parent_name());
380
381 let match_arms: Vec<syn::Arm> = executable_enum
382 .variants()
383 .iter()
384 .map(|variant| {
385 let variant_name_ident = names::enum_variant_ident(variant.parent_name());
386 let variant_name_lit_str = syn::LitStr::new(variant.parent_name(), Span::mixed_site());
387
388 parse_quote! {
389 #variant_name_lit_str => shopify_function::wasm_api::Deserialize::deserialize(value).map(Self::#variant_name_ident),
390 }
391 }).collect();
392
393 vec![parse_quote! {
394 impl shopify_function::wasm_api::Deserialize for #name_ident {
395 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
396 let typename = value.get_obj_prop("__typename");
397 let typename_str: ::std::string::String = shopify_function::wasm_api::Deserialize::deserialize(&typename)?;
398
399 match typename_str.as_str() {
400 #(#match_arms)*
401 _ => ::std::result::Result::Ok(Self::Other),
402 }
403 }
404 }
405 }]
406 }
407
408 fn additional_impls_for_enum(
409 &self,
410 enum_type_definition: &impl EnumTypeDefinition,
411 ) -> Vec<syn::ItemImpl> {
412 let name_ident = names::type_ident(enum_type_definition.name());
413
414 let from_str_match_arms: Vec<syn::Arm> = enum_type_definition
415 .enum_value_definitions()
416 .iter()
417 .map(|evd| {
418 let variant_name_ident = names::enum_variant_ident(evd.name());
419 let variant_name_lit_str = syn::LitStr::new(evd.name(), Span::mixed_site());
420
421 parse_quote! {
422 #variant_name_lit_str => Self::#variant_name_ident,
423 }
424 })
425 .collect();
426
427 let as_str_match_arms: Vec<syn::Arm> = enum_type_definition
428 .enum_value_definitions()
429 .iter()
430 .map(|evd| {
431 let variant_name_ident = names::enum_variant_ident(evd.name());
432 let variant_name_lit_str = syn::LitStr::new(evd.name(), Span::mixed_site());
433
434 parse_quote! {
435 Self::#variant_name_ident => #variant_name_lit_str,
436 }
437 })
438 .collect();
439
440 let non_trait_method_impls = parse_quote! {
441 impl #name_ident {
442 pub fn from_str(s: &str) -> Self {
443 match s {
444 #(#from_str_match_arms)*
445 _ => Self::Other,
446 }
447 }
448
449 fn as_str(&self) -> &::std::primitive::str {
450 match self {
451 #(#as_str_match_arms)*
452 Self::Other => panic!("Cannot serialize `Other` variant"),
453 }
454 }
455 }
456 };
457
458 let serialize_impl = parse_quote! {
459 impl shopify_function::wasm_api::Serialize for #name_ident {
460 fn serialize(&self, context: &mut shopify_function::wasm_api::Context) -> ::std::result::Result<(), shopify_function::wasm_api::write::Error> {
461 let str_value = self.as_str();
462 context.write_utf8_str(str_value)
463 }
464 }
465 };
466
467 let deserialize_impl = parse_quote! {
468 impl shopify_function::wasm_api::Deserialize for #name_ident {
469 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
470 let str_value: ::std::string::String = shopify_function::wasm_api::Deserialize::deserialize(value)?;
471
472 ::std::result::Result::Ok(Self::from_str(&str_value))
473 }
474 }
475 };
476
477 let display_impl = parse_quote! {
478 impl std::fmt::Display for #name_ident {
479 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480 write!(f, "{}", self.as_str())
481 }
482 }
483 };
484
485 vec![
486 non_trait_method_impls,
487 serialize_impl,
488 deserialize_impl,
489 display_impl,
490 ]
491 }
492
493 fn additional_impls_for_input_object(
494 &self,
495 #[allow(unused_variables)] input_object_type_definition: &impl InputObjectTypeDefinition,
496 ) -> Vec<syn::ItemImpl> {
497 let name_ident = names::type_ident(input_object_type_definition.name());
498
499 let field_statements: Vec<syn::Stmt> = input_object_type_definition
500 .input_field_definitions()
501 .iter()
502 .flat_map(|ivd| {
503 let field_name_ident = names::field_ident(ivd.name());
504 let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site());
505
506 vec![
507 parse_quote! {
508 context.write_utf8_str(#field_name_lit_str)?;
509 },
510 parse_quote! {
511 self.#field_name_ident.serialize(context)?;
512 },
513 ]
514 })
515 .collect();
516
517 let num_fields = input_object_type_definition.input_field_definitions().len();
518
519 let serialize_impl = parse_quote! {
520 impl shopify_function::wasm_api::Serialize for #name_ident {
521 fn serialize(&self, context: &mut shopify_function::wasm_api::Context) -> ::std::result::Result<(), shopify_function::wasm_api::write::Error> {
522 context.write_object(
523 |context| {
524 #(#field_statements)*
525 ::std::result::Result::Ok(())
526 },
527 #num_fields,
528 )
529 }
530 }
531 };
532
533 let field_values: Vec<syn::FieldValue> = input_object_type_definition
534 .input_field_definitions()
535 .iter()
536 .map(|ivd| {
537 let field_name_ident = names::field_ident(ivd.name());
538 let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site());
539 parse_quote! { #field_name_ident: shopify_function::wasm_api::Deserialize::deserialize(&value.get_obj_prop(#field_name_lit_str))? }
540 })
541 .collect();
542
543 let deserialize_impl = parse_quote! {
544 impl shopify_function::wasm_api::Deserialize for #name_ident {
545 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
546 ::std::result::Result::Ok(Self {
547 #(#field_values),*
548 })
549 }
550 }
551 };
552
553 vec![serialize_impl, deserialize_impl]
554 }
555
556 fn additional_impls_for_one_of_input_object(
557 &self,
558 input_object_type_definition: &impl InputObjectTypeDefinition,
559 ) -> Vec<syn::ItemImpl> {
560 let name_ident = names::type_ident(input_object_type_definition.name());
561
562 let match_arms: Vec<syn::Arm> = input_object_type_definition
563 .input_field_definitions()
564 .iter()
565 .map(|ivd| {
566 let variant_ident = names::enum_variant_ident(ivd.name());
567 let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site());
568
569 parse_quote! {
570 Self::#variant_ident(value) => {
571 context.write_utf8_str(#field_name_lit_str)?;
572 shopify_function::wasm_api::Serialize::serialize(value, context)?;
573 }
574 }
575 })
576 .collect();
577
578 let serialize_impl = parse_quote! {
579 impl shopify_function::wasm_api::Serialize for #name_ident {
580 fn serialize(&self, context: &mut shopify_function::wasm_api::Context) -> ::std::result::Result<(), shopify_function::wasm_api::write::Error> {
581 context.write_object(|context| {
582 match self {
583 #(#match_arms)*
584 }
585 ::std::result::Result::Ok(())
586 }, 1)
587 }
588 }
589 };
590
591 let deserialize_match_arms: Vec<syn::Arm> = input_object_type_definition
592 .input_field_definitions()
593 .iter()
594 .map(|ivd| {
595 let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site());
596 let variant_ident = names::enum_variant_ident(ivd.name());
597
598 parse_quote! {
599 #field_name_lit_str => {
600 let value = shopify_function::wasm_api::Deserialize::deserialize(&field_value)?;
601 ::std::result::Result::Ok(Self::#variant_ident(value))
602 }
603 }
604 })
605 .collect();
606
607 let deserialize_impl = parse_quote! {
608 impl shopify_function::wasm_api::Deserialize for #name_ident {
609 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
610 let ::std::option::Option::Some(obj_len) = value.obj_len() else {
611 return ::std::result::Result::Err(shopify_function::wasm_api::read::Error::InvalidType);
612 };
613
614 if obj_len != 1 {
615 return ::std::result::Result::Err(shopify_function::wasm_api::read::Error::InvalidType);
616 }
617
618 let ::std::option::Option::Some(field_name) = value.get_obj_key_at_index(0) else {
619 return ::std::result::Result::Err(shopify_function::wasm_api::read::Error::InvalidType);
620 };
621 let field_value = value.get_at_index(0);
622
623 match field_name.as_str() {
624 #(#deserialize_match_arms)*
625 _ => ::std::result::Result::Err(shopify_function::wasm_api::read::Error::InvalidType),
626 }
627 }
628 }
629 };
630
631 vec![serialize_impl, deserialize_impl]
632 }
633
634 fn attributes_for_enum(
635 &self,
636 _enum_type_definition: &impl EnumTypeDefinition,
637 ) -> Vec<syn::Attribute> {
638 vec![
639 parse_quote! { #[derive(::std::fmt::Debug, ::std::cmp::PartialEq, ::std::clone::Clone, ::std::marker::Copy)] },
640 ]
641 }
642
643 fn attributes_for_input_object(
644 &self,
645 _input_object_type_definition: &impl InputObjectTypeDefinition,
646 ) -> Vec<syn::Attribute> {
647 vec![
648 parse_quote! { #[derive(::std::fmt::Debug, ::std::cmp::PartialEq, ::std::clone::Clone)] },
649 ]
650 }
651
652 fn attributes_for_one_of_input_object(
653 &self,
654 _input_object_type_definition: &impl InputObjectTypeDefinition,
655 ) -> Vec<syn::Attribute> {
656 vec![
657 parse_quote! { #[derive(::std::fmt::Debug, ::std::cmp::PartialEq, ::std::clone::Clone)] },
658 ]
659 }
660}
661
662impl ShopifyFunctionCodeGenerator {
663 fn type_for_field(
664 executable_struct: &ExecutableStruct,
665 r#type: &WrappedExecutableType,
666 reference: bool,
667 ) -> syn::Type {
668 match r#type {
669 WrappedExecutableType::Base(base) => {
670 let base_type = executable_struct.compute_base_type(base);
671 if reference {
672 parse_quote! { &#base_type }
673 } else {
674 base_type
675 }
676 }
677 WrappedExecutableType::Optional(inner) => {
678 let inner_type = Self::type_for_field(executable_struct, inner, reference);
679 parse_quote! { ::std::option::Option<#inner_type> }
680 }
681 WrappedExecutableType::Vec(inner) => {
682 let inner_type = Self::type_for_field(executable_struct, inner, false);
683 if reference {
684 parse_quote! { &[#inner_type] }
685 } else {
686 parse_quote! { ::std::vec::Vec<#inner_type> }
687 }
688 }
689 }
690 }
691
692 fn reference_variable_for_type(
693 r#type: &WrappedExecutableType,
694 variable: &syn::Ident,
695 ) -> syn::Expr {
696 match r#type {
697 WrappedExecutableType::Base(_) => {
698 parse_quote! { #variable }
699 }
700 WrappedExecutableType::Vec(_) => {
701 parse_quote! { #variable.as_slice()}
702 }
703 WrappedExecutableType::Optional(inner) => {
704 let inner_variable = format_ident!("v_inner");
705 let inner_reference = Self::reference_variable_for_type(inner, &inner_variable);
706 parse_quote! { ::std::option::Option::as_ref(#variable).map(|#inner_variable| #inner_reference) }
707 }
708 }
709 }
710}
711
712#[proc_macro_derive(Deserialize, attributes(shopify_function))]
733pub fn derive_deserialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
734 let input = syn::parse_macro_input!(input as syn::DeriveInput);
735
736 derive_deserialize_for_derive_input(&input)
737 .map(|impl_item| impl_item.to_token_stream().into())
738 .unwrap_or_else(|error| error.to_compile_error().into())
739}
740
741#[derive(Default)]
742struct FieldAttributes {
743 rename: Option<String>,
744 has_default: bool,
745}
746
747fn parse_field_attributes(field: &syn::Field) -> syn::Result<FieldAttributes> {
748 let mut attributes = FieldAttributes::default();
749
750 for attr in field.attrs.iter() {
751 if attr.path().is_ident("shopify_function") {
752 attr.parse_nested_meta(|meta| {
753 if meta.path.is_ident("rename") {
754 attributes.rename = Some(meta.value()?.parse::<syn::LitStr>()?.value());
755 Ok(())
756 } else if meta.path.is_ident("default") {
757 attributes.has_default = true;
758 Ok(())
759 } else {
760 Err(meta.error("unrecognized field attribute"))
761 }
762 })?;
763 }
764 }
765
766 Ok(attributes)
767}
768
769fn derive_deserialize_for_derive_input(input: &syn::DeriveInput) -> syn::Result<syn::ItemImpl> {
770 match &input.data {
771 syn::Data::Struct(data) => match &data.fields {
772 syn::Fields::Named(fields) => {
773 let name_ident = &input.ident;
774
775 let mut rename_all: Option<syn::LitStr> = None;
776
777 for attr in input.attrs.iter() {
778 if attr.path().is_ident("shopify_function") {
779 attr.parse_nested_meta(|meta| {
780 if meta.path.is_ident("rename_all") {
781 rename_all = Some(meta.value()?.parse()?);
782 Ok(())
783 } else {
784 Err(meta.error("unrecognized repr"))
785 }
786 })?;
787 }
788 }
789
790 let case_style = match rename_all {
791 Some(rename_all) => match rename_all.value().as_str() {
792 "camelCase" => Some(Case::Camel),
793 "snake_case" => Some(Case::Snake),
794 "kebab-case" => Some(Case::Kebab),
795 _ => {
796 return Err(syn::Error::new_spanned(
797 rename_all,
798 "unrecognized rename_all",
799 ))
800 }
801 },
802 None => None,
803 };
804
805 let field_values: Vec<syn::FieldValue> = fields
806 .named
807 .iter()
808 .map(|field| {
809 let field_name_ident = field.ident.as_ref().expect("Named fields must have identifiers");
810
811 let field_attrs = parse_field_attributes(field)?;
812
813 let field_name_str = match field_attrs.rename {
814 Some(custom_name) => custom_name,
815 None => {
816 case_style.map_or_else(
818 || field_name_ident.to_string(),
819 |case_style| field_name_ident.to_string().to_case(case_style)
820 )
821 }
822 };
823
824 let field_name_lit_str = syn::LitStr::new(field_name_str.as_str(), Span::mixed_site());
825
826 if field_attrs.has_default {
827 Ok(parse_quote! {
832 #field_name_ident: {
833 let prop = value.get_obj_prop(#field_name_lit_str);
834 if prop.is_null() {
835 ::std::default::Default::default()
836 } else {
837 shopify_function::wasm_api::Deserialize::deserialize(&prop)?
838 }
839 }
840 })
841 } else {
842 Ok(parse_quote! {
844 #field_name_ident: shopify_function::wasm_api::Deserialize::deserialize(&value.get_obj_prop(#field_name_lit_str))?
845 })
846 }
847 })
848 .collect::<syn::Result<Vec<_>>>()?;
849
850 let deserialize_impl = parse_quote! {
851 impl shopify_function::wasm_api::Deserialize for #name_ident {
852 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
853 ::std::result::Result::Ok(Self {
854 #(#field_values),*
855 })
856 }
857 }
858 };
859
860 Ok(deserialize_impl)
861 }
862 syn::Fields::Unnamed(_) | syn::Fields::Unit => Err(syn::Error::new_spanned(
863 input,
864 "Structs must have named fields to derive `Deserialize`",
865 )),
866 },
867 syn::Data::Enum(_) => Err(syn::Error::new_spanned(
868 input,
869 "Enum types are not supported for deriving `Deserialize`",
870 )),
871 syn::Data::Union(_) => Err(syn::Error::new_spanned(
872 input,
873 "Union types are not supported for deriving `Deserialize`",
874 )),
875 }
876}