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}