Skip to main content

ai_lib_rust/
error.rs

1//! 错误处理模块:提供统一的错误类型和结构化错误上下文。
2//!
3//! # Error Handling Module
4//!
5//! This module provides unified error types and structured error contexts for
6//! comprehensive error handling across the ai-lib-rust library.
7//!
8//! ## Overview
9//!
10//! The error system provides:
11//! - **Unified Error Type**: Single [`Error`] enum for all error conditions
12//! - **Structured Context**: Rich [`ErrorContext`] for debugging information
13//! - **Actionable Hints**: User-friendly suggestions for error resolution
14//! - **Error Classification**: Retryable and fallbackable error marking
15//!
16//! ## Error Categories
17//!
18//! | Variant | Description |
19//! |---------|-------------|
20//! | `Protocol` | Protocol specification errors |
21//! | `Pipeline` | Streaming pipeline errors |
22//! | `Configuration` | Configuration and setup errors |
23//! | `Validation` | Input validation errors |
24//! | `Runtime` | Runtime execution errors |
25//! | `Transport` | Network transport errors |
26//! | `Remote` | Remote API errors (with HTTP status) |
27//!
28//! ## Example
29//!
30//! ```rust
31//! use ai_lib_rust::error::{Error, ErrorContext};
32//!
33//! // Create error with context
34//! let error = Error::validation_with_context(
35//!     "Invalid temperature value",
36//!     ErrorContext::new()
37//!         .with_field_path("request.temperature")
38//!         .with_details("Value must be between 0.0 and 2.0")
39//!         .with_hint("Try setting temperature to 0.7 for balanced output"),
40//! );
41//! ```
42
43use crate::pipeline::PipelineError;
44use crate::protocol::ProtocolError;
45use thiserror::Error;
46
47/// Structured error context for better error handling and debugging.
48///
49/// Provides rich metadata about errors including field paths, details,
50/// hints, and operational flags for retry/fallback decisions.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct ErrorContext {
53    /// Field path or configuration key that caused the error
54    /// (e.g., "manifest.base_url", "request.messages\[0\].content")
55    pub field_path: Option<String>,
56    /// Additional context about the error (e.g., expected type, actual value)
57    pub details: Option<String>,
58    /// Source of the error (e.g., "protocol_loader", "request_validator")
59    pub source: Option<String>,
60    /// Actionable hint or suggestion for the user
61    pub hint: Option<String>,
62    /// Request identifiers for tracking
63    pub request_id: Option<String>,
64    /// HTTP status code if applicable
65    pub status_code: Option<u16>,
66    /// Provider-specific error code
67    pub error_code: Option<String>,
68    /// Flag indicating if the error is retryable
69    pub retryable: Option<bool>,
70    /// Flag indicating if the error should trigger a fallback
71    pub fallbackable: Option<bool>,
72}
73
74impl ErrorContext {
75    pub fn new() -> Self {
76        Self {
77            field_path: None,
78            details: None,
79            source: None,
80            hint: None,
81            request_id: None,
82            status_code: None,
83            error_code: None,
84            retryable: None,
85            fallbackable: None,
86        }
87    }
88
89    pub fn with_field_path(mut self, path: impl Into<String>) -> Self {
90        self.field_path = Some(path.into());
91        self
92    }
93
94    pub fn with_details(mut self, details: impl Into<String>) -> Self {
95        self.details = Some(details.into());
96        self
97    }
98
99    pub fn with_source(mut self, source: impl Into<String>) -> Self {
100        self.source = Some(source.into());
101        self
102    }
103
104    pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
105        self.hint = Some(hint.into());
106        self
107    }
108
109    pub fn with_request_id(mut self, id: impl Into<String>) -> Self {
110        self.request_id = Some(id.into());
111        self
112    }
113
114    pub fn with_status_code(mut self, code: u16) -> Self {
115        self.status_code = Some(code);
116        self
117    }
118
119    pub fn with_error_code(mut self, code: impl Into<String>) -> Self {
120        self.error_code = Some(code.into());
121        self
122    }
123
124    pub fn with_retryable(mut self, retryable: bool) -> Self {
125        self.retryable = Some(retryable);
126        self
127    }
128
129    pub fn with_fallbackable(mut self, fallbackable: bool) -> Self {
130        self.fallbackable = Some(fallbackable);
131        self
132    }
133}
134
135impl Default for ErrorContext {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141/// Unified error type for the AI-Protocol Runtime
142/// This aggregates all low-level errors into actionable, high-level categories
143#[derive(Debug, Error)]
144pub enum Error {
145    #[error("Protocol specification error: {0}")]
146    Protocol(#[from] ProtocolError),
147
148    #[error("Pipeline processing error: {0}")]
149    Pipeline(#[from] PipelineError),
150
151    #[error("Configuration error: {message}{}", format_context(.context))]
152    Configuration {
153        message: String,
154        context: ErrorContext,
155    },
156
157    #[error("Validation error: {message}{}", format_context(.context))]
158    Validation {
159        message: String,
160        context: ErrorContext,
161    },
162
163    #[error("Runtime error: {message}{}", format_context(.context))]
164    Runtime {
165        message: String,
166        context: ErrorContext,
167    },
168
169    #[error("Network transport error: {0}")]
170    Transport(#[from] crate::transport::TransportError),
171
172    #[error("I/O error: {0}")]
173    Io(#[from] std::io::Error),
174
175    #[error("Serialization error: {0}")]
176    Serialization(#[from] serde_json::Error),
177
178    #[error("Remote error: HTTP {status} ({class}): {message}{}", format_optional_context(.context))]
179    Remote {
180        status: u16,
181        class: String,
182        message: String,
183        retryable: bool,
184        fallbackable: bool,
185        retry_after_ms: Option<u32>,
186        context: Option<ErrorContext>,
187    },
188
189    #[error("Unknown error: {message}{}", format_context(.context))]
190    Unknown {
191        message: String,
192        context: ErrorContext,
193    },
194}
195
196// Helper function to format error context for display
197fn format_context(ctx: &ErrorContext) -> String {
198    let mut parts = Vec::new();
199    if let Some(ref field) = ctx.field_path {
200        parts.push(format!("field: {}", field));
201    }
202    if let Some(ref details) = ctx.details {
203        parts.push(format!("details: {}", details));
204    }
205    if let Some(ref source) = ctx.source {
206        parts.push(format!("source: {}", source));
207    }
208    if let Some(ref id) = ctx.request_id {
209        parts.push(format!("request_id: {}", id));
210    }
211    if let Some(code) = ctx.status_code {
212        parts.push(format!("status: {}", code));
213    }
214    if let Some(ref code) = ctx.error_code {
215        parts.push(format!("error_code: {}", code));
216    }
217    if let Some(retryable) = ctx.retryable {
218        parts.push(format!("retryable: {}", retryable));
219    }
220    if let Some(fallbackable) = ctx.fallbackable {
221        parts.push(format!("fallbackable: {}", fallbackable));
222    }
223
224    let ctx_str = if parts.is_empty() {
225        String::new()
226    } else {
227        format!(" [{}]", parts.join(", "))
228    };
229
230    if let Some(ref hint) = ctx.hint {
231        format!("{}\n💡 Hint: {}", ctx_str, hint)
232    } else {
233        ctx_str
234    }
235}
236
237fn format_optional_context(ctx: &Option<ErrorContext>) -> String {
238    ctx.as_ref().map(format_context).unwrap_or_default()
239}
240
241impl Error {
242    /// Create a new runtime error with structured context
243    pub fn runtime_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
244        Error::Runtime {
245            message: msg.into(),
246            context,
247        }
248    }
249
250    /// Create a new validation error with structured context
251    pub fn validation_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
252        Error::Validation {
253            message: msg.into(),
254            context,
255        }
256    }
257
258    /// Create a new configuration error with structured context
259    pub fn configuration_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
260        Error::Configuration {
261            message: msg.into(),
262            context,
263        }
264    }
265
266    /// Create a new unknown error with structured context
267    pub fn unknown_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
268        Error::Unknown {
269            message: msg.into(),
270            context,
271        }
272    }
273
274    /// Create a simple validation error
275    pub fn validation(msg: impl Into<String>) -> Self {
276        Self::validation_with_context(msg, ErrorContext::new())
277    }
278
279    /// Create a simple configuration error
280    pub fn configuration(msg: impl Into<String>) -> Self {
281        Self::configuration_with_context(msg, ErrorContext::new())
282    }
283
284    /// Create a network error with context
285    pub fn network_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
286        Error::Runtime {
287            message: format!("Network error: {}", msg.into()),
288            context,
289        }
290    }
291
292    /// Create an API error with context
293    pub fn api_with_context(msg: impl Into<String>, context: ErrorContext) -> Self {
294        Error::Runtime {
295            message: format!("API error: {}", msg.into()),
296            context,
297        }
298    }
299
300    /// Create a parsing error
301    pub fn parsing(msg: impl Into<String>) -> Self {
302        Error::Validation {
303            message: format!("Parsing error: {}", msg.into()),
304            context: ErrorContext::new().with_source("parsing"),
305        }
306    }
307
308    /// Extract error context if available
309    pub fn context(&self) -> Option<&ErrorContext> {
310        match self {
311            Error::Configuration { context, .. }
312            | Error::Validation { context, .. }
313            | Error::Runtime { context, .. }
314            | Error::Unknown { context, .. } => Some(context),
315            Error::Remote {
316                context: Some(ref c),
317                ..
318            } => Some(c),
319            _ => None,
320        }
321    }
322
323    /// Attach or update context to the error
324    pub fn with_context(mut self, new_ctx: ErrorContext) -> Self {
325        match &mut self {
326            Error::Configuration {
327                ref mut context, ..
328            }
329            | Error::Validation {
330                ref mut context, ..
331            }
332            | Error::Runtime {
333                ref mut context, ..
334            }
335            | Error::Unknown {
336                ref mut context, ..
337            } => {
338                *context = new_ctx;
339            }
340            Error::Remote {
341                ref mut context, ..
342            } => {
343                *context = Some(new_ctx);
344            }
345            _ => {}
346        }
347        self
348    }
349}
350
351// Re-export specific error types for convenience
352pub use crate::pipeline::PipelineError as Pipeline;
353pub use crate::protocol::ProtocolError as Protocol;