use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("Configuration error: {0}")]
Config(String),
#[error("Parse error: {0}")]
Parse(String),
#[error("Component error: {0}")]
Component(String),
#[error("Template error: {0}")]
Template(String),
#[error("Data error: {0}")]
Data(String),
#[error("Cache error: {0}")]
Cache(String),
#[error("Action error: {0}")]
Action(String),
#[error("Server error: {0}")]
Server(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("TOML error: {0}")]
Toml(#[from] toml::de::Error),
#[error("Session error: {0}")]
Session(String),
#[error("SQLite error: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("Authentication error: {0}")]
Auth(String),
#[error("JWT error: {0}")]
Jwt(#[from] jsonwebtoken::errors::Error),
#[error("Upload error: {0}")]
Upload(String),
#[error("Validation error: {0}")]
Validation(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_error_display() {
let err = Error::Config("missing field".to_string());
assert_eq!(err.to_string(), "Configuration error: missing field");
}
#[test]
fn test_parse_error_display() {
let err = Error::Parse("unexpected token".to_string());
assert_eq!(err.to_string(), "Parse error: unexpected token");
}
#[test]
fn test_component_error_display() {
let err = Error::Component("slot not found".to_string());
assert_eq!(err.to_string(), "Component error: slot not found");
}
#[test]
fn test_template_error_display() {
let err = Error::Template("invalid syntax".to_string());
assert_eq!(err.to_string(), "Template error: invalid syntax");
}
#[test]
fn test_data_error_display() {
let err = Error::Data("source unavailable".to_string());
assert_eq!(err.to_string(), "Data error: source unavailable");
}
#[test]
fn test_cache_error_display() {
let err = Error::Cache("connection lost".to_string());
assert_eq!(err.to_string(), "Cache error: connection lost");
}
#[test]
fn test_action_error_display() {
let err = Error::Action("handler failed".to_string());
assert_eq!(err.to_string(), "Action error: handler failed");
}
#[test]
fn test_server_error_display() {
let err = Error::Server("bind failed".to_string());
assert_eq!(err.to_string(), "Server error: bind failed");
}
#[test]
fn test_session_error_display() {
let err = Error::Session("expired".to_string());
assert_eq!(err.to_string(), "Session error: expired");
}
#[test]
fn test_auth_error_display() {
let err = Error::Auth("unauthorized".to_string());
assert_eq!(err.to_string(), "Authentication error: unauthorized");
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err: Error = Error::from(io_err);
match &err {
Error::Io(_) => {}
_ => panic!("Expected Error::Io variant, got: {:?}", err),
}
assert!(err.to_string().contains("file not found"));
}
#[test]
fn test_from_json_error() {
let json_result: std::result::Result<serde_json::Value, _> =
serde_json::from_str("not json");
let json_err = json_result.unwrap_err();
let err: Error = Error::from(json_err);
match &err {
Error::Json(_) => {}
_ => panic!("Expected Error::Json variant, got: {:?}", err),
}
assert!(err.to_string().starts_with("JSON error:"));
}
#[test]
fn test_from_toml_error() {
let toml_result: std::result::Result<toml::Value, _> = toml::from_str("not valid [[[toml");
let toml_err = toml_result.unwrap_err();
let err: Error = Error::from(toml_err);
match &err {
Error::Toml(_) => {}
_ => panic!("Expected Error::Toml variant, got: {:?}", err),
}
assert!(err.to_string().starts_with("TOML error:"));
}
#[test]
fn test_from_rusqlite_error() {
let sqlite_err = rusqlite::Error::InvalidParameterName("bad_param".to_string());
let err: Error = Error::from(sqlite_err);
match &err {
Error::Sqlite(_) => {}
_ => panic!("Expected Error::Sqlite variant, got: {:?}", err),
}
assert!(err.to_string().starts_with("SQLite error:"));
}
#[test]
fn test_from_jwt_error() {
let jwt_result = jsonwebtoken::decode::<serde_json::Value>(
"not.a.token",
&jsonwebtoken::DecodingKey::from_secret(b"secret"),
&jsonwebtoken::Validation::default(),
);
let jwt_err = jwt_result.unwrap_err();
let err: Error = Error::from(jwt_err);
match &err {
Error::Jwt(_) => {}
_ => panic!("Expected Error::Jwt variant, got: {:?}", err),
}
assert!(err.to_string().starts_with("JWT error:"));
}
#[test]
fn test_result_type_ok() {
let result: Result<i32> = Ok(42);
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_result_type_err() {
let result: Result<i32> = Err(Error::Config("test".to_string()));
assert!(result.is_err());
}
#[test]
fn test_error_is_debuggable() {
let err = Error::Config("test error".to_string());
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("Config"));
assert!(debug_str.contains("test error"));
}
#[test]
fn test_error_with_empty_message() {
let err = Error::Parse(String::new());
assert_eq!(err.to_string(), "Parse error: ");
}
#[test]
fn test_io_error_via_question_mark() {
fn may_fail() -> Result<()> {
let _file = std::fs::File::open("/nonexistent/file/path")?;
Ok(())
}
let result = may_fail();
assert!(result.is_err());
match result.unwrap_err() {
Error::Io(_) => {}
other => panic!("Expected Error::Io, got: {:?}", other),
}
}
}