use thiserror::Error;
pub type RsllmResult<T> = Result<T, RsllmError>;
#[derive(Error, Debug)]
pub enum RsllmError {
#[error("Configuration error: {message}")]
Configuration {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Provider error ({provider}): {message}")]
Provider {
provider: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Network error: {message}")]
Network {
message: String,
status_code: Option<u16>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Authentication error: {message}")]
Authentication {
message: String,
},
#[error("Rate limit exceeded: {message}")]
RateLimit {
message: String,
retry_after: Option<std::time::Duration>,
},
#[error("API error ({provider}): {message} (code: {code})")]
Api {
provider: String,
message: String,
code: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Serialization error: {message}")]
Serialization {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Streaming error: {message}")]
Streaming {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Operation timed out after {timeout_ms}ms: {operation}")]
Timeout {
operation: String,
timeout_ms: u64,
},
#[error("Validation error: {field} - {message}")]
Validation {
field: String,
message: String,
},
#[error("Resource not found: {resource}")]
NotFound {
resource: String,
},
#[error("Invalid state: {message}")]
InvalidState {
message: String,
},
}
impl RsllmError {
pub fn configuration(message: impl Into<String>) -> Self {
Self::Configuration {
message: message.into(),
source: None,
}
}
pub fn configuration_with_source(
message: impl Into<String>,
source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Configuration {
message: message.into(),
source: Some(source.into()),
}
}
pub fn provider(
provider: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::Provider {
provider: provider.into(),
message: message.into(),
source: None,
}
}
pub fn provider_with_source(
provider: impl Into<String>,
message: impl Into<String>,
source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Provider {
provider: provider.into(),
message: message.into(),
source: Some(source.into()),
}
}
pub fn network(message: impl Into<String>) -> Self {
Self::Network {
message: message.into(),
status_code: None,
source: None,
}
}
pub fn network_with_status(
message: impl Into<String>,
status_code: u16,
) -> Self {
Self::Network {
message: message.into(),
status_code: Some(status_code),
source: None,
}
}
pub fn authentication(message: impl Into<String>) -> Self {
Self::Authentication {
message: message.into(),
}
}
pub fn rate_limit(
message: impl Into<String>,
retry_after: Option<std::time::Duration>,
) -> Self {
Self::RateLimit {
message: message.into(),
retry_after,
}
}
pub fn api(
provider: impl Into<String>,
message: impl Into<String>,
code: impl Into<String>,
) -> Self {
Self::Api {
provider: provider.into(),
message: message.into(),
code: code.into(),
source: None,
}
}
pub fn serialization(message: impl Into<String>) -> Self {
Self::Serialization {
message: message.into(),
source: None,
}
}
pub fn streaming(message: impl Into<String>) -> Self {
Self::Streaming {
message: message.into(),
source: None,
}
}
pub fn timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
Self::Timeout {
operation: operation.into(),
timeout_ms,
}
}
pub fn validation(field: impl Into<String>, message: impl Into<String>) -> Self {
Self::Validation {
field: field.into(),
message: message.into(),
}
}
pub fn not_found(resource: impl Into<String>) -> Self {
Self::NotFound {
resource: resource.into(),
}
}
pub fn invalid_state(message: impl Into<String>) -> Self {
Self::InvalidState {
message: message.into(),
}
}
pub fn category(&self) -> &'static str {
match self {
Self::Configuration { .. } => "configuration",
Self::Provider { .. } => "provider",
Self::Network { .. } => "network",
Self::Authentication { .. } => "authentication",
Self::RateLimit { .. } => "rate_limit",
Self::Api { .. } => "api",
Self::Serialization { .. } => "serialization",
Self::Streaming { .. } => "streaming",
Self::Timeout { .. } => "timeout",
Self::Validation { .. } => "validation",
Self::NotFound { .. } => "not_found",
Self::InvalidState { .. } => "invalid_state",
}
}
pub fn is_retryable(&self) -> bool {
match self {
Self::Network { .. } => true,
Self::RateLimit { .. } => true,
Self::Timeout { .. } => true,
Self::Provider { .. } => false, Self::Api { .. } => false, _ => false,
}
}
pub fn retry_delay(&self) -> Option<std::time::Duration> {
match self {
Self::RateLimit { retry_after, .. } => *retry_after,
Self::Network { .. } => Some(std::time::Duration::from_secs(1)),
Self::Timeout { .. } => Some(std::time::Duration::from_secs(2)),
_ => None,
}
}
}
impl From<serde_json::Error> for RsllmError {
fn from(err: serde_json::Error) -> Self {
Self::serialization(format!("JSON error: {}", err))
}
}
impl From<url::ParseError> for RsllmError {
fn from(err: url::ParseError) -> Self {
Self::configuration(format!("Invalid URL: {}", err))
}
}
#[cfg(feature = "openai")]
impl From<reqwest::Error> for RsllmError {
fn from(err: reqwest::Error) -> Self {
if err.is_timeout() {
Self::timeout("HTTP request", 30000) } else if err.is_connect() {
Self::network(format!("Connection error: {}", err))
} else if let Some(status) = err.status() {
Self::network_with_status(
format!("HTTP error: {}", err),
status.as_u16(),
)
} else {
Self::network(format!("Request error: {}", err))
}
}
}
impl From<tokio::time::error::Elapsed> for RsllmError {
fn from(_err: tokio::time::error::Elapsed) -> Self {
Self::timeout("operation", 0)
}
}