Skip to main content

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}