Skip to main content

rlmesh_runtime/driver/
error.rs

1//! The driver's error type, [`RuntimeError`].
2
3use std::time::Duration;
4
5use crate::hooks::HookError;
6
7/// Type-erased structured error preserved as the `#[source]` of RPC failures.
8pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
9
10#[derive(Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum RuntimeError {
13    #[error("invalid runtime session spec: {0}")]
14    InvalidSpec(String),
15
16    #[error(
17        "{operation} timed out on route {route_id} component {component_id} at runtime step {step} after {timeout:?}"
18    )]
19    OperationTimeout {
20        route_id: String,
21        component_id: String,
22        operation: &'static str,
23        step: i64,
24        timeout: Duration,
25    },
26
27    #[error("route {route_id} cancelled at runtime step {step}: {reason}")]
28    RouteCancelled {
29        route_id: String,
30        step: i64,
31        reason: String,
32    },
33
34    #[error(
35        "environment {operation} failed at runtime step {step}: {message}. If the source is 'transport error: connection closed', the environment server exited, crashed, or received SIGTERM before replying; inspect the environment container logs immediately before the runtime error timestamp"
36    )]
37    EnvRpc {
38        operation: &'static str,
39        step: i64,
40        message: String,
41        /// Whether the underlying transport error is retryable. Captured at
42        /// construction by the adapter, which owns the structured error.
43        recoverable: bool,
44        /// The structured underlying error, preserved so callers can downcast
45        /// or inspect the chain. `rlmesh-runtime` does not depend on
46        /// `rlmesh-grpc`, so the concrete type is erased here.
47        #[source]
48        source: Option<BoxError>,
49    },
50
51    #[error("model endpoint {component_id} request failed: {message}")]
52    ModelRpc {
53        component_id: String,
54        message: String,
55        /// Whether the underlying error is retryable. Captured at construction.
56        recoverable: bool,
57        #[source]
58        source: Option<BoxError>,
59    },
60
61    #[error(
62        "model endpoint {component_id} returned mismatched route identity for request {request_id}"
63    )]
64    ModelRouteMismatch {
65        component_id: String,
66        request_id: String,
67    },
68
69    #[error("protocol error: {0}")]
70    Protocol(String),
71
72    #[error("runtime hook failed: {0}")]
73    Hook(HookError),
74}
75
76impl RuntimeError {
77    pub fn operation_timeout(
78        route_id: impl Into<String>,
79        component_id: impl Into<String>,
80        operation: &'static str,
81        step: i64,
82        timeout: Duration,
83    ) -> Self {
84        Self::OperationTimeout {
85            route_id: route_id.into(),
86            component_id: component_id.into(),
87            operation,
88            step,
89            timeout,
90        }
91    }
92
93    pub fn route_cancelled(
94        route_id: impl Into<String>,
95        step: i64,
96        reason: impl Into<String>,
97    ) -> Self {
98        Self::RouteCancelled {
99            route_id: route_id.into(),
100            step,
101            reason: reason.into(),
102        }
103    }
104
105    /// Constructs an [`EnvRpc`](Self::EnvRpc) error, capturing the underlying
106    /// error's recoverability and preserving it as a structured `#[source]`.
107    pub fn env_rpc<E>(operation: &'static str, step: i64, source: E) -> Self
108    where
109        E: std::error::Error + Send + Sync + 'static,
110    {
111        Self::env_rpc_with_recoverability(operation, step, false, source)
112    }
113
114    /// Constructs an [`EnvRpc`](Self::EnvRpc) error with an explicit
115    /// recoverability flag (e.g. from `GrpcError::is_recoverable`), preserving
116    /// the structured source.
117    pub fn env_rpc_with_recoverability<E>(
118        operation: &'static str,
119        step: i64,
120        recoverable: bool,
121        source: E,
122    ) -> Self
123    where
124        E: std::error::Error + Send + Sync + 'static,
125    {
126        Self::EnvRpc {
127            operation,
128            step,
129            message: source.to_string(),
130            recoverable,
131            source: Some(Box::new(source)),
132        }
133    }
134
135    /// Constructs a [`ModelRpc`](Self::ModelRpc) error, preserving the
136    /// structured source. Recoverability defaults to `false`.
137    pub fn model_rpc<E>(component_id: impl Into<String>, source: E) -> Self
138    where
139        E: std::error::Error + Send + Sync + 'static,
140    {
141        Self::model_rpc_with_recoverability(component_id, false, source)
142    }
143
144    /// Constructs a [`ModelRpc`](Self::ModelRpc) error with an explicit
145    /// recoverability flag, preserving the structured source.
146    pub fn model_rpc_with_recoverability<E>(
147        component_id: impl Into<String>,
148        recoverable: bool,
149        source: E,
150    ) -> Self
151    where
152        E: std::error::Error + Send + Sync + 'static,
153    {
154        Self::ModelRpc {
155            component_id: component_id.into(),
156            message: source.to_string(),
157            recoverable,
158            source: Some(Box::new(source)),
159        }
160    }
161
162    /// Whether this error is recoverable (retryable).
163    ///
164    /// For RPC failures this reflects the recoverability captured from the
165    /// underlying transport error at construction. All other variants are
166    /// treated as non-recoverable.
167    pub fn is_recoverable(&self) -> bool {
168        match self {
169            Self::EnvRpc { recoverable, .. } | Self::ModelRpc { recoverable, .. } => *recoverable,
170            _ => false,
171        }
172    }
173}