Skip to main content

ai_lib_rust/
error.rs

1use crate::pipeline::PipelineError;
2use crate::protocol::ProtocolError;
3use thiserror::Error;
4
5/// Structured error context for better error handling and debugging.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ErrorContext {
8    /// Field path or configuration key that caused the error (e.g., "manifest.base_url", "request.messages[0].content")
9    pub field_path: Option<String>,
10    /// Additional context about the error (e.g., expected type, actual value)
11    pub details: Option<String>,
12    /// Source of the error (e.g., "protocol_loader", "request_validator")
13    pub source: Option<String>,
14    /// Actionable hint or suggestion for the user
15    pub hint: Option<String>,
16    /// Request identifiers for tracking
17    pub request_id: Option<String>,
18    /// HTTP status code if applicable
19    pub status_code: Option<u16>,
20    /// Provider-specific error code
21    pub error_code: Option<String>,
22    /// Flag indicating if the error is retryable
23    pub retryable: Option<bool>,
24    /// Flag indicating if the error should trigger a fallback
25    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/// Unified error type for the AI-Protocol Runtime
96/// This aggregates all low-level errors into actionable, high-level categories
97#[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
150// Helper function to format error context for display
151fn 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    /// Create a new runtime error with structured context
197    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    /// Create a new validation error with structured context
205    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    /// Create a new configuration error with structured context
213    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    /// Create a new unknown error with structured context
221    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    /// Extract error context if available
229    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    /// Attach or update context to the error
244    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
271// Re-export specific error types for convenience
272pub use crate::pipeline::PipelineError as Pipeline;
273pub use crate::protocol::ProtocolError as Protocol;