mod collection_tokens;
mod guards;
mod merge_tokens;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::derive::build::CollectionStrategies;
use crate::derive::generate::generated_doc;
use collection_tokens::{append_collection_tokens, map_collection_tokens};
use merge_tokens::{FinishCollectionTokens, finish_tokens, merge_layer_tokens};
pub(crate) use collection_tokens::unique_append_fields;
pub(crate) fn generate_declarative_state_struct(
state_ident: &syn::Ident,
config_ident: &syn::Ident,
strategies: &CollectionStrategies,
krate: &TokenStream,
) -> TokenStream {
let unique_fields = unique_append_fields(&strategies.append);
let append_state_fields = unique_fields.iter().map(|(name, _ty)| {
let state_field_ident = format_ident!("append_{}", name);
quote! {
#state_field_ident: Option<Vec<#krate::serde_json::Value>>
}
});
let map_replace_fields = strategies.map_replace.iter().map(|(name, _ty)| {
let state_field_ident = format_ident!("replace_{}", name);
quote! {
#state_field_ident: Option<#krate::serde_json::Value>
}
});
let doc = generated_doc(config_ident, "Declarative merge state");
quote! {
#[doc = #doc]
#[derive(Default)]
struct #state_ident {
value: #krate::serde_json::Value,
#( #append_state_fields, )*
#( #map_replace_fields, )*
}
}
}
pub(crate) fn generate_declarative_merge_impl(
state_ident: &syn::Ident,
config_ident: &syn::Ident,
strategies: &CollectionStrategies,
krate: &TokenStream,
) -> TokenStream {
let append_tokens = append_collection_tokens(strategies, krate);
let map_tokens = map_collection_tokens(strategies);
let collection_tokens::CollectionTokens {
merge_logic: append_merge_logic,
destructured: append_destructured,
inserts: append_inserts,
} = append_tokens;
let collection_tokens::CollectionTokens {
merge_logic: map_merge_logic,
destructured: map_destructured,
inserts: map_inserts,
} = map_tokens;
let merge_layer_body =
merge_layer_tokens(config_ident, krate, &map_merge_logic, &append_merge_logic);
let finish_body = finish_tokens(
state_ident,
krate,
&FinishCollectionTokens {
append_destructured: &append_destructured,
map_destructured: &map_destructured,
append_inserts: &append_inserts,
map_inserts: &map_inserts,
},
);
quote! {
impl #krate::DeclarativeMerge for #state_ident {
type Output = #config_ident;
fn merge_layer(&mut self, layer: #krate::MergeLayer<'_>) -> #krate::OrthoResult<()> {
#merge_layer_body
}
fn finish(self) -> #krate::OrthoResult<Self::Output> {
#finish_body
}
}
}
}
pub(crate) fn generate_declarative_merge_from_layers_fn(
state_ident: &syn::Ident,
config_ident: &syn::Ident,
has_post_merge_hook: bool,
krate: &TokenStream,
) -> TokenStream {
let ctx_init = if has_post_merge_hook {
quote! {
let mut ctx = #krate::PostMergeContext::new(Self::prefix());
}
} else {
quote! {}
};
let ctx_per_layer = if has_post_merge_hook {
quote! {
if let Some(path) = layer.path() {
ctx.with_file(path.to_owned());
}
if layer.provenance() == #krate::MergeProvenance::Cli {
ctx.with_cli_input();
}
}
} else {
quote! {}
};
let post_merge = if has_post_merge_hook {
quote! {
let mut result = #krate::DeclarativeMerge::finish(state)?;
#krate::PostMergeHook::post_merge(&mut result, &ctx)?;
Ok(result)
}
} else {
quote! {
#krate::DeclarativeMerge::finish(state)
}
};
let merge_body = quote! {
let mut state = #state_ident::default();
#ctx_init
for layer in layers {
#ctx_per_layer
#krate::DeclarativeMerge::merge_layer(&mut state, layer)?;
}
#post_merge
};
quote! {
impl #config_ident {
pub fn merge_from_layers<'a, I>(layers: I) -> #krate::OrthoResult<Self>
where
I: IntoIterator<Item = #krate::MergeLayer<'a>>,
{
#merge_body
}
}
}
}
pub(crate) fn generate_declarative_impl(
config_ident: &syn::Ident,
strategies: &CollectionStrategies,
has_post_merge_hook: bool,
krate: &TokenStream,
) -> TokenStream {
let state_ident = format_ident!("__{}DeclarativeMergeState", config_ident);
let state_struct =
generate_declarative_state_struct(&state_ident, config_ident, strategies, krate);
let merge_impl = generate_declarative_merge_impl(&state_ident, config_ident, strategies, krate);
let merge_fn = generate_declarative_merge_from_layers_fn(
&state_ident,
config_ident,
has_post_merge_hook,
krate,
);
quote! {
#state_struct
#merge_impl
#merge_fn
}
}
#[cfg(test)]
mod tests;