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>;