neuer_error/macros.rs
1//! Macros for the users.
2
3/// Create a helper trait `NeuErrAttachments` that is implemented for
4/// [`NeuErr`](crate::NeuErr), which allows to directly retrieve your attachments. You can
5/// modify visibility and name by re-exporting via `pub use` if needed.
6///
7/// This improves discoverability and allows you to unwrap potential new-types you might have had to
8/// use (or wanted to use).
9///
10/// ## Usage
11///
12/// Simple getters without type transformation:
13///
14/// ```rust
15/// # use neuer_error::provided_attachments;
16/// #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
17/// enum Retryable { Yes, No }
18///
19/// provided_attachments!(
20/// retryable(single: Retryable) -> Option<&Retryable> { |v| v };
21/// );
22/// ```
23///
24/// This will create a method `fn retryable(&self) -> Option<&Retryable>` on `NeuErr`.
25///
26/// You can also make use of the transformation expression that will be applied to the attachment
27/// before returning it:
28///
29/// ```rust
30/// # use neuer_error::provided_attachments;
31/// #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
32/// enum Retryable { Yes, No }
33///
34/// provided_attachments!(
35/// retryable(single: Retryable) -> Retryable { |retry| retry.copied().unwrap_or(Retryable::No) };
36/// );
37/// ```
38///
39/// This will create a method `fn retryable(&self) -> Retryable` on `NeuErr`. The closure receives
40/// the `Option<&Retryable>` and returns a `Retryable`.
41///
42/// Finally, you can also retrieve multiple attachments of the same type and transform the iterator
43/// into your return type:
44///
45/// ```rust
46/// # use neuer_error::provided_attachments;
47/// #[derive(Debug, PartialEq, Clone)]
48/// struct UserInfo(String);
49///
50/// provided_attachments!(
51/// user_info(multiple: UserInfo) -> String { |iter| iter.map(|UserInfo(s)| s.as_str()).collect() };
52/// );
53/// ```
54///
55/// This will create a method `fn user_info(&self) -> String` on `NeuErr`, which collects all
56/// `UserInfo` attachments, unpacks them and collects them into a single `String`.
57#[macro_export]
58macro_rules! provided_attachments {
59 // Declare rule for single attachment.
60 (@declare $getter_name:ident (single: $attachment_type:ty) -> $return_type:ty {
61 // Transformation closure, receiving type Option<&$attachment_type> and returning $return_type.
62 |$bind:ident| $transform:expr
63 }) => {
64 #[doc = concat!("Get attachment `", stringify!($getter_name), "` via type `", stringify!($attachment_type), "` from the error.")]
65 fn $getter_name(&self) -> $return_type;
66 };
67
68 // Implement rule for single attachment.
69 (@implement $getter_name:ident (single: $attachment_type:ty) -> $return_type:ty {
70 // Transformation closure, receiving type Option<&$attachment_type> and returning $return_type.
71 |$bind:ident| $transform:expr
72 }) => {
73 fn $getter_name(&self) -> $return_type {
74 let $bind = Self::attachment::<$attachment_type>(self);
75 $transform
76 }
77 };
78
79 // Declare rule for multiple attachment.
80 (@declare $getter_name:ident (multiple: $attachment_type:ty) -> $return_type:ty {
81 // Transformation closure, receiving type impl Iterator<Item = &$attachment_type> and returning $return_type.
82 |$bind:ident| $transform:expr
83 }) => {
84 #[doc = concat!("Get attachment `", stringify!($getter_name), "` via type `", stringify!($attachment_type), "` from the error.")]
85 fn $getter_name(&self) -> $return_type;
86 };
87
88 // Implement rule for multiple attachment.
89 (@implement $getter_name:ident (multiple: $attachment_type:ty) -> $return_type:ty {
90 // Transformation closure, receiving type impl Iterator<Item = &$attachment_type> and returning $return_type.
91 |$bind:ident| $transform:expr
92 }) => {
93 fn $getter_name(&self) -> $return_type {
94 let $bind = Self::attachments::<$attachment_type>(self);
95 $transform
96 }
97 };
98
99 // Main matcher, splitting into attachment list.
100 ($(
101 $getter_name:ident ($multiplicity_matcher:ident : $attachment_type:ty) -> $return_type:ty { |$bind:ident| $transform:expr }
102 );* $(;)?) => {
103 #[doc = "Helper trait that is implemented for [`NeuErr`], which allows to comfortably retrieve typed context information."]
104 pub trait NeuErrAttachments {
105 $(
106 $crate::provided_attachments!(@declare $getter_name($multiplicity_matcher: $attachment_type) -> $return_type {
107 |$bind| $transform
108 });
109 )*
110 }
111
112 impl NeuErrAttachments for $crate::NeuErr {
113 $(
114 $crate::provided_attachments!(@implement $getter_name($multiplicity_matcher: $attachment_type) -> $return_type {
115 |$bind| $transform
116 });
117 )*
118 }
119 };
120}
121
122/// Apply this to a function, to automatically make inner errors require context.
123/// Ensures there will be a compile-time error when you forget to provide context to an error within
124/// the current function.
125///
126/// It is highly recommended to make use of the crate `macro_rules_attribute` to use it as an
127/// attribute instead of surrounding the function with the macro:
128///
129/// ```
130/// # use ::macro_rules_attribute::apply;
131/// # use ::neuer_error::{Result, require_context};
132/// #[apply(require_context)]
133/// fn do_something() -> Result<()> {
134/// Ok(())
135/// }
136/// ```
137///
138/// Otherwise, you can use it like this:
139///
140/// ```
141/// # use ::neuer_error::{Result, require_context};
142/// require_context!(
143/// fn do_something() -> Result<()> {
144/// Ok(())
145/// }
146/// );
147/// ```
148///
149/// Only the following kinds of functions are supported currently:
150///
151/// - Function must not be const, unsafe or have an external ABI.
152/// - Function cannot have generics.
153///
154/// It is also possible to use the macro within functions, specifying sync vs async with a marker
155/// up-front:
156///
157/// ```
158/// # use ::neuer_error::{Result, require_context};
159/// fn do_something() -> Result<()> {
160/// require_context!(@sync
161/// let result = Ok(());
162/// result
163/// )
164/// }
165/// ```
166#[macro_export]
167macro_rules! require_context {
168 // Macro entry for within async function body blocks.
169 (@async $($body:tt)* ) => {{
170 let call = async move || -> ::core::result::Result<_, $crate::NeuErr<$crate::ProvideContext>> { $($body)* };
171 call().await.map_err($crate::NeuErr::<$crate::ProvideContext>::remove_marker)
172 }};
173
174 // Macro entry for within sync function body blocks.
175 (@sync $($body:tt)* ) => {{
176 let call = move || -> ::core::result::Result<_, $crate::NeuErr<$crate::ProvideContext>> { $($body)* };
177 call().map_err($crate::NeuErr::<$crate::ProvideContext>::remove_marker)
178 }};
179
180 // Async functions.
181 (
182 $(#[$attrs:meta])*
183 $vis:vis async fn $name:ident
184 ( $($params:tt)* ) -> $return:ty
185 $body:block
186 ) => {
187 $(#[$attrs])*
188 $vis async fn $name
189 ( $($params)* ) -> $return
190 {
191 $crate::require_context!(@async $body)
192 }
193 };
194
195 // Sync functions.
196 (
197 $(#[$attrs:meta])*
198 $vis:vis fn $name:ident
199 ( $($params:tt)* ) -> $return:ty
200 $body:block
201 ) => {
202 $(#[$attrs])*
203 $vis fn $name
204 ( $($params)* ) -> $return
205 {
206 $crate::require_context!(@sync $body)
207 }
208 };
209}
210
211#[cfg(test)]
212mod compile_tests {
213 #![allow(dead_code, reason = "Compile tests")]
214
215 use ::macro_rules_attribute::apply;
216
217 use crate::Result;
218
219 require_context!(
220 #[cfg(test)]
221 pub async fn test1(#[cfg(test)] _param: bool, (): ()) -> Result<()> {
222 Ok(())
223 }
224 );
225
226 #[apply(require_context)]
227 fn test2() -> Result<()> {
228 Ok(())
229 }
230}