waddling-errors-macros 0.7.3

Procedural macros for structured error codes with compile-time validation and taxonomy enforcement
Documentation
//! Example: Multiple Namespaces and Catalog Generation
//!
//! This example demonstrates how different namespaces create separate catalogs.
//!
//! Run with: cargo run --example multiple_namespaces --features "doc-gen,auto-register"

#[cfg(not(all(feature = "doc-gen", feature = "auto-register")))]
compile_error!(
    "This example requires the 'doc-gen' and 'auto-register' features.\n\
     Run with: cargo run --example multiple_namespaces --features \"doc-gen,auto-register\""
);

#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
use waddling_errors_macros::setup;

// ============================================================================
// Define Components, Primaries, and Sequences for diag! validation
// ============================================================================

/// Component definitions for the example
#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
pub mod components {
    use waddling_errors_macros::component;

    component! {
        Auth {
            docs: "Authentication and authorization system",
            tags: ["security", "authentication"],
        },
        Payment {
            docs: "Payment processing system",
            tags: ["payment", "billing"],
        },
        Core {
            docs: "Core system functionality",
            tags: ["core", "system"],
        },
    }
}

/// Primary category definitions
#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
pub mod primaries {
    use waddling_errors_macros::primary;

    primary! {
        pub enum Primary {
            Token {
                description: "Authentication token errors",
            },
            Card {
                description: "Credit card errors",
            },
            Init {
                description: "Initialization errors",
            },
        }
    }
}

/// Sequence definitions for error codes
#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
pub mod sequences {
    use waddling_errors_macros::sequence;

    sequence! {
        EXPIRED(17) {
            description: "Token or resource has expired",
        },
        INVALID(3) {
            description: "Validation failed",
        },
        DECLINED(51) {
            description: "Request was declined",
        },
        FAILED(99) {
            description: "Operation failed",
        },
    }
}

// Setup once at crate root - tells diag! where to find definitions
#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
setup! {
    components = crate::components,
    primaries = crate::primaries,
    sequences = crate::sequences,
}

// ============================================================================
// NAMESPACE 1: auth_service
// ============================================================================
#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
mod auth_errors {
    use waddling_errors_macros::diag;

    diag! {
        namespace: "auth_service",
        strict(component, primary, sequence, naming, duplicates, sequence_values, string_values),
        <json>,

        E.Auth.Token.EXPIRED: {
            message: "Authentication token has expired",
            fields: [token_id],
        },

        E.Auth.Token.INVALID: {
            message: "Authentication token is invalid",
            fields: [token_id, reason],
        },
    }
}

// ============================================================================
// NAMESPACE 2: payment_service
// ============================================================================
#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
mod payment_errors {
    use waddling_errors_macros::diag;

    diag! {
        namespace: "payment_service",
        strict(component, primary, sequence, naming, duplicates, sequence_values, string_values),
        <json>,

        E.Payment.Card.DECLINED: {
            message: "Credit card was declined",
            fields: [card_last_four, decline_reason],
        },

        E.Payment.Card.EXPIRED: {
            message: "Credit card has expired",
            fields: [card_last_four, expiry_date],
        },
    }
}

// ============================================================================
// NAMESPACE 3: No namespace (global/default)
// ============================================================================
#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
mod core_errors {
    use waddling_errors_macros::diag;

    diag! {
        strict(component, primary, sequence, naming, duplicates, sequence_values, string_values),
        <json>,

        E.Core.Init.FAILED: {
            message: "Core system initialization failed",
            fields: [subsystem, error_code],
        },
    }
}

#[cfg(all(feature = "doc-gen", feature = "auto-register"))]
fn main() {
    println!("=== Multiple Namespace Example ===\n");

    // Auth service errors (namespace: "auth_service")
    println!("--- Auth Service Namespace ---");
    println!("E_AUTH_TOKEN_EXPIRED:");
    println!("  Code: {}", auth_errors::E_AUTH_TOKEN_EXPIRED.code);
    println!(
        "  Namespace: {:?}",
        auth_errors::E_AUTH_TOKEN_EXPIRED.namespace
    );
    println!(
        "  Namespace Hash: {:?}",
        auth_errors::E_AUTH_TOKEN_EXPIRED.namespace_hash
    );
    println!();

    println!("E_AUTH_TOKEN_INVALID:");
    println!("  Code: {}", auth_errors::E_AUTH_TOKEN_INVALID.code);
    println!(
        "  Namespace: {:?}",
        auth_errors::E_AUTH_TOKEN_INVALID.namespace
    );
    println!(
        "  Namespace Hash: {:?}",
        auth_errors::E_AUTH_TOKEN_INVALID.namespace_hash
    );
    println!();

    // Payment service errors (namespace: "payment_service")
    println!("--- Payment Service Namespace ---");
    println!("E_PAYMENT_CARD_DECLINED:");
    println!("  Code: {}", payment_errors::E_PAYMENT_CARD_DECLINED.code);
    println!(
        "  Namespace: {:?}",
        payment_errors::E_PAYMENT_CARD_DECLINED.namespace
    );
    println!(
        "  Namespace Hash: {:?}",
        payment_errors::E_PAYMENT_CARD_DECLINED.namespace_hash
    );
    println!();

    println!("E_PAYMENT_CARD_EXPIRED:");
    println!("  Code: {}", payment_errors::E_PAYMENT_CARD_EXPIRED.code);
    println!(
        "  Namespace: {:?}",
        payment_errors::E_PAYMENT_CARD_EXPIRED.namespace
    );
    println!(
        "  Namespace Hash: {:?}",
        payment_errors::E_PAYMENT_CARD_EXPIRED.namespace_hash
    );
    println!();

    // Core errors (no namespace)
    println!("--- No Namespace (Global) ---");
    println!("E_CORE_INIT_FAILED:");
    println!("  Code: {}", core_errors::E_CORE_INIT_FAILED.code);
    println!(
        "  Namespace: {:?}",
        core_errors::E_CORE_INIT_FAILED.namespace
    );
    println!(
        "  Namespace Hash: {:?}",
        core_errors::E_CORE_INIT_FAILED.namespace_hash
    );
    println!();

    // Summary
    println!("=== Summary ===");
    println!("Unique namespaces detected:");
    println!("  1. 'auth_service' - 2 errors");
    println!("  2. 'payment_service' - 2 errors");
    println!("  3. None (global) - 1 error");
    println!();
    println!("This would generate 3 separate catalogs when using doc-gen.");

    // Verify namespace hashes are consistent within same namespace
    println!("\n=== Namespace Hash Consistency ===");
    let auth_ns_hash_1 = auth_errors::E_AUTH_TOKEN_EXPIRED.namespace_hash;
    let auth_ns_hash_2 = auth_errors::E_AUTH_TOKEN_INVALID.namespace_hash;
    println!(
        "Auth service namespace hashes match: {}",
        auth_ns_hash_1 == auth_ns_hash_2
    );

    let payment_ns_hash_1 = payment_errors::E_PAYMENT_CARD_DECLINED.namespace_hash;
    let payment_ns_hash_2 = payment_errors::E_PAYMENT_CARD_EXPIRED.namespace_hash;
    println!(
        "Payment service namespace hashes match: {}",
        payment_ns_hash_1 == payment_ns_hash_2
    );

    // Show different namespaces have different hashes
    println!(
        "Auth vs Payment hashes differ: {}",
        auth_ns_hash_1 != payment_ns_hash_1
    );

    // =========================================================================
    // Check the global registry (auto-populated by <json> format + auto-register)
    // =========================================================================
    println!("\n=== Global Registry (auto-registered via <json>) ===\n");

    // Get all registered diagnostics from the global registry
    let diagnostics = waddling_errors::registry::get_diagnostics();
    println!("Total registered diagnostics: {}", diagnostics.len());

    // List all errors by namespace
    println!("\nErrors by namespace:");
    for reg in &diagnostics {
        println!(
            "  - {} (namespace: {:?}, namespace_hash: {:?})",
            reg.diagnostic.runtime.code,
            reg.diagnostic.runtime.namespace,
            reg.diagnostic.runtime.namespace_hash
        );
    }

    // Group errors by namespace to show catalog structure
    println!("\n=== Catalog Structure ===");

    let mut by_namespace: std::collections::HashMap<Option<&str>, Vec<&str>> =
        std::collections::HashMap::new();
    for reg in &diagnostics {
        by_namespace
            .entry(reg.diagnostic.runtime.namespace)
            .or_default()
            .push(reg.diagnostic.runtime.code);
    }

    let mut catalog_count = 0;
    for (namespace, codes) in &by_namespace {
        catalog_count += 1;
        match namespace {
            Some(ns) => println!(
                "Catalog {}: namespace '{}' ({} errors)",
                catalog_count,
                ns,
                codes.len()
            ),
            None => println!(
                "Catalog {}: global/default ({} errors)",
                catalog_count,
                codes.len()
            ),
        }
        for code in codes {
            println!("    - {}", code);
        }
    }

    println!("\n=== Summary ===");
    println!("Total catalogs that would be generated: {}", catalog_count);

    // =========================================================================
    // Generate JSON catalogs using DocRegistry
    // =========================================================================
    println!("\n=== JSON Catalog Generation ===\n");

    use waddling_errors::doc_generator::{DocRegistry, JsonRenderer};

    // Create a DocRegistry and populate it from the global registry
    let mut doc_registry = DocRegistry::new("MultiNamespaceDemo", "1.0.0");

    // Register all diagnostics with the doc registry
    waddling_errors::registry::register_all_with_doc_gen(&mut doc_registry);

    // Show what's in the doc registry
    println!(
        "DocRegistry contains {} errors",
        doc_registry.errors().len()
    );
    for (code, error) in doc_registry.errors() {
        println!("  - {} (namespace: {:?})", code, error.namespace);
    }

    // Render to a temp directory
    let output_dir = std::env::temp_dir().join("multiple_namespaces_output");
    std::fs::create_dir_all(&output_dir).expect("Failed to create output dir");

    doc_registry
        .render(vec![Box::new(JsonRenderer)], &output_dir)
        .expect("Failed to render JSON");

    // Read and display the generated JSON
    let json_path = output_dir.join("MultiNamespaceDemo.json");
    let json_content = std::fs::read_to_string(&json_path).expect("Failed to read generated JSON");

    println!("\n=== Generated JSON Catalog ===\n");
    println!("{}", json_content);

    println!("\n=== Output Location ===");
    println!("JSON catalog written to: {}", json_path.display());
}

// Dummy main when features not enabled
#[cfg(not(all(feature = "doc-gen", feature = "auto-register")))]
fn main() {}