Skip to main content

ark_client/
error.rs

1use std::error::Error as StdError;
2use std::fmt;
3
4type Source = Box<dyn StdError + Send + Sync + 'static>;
5
6#[derive(Debug)]
7pub struct Error {
8    inner: Box<ErrorImpl>,
9}
10
11#[derive(Debug)]
12struct ErrorImpl {
13    kind: Kind,
14    cause: Option<Error>,
15}
16
17#[derive(Debug)]
18enum Kind {
19    /// Ad-hoc error,
20    AdHoc(AdHocError),
21    /// An error related to interactions with the Ark server.
22    ArkServer(ArkServerError),
23    /// An error from [`ark_core`].
24    Core(CoreError),
25    /// An error related to coin selection of VTXOs and boarding outputs.
26    CoinSelect(CoinSelectError),
27    /// An error related to actions within the wallet.
28    Wallet(WalletError),
29    /// An error thrown by a user of this library
30    Consumer(ConsumerError),
31}
32
33#[derive(Debug)]
34struct AdHocError {
35    source: Source,
36}
37
38#[derive(Debug)]
39struct ArkServerError {
40    source: Source,
41}
42
43#[derive(Debug)]
44struct CoreError {
45    source: ark_core::Error,
46}
47
48#[derive(Debug)]
49struct CoinSelectError {
50    source: Source,
51}
52
53#[derive(Debug)]
54struct WalletError {
55    source: Source,
56}
57
58#[derive(Debug)]
59struct ConsumerError {
60    source: Source,
61}
62
63impl Error {
64    fn new(kind: Kind) -> Self {
65        Self {
66            inner: Box::new(ErrorImpl { kind, cause: None }),
67        }
68    }
69
70    pub(crate) fn ad_hoc(source: impl Into<Source>) -> Self {
71        Error::new(Kind::AdHoc(AdHocError {
72            source: source.into(),
73        }))
74    }
75
76    pub(crate) fn ark_server(source: impl Into<Source>) -> Self {
77        Error::new(Kind::ArkServer(ArkServerError {
78            source: source.into(),
79        }))
80    }
81
82    pub(crate) fn coin_select(source: impl Into<Source>) -> Self {
83        Error::new(Kind::CoinSelect(CoinSelectError {
84            source: source.into(),
85        }))
86    }
87
88    pub fn wallet(source: impl Into<Source>) -> Self {
89        Error::new(Kind::Wallet(WalletError {
90            source: source.into(),
91        }))
92    }
93
94    pub fn consumer(source: impl Into<Source>) -> Self {
95        Error::new(Kind::Consumer(ConsumerError {
96            source: source.into(),
97        }))
98    }
99}
100
101impl fmt::Display for Error {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        let mut err = self;
104        loop {
105            write!(f, "{}", err.inner.kind)?;
106            err = match err.inner.cause.as_ref() {
107                None => break,
108                Some(err) => err,
109            };
110            write!(f, ": ")?;
111        }
112        Ok(())
113    }
114}
115
116impl fmt::Display for Kind {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match *self {
119            Kind::AdHoc(ref err) => err.fmt(f),
120            Kind::ArkServer(ref err) => err.fmt(f),
121            Kind::Core(ref err) => err.fmt(f),
122            Kind::CoinSelect(ref err) => err.fmt(f),
123            Kind::Wallet(ref err) => err.fmt(f),
124            Kind::Consumer(ref err) => err.fmt(f),
125        }
126    }
127}
128
129impl fmt::Display for AdHocError {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        self.source.fmt(f)
132    }
133}
134
135impl fmt::Display for ArkServerError {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        self.source.fmt(f)
138    }
139}
140
141impl fmt::Display for CoreError {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        self.source.fmt(f)
144    }
145}
146
147impl fmt::Display for CoinSelectError {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        self.source.fmt(f)
150    }
151}
152
153impl fmt::Display for WalletError {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        self.source.fmt(f)
156    }
157}
158
159impl fmt::Display for ConsumerError {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        self.source.fmt(f)
162    }
163}
164
165impl From<ark_core::Error> for Error {
166    fn from(value: ark_core::Error) -> Self {
167        Self::new(Kind::Core(CoreError { source: value }))
168    }
169}
170
171pub trait IntoError {
172    fn into_error(self) -> Error;
173}
174
175impl IntoError for Error {
176    fn into_error(self) -> Error {
177        self
178    }
179}
180
181impl IntoError for &'static str {
182    fn into_error(self) -> Error {
183        Error::ad_hoc(self)
184    }
185}
186
187impl IntoError for String {
188    fn into_error(self) -> Error {
189        Error::ad_hoc(self)
190    }
191}
192
193/// A trait for contextualizing error values.
194///
195/// This makes it easy to contextualize either `Error` or `Result<T, Error>`.
196/// Specifically, in the latter case, it absolves one of the need to call
197/// `map_err` everywhere one wants to add context to an error.
198///
199/// This trick was borrowed from `jiff`, which borrowed it from `anyhow`.
200pub trait ErrorContext {
201    /// Contextualize the given consequent error with this (`self`) error as
202    /// the cause.
203    ///
204    /// This is equivalent to saying that "consequent is caused by self."
205    ///
206    /// Note that if an `Error` is given for `kind`, then this panics if it has
207    /// a cause. (Because the cause would otherwise be dropped. An error causal
208    /// chain is just a linked list, not a tree.)
209    fn context(self, consequent: impl IntoError) -> Self;
210
211    /// Like `context`, but hides error construction within a closure.
212    ///
213    /// This is useful if the creation of the consequent error is not otherwise
214    /// guarded and when error construction is potentially "costly" (i.e., it
215    /// allocates). The closure avoids paying the cost of contextual error
216    /// creation in the happy path.
217    ///
218    /// Usually this only makes sense to use on a `Result<T, Error>`, otherwise
219    /// the closure is just executed immediately anyway.
220    fn with_context<E: IntoError>(self, consequent: impl FnOnce() -> E) -> Self;
221}
222
223impl ErrorContext for Error {
224    fn context(self, consequent: impl IntoError) -> Error {
225        let mut err = consequent.into_error();
226        assert!(
227            err.inner.cause.is_none(),
228            "cause of consequence must be `None`"
229        );
230
231        err.inner.cause = Some(self);
232        err
233    }
234
235    fn with_context<E: IntoError>(self, consequent: impl FnOnce() -> E) -> Error {
236        let mut err = consequent().into_error();
237        assert!(
238            err.inner.cause.is_none(),
239            "cause of consequence must be `None`"
240        );
241
242        err.inner.cause = Some(self);
243        err
244    }
245}
246
247impl<T> ErrorContext for Result<T, Error> {
248    fn context(self, consequent: impl IntoError) -> Result<T, Error> {
249        self.map_err(|err| err.context(consequent))
250    }
251
252    fn with_context<E: IntoError>(self, consequent: impl FnOnce() -> E) -> Result<T, Error> {
253        self.map_err(|err| err.with_context(consequent))
254    }
255}
256
257impl From<ark_grpc::Error> for Error {
258    fn from(value: ark_grpc::Error) -> Self {
259        Self::ark_server(value)
260    }
261}
262
263impl StdError for Error {
264    fn source(&self) -> Option<&(dyn StdError + 'static)> {
265        self.inner
266            .cause
267            .as_ref()
268            .map(|e| e as &(dyn StdError + 'static))
269    }
270}