diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-18 22:48:16 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-18 22:48:16 +0800 |
| commit | 0f7b2a50b05f38d886234ff6b031766c7af1dabb (patch) | |
| tree | 9c9b5d4aa11c91c117b08e829ec33361c4aa6275 | |
| parent | dd28430b67dcfda6dd2e91750a4c1a62c085150a (diff) | |
Add `pack_err!` macro for error structs with automatic name field
| -rw-r--r-- | CHANGELOG.md | 52 | ||||
| -rw-r--r-- | docs/example-pages/examples.json | 24 | ||||
| -rw-r--r-- | examples/example-pack-err/Cargo.lock | 141 | ||||
| -rw-r--r-- | examples/example-pack-err/Cargo.toml | 16 | ||||
| -rw-r--r-- | examples/example-pack-err/page.toml | 10 | ||||
| -rw-r--r-- | examples/example-pack-err/src/main.rs | 101 | ||||
| -rw-r--r-- | examples/test-examples.toml | 20 | ||||
| -rw-r--r-- | mingling/src/example_docs.rs | 125 | ||||
| -rw-r--r-- | mingling/src/lib.rs | 6 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 70 | ||||
| -rw-r--r-- | mingling_macros/src/pack_err.rs | 143 |
11 files changed, 704 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 78698b3..b9ca68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,6 +124,58 @@ fn render_entry_show(_args: EntryShow, res: &mut LazyRes<ResLargeData>) { 7. **\[core:comp\]** Added `Program::is_completing()` method to check whether the program is currently running in completion mode. This provides a convenient way to conditionally skip certain logic during completion generation, where those operations may be unnecessary or undesirable. +8. **\[macros\]** Added the `pack_err!` macro for creating error structs with automatic `name` field. + +The `pack_err!` macro provides a concise way to define error types that implement `Groupped` and are automatically registered for inclusion in the program enum. The `name` field is automatically set to the snake_case version of the struct name at compile time. + +Two forms are supported: + +```rust +// Simple form — generates a struct with only `name: String` and a `Default` impl: +pack_err!(ErrorNotFound); + +// Typed form — generates a struct with `name: String` + `info: Type` and a `new(info)` constructor: +pack_err!(ErrorNotDir = PathBuf); +``` + +For `pack_err!(ErrorNotFound)`, the generated code is: + +```rust +#[derive(::mingling::Groupped)] +pub struct ErrorNotFound { + name: String, +} + +impl Default for ErrorNotFound { + fn default() -> Self { + Self { + name: "error_not_found".into(), + } + } +} +``` + +For `pack_err!(ErrorNotDir = PathBuf)`: + +```rust +#[derive(::mingling::Groupped)] +pub struct ErrorNotDir { + name: String, + info: PathBuf, +} + +impl ErrorNotDir { + pub fn new(info: PathBuf) -> Self { + Self { + name: "error_not_dir".into(), + info, + } + } +} +``` + +This macro is only available with the `extra_macros` feature. + #### **BREAKING CHANGES** (API CHANGES): 1. **\[core\]** Changed the signature of `ProgramSetup::setup` from `fn setup(&mut self, program: &mut Program<C>) -> S` to `fn setup(self, program: &mut Program<C>)`, consuming `self` instead of taking a mutable reference. Correspondingly, `Program::with_setup` now accepts `S` by value (`&mut self, setup: S`) instead of by mutable reference (`&mut self, setup: &mut S`). diff --git a/docs/example-pages/examples.json b/docs/example-pages/examples.json index e7f0c0c..8e8719b 100644 --- a/docs/example-pages/examples.json +++ b/docs/example-pages/examples.json @@ -220,6 +220,23 @@ ] }, { + "id": "example-pack-err", + "name": "pack_err!", + "icon": "🛑", + "category": "macros", + "desc": "Demonstrates how to use the `pack_err!` macro to define error types with automatic `name` field (snake_case at compile time) and optional `info` field. Also shows `--json` serialization when `general_renderer` is enabled.\n", + "tags": [ + "pack_err!", + "extra_macros", + "general_renderer", + "--json" + ], + "files": [ + "src/main.rs", + "Cargo.toml" + ] + }, + { "id": "example-panic-unwind", "name": "Panic Unwind", "icon": "💥", @@ -258,9 +275,8 @@ "injection" ], "files": [ - "Cargo.toml", "src/main.rs", - "src/lib.rs" + "Cargo.toml" ] }, { @@ -289,8 +305,8 @@ "extra_macros" ], "files": [ - "Cargo.toml", - "src/main.rs" + "src/main.rs", + "Cargo.toml" ] }, { diff --git a/examples/example-pack-err/Cargo.lock b/examples/example-pack-err/Cargo.lock new file mode 100644 index 0000000..36d4b6f --- /dev/null +++ b/examples/example-pack-err/Cargo.lock @@ -0,0 +1,141 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "example-pack-err" +version = "0.1.0" +dependencies = [ + "mingling", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "just_fmt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "mingling" +version = "0.2.0" +dependencies = [ + "mingling_core", + "mingling_macros", + "serde", +] + +[[package]] +name = "mingling_core" +version = "0.2.0" +dependencies = [ + "just_fmt", + "serde", + "serde_json", +] + +[[package]] +name = "mingling_macros" +version = "0.2.0" +dependencies = [ + "just_fmt", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/examples/example-pack-err/Cargo.toml b/examples/example-pack-err/Cargo.toml new file mode 100644 index 0000000..883fc89 --- /dev/null +++ b/examples/example-pack-err/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example-pack-err" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } + +[dependencies.mingling] +path = "../../mingling" +features = [ + "general_renderer", + "extra_macros", +] + +[workspace] diff --git a/examples/example-pack-err/page.toml b/examples/example-pack-err/page.toml new file mode 100644 index 0000000..5534236 --- /dev/null +++ b/examples/example-pack-err/page.toml @@ -0,0 +1,10 @@ +[example] +id = "example-pack-err" +name = "pack_err!" +icon = "🛑" +category = "macros" +desc = """ +Demonstrates how to use the `pack_err!` macro to define error types with automatic `name` field (snake_case at compile time) and optional `info` field. Also shows `--json` serialization when `general_renderer` is enabled. +""" +tags = ["pack_err!", "extra_macros", "general_renderer", "--json"] +files = ["src/main.rs", "Cargo.toml"] diff --git a/examples/example-pack-err/src/main.rs b/examples/example-pack-err/src/main.rs new file mode 100644 index 0000000..72fecd6 --- /dev/null +++ b/examples/example-pack-err/src/main.rs @@ -0,0 +1,101 @@ +//! Example `pack_err!` +//! +//! > This example demonstrates how to use the `pack_err!` macro to define error types +//! > with automatic `name` field (set to snake_case at compile time) and optional `info` field. +//! > Also demonstrates `--json` serialization when `general_renderer` is enabled. +//! +//! Run: +//! ```bash +//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find +//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find --json +//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find Cargo.toml +//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find Cargo.toml --json +//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find src +//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find src --json +//! ``` +//! +//! Output: +//! ```plaintext +//! Search path not provided +//! {"name":"error_not_found"} +//! Not a directory: Cargo.toml +//! {"name":"error_not_dir","info":"Cargo.toml"} +//! Found directory: src +//! {"inner":"src"} +//! ``` + +use mingling::prelude::*; +use mingling::setup::GeneralRendererSetup; +use std::path::PathBuf; + +dispatcher!("find", CMDFind => EntryFind); + +// --------- IMPORTANT --------- +// `pack_err!` is a convenient macro for defining error types. +// +// Simple form: pack_err!(ErrorNotFound); +// Typed form: pack_err!(ErrorNotDir = PathBuf); +// +// The simple form generates a struct with `name: String` and `impl Default`. +// name = "error_not_found" (automatically snake_cased at compile time) +// +// The typed form additionally generates `pub fn new(info)`. +// name = "error_not_dir" +// +// When `general_renderer` is enabled, the struct also gets +// `#[derive(serde::Serialize)]` for --json / --yaml output. +// --------- IMPORTANT --------- + +// Simple form — name = "error_not_found" +pack_err!(ErrorNotFound); + +// Typed form — name = "error_not_dir" +pack_err!(ErrorNotDir = PathBuf); + +// Success type using traditional pack! +pack!(ResultPath = PathBuf); + +#[chain] +fn handle_find(args: EntryFind) -> Next { + let Some(path_str) = args.inner.first().cloned() else { + // No path provided → use the simple error form (Default) + return ErrorNotFound::default().to_render(); + }; + + let path = PathBuf::from(&path_str); + if path.is_dir() { + // Is a directory → success + ResultPath::new(path).to_render() + } else { + // Not a directory (or doesn't exist) → use the typed error form + ErrorNotDir::new(path).to_render() + } +} + +/// Renders the successful result with the found directory path. +#[renderer] +fn render_result_path(path: ResultPath) { + r_println!("Found directory: {}", path.display()); +} + +/// Renders the error when no search path is provided. +#[renderer] +fn render_error_not_found(_: ErrorNotFound) { + r_println!("Search path not provided"); +} + +/// Renders the error when the given path is not a directory. +#[renderer] +fn render_error_not_dir(err: ErrorNotDir) { + r_println!("Not a directory: {}", err.info.display()); +} + +gen_program!(); + +fn main() { + let mut program = ThisProgram::new(); + // Add GeneralRendererSetup to support --json / --yaml flags + program.with_setup(GeneralRendererSetup); + program.with_dispatcher(CMDFind); + let _ = program.exec(); +} diff --git a/examples/test-examples.toml b/examples/test-examples.toml index 8d2cf0d..4a50ab1 100644 --- a/examples/test-examples.toml +++ b/examples/test-examples.toml @@ -207,3 +207,23 @@ expect.result = "Hello, Alice!" command = "greet Alice -r 5" expect.exit-code = 0 expect.result = "Hello, Alice, Alice, Alice, Alice, Alice!" + +[[test.example-pack-err]] +command = "find" +expect.exit-code = 0 +expect.result = "Search path not provided" + +[[test.example-pack-err]] +command = "find --json" +expect.exit-code = 0 +expect.result = '{"name":"error_not_found"}' + +[[test.example-pack-err]] +command = "find Cargo.toml" +expect.exit-code = 0 +expect.result = "Not a directory: Cargo.toml" + +[[test.example-pack-err]] +command = "find Cargo.toml --json" +expect.exit-code = 0 +expect.result = '{"name":"error_not_dir","info":"Cargo.toml"}' diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index f4a27bd..b1d693b 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -1550,6 +1550,131 @@ pub mod example_implicit_dispatcher {} /// gen_program!(); /// ``` pub mod example_lazy_resources {} +/// Example `pack_err!` +/// +/// > This example demonstrates how to use the `pack_err!` macro to define error types +/// > with automatic `name` field (set to snake_case at compile time) and optional `info` field. +/// > Also demonstrates `--json` serialization when `general_renderer` is enabled. +/// +/// Run: +/// ```bash +/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find +/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find --json +/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find Cargo.toml +/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find Cargo.toml --json +/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find src +/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find src --json +/// ``` +/// +/// Output: +/// ```plaintext +/// Search path not provided +/// {"name":"error_not_found"} +/// Not a directory: Cargo.toml +/// {"name":"error_not_dir","info":"Cargo.toml"} +/// Found directory: src +/// {"inner":"src"} +/// ``` +/// +/// Source code (./Cargo.toml) +/// ```toml +/// [package] +/// name = "example-pack-err" +/// version = "0.1.0" +/// edition = "2024" +/// +/// [dependencies] +/// serde = { version = "1.0.228", features = ["derive"] } +/// +/// [dependencies.mingling] +/// path = "../../mingling" +/// features = [ +/// "general_renderer", +/// "extra_macros", +/// ] +/// +/// [workspace] +/// ``` +/// +/// Source code (./src/main.rs) +/// ```ignore +/// use mingling::prelude::*; +/// use mingling::setup::GeneralRendererSetup; +/// use std::path::PathBuf; +/// +/// dispatcher!("find", CMDFind => EntryFind); +/// +/// // --------- IMPORTANT --------- +/// // `pack_err!` is a convenient macro for defining error types. +/// // +/// // Simple form: pack_err!(ErrorNotFound); +/// // Typed form: pack_err!(ErrorNotDir = PathBuf); +/// // +/// // The simple form generates a struct with `name: String` and `impl Default`. +/// // name = "error_not_found" (automatically snake_cased at compile time) +/// // +/// // The typed form additionally generates `pub fn new(info)`. +/// // name = "error_not_dir" +/// // +/// // When `general_renderer` is enabled, the struct also gets +/// // `#[derive(serde::Serialize)]` for --json / --yaml output. +/// // --------- IMPORTANT --------- +/// +/// // Simple form — name = "error_not_found" +/// pack_err!(ErrorNotFound); +/// +/// // Typed form — name = "error_not_dir" +/// pack_err!(ErrorNotDir = PathBuf); +/// +/// // Success type using traditional pack! +/// pack!(ResultPath = PathBuf); +/// +/// #[chain] +/// fn handle_find(args: EntryFind) -> Next { +/// let Some(path_str) = args.inner.first().cloned() else { +/// // No path provided → use the simple error form (Default) +/// return ErrorNotFound::default().to_render(); +/// }; +/// +/// let path = PathBuf::from(&path_str); +/// if path.is_dir() { +/// // Is a directory → success +/// ResultPath::new(path).to_render() +/// } else { +/// // Not a directory (or doesn't exist) → use the typed error form +/// ErrorNotDir::new(path).to_render() +/// } +/// } +/// +/// /// Renders the successful result with the found directory path. +/// #[renderer] +/// fn render_result_path(path: ResultPath) { +/// r_println!("Found directory: {}", path.display()); +/// } +/// +/// /// Renders the error when no search path is provided. +/// #[renderer] +/// fn render_error_not_found(_: ErrorNotFound) { +/// r_println!("Search path not provided"); +/// } +/// +/// /// Renders the error when the given path is not a directory. +/// #[renderer] +/// fn render_error_not_dir(err: ErrorNotDir) { +/// r_println!("Not a directory: {}", err.info.display()); +/// } +/// +/// gen_program!(); +/// +/// fn main() { +/// let mut program = ThisProgram::new(); +/// // Add GeneralRendererSetup to support --json / --yaml flags +/// program.with_setup(GeneralRendererSetup); +/// program.with_dispatcher(CMDFind); +/// let _ = program.exec(); +/// } +/// ``` +pub mod example_pack_err {} /// Example Panic Unwind /// /// > This example introduces how to catch Panic in the Mingling program loop diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index 7b96d34..f1232d3 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -99,6 +99,9 @@ pub mod macros { pub use mingling_macros::node; /// Used to create a wrapper type for use with `Chain` and `Renderer` pub use mingling_macros::pack; + /// Used to create an error struct with automatic `name` field + #[cfg(feature = "extra_macros")] + pub use mingling_macros::pack_err; #[cfg(feature = "comp")] /// Internal macro for '`gen_program`' used to finally generate the completion structure pub use mingling_macros::program_comp_gen; @@ -192,6 +195,9 @@ pub mod prelude { pub use crate::macros::gen_program; /// Re-export of the `pack` macro for creating wrapper types. pub use crate::macros::pack; + /// Re-export of the `pack_err` macro for creating error types. + #[cfg(feature = "extra_macros")] + pub use crate::macros::pack_err; /// Re-export of the `r_print` macro for printing within a renderer context. pub use crate::macros::r_print; /// Re-export of the `r_println` macro for printing with a newline within a renderer diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 97dd824..408450a 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -158,6 +158,8 @@ mod help; mod node; mod pack; #[cfg(feature = "extra_macros")] +mod pack_err; +#[cfg(feature = "extra_macros")] mod program_setup; mod render; mod renderer; @@ -319,6 +321,74 @@ pub fn pack(input: TokenStream) -> TokenStream { pack::pack(input) } +/// Creates an error struct with a `name: String` field and optional `info: Type` field. +/// +/// This macro provides a concise way to define error types that implement `Groupped` +/// and are registered for inclusion in the program enum. +/// +/// The `name` field is automatically set to the snake_case version of the struct name +/// at compile time. +/// +/// # Syntax +/// +/// Two forms are supported: +/// +/// ```rust,ignore +/// // Simple form — generates a struct with only `name: String` and a `Default` impl: +/// pack_err!(ErrorNotFound); +/// +/// // Typed form — generates a struct with `name: String` + `info: Type` and a `new(info)` constructor: +/// pack_err!(ErrorNotDir = PathBuf); +/// ``` +/// +/// # Generated code +/// +/// For `pack_err!(ErrorNotFound)`: +/// +/// ```rust,ignore +/// #[derive(::mingling::Groupped)] +/// pub struct ErrorNotFound { +/// name: String, +/// } +/// +/// impl Default for ErrorNotFound { +/// fn default() -> Self { +/// Self { +/// name: "error_not_found".into(), +/// } +/// } +/// } +/// ``` +/// +/// For `pack_err!(ErrorNotDir = PathBuf)`: +/// +/// ```rust,ignore +/// #[derive(::mingling::Groupped)] +/// pub struct ErrorNotDir { +/// name: String, +/// info: PathBuf, +/// } +/// +/// impl ErrorNotDir { +/// pub fn new(info: PathBuf) -> Self { +/// Self { +/// name: "error_not_dir".into(), +/// info, +/// } +/// } +/// } +/// ``` +/// +/// When the `general_renderer` feature is enabled, the struct also gets +/// `#[derive(serde::Serialize)]`. +/// +/// This macro is only available with the `extra_macros` feature. +#[cfg(feature = "extra_macros")] +#[proc_macro] +pub fn pack_err(input: TokenStream) -> TokenStream { + pack_err::pack_err(input) +} + /// Early-returns an error from a `Result`, converting the `Ok` branch to a /// `ChainProcess`. /// diff --git a/mingling_macros/src/pack_err.rs b/mingling_macros/src/pack_err.rs new file mode 100644 index 0000000..dd7b083 --- /dev/null +++ b/mingling_macros/src/pack_err.rs @@ -0,0 +1,143 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Ident, Token, Type, parse_macro_input}; + +/// Converts a PascalCase/UpperCamelCase identifier string to snake_case. +/// +/// Examples: +/// - `ErrorNotFound` → `"error_not_found"` +/// - `ErrorNotDir` → `"error_not_dir"` +/// - `FileIO` → `"file_io"` +/// - `XMLParser` → `"xml_parser"` +fn to_snake_case(ident: &str) -> String { + let mut result = String::new(); + let mut prev_is_upper = false; + + for (i, c) in ident.chars().enumerate() { + if c.is_uppercase() { + if i > 0 && !prev_is_upper { + result.push('_'); + } + for lower_c in c.to_lowercase() { + result.push(lower_c); + } + prev_is_upper = true; + } else { + result.push(c); + prev_is_upper = false; + } + } + + result +} + +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 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 = to_snake_case(&name_str); + + #[cfg(not(feature = "general_renderer"))] + let derive = quote! { + #[derive(::mingling::Groupped)] + }; + + #[cfg(feature = "general_renderer")] + let derive = quote! { + #[derive(::mingling::Groupped, ::serde::Serialize)] + }; + + let expanded = quote! { + #derive + pub struct #type_name { + /// The snake_case name of this error, automatically set at compile time. + 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 = to_snake_case(&name_str); + + #[cfg(not(feature = "general_renderer"))] + let derive = quote! { + #[derive(::mingling::Groupped)] + }; + + #[cfg(feature = "general_renderer")] + let derive = quote! { + #[derive(::mingling::Groupped, ::serde::Serialize)] + }; + + let expanded = quote! { + #derive + pub struct #type_name { + /// The snake_case name of this error, automatically set at compile time. + name: String, + /// Additional context info for this error. + 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() + } + } +} |
