use std::collections::BTreeSet;
use cratestack_core::{Field, TypeArity};
use quote::quote;
use crate::shared::ident;
use super::scalar::{domain_from_wire_expr, scalar_wire, wire_from_domain_expr};
pub(super) struct PatchFieldPlan {
pub(super) prost_field: proc_macro2::TokenStream,
pub(super) from_domain_init: proc_macro2::TokenStream,
pub(super) try_from_wire_let: proc_macro2::TokenStream,
}
pub(super) fn render_patch_field(
owner: &str,
field: &Field,
number: i32,
enum_names: &BTreeSet<&str>,
) -> PatchFieldPlan {
let field_ident = ident(&field.name);
let field_name = field.name.as_str();
let type_name = field.ty.name.as_str();
let number_lit = proc_macro2::Literal::i32_unsuffixed(number);
let arity = field.ty.arity;
if let Some(wire) = scalar_wire(type_name) {
let rust_inner = &wire.rust_type;
let kind = &wire.prost_kind;
let to_domain = move |expr| domain_from_wire_expr(type_name, expr, owner, field_name);
let to_wire = move |expr| wire_from_domain_expr(type_name, expr);
render_patch_field_generic(
&field_ident,
number_lit,
arity,
quote! { #kind, optional },
quote! { #kind, repeated },
rust_inner.clone(),
to_domain,
to_wire,
)
} else if enum_names.contains(type_name) {
let enum_ident = ident(type_name);
let domain_enum_path = quote! { super::super::#enum_ident };
render_patch_field_generic(
&field_ident,
number_lit,
arity,
quote! { int32, optional },
quote! { int32, repeated },
quote! { i32 },
move |expr| {
quote! { <#domain_enum_path as ::core::convert::TryFrom<i32>>::try_from(#expr) }
},
move |expr| quote! { i32::from(&(#expr)) },
)
} else {
let message_ident = ident(type_name);
let domain_message_path = quote! { super::super::#message_ident };
render_patch_field_generic(
&field_ident,
number_lit,
arity,
quote! { message, optional, boxed },
quote! { message, repeated },
quote! { Box<#message_ident> },
move |expr| quote! { #domain_message_path::try_from(*(#expr)) },
move |expr| quote! { Box::new(#message_ident::from(&(#expr))) },
)
}
}
#[allow(clippy::too_many_arguments)]
fn render_patch_field_generic(
field_ident: &syn::Ident,
number_lit: proc_macro2::Literal,
arity: TypeArity,
optional_attr: proc_macro2::TokenStream,
repeated_attr: proc_macro2::TokenStream,
rust_inner: proc_macro2::TokenStream,
domain_expr: impl Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream,
to_wire_expr: impl Fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream,
) -> PatchFieldPlan {
if arity == TypeArity::List {
let to_domain = domain_expr(quote! { raw });
let to_wire = to_wire_expr(quote! { inner });
return PatchFieldPlan {
prost_field: quote! {
#[prost(#repeated_attr, tag = #number_lit)]
pub #field_ident: Vec<#rust_inner>,
},
from_domain_init: quote! {
#field_ident: value.#field_ident.clone().unwrap_or_default()
.into_iter()
.map(|inner| #to_wire)
.collect(),
},
try_from_wire_let: quote! {
let #field_ident = if value.#field_ident.is_empty() {
None
} else {
Some(value.#field_ident
.into_iter()
.map(|raw| -> ::core::result::Result<_, ::cratestack::CoolError> { #to_domain })
.collect::<::core::result::Result<Vec<_>, ::cratestack::CoolError>>()?)
};
},
};
}
let to_domain = domain_expr(quote! { raw });
let to_wire = to_wire_expr(quote! { inner });
let prost_field = quote! {
#[prost(#optional_attr, tag = #number_lit)]
pub #field_ident: Option<#rust_inner>,
};
if arity == TypeArity::Optional {
PatchFieldPlan {
prost_field,
from_domain_init: quote! {
#field_ident: value.#field_ident.clone().flatten().map(|inner| #to_wire),
},
try_from_wire_let: quote! {
let #field_ident = match value.#field_ident {
None => None,
Some(raw) => Some(Some(#to_domain?)),
};
},
}
} else {
PatchFieldPlan {
prost_field,
from_domain_init: quote! {
#field_ident: value.#field_ident.clone().map(|inner| #to_wire),
},
try_from_wire_let: quote! {
let #field_ident = value.#field_ident
.map(|raw| -> ::core::result::Result<_, ::cratestack::CoolError> { #to_domain })
.transpose()?;
},
}
}
}