use proc_macro_crate::{FoundCrate, crate_name};
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::{Attribute, Field, FieldsNamed, Token, Type, punctuated::Punctuated};
pub(crate) fn eventide_domain_path() -> TokenStream {
if let Ok(found) = crate_name("eventide") {
let name = match found {
FoundCrate::Itself => "eventide".to_string(),
FoundCrate::Name(name) => name,
};
let id = syn::Ident::new(&name, Span::call_site());
return quote!(::#id::domain);
}
if let Ok(found) = crate_name("eventide-domain") {
let name = match found {
FoundCrate::Itself => "eventide_domain".to_string(),
FoundCrate::Name(name) => name,
};
let id = syn::Ident::new(&name, Span::call_site());
return quote!(::#id);
}
quote!(::eventide_domain)
}
pub(crate) fn serde_path() -> TokenStream {
let domain = eventide_domain_path();
quote!(#domain::__serde)
}
pub(crate) fn serde_crate_attr() -> Attribute {
let path = serde_crate_string();
syn::parse_quote!(#[serde(crate = #path)])
}
fn serde_crate_string() -> String {
if let Ok(found) = crate_name("eventide") {
let name = match found {
FoundCrate::Itself => "eventide".to_string(),
FoundCrate::Name(name) => name,
};
return format!("::{}::domain::__serde", name);
}
if let Ok(found) = crate_name("eventide-domain") {
let name = match found {
FoundCrate::Itself => "eventide_domain".to_string(),
FoundCrate::Name(name) => name,
};
return format!("::{}::__serde", name);
}
"::eventide_domain::__serde".to_string()
}
pub(crate) fn split_derives(attrs: &[Attribute]) -> (Vec<Attribute>, Vec<syn::Path>) {
let mut retained = Vec::new();
let mut existing = Vec::new();
for attr in attrs.iter() {
if attr.path().is_ident("derive") {
if let Ok(list) = attr.parse_args_with(
syn::punctuated::Punctuated::<syn::Path, Token![,]>::parse_terminated,
) {
for p in list.into_iter() {
existing.push(p);
}
}
} else {
retained.push(attr.clone());
}
}
(retained, existing)
}
pub(crate) fn merge_derives(existing: Vec<syn::Path>, required: Vec<syn::Path>) -> Attribute {
let mut seen = std::collections::HashSet::<String>::new();
let mut final_list: Vec<syn::Path> = Vec::new();
let mut push_unique = |p: syn::Path| {
let key = derive_key(&p);
if seen.insert(key) {
final_list.push(p);
}
};
for p in required {
push_unique(p);
}
for p in existing {
push_unique(p);
}
syn::parse_quote!(#[derive(#(#final_list),*)])
}
pub(crate) fn derive_key(p: &syn::Path) -> String {
if let Some(last) = p.segments.last() {
let last_ident = last.ident.to_string();
match last_ident.as_str() {
"Serialize" | "Deserialize" => format!("serde::{}", last_ident),
_ => last_ident,
}
} else {
p.to_token_stream().to_string()
}
}
pub(crate) fn apply_derives(
attrs: &mut Vec<Attribute>,
required: Vec<syn::Path>,
helper_attrs: Vec<Attribute>,
) {
let (retained, existing) = split_derives(attrs);
let merged = merge_derives(existing, required);
*attrs = std::iter::once(merged)
.chain(helper_attrs)
.chain(retained)
.collect();
}
pub(crate) fn ensure_required_fields(
fields_named: &mut FieldsNamed,
required: &[(&str, &Type)],
reposition_existing: bool,
) {
let old_named = fields_named.named.clone();
let mut new_named: Punctuated<Field, Token![,]> = Punctuated::new();
if reposition_existing {
for (name, ty) in required.iter() {
if let Some(existing) = old_named
.iter()
.find(|f| f.ident.as_ref().map(|i| i == *name).unwrap_or(false))
{
new_named.push(existing.clone());
} else {
let ident: syn::Ident = syn::parse_str(name).expect("valid field ident");
let field: Field = syn::parse_quote! { #ident: #ty };
new_named.push(field);
}
}
for f in old_named.into_iter() {
let is_required = f
.ident
.as_ref()
.map(|i| required.iter().any(|(n, _)| i == n))
.unwrap_or(false);
if !is_required {
new_named.push(f);
}
}
} else {
for (name, ty) in required.iter() {
if !has_field_named_in(&old_named, name) {
let ident: syn::Ident = syn::parse_str(name).expect("valid field ident");
let field: Field = syn::parse_quote! { #ident: #ty };
new_named.push(field);
}
}
for f in old_named.into_iter() {
new_named.push(f);
}
}
fields_named.named = new_named;
}
fn has_field_named_in(named: &Punctuated<Field, Token![,]>, name: &str) -> bool {
named
.iter()
.any(|f| f.ident.as_ref().map(|i| i == name).unwrap_or(false))
}