use proc_macro2::TokenStream;
use quote::quote;
use prax_schema::ast::Enum;
use super::{generate_doc_comment, pascal_ident, snake_ident};
pub fn generate_enum_module(enum_def: &Enum) -> Result<TokenStream, syn::Error> {
let enum_name = pascal_ident(enum_def.name());
let module_name = snake_ident(enum_def.name());
let doc = generate_doc_comment(enum_def.documentation.as_ref().map(|d| d.text.as_str()));
let variants: Vec<_> = enum_def
.variants
.iter()
.map(|variant| {
let variant_name = pascal_ident(variant.name());
let variant_doc =
generate_doc_comment(variant.documentation.as_ref().map(|d| d.text.as_str()));
let db_value = variant.db_value().to_string();
quote! {
#variant_doc
#[serde(rename = #db_value)]
#variant_name
}
})
.collect();
let variant_names: Vec<_> = enum_def
.variants
.iter()
.map(|v| pascal_ident(v.name()))
.collect();
let variant_strs: Vec<_> = enum_def
.variants
.iter()
.map(|v| v.db_value().to_string())
.collect();
let db_name = enum_def.db_name().to_string();
let db_name_str = db_name.as_str();
Ok(quote! {
#doc
pub mod #module_name {
use serde::{Deserialize, Serialize};
pub const DB_NAME: &str = #db_name_str;
#doc
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum #enum_name {
#(#variants,)*
}
impl #enum_name {
pub const fn variants() -> &'static [Self] {
&[#(Self::#variant_names,)*]
}
pub const fn as_str(&self) -> &'static str {
match self {
#(Self::#variant_names => #variant_strs,)*
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
#(#variant_strs => Some(Self::#variant_names),)*
_ => None,
}
}
}
impl std::fmt::Display for #enum_name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for #enum_name {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str(s).ok_or_else(|| {
format!("Unknown {} variant: {}", stringify!(#enum_name), s)
})
}
}
impl Default for #enum_name {
fn default() -> Self {
Self::variants()[0]
}
}
}
pub use #module_name::#enum_name;
})
}
#[cfg(test)]
mod tests {
use super::*;
use prax_schema::ast::{EnumVariant, Ident, Span};
fn make_span() -> Span {
Span::new(0, 0)
}
fn make_ident(name: &str) -> Ident {
Ident::new(name, make_span())
}
#[test]
fn test_generate_simple_enum() {
let mut enum_def = Enum::new(make_ident("Role"), make_span());
enum_def.add_variant(EnumVariant::new(make_ident("USER"), make_span()));
enum_def.add_variant(EnumVariant::new(make_ident("ADMIN"), make_span()));
let result = generate_enum_module(&enum_def);
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("pub mod role"));
assert!(code.contains("pub enum Role"));
assert!(code.contains("User"));
assert!(code.contains("Admin"));
}
#[test]
fn test_generate_enum_with_documentation() {
use prax_schema::ast::Documentation;
let doc = Documentation::new("User status", make_span());
let mut enum_def = Enum::new(make_ident("Status"), make_span()).with_documentation(doc);
enum_def.add_variant(EnumVariant::new(make_ident("ACTIVE"), make_span()));
enum_def.add_variant(EnumVariant::new(make_ident("INACTIVE"), make_span()));
let result = generate_enum_module(&enum_def);
assert!(result.is_ok());
let code = result.unwrap().to_string();
assert!(code.contains("User status"));
}
}