waddling-errors-macros 0.7.3

Procedural macros for structured error codes with compile-time validation and taxonomy enforcement
Documentation
//! Implementation of the `setup!` proc macro
//!
//! This macro configures the paths where component, primary, and sequence
//! definitions are located, enabling flexible project structure.
//!
//! # Example
//!
//! ```ignore
//! waddling_errors::setup! {
//!     components = crate::my_components,
//!     primaries = crate::error_primaries,
//!     sequences = crate::my_sequences,
//! }
//! ```
//!
//! This generates a hidden module that `diag!` uses to resolve paths:
//!
//! ```ignore
//! pub(crate) mod __wd_paths {
//!     pub use crate::my_components as components;
//!     pub use crate::error_primaries as primaries;
//!     pub use crate::my_sequences as sequences;
//! }
//! ```

use proc_macro::TokenStream;
use quote::quote;
use syn::{
    Ident, Path, Result, Token,
    parse::{Parse, ParseStream},
};

/// Parsed input for the setup! macro
struct SetupInput {
    components: Option<Path>,
    primaries: Option<Path>,
    sequences: Option<Path>,
}

impl Parse for SetupInput {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut components = None;
        let mut primaries = None;
        let mut sequences = None;

        while !input.is_empty() {
            let key: Ident = input.parse()?;
            input.parse::<Token![=]>()?;
            let path: Path = input.parse()?;

            // Optional trailing comma
            if input.peek(Token![,]) {
                input.parse::<Token![,]>()?;
            }

            match key.to_string().as_str() {
                "components" => {
                    if components.is_some() {
                        return Err(syn::Error::new(key.span(), "duplicate 'components' key"));
                    }
                    components = Some(path);
                }
                "primaries" => {
                    if primaries.is_some() {
                        return Err(syn::Error::new(key.span(), "duplicate 'primaries' key"));
                    }
                    primaries = Some(path);
                }
                "sequences" => {
                    if sequences.is_some() {
                        return Err(syn::Error::new(key.span(), "duplicate 'sequences' key"));
                    }
                    sequences = Some(path);
                }
                other => {
                    return Err(syn::Error::new(
                        key.span(),
                        format!(
                            "unknown key '{}'. Expected: components, primaries, or sequences",
                            other
                        ),
                    ));
                }
            }
        }

        Ok(SetupInput {
            components,
            primaries,
            sequences,
        })
    }
}

pub fn expand(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as SetupInput);

    // Generate the path aliases
    // If not specified, fall back to default paths for backward compatibility
    let components_use = match &input.components {
        Some(path) => quote! { pub use #path as components; },
        None => quote! { pub use crate::components as components; },
    };

    let primaries_use = match &input.primaries {
        Some(path) => quote! { pub use #path as primaries; },
        None => quote! { pub use crate::primaries as primaries; },
    };

    let sequences_use = match &input.sequences {
        Some(path) => quote! { pub use #path as sequences; },
        None => quote! { pub use crate::sequences as sequences; },
    };

    let output = quote! {
        /// Internal module for waddling-errors path resolution.
        /// Generated by `waddling_errors::setup!` macro.
        /// Do not use directly - this is used internally by `diag!` macro.
        #[doc(hidden)]
        pub mod __wd_paths {
            #components_use
            #primaries_use
            #sequences_use
        }
    };

    TokenStream::from(output)
}