use std::io;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, ScipixError>;
#[derive(Debug, Error)]
pub enum ScipixError {
#[error("Image error: {0}")]
Image(String),
#[error("Model error: {0}")]
Model(String),
#[error("OCR error: {0}")]
Ocr(String),
#[error("LaTeX error: {0}")]
LaTeX(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Timeout: operation took longer than {0}s")]
Timeout(u64),
#[error("Not found: {0}")]
NotFound(String),
#[error("Authentication error: {0}")]
Auth(String),
#[error("Rate limit exceeded: {0}")]
RateLimit(String),
#[error("Internal error: {0}")]
Internal(String),
}
impl ScipixError {
pub fn is_retryable(&self) -> bool {
match self {
ScipixError::Timeout(_) => true,
ScipixError::RateLimit(_) => true,
ScipixError::Io(_) => true,
ScipixError::Internal(_) => true,
ScipixError::Image(_) => false,
ScipixError::Model(_) => false,
ScipixError::Ocr(_) => false,
ScipixError::LaTeX(_) => false,
ScipixError::Config(_) => false,
ScipixError::Serialization(_) => false,
ScipixError::InvalidInput(_) => false,
ScipixError::NotFound(_) => false,
ScipixError::Auth(_) => false,
}
}
pub fn status_code(&self) -> u16 {
match self {
ScipixError::Auth(_) => 401,
ScipixError::NotFound(_) => 404,
ScipixError::InvalidInput(_) => 400,
ScipixError::RateLimit(_) => 429,
ScipixError::Timeout(_) => 408,
ScipixError::Config(_) => 400,
ScipixError::Internal(_) => 500,
_ => 500,
}
}
pub fn category(&self) -> &'static str {
match self {
ScipixError::Image(_) => "image",
ScipixError::Model(_) => "model",
ScipixError::Ocr(_) => "ocr",
ScipixError::LaTeX(_) => "latex",
ScipixError::Config(_) => "config",
ScipixError::Io(_) => "io",
ScipixError::Serialization(_) => "serialization",
ScipixError::InvalidInput(_) => "invalid_input",
ScipixError::Timeout(_) => "timeout",
ScipixError::NotFound(_) => "not_found",
ScipixError::Auth(_) => "auth",
ScipixError::RateLimit(_) => "rate_limit",
ScipixError::Internal(_) => "internal",
}
}
}
impl From<serde_json::Error> for ScipixError {
fn from(err: serde_json::Error) -> Self {
ScipixError::Serialization(err.to_string())
}
}
impl From<toml::de::Error> for ScipixError {
fn from(err: toml::de::Error) -> Self {
ScipixError::Config(err.to_string())
}
}
impl From<toml::ser::Error> for ScipixError {
fn from(err: toml::ser::Error) -> Self {
ScipixError::Serialization(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = ScipixError::Image("Failed to load".to_string());
assert_eq!(err.to_string(), "Image error: Failed to load");
}
#[test]
fn test_is_retryable() {
assert!(ScipixError::Timeout(30).is_retryable());
assert!(ScipixError::RateLimit("Exceeded".to_string()).is_retryable());
assert!(!ScipixError::Config("Invalid".to_string()).is_retryable());
assert!(!ScipixError::Auth("Unauthorized".to_string()).is_retryable());
}
#[test]
fn test_status_codes() {
assert_eq!(ScipixError::Auth("".to_string()).status_code(), 401);
assert_eq!(ScipixError::NotFound("".to_string()).status_code(), 404);
assert_eq!(ScipixError::InvalidInput("".to_string()).status_code(), 400);
assert_eq!(ScipixError::RateLimit("".to_string()).status_code(), 429);
assert_eq!(ScipixError::Timeout(0).status_code(), 408);
assert_eq!(ScipixError::Internal("".to_string()).status_code(), 500);
}
#[test]
fn test_category() {
assert_eq!(ScipixError::Image("".to_string()).category(), "image");
assert_eq!(ScipixError::Model("".to_string()).category(), "model");
assert_eq!(ScipixError::Ocr("".to_string()).category(), "ocr");
assert_eq!(ScipixError::LaTeX("".to_string()).category(), "latex");
assert_eq!(ScipixError::Config("".to_string()).category(), "config");
assert_eq!(ScipixError::Auth("".to_string()).category(), "auth");
}
#[test]
fn test_from_io_error() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "File not found");
let scipix_err: ScipixError = io_err.into();
assert!(matches!(scipix_err, ScipixError::Io(_)));
}
#[test]
fn test_from_json_error() {
let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
let scipix_err: ScipixError = json_err.into();
assert!(matches!(scipix_err, ScipixError::Serialization(_)));
}
}