Skip to content

Commit

Permalink
Support fields aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
vklachkov committed Oct 31, 2024
1 parent 7160d47 commit a8dec3b
Showing 1 changed file with 65 additions and 19 deletions.
84 changes: 65 additions & 19 deletions crates/jrsonnet-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -412,6 +413,7 @@ fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream
#[allow(clippy::struct_excessive_bools)]
struct TypedAttr {
rename: Option<String>,
aliases: Vec<String>,
flatten: bool,
/// flatten(ok) strategy for flattened optionals
/// field would be None in case of any parsing error (as in serde)
Expand All @@ -437,6 +439,11 @@ impl Parse for TypedAttr {
));
}
out.rename = Some(name.value());
} else if lookahead.peek(kw::alias) {
input.parse::<kw::alias>()?;
input.parse::<Token![=]>()?;
let alias = input.parse::<LitStr>()?;
out.aliases.push(alias.value());
} else if lookahead.peek(kw::flatten) {
input.parse::<kw::flatten>()?;
out.flatten = true;
Expand Down Expand Up @@ -538,40 +545,79 @@ 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());
}
};

quote! {
#ident: #value,
}
}

fn expand_serialize(&self) -> TokenStream {
let ident = &self.ident;
let ty = &self.ty;
Expand Down

0 comments on commit a8dec3b

Please sign in to comment.