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}