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 strat = self.map_trait(tr).or_else(|| self.default_strategy(tr));
if let Some(strategy) = strat {
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);
}
} else if let Some(path) = tr.derive_path() {
if matches!(tr, TraitKind::Deserialize) {
has_serde_deserialize = true;
}
if matches!(tr, TraitKind::CandidType) {
attrs.push(quote!(
#[candid_path("::icydb_model::__reexports::candid")]
));
}
derive_traits.push(path);
}
if matches!(tr, TraitKind::Sorted) {
attrs.push(quote!(#[::icydb_model::__reexports::remain::sorted]));
}
}
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 traits(&self) -> Vec<TraitKind> {
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,
}
}
}
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!()
}
}