1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! Procedural macros for klauthed.
//!
//! Currently: [`macro@DomainError`], a derive that generates the
//! `klauthed_error::DomainError` impl from `#[domain(...)]` attributes, so error
//! types don't hand-write the `category()` / `code()` match arms.
use TokenStream;
use ;
use expand;
/// Derive `klauthed_error::DomainError`.
///
/// Annotate variants (or a struct) with `#[domain(...)]`:
///
/// ## Compile-time validation
///
/// Both `code` and `prefix` are validated at macro-expansion time:
/// they must match `[a-z][a-z0-9_]*` (plus dots in `code` for fully-qualified
/// codes). Violations are hard compile errors, not silent runtime bugs.
///
/// ```compile_fail
/// # use klauthed_macros::DomainError;
/// #[derive(Debug, DomainError)]
/// #[domain(prefix = "BadPrefix")] // uppercase → compile error
/// enum Bad { A }
/// # impl std::fmt::Display for Bad { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) } }
/// # impl std::error::Error for Bad {}
/// ```
///
/// ```compile_fail
/// # use klauthed_macros::DomainError;
/// #[derive(Debug, DomainError)]
/// #[domain(prefix = "my")]
/// enum Bad {
/// #[domain(code = "bad code with spaces")] // spaces → compile error
/// A,
/// }
/// # impl std::fmt::Display for Bad { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { Ok(()) } }
/// # impl std::error::Error for Bad {}
/// ```
///
/// * `category = "internal"` — one of the snake-case `ErrorCategory` names
/// (`bad_request`, `unauthorized`, `forbidden`, `not_found`, `conflict`,
/// `rate_limited`, `timeout`, `unavailable`, `internal`). Defaults to the
/// container's `category`, else `internal`.
/// * `code = "missing"` — the error code. Defaults to the snake-cased variant
/// name. A container `#[domain(prefix = "config")]` prefixes every code, so
/// `MissingRequired` → `config.missing_required`.
/// * `transparent` — delegate `category()` and `code()` to the variant's single
/// field (which must itself be a `DomainError`), for wrapped/`#[from]` errors.
///
/// The type must also implement `std::error::Error` (e.g. via `thiserror`),
/// since `DomainError` requires it.
/// Derive `klauthed_core::config::FromConfig`.
///
/// Binds a struct to a section of the resolved configuration, generating a
/// `from_config(&Config)` that reads (and deserializes) it — the klauthed analog
/// of Spring's `@ConfigurationProperties`. The type must also implement
/// `serde::Deserialize`.
///
/// ```text
/// #[derive(serde::Deserialize, FromConfig)]
/// #[config(key = "database")] // defaults to the snake_cased type name
/// struct DatabaseSettings { /* … */ }
///
/// #[derive(Default, serde::Deserialize, FromConfig)]
/// #[config(key = "cache", default)] // a missing section binds to `Default`
/// struct CacheSettings { /* … */ }
/// ```