275 lines
9 KiB
Rust
275 lines
9 KiB
Rust
//! Common error types for Veza services
|
|
//!
|
|
//! This module provides standardized error handling across all Veza services.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
|
|
/// Common error type for Veza services
|
|
#[derive(Debug, Error, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(tag = "type", content = "message")]
|
|
pub enum CommonError {
|
|
/// Resource not found
|
|
#[error("Not found: {0}")]
|
|
NotFound(String),
|
|
|
|
/// Validation error
|
|
#[error("Validation error: {0}")]
|
|
ValidationError(String),
|
|
|
|
/// Internal server error
|
|
#[error("Internal error: {0}")]
|
|
InternalError(String),
|
|
|
|
/// Authentication error
|
|
#[error("Authentication error: {0}")]
|
|
AuthenticationError(String),
|
|
|
|
/// Authorization error
|
|
#[error("Authorization error: {0}")]
|
|
AuthorizationError(String),
|
|
|
|
/// Invalid input data
|
|
#[error("Invalid input: {0}")]
|
|
InvalidInput(String),
|
|
|
|
/// Conflict error (e.g., duplicate resource)
|
|
#[error("Conflict: {0}")]
|
|
Conflict(String),
|
|
|
|
/// Rate limit exceeded
|
|
#[error("Rate limit exceeded: {0}")]
|
|
RateLimitExceeded(String),
|
|
|
|
/// IO error
|
|
#[error("IO error: {0}")]
|
|
IoError(String),
|
|
|
|
/// Serialization/deserialization error
|
|
#[error("Serialization error: {0}")]
|
|
SerializationError(String),
|
|
}
|
|
|
|
/// Error code for each error type
|
|
impl CommonError {
|
|
/// Get the error code for this error
|
|
pub fn code(&self) -> &'static str {
|
|
match self {
|
|
CommonError::NotFound(_) => "NOT_FOUND",
|
|
CommonError::ValidationError(_) => "VALIDATION_ERROR",
|
|
CommonError::InternalError(_) => "INTERNAL_ERROR",
|
|
CommonError::AuthenticationError(_) => "AUTHENTICATION_ERROR",
|
|
CommonError::AuthorizationError(_) => "AUTHORIZATION_ERROR",
|
|
CommonError::InvalidInput(_) => "INVALID_INPUT",
|
|
CommonError::Conflict(_) => "CONFLICT",
|
|
CommonError::RateLimitExceeded(_) => "RATE_LIMIT_EXCEEDED",
|
|
CommonError::IoError(_) => "IO_ERROR",
|
|
CommonError::SerializationError(_) => "SERIALIZATION_ERROR",
|
|
}
|
|
}
|
|
|
|
/// Get the HTTP status code for this error
|
|
pub fn http_status_code(&self) -> u16 {
|
|
match self {
|
|
CommonError::NotFound(_) => 404,
|
|
CommonError::ValidationError(_) => 400,
|
|
CommonError::InternalError(_) => 500,
|
|
CommonError::AuthenticationError(_) => 401,
|
|
CommonError::AuthorizationError(_) => 403,
|
|
CommonError::InvalidInput(_) => 400,
|
|
CommonError::Conflict(_) => 409,
|
|
CommonError::RateLimitExceeded(_) => 429,
|
|
CommonError::IoError(_) => 500,
|
|
CommonError::SerializationError(_) => 400,
|
|
}
|
|
}
|
|
|
|
/// Get the error message
|
|
pub fn message(&self) -> &str {
|
|
match self {
|
|
CommonError::NotFound(msg) => msg,
|
|
CommonError::ValidationError(msg) => msg,
|
|
CommonError::InternalError(msg) => msg,
|
|
CommonError::AuthenticationError(msg) => msg,
|
|
CommonError::AuthorizationError(msg) => msg,
|
|
CommonError::InvalidInput(msg) => msg,
|
|
CommonError::Conflict(msg) => msg,
|
|
CommonError::RateLimitExceeded(msg) => msg,
|
|
CommonError::IoError(msg) => msg,
|
|
CommonError::SerializationError(msg) => msg,
|
|
}
|
|
}
|
|
|
|
/// Check if this error should be logged
|
|
pub fn should_log(&self) -> bool {
|
|
match self {
|
|
CommonError::InternalError(_) | CommonError::IoError(_) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Result type alias for CommonError
|
|
pub type CommonResult<T> = Result<T, CommonError>;
|
|
|
|
/// Conversion from std::io::Error
|
|
impl From<std::io::Error> for CommonError {
|
|
fn from(err: std::io::Error) -> Self {
|
|
CommonError::IoError(err.to_string())
|
|
}
|
|
}
|
|
|
|
/// Conversion from serde_json::Error
|
|
impl From<serde_json::Error> for CommonError {
|
|
fn from(err: serde_json::Error) -> Self {
|
|
CommonError::SerializationError(err.to_string())
|
|
}
|
|
}
|
|
|
|
/// Conversion from uuid::Error
|
|
impl From<uuid::Error> for CommonError {
|
|
fn from(err: uuid::Error) -> Self {
|
|
CommonError::ValidationError(format!("Invalid UUID: {}", err))
|
|
}
|
|
}
|
|
|
|
/// Conversion from chrono::ParseError
|
|
impl From<chrono::ParseError> for CommonError {
|
|
fn from(err: chrono::ParseError) -> Self {
|
|
CommonError::ValidationError(format!("Invalid date format: {}", err))
|
|
}
|
|
}
|
|
|
|
/// JSON representation of the error for API responses
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ErrorResponse {
|
|
pub error: String,
|
|
pub code: String,
|
|
pub status: u16,
|
|
pub message: String,
|
|
}
|
|
|
|
impl From<&CommonError> for ErrorResponse {
|
|
fn from(err: &CommonError) -> Self {
|
|
ErrorResponse {
|
|
error: err.code().to_string(),
|
|
code: err.code().to_string(),
|
|
status: err.http_status_code(),
|
|
message: err.message().to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<CommonError> for ErrorResponse {
|
|
fn from(err: CommonError) -> Self {
|
|
ErrorResponse::from(&err)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_error_codes() {
|
|
assert_eq!(CommonError::NotFound("test".to_string()).code(), "NOT_FOUND");
|
|
assert_eq!(CommonError::ValidationError("test".to_string()).code(), "VALIDATION_ERROR");
|
|
assert_eq!(CommonError::InternalError("test".to_string()).code(), "INTERNAL_ERROR");
|
|
}
|
|
|
|
#[test]
|
|
fn test_http_status_codes() {
|
|
assert_eq!(CommonError::NotFound("test".to_string()).http_status_code(), 404);
|
|
assert_eq!(CommonError::ValidationError("test".to_string()).http_status_code(), 400);
|
|
assert_eq!(CommonError::InternalError("test".to_string()).http_status_code(), 500);
|
|
assert_eq!(CommonError::AuthenticationError("test".to_string()).http_status_code(), 401);
|
|
assert_eq!(CommonError::AuthorizationError("test".to_string()).http_status_code(), 403);
|
|
assert_eq!(CommonError::Conflict("test".to_string()).http_status_code(), 409);
|
|
assert_eq!(CommonError::RateLimitExceeded("test".to_string()).http_status_code(), 429);
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_message() {
|
|
let err = CommonError::NotFound("Resource not found".to_string());
|
|
assert_eq!(err.message(), "Resource not found");
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_should_log() {
|
|
assert!(CommonError::InternalError("test".to_string()).should_log());
|
|
assert!(CommonError::IoError("test".to_string()).should_log());
|
|
assert!(!CommonError::NotFound("test".to_string()).should_log());
|
|
assert!(!CommonError::ValidationError("test".to_string()).should_log());
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_io_error() {
|
|
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
|
|
let common_err: CommonError = io_err.into();
|
|
match common_err {
|
|
CommonError::IoError(msg) => assert!(msg.contains("File not found")),
|
|
_ => panic!("Expected IoError"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_serde_json_error() {
|
|
let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
|
|
let common_err: CommonError = json_err.into();
|
|
match common_err {
|
|
CommonError::SerializationError(_) => {},
|
|
_ => panic!("Expected SerializationError"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_response() {
|
|
let err = CommonError::NotFound("Resource not found".to_string());
|
|
let response: ErrorResponse = (&err).into();
|
|
assert_eq!(response.error, "NOT_FOUND");
|
|
assert_eq!(response.code, "NOT_FOUND");
|
|
assert_eq!(response.status, 404);
|
|
assert_eq!(response.message, "Resource not found");
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_serialize() {
|
|
let err = CommonError::ValidationError("Invalid input".to_string());
|
|
let json = serde_json::to_string(&err).unwrap();
|
|
assert!(json.contains("ValidationError"));
|
|
assert!(json.contains("Invalid input"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_deserialize() {
|
|
let json = r#"{"type":"ValidationError","message":"Invalid input"}"#;
|
|
let err: CommonError = serde_json::from_str(json).unwrap();
|
|
match err {
|
|
CommonError::ValidationError(msg) => assert_eq!(msg, "Invalid input"),
|
|
_ => panic!("Expected ValidationError"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_display() {
|
|
let err = CommonError::NotFound("User not found".to_string());
|
|
let display = format!("{}", err);
|
|
assert!(display.contains("Not found"));
|
|
assert!(display.contains("User not found"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_result_type() {
|
|
fn example_function() -> CommonResult<i32> {
|
|
Ok(42)
|
|
}
|
|
|
|
fn example_error() -> CommonResult<i32> {
|
|
Err(CommonError::NotFound("Not found".to_string()))
|
|
}
|
|
|
|
assert!(example_function().is_ok());
|
|
assert!(example_error().is_err());
|
|
}
|
|
}
|
|
|