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