Skip to main content

tonin_core/
error.rs

1//! Framework error type.
2//!
3//! One enum spans both transport errors (existing) and capability errors
4//! (Phase 3). The capability variants carry a transient-vs-permanent
5//! distinction so subscriber loops can implement retry/nack discipline
6//! without matching on the variant directly — call `is_transient()`.
7
8#[derive(Debug, thiserror::Error)]
9pub enum Error {
10    // --- transport / config (pre-Phase-3) ---
11    #[error("transport error: {0}")]
12    Transport(#[from] tonic::transport::Error),
13
14    #[error("config error: {0}")]
15    Config(String),
16
17    #[error(transparent)]
18    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
19
20    // --- capability errors (Phase 3) ---
21    /// The backend rejected the call for a reason that won't go away with
22    /// a retry: auth failure, malformed key, schema mismatch. Fix the call.
23    #[error("capability call rejected: {0}")]
24    CapabilityPermanent(String),
25
26    /// Transient backend trouble: connection blip, timeout, throttle.
27    /// Safe to retry or nack-with-backoff.
28    #[error("capability call failed transiently: {0}")]
29    CapabilityTransient(String),
30
31    /// The impl is a deliberate stub. `tonin-redis::EventBus` returns
32    /// this in Phase 4 (Q2). Callers can detect it and fail loud rather
33    /// than silently dropping work.
34    #[error("not implemented: {0}")]
35    NotImplemented(&'static str),
36}
37
38impl Error {
39    /// True for backend failures that may succeed on retry (network blip,
40    /// timeout, throttle). Subscriber loops use this to decide between
41    /// nack-with-backoff and ack-and-drop.
42    pub fn is_transient(&self) -> bool {
43        matches!(self, Error::CapabilityTransient(_))
44    }
45}
46
47impl From<crate::auth::AuthError> for Error {
48    fn from(e: crate::auth::AuthError) -> Self {
49        Error::Config(e.to_string())
50    }
51}
52
53pub type Result<T> = std::result::Result<T, Error>;