core_crypto/error/recursive.rs
1#![allow(missing_docs)]
2
3/// Attach context to an error which [`RecursiveError`] knows how to wrap.
4///
5/// By forcing ourselves to map errors everywhere in order for question mark operators to work, we ensure that we can
6/// take the opportunity to include a little bit of manual context. Pervasively done, this means that our errors have
7/// quite a lot of contextual information about the call stack and what precisely has gone wrong.
8///
9/// Implementing this trait is what lets [`RecursiveError::context`] select the right variant from the error's type
10/// alone, so most call sites never need to name a variant. It also lets generic code wrap an arbitrary
11/// module error without knowing which one it has; `core-crypto-ffi` relies on that.
12pub trait ToRecursiveError {
13 /// Construct a recursive error given the current context
14 fn construct_recursive(self, context: &'static str) -> RecursiveError;
15}
16
17/// Build [`RecursiveError`] from a list of `Variant: SourceType => constructor` entries.
18///
19/// Each entry generates the enum variant, a `RecursiveError::<constructor>` helper, the
20/// [`ToRecursiveError`] impl for the source type, and the `Display` and `Error::source` arms, so
21/// wrapping a new error type is a one-line change here instead of four coordinated edits.
22///
23/// We hand-roll `Error` rather than deriving `thiserror::Error`, and that is deliberate:
24/// given `#[source] source: Box<E>`, thiserror hands `&Box<E>` to the error chain, and
25/// `downcast_ref::<E>()` does not match that. Both the ffi error mapping in `core-crypto-ffi` and
26/// the `innermost_source_matches!` test helper downcast to the concrete error type, so the chain
27/// has to expose `&E` via `Box::as_ref`.
28macro_rules! recursive_error {
29 ($(
30 $( #[cfg($meta:meta)] )*
31 $variant:ident : $source:path => $constructor:ident ;
32 )+) => {
33 /// These errors wrap each of the module-specific errors in CoreCrypto.
34 ///
35 /// The goal here is to reduce the need to redeclare each of these error
36 /// types as an individual variant of a module-specific error type.
37 #[derive(Debug, derive_more::Display)]
38 pub enum RecursiveError {
39 $(
40 $( #[cfg($meta)] )*
41 #[display("{context}")]
42 $variant {
43 context: &'static str,
44 source: Box<$source>,
45 },
46 )+
47 }
48
49 impl RecursiveError {
50 $(
51 /// Wrap an error convertible into
52 // deliberately not an intra-doc link: some of these targets are `pub(crate)`, and a
53 // public doc comment may not link to a private item.
54 #[doc = concat!("`", stringify!($source), "`,")]
55 /// given the current context.
56 ///
57 /// Prefer [`Self::context`] unless you are relying on the `Into` conversion.
58 $( #[cfg($meta)] )*
59 pub fn $constructor<E: Into<$source>>(context: &'static str) -> impl FnOnce(E) -> Self {
60 move |into_source| Self::$variant {
61 context,
62 source: Box::new(into_source.into()),
63 }
64 }
65 )+
66 }
67
68 impl std::error::Error for RecursiveError {
69 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70 match self {
71 $(
72 $( #[cfg($meta)] )*
73 Self::$variant { source, .. } => Some(source.as_ref()),
74 )+
75 }
76 }
77 }
78
79 $(
80 $( #[cfg($meta)] )*
81 impl ToRecursiveError for $source {
82 fn construct_recursive(self, context: &'static str) -> RecursiveError {
83 RecursiveError::$variant {
84 context,
85 source: Box::new(self),
86 }
87 }
88 }
89 )+
90 };
91}
92
93recursive_error! {
94 Root: crate::Error => root;
95 TransactionContext: crate::transaction_context::Error => transaction;
96 E2e: wire_e2e_identity::E2eIdentityError => e2e_identity;
97 MlsClient: crate::mls::session::Error => mls_client;
98 MlsConversation: crate::mls::conversation::Error => mls_conversation;
99 MlsCredential: crate::mls::credential::Error => mls_credential;
100 #[cfg(test)]
101 Test: crate::test_utils::TestError => test;
102}
103
104impl RecursiveError {
105 /// Wrap any error which [`RecursiveError`] knows how to wrap, given the current context.
106 ///
107 /// The variant is selected from the error's type, so this is usually what you want:
108 ///
109 /// ```ignore
110 /// self.session().await.map_err(RecursiveError::context("getting mls client"))?
111 /// ```
112 ///
113 /// This only accepts the module error types themselves. When you need to convert first, for
114 /// instance because you are holding a [`crate::TlsCodecError`] which several module errors
115 /// can absorb, reach for the variant-specific constructor such as
116 /// [`Self::mls_conversation`] instead.
117 pub fn context<E: ToRecursiveError>(context: &'static str) -> impl FnOnce(E) -> Self {
118 move |source| source.construct_recursive(context)
119 }
120}