use thiserror::Error;
pub mod param_error;
pub use param_error::{ParamErrorContext, ParamType};
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum Error {
#[error("HTTP error: {0}")]
Http(String),
#[error("Database error: {0}")]
Database(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Authentication error: {0}")]
Authentication(String),
#[error("Authorization error: {0}")]
Authorization(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Template not found: {0}")]
TemplateNotFound(String),
#[error("Method not allowed: {0}")]
MethodNotAllowed(String),
#[error("Conflict: {0}")]
Conflict(String),
#[error("Internal server error: {0}")]
Internal(String),
#[error("Improperly configured: {0}")]
ImproperlyConfigured(String),
#[error("Body already consumed")]
BodyAlreadyConsumed,
#[error("Parse error: {0}")]
ParseError(String),
#[error("Missing Content-Type header")]
MissingContentType,
#[error("Invalid page: {0}")]
InvalidPage(String),
#[error("Invalid cursor: {0}")]
InvalidCursor(String),
#[error("Invalid limit: {0}")]
InvalidLimit(String),
#[error("Missing parameter: {0}")]
MissingParameter(String),
#[error("{}", .0.format_error())]
ParamValidation(Box<ParamErrorContext>),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorKind {
Http,
Database,
Serialization,
Validation,
Authentication,
Authorization,
NotFound,
MethodNotAllowed,
Conflict,
Internal,
ImproperlyConfigured,
BodyAlreadyConsumed,
Parse,
ParamValidation,
Other,
}
impl Error {
pub fn status_code(&self) -> u16 {
match self {
Error::Http(_) => 400,
Error::Database(_) => 500,
Error::Serialization(_) => 400,
Error::Validation(_) => 400,
Error::Authentication(_) => 401,
Error::Authorization(_) => 403,
Error::NotFound(_) => 404,
Error::TemplateNotFound(_) => 404,
Error::MethodNotAllowed(_) => 405,
Error::Conflict(_) => 409,
Error::Internal(_) => 500,
Error::ImproperlyConfigured(_) => 500,
Error::BodyAlreadyConsumed => 400,
Error::ParseError(_) => 400,
Error::MissingContentType => 400,
Error::InvalidPage(_) => 400,
Error::InvalidCursor(_) => 400,
Error::InvalidLimit(_) => 400,
Error::MissingParameter(_) => 400,
Error::ParamValidation(_) => 400,
Error::Other(_) => 500,
}
}
pub fn kind(&self) -> ErrorKind {
match self {
Error::Http(_) => ErrorKind::Http,
Error::Database(_) => ErrorKind::Database,
Error::Serialization(_) => ErrorKind::Serialization,
Error::Validation(_) => ErrorKind::Validation,
Error::Authentication(_) => ErrorKind::Authentication,
Error::Authorization(_) => ErrorKind::Authorization,
Error::NotFound(_) => ErrorKind::NotFound,
Error::TemplateNotFound(_) => ErrorKind::NotFound,
Error::MethodNotAllowed(_) => ErrorKind::MethodNotAllowed,
Error::Conflict(_) => ErrorKind::Conflict,
Error::Internal(_) => ErrorKind::Internal,
Error::ImproperlyConfigured(_) => ErrorKind::ImproperlyConfigured,
Error::BodyAlreadyConsumed => ErrorKind::BodyAlreadyConsumed,
Error::ParseError(_) => ErrorKind::Parse,
Error::MissingContentType => ErrorKind::Http,
Error::InvalidPage(_) => ErrorKind::Validation,
Error::InvalidCursor(_) => ErrorKind::Validation,
Error::InvalidLimit(_) => ErrorKind::Validation,
Error::MissingParameter(_) => ErrorKind::Validation,
Error::ParamValidation(_) => ErrorKind::ParamValidation,
Error::Other(_) => ErrorKind::Other,
}
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Serialization(err.to_string())
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Internal(format!("IO error: {}", err))
}
}
impl From<http::Error> for Error {
fn from(err: http::Error) -> Self {
Error::Http(err.to_string())
}
}
impl From<String> for Error {
fn from(msg: String) -> Self {
Error::Internal(msg)
}
}
impl From<&str> for Error {
fn from(msg: &str) -> Self {
Error::Internal(msg.to_string())
}
}
impl From<crate::validators::ValidationErrors> for Error {
fn from(err: crate::validators::ValidationErrors) -> Self {
Error::Validation(format!("Validation failed: {}", err))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_kind_mapping() {
assert_eq!(Error::Http("test".to_string()).kind(), ErrorKind::Http);
assert_eq!(Error::MissingContentType.kind(), ErrorKind::Http);
assert_eq!(
Error::Database("test".to_string()).kind(),
ErrorKind::Database
);
assert_eq!(
Error::Serialization("test".to_string()).kind(),
ErrorKind::Serialization
);
assert_eq!(
Error::Validation("test".to_string()).kind(),
ErrorKind::Validation
);
assert_eq!(
Error::InvalidPage("test".to_string()).kind(),
ErrorKind::Validation
);
assert_eq!(
Error::InvalidCursor("test".to_string()).kind(),
ErrorKind::Validation
);
assert_eq!(
Error::InvalidLimit("test".to_string()).kind(),
ErrorKind::Validation
);
assert_eq!(
Error::MissingParameter("test".to_string()).kind(),
ErrorKind::Validation
);
assert_eq!(
Error::Authentication("test".to_string()).kind(),
ErrorKind::Authentication
);
assert_eq!(
Error::Authorization("test".to_string()).kind(),
ErrorKind::Authorization
);
assert_eq!(
Error::NotFound("test".to_string()).kind(),
ErrorKind::NotFound
);
assert_eq!(
Error::TemplateNotFound("test".to_string()).kind(),
ErrorKind::NotFound
);
assert_eq!(
Error::MethodNotAllowed("test".to_string()).kind(),
ErrorKind::MethodNotAllowed
);
assert_eq!(
Error::Internal("test".to_string()).kind(),
ErrorKind::Internal
);
assert_eq!(
Error::ImproperlyConfigured("test".to_string()).kind(),
ErrorKind::ImproperlyConfigured
);
assert_eq!(
Error::BodyAlreadyConsumed.kind(),
ErrorKind::BodyAlreadyConsumed
);
assert_eq!(
Error::ParseError("test".to_string()).kind(),
ErrorKind::Parse
);
assert_eq!(
Error::Other(anyhow::anyhow!("test")).kind(),
ErrorKind::Other
);
}
#[test]
fn test_from_serde_json_error() {
let json_error = serde_json::from_str::<i32>("invalid").unwrap_err();
let error: Error = json_error.into();
assert_eq!(error.status_code(), 400);
assert_eq!(error.kind(), ErrorKind::Serialization);
assert!(error.to_string().contains("Serialization error"));
}
#[test]
fn test_from_io_error() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let error: Error = io_error.into();
assert_eq!(error.status_code(), 500);
assert_eq!(error.kind(), ErrorKind::Internal);
assert!(error.to_string().contains("IO error"));
}
#[test]
fn test_status_codes_comprehensive() {
assert_eq!(Error::Http("test".to_string()).status_code(), 400);
assert_eq!(Error::Serialization("test".to_string()).status_code(), 400);
assert_eq!(Error::Validation("test".to_string()).status_code(), 400);
assert_eq!(Error::BodyAlreadyConsumed.status_code(), 400);
assert_eq!(Error::ParseError("test".to_string()).status_code(), 400);
assert_eq!(Error::MissingContentType.status_code(), 400);
assert_eq!(Error::InvalidPage("test".to_string()).status_code(), 400);
assert_eq!(Error::InvalidCursor("test".to_string()).status_code(), 400);
assert_eq!(Error::InvalidLimit("test".to_string()).status_code(), 400);
assert_eq!(
Error::MissingParameter("test".to_string()).status_code(),
400
);
assert_eq!(Error::Authentication("test".to_string()).status_code(), 401);
assert_eq!(Error::Authorization("test".to_string()).status_code(), 403);
assert_eq!(Error::NotFound("test".to_string()).status_code(), 404);
assert_eq!(
Error::TemplateNotFound("test".to_string()).status_code(),
404
);
assert_eq!(
Error::MethodNotAllowed("test".to_string()).status_code(),
405
);
assert_eq!(Error::Database("test".to_string()).status_code(), 500);
assert_eq!(Error::Internal("test".to_string()).status_code(), 500);
assert_eq!(
Error::ImproperlyConfigured("test".to_string()).status_code(),
500
);
assert_eq!(Error::Other(anyhow::anyhow!("test")).status_code(), 500);
}
#[test]
fn test_template_not_found_error() {
let error = Error::TemplateNotFound("index.html".to_string());
assert_eq!(error.status_code(), 404);
assert_eq!(error.kind(), ErrorKind::NotFound);
assert!(error.to_string().contains("Template not found"));
assert!(error.to_string().contains("index.html"));
}
#[test]
fn test_pagination_errors() {
let page_error = Error::InvalidPage("page must be positive".to_string());
assert_eq!(page_error.status_code(), 400);
assert_eq!(page_error.kind(), ErrorKind::Validation);
let cursor_error = Error::InvalidCursor("invalid base64".to_string());
assert_eq!(cursor_error.status_code(), 400);
assert_eq!(cursor_error.kind(), ErrorKind::Validation);
let limit_error = Error::InvalidLimit("limit too large".to_string());
assert_eq!(limit_error.status_code(), 400);
assert_eq!(limit_error.kind(), ErrorKind::Validation);
}
#[test]
fn test_missing_parameter_error() {
let error = Error::MissingParameter("user_id".to_string());
assert_eq!(error.status_code(), 400);
assert_eq!(error.kind(), ErrorKind::Validation);
assert!(error.to_string().contains("Missing parameter"));
assert!(error.to_string().contains("user_id"));
}
}