1use crate::pipeline::PipelineError;
2use crate::protocol::ProtocolError;
3use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ErrorContext {
8 pub field_path: Option<String>,
10 pub details: Option<String>,
12 pub source: Option<String>,
14 pub hint: Option<String>,
16 pub request_id: Option<String>,
18 pub status_code: Option<u16>,
20 pub error_code: Option<String>,
22 pub retryable: Option<bool>,
24 pub fallbackable: Option<bool>,
26}
27
28impl ErrorContext {
29 pub fn new() -> Self {
30 Self {
31 field_path: None,
32 details: None,
33 source: None,
34 hint: None,
35 request_id: None,
36 status_code: None,
37 error_code: None,
38 retryable: None,
39 fallbackable: None,
40 }
41 }
42
43 pub fn with_field_path(mut self, path: impl Into<String>) -> Self {
44 self.field_path = Some(path.into());
45 self
46 }
47
48 pub fn with_details(mut self, details: impl Into<String>) -> Self {
49 self.details = Some(details.into());
50 self
51 }
52
53 pub fn with_source(mut self, source: impl Into<String>) -> Self {
54 self.source = Some(source.into());
55 self
56 }
57
58 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
59 self.hint = Some(hint.into());
60 self
61 }
62
63 pub fn with_request_id(mut self, id: impl Into<String>) -> Self {
64 self.request_id = Some(id.into());
65 self
66 }
67
68 pub fn with_status_code(mut self, code: u16) -> Self {
69 self.status_code = Some(code);
70 self
71 }
72
73 pub fn with_error_code(mut self, code: impl Into<String>) -> Self {
74 self.error_code = Some(code.into());
75 self
76 }
77
78 pub fn with_retryable(mut self, retryable: bool) -> Self {
79 self.retryable = Some(retryable);
80 self
81 }
82
83 pub fn with_fallbackable(mut self, fallbackable: bool) -> Self {
84 self.fallbackable = Some(fallbackable);
85 self
86 }
87}
88
89impl Default for ErrorContext {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95#[derive(Debug, Error)]
98pub enum Error {
99 #[error("Protocol specification error: {0}")]
100 Protocol(#[from] ProtocolError),
101
102 #[error("Pipeline processing error: {0}")]
103 Pipeline(#[from] PipelineError),
104
105 #[error("Configuration error: {message}{}", format_context(.context))]
106 Configuration {
107 message: String,
108 context: ErrorContext,
109 },
110
111 #[error("Validation error: {message}{}", format_context(.context))]
112 Validation {
113 message: String,
114 context: ErrorContext,
115 },
116
117 #[error("Runtime error: {message}{}", format_context(.context))]
118 Runtime {
119 message: String,
120 context: ErrorContext,
121 },
122
123 #[error("Network transport error: {0}")]
124 Transport(#[from] crate::transport::TransportError),
125
126 #[error("I/O error: {0}")]
127 Io(#[from] std::io::Error),
128
129 #[error("Serialization error: {0}")]
130 Serialization(#[from] serde_json::Error),
131
132 #[error("Remote error: HTTP {status} ({class}): {message}{}", format_optional_context(.context))]
133 Remote {
134 status: u16,
135 class: String,
136 message: String,
137 retryable: bool,
138 fallbackable: bool,
139 retry_after_ms: Option<u32>,
140 context: Option<ErrorContext>,
141 },
142
143 #[error("Unknown error: {message}{}", format_context(.context))]
144 Unknown {
145 message: String,
146 context: ErrorContext,
147 },
148}
149
150fn format_context(ctx: &ErrorContext) -> String {
152 let mut parts = Vec::new();
153 if let Some(ref field) = ctx.field_path {
154 parts.push(format!("field: {}", field));
155 }
156 if let Some(ref details) = ctx.details {
157 parts.push(format!("details: {}", details));
158 }
159 if let Some(ref source) = ctx.source {
160 parts.push(format!("source: {}", source));
161 }
162 if let Some(ref id) = ctx.request_id {
163 parts.push(format!("request_id: {}", id));
164 }
165 if let Some(code) = ctx.status_code {
166 parts.push(format!("status: {}", code));
167 }
168 if let Some(ref code) = ctx.error_code {
169 parts.push(format!("error_code: {}", code));
170 }
171 if let Some(retryable) = ctx.retryable {
172 parts.push(format!("retryable: {}", retryable));
173 }
174 if let Some(fallbackable) = ctx.fallbackable {
175 parts.push(format!("fallbackable: {}", fallbackable));
176 }
177
178 let ctx_str = if parts.is_empty() {
179 String::new()
180 } else {
181 format!(" [{}]", parts.join(", "))
182 };
183
184 if let Some(ref hint) = ctx.hint {
185 format!("{}\n💡 Hint: {}", ctx_str, hint)
186 } else {
187 ctx_str
188 }
189}
190
191fn format_optional_context(ctx: &Option<ErrorContext>) -> String {
192 ctx.as_ref().map(format_context).unwrap_or_default()
193}
194
195impl Error {
196 pub fn runtime_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
198 Error::Runtime {
199 message: msg.into(),
200 context,
201 }
202 }
203
204 pub fn validation_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
206 Error::Validation {
207 message: msg.into(),
208 context,
209 }
210 }
211
212 pub fn configuration_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
214 Error::Configuration {
215 message: msg.into(),
216 context,
217 }
218 }
219
220 pub fn unknown_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
222 Error::Unknown {
223 message: msg.into(),
224 context,
225 }
226 }
227
228 pub fn context(&self) -> Option<&ErrorContext> {
230 match self {
231 Error::Configuration { context, .. }
232 | Error::Validation { context, .. }
233 | Error::Runtime { context, .. }
234 | Error::Unknown { context, .. } => Some(context),
235 Error::Remote {
236 context: Some(ref c),
237 ..
238 } => Some(c),
239 _ => None,
240 }
241 }
242
243 pub fn with_context(mut self, new_ctx: ErrorContext) -> Self {
245 match &mut self {
246 Error::Configuration {
247 ref mut context, ..
248 }
249 | Error::Validation {
250 ref mut context, ..
251 }
252 | Error::Runtime {
253 ref mut context, ..
254 }
255 | Error::Unknown {
256 ref mut context, ..
257 } => {
258 *context = new_ctx;
259 }
260 Error::Remote {
261 ref mut context, ..
262 } => {
263 *context = Some(new_ctx);
264 }
265 _ => {}
266 }
267 self
268 }
269}
270
271pub use crate::pipeline::PipelineError as Pipeline;
273pub use crate::protocol::ProtocolError as Protocol;