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 let value = self.#field_name_ident.get_or_init(|| {
353 let value = self.__wasm_value.get_obj_prop(#field_name_lit_str);
354 shopify_function::wasm_api::Deserialize::deserialize(&value).unwrap()
355 });
356 let value_ref = &value;
357 #properly_referenced_value
358 }
359 }
360 })
361 .collect();
362
363 let accessor_impl = parse_quote! {
364 impl #name_ident {
365 #(#accessors)*
366 }
367 };
368
369 vec![deserialize_impl, accessor_impl]
370 }
371
372 fn additional_impls_for_executable_enum(
373 &self,
374 executable_enum: &bluejay_typegen_codegen::ExecutableEnum,
375 ) -> Vec<syn::ItemImpl> {
376 let name_ident = names::type_ident(executable_enum.parent_name());
377
378 let match_arms: Vec<syn::Arm> = executable_enum
379 .variants()
380 .iter()
381 .map(|variant| {
382 let variant_name_ident = names::enum_variant_ident(variant.parent_name());
383 let variant_name_lit_str = syn::LitStr::new(variant.parent_name(), Span::mixed_site());
384
385 parse_quote! {
386 #variant_name_lit_str => shopify_function::wasm_api::Deserialize::deserialize(value).map(Self::#variant_name_ident),
387 }
388 }).collect();
389
390 vec![parse_quote! {
391 impl shopify_function::wasm_api::Deserialize for #name_ident {
392 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
393 let typename = value.get_obj_prop("__typename");
394 let typename_str: ::std::string::String = shopify_function::wasm_api::Deserialize::deserialize(&typename)?;
395
396 match typename_str.as_str() {
397 #(#match_arms)*
398 _ => ::std::result::Result::Ok(Self::Other),
399 }
400 }
401 }
402 }]
403 }
404
405 fn additional_impls_for_enum(
406 &self,
407 enum_type_definition: &impl EnumTypeDefinition,
408 ) -> Vec<syn::ItemImpl> {
409 let name_ident = names::type_ident(enum_type_definition.name());
410
411 let from_str_match_arms: Vec<syn::Arm> = enum_type_definition
412 .enum_value_definitions()
413 .iter()
414 .map(|evd| {
415 let variant_name_ident = names::enum_variant_ident(evd.name());
416 let variant_name_lit_str = syn::LitStr::new(evd.name(), Span::mixed_site());
417
418 parse_quote! {
419 #variant_name_lit_str => Self::#variant_name_ident,
420 }
421 })
422 .collect();
423
424 let as_str_match_arms: Vec<syn::Arm> = enum_type_definition
425 .enum_value_definitions()
426 .iter()
427 .map(|evd| {
428 let variant_name_ident = names::enum_variant_ident(evd.name());
429 let variant_name_lit_str = syn::LitStr::new(evd.name(), Span::mixed_site());
430
431 parse_quote! {
432 Self::#variant_name_ident => #variant_name_lit_str,
433 }
434 })
435 .collect();
436
437 let non_trait_method_impls = parse_quote! {
438 impl #name_ident {
439 pub fn from_str(s: &str) -> Self {
440 match s {
441 #(#from_str_match_arms)*
442 _ => Self::Other,
443 }
444 }
445
446 fn as_str(&self) -> &::std::primitive::str {
447 match self {
448 #(#as_str_match_arms)*
449 Self::Other => panic!("Cannot serialize `Other` variant"),
450 }
451 }
452 }
453 };
454
455 let serialize_impl = parse_quote! {
456 impl shopify_function::wasm_api::Serialize for #name_ident {
457 fn serialize(&self, context: &mut shopify_function::wasm_api::Context) -> ::std::result::Result<(), shopify_function::wasm_api::write::Error> {
458 let str_value = self.as_str();
459 context.write_utf8_str(str_value)
460 }
461 }
462 };
463
464 let deserialize_impl = parse_quote! {
465 impl shopify_function::wasm_api::Deserialize for #name_ident {
466 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
467 let str_value: ::std::string::String = shopify_function::wasm_api::Deserialize::deserialize(value)?;
468
469 ::std::result::Result::Ok(Self::from_str(&str_value))
470 }
471 }
472 };
473
474 let display_impl = parse_quote! {
475 impl std::fmt::Display for #name_ident {
476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477 write!(f, "{}", self.as_str())
478 }
479 }
480 };
481
482 vec![
483 non_trait_method_impls,
484 serialize_impl,
485 deserialize_impl,
486 display_impl,
487 ]
488 }
489
490 fn additional_impls_for_input_object(
491 &self,
492 #[allow(unused_variables)] input_object_type_definition: &impl InputObjectTypeDefinition,
493 ) -> Vec<syn::ItemImpl> {
494 let name_ident = names::type_ident(input_object_type_definition.name());
495
496 let field_statements: Vec<syn::Stmt> = input_object_type_definition
497 .input_field_definitions()
498 .iter()
499 .flat_map(|ivd| {
500 let field_name_ident = names::field_ident(ivd.name());
501 let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site());
502
503 vec![
504 parse_quote! {
505 context.write_utf8_str(#field_name_lit_str)?;
506 },
507 parse_quote! {
508 self.#field_name_ident.serialize(context)?;
509 },
510 ]
511 })
512 .collect();
513
514 let num_fields = input_object_type_definition.input_field_definitions().len();
515
516 let serialize_impl = parse_quote! {
517 impl shopify_function::wasm_api::Serialize for #name_ident {
518 fn serialize(&self, context: &mut shopify_function::wasm_api::Context) -> ::std::result::Result<(), shopify_function::wasm_api::write::Error> {
519 context.write_object(
520 |context| {
521 #(#field_statements)*
522 ::std::result::Result::Ok(())
523 },
524 #num_fields,
525 )
526 }
527 }
528 };
529
530 let field_values: Vec<syn::FieldValue> = input_object_type_definition
531 .input_field_definitions()
532 .iter()
533 .map(|ivd| {
534 let field_name_ident = names::field_ident(ivd.name());
535 let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site());
536 parse_quote! { #field_name_ident: shopify_function::wasm_api::Deserialize::deserialize(&value.get_obj_prop(#field_name_lit_str))? }
537 })
538 .collect();
539
540 let deserialize_impl = parse_quote! {
541 impl shopify_function::wasm_api::Deserialize for #name_ident {
542 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
543 ::std::result::Result::Ok(Self {
544 #(#field_values),*
545 })
546 }
547 }
548 };
549
550 vec![serialize_impl, deserialize_impl]
551 }
552
553 fn additional_impls_for_one_of_input_object(
554 &self,
555 input_object_type_definition: &impl InputObjectTypeDefinition,
556 ) -> Vec<syn::ItemImpl> {
557 let name_ident = names::type_ident(input_object_type_definition.name());
558
559 let match_arms: Vec<syn::Arm> = input_object_type_definition
560 .input_field_definitions()
561 .iter()
562 .map(|ivd| {
563 let variant_ident = names::enum_variant_ident(ivd.name());
564 let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site());
565
566 parse_quote! {
567 Self::#variant_ident(value) => {
568 context.write_utf8_str(#field_name_lit_str)?;
569 shopify_function::wasm_api::Serialize::serialize(value, context)?;
570 }
571 }
572 })
573 .collect();
574
575 let serialize_impl = parse_quote! {
576 impl shopify_function::wasm_api::Serialize for #name_ident {
577 fn serialize(&self, context: &mut shopify_function::wasm_api::Context) -> ::std::result::Result<(), shopify_function::wasm_api::write::Error> {
578 context.write_object(|context| {
579 match self {
580 #(#match_arms)*
581 }
582 ::std::result::Result::Ok(())
583 }, 1)
584 }
585 }
586 };
587
588 let deserialize_match_arms: Vec<syn::Arm> = input_object_type_definition
589 .input_field_definitions()
590 .iter()
591 .map(|ivd| {
592 let field_name_lit_str = syn::LitStr::new(ivd.name(), Span::mixed_site());
593 let variant_ident = names::enum_variant_ident(ivd.name());
594
595 parse_quote! {
596 #field_name_lit_str => {
597 let value = shopify_function::wasm_api::Deserialize::deserialize(&field_value)?;
598 ::std::result::Result::Ok(Self::#variant_ident(value))
599 }
600 }
601 })
602 .collect();
603
604 let deserialize_impl = parse_quote! {
605 impl shopify_function::wasm_api::Deserialize for #name_ident {
606 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
607 let ::std::option::Option::Some(obj_len) = value.obj_len() else {
608 return ::std::result::Result::Err(shopify_function::wasm_api::read::Error::InvalidType);
609 };
610
611 if obj_len != 1 {
612 return ::std::result::Result::Err(shopify_function::wasm_api::read::Error::InvalidType);
613 }
614
615 let ::std::option::Option::Some(field_name) = value.get_obj_key_at_index(0) else {
616 return ::std::result::Result::Err(shopify_function::wasm_api::read::Error::InvalidType);
617 };
618 let field_value = value.get_at_index(0);
619
620 match field_name.as_str() {
621 #(#deserialize_match_arms)*
622 _ => ::std::result::Result::Err(shopify_function::wasm_api::read::Error::InvalidType),
623 }
624 }
625 }
626 };
627
628 vec![serialize_impl, deserialize_impl]
629 }
630
631 fn attributes_for_enum(
632 &self,
633 _enum_type_definition: &impl EnumTypeDefinition,
634 ) -> Vec<syn::Attribute> {
635 vec![
636 parse_quote! { #[derive(::std::fmt::Debug, ::std::cmp::PartialEq, ::std::clone::Clone, ::std::marker::Copy)] },
637 ]
638 }
639
640 fn attributes_for_input_object(
641 &self,
642 _input_object_type_definition: &impl InputObjectTypeDefinition,
643 ) -> Vec<syn::Attribute> {
644 vec![
645 parse_quote! { #[derive(::std::fmt::Debug, ::std::cmp::PartialEq, ::std::clone::Clone)] },
646 ]
647 }
648
649 fn attributes_for_one_of_input_object(
650 &self,
651 _input_object_type_definition: &impl InputObjectTypeDefinition,
652 ) -> Vec<syn::Attribute> {
653 vec![
654 parse_quote! { #[derive(::std::fmt::Debug, ::std::cmp::PartialEq, ::std::clone::Clone)] },
655 ]
656 }
657}
658
659impl ShopifyFunctionCodeGenerator {
660 fn type_for_field(
661 executable_struct: &ExecutableStruct,
662 r#type: &WrappedExecutableType,
663 reference: bool,
664 ) -> syn::Type {
665 match r#type {
666 WrappedExecutableType::Base(base) => {
667 let base_type = executable_struct.compute_base_type(base);
668 if reference {
669 parse_quote! { &#base_type }
670 } else {
671 base_type
672 }
673 }
674 WrappedExecutableType::Optional(inner) => {
675 let inner_type = Self::type_for_field(executable_struct, inner, reference);
676 parse_quote! { ::std::option::Option<#inner_type> }
677 }
678 WrappedExecutableType::Vec(inner) => {
679 let inner_type = Self::type_for_field(executable_struct, inner, false);
680 if reference {
681 parse_quote! { &[#inner_type] }
682 } else {
683 parse_quote! { ::std::vec::Vec<#inner_type> }
684 }
685 }
686 }
687 }
688
689 fn reference_variable_for_type(
690 r#type: &WrappedExecutableType,
691 variable: &syn::Ident,
692 ) -> syn::Expr {
693 match r#type {
694 WrappedExecutableType::Base(_) => {
695 parse_quote! { #variable }
696 }
697 WrappedExecutableType::Vec(_) => {
698 parse_quote! { #variable.as_slice()}
699 }
700 WrappedExecutableType::Optional(inner) => {
701 let inner_variable = format_ident!("v_inner");
702 let inner_reference = Self::reference_variable_for_type(inner, &inner_variable);
703 parse_quote! { ::std::option::Option::as_ref(#variable).map(|#inner_variable| #inner_reference) }
704 }
705 }
706 }
707}
708
709#[proc_macro_derive(Deserialize, attributes(shopify_function))]
730pub fn derive_deserialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
731 let input = syn::parse_macro_input!(input as syn::DeriveInput);
732
733 derive_deserialize_for_derive_input(&input)
734 .map(|impl_item| impl_item.to_token_stream().into())
735 .unwrap_or_else(|error| error.to_compile_error().into())
736}
737
738#[derive(Default)]
739struct FieldAttributes {
740 rename: Option<String>,
741 has_default: bool,
742}
743
744fn parse_field_attributes(field: &syn::Field) -> syn::Result<FieldAttributes> {
745 let mut attributes = FieldAttributes::default();
746
747 for attr in field.attrs.iter() {
748 if attr.path().is_ident("shopify_function") {
749 attr.parse_nested_meta(|meta| {
750 if meta.path.is_ident("rename") {
751 attributes.rename = Some(meta.value()?.parse::<syn::LitStr>()?.value());
752 Ok(())
753 } else if meta.path.is_ident("default") {
754 attributes.has_default = true;
755 Ok(())
756 } else {
757 Err(meta.error("unrecognized field attribute"))
758 }
759 })?;
760 }
761 }
762
763 Ok(attributes)
764}
765
766fn derive_deserialize_for_derive_input(input: &syn::DeriveInput) -> syn::Result<syn::ItemImpl> {
767 match &input.data {
768 syn::Data::Struct(data) => match &data.fields {
769 syn::Fields::Named(fields) => {
770 let name_ident = &input.ident;
771
772 let mut rename_all: Option<syn::LitStr> = None;
773
774 for attr in input.attrs.iter() {
775 if attr.path().is_ident("shopify_function") {
776 attr.parse_nested_meta(|meta| {
777 if meta.path.is_ident("rename_all") {
778 rename_all = Some(meta.value()?.parse()?);
779 Ok(())
780 } else {
781 Err(meta.error("unrecognized repr"))
782 }
783 })?;
784 }
785 }
786
787 let case_style = match rename_all {
788 Some(rename_all) => match rename_all.value().as_str() {
789 "camelCase" => Some(Case::Camel),
790 "snake_case" => Some(Case::Snake),
791 "kebab-case" => Some(Case::Kebab),
792 _ => {
793 return Err(syn::Error::new_spanned(
794 rename_all,
795 "unrecognized rename_all",
796 ))
797 }
798 },
799 None => None,
800 };
801
802 let field_values: Vec<syn::FieldValue> = fields
803 .named
804 .iter()
805 .map(|field| {
806 let field_name_ident = field.ident.as_ref().expect("Named fields must have identifiers");
807
808 let field_attrs = parse_field_attributes(field)?;
809
810 let field_name_str = match field_attrs.rename {
811 Some(custom_name) => custom_name,
812 None => {
813 case_style.map_or_else(
815 || field_name_ident.to_string(),
816 |case_style| field_name_ident.to_string().to_case(case_style)
817 )
818 }
819 };
820
821 let field_name_lit_str = syn::LitStr::new(field_name_str.as_str(), Span::mixed_site());
822
823 if field_attrs.has_default {
824 Ok(parse_quote! {
829 #field_name_ident: {
830 let prop = value.get_obj_prop(#field_name_lit_str);
831 if prop.is_null() {
832 ::std::default::Default::default()
833 } else {
834 shopify_function::wasm_api::Deserialize::deserialize(&prop)?
835 }
836 }
837 })
838 } else {
839 Ok(parse_quote! {
841 #field_name_ident: shopify_function::wasm_api::Deserialize::deserialize(&value.get_obj_prop(#field_name_lit_str))?
842 })
843 }
844 })
845 .collect::<syn::Result<Vec<_>>>()?;
846
847 let deserialize_impl = parse_quote! {
848 impl shopify_function::wasm_api::Deserialize for #name_ident {
849 fn deserialize(value: &shopify_function::wasm_api::Value) -> ::std::result::Result<Self, shopify_function::wasm_api::read::Error> {
850 ::std::result::Result::Ok(Self {
851 #(#field_values),*
852 })
853 }
854 }
855 };
856
857 Ok(deserialize_impl)
858 }
859 syn::Fields::Unnamed(_) | syn::Fields::Unit => Err(syn::Error::new_spanned(
860 input,
861 "Structs must have named fields to derive `Deserialize`",
862 )),
863 },
864 syn::Data::Enum(_) => Err(syn::Error::new_spanned(
865 input,
866 "Enum types are not supported for deriving `Deserialize`",
867 )),
868 syn::Data::Union(_) => Err(syn::Error::new_spanned(
869 input,
870 "Union types are not supported for deriving `Deserialize`",
871 )),
872 }
873}