From a8dec3bab538c7b2491a503f928ca1de9f025188 Mon Sep 17 00:00:00 2001 From: Valery Klachkov Date: Thu, 31 Oct 2024 13:58:11 +0000 Subject: [PATCH] Support fields aliases --- crates/jrsonnet-macros/src/lib.rs | 84 ++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/crates/jrsonnet-macros/src/lib.rs b/crates/jrsonnet-macros/src/lib.rs index 05e3ea19..b2e1d894 100644 --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -96,6 +96,7 @@ impl Parse for Field { mod kw { syn::custom_keyword!(fields); syn::custom_keyword!(rename); + syn::custom_keyword!(alias); syn::custom_keyword!(flatten); syn::custom_keyword!(add); syn::custom_keyword!(hide); @@ -412,6 +413,7 @@ fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result, + aliases: Vec, flatten: bool, /// flatten(ok) strategy for flattened optionals /// field would be None in case of any parsing error (as in serde) @@ -437,6 +439,11 @@ impl Parse for TypedAttr { )); } out.rename = Some(name.value()); + } else if lookahead.peek(kw::alias) { + input.parse::()?; + input.parse::()?; + let alias = input.parse::()?; + out.aliases.push(alias.value()); } else if lookahead.peek(kw::flatten) { input.parse::()?; out.flatten = true; @@ -538,33 +545,71 @@ impl TypedField { }) } fn expand_parse(&self) -> TokenStream { + if self.is_option { + self.expand_parse_optional() + } else { + self.expand_parse_mandatory() + } + } + + fn expand_parse_optional(&self) -> TokenStream { let ident = &self.ident; let ty = &self.ty; + + // optional flatten is handled in same way as serde if self.attr.flatten { - // optional flatten is handled in same way as serde - return if self.is_option { - quote! { - #ident: <#ty as TypedObj>::parse(&obj).ok(), - } - } else { - quote! { - #ident: <#ty as TypedObj>::parse(&obj)?, - } + return quote! { + #ident: <#ty as TypedObj>::parse(&obj).ok(), }; - }; + } let name = self.name().unwrap(); - let value = if self.is_option { - quote! { - if let Some(value) = obj.get(#name.into())? { - Some(<#ty as Typed>::from_untyped(value)?) - } else { - None - } + let aliases = &self.attr.aliases; + + let value = quote! { + if let Some(__value) = obj.get(#name.into())? { + Some(<#ty as Typed>::from_untyped(__value)?) + } #(else if let Some(__value) = obj.get(#aliases) { + Some(<#ty as Typed>::from_untyped(__value)?) + })* else { + None } + }; + + quote! { + #ident: #value, + } + } + + fn expand_parse_mandatory(&self) -> TokenStream { + let ident = &self.ident; + let ty = &self.ty; + + // optional flatten is handled in same way as serde + if self.attr.flatten { + return quote! { + #ident: <#ty as TypedObj>::parse(&obj)?, + }; + } + + let name = self.name().unwrap(); + let aliases = &self.attr.aliases; + + let error_text = if aliases.is_empty() { + // clippy does not understand name variable usage in quote! macro + #[allow(clippy::redundant_clone)] + name.clone() } else { - quote! { - <#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)? + format!("{name} (alias {})", aliases.join(", ")) + }; + + let value = quote! { + if let Some(__value) = obj.get(#name.into())? { + <#ty as Typed>::from_untyped(__value)? + } #(else if let Some(__value) = obj.get(#aliases.into())? { + <#ty as Typed>::from_untyped(__value)? + })* else { + return Err(ErrorKind::NoSuchField(#error_text.into(), vec![]).into()); } }; @@ -572,6 +617,7 @@ impl TypedField { #ident: #value, } } + fn expand_serialize(&self) -> TokenStream { let ident = &self.ident; let ty = &self.ty;