use crate::case::{Case, Casing};
use crate::{imp::*, prelude::*};
#[derive(Debug, FromMeta)]
pub struct Enum {
#[darling(default, skip)]
pub(crate) def: Def,
#[darling(default)]
pub(crate) name: Option<LitStr>,
#[darling(multiple, rename = "variant")]
pub(crate) variants: Vec<EnumVariant>,
#[darling(default)]
pub(crate) ty: Type,
#[darling(default)]
pub(crate) traits: TraitBuilder,
}
impl Enum {
pub fn is_unit_enum(&self) -> bool {
self.variants.iter().all(|v| v.value.is_none())
}
pub fn default_variant(&self) -> Option<&EnumVariant> {
self.variants.iter().find(|v| v.default)
}
}
impl HasDef for Enum {
fn def(&self) -> &Def {
&self.def
}
}
impl ValidateNode for Enum {
fn validate(&self) -> Result<(), DarlingError> {
self.traits.with_type_traits().validate()?;
for variant in &self.variants {
variant.validate()?;
}
let mut default_count = 0;
for variant in &self.variants {
if variant.default {
default_count += 1;
if default_count > 1 {
return Err(DarlingError::custom(format!(
"exactly one variant must be marked as default, found {default_count}"
))
.with_span(&variant.name));
}
}
}
let default_requested = self.traits.explicitly_adds(TraitKind::Default);
if default_requested && self.default_variant().is_none() {
return Err(DarlingError::custom(format!(
"Default was requested for enum {}, but no variant is marked `default`",
self.def.ident()
))
.with_span(&self.def.ident()));
}
if !default_requested && let Some(default_variant) = self.default_variant() {
return Err(DarlingError::custom(format!(
"enum {} marks a Rust default variant but does not enable `traits(add(Default))`",
self.def.ident()
))
.with_span(&default_variant.name));
}
Ok(())
}
}
impl HasSchema for Enum {
fn schema_node_kind() -> SchemaNodeKind {
SchemaNodeKind::Enum
}
}
impl HasSchemaPart for Enum {
fn schema_part(&self) -> TokenStream {
let def = &self.def.schema_part();
let name = self.current_name_literal(self.name.as_ref());
let variants = self.variants.iter().map(EnumVariant::schema_part);
let ty = &self.ty.schema_part();
quote! {
::icydb_model::node::Enum::new(#def, #name, &[#(#variants),*], #ty)
}
}
}
impl HasTraits for Enum {
fn traits(&self) -> Vec<TraitKind> {
let mut traits = self.traits.with_type_traits().build();
if self.is_unit_enum() {
traits.extend([TraitKind::Copy, TraitKind::Hash, TraitKind::PartialOrd]);
}
traits.into_vec()
}
fn map_trait(&self, t: TraitKind) -> Option<TraitStrategy> {
match t {
TraitKind::Default => DefaultTrait::strategy(self),
TraitKind::NormalizeAuto => NormalizeAutoTrait::strategy(self),
TraitKind::ValidateAuto => ValidateAutoTrait::strategy(self),
TraitKind::Visitable => VisitableTrait::strategy(self),
_ => None,
}
}
}
impl HasType for Enum {
fn type_part(&self) -> TokenStream {
let ident = self.def.ident();
let variants = self.variants.iter().map(HasTypeExpr::type_expr);
quote! {
pub enum #ident {
#(#variants),*
}
}
}
}
impl ToTokens for Enum {
fn to_tokens(&self, tokens: &mut TokenStream) {
let base = self.all_tokens();
let typed_adapter = crate::node::typed_adapter::enum_adapter_tokens(self);
tokens.extend(quote! {
#base
#typed_adapter
});
}
}
#[derive(Clone, Debug, FromMeta)]
pub struct EnumVariant {
pub(crate) name: Ident,
#[darling(default)]
pub(crate) value: Option<Value>,
#[darling(default)]
pub(crate) default: bool,
}
impl EnumVariant {
pub fn validate(&self) -> Result<(), DarlingError> {
let name = self.name.to_string();
if !name.is_case(Case::UpperCamel) {
return Err(DarlingError::custom(format!(
"variant name '{name}' must be in UpperCamelCase",
))
.with_span(&self.name));
}
if let Some(value) = &self.value {
value.validate()?;
if value.cardinality() == Cardinality::Many
&& !value.item.indirect
&& value.item.relation.is_none()
{
let item_ty = value.item.type_expr().to_string().replace(' ', "");
let message = format!(
"Vec<{item_ty}> does not implement the generated value surface. If this list holds a recursive or complex value type, use item(indirect, ...) to store Vec<Box<{item_ty}>>."
);
return Err(DarlingError::custom(message).with_span(&self.name));
}
}
Ok(())
}
}
impl HasSchemaPart for EnumVariant {
fn schema_part(&self) -> TokenStream {
let name = quote_one(&self.name, to_str_lit);
let value = quote_option(self.value.as_ref(), Value::schema_part);
quote! {
::icydb_model::node::EnumVariant::new(
#name,
#value,
)
}
}
}
impl HasTypeExpr for EnumVariant {
fn type_expr(&self) -> TokenStream {
let name = &self.name;
let body = if let Some(value) = &self.value {
let value = value.type_expr();
quote!(#name(#value))
} else {
quote!(#name)
};
quote! {
#body
}
}
}