waddling-errors-macros
Procedural macros for ergonomic error code definitions
Overview
This crate provides procedural macros for waddling-errors, reducing boilerplate by ~70% compared to the manual trait-based approach.
Manual approach:
// Define enum
// Implement trait
// Create constant
const ERR: = error;
Macro approach:
component!
diag!
Installation
[]
= "0.5"
= "0.5"
# For documentation generation
[]
= ["waddling-errors-macros/doc-gen", "waddling-errors-macros/metadata"]
Quick Start
use ;
// Define components
component!
// Define primary categories
primary!
// Define diagnostics
diag!
// Use the generated constant
let error = E_AUTH_TOKEN_EXPIRED;
println!; // E.AUTH.TOKEN.EXPIRED
Macros
component! - Define Components
component!
primary! - Define Primary Categories
primary!
sequence! - Define Sequences
diag! - Define Diagnostics
diag!
Field Placeholders:
{{field}}- Non-PII field, sent infobject in wire protocol{{pii/field}}- PII field, sent inpii.dataobject for access-controlled handling
Visibility Markers
Control where metadata appears using visibility markers:
| Marker | Context | Use Case |
|---|---|---|
'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
diag!
Field Defaults
When no marker is specified:
| Field | Default | Rationale |
|---|---|---|
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:
diag!
Generated constants access role-specific fields:
// Runtime access
let hints_pub = error.hints_for_role;
let hints_dev = error.hints_for_role;
let hints_int = error.hints_for_role;
Component Location Tracking
Mark where components are implemented with role-based filtering:
use in_component;
// Public documentation example (visible to all)
// Internal implementation (default: internal role)
// Developer debugging utilities
Security: File paths are filtered by role in generated documentation!
Compile-Time Validation
Enable strict validation to catch errors at compile time:
component!
diag!
Use #[validate_relaxed] for prototyping.
See VALIDATION.md for complete guide.
Auto-Registration
Automatically register diagnostics for documentation generation:
diag!
Enable with auto-register feature:
[]
= { = "0.5", = ["auto-register"] }
Documentation Generation Workflow
Complete end-to-end workflow for generating documentation:
Step 1: Define Errors with Auto-Registration
// src/errors/mod.rs
use ;
// Configure paths (in lib.rs or main.rs)
setup!
component!
primary!
diag!
Step 2: Create Separate Doc Generation Binary
// src/bin/doc_gen.rs
use *; // Imports all errors → triggers auto-registration
use ;
use registry;
Step 3: Configure Cargo.toml
# In your Cargo.toml
[]
= { = "0.5", = ["metadata"] }
= { = "0.5", = ["metadata", "auto-register"] }
[]
= { = "0.5", = ["doc-gen", "auto-register"] }
[[]]
= "doc-gen"
= "src/bin/doc_gen.rs"
= ["waddling-errors/doc-gen", "waddling-errors-macros/auto-register"]
Step 4: Generate Documentation
# Generate docs
# Or create a cargo alias in .cargo/config.toml:
# [alias]
# docs = "run --bin doc-gen --features waddling-errors/doc-gen,waddling-errors-macros/auto-register"
Why This Pattern?
✅ Advantages:
- Clean main binary: No doc-gen dependencies, no error imports
- Zero boilerplate:
<json, html>indiag!handles registration - Separate concerns: Doc generation isolated from app logic
- Production-ready: Main binary has zero doc-gen overhead
- Idiomatic Rust: Same pattern as criterion benchmarks, cargo-bench
Production binary (cargo build --release):
- No serde, serde_json
- No HTML/JSON renderers
- No doc generation code
- Only lightweight error structs with WDP hashes
Doc-gen binary (cargo run --bin doc-gen):
- Only runs during development/CI
- Never ships to production
- Loads errors → triggers auto-registration → generates docs
Generated Constants
The diag! macro generates up to three constants:
// ✅ Always generated (no feature flags needed)
pub const E_AUTH_TOKEN_EXPIRED: DiagnosticRuntime = /* ... */;
// 📚 Only with 'metadata' feature
pub const E_AUTH_TOKEN_EXPIRED_DOCS: DiagnosticDocs = /* ... */;
pub const E_AUTH_TOKEN_EXPIRED_COMPLETE: DiagnosticComplete = /* ... */;
Usage:
// Production: lightweight runtime
let error = E_AUTH_TOKEN_EXPIRED;
println!;
println!;
// Documentation generation: full metadata
Examples
Run examples to see macros in action:
# Complete system with macros (~70% less code than manual)
# Browser-server catalog for IoT/mobile
# Component location security
# Compile-time validation
# Custom XML renderer
# WASM/no_std
See examples/ directory for all examples.
Manual vs Macro Approach
| Aspect | Manual | Macros |
|---|---|---|
| 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 for manual approach comparison.
Features
| Feature | Description | Default |
|---|---|---|
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 - Comprehensive examples with explanations
- Validation Guide - Compile-time validation modes
- Component Location Roles - File path security
- In-Component Attribute - Component tracking
- API Documentation - 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 and LICENSE-APACHE.