1use thiserror::Error;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum RetryHint {
8 Retryable,
10 RetryableWithBackoff,
12 NotRetryable,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ErrorCode {
19 ConnectionFailed,
21 ConnectionTimeout,
22 ServiceUnavailable,
23
24 InvalidConfiguration,
26 MissingConfiguration,
27
28 ExecutionFailed,
30 ExecutionTimeout,
31 ExecutionSuspended,
32 DurabilityUnavailable,
33 NondeterministicReplay,
34 StaleAuthority,
35 ActivationCancelled,
36 ActivationContended,
37 UnknownOutcome,
38 PayloadConflict,
39 IllegalTransition,
40 StateVersionConflict,
41 RequiredChildUnresolved,
42
43 InvalidInput,
45 InvalidMessage,
46 InvalidState,
47
48 ResourceExhausted,
50 QuotaExceeded,
51
52 InternalError,
54 NotImplemented,
55
56 SandboxExecutionFailed,
58 SandboxUnavailable,
59 UnsupportedLanguage,
60 UnsupportedOperation,
61}
62
63#[derive(Error, Debug)]
64pub enum SdkError {
65 #[error("gRPC transport error: {0}")]
67 Transport(#[from] tonic::transport::Error),
68
69 #[error("gRPC status error: {0}")]
70 Status(#[from] tonic::Status),
71
72 #[error("Serialization error: {0}")]
73 Serialization(#[from] serde_json::Error),
74
75 #[error("Other error: {0}")]
76 Other(#[from] anyhow::Error),
77
78 #[error("Connection error: {message}")]
80 Connection {
81 message: String,
82 code: ErrorCode,
83 #[source]
84 source: Option<Box<dyn std::error::Error + Send + Sync>>,
85 },
86
87 #[error("Worker registration failed: {message}")]
88 Registration { message: String, code: ErrorCode },
89
90 #[error("Invalid configuration: {message}")]
91 Configuration {
92 message: String,
93 field: Option<String>,
94 },
95
96 #[error("Function invocation error: {message}")]
97 Invocation {
98 message: String,
99 function_name: Option<String>,
100 },
101
102 #[error("State management error: {message}")]
103 State { message: String, code: ErrorCode },
104
105 #[error("Timeout error: {message}")]
106 Timeout {
107 message: String,
108 operation: String,
109 duration_ms: Option<u64>,
110 },
111
112 #[error("Invalid message: {message}")]
113 InvalidMessage {
114 message: String,
115 field: Option<String>,
116 },
117
118 #[error("Execution suspended: {message}")]
119 SuspendedExecution { message: String, reason: String },
120
121 #[error("Durable timer suspended: {}", suspension.timer_key)]
122 DurableSuspension {
123 suspension: Box<crate::pb::WorkerSuspension>,
124 },
125
126 #[error("Replay error: {message}")]
127 ReplayError {
128 message: String,
129 step_id: Option<String>,
130 },
131
132 #[error("Durable activation error ({code:?}): {message}")]
133 Activation {
134 message: String,
135 code: ErrorCode,
136 activation_id: Option<String>,
137 attempt: Option<u32>,
138 },
139
140 #[error("Service call error: {message}")]
141 ServiceCallError { message: String, service: String },
142
143 #[error("Telemetry error: {0}")]
144 TelemetryError(String),
145
146 #[error("Internal error: {0}")]
147 Internal(String),
148
149 #[error("Service unavailable: {message}")]
150 Unavailable {
151 message: String,
152 service: Option<String>,
153 },
154
155 #[error("Invalid argument: {message}")]
156 InvalidArgument {
157 message: String,
158 argument: Option<String>,
159 },
160
161 #[error("LM API error ({status}): {message}")]
162 LmApiError {
163 status: u16,
164 provider: String,
165 message: String,
166 request_id: Option<String>,
167 },
168
169 #[error("Sandbox error ({operation}): {message}")]
170 Sandbox {
171 message: String,
172 operation: String,
173 code: ErrorCode,
174 },
175}
176
177impl SdkError {
178 pub fn code(&self) -> ErrorCode {
180 match self {
181 Self::Transport(_) | Self::Status(_) => ErrorCode::ConnectionFailed,
182 Self::Connection { code, .. } => *code,
183 Self::Registration { code, .. } => *code,
184 Self::Configuration { .. } => ErrorCode::InvalidConfiguration,
185 Self::Invocation { .. } => ErrorCode::ExecutionFailed,
186 Self::State { code, .. } => *code,
187 Self::Timeout { .. } => ErrorCode::ExecutionTimeout,
188 Self::InvalidMessage { .. } => ErrorCode::InvalidMessage,
189 Self::SuspendedExecution { .. } => ErrorCode::ExecutionSuspended,
190 Self::DurableSuspension { .. } => ErrorCode::ExecutionSuspended,
191 Self::ReplayError { .. } => ErrorCode::ExecutionFailed,
192 Self::Activation { code, .. } => *code,
193 Self::ServiceCallError { .. } => ErrorCode::ServiceUnavailable,
194 Self::TelemetryError(_) => ErrorCode::InternalError,
195 Self::Internal(_) => ErrorCode::InternalError,
196 Self::Unavailable { .. } => ErrorCode::ServiceUnavailable,
197 Self::InvalidArgument { .. } => ErrorCode::InvalidInput,
198 Self::Serialization(_) => ErrorCode::InvalidInput,
199 Self::Other(_) => ErrorCode::InternalError,
200 Self::LmApiError { status, .. } => match *status {
201 401 | 403 => ErrorCode::InvalidConfiguration,
202 429 => ErrorCode::ResourceExhausted,
203 408 => ErrorCode::ExecutionTimeout,
204 500 | 502 | 503 | 504 | 529 => ErrorCode::ServiceUnavailable,
205 _ => ErrorCode::InvalidInput,
206 },
207 Self::Sandbox { code, .. } => *code,
208 }
209 }
210
211 pub fn retry_hint(&self) -> RetryHint {
213 match self {
214 Self::Transport(_) | Self::Status(_) => RetryHint::RetryableWithBackoff,
216
217 Self::Connection { code, .. } => match code {
218 ErrorCode::ConnectionTimeout | ErrorCode::ServiceUnavailable => {
219 RetryHint::RetryableWithBackoff
220 }
221 _ => RetryHint::NotRetryable,
222 },
223
224 Self::Unavailable { .. } => RetryHint::RetryableWithBackoff,
226 Self::ServiceCallError { .. } => RetryHint::RetryableWithBackoff,
227
228 Self::Timeout { .. } => RetryHint::Retryable,
230
231 Self::State {
233 code: ErrorCode::ResourceExhausted,
234 ..
235 } => RetryHint::RetryableWithBackoff,
236
237 Self::Configuration { .. }
239 | Self::InvalidMessage { .. }
240 | Self::InvalidArgument { .. }
241 | Self::Serialization(_)
242 | Self::Internal(_)
243 | Self::TelemetryError(_) => RetryHint::NotRetryable,
244
245 Self::Invocation { .. } | Self::ReplayError { .. } => RetryHint::NotRetryable,
247
248 Self::Activation { .. } => RetryHint::NotRetryable,
252
253 Self::SuspendedExecution { .. } => RetryHint::NotRetryable,
255 Self::DurableSuspension { .. } => RetryHint::NotRetryable,
256
257 Self::Registration { .. } => RetryHint::NotRetryable,
259
260 Self::State { .. } => RetryHint::NotRetryable,
262
263 Self::Other(_) => RetryHint::NotRetryable,
265
266 Self::LmApiError { status, .. } => match *status {
268 408 | 429 | 500 | 502 | 503 | 504 | 529 => RetryHint::RetryableWithBackoff,
269 _ => RetryHint::NotRetryable,
270 },
271
272 Self::Sandbox { code, .. } => match code {
274 ErrorCode::SandboxUnavailable => RetryHint::RetryableWithBackoff,
275 _ => RetryHint::NotRetryable,
276 },
277 }
278 }
279
280 pub fn is_retryable(&self) -> bool {
282 matches!(
283 self.retry_hint(),
284 RetryHint::Retryable | RetryHint::RetryableWithBackoff
285 )
286 }
287}
288
289pub type Result<T> = std::result::Result<T, SdkError>;