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::error_code::StandardErrorCode;
44use crate::pipeline::PipelineError;
45use crate::protocol::ProtocolError;
46use thiserror::Error;
47
48/// Structured error context for better error handling and debugging.
49///
50/// Provides rich metadata about errors including field paths, details,
51/// hints, and operational flags for retry/fallback decisions.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ErrorContext {
54    /// Field path or configuration key that caused the error
55    /// (e.g., "manifest.base_url", "request.messages\[0\].content")
56    pub field_path: Option<String>,
57    /// Additional context about the error (e.g., expected type, actual value)
58    pub details: Option<String>,
59    /// Source of the error (e.g., "protocol_loader", "request_validator")
60    pub source: Option<String>,
61    /// Actionable hint or suggestion for the user
62    pub hint: Option<String>,
63    /// Request identifiers for tracking
64    pub request_id: Option<String>,
65    /// HTTP status code if applicable
66    pub status_code: Option<u16>,
67    /// Provider-specific error code
68    pub error_code: Option<String>,
69    /// Flag indicating if the error is retryable
70    pub retryable: Option<bool>,
71    /// Flag indicating if the error should trigger a fallback
72    pub fallbackable: Option<bool>,
73    /// AI-Protocol V2 standard error code
74    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/// Unified error type for the AI-Protocol Runtime
151/// This aggregates all low-level errors into actionable, high-level categories
152#[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
205// Helper function to format error context for display.
206// Uses a single String buffer to minimize allocations.
207fn format_context(ctx: &ErrorContext) -> String {
208    use std::fmt::Write;
209    let mut buf = String::new();
210
211    // Estimate whether we have any metadata to show
212    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    /// Create a new runtime error with structured context
275    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    /// Create a new validation error with structured context
283    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    /// Create a new configuration error with structured context
291    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    /// Create a new unknown error with structured context
299    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    /// Create a simple validation error
307    pub fn validation(msg: impl Into<String>) -> Self {
308        Self::validation_with_context(msg, ErrorContext::new())
309    }
310
311    /// Create a simple configuration error
312    pub fn configuration(msg: impl Into<String>) -> Self {
313        Self::configuration_with_context(msg, ErrorContext::new())
314    }
315
316    /// Create a network error with context
317    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    /// Create an API error with context
325    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    /// Create a parsing error
333    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    /// Extract error context if available
341    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    /// Returns the AI-Protocol V2 standard error code when available.
356    ///
357    /// For `Remote` errors, derives the code from the error class if not set in context.
358    /// Other variants return the standard code from their `ErrorContext` when present.
359    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                    // Derive from class name, or from HTTP status if class unknown
371                    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    /// Attach or update context to the error
387    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
414// Re-export specific error types for convenience
415pub use crate::pipeline::PipelineError as Pipeline;
416pub use crate::protocol::ProtocolError as Protocol;