keel/error.rs
1use keel_core_api::{ErrorCode, OutcomeError};
2use std::fmt;
3
4/// The error a `#[keel::wrap]`-wrapped function or [`crate::KeelMiddleware`]
5/// surfaces.
6///
7/// `Original` is your own code's error, unchanged, from the attempt the
8/// engine actually ran last (DX invariant 5's "re-raise the original
9/// exception unchanged", ported to a typed enum since Rust has no untyped
10/// exceptions to re-raise). `Keel` is a judgment the engine made *without*
11/// ever calling your code: a circuit breaker fast-fail (`KEEL-E012`), a rate
12/// budget rejection (`KEEL-E013`), or a non-idempotent call correctly left
13/// un-retried (`KEEL-E014`) — see `contracts/error-codes.json` for the full
14/// taxonomy.
15#[derive(Debug)]
16pub enum Error<E> {
17 /// Your code/library's own error from the last attempt.
18 Original(E),
19 /// A judgment the engine made without calling your code.
20 Keel(OutcomeError),
21}
22
23impl<E: fmt::Display> fmt::Display for Error<E> {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Error::Original(err) => write!(f, "{err}"),
27 Error::Keel(err) => write!(f, "{}: {}", err.code, err.message),
28 }
29 }
30}
31
32impl<E: fmt::Debug + fmt::Display> std::error::Error for Error<E> {}
33
34impl<E> Error<E> {
35 /// The `KEEL-E0NN` code the engine assigned, if this is a [`Error::Keel`]
36 /// judgment rather than your own code's [`Error::Original`] error.
37 #[must_use]
38 pub const fn code(&self) -> Option<ErrorCode> {
39 match self {
40 Error::Original(_) => None,
41 Error::Keel(err) => Some(err.code),
42 }
43 }
44
45 /// Recover your code/library's original error, if this wraps one — an
46 /// engine judgment made without ever calling your code has none to give
47 /// back.
48 pub fn into_original(self) -> Option<E> {
49 match self {
50 Error::Original(err) => Some(err),
51 Error::Keel(_) => None,
52 }
53 }
54}