# Compile-Time Validation System
Complete guide to the `diag!` macro's validation capabilities in `waddling-errors-macros`.
---
## Table of Contents
1. [Overview](#overview)
2. [Validation Modes](#validation-modes)
3. [Usage Examples](#usage-examples)
4. [Error Messages](#error-messages)
5. [Best Practices](#best-practices)
6. [Testing](#testing)
---
## Overview
The `diag!` macro provides **7 granular validation modes** to catch errors at compile time:
| `sequence` | Sequence constant exists | Always (catches typos) |
| `primary` | Primary constant exists | When using primary hub |
| `component` | Component enum variant exists | When using component hub |
| `naming` | UPPER_SNAKE_CASE / PascalCase | To enforce conventions |
| `duplicates` | No duplicate codes in block | To prevent conflicts |
| `sequence_values` | Sequence is u16 type | Type safety |
| `string_values` | Primary is &str, component is enum | Type safety |
### Philosophy
- **Fail fast**: Catch errors at compile time, not runtime
- **Granular control**: Enable only the validations you need
- **Clear messages**: Helpful error messages with suggestions
- **Zero overhead**: All checks happen at compile time
---
## Validation Modes
### 1. Sequence Validation (`sequence`)
**Validates:** The sequence constant exists in `crate::sequences`
```rust
diag! {
strict(sequence),
E.Auth.Token.EXPIRED: {
message: "Token expired",
}
}
```
**What it does:**
- ✅ Checks that `crate::sequences::EXPIRED` exists
- ✅ Prevents typos like `EXPRIED` or `EXIRED`
- ✅ Generates compile-time reference: `let _ = crate::sequences::EXPIRED;`
**Error example:**
```
error[E0425]: cannot find value `EXPRIED` in module `crate::sequences`
--> src/diagnostics.rs:42:18
|
```
---
### 2. Primary Validation (`primary`)
**Validates:** The primary constant exists in `crate::primaries`
```rust
diag! {
strict(primary),
E.Auth.Token.EXPIRED: {
message: "Token expired",
}
}
```
**What it does:**
- ✅ Checks that `crate::primaries::Token` exists
- ✅ Prevents typos like `Tokne` or `Toekn`
- ✅ Generates compile-time reference: `let _ = crate::primaries::Token;`
**Error example:**
```
error[E0425]: cannot find value `Tokne` in module `crate::primaries`
--> src/diagnostics.rs:42:12
|
```
---
### 3. Component Validation (`component`)
**Validates:** The component enum variant exists in `crate::components`
```rust
diag! {
strict(component),
E.Auth.Token.EXPIRED: {
message: "Token expired",
}
}
```
**What it does:**
- ✅ Checks that `crate::components::Auth` exists
- ✅ Prevents typos like `Auht` or `Atuh`
- ✅ Generates compile-time reference: `crate::components::Auth;`
**Error example:**
```
error[E0425]: cannot find value `Auht` in module `crate::components`
--> src/diagnostics.rs:42:7
|
```
---
### 4. Naming Convention Validation (`naming`)
**Validates:** Naming follows standard conventions
```rust
diag! {
strict(naming),
E.Auth.Token.EXPIRED: {
message: "Token expired",
}
}
```
**What it checks:**
- ✅ **Sequences**: UPPER_SNAKE_CASE (e.g., `EXPIRED`, `MISSING`, `NOT_FOUND`)
- ✅ **Primaries**: PascalCase (e.g., `Token`, `Permission`, `Connection`)
- ✅ **Components**: PascalCase (e.g., `Auth`, `Database`, `ApiGateway`)
**Error examples:**
Lowercase sequence:
```
error: Sequence 'expired' should be UPPER_SNAKE_CASE (e.g., 'MISSING', 'INVALID', 'EXPIRED')
--> src/diagnostics.rs:42:18
|
```
Lowercase primary:
```
error: Primary 'token' should be PascalCase (e.g., 'Token', 'Permission', 'Connection')
--> src/diagnostics.rs:42:12
|
```
**Rules:**
- Sequences must not start/end with underscore
- Sequences must not have consecutive underscores
- PascalCase must start with uppercase letter
- PascalCase must contain only letters and digits (no underscores)
---
### 5. Duplicate Detection (`duplicates`)
**Validates:** No duplicate diagnostic codes within the same block
```rust
diag! {
strict(duplicates),
E.Auth.Token.MISSING: {
message: "First definition",
},
E.Auth.Token.MISSING: { // ❌ Error: Duplicate!
message: "Second definition",
}
}
```
**What it does:**
- ✅ Tracks all diagnostic codes in the block
- ✅ Detects exact duplicates (Severity.Component.Primary.Sequence)
- ✅ Shows both occurrences in error message
**Error example:**
```
error: Duplicate diagnostic code 'E.Auth.Token.MISSING'. This code was already defined earlier in this block.
Each diagnostic code must be unique within a block.
--> src/diagnostics.rs:47:5
|
error: First occurrence of 'E.Auth.Token.MISSING' is here
--> src/diagnostics.rs:42:5
|
```
**Important:** Duplicates are checked **per block**, not across files. Different `diag!` blocks can have the same code.
---
### 6. Sequence Value Validation (`sequence_values`)
**Validates:** Sequence constants are of type `u16`
```rust
diag! {
strict(sequence_values),
E.Auth.Token.EXPIRED: {
message: "Token expired",
}
}
```
**What it does:**
- ✅ Type-checks that `crate::sequences::EXPIRED` is `u16`
- ✅ Ensures sequences are valid numeric constants
- ✅ Generates: `let _seq_value: u16 = crate::sequences::EXPIRED;`
**Purpose:**
- Catch accidental string constants (`const EXPIRED: &str = "17"`)
- Ensure numeric types are correct
- Validate that sequences are numbers, not other types
**Note:** The actual numeric value (e.g., 17) is enforced by the `sequence!` macro, not by `diag!`. This validation ensures the **type** is correct.
---
### 7. String Value Validation (`string_values`)
**Validates:** Primaries are `&str` constants and components are enum variants
```rust
diag! {
strict(string_values),
E.Auth.Token.EXPIRED: {
message: "Token expired",
}
}
```
**What it does:**
- ✅ Type-checks that `crate::primaries::Token` is `&str`
- ✅ Verifies that `crate::components::Auth` is an enum variant
- ✅ Generates: `let _primary: &str = crate::primaries::Token;`
**Purpose:**
- Catch accidental numeric constants (`const Token: u16 = 1`)
- Ensure string types are correct
- Validate that primaries are strings, not other types
**Note:** The actual string values (e.g., "TOKEN") are enforced by the `primary!` and `component!` macros. This validation ensures the **types** are correct.
---
## Usage Examples
### Recommended: Full Validation
```rust
diag! {
strict(sequence, primary, component, naming, duplicates, sequence_values, string_values),
E.Auth.Token.EXPIRED: {
message: "Authentication token has expired",
hints: ["Refresh your authentication token"],
},
E.Auth.Permission.DENIED: {
message: "Permission denied for this operation",
hints: ["Check your user permissions"],
}
}
```
**Best for:** Production code, shared libraries, public APIs
---
### Common: Basic Validation
```rust
diag! {
strict(sequence, primary, component),
E.Auth.Token.MISSING: {
message: "Token is missing",
}
}
```
**Best for:** Most projects, catches the most common errors
---
### Minimal: Sequence Only
```rust
diag! {
strict(sequence),
E.Auth.Token.INVALID: {
message: "Token is invalid",
}
}
```
**Best for:** Rapid prototyping, when hubs aren't fully set up
---
### Flexible: Relaxed Mode
```rust
diag! {
relaxed, // or omit strict() entirely
E.AnyComponent.AnyPrimary.ANY_SEQUENCE: {
message: "Maximum flexibility - no validation",
}
}
```
**Best for:** Experimentation, dynamic code generation, when references aren't available yet
---
### Selective: Choose Your Validations
```rust
// Only validate naming and duplicates
diag! {
strict(naming, duplicates),
E.Auth.Token.EXPIRED: { ... }
}
// Only validate existence, skip naming
diag! {
strict(sequence, primary, component),
E.Auth.Token.EXPIRED: { ... }
}
// Only validate types, skip existence checks
diag! {
strict(sequence_values, string_values),
E.Auth.Token.EXPIRED: { ... }
}
```
**Best for:** Custom workflows, incremental adoption, specific requirements
---
## Error Messages
### Error Message Anatomy
All validation errors follow this pattern:
```
error: [Brief description]
--> [file path]:[line]:[column]
|
|
= note: [Additional context or suggestions]
```
### Common Errors and Solutions
#### Missing Sequence
**Error:**
```
error[E0425]: cannot find value `EXPIERD` in module `crate::sequences`
```
**Solution:** Fix the typo or add the sequence to your sequences module
```rust
sequence! {
EXPIRED(17) { ... } // ← Add this
}
```
#### Missing Primary
**Error:**
```
error[E0425]: cannot find value `Tokne` in module `crate::primaries`
```
**Solution:** Fix the typo or add the primary
```rust
primary! {
pub enum Primary {
Token { ... } // ← Use correct spelling
}
}
```
#### Invalid Naming
**Error:**
```
error: Sequence 'Missing' should be UPPER_SNAKE_CASE
```
**Solution:** Use uppercase with underscores
```rust
E.Auth.Token.MISSING // ✅ Correct
E.Auth.Token.Missing // ❌ Wrong
```
#### Duplicate Code
**Error:**
```
error: Duplicate diagnostic code 'E.Auth.Token.MISSING'
```
**Solution:** Use a different sequence or wrap in modules
```rust
// Option 1: Use different sequence
E.Auth.Token.MISSING: { ... }
E.Auth.Token.INVALID: { ... } // ✅ Different
// Option 2: Separate blocks
mod block1 {
diag! {
E.Auth.Token.MISSING: { ... }
}
}
mod block2 {
diag! {
E.Auth.Token.MISSING: { ... } // ✅ Different block
}
}
```
---
## Best Practices
### 1. Start Strict, Relax as Needed
```rust
// Start with full validation
diag! {
strict(sequence, primary, component, naming, duplicates),
// ...
}
// Relax if blocking development
diag! {
strict(sequence), // Minimal but useful
// ...
}
// Relax further if needed
diag! {
relaxed,
// ...
}
```
### 2. Use Full Validation in CI
```yaml
# .github/workflows/ci.yml
- name: Test with full validation
run: cargo test --features metadata
env:
RUSTFLAGS: "-D warnings"
```
All tests use `strict(...)` by default, ensuring your CI catches validation errors.
### 3. Enable All Validations for Public APIs
```rust
// src/errors.rs (public API)
diag! {
strict(sequence, primary, component, naming, duplicates, sequence_values, string_values),
E.Auth.Token.EXPIRED: {
message: "Token has expired",
}
}
```
Public APIs benefit most from strict validation.
### 4. Document Your Validation Policy
```rust
//! # Validation Policy
//!
//! All diagnostics use `strict(sequence, primary, component, naming, duplicates)`.
//!
//! This ensures:
//! - No typos in sequence/primary/component references
//! - Consistent naming conventions
//! - No duplicate diagnostic codes
```
### 5. Use Relaxed Mode for Generated Code
```rust
// build.rs or code generation
println!(r#"
diag! {{
relaxed, // Generated code doesn't need validation
E.Generated.Code.{}: {{
message: "{}",
}}
}}
"#, sequence, message);
```
### 6. Validate in Development, Document in Production
```rust
#[cfg(debug_assertions)]
diag! {
strict(sequence, primary, component, naming, duplicates, sequence_values, string_values),
E.Auth.Token.EXPIRED: { ... }
}
#[cfg(not(debug_assertions))]
diag! {
strict(sequence), // Minimal for release builds
E.Auth.Token.EXPIRED: { ... }
}
```
Though validation is compile-time, you can conditionally compile different validation levels.
---
## Testing
### Running Validation Tests
```bash
# Run all validation tests
cargo test -p waddling-errors-macros --test compile_fail --features metadata
# Run only compile-fail tests
cargo test -p waddling-errors-macros compile_fail_tests --features metadata
# Run only compile-pass tests
cargo test -p waddling-errors-macros compile_pass_tests --features metadata
```
### Test Coverage
| `sequence` | ✅ `invalid_sequence.rs` | ✅ `strict_mode_with_valid_refs.rs` |
| `primary` | ✅ `invalid_primary.rs` | ✅ `strict_mode_with_valid_refs.rs` |
| `component` | ✅ `invalid_component.rs` | ✅ `strict_mode_with_valid_refs.rs` |
| `naming` | ✅ `invalid_naming_*.rs` (3 tests) | ✅ `valid_naming_conventions.rs` |
| `duplicates` | ✅ `duplicate_diagnostic_codes.rs` | ✅ `no_duplicate_codes.rs` |
| `sequence_values` | N/A (type check) | ✅ `validates_sequence_values.rs` |
| `string_values` | N/A (type check) | ✅ `validates_string_values.rs` |
| `relaxed` | N/A | ✅ `relaxed_mode_allows_invalid.rs` |
### Adding Custom Validation Tests
See `tests/README.md` for detailed instructions on adding tests.
---
## Migration Guide
### From No Validation to Basic Validation
```rust
// Before
diag! {
E.Auth.Token.EXPIRED: { message: "..." }
}
// After
diag! {
strict(sequence),
E.Auth.Token.EXPIRED: { message: "..." }
}
```
**Impact:** May reveal typos or missing sequences. Fix incrementally.
### From Basic to Full Validation
```rust
// Before
diag! {
strict(sequence),
E.Auth.Token.EXPIRED: { message: "..." }
}
// After
diag! {
strict(sequence, primary, component, naming, duplicates, sequence_values, string_values),
E.Auth.Token.EXPIRED: { message: "..." }
}
```
**Impact:** May reveal naming issues or duplicates. Fix before enabling.
---
## Performance
### Compile-Time Impact
- **Minimal**: Validation runs during macro expansion
- **No runtime cost**: All checks happen at compile time
- **Incremental**: Only validates when `diag!` blocks change
### Build Time
| `relaxed` | 0 ms (baseline) |
| `strict(sequence)` | +1-2 ms per block |
| Full validation | +3-5 ms per block |
**For 100 diagnostic blocks:** ~300-500 ms total overhead (negligible in most projects)
---
## FAQ
### Q: Should I always use full validation?
**A:** In production code, yes. For prototyping, start with `strict(sequence)` and add more as needed.
### Q: Does validation affect runtime performance?
**A:** No. All validation happens at compile time. Zero runtime overhead.
### Q: Can I disable validation for specific diagnostics?
**A:** Yes, use separate `diag!` blocks with different validation modes:
```rust
// Strict validation for public APIs
diag! {
strict(sequence, primary, component, naming),
E.Auth.Token.EXPIRED: { ... }
}
// Relaxed for internal/dynamic code
diag! {
relaxed,
E.Internal.Dynamic.CODE: { ... }
}
```
### Q: What if I have false positives?
**A:** Use relaxed mode or selective validation:
```rust
diag! {
strict(sequence), // Only validate what you need
E.Auth.Token.EXPIRED: { ... }
}
```
### Q: Can validation check actual numeric values?
**A:** Not directly. `sequence_values` validates the **type** (u16), but the actual numeric value is enforced by the `sequence!` macro. This separation keeps validation simple and fast.
### Q: Can validation check actual string values?
**A:** Not directly. `string_values` validates the **type** (&str), but the actual string value is enforced by the `primary!` and `component!` macros. The identifier name should match the value by convention.
---
## Summary
The validation system provides **7 granular modes** to catch errors at compile time:
1. ✅ **`sequence`** - Catch typos in sequence names
2. ✅ **`primary`** - Catch typos in primary names
3. ✅ **`component`** - Catch typos in component names
4. ✅ **`naming`** - Enforce naming conventions
5. ✅ **`duplicates`** - Prevent duplicate codes
6. ✅ **`sequence_values`** - Type-check sequences (u16)
7. ✅ **`string_values`** - Type-check primaries (&str)
**Start with basic validation and add more as needed. Full validation provides maximum safety with zero runtime cost.**
For more details, see:
- `tests/README.md` - Test documentation
- `examples/strict_validation_demo.rs` - Usage examples
- `tests/compile-fail/*.rs` - Error examples
- `tests/compile-pass/*.rs` - Valid usage examples