use crate::case::{Case, Casing};
use crate::prelude::*;
pub trait HasDef {
fn def(&self) -> &Def;
fn current_name_literal(&self, name: Option<&LitStr>) -> LitStr {
name.cloned().unwrap_or_else(|| {
let ident = self.def().ident();
LitStr::new(&ident.to_string(), ident.span())
})
}
}
pub trait ValidateNode {
fn validate(&self) -> Result<(), DarlingError>;
fn fatal_errors(&self) -> Vec<syn::Error> {
Vec::new()
}
}
pub struct TraitTokens {
pub(crate) derive: TokenStream,
pub(crate) impls: TokenStream,
}
pub trait HasMacro: HasSchema + HasTraits + HasType + ToTokens {
fn all_tokens(&self) -> TokenStream {
let TraitTokens { derive, impls } = self.resolve_trait_tokens();
let schema = self.schema_tokens();
let type_part = self.type_part();
quote! {
#schema
#derive
#type_part
#impls
}
}
fn resolve_trait_tokens(&self) -> TraitTokens {
let mut derive_traits = Vec::new();
let mut attrs = Vec::new();
let mut impls = TokenStream::new();
let mut has_serde_deserialize = false;
for tr in self.traits() {
let Some(strategy) = self.trait_strategy(tr) else {
impls.extend(
self.missing_trait_strategy_error(self.application_type_kind(), tr)
.write_errors(),
);
continue;
};
if let Some(ts) = strategy.imp {
impls.extend(ts);
}
if let Some(derive_tr) = strategy.derive
&& let Some(path) = derive_tr.derive_path()
{
if matches!(derive_tr, TraitKind::Deserialize) {
has_serde_deserialize = true;
}
if matches!(derive_tr, TraitKind::CandidType) {
attrs.push(quote!(
#[candid_path("::icydb_model::__reexports::candid")]
));
}
derive_traits.push(path);
}
}
let mut derive = if derive_traits.is_empty() {
quote!()
} else {
quote!(#[derive(#(#derive_traits),*)])
};
if has_serde_deserialize {
attrs.push(quote!(#[serde(crate = "::icydb_model::__reexports::serde")]));
}
derive.extend(attrs);
TraitTokens { derive, impls }
}
}
impl<T> HasMacro for T where T: HasDef + HasSchema + HasTraits + HasType + ToTokens {}
pub trait HasType: HasDef {
fn type_part(&self) -> TokenStream {
quote!()
}
}
pub trait HasTypeExpr {
fn type_expr(&self) -> TokenStream {
quote!()
}
}
pub trait HasTraits: HasType {
fn application_type_kind(&self) -> Option<ApplicationTypeKind> {
None
}
fn trait_builder(&self) -> Option<&TraitBuilder> {
None
}
fn trait_baseline(&self) -> TraitSet {
application_type_trait_set()
}
fn traits(&self) -> Vec<TraitKind> {
let Some(builder) = self.trait_builder() else {
return Vec::new();
};
builder.build_for_type(self.trait_baseline()).into_vec()
}
fn map_trait(&self, _: TraitKind) -> Option<TraitStrategy> {
None
}
fn default_strategy(&self, t: TraitKind) -> Option<TraitStrategy> {
let def = self.def();
let ident = def.ident();
match t {
TraitKind::Path => {
let q = quote! {
const PATH: &'static str = concat!(module_path!(), "::", stringify!(#ident));
};
let tokens = Implementor::new(def, t).set_tokens(q).to_token_stream();
Some(TraitStrategy::from_impl(tokens))
}
TraitKind::NormalizeAuto
| TraitKind::NormalizeCustom
| TraitKind::ValidateAuto
| TraitKind::ValidateCustom
| TraitKind::Visitable => {
let tokens = Implementor::new(def, t).to_token_stream();
Some(TraitStrategy::from_impl(tokens))
}
_ => None,
}
}
fn trait_strategy(&self, trait_kind: TraitKind) -> Option<TraitStrategy> {
self.map_trait(trait_kind)
.or_else(|| self.default_strategy(trait_kind))
.or_else(|| {
trait_kind
.derive_path()
.map(|_| TraitStrategy::from_derive(trait_kind))
})
}
fn validate_traits(&self) -> Result<(), DarlingError> {
let Some(node_kind) = self.application_type_kind() else {
return Ok(());
};
let Some(builder) = self.trait_builder() else {
return Err(DarlingError::custom(format!(
"internal {} trait resolver has no authored directive owner",
node_kind.as_str(),
)));
};
let baseline = self.trait_baseline();
builder.validate_for_type(node_kind, baseline.clone())?;
let selected = builder.build_for_type(baseline).into_vec();
for trait_kind in selected {
let Some(strategy) = self.trait_strategy(trait_kind) else {
return Err(self.missing_trait_strategy_error(Some(node_kind), trait_kind));
};
let has_impl = strategy
.imp
.as_ref()
.is_some_and(|tokens| !tokens.is_empty());
let has_derive = strategy
.derive
.is_some_and(|derived| derived.derive_path().is_some());
if !has_impl && !has_derive {
return Err(self.missing_trait_strategy_error(Some(node_kind), trait_kind));
}
}
Ok(())
}
fn missing_trait_strategy_error(
&self,
node_kind: Option<ApplicationTypeKind>,
trait_kind: TraitKind,
) -> DarlingError {
let node_kind = node_kind.map_or("generated node", ApplicationTypeKind::as_str);
DarlingError::custom(format!(
"generated trait '{trait_kind:?}' for {} {} has no derive or implementation strategy",
node_kind,
self.def().ident(),
))
.with_span(&self.def().ident())
}
}
pub trait HasSchema: HasSchemaPart + HasDef {
fn schema_node_kind() -> SchemaNodeKind;
fn schema_const(&self) -> Ident {
let ident_s = self.def().ident().to_string().to_case(Case::UpperSnake);
format_ident!("{ident_s}_CONST")
}
fn schema_tokens(&self) -> TokenStream {
let schema_expr = self.schema_part();
if schema_expr.is_empty() {
return quote!();
}
let const_var = self.schema_const();
let ctor = format_ident!(
"__icydb_register_{}",
const_var.to_string().to_case(Case::Snake)
);
let kind = Self::schema_node_kind();
quote! {
const #const_var: ::icydb_model::node::#kind = #schema_expr;
#[cfg(not(target_arch = "wasm32"))]
#[::icydb_model::__reexports::ctor::ctor(
unsafe,
anonymous,
crate_path = ::icydb_model::__reexports::ctor
)]
fn #ctor() {
::icydb_model::build::register_node(
::icydb_model::node::SchemaNode::#kind(#const_var)
);
}
}
}
}
#[derive(Debug)]
#[remain::sorted]
pub enum SchemaNodeKind {
Canister,
Entity,
Enum,
List,
Map,
Newtype,
Normalizer,
Record,
Set,
Store,
Tuple,
Validator,
}
impl ToTokens for SchemaNodeKind {
fn to_tokens(&self, tokens: &mut TokenStream) {
format_ident!("{self:?}").to_tokens(tokens);
}
}
pub trait HasSchemaPart {
fn schema_part(&self) -> TokenStream {
quote!()
}
}