Skip to main content

systemprompt_models/errors/
macros.rs

1//! Declarative `domain_error!` macro for domain crates.
2//!
3//! Eliminates the boilerplate `Database`, `Io`, `Json`, `Validation`,
4//! `NotFound`, etc. variants that every crate hand-rolls. Domain crates
5//! invoke the macro with a `common: [...]` list of pre-canned variants and
6//! their own domain-specific variants underneath.
7//!
8//! ```ignore
9//! use systemprompt_models::domain_error;
10//!
11//! domain_error! {
12//!     pub enum FilesError {
13//!         common: [repository, io, json, validation, not_found],
14//!
15//!         #[error("permission denied: {path}")]
16//!         PermissionDenied { path: String },
17//!     }
18//! }
19//! ```
20//!
21//! The `repository` token funnels database errors through the canonical
22//! `systemprompt_database::RepositoryError` rather than `sqlx::Error`
23//! directly, so the layer boundary is preserved.
24
25#[macro_export]
26macro_rules! domain_error {
27    (
28        $(#[$emeta:meta])*
29        pub enum $name:ident {
30            common: [ $($common:ident),* $(,)? ]
31            $(, $($body:tt)*)?
32        }
33    ) => {
34        $crate::__domain_error_emit! {
35            @attrs [$(#[$emeta])*]
36            @name $name
37            @commons [ $($common)* ]
38            @body { $($($body)*)? }
39        }
40    };
41    (
42        $(#[$emeta:meta])*
43        pub enum $name:ident {
44            $($body:tt)*
45        }
46    ) => {
47        $(#[$emeta])*
48        #[derive(::std::fmt::Debug, ::thiserror::Error)]
49        pub enum $name {
50            $($body)*
51        }
52    };
53}
54
55#[macro_export]
56#[doc(hidden)]
57macro_rules! __domain_error_emit {
58    (@attrs [$($attrs:tt)*] @name $name:ident @commons [io $($rest:ident)*] @body { $($body:tt)* }) => {
59        $crate::__domain_error_emit! {
60            @attrs [$($attrs)*]
61            @name $name
62            @commons [$($rest)*]
63            @body {
64                #[error("io: {0}")]
65                Io(#[from] ::std::io::Error),
66                $($body)*
67            }
68        }
69    };
70    (@attrs [$($attrs:tt)*] @name $name:ident @commons [json $($rest:ident)*] @body { $($body:tt)* }) => {
71        $crate::__domain_error_emit! {
72            @attrs [$($attrs)*]
73            @name $name
74            @commons [$($rest)*]
75            @body {
76                #[error("json: {0}")]
77                Json(#[from] ::serde_json::Error),
78                $($body)*
79            }
80        }
81    };
82    (@attrs [$($attrs:tt)*] @name $name:ident @commons [yaml $($rest:ident)*] @body { $($body:tt)* }) => {
83        $crate::__domain_error_emit! {
84            @attrs [$($attrs)*]
85            @name $name
86            @commons [$($rest)*]
87            @body {
88                #[error("yaml: {0}")]
89                Yaml(#[from] ::serde_yaml::Error),
90                $($body)*
91            }
92        }
93    };
94    (@attrs [$($attrs:tt)*] @name $name:ident @commons [validation $($rest:ident)*] @body { $($body:tt)* }) => {
95        $crate::__domain_error_emit! {
96            @attrs [$($attrs)*]
97            @name $name
98            @commons [$($rest)*]
99            @body {
100                #[error("validation: {0}")]
101                Validation(::std::string::String),
102                $($body)*
103            }
104        }
105    };
106    (@attrs [$($attrs:tt)*] @name $name:ident @commons [not_found $($rest:ident)*] @body { $($body:tt)* }) => {
107        $crate::__domain_error_emit! {
108            @attrs [$($attrs)*]
109            @name $name
110            @commons [$($rest)*]
111            @body {
112                #[error("not found: {0}")]
113                NotFound(::std::string::String),
114                $($body)*
115            }
116        }
117    };
118    (@attrs [$($attrs:tt)*] @name $name:ident @commons [config $($rest:ident)*] @body { $($body:tt)* }) => {
119        $crate::__domain_error_emit! {
120            @attrs [$($attrs)*]
121            @name $name
122            @commons [$($rest)*]
123            @body {
124                #[error("config: {0}")]
125                Config(::std::string::String),
126                $($body)*
127            }
128        }
129    };
130    (@attrs [$($attrs:tt)*] @name $name:ident @commons [http $($rest:ident)*] @body { $($body:tt)* }) => {
131        $crate::__domain_error_emit! {
132            @attrs [$($attrs)*]
133            @name $name
134            @commons [$($rest)*]
135            @body {
136                #[error("http: {0}")]
137                Http(#[from] ::reqwest::Error),
138                $($body)*
139            }
140        }
141    };
142    (@attrs [$($attrs:tt)*] @name $name:ident @commons [repository $($rest:ident)*] @body { $($body:tt)* }) => {
143        $crate::__domain_error_emit! {
144            @attrs [$($attrs)*]
145            @name $name
146            @commons [$($rest)*]
147            @body {
148                #[error("repository: {0}")]
149                Repository(#[from] ::systemprompt_database::RepositoryError),
150                $($body)*
151            }
152        }
153    };
154    (@attrs [$($attrs:tt)*] @name $name:ident @commons [anyhow $($rest:ident)*] @body { $($body:tt)* }) => {
155        $crate::__domain_error_emit! {
156            @attrs [$($attrs)*]
157            @name $name
158            @commons [$($rest)*]
159            @body {
160                #[error("{0}")]
161                Other(#[from] ::anyhow::Error),
162                $($body)*
163            }
164        }
165    };
166    (@attrs [$($attrs:tt)*] @name $name:ident @commons [] @body { $($body:tt)* }) => {
167        $($attrs)*
168        #[derive(::std::fmt::Debug, ::thiserror::Error)]
169        pub enum $name {
170            $($body)*
171        }
172    };
173}