diff options
Diffstat (limited to 'mingling_macros/src/func/pack_err.rs')
| -rw-r--r-- | mingling_macros/src/func/pack_err.rs | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/mingling_macros/src/func/pack_err.rs b/mingling_macros/src/func/pack_err.rs new file mode 100644 index 0000000..36e550a --- /dev/null +++ b/mingling_macros/src/func/pack_err.rs @@ -0,0 +1,209 @@ +use just_fmt::snake_case; +use proc_macro::TokenStream; +use quote::quote; +use syn::{Ident, Token, Type, parse_macro_input}; + +enum PackErrInput { + /// pack_err!(ErrorNotFound) + Simple { type_name: Ident }, + /// pack_err!(ErrorNotDir = PathBuf) + Typed { + type_name: Ident, + inner_type: Box<Type>, + }, +} + +impl syn::parse::Parse for PackErrInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { + let type_name: Ident = input.parse()?; + + if input.peek(Token![=]) { + input.parse::<Token![=]>()?; + let inner_type: Type = input.parse()?; + Ok(PackErrInput::Typed { + type_name, + inner_type: Box::new(inner_type), + }) + } else { + Ok(PackErrInput::Simple { type_name }) + } + } +} + +#[allow(clippy::too_many_lines)] +pub(crate) fn pack_err(input: TokenStream) -> TokenStream { + let parsed = parse_macro_input!(input as PackErrInput); + + match parsed { + PackErrInput::Simple { type_name } => { + let name_str = type_name.to_string(); + let snake_name = snake_case!(&name_str); + + // Note: No longer derives Serialize under structural_renderer. + // Use pack_err_structural for structured output support. + let derive = quote! { + #[derive(::mingling::Grouped)] + }; + + let expanded = quote! { + #derive + pub struct #type_name { + /// The snake_case name of this error, automatically set at compile time. + pub name: String, + } + + impl ::std::default::Default for #type_name { + fn default() -> Self { + Self { + name: #snake_name.into(), + } + } + } + + ::mingling::macros::register_type!(#type_name); + }; + + expanded.into() + } + PackErrInput::Typed { + type_name, + inner_type, + } => { + let name_str = type_name.to_string(); + let snake_name = snake_case!(&name_str); + + // Note: No longer derives Serialize under structural_renderer. + // Use pack_err_structural for structured output support. + let derive = quote! { + #[derive(::mingling::Grouped)] + }; + + let expanded = quote! { + #derive + pub struct #type_name { + /// The snake_case name of this error, automatically set at compile time. + pub name: String, + /// Additional context info for this error. + pub info: #inner_type, + } + + impl #type_name { + /// Creates a new error with the given info. + /// The `name` field is automatically set to the snake_case of the struct name. + pub fn new(info: #inner_type) -> Self { + Self { + name: #snake_name.into(), + info, + } + } + } + + ::mingling::macros::register_type!(#type_name); + }; + + expanded.into() + } + } +} + +/// `pack_err_structural!` — like `pack_err!` but also marks the type as +/// supporting structured output via `StructuralData`. +/// +/// # Syntax +/// +/// ```rust,ignore +/// pack_err_structural!(ErrorNotFound); +/// pack_err_structural!(ErrorNotDir = PathBuf); +/// ``` +/// +/// This is equivalent to: +/// ```rust,ignore +/// pack_err!(ErrorNotFound); +/// impl ::mingling::__private::StructuralDataSealed for ErrorNotFound {} +/// impl ::mingling::__private::StructuralData for ErrorNotFound {} +/// ``` +#[cfg(feature = "structural_renderer")] +pub(crate) fn pack_err_structural(input: TokenStream) -> TokenStream { + let parsed = parse_macro_input!(input as PackErrInput); + + let type_name = match &parsed { + PackErrInput::Simple { type_name } => type_name.clone(), + PackErrInput::Typed { type_name, .. } => type_name.clone(), + }; + + // Register in STRUCTURED_TYPES + let type_name_str = type_name.to_string(); + crate::get_global_set(&crate::STRUCTURED_TYPES) + .lock() + .unwrap() + .insert(type_name_str); + + let structural_data = quote! { + impl ::mingling::__private::StructuralDataSealed for #type_name {} + impl ::mingling::__private::StructuralData for #type_name {} + }; + + // Generate the struct + impls (same as pack_err! but with Serialize derive + sealed) + match parsed { + PackErrInput::Simple { type_name } => { + let name_str = type_name.to_string(); + let snake_name = snake_case!(&name_str); + + let expanded = quote! { + #[derive(::mingling::Grouped, ::serde::Serialize)] + pub struct #type_name { + /// The snake_case name of this error, automatically set at compile time. + pub name: String, + } + + impl ::std::default::Default for #type_name { + fn default() -> Self { + Self { + name: #snake_name.into(), + } + } + } + + ::mingling::macros::register_type!(#type_name); + + #structural_data + }; + + expanded.into() + } + PackErrInput::Typed { + type_name, + inner_type, + } => { + let name_str = type_name.to_string(); + let snake_name = snake_case!(&name_str); + + let expanded = quote! { + #[derive(::mingling::Grouped, ::serde::Serialize)] + pub struct #type_name { + /// The snake_case name of this error, automatically set at compile time. + pub name: String, + /// Additional context info for this error. + pub info: #inner_type, + } + + impl #type_name { + /// Creates a new error with the given info. + /// The `name` field is automatically set to the snake_case of the struct name. + pub fn new(info: #inner_type) -> Self { + Self { + name: #snake_name.into(), + info, + } + } + } + + ::mingling::macros::register_type!(#type_name); + + #structural_data + }; + + expanded.into() + } + } +} |
