waddling_errors_macros/
lib.rs

1//! Procedural macros for waddling-errors
2//!
3//! This crate provides convenient macros for defining error code components,
4//! primaries, and sequences with rich metadata support.
5//!
6//! # Macros
7//!
8//! - `component!` - Define component enums with automatic ComponentId impl
9//! - `primary!` - Define primary enums with automatic PrimaryId impl
10//! - `sequence!` - Define sequence constants with metadata
11//!
12//! # Examples
13//!
14//! ```ignore
15//! use waddling_errors_macros::{component, primary, sequence};
16//!
17//! component! {
18//!     Auth,
19//!     Database {
20//!         docs: "Database operations",
21//!         tags: ["backend"],
22//!     },
23//! }
24//!
25//! primary! {
26//!     Token,
27//!     Connection {
28//!         docs: "Connection errors",
29//!     },
30//! }
31//!
32//! sequence! {
33//!     MISSING(001) {
34//!         description: "Item not found",
35//!         hints: ["Check if item exists"],
36//!     },
37//! }
38//! ```
39
40use proc_macro::TokenStream;
41
42mod component;
43mod diag;
44mod doc_gen_attr;
45mod doc_gen_macro;
46mod in_component;
47mod primary;
48mod sequence;
49mod setup;
50
51/// Define component enums with automatic ComponentId trait implementation.
52///
53/// # WDP Naming Convention
54///
55/// Per WDP specification, component names SHOULD use **PascalCase**:
56/// - ✅ `Auth`, `Database`, `ApiGateway`, `TokenValidator`
57/// - ❌ `AUTH`, `DATABASE`, `API_GATEWAY` (non-standard)
58///
59/// Error codes follow the format: `E.Component.Primary.SEQUENCE`
60/// - Example: `E.Auth.Token.EXPIRED` not `E.AUTH.TOKEN.EXPIRED`
61///
62/// # Metadata Fields
63///
64/// Supports all metadata fields in any order:
65/// - `description` - Documentation string
66/// - `introduced` - Version introduced
67/// - `deprecated` - Deprecation notice
68/// - `examples` - Array of example error codes
69/// - `tags` - Array of category tags
70///
71/// # Examples
72///
73/// ```ignore
74/// component! {
75///     Auth,                    // Simple: "Auth"
76///     Database {               // With metadata
77///         description: "Database operations",
78///         introduced: "1.0.0",
79///         tags: ["backend", "storage"],
80///     },
81/// }
82///
83/// // Generated code implements ComponentIdDocumented trait:
84/// let desc = Component::Database.description();  // Trait method
85/// let tags = Component::Database.tags();
86/// ```
87#[proc_macro]
88pub fn component(input: TokenStream) -> TokenStream {
89    component::expand(input)
90}
91
92/// Define primary enums with automatic PrimaryId trait implementation.
93///
94/// # WDP Naming Convention
95///
96/// Per WDP specification, primary names SHOULD use **PascalCase**:
97/// - ✅ `Token`, `Connection`, `Validation`, `RateLimit`
98/// - ❌ `TOKEN`, `CONNECTION`, `VALIDATION` (non-standard)
99///
100/// Error codes follow the format: `E.Component.Primary.SEQUENCE`
101/// - Example: `E.Database.Connection.TIMEOUT` not `E.DATABASE.CONNECTION.TIMEOUT`
102///
103/// # Metadata Fields
104///
105/// Supports all metadata fields in any order:
106/// - `description` - Documentation string
107/// - `introduced` - Version introduced
108/// - `deprecated` - Deprecation notice
109/// - `examples` - Array of example error codes
110/// - `related` - Array of related primary codes
111///
112/// # Examples
113///
114/// ```ignore
115/// primary! {
116///     Token,                   // Simple: "Token"
117///     Connection {             // With metadata
118///         description: "Connection errors",
119///         related: ["Timeout", "Pool"],
120///     },
121/// }
122///
123/// // Generated code implements PrimaryIdDocumented trait:
124/// let desc = Primary::Connection.description();  // Trait method
125/// let related = Primary::Connection.related();
126/// ```
127#[proc_macro]
128pub fn primary(input: TokenStream) -> TokenStream {
129    primary::expand(input)
130}
131
132/// Define sequence constants with rich metadata.
133///
134/// Supports all 5 metadata fields in any order:
135/// - `description` - Human-readable description
136/// - `typical_severity` - Typical severity level
137/// - `hints` - Array of helpful hints
138/// - `related` - Array of related sequences
139/// - `introduced` - Version introduced
140///
141/// # Examples
142///
143/// ```ignore
144/// sequence! {
145///     MISSING(001),            // Simple
146///     CUSTOM(042) {            // With metadata
147///         description: "Custom logic error",
148///         hints: ["Check business rules", "Verify input"],
149///         related: ["043", "044"],
150///         introduced: "1.2.0",
151///     },
152/// }
153/// ```
154#[proc_macro]
155pub fn sequence(input: TokenStream) -> TokenStream {
156    sequence::expand(input)
157}
158
159/// Define complete diagnostic metadata with full 4-part error codes.
160///
161/// **This macro generates THREE constants**:
162/// 1. `E_COMPONENT_PRIMARY_SEQUENCE` - Runtime metadata (always present)
163/// 2. `E_COMPONENT_PRIMARY_SEQUENCE_DOCS` - Compile-time docs (with `metadata` feature)
164/// 3. `E_COMPONENT_PRIMARY_SEQUENCE_COMPLETE` - Combined (with `metadata` feature)
165///
166/// # 4-Part Code Format
167///
168/// `Severity.Component.Primary.Sequence`
169///
170/// # Visibility Markers
171///
172/// Fields can have visibility markers to control where they're included:
173/// - `'C` - Compile-time only (documentation generation, not in binary)
174/// - `'R` - Runtime only (in production binary, not in docs)
175/// - `'RC` or `'CR` - Both (default for most fields)
176///
177/// **Mnemonic**: `'CR` = Compile + Runtime (both contexts)
178///
179/// # Supported Fields (in any order)
180///
181/// **Core fields (always present):**
182/// - `message` (required) - Message template with `{{field}}` placeholders
183/// - `fields` (optional) - Array of non-PII field names used in message (referenced as `{{field}}`)
184/// - `pii` (optional) - Array of PII field names (referenced as `{{pii/field}}` in message)
185/// - `severity` (optional) - Override the severity from the code
186///
187/// **Fields with visibility markers:**
188/// - `description ['C|'R|'RC|'CR]` - Human-readable description
189/// - `hints ['C|'R|'RC|'CR]` - Array of helpful hints
190/// - `role ['R|'RC|'CR]` - Role-based visibility
191/// - `tags ['R]` - Categorization tags (runtime only)
192/// - `related_codes ['R]` - Related error codes (runtime only)
193/// - `code_snippet ['C]` - Code examples (compile-time only)
194/// - `docs_url ['C]` - Documentation URL (compile-time only)
195/// - `introduced ['C]` - Version introduced (compile-time only)
196/// - `deprecated ['C]` - Deprecation notice (compile-time only)
197///
198/// # Examples
199///
200/// **Basic usage (defaults to 'RC - both contexts):**
201/// ```ignore
202/// diag! {
203///     Error.Auth.Token.MISSING: {
204///         message: "Token '{{token}}' not found",
205///         fields: [token],
206///         description: "API token missing",  // Available in both
207///         hints: ["Check your credentials"],  // Available in both
208///     },
209/// }
210/// ```
211///
212/// **With PII fields:**
213/// ```ignore
214/// diag! {
215///     Error.Auth.Login.FAILED: {
216///         message: "Login failed for {{pii/email}} at {{timestamp}}",
217///         fields: [timestamp],           // Non-PII: goes to `f` in wire protocol
218///         pii: [email],                  // PII: goes to `pii.data` in wire protocol
219///         description: "Authentication failed",
220///     },
221/// }
222/// ```
223///
224/// **Advanced usage with visibility markers:**
225/// ```ignore
226/// diag! {
227///     Error.Auth.Token.MISSING: {
228///         message: "Token '{{token}}' not found",
229///         fields: [token],
230///
231///         // Verbose docs for compile-time
232///         description 'C: "Detailed explanation for documentation...",
233///
234///         // Short message for runtime
235///         description 'R: "Token missing",
236///
237///         // Different hints for different audiences
238///         hints 'C: ["For developers: Check auth flow", "See RFC 6750"],
239///         hints 'R: ["Include Authorization header", "Verify API key"],
240///         hints 'CR: ["Contact support if needed"],  // 'RC also works
241///
242///         // Runtime categorization
243///         role 'R: "Public",
244///         tags: ["auth", "security"],
245///
246///         // Compile-time documentation
247///         code_snippet: {
248///             wrong: "fetch(url)",
249///             correct: "fetch(url, { headers: { 'Authorization': 'Bearer <token>' } })",
250///         },
251///         docs_url: "https://docs.example.com/auth",
252///         introduced: "1.0.0",
253///     },
254/// }
255/// ```
256///
257/// # Generated Code
258///
259/// This generates THREE constants:
260///
261/// ```ignore
262/// // Always generated (no feature flags)
263/// pub const E_AUTH_TOKEN_MISSING: DiagnosticRuntime = /* runtime metadata */;
264///
265/// // Only with metadata feature
266/// #[cfg(feature = "metadata")]
267/// pub const E_AUTH_TOKEN_MISSING_DOCS: DiagnosticDocs = /* compile-time docs */;
268///
269/// #[cfg(feature = "metadata")]
270/// pub const E_AUTH_TOKEN_MISSING_COMPLETE: DiagnosticComplete = /* both */;
271/// ```
272///
273/// Frameworks can use the runtime constant for error handling, and the complete
274/// constant for documentation generation.
275#[proc_macro]
276pub fn diag(input: TokenStream) -> TokenStream {
277    diag::expand(input)
278}
279
280/// Attribute macro for automatic documentation generation
281///
282/// Wraps `diag!` and generates registration code for specified formats.
283///
284/// # Examples
285///
286/// ```ignore
287/// #[doc_gen("json", "html")]
288/// diag! {
289///     E.Auth.Token.MISSING: {
290///         message: "Token not found",
291///         // ...
292///     },
293/// }
294/// ```
295///
296/// This generates a `__doc_gen_registry` module with format tracking.
297#[proc_macro_attribute]
298pub fn doc_gen(attr: TokenStream, item: TokenStream) -> TokenStream {
299    doc_gen_attr::expand(attr, item)
300}
301
302/// Generates documentation registration function for a diagnostic.
303///
304/// This macro creates a registration function that can be called to register
305/// a diagnostic with a DocRegistry for documentation generation.
306///
307/// # Example
308/// ```rust,ignore
309/// // Define diagnostic
310/// diag! {
311///     E.Auth.Token.EXPIRED: {
312///         message: "Token expired",
313///         // ...
314///     },
315/// }
316///
317/// // Generate registration code
318/// doc_gen_register! {
319///     E.Auth.Token.EXPIRED => ["json", "html"]
320/// }
321///
322/// // Later, use the generated function:
323/// let mut registry = DocRegistry::new("myapp", "1.0.0");
324/// register_e_auth_token_expired_complete_for_doc_gen(&mut registry);
325/// ```
326#[proc_macro]
327pub fn doc_gen_register(input: TokenStream) -> TokenStream {
328    doc_gen_macro::expand(input)
329}
330
331/// Mark modules or files as belonging to a specific component.
332///
333/// This attribute enables:
334/// - **Discovery**: Find all files in a component via grep or IDE search
335/// - **Navigation**: IDE integration for "jump to component" features
336/// - **Documentation**: Auto-group errors by component in generated docs
337/// - **Organization**: Track scattered components across different folders
338///
339/// # Syntax
340///
341/// ```ignore
342/// #[in_component(ComponentName)]
343/// ```
344///
345/// # Examples
346///
347/// **Marking a folder** (via mod.rs):
348/// ```ignore
349/// // src/auth/mod.rs
350/// #[in_component(Auth)]
351///
352/// mod token;
353/// mod session;
354/// ```
355///
356/// **Marking scattered files**:
357/// ```ignore
358/// // src/api/middleware.rs
359/// #[in_component(Auth)]  // Auth logic here too!
360///
361/// use waddling_errors::diag;
362/// // ... Auth-related errors ...
363/// ```
364///
365/// **Finding all Auth components**:
366/// ```bash
367/// grep -r "#\[in_component(Auth)\]" src/
368/// ```
369///
370/// # Metadata Generated
371///
372/// The macro creates a hidden module with:
373/// - Component name constant
374/// - Module path (for IDE integration)
375/// - File path (for tooling)
376/// - Marker trait (for discovery)
377#[proc_macro_attribute]
378pub fn in_component(attr: TokenStream, item: TokenStream) -> TokenStream {
379    in_component::expand(attr, item)
380}
381
382/// Configure module paths for waddling-errors definitions.
383///
384/// This macro sets up path aliases that `diag!` uses to resolve component,
385/// primary, and sequence definitions. This enables flexible project structure
386/// where definitions can be placed in any module.
387///
388/// **Must be called once at the crate root (lib.rs or main.rs).**
389///
390/// # Syntax
391///
392/// ```ignore
393/// waddling_errors::setup! {
394///     components = crate::my_components,
395///     primaries = crate::error_primaries,
396///     sequences = crate::my_sequences,
397/// }
398/// ```
399///
400/// # What it Generates
401///
402/// ```ignore
403/// pub(crate) mod __wd_paths {
404///     pub use crate::my_components as components;
405///     pub use crate::error_primaries as primaries;
406///     pub use crate::my_sequences as sequences;
407/// }
408/// ```
409///
410/// # Why Use This
411///
412/// Without `setup!`, `diag!` expects definitions at fixed paths:
413/// - `crate::components::*`
414/// - `crate::primaries::*`
415/// - `crate::sequences::*`
416///
417/// With `setup!`, you can place definitions anywhere and `diag!` will find them.
418///
419/// # Example Project Structure
420///
421/// ```ignore
422/// // src/lib.rs
423/// waddling_errors::setup! {
424///     components = crate::errors::components,
425///     primaries = crate::errors::primaries,
426///     sequences = crate::errors::sequences,
427/// }
428///
429/// mod errors {
430///     pub mod components { /* component! definitions */ }
431///     pub mod primaries { /* primary! definitions */ }
432///     pub mod sequences { /* sequence! definitions */ }
433/// }
434///
435/// mod api {
436///     // diag! here works - finds definitions via __wd_paths
437///     diag! { E.Api.Auth.001: { ... } }
438/// }
439/// ```
440///
441/// # Partial Configuration
442///
443/// You can specify only some paths - others will use defaults:
444///
445/// ```ignore
446/// waddling_errors::setup! {
447///     sequences = crate::my_sequences,
448///     // components defaults to crate::components
449///     // primaries defaults to crate::primaries
450/// }
451/// ```
452///
453/// # No Collision Between Crates
454///
455/// Each crate has its own `__wd_paths` module (via `crate::`), so a library
456/// using waddling-errors won't collide with an application using it too.
457#[proc_macro]
458pub fn setup(input: TokenStream) -> TokenStream {
459    setup::expand(input)
460}