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//! Copyright (c) systemprompt.io — Business Source License 1.1.
26//! See <https://systemprompt.io> for licensing details.
27
28#[macro_export]
29macro_rules! domain_error {
30    (
31        $(#[$emeta:meta])*
32        pub enum $name:ident {
33            common: [ $($common:ident),* $(,)? ]
34            $(, $($body:tt)*)?
35        }
36    ) => {
37        $crate::__domain_error_emit! {
38            @attrs [$(#[$emeta])*]
39            @name $name
40            @commons [ $($common)* ]
41            @body { $($($body)*)? }
42        }
43    };
44    (
45        $(#[$emeta:meta])*
46        pub enum $name:ident {
47            $($body:tt)*
48        }
49    ) => {
50        $(#[$emeta])*
51        #[derive(::std::fmt::Debug, ::thiserror::Error)]
52        pub enum $name {
53            $($body)*
54        }
55    };
56}
57
58#[macro_export]
59#[doc(hidden)]
60macro_rules! __domain_error_emit {
61    (@attrs [$($attrs:tt)*] @name $name:ident @commons [io $($rest:ident)*] @body { $($body:tt)* }) => {
62        $crate::__domain_error_emit! {
63            @attrs [$($attrs)*]
64            @name $name
65            @commons [$($rest)*]
66            @body {
67                #[error("io: {0}")]
68                Io(#[from] ::std::io::Error),
69                $($body)*
70            }
71        }
72    };
73    (@attrs [$($attrs:tt)*] @name $name:ident @commons [json $($rest:ident)*] @body { $($body:tt)* }) => {
74        $crate::__domain_error_emit! {
75            @attrs [$($attrs)*]
76            @name $name
77            @commons [$($rest)*]
78            @body {
79                #[error("json: {0}")]
80                Json(#[from] ::serde_json::Error),
81                $($body)*
82            }
83        }
84    };
85    (@attrs [$($attrs:tt)*] @name $name:ident @commons [yaml $($rest:ident)*] @body { $($body:tt)* }) => {
86        $crate::__domain_error_emit! {
87            @attrs [$($attrs)*]
88            @name $name
89            @commons [$($rest)*]
90            @body {
91                #[error("yaml: {0}")]
92                Yaml(#[from] ::serde_yaml::Error),
93                $($body)*
94            }
95        }
96    };
97    (@attrs [$($attrs:tt)*] @name $name:ident @commons [validation $($rest:ident)*] @body { $($body:tt)* }) => {
98        $crate::__domain_error_emit! {
99            @attrs [$($attrs)*]
100            @name $name
101            @commons [$($rest)*]
102            @body {
103                #[error("validation: {0}")]
104                Validation(::std::string::String),
105                $($body)*
106            }
107        }
108    };
109    (@attrs [$($attrs:tt)*] @name $name:ident @commons [not_found $($rest:ident)*] @body { $($body:tt)* }) => {
110        $crate::__domain_error_emit! {
111            @attrs [$($attrs)*]
112            @name $name
113            @commons [$($rest)*]
114            @body {
115                #[error("not found: {0}")]
116                NotFound(::std::string::String),
117                $($body)*
118            }
119        }
120    };
121    (@attrs [$($attrs:tt)*] @name $name:ident @commons [config $($rest:ident)*] @body { $($body:tt)* }) => {
122        $crate::__domain_error_emit! {
123            @attrs [$($attrs)*]
124            @name $name
125            @commons [$($rest)*]
126            @body {
127                #[error("config: {0}")]
128                Config(::std::string::String),
129                $($body)*
130            }
131        }
132    };
133    (@attrs [$($attrs:tt)*] @name $name:ident @commons [http $($rest:ident)*] @body { $($body:tt)* }) => {
134        $crate::__domain_error_emit! {
135            @attrs [$($attrs)*]
136            @name $name
137            @commons [$($rest)*]
138            @body {
139                #[error("http: {0}")]
140                Http(#[from] ::reqwest::Error),
141                $($body)*
142            }
143        }
144    };
145    (@attrs [$($attrs:tt)*] @name $name:ident @commons [repository $($rest:ident)*] @body { $($body:tt)* }) => {
146        $crate::__domain_error_emit! {
147            @attrs [$($attrs)*]
148            @name $name
149            @commons [$($rest)*]
150            @body {
151                #[error("repository: {0}")]
152                Repository(#[from] ::systemprompt_database::RepositoryError),
153                $($body)*
154            }
155        }
156    };
157    (@attrs [$($attrs:tt)*] @name $name:ident @commons [] @body { $($body:tt)* }) => {
158        $($attrs)*
159        #[derive(::std::fmt::Debug, ::thiserror::Error)]
160        pub enum $name {
161            $($body)*
162        }
163    };
164}