1use crate::error_code::StandardErrorCode;
44use crate::pipeline::PipelineError;
45use crate::protocol::ProtocolError;
46use thiserror::Error;
47
48#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ErrorContext {
54 pub field_path: Option<String>,
57 pub details: Option<String>,
59 pub source: Option<String>,
61 pub hint: Option<String>,
63 pub request_id: Option<String>,
65 pub status_code: Option<u16>,
67 pub error_code: Option<String>,
69 pub retryable: Option<bool>,
71 pub fallbackable: Option<bool>,
73 pub standard_code: Option<StandardErrorCode>,
75}
76
77impl ErrorContext {
78 pub fn new() -> Self {
79 Self {
80 field_path: None,
81 details: None,
82 source: None,
83 hint: None,
84 request_id: None,
85 status_code: None,
86 error_code: None,
87 retryable: None,
88 fallbackable: None,
89 standard_code: None,
90 }
91 }
92
93 pub fn with_field_path(mut self, path: impl Into<String>) -> Self {
94 self.field_path = Some(path.into());
95 self
96 }
97
98 pub fn with_details(mut self, details: impl Into<String>) -> Self {
99 self.details = Some(details.into());
100 self
101 }
102
103 pub fn with_source(mut self, source: impl Into<String>) -> Self {
104 self.source = Some(source.into());
105 self
106 }
107
108 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
109 self.hint = Some(hint.into());
110 self
111 }
112
113 pub fn with_request_id(mut self, id: impl Into<String>) -> Self {
114 self.request_id = Some(id.into());
115 self
116 }
117
118 pub fn with_status_code(mut self, code: u16) -> Self {
119 self.status_code = Some(code);
120 self
121 }
122
123 pub fn with_error_code(mut self, code: impl Into<String>) -> Self {
124 self.error_code = Some(code.into());
125 self
126 }
127
128 pub fn with_retryable(mut self, retryable: bool) -> Self {
129 self.retryable = Some(retryable);
130 self
131 }
132
133 pub fn with_fallbackable(mut self, fallbackable: bool) -> Self {
134 self.fallbackable = Some(fallbackable);
135 self
136 }
137
138 pub fn with_standard_code(mut self, code: StandardErrorCode) -> Self {
139 self.standard_code = Some(code);
140 self
141 }
142}
143
144impl Default for ErrorContext {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150#[derive(Debug, Error)]
153pub enum Error {
154 #[error("Protocol specification error: {0}")]
155 Protocol(#[from] ProtocolError),
156
157 #[error("Pipeline processing error: {0}")]
158 Pipeline(#[from] PipelineError),
159
160 #[error("Configuration error: {message}{}", format_context(.context))]
161 Configuration {
162 message: String,
163 context: ErrorContext,
164 },
165
166 #[error("Validation error: {message}{}", format_context(.context))]
167 Validation {
168 message: String,
169 context: ErrorContext,
170 },
171
172 #[error("Runtime error: {message}{}", format_context(.context))]
173 Runtime {
174 message: String,
175 context: ErrorContext,
176 },
177
178 #[error("Network transport error: {0}")]
179 Transport(#[from] crate::transport::TransportError),
180
181 #[error("I/O error: {0}")]
182 Io(#[from] std::io::Error),
183
184 #[error("Serialization error: {0}")]
185 Serialization(#[from] serde_json::Error),
186
187 #[error("Remote error: HTTP {status} ({class}): {message}{}", format_optional_context(.context))]
188 Remote {
189 status: u16,
190 class: String,
191 message: String,
192 retryable: bool,
193 fallbackable: bool,
194 retry_after_ms: Option<u32>,
195 context: Option<ErrorContext>,
196 },
197
198 #[error("Unknown error: {message}{}", format_context(.context))]
199 Unknown {
200 message: String,
201 context: ErrorContext,
202 },
203}
204
205fn format_context(ctx: &ErrorContext) -> String {
208 use std::fmt::Write;
209 let mut buf = String::new();
210
211 let has_meta = ctx.field_path.is_some()
213 || ctx.details.is_some()
214 || ctx.source.is_some()
215 || ctx.request_id.is_some()
216 || ctx.status_code.is_some()
217 || ctx.error_code.is_some()
218 || ctx.retryable.is_some()
219 || ctx.fallbackable.is_some()
220 || ctx.standard_code.is_some();
221
222 if has_meta {
223 buf.push_str(" [");
224 let mut first = true;
225 macro_rules! append_field {
226 ($label:expr, $val:expr) => {
227 if let Some(ref v) = $val {
228 if !first { buf.push_str(", "); }
229 let _ = write!(buf, "{}: {}", $label, v);
230 first = false;
231 }
232 };
233 }
234 append_field!("field", ctx.field_path);
235 append_field!("details", ctx.details);
236 append_field!("source", ctx.source);
237 append_field!("request_id", ctx.request_id);
238 if let Some(code) = ctx.status_code {
239 if !first { buf.push_str(", "); }
240 let _ = write!(buf, "status: {}", code);
241 first = false;
242 }
243 append_field!("error_code", ctx.error_code);
244 if let Some(r) = ctx.retryable {
245 if !first { buf.push_str(", "); }
246 let _ = write!(buf, "retryable: {}", r);
247 first = false;
248 }
249 if let Some(f) = ctx.fallbackable {
250 if !first { buf.push_str(", "); }
251 let _ = write!(buf, "fallbackable: {}", f);
252 #[allow(unused_assignments)]
253 { first = false; }
254 }
255 if let Some(ref std_code) = ctx.standard_code {
256 if !first { buf.push_str(", "); }
257 let _ = write!(buf, "standard_code: {}", std_code.code());
258 }
259 buf.push(']');
260 }
261
262 if let Some(ref hint) = ctx.hint {
263 let _ = write!(buf, "\n💡 Hint: {}", hint);
264 }
265
266 buf
267}
268
269fn format_optional_context(ctx: &Option<ErrorContext>) -> String {
270 ctx.as_ref().map(format_context).unwrap_or_default()
271}
272
273impl Error {
274 pub fn runtime_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
276 Error::Runtime {
277 message: msg.into(),
278 context,
279 }
280 }
281
282 pub fn validation_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
284 Error::Validation {
285 message: msg.into(),
286 context,
287 }
288 }
289
290 pub fn configuration_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
292 Error::Configuration {
293 message: msg.into(),
294 context,
295 }
296 }
297
298 pub fn unknown_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
300 Error::Unknown {
301 message: msg.into(),
302 context,
303 }
304 }
305
306 pub fn validation(msg: impl Into<String>) -> Self {
308 Self::validation_with_context(msg, ErrorContext::new())
309 }
310
311 pub fn configuration(msg: impl Into<String>) -> Self {
313 Self::configuration_with_context(msg, ErrorContext::new())
314 }
315
316 pub fn network_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
318 Error::Runtime {
319 message: format!("Network error: {}", msg.into()),
320 context,
321 }
322 }
323
324 pub fn api_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
326 Error::Runtime {
327 message: format!("API error: {}", msg.into()),
328 context,
329 }
330 }
331
332 pub fn parsing(msg: impl Into<String>) -> Self {
334 Error::Validation {
335 message: format!("Parsing error: {}", msg.into()),
336 context: ErrorContext::new().with_source("parsing"),
337 }
338 }
339
340 pub fn context(&self) -> Option<&ErrorContext> {
342 match self {
343 Error::Configuration { context, .. }
344 | Error::Validation { context, .. }
345 | Error::Runtime { context, .. }
346 | Error::Unknown { context, .. } => Some(context),
347 Error::Remote {
348 context: Some(ref c),
349 ..
350 } => Some(c),
351 _ => None,
352 }
353 }
354
355 pub fn standard_code(&self) -> Option<StandardErrorCode> {
360 match self {
361 Error::Remote {
362 status,
363 class,
364 context,
365 ..
366 } => context
367 .as_ref()
368 .and_then(|c| c.standard_code)
369 .or_else(|| {
370 let from_class = StandardErrorCode::from_error_class(class);
372 if from_class == StandardErrorCode::Unknown {
373 Some(StandardErrorCode::from_http_status(*status))
374 } else {
375 Some(from_class)
376 }
377 }),
378 Error::Configuration { context, .. }
379 | Error::Validation { context, .. }
380 | Error::Runtime { context, .. }
381 | Error::Unknown { context, .. } => context.standard_code,
382 _ => None,
383 }
384 }
385
386 pub fn with_context(mut self, new_ctx: ErrorContext) -> Self {
388 match &mut self {
389 Error::Configuration {
390 ref mut context, ..
391 }
392 | Error::Validation {
393 ref mut context, ..
394 }
395 | Error::Runtime {
396 ref mut context, ..
397 }
398 | Error::Unknown {
399 ref mut context, ..
400 } => {
401 *context = new_ctx;
402 }
403 Error::Remote {
404 ref mut context, ..
405 } => {
406 *context = Some(new_ctx);
407 }
408 _ => {}
409 }
410 self
411 }
412}
413
414pub use crate::pipeline::PipelineError as Pipeline;
416pub use crate::protocol::ProtocolError as Protocol;