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 AdHoc(AdHocError),
21 ArkServer(ArkServerError),
23 Core(CoreError),
25 CoinSelect(CoinSelectError),
27 Wallet(WalletError),
29 ServerInfoChanged(ServerInfoChangedError),
31 Consumer(ConsumerError),
33}
34
35#[derive(Debug)]
36struct AdHocError {
37 source: Source,
38}
39
40#[derive(Debug)]
41struct ArkServerError {
42 source: Source,
43}
44
45#[derive(Debug)]
46struct CoreError {
47 source: ark_core::Error,
48}
49
50#[derive(Debug)]
51struct CoinSelectError {
52 source: Source,
53}
54
55#[derive(Debug)]
56struct WalletError {
57 source: Source,
58}
59
60#[derive(Debug)]
61struct ServerInfoChangedError;
62
63#[derive(Debug)]
64struct ConsumerError {
65 source: Source,
66}
67
68impl Error {
69 fn new(kind: Kind) -> Self {
70 Self {
71 inner: Box::new(ErrorImpl { kind, cause: None }),
72 }
73 }
74
75 pub(crate) fn ad_hoc(source: impl Into<Source>) -> Self {
76 Error::new(Kind::AdHoc(AdHocError {
77 source: source.into(),
78 }))
79 }
80
81 pub(crate) fn ark_server(source: impl Into<Source>) -> Self {
82 Error::new(Kind::ArkServer(ArkServerError {
83 source: source.into(),
84 }))
85 }
86
87 pub(crate) fn coin_select(source: impl Into<Source>) -> Self {
88 Error::new(Kind::CoinSelect(CoinSelectError {
89 source: source.into(),
90 }))
91 }
92
93 pub fn wallet(source: impl Into<Source>) -> Self {
94 Error::new(Kind::Wallet(WalletError {
95 source: source.into(),
96 }))
97 }
98
99 pub fn consumer(source: impl Into<Source>) -> Self {
100 Error::new(Kind::Consumer(ConsumerError {
101 source: source.into(),
102 }))
103 }
104
105 pub(crate) fn server_info_changed(source: impl Into<Error>) -> Self {
106 source
107 .into()
108 .context(Error::new(Kind::ServerInfoChanged(ServerInfoChangedError)))
109 }
110
111 pub fn is_server_info_changed(&self) -> bool {
112 let mut err = self;
113 loop {
114 if matches!(err.inner.kind, Kind::ServerInfoChanged(_)) {
115 return true;
116 }
117 err = match err.inner.cause.as_ref() {
118 Some(err) => err,
119 None => return false,
120 };
121 }
122 }
123
124 pub fn is_version_mismatch(&self) -> bool {
126 let mut err = self;
127 loop {
128 if err.inner.kind.is_version_mismatch() {
129 return true;
130 }
131 err = match err.inner.cause.as_ref() {
132 Some(err) => err,
133 None => return false,
134 };
135 }
136 }
137}
138
139impl Kind {
140 fn is_version_mismatch(&self) -> bool {
141 match self {
142 Kind::ArkServer(err) => {
143 err.source.is::<ark_grpc::Error>()
144 && err
145 .source
146 .downcast_ref::<ark_grpc::Error>()
147 .is_some_and(ark_grpc::Error::is_version_mismatch)
148 }
149 Kind::AdHoc(_)
150 | Kind::Core(_)
151 | Kind::CoinSelect(_)
152 | Kind::Wallet(_)
153 | Kind::ServerInfoChanged(_)
154 | Kind::Consumer(_) => false,
155 }
156 }
157}
158
159impl fmt::Display for Error {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 let mut err = self;
162 loop {
163 write!(f, "{}", err.inner.kind)?;
164 err = match err.inner.cause.as_ref() {
165 None => break,
166 Some(err) => err,
167 };
168 write!(f, ": ")?;
169 }
170 Ok(())
171 }
172}
173
174impl fmt::Display for Kind {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 match *self {
177 Kind::AdHoc(ref err) => err.fmt(f),
178 Kind::ArkServer(ref err) => err.fmt(f),
179 Kind::Core(ref err) => err.fmt(f),
180 Kind::CoinSelect(ref err) => err.fmt(f),
181 Kind::Wallet(ref err) => err.fmt(f),
182 Kind::ServerInfoChanged(ref err) => err.fmt(f),
183 Kind::Consumer(ref err) => err.fmt(f),
184 }
185 }
186}
187
188impl fmt::Display for AdHocError {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 self.source.fmt(f)
191 }
192}
193
194impl fmt::Display for ArkServerError {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 self.source.fmt(f)
197 }
198}
199
200impl fmt::Display for CoreError {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 self.source.fmt(f)
203 }
204}
205
206impl fmt::Display for CoinSelectError {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 self.source.fmt(f)
209 }
210}
211
212impl fmt::Display for WalletError {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 self.source.fmt(f)
215 }
216}
217
218impl fmt::Display for ServerInfoChangedError {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 f.write_str("Ark server info changed while processing the request. Server info was refreshed, but the failed operation was not retried. Rebuild the request and retry if safe")
221 }
222}
223
224impl fmt::Display for ConsumerError {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 self.source.fmt(f)
227 }
228}
229
230impl From<ark_core::Error> for Error {
231 fn from(value: ark_core::Error) -> Self {
232 Self::new(Kind::Core(CoreError { source: value }))
233 }
234}
235
236pub trait IntoError {
237 fn into_error(self) -> Error;
238}
239
240impl IntoError for Error {
241 fn into_error(self) -> Error {
242 self
243 }
244}
245
246impl IntoError for &'static str {
247 fn into_error(self) -> Error {
248 Error::ad_hoc(self)
249 }
250}
251
252impl IntoError for String {
253 fn into_error(self) -> Error {
254 Error::ad_hoc(self)
255 }
256}
257
258pub trait ErrorContext {
266 type Output;
267
268 fn context(self, consequent: impl IntoError) -> Self::Output;
277
278 fn with_context<E: IntoError>(self, consequent: impl FnOnce() -> E) -> Self::Output;
285}
286
287impl ErrorContext for Error {
288 type Output = Error;
289
290 fn context(self, consequent: impl IntoError) -> Error {
291 let mut err = consequent.into_error();
292 assert!(
293 err.inner.cause.is_none(),
294 "cause of consequence must be `None`"
295 );
296
297 err.inner.cause = Some(self);
298 err
299 }
300
301 fn with_context<E: IntoError>(self, consequent: impl FnOnce() -> E) -> Error {
302 let mut err = consequent().into_error();
303 assert!(
304 err.inner.cause.is_none(),
305 "cause of consequence must be `None`"
306 );
307
308 err.inner.cause = Some(self);
309 err
310 }
311}
312
313impl<T, E> ErrorContext for Result<T, E>
314where
315 E: StdError + Send + Sync + 'static,
316{
317 type Output = Result<T, Error>;
318
319 fn context(self, consequent: impl IntoError) -> Result<T, Error> {
320 self.map_err(|err| {
321 let err: Box<dyn StdError + Send + Sync + 'static> = Box::new(err);
322 match err.downcast::<Error>() {
323 Ok(err) => (*err).context(consequent),
324 Err(err) => Error::ad_hoc(err).context(consequent),
325 }
326 })
327 }
328
329 fn with_context<C: IntoError>(self, consequent: impl FnOnce() -> C) -> Result<T, Error> {
330 self.map_err(|err| {
331 let err: Box<dyn StdError + Send + Sync + 'static> = Box::new(err);
332 match err.downcast::<Error>() {
333 Ok(err) => (*err).with_context(consequent),
334 Err(err) => Error::ad_hoc(err).with_context(consequent),
335 }
336 })
337 }
338}
339
340impl From<ark_grpc::Error> for Error {
341 fn from(value: ark_grpc::Error) -> Self {
342 if value.is_server_info_changed() {
343 Self::server_info_changed(Self::ark_server(value))
344 } else {
345 Self::ark_server(value)
346 }
347 }
348}
349
350impl StdError for Error {
351 fn source(&self) -> Option<&(dyn StdError + 'static)> {
352 self.inner
353 .cause
354 .as_ref()
355 .map(|e| e as &(dyn StdError + 'static))
356 }
357}