use darling::{FromDeriveInput, FromField, FromVariant};
use proc_macro2::Span;
use syn::{parse_quote, Arm, DeriveInput, Error, Expr, Ident, ItemImpl, Path, Result};
fn try_from_proto_buf_struct(
self_ident: Path,
option_all: bool,
proto_buf_type: Path,
fields: darling::ast::Fields<TryFromProtoBufStructField>,
) -> Result<ItemImpl> {
let field_idents = fields
.fields
.iter()
.map(|f| {
f.ident
.as_ref()
.ok_or_else(|| Error::new(Span::call_site(), "named fields required"))
})
.collect::<Result<Vec<_>>>()?;
let exprs = fields.fields.iter().map(|f| -> Expr {
let field = f.ident.as_ref().unwrap();
if f.option || option_all {
let field_name = field.to_string();
parse_quote! {
crate::TryFromProtoBuf::try_from_proto_buf(
p.#field.ok_or(::anyhow::anyhow!("missing field {}", #field_name))?
)?
}
} else if f.default {
parse_quote! {
if let ::std::option::Option::Some(v) = p.#field {
crate::TryFromProtoBuf::try_from_proto_buf(v)?
} else {
::std::default::Default::default()
}
}
} else {
parse_quote! {
crate::TryFromProtoBuf::try_from_proto_buf(p.#field)?
}
}
});
Ok(parse_quote! {
impl crate::TryFromProtoBuf for #self_ident {
type ProtoBufType = #proto_buf_type;
fn try_from_proto_buf(p: Self::ProtoBufType) -> ::anyhow::Result<Self> {
Ok(Self {
#(#field_idents: #exprs),*
})
}
}
})
}
fn try_from_proto_buf_struct_try_from_into(
self_ident: Path,
proto_buf_type: Path,
) -> Result<ItemImpl> {
Ok(parse_quote! {
impl crate::TryFromProtoBuf for #self_ident {
type ProtoBufType = #proto_buf_type;
fn try_from_proto_buf(p: Self::ProtoBufType) -> ::anyhow::Result<Self> {
::std::result::Result::map_err(
::std::convert::TryFrom::try_from(p),
|_| {
::anyhow::anyhow!(
::std::concat!("malformed ", ::std::stringify!(#self_ident))
)
}
)
}
}
})
}
fn try_from_proto_buf_unit_enum(
self_path: Path,
proto_buf_type: Path,
variants: Vec<TryFromProtoBufEnumVariant>,
) -> Result<ItemImpl> {
let arms = variants.iter().map(|v| -> Arm {
let variant_ident = &v.ident;
parse_quote! {
Ok(#proto_buf_type::#variant_ident) => Ok(Self::#variant_ident)
}
});
let self_name = self_path.segments.last().unwrap().ident.to_string();
Ok(parse_quote! {
impl crate::TryFromProtoBuf for #self_path {
type ProtoBufType = i32;
fn try_from_proto_buf(p: Self::ProtoBufType) -> ::anyhow::Result<Self> {
match #proto_buf_type::try_from(p) {
#(#arms,)*
_ => Err(::anyhow::anyhow!("malformed `{}`", #self_name))
}
}
}
})
}
fn try_from_proto_buf_enum(
self_path: Path,
proto_buf_type: Path,
enum_type: Path,
variants: Vec<TryFromProtoBufEnumVariant>,
) -> Result<ItemImpl> {
let self_name = self_path.segments.last().unwrap().ident.to_string();
let arms = variants
.iter()
.map(|v| -> Result<Arm> {
let variant_ident = &v.ident;
Ok(match v.fields.style {
darling::ast::Style::Unit => {
parse_quote! {
#enum_type::#variant_ident(_) => Ok(Self::#variant_ident)
}
}
darling::ast::Style::Tuple if v.fields.len() == 1 => {
parse_quote! {
#enum_type::#variant_ident(f) => Ok(Self::#variant_ident(
crate::TryFromProtoBuf::try_from_proto_buf(f)?
))
}
}
darling::ast::Style::Tuple => {
let variant_type = v
.proto_buf_type
.as_ref()
.ok_or(Error::new(variant_ident.span(), "missing path_type"))?;
let num_fields = v.fields.len();
let field_idents1 =
(0..num_fields).map(|n| Ident::new(&format!("f{n}"), Span::call_site()));
let field_idents2 = field_idents1.clone();
parse_quote! {
#enum_type::#variant_ident(v) => {
let (#(#field_idents1),*) =
<_ as ::std::convert::From<#variant_type>>::from(v);
Ok(Self::#variant_ident(
#(crate::TryFromProtoBuf::try_from_proto_buf(#field_idents2)?),*
))
}
}
}
darling::ast::Style::Struct => {
let field_idents1 = v.fields.iter().map(|f| &f.ident);
let field_idents2 = field_idents1.clone();
let variant_type = v
.proto_buf_type
.as_ref()
.ok_or(Error::new(variant_ident.span(), "missing path_type"))?;
let field_exprs = v.fields.iter().map(|v| -> Expr {
let ident = &v.ident;
if v.option {
parse_quote! {
crate::TryFromProtoBuf::try_from_proto_buf(
#ident.ok_or(::anyhow::anyhow!("malformed `{}`", #self_name))?
)?
}
} else if v.default {
parse_quote! {
if let ::std::option::Option::Some(v) = #ident {
crate::TryFromProtoBuf::try_from_proto_buf(v)?
} else {
::std::default::Default::default()
}
}
} else {
parse_quote! { crate::TryFromProtoBuf::try_from_proto_buf(#ident)? }
}
});
parse_quote! {
#enum_type::#variant_ident(#variant_type { #(#field_idents1),* }) => {
Ok(Self::#variant_ident {
#(#field_idents2: #field_exprs),*
})
}
}
}
})
})
.collect::<Result<Vec<Arm>>>()?;
Ok(parse_quote! {
impl crate::TryFromProtoBuf for #self_path {
type ProtoBufType = #proto_buf_type;
fn try_from_proto_buf(p: Self::ProtoBufType) -> ::anyhow::Result<Self> {
match <#enum_type as ::std::convert::TryFrom<#proto_buf_type>>::try_from(p)? {
#(#arms,)*
_ => Err(::anyhow::anyhow!("malformed `{}`", #self_name))
}
}
}
})
}
pub fn main(input: DeriveInput) -> Result<ItemImpl> {
let input = TryFromProtoBufInput::from_derive_input(&input)?;
let self_path = input.ident.into();
let proto_buf_type = input.proto_buf_type;
match input.data {
darling::ast::Data::Struct(fields) => {
if input.try_from_into {
try_from_proto_buf_struct_try_from_into(self_path, proto_buf_type)
} else {
try_from_proto_buf_struct(self_path, input.option_all, proto_buf_type, fields)
}
}
darling::ast::Data::Enum(variants) => {
let all_unit = variants
.iter()
.all(|f| f.fields.style == darling::ast::Style::Unit);
if all_unit {
try_from_proto_buf_unit_enum(self_path, proto_buf_type, variants)
} else {
let enum_type = input.enum_type.unwrap_or(proto_buf_type.clone());
try_from_proto_buf_enum(self_path, proto_buf_type, enum_type, variants)
}
}
}
}
#[derive(Clone, Debug, FromField)]
#[darling(attributes(proto))]
struct TryFromProtoBufStructField {
ident: Option<Ident>,
#[darling(default)]
option: bool,
#[darling(default)]
default: bool,
}
#[derive(Clone, Debug, FromVariant)]
#[darling(attributes(proto))]
struct TryFromProtoBufEnumVariant {
ident: Ident,
fields: darling::ast::Fields<TryFromProtoBufStructField>,
proto_buf_type: Option<Path>,
}
#[derive(Clone, Debug, FromDeriveInput)]
#[darling(supports(struct_any, enum_any))]
#[darling(attributes(proto))]
struct TryFromProtoBufInput {
ident: Ident,
data: darling::ast::Data<TryFromProtoBufEnumVariant, TryFromProtoBufStructField>,
proto_buf_type: Path,
enum_type: Option<Path>,
#[darling(default)]
option_all: bool,
#[darling(default)]
try_from_into: bool,
}