Skip to main content

headless_lms_utils/error/
macros.rs

1/*!
2Macro utilities for creating error types more ergonomically.
3
4This module provides helper macros that simplify error construction
5for types implementing `BackendError`.
6*/
7
8/// Defines a crate-local error creation macro that can be re-exported from a module.
9///
10/// This avoids using the `paste` crate and keeps the implementation simple.
11/// Each error module should use this to define their own specialized macro.
12///
13/// This helper macro (`define_err_macro!`) is `#[macro_export]` so it can be
14/// invoked from sibling crates. The generated macro itself is crate-local, so
15/// callers can re-export it through a crate prelude without creating a
16/// root-level exported macro and without triggering
17/// `macro_expanded_macro_exports_accessed_by_absolute_paths`.
18///
19/// # Examples
20///
21/// ```ignore
22/// // In models/src/error.rs:
23/// define_err_macro!(
24///     model_err,
25///     ModelError,
26///     ModelErrorType,
27///     ModelErrorType,
28///     "Create a ModelError."
29/// );
30/// ```
31#[macro_export]
32macro_rules! define_err_macro {
33    ($macro_name:ident, $error:ty, $error_type:ty, $error_type_ident:ident, $doc:expr) => {
34        #[doc = concat!(
35                    $doc,
36                    "\n\n# Examples\n\n",
37                    "```ignore\n",
38                    "// Without source\n",
39                    "let err = ",
40                    stringify!($macro_name),
41                    "!(Generic, \"message\".to_string());\n\n",
42                    "// With source\n",
43                    "let err = ",
44                    stringify!($macro_name),
45                    "!(Generic, \"message\".to_string(), source_err);\n\n",
46                    "// With format!\n",
47                    "let err = ",
48                    stringify!($macro_name),
49                    "!(Generic, format!(\"Failed: {}\", detail));\n\n",
50                    "// With tuple variant payload\n",
51                    "let err = ",
52                    stringify!($macro_name),
53                    "!(UnauthorizedWithReason(reason), \"message\".to_string());\n\n",
54                    "// With struct variant payload\n",
55                    "let err = ",
56                    stringify!($macro_name),
57                    "!(BadRequestWithData { code: \"x\" }, \"message\".to_string());\n",
58                    "```\n"
59                )]
60        #[allow(unused_macros)]
61        macro_rules! $macro_name {
62                    // Reject fully-qualified enum paths: model_err!(ModelErrorType::NotFound, ...)
63                    ($error_type_ident::$variant:ident, $msg:expr) => {
64                        compile_error!(concat!(
65                            stringify!($macro_name),
66                            "! expects short variant syntax.\n",
67                            "Use `",
68                            stringify!($macro_name),
69                            "!(",
70                            stringify!($variant),
71                            ", msg)` instead of `",
72                            stringify!($macro_name),
73                            "!(",
74                            stringify!($error_type_ident),
75                            "::",
76                            stringify!($variant),
77                            ", msg)`."
78                        ))
79                    };
80                    ($error_type_ident::$variant:ident, $msg:expr, $src:expr) => {
81                        compile_error!(concat!(
82                            stringify!($macro_name),
83                            "! expects short variant syntax.\n",
84                            "Use `",
85                            stringify!($macro_name),
86                            "!(",
87                            stringify!($variant),
88                            ", msg, src)` instead of `",
89                            stringify!($macro_name),
90                            "!(",
91                            stringify!($error_type_ident),
92                            "::",
93                            stringify!($variant),
94                            ", msg, src)`."
95                        ))
96                    };
97                    ($error_type_ident::$variant:ident $payload:tt, $msg:expr) => {
98                        compile_error!(concat!(
99                            stringify!($macro_name),
100                            "! expects short variant syntax.\n",
101                            "Use `",
102                            stringify!($macro_name),
103                            "!(",
104                            stringify!($variant),
105                            "..., msg)` instead of `",
106                            stringify!($macro_name),
107                            "!(",
108                            stringify!($error_type_ident),
109                            "::",
110                            stringify!($variant),
111                            "..., msg)`."
112                        ))
113                    };
114                    ($error_type_ident::$variant:ident $payload:tt, $msg:expr, $src:expr) => {
115                        compile_error!(concat!(
116                            stringify!($macro_name),
117                            "! expects short variant syntax.\n",
118                            "Use `",
119                            stringify!($macro_name),
120                            "!(",
121                            stringify!($variant),
122                            "..., msg, src)` instead of `",
123                            stringify!($macro_name),
124                            "!(",
125                            stringify!($error_type_ident),
126                            "::",
127                            stringify!($variant),
128                            "..., msg, src)`."
129                        ))
130                    };
131                    // Payload variant without source: tuple or struct payload.
132                    ($variant:ident $payload:tt, $msg:expr) => {
133                        <$error>::new($error_type_ident::$variant $payload, $msg, None)
134                    };
135                    // Payload variant with source: tuple or struct payload.
136                    ($variant:ident $payload:tt, $msg:expr, $src:expr) => {
137                        <$error>::new($error_type_ident::$variant $payload, $msg, Some($src.into()))
138                    };
139                    // Without source: model_err!(Generic, "message")
140                    ($variant:ident, $msg:expr) => {
141                        <$error>::new($error_type_ident::$variant, $msg, None)
142                    };
143                    // With source: model_err!(Generic, "message", source_err)
144                    ($variant:ident, $msg:expr, $src:expr) => {
145                        <$error>::new($error_type_ident::$variant, $msg, Some($src.into()))
146                    };
147                }
148
149        // Re-export into module namespace so crate preludes can expose it.
150        #[allow(unused_imports)]
151        pub(crate) use $macro_name;
152    };
153}