langextract_rust/
exceptions.rs

1//! Error types and result definitions for LangExtract.
2//!
3//! This module provides a comprehensive error handling system for the langextract
4//! library, with specific error types for different failure modes.
5
6use thiserror::Error;
7
8/// Result type alias for LangExtract operations
9pub type LangExtractResult<T> = Result<T, LangExtractError>;
10
11/// Base error type for all LangExtract operations
12///
13/// This enum covers all possible error conditions that can occur
14/// during text extraction and processing.
15#[derive(Error, Debug)]
16pub enum LangExtractError {
17    /// Configuration-related errors (missing API keys, invalid model IDs, etc.)
18    #[error("Configuration error: {0}")]
19    ConfigurationError(String),
20
21    /// Runtime inference errors (API failures, network issues, etc.)
22    #[error("Inference error: {message}")]
23    InferenceError {
24        message: String,
25        provider: Option<String>,
26        #[source]
27        source: Option<Box<dyn std::error::Error + Send + Sync>>,
28    },
29
30    /// Invalid input provided to the library
31    #[error("Invalid input: {0}")]
32    InvalidInput(String),
33
34    /// Network-related errors (URL download failures, etc.)
35    #[error("Network error: {0}")]
36    NetworkError(#[from] reqwest::Error),
37
38    /// JSON/YAML parsing errors
39    #[error("Parsing error: {0}")]
40    ParsingError(String),
41
42    /// I/O errors (file operations, etc.)
43    #[error("I/O error: {0}")]
44    IoError(#[from] std::io::Error),
45
46    /// Serialization/deserialization errors
47    #[error("Serialization error: {0}")]
48    SerializationError(String),
49
50    /// Text processing errors (chunking, alignment, etc.)
51    #[error("Processing error: {0}")]
52    ProcessingError(String),
53
54    /// Tokenization errors
55    #[error("Tokenization error: {0}")]
56    TokenizationError(String),
57
58    /// Chunking errors
59    #[error("Chunking error: {0}")]
60    ChunkingError(String),
61
62    /// Visualization errors
63    #[error("Visualization error: {0}")]
64    VisualizationError(String),
65
66    /// Generic error for unexpected conditions
67    #[error("Unexpected error: {0}")]
68    UnexpectedError(String),
69}
70
71impl LangExtractError {
72    /// Create a new configuration error
73    pub fn configuration<S: Into<String>>(message: S) -> Self {
74        Self::ConfigurationError(message.into())
75    }
76
77    /// Create a new inference error with provider information
78    pub fn inference<S: Into<String>>(
79        message: S,
80        provider: Option<String>,
81        source: Option<Box<dyn std::error::Error + Send + Sync>>,
82    ) -> Self {
83        Self::InferenceError {
84            message: message.into(),
85            provider,
86            source,
87        }
88    }
89
90    /// Create a new inference error without provider information
91    pub fn inference_simple<S: Into<String>>(message: S) -> Self {
92        Self::InferenceError {
93            message: message.into(),
94            provider: None,
95            source: None,
96        }
97    }
98
99    /// Create a new invalid input error
100    pub fn invalid_input<S: Into<String>>(message: S) -> Self {
101        Self::InvalidInput(message.into())
102    }
103
104    /// Create a new parsing error
105    pub fn parsing<S: Into<String>>(message: S) -> Self {
106        Self::ParsingError(message.into())
107    }
108
109    /// Create a new serialization error
110    pub fn serialization<S: Into<String>>(message: S) -> Self {
111        Self::SerializationError(message.into())
112    }
113
114    /// Create a new processing error
115    pub fn processing<S: Into<String>>(message: S) -> Self {
116        Self::ProcessingError(message.into())
117    }
118
119    /// Create a new tokenization error
120    pub fn tokenization<S: Into<String>>(message: S) -> Self {
121        Self::TokenizationError(message.into())
122    }
123
124    /// Create a new chunking error
125    pub fn chunking<S: Into<String>>(message: S) -> Self {
126        Self::ChunkingError(message.into())
127    }
128
129    /// Create a new visualization error
130    pub fn visualization<S: Into<String>>(message: S) -> Self {
131        Self::VisualizationError(message.into())
132    }
133
134    /// Create a new unexpected error
135    pub fn unexpected<S: Into<String>>(message: S) -> Self {
136        Self::UnexpectedError(message.into())
137    }
138
139    /// Get the provider name if this is an inference error
140    pub fn provider(&self) -> Option<&str> {
141        match self {
142            Self::InferenceError { provider, .. } => provider.as_deref(),
143            _ => None,
144        }
145    }
146
147    /// Check if this error is related to configuration
148    pub fn is_configuration_error(&self) -> bool {
149        matches!(self, Self::ConfigurationError(_))
150    }
151
152    /// Check if this error is related to inference
153    pub fn is_inference_error(&self) -> bool {
154        matches!(self, Self::InferenceError { .. })
155    }
156
157    /// Check if this error is related to network issues
158    pub fn is_network_error(&self) -> bool {
159        matches!(self, Self::NetworkError(_))
160    }
161
162    /// Check if this error is related to parsing
163    pub fn is_parsing_error(&self) -> bool {
164        matches!(self, Self::ParsingError(_))
165    }
166}
167
168// Convert from serde JSON errors
169impl From<serde_json::Error> for LangExtractError {
170    fn from(err: serde_json::Error) -> Self {
171        Self::SerializationError(format!("JSON error: {}", err))
172    }
173}
174
175// Convert from serde YAML errors
176impl From<serde_yaml::Error> for LangExtractError {
177    fn from(err: serde_yaml::Error) -> Self {
178        Self::SerializationError(format!("YAML error: {}", err))
179    }
180}
181
182// Convert from environment variable errors
183impl From<dotenvy::Error> for LangExtractError {
184    fn from(err: dotenvy::Error) -> Self {
185        Self::ConfigurationError(format!("Environment variable error: {}", err))
186    }
187}
188
189/// Specialized error for inference operations
190#[derive(Error, Debug)]
191pub enum InferenceError {
192    /// No scored outputs available from the language model
193    #[error("No outputs available: {message}")]
194    NoOutputsAvailable { message: String },
195
196    /// API rate limit exceeded
197    #[error("Rate limit exceeded for provider {provider}")]
198    RateLimitExceeded { provider: String },
199
200    /// Invalid model configuration
201    #[error("Invalid model configuration: {message}")]
202    InvalidConfiguration { message: String },
203
204    /// Model not found or unsupported
205    #[error("Model not found: {model_id}")]
206    ModelNotFound { model_id: String },
207
208    /// Authentication failed
209    #[error("Authentication failed for provider {provider}: {message}")]
210    AuthenticationFailed { provider: String, message: String },
211
212    /// Quota exceeded
213    #[error("Quota exceeded for provider {provider}")]
214    QuotaExceeded { provider: String },
215
216    /// Service temporarily unavailable
217    #[error("Service unavailable for provider {provider}")]
218    ServiceUnavailable { provider: String },
219
220    /// Generic inference failure
221    #[error("Inference failed: {message}")]
222    InferenceFailed { message: String },
223}
224
225impl From<InferenceError> for LangExtractError {
226    fn from(err: InferenceError) -> Self {
227        let provider = match &err {
228            InferenceError::RateLimitExceeded { provider } => Some(provider.clone()),
229            InferenceError::AuthenticationFailed { provider, .. } => Some(provider.clone()),
230            InferenceError::QuotaExceeded { provider } => Some(provider.clone()),
231            InferenceError::ServiceUnavailable { provider } => Some(provider.clone()),
232            _ => None,
233        };
234
235        Self::InferenceError {
236            message: err.to_string(),
237            provider,
238            source: Some(Box::new(err)),
239        }
240    }
241}
242
243/// Specialized error for resolver operations
244#[derive(Error, Debug)]
245pub enum ResolverError {
246    /// Failed to parse model output
247    #[error("Failed to parse output: {message}")]
248    ParseError { message: String },
249
250    /// Invalid output format
251    #[error("Invalid output format: expected {expected}, got {actual}")]
252    InvalidFormat { expected: String, actual: String },
253
254    /// Missing required fields in output
255    #[error("Missing required fields: {fields:?}")]
256    MissingFields { fields: Vec<String> },
257
258    /// Schema validation failed
259    #[error("Schema validation failed: {message}")]
260    SchemaValidationFailed { message: String },
261}
262
263impl From<ResolverError> for LangExtractError {
264    fn from(err: ResolverError) -> Self {
265        Self::ParsingError(err.to_string())
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_error_creation() {
275        let config_err = LangExtractError::configuration("Missing API key");
276        assert!(config_err.is_configuration_error());
277        assert!(!config_err.is_inference_error());
278
279        let inference_err = LangExtractError::inference(
280            "Model failed",
281            Some("gemini".to_string()),
282            None,
283        );
284        assert!(inference_err.is_inference_error());
285        assert_eq!(inference_err.provider(), Some("gemini"));
286
287        let parsing_err = LangExtractError::parsing("Invalid JSON");
288        assert!(parsing_err.is_parsing_error());
289    }
290
291    #[test]
292    fn test_error_conversion() {
293        let json_error = serde_json::from_str::<serde_json::Value>("invalid json");
294        assert!(json_error.is_err());
295        
296        let lang_error: LangExtractError = json_error.unwrap_err().into();
297        assert!(lang_error.is_parsing_error() || matches!(lang_error, LangExtractError::SerializationError(_)));
298    }
299
300    #[test]
301    fn test_inference_error_conversion() {
302        let inference_err = InferenceError::ModelNotFound {
303            model_id: "unknown-model".to_string(),
304        };
305        
306        let lang_err: LangExtractError = inference_err.into();
307        assert!(lang_err.is_inference_error());
308    }
309
310    #[test]
311    fn test_error_display() {
312        let error = LangExtractError::configuration("Test error message");
313        let display = format!("{}", error);
314        assert!(display.contains("Configuration error"));
315        assert!(display.contains("Test error message"));
316    }
317
318    #[test]
319    fn test_resolver_error_conversion() {
320        let resolver_err = ResolverError::ParseError {
321            message: "Invalid JSON structure".to_string(),
322        };
323        
324        let lang_err: LangExtractError = resolver_err.into();
325        assert!(lang_err.is_parsing_error());
326    }
327}