# waddling-errors-macros
**Procedural macros for ergonomic error code definitions**
[](https://crates.io/crates/waddling-errors-macros)
[](https://docs.rs/waddling-errors-macros)
[](../LICENSE)
---
## Overview
This crate provides procedural macros for [waddling-errors](../waddling-errors), reducing boilerplate by ~70% compared to the manual trait-based approach.
**Manual approach:**
```rust
// Define enum
#[derive(Debug, Copy, Clone)]
enum Component { Auth }
// Implement trait
impl ComponentId for Component {
fn as_str(&self) -> &'static str {
match self { Component::Auth => "AUTH" }
}
}
// Create constant
const ERR: Code<Component, Primary> = Code::error(Component::Auth, Primary::Token, 1);
```
**Macro approach:**
```rust
component! { Auth { value: "AUTH", docs: "Authentication" } }
diag! { E.AUTH.TOKEN.MISSING: { message: "Token missing" } }
```
---
## Installation
```toml
[dependencies]
waddling-errors = "0.5"
waddling-errors-macros = "0.5"
# For documentation generation
[features]
doc-gen = ["waddling-errors-macros/doc-gen", "waddling-errors-macros/metadata"]
```
---
## Quick Start
```rust
use waddling_errors_macros::{component, primary, diag};
// Define components
component! {
Auth {
value: "AUTH",
docs: "Authentication and authorization",
tags: ["security", "user-management"],
},
}
// Define primary categories
primary! {
Token {
value: "TOKEN",
docs: "Token-related errors",
related: ["Session"],
},
}
// Define diagnostics
diag! {
E.AUTH.TOKEN.EXPIRED: {
message: "JWT token expired at {timestamp}",
fields: [timestamp],
'CR 'Pub description: "Your session has expired. Please log in again.",
'CR 'Dev description: "Token TTL exceeded. Check refresh logic.",
'CR 'Int description: "Query auth_tokens table for debug info.",
'R role: "Public",
'R tags: ["auth", "session"],
},
}
// Use the generated constant
let error = E_AUTH_TOKEN_EXPIRED;
println!("{}", error.runtime.full_code()); // E.AUTH.TOKEN.EXPIRED
```
---
## Macros
### `component!` - Define Components
```rust
component! {
ComponentName {
value: "COMPONENT_VALUE", // Required: UPPER_SNAKE_CASE
docs: "Component description", // Optional
tags: ["tag1", "tag2"], // Optional
related: ["OtherComponent"], // Optional
},
}
```
### `primary!` - Define Primary Categories
```rust
primary! {
CategoryName {
value: "CATEGORY_VALUE", // Required: UPPER_SNAKE_CASE
docs: "Category description", // Optional
tags: ["tag1", "tag2"], // Optional
related: ["OtherCategory"], // Optional
},
}
```
### `sequence!` - Define Sequences
```rust
pub mod sequences {
use waddling_errors_macros::sequence;
sequence! {
MISSING(1) {
description: "Required item not provided",
typical_severity: "Error",
hints: ["Check required parameters"],
},
}
}
```
### `diag!` - Define Diagnostics
```rust
diag! {
// Optional: Auto-register for doc generation
<json, html, catalog>,
SEVERITY.COMPONENT.PRIMARY.SEQUENCE: {
message: "Error message with {field}",
fields: [field],
// Role-based descriptions
'CR 'Pub description: "User-facing description",
'CR 'Dev description: "Developer description",
'CR 'Int description: "Internal team description",
// Metadata
'R role: "Public",
'R tags: ["tag1", "tag2"],
'C docs_url: "https://docs.example.com/errors",
},
}
```
---
## Visibility Markers
Control where metadata appears using visibility markers:
| `'C` | Compile-time only | Documentation, code snippets, URLs |
| `'R` | Runtime only | Role, tags, runtime categorization |
| `'CR` or `'RC` | Both contexts | Descriptions, hints, messages |
**Why this matters:**
- **Smaller binaries** - Documentation doesn't bloat production code
- **Flexible content** - Different hints for docs vs runtime
- **Security** - Keep sensitive info out of binaries
### Example
```rust
diag! {
E.AUTH.SECRET.ROTATION_FAILED: {
message: "Secret rotation failed",
// Documentation: verbose explanation
'C description: "Key rotation failed during HSM communication. \
Check network connectivity to key management service...",
// Runtime: concise message
'R description: "Key rotation failed",
// Both: end-user hint
'CR hints: ["Contact security team if issue persists"],
// Documentation only
'C code_snippet: {
wrong: "rotate_key()",
correct: "rotate_key().with_retry(3)",
},
// Runtime only
'R role: "Internal",
},
}
```
### Field Defaults
When no marker is specified:
| `description`, `hints` | `'CR` | Useful in both contexts |
| `role`, `tags`, `related_codes` | `'R` | Runtime behavior |
| `code_snippet`, `docs_url`, `introduced`, `deprecated` | `'C` | Documentation only |
---
## Role-Based Documentation
Support three documentation roles with gated fields:
```rust
diag! {
E.AUTH.TOKEN.EXPIRED: {
message: "Token expired",
// Public: sanitized, safe for end users
'CR 'Pub description: "Your session has expired.",
'CR 'Pub hints: ["Click login button"],
// Developer: debugging context
'CR 'Dev description: "JWT token TTL exceeded (3600s default).",
'CR 'Dev hints: ["Check token refresh logic", "Verify server time sync"],
// Internal: full transparency
'CR 'Int description: "Token expired. Redis key: auth:token:{user_id}",
'CR 'Int hints: ["Query auth_tokens table", "Check token_refresh_log"],
'R role: "Public", // Minimum role to see this error
},
}
```
**Generated constants access role-specific fields:**
```rust
// Runtime access
let hints_pub = error.hints_for_role(Role::Public);
let hints_dev = error.hints_for_role(Role::Developer);
let hints_int = error.hints_for_role(Role::Internal);
```
---
## Component Location Tracking
Mark where components are implemented with role-based filtering:
```rust
use waddling_errors_macros::in_component;
// Public documentation example (visible to all)
#[in_component(Auth, role = public)]
mod auth_example {
// Example code
}
// Internal implementation (default: internal role)
#[in_component(Auth)]
mod auth_internals {
// Internal implementation
}
// Developer debugging utilities
#[in_component(Auth, role = developer)]
mod auth_debug {
// Debug utilities
}
```
**Security**: File paths are filtered by role in generated documentation!
---
## Compile-Time Validation
Enable strict validation to catch errors at compile time:
```rust
#[validate_strict]
component! {
Auth {
value: "AUTH", // Must be UPPER_SNAKE_CASE
docs: "Authentication",
}
}
#[validate_strict]
diag! {
E.AUTH.TOKEN.INVALID: {
message: "Invalid token",
// Compiler checks:
// - Component "AUTH" exists
// - Primary "TOKEN" exists
// - Sequence value is valid
// - Severity is valid
}
}
```
Use `#[validate_relaxed]` for prototyping.
See [VALIDATION.md](VALIDATION.md) for complete guide.
---
## Auto-Registration
Automatically register diagnostics for documentation generation:
```rust
diag! {
<json, html, catalog>, // Auto-register with these renderers
E.AUTH.TOKEN.EXPIRED: {
message: "Token expired",
'CR 'Pub description: "Session expired",
},
}
```
Enable with `auto-register` feature:
```toml
[dependencies]
waddling-errors-macros = { version = "0.5", features = ["auto-register"] }
```
---
## Generated Constants
The `diag!` macro generates up to three constants:
```rust
// ✅ Always generated (no feature flags needed)
pub const E_AUTH_TOKEN_EXPIRED: DiagnosticRuntime = /* ... */;
// 📚 Only with 'metadata' feature
#[cfg(feature = "metadata")]
pub const E_AUTH_TOKEN_EXPIRED_DOCS: DiagnosticDocs = /* ... */;
#[cfg(feature = "metadata")]
pub const E_AUTH_TOKEN_EXPIRED_COMPLETE: DiagnosticComplete = /* ... */;
```
**Usage:**
```rust
// Production: lightweight runtime
let error = E_AUTH_TOKEN_EXPIRED;
println!("Code: {}", error.runtime.full_code());
println!("Message: {}", error.runtime.message);
// Documentation generation: full metadata
#[cfg(feature = "metadata")]
fn generate_docs(registry: &mut DocRegistry) {
let complete = E_AUTH_TOKEN_EXPIRED_COMPLETE;
registry.register_diagnostic(&complete)?;
}
```
---
## Examples
Run examples to see macros in action:
```bash
# Complete system with macros (~70% less code than manual)
cargo run --example complete_system --features "metadata,doc-gen,auto-register"
# Browser-server catalog for IoT/mobile
cargo run --example browser_server_catalog --features "metadata,hash"
# Component location security
cargo run --example component_location_security --features "metadata,doc-gen"
# Compile-time validation
cargo run --example strict_validation_demo --features "metadata"
# Custom XML renderer
cargo run --example custom_xml_renderer --features "metadata,doc-gen,auto-register"
# WASM/no_std
cargo run --example no_std_wasm --features "metadata"
```
See [examples/](examples/) directory for all examples.
---
## Manual vs Macro Approach
| **Boilerplate** | High (~100 lines per component) | Low (~10 lines) |
| **Type safety** | Manual trait impl | Automatic |
| **Validation** | Runtime only | Compile-time + runtime |
| **Role gating** | Manual filtering | Automatic |
| **Auto-registration** | Manual calls | Automatic (with feature) |
| **Learning curve** | Steep (traits + generics) | Gentle (declarative) |
| **Flexibility** | Full control | Sufficient for most use cases |
**Use manual approach when:**
- You need maximum flexibility
- You're integrating with existing trait-based code
- You want to avoid proc macros
**Use macro approach when:**
- You want less boilerplate (~70% reduction)
- You want compile-time validation
- You're starting a new project
- You want automatic doc generation
See [waddling-errors/examples/complete_system](../waddling-errors/examples/complete_system) for manual approach comparison.
---
## Features
| `metadata` | Enable compile-time documentation metadata | ❌ |
| `doc-gen` | Enable documentation generation (includes metadata) | ❌ |
| `auto-register` | Automatic diagnostic registration | ❌ |
| `hash` | Base62 hash code generation | ❌ |
---
## Documentation
- **[Examples](examples/README.md)** - Comprehensive examples with explanations
- **[Validation Guide](VALIDATION.md)** - Compile-time validation modes
- **[Component Location Roles](../docs/COMPONENT_LOCATION_ROLES.md)** - File path security
- **[In-Component Attribute](../docs/IN_COMPONENT_ROLE_SUPPORT.md)** - Component tracking
- **[API Documentation](https://docs.rs/waddling-errors-macros)** - Full API reference
---
## When to Use Macros
**✅ Perfect For:**
- New projects starting fresh
- Teams wanting less boilerplate
- Projects needing compile-time validation
- Documentation-heavy error systems
- Multi-role security requirements
**❌ Consider Manual Approach:**
- Maximum flexibility needed
- Existing trait-based codebase
- Proc macro avoidance required
- Learning trait system is goal
---
## License
Dual-licensed under MIT or Apache-2.0. See [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE](LICENSE-APACHE).