waddling-errors-macros 0.7.3

Procedural macros for structured error codes with compile-time validation and taxonomy enforcement
Documentation
//! Test: `component_location!` macro - standalone location registration
//!
//! This example demonstrates the new `component_location!` macro which allows
//! registering component locations without attaching to a module.
//!
//! ## Features
//!
//! - Multiple component locations per file
//! - No need to wrap code in a module
//! - Role-based visibility control
//! - Works alongside `#[in_component]`
//!
//! ## Run
//!
//! ```bash
//! cargo run --example component_location_standalone --features metadata,doc-gen
//! ```

#[cfg(not(all(feature = "metadata", feature = "doc-gen")))]
compile_error!(
    "\n\n\
    ❌ This example requires both 'metadata' and 'doc-gen' features!\n\
    \n\
    Run with:\n\
    cargo run --example component_location_standalone --features metadata,doc-gen\n\
    "
);

#[cfg(all(feature = "metadata", feature = "doc-gen"))]
#[allow(dead_code)]
use std::fs;
#[cfg(all(feature = "metadata", feature = "doc-gen"))]
use waddling_errors_macros::{component_location, diag, setup};

// ============================================================================
// Setup: Components, Primaries, Sequences
// ============================================================================

#[cfg(all(feature = "metadata", feature = "doc-gen"))]
pub mod components {
    use waddling_errors_macros::component;

    component! {
        pub enum Component {
            Auth {
                docs: "Authentication and authorization",
            },
            Api {
                docs: "External API integrations",
            },
            Crypto {
                docs: "Cryptographic operations",
            },
        }
    }

    pub use Component::*;
}

#[cfg(all(feature = "metadata", feature = "doc-gen"))]
pub mod primaries {
    use waddling_errors_macros::primary;

    primary! {
        pub enum Primary {
            Token {
                docs: "Token-related errors",
            },
            Connection {
                docs: "Connection errors",
            },
            Key {
                docs: "Key errors",
            },
        }
    }
}

#[cfg(all(feature = "metadata", feature = "doc-gen"))]
pub mod sequences {
    use waddling_errors_macros::sequence;

    sequence! {
        EXPIRED(1) {
            description: "Resource has expired",
        },
        INVALID(2) {
            description: "Resource is invalid",
        },
        MISSING(3) {
            description: "Resource is missing",
        },
    }
}

#[cfg(all(feature = "metadata", feature = "doc-gen"))]
setup! {
    components = crate::components,
    primaries = crate::primaries,
    sequences = crate::sequences,
}

#[cfg(not(all(feature = "metadata", feature = "doc-gen")))]
pub mod components {}
#[cfg(not(all(feature = "metadata", feature = "doc-gen")))]
pub mod primaries {}
#[cfg(not(all(feature = "metadata", feature = "doc-gen")))]
pub mod sequences {}

// ============================================================================
// Using component_location! - Multiple components in a single file
// ============================================================================

// This file is shared between Auth and Crypto components
// We can register BOTH component locations without needing separate modules!

#[cfg(all(feature = "metadata", feature = "doc-gen"))]
component_location!(Auth, role = public);
#[cfg(all(feature = "metadata", feature = "doc-gen"))]
component_location!(Crypto, role = developer);

// API component is also partially implemented here
#[cfg(all(feature = "metadata", feature = "doc-gen"))]
component_location!(Api, role = internal);

// Define some errors for Auth
#[cfg(all(feature = "metadata", feature = "doc-gen"))]
diag! {
    strict(component, primary, sequence, naming, duplicates, sequence_values, string_values),
    E.Auth.Token.EXPIRED: {
        message: "JWT token has expired",
        hints: ["Request a new token"],
    },
    E.Auth.Token.INVALID: {
        message: "JWT token is invalid",
        hints: ["Check token format"],
    },
}

// Define some errors for Crypto
#[cfg(all(feature = "metadata", feature = "doc-gen"))]
diag! {
    strict(component, primary, sequence, naming, duplicates, sequence_values, string_values),
    E.Crypto.Key.MISSING: {
        message: "Encryption key not found",
        hints: ["Check key configuration"],
    },
}

// Define some errors for API
#[cfg(all(feature = "metadata", feature = "doc-gen"))]
diag! {
    strict(component, primary, sequence, naming, duplicates, sequence_values, string_values),
    E.Api.Connection.INVALID: {
        message: "API endpoint configuration invalid",
        hints: ["Verify API URL"],
    },
}

// ============================================================================
// Simulated separate module (showing #[in_component] still works)
// ============================================================================

#[cfg(all(feature = "metadata", feature = "doc-gen"))]
mod auth_internal {
    use waddling_errors_macros::in_component;

    // Make the module public so we can access its items
    #[in_component(Auth, role = internal)]
    pub mod jwt_impl {

        #[allow(dead_code)]
        pub fn verify_signature() {
            println!("Verifying JWT signature...");
        }

        waddling_errors_macros::diag! {
            strict(component, primary, sequence, naming, duplicates, sequence_values, string_values),
            E.Auth.Token.MISSING: {
                message: "JWT token not provided",
                hints: ["Include Authorization header"],
            },
        }
    }

    pub use jwt_impl::*;
}

// ============================================================================
// Main: Test and verify
// ============================================================================

#[cfg(all(feature = "metadata", feature = "doc-gen"))]
fn main() {
    use waddling_errors::doc_generator::{DocRegistry, JsonRenderer};

    println!("🦆 component_location! Macro Test");
    println!("==================================\n");

    println!("📋 This example demonstrates:");
    println!("   - Multiple component locations in ONE file");
    println!("   - No need to wrap code in modules");
    println!("   - Role-based visibility for each location");
    println!();

    // Step 1: Create registry and register diagnostics
    let mut registry = DocRegistry::new("component_location_test", "1.0.0");

    println!("📝 Registering diagnostics...");
    registry.register_diagnostic_runtime(&E_AUTH_TOKEN_EXPIRED);
    registry.register_diagnostic_runtime(&E_AUTH_TOKEN_INVALID);
    registry.register_diagnostic_runtime(&E_CRYPTO_KEY_MISSING);
    registry.register_diagnostic_runtime(&E_API_CONNECTION_INVALID);
    registry.register_diagnostic_runtime(&auth_internal::E_AUTH_TOKEN_MISSING);
    println!("   ✓ Registered 5 diagnostics");

    // Step 2: Register component locations from component_location! macro
    println!("\n📍 Registering component locations...\n");

    // Access the generated marker modules: __component_loc_<component_lowercase>
    // These provide: COMPONENT, FILE, MODULE_PATH, ROLE constants and register() function
    __component_loc_auth::register(&mut registry);
    __component_loc_crypto::register(&mut registry);
    __component_loc_api::register(&mut registry);

    // This is from #[in_component] - different pattern
    auth_internal::jwt_impl::__register_component_location(&mut registry);

    println!("   ✓ Registered locations:");
    println!("     - AUTH (from component_location! - public)");
    println!("     - CRYPTO (from component_location! - developer)");
    println!("     - API (from component_location! - internal)");
    println!("     - AUTH (from #[in_component] - internal)");

    // Step 3: Verify constants are correct
    println!("\n🔍 Verifying generated constants...");

    // Check component names match how they're written (preserves case to match component! macro)
    assert_eq!(__component_loc_auth::COMPONENT, "Auth");
    assert_eq!(__component_loc_crypto::COMPONENT, "Crypto");
    assert_eq!(__component_loc_api::COMPONENT, "Api");
    println!("   ✓ Component names match component! registration");

    // Check file paths
    assert!(__component_loc_auth::FILE.contains("component_location_standalone.rs"));
    println!("   ✓ File paths are correct");

    // Step 4: Generate documentation
    let output_dir = "target/doc/component_location_test";
    println!("\n📚 Generating documentation to {}/...", output_dir);

    match registry.render_all_roles(vec![Box::new(JsonRenderer)], output_dir) {
        Ok(_) => println!("   ✓ Documentation generated successfully"),
        Err(e) => {
            eprintln!("   ❌ Failed: {}", e);
            std::process::exit(1);
        }
    }

    // Step 5: Verify JSON output
    println!("\n🔬 Verifying JSON output...\n");

    let int_json = fs::read_to_string(format!("{}/component_location_test-int.json", output_dir))
        .expect("Failed to read internal JSON");

    // Check that all components have locations
    assert!(int_json.contains("\"AUTH\""), "AUTH component missing");
    assert!(int_json.contains("\"CRYPTO\""), "CRYPTO component missing");
    assert!(int_json.contains("\"API\""), "API component missing");

    // Count locations
    let auth_section = int_json.find("\"AUTH\"").unwrap();
    let auth_end = int_json[auth_section..].find('}').unwrap() + auth_section;
    let auth_content = &int_json[auth_section..auth_end];
    let auth_locations = auth_content.matches("\"path\":").count();

    println!("   Component locations in internal JSON:");
    println!("     - AUTH: {} locations", auth_locations);
    println!("     - CRYPTO: has Public/Developer visible locations");
    println!("     - API: has Internal only locations");

    println!("\n✅ All tests passed!");
    println!("\n📋 Summary:");
    println!("   - component_location! allows multiple components per file");
    println!("   - Each call generates its own marker module");
    println!("   - Constants are accessible via __component_loc_<name>::*");
    println!("   - Works alongside #[in_component] on modules");
    println!();
    println!("🎉 The component_location! macro is working correctly!");
}

// Dummy main when features are not enabled (compile_error! will show first)
#[cfg(not(all(feature = "metadata", feature = "doc-gen")))]
fn main() {}