use thiserror::Error;
pub type LangExtractResult<T> = Result<T, LangExtractError>;
#[derive(Error, Debug)]
pub enum LangExtractError {
#[error("Configuration error: {0}")]
ConfigurationError(String),
#[error("Inference error: {message}")]
InferenceError {
message: String,
provider: Option<String>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Network error: {0}")]
NetworkError(#[from] reqwest::Error),
#[error("Parsing error: {0}")]
ParsingError(String),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Processing error: {0}")]
ProcessingError(String),
#[error("Tokenization error: {0}")]
TokenizationError(String),
#[error("Chunking error: {0}")]
ChunkingError(String),
#[error("Visualization error: {0}")]
VisualizationError(String),
#[error("Unexpected error: {0}")]
UnexpectedError(String),
}
impl LangExtractError {
pub fn configuration<S: Into<String>>(message: S) -> Self {
Self::ConfigurationError(message.into())
}
pub fn inference<S: Into<String>>(
message: S,
provider: Option<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::InferenceError {
message: message.into(),
provider,
source,
}
}
pub fn inference_simple<S: Into<String>>(message: S) -> Self {
Self::InferenceError {
message: message.into(),
provider: None,
source: None,
}
}
pub fn invalid_input<S: Into<String>>(message: S) -> Self {
Self::InvalidInput(message.into())
}
pub fn parsing<S: Into<String>>(message: S) -> Self {
Self::ParsingError(message.into())
}
pub fn serialization<S: Into<String>>(message: S) -> Self {
Self::SerializationError(message.into())
}
pub fn processing<S: Into<String>>(message: S) -> Self {
Self::ProcessingError(message.into())
}
pub fn tokenization<S: Into<String>>(message: S) -> Self {
Self::TokenizationError(message.into())
}
pub fn chunking<S: Into<String>>(message: S) -> Self {
Self::ChunkingError(message.into())
}
pub fn visualization<S: Into<String>>(message: S) -> Self {
Self::VisualizationError(message.into())
}
pub fn unexpected<S: Into<String>>(message: S) -> Self {
Self::UnexpectedError(message.into())
}
pub fn provider(&self) -> Option<&str> {
match self {
Self::InferenceError { provider, .. } => provider.as_deref(),
_ => None,
}
}
pub fn is_configuration_error(&self) -> bool {
matches!(self, Self::ConfigurationError(_))
}
pub fn is_inference_error(&self) -> bool {
matches!(self, Self::InferenceError { .. })
}
pub fn is_network_error(&self) -> bool {
matches!(self, Self::NetworkError(_))
}
pub fn is_parsing_error(&self) -> bool {
matches!(self, Self::ParsingError(_))
}
}
impl From<serde_json::Error> for LangExtractError {
fn from(err: serde_json::Error) -> Self {
Self::SerializationError(format!("JSON error: {}", err))
}
}
impl From<serde_yaml::Error> for LangExtractError {
fn from(err: serde_yaml::Error) -> Self {
Self::SerializationError(format!("YAML error: {}", err))
}
}
impl From<dotenvy::Error> for LangExtractError {
fn from(err: dotenvy::Error) -> Self {
Self::ConfigurationError(format!("Environment variable error: {}", err))
}
}
#[derive(Error, Debug)]
pub enum InferenceError {
#[error("No outputs available: {message}")]
NoOutputsAvailable { message: String },
#[error("Rate limit exceeded for provider {provider}")]
RateLimitExceeded { provider: String },
#[error("Invalid model configuration: {message}")]
InvalidConfiguration { message: String },
#[error("Model not found: {model_id}")]
ModelNotFound { model_id: String },
#[error("Authentication failed for provider {provider}: {message}")]
AuthenticationFailed { provider: String, message: String },
#[error("Quota exceeded for provider {provider}")]
QuotaExceeded { provider: String },
#[error("Service unavailable for provider {provider}")]
ServiceUnavailable { provider: String },
#[error("Inference failed: {message}")]
InferenceFailed { message: String },
}
impl From<InferenceError> for LangExtractError {
fn from(err: InferenceError) -> Self {
let provider = match &err {
InferenceError::RateLimitExceeded { provider } => Some(provider.clone()),
InferenceError::AuthenticationFailed { provider, .. } => Some(provider.clone()),
InferenceError::QuotaExceeded { provider } => Some(provider.clone()),
InferenceError::ServiceUnavailable { provider } => Some(provider.clone()),
_ => None,
};
Self::InferenceError {
message: err.to_string(),
provider,
source: Some(Box::new(err)),
}
}
}
#[derive(Error, Debug)]
pub enum ResolverError {
#[error("Failed to parse output: {message}")]
ParseError { message: String },
#[error("Invalid output format: expected {expected}, got {actual}")]
InvalidFormat { expected: String, actual: String },
#[error("Missing required fields: {fields:?}")]
MissingFields { fields: Vec<String> },
#[error("Schema validation failed: {message}")]
SchemaValidationFailed { message: String },
}
impl From<ResolverError> for LangExtractError {
fn from(err: ResolverError) -> Self {
Self::ParsingError(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let config_err = LangExtractError::configuration("Missing API key");
assert!(config_err.is_configuration_error());
assert!(!config_err.is_inference_error());
let inference_err = LangExtractError::inference(
"Model failed",
Some("gemini".to_string()),
None,
);
assert!(inference_err.is_inference_error());
assert_eq!(inference_err.provider(), Some("gemini"));
let parsing_err = LangExtractError::parsing("Invalid JSON");
assert!(parsing_err.is_parsing_error());
}
#[test]
fn test_error_conversion() {
let json_error = serde_json::from_str::<serde_json::Value>("invalid json");
assert!(json_error.is_err());
let lang_error: LangExtractError = json_error.unwrap_err().into();
assert!(lang_error.is_parsing_error() || matches!(lang_error, LangExtractError::SerializationError(_)));
}
#[test]
fn test_inference_error_conversion() {
let inference_err = InferenceError::ModelNotFound {
model_id: "unknown-model".to_string(),
};
let lang_err: LangExtractError = inference_err.into();
assert!(lang_err.is_inference_error());
}
#[test]
fn test_error_display() {
let error = LangExtractError::configuration("Test error message");
let display = format!("{}", error);
assert!(display.contains("Configuration error"));
assert!(display.contains("Test error message"));
}
#[test]
fn test_resolver_error_conversion() {
let resolver_err = ResolverError::ParseError {
message: "Invalid JSON structure".to_string(),
};
let lang_err: LangExtractError = resolver_err.into();
assert!(lang_err.is_parsing_error());
}
}