117 lines
3.8 KiB
Rust
117 lines
3.8 KiB
Rust
//! Domain error types for the CMS crate.
|
|||
|
|
//!
|
||
|
|
//! Typed error enums for repository and service operations.
|
||
|
|
//! `From` impls connect `std::io::Error` and `serde_json::Error` into
|
||
|
|
//! `RepositoryError`, and `RepositoryError` into `ServiceError`.
|
||
|
|
//! Anyhow's blanket `From<E: StdError + Send + Sync + 'static>`
|
||
|
|
//! covers conversion to `anyhow::Error` for downstream code.
|
||
|
|
|
||
|
|
use std::fmt;
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// RepositoryError
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
/// Errors from repository / persistence operations in the CMS domain.
|
||
|
|
#[derive(Debug)]
|
||
|
|
pub enum RepositoryError {
|
||
|
|
/// The requested entity does not exist.
|
||
|
|
NotFound(String),
|
||
|
|
/// A conflict occurred (e.g. duplicate entry).
|
||
|
|
Conflict(String),
|
||
|
|
/// An I/O error occurred during persistence.
|
||
|
|
Io(std::io::Error),
|
||
|
|
/// A serialisation / deserialisation error occurred.
|
||
|
|
Serialization(serde_json::Error),
|
||
|
|
/// The supplied identifier is invalid (e.g. path traversal attempt).
|
||
|
|
InvalidId(String),
|
||
|
|
/// An error that could not be downcast to a specific variant.
|
||
|
|
Other(String),
|
||
|
|
}
|
||
|
|
|
||
|
|
impl RepositoryError {
|
||
|
|
/// Convert an `anyhow::Error` to `RepositoryError` by attempting
|
||
|
|
/// downcast to known inner types.
|
||
|
|
pub fn from_anyhow(e: anyhow::Error) -> Self {
|
||
|
|
if let Some(ioe) = e.downcast_ref::<std::io::Error>() {
|
||
|
|
return RepositoryError::Io(std::io::Error::new(ioe.kind(), ioe.to_string()));
|
||
|
|
}
|
||
|
|
RepositoryError::Other(e.to_string())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl fmt::Display for RepositoryError {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
|
match self {
|
||
|
|
RepositoryError::NotFound(msg) => write!(f, "not found: {msg}"),
|
||
|
|
RepositoryError::Conflict(msg) => write!(f, "conflict: {msg}"),
|
||
|
|
RepositoryError::Io(e) => write!(f, "I/O error: {e}"),
|
||
|
|
RepositoryError::Serialization(e) => write!(f, "serialization error: {e}"),
|
||
|
|
RepositoryError::InvalidId(msg) => write!(f, "invalid id: {msg}"),
|
||
|
|
RepositoryError::Other(msg) => write!(f, "{msg}"),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::error::Error for RepositoryError {
|
||
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||
|
|
match self {
|
||
|
|
RepositoryError::Io(e) => Some(e),
|
||
|
|
RepositoryError::Serialization(e) => Some(e),
|
||
|
|
_ => None,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl From<std::io::Error> for RepositoryError {
|
||
|
|
fn from(e: std::io::Error) -> Self {
|
||
|
|
RepositoryError::Io(e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl From<serde_json::Error> for RepositoryError {
|
||
|
|
fn from(e: serde_json::Error) -> Self {
|
||
|
|
RepositoryError::Serialization(e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// ServiceError
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
/// Errors from service / use-case operations in the CMS domain.
|
||
|
|
#[derive(Debug)]
|
||
|
|
pub enum ServiceError {
|
||
|
|
/// A repository operation failed.
|
||
|
|
Repository(RepositoryError),
|
||
|
|
/// The provided input is invalid.
|
||
|
|
InvalidInput(String),
|
||
|
|
/// A generic error with a message.
|
||
|
|
Other(String),
|
||
|
|
}
|
||
|
|
|
||
|
|
impl fmt::Display for ServiceError {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
|
match self {
|
||
|
|
ServiceError::Repository(e) => write!(f, "repository error: {e}"),
|
||
|
|
ServiceError::InvalidInput(msg) => write!(f, "invalid input: {msg}"),
|
||
|
|
ServiceError::Other(msg) => write!(f, "{msg}"),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::error::Error for ServiceError {
|
||
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||
|
|
match self {
|
||
|
|
ServiceError::Repository(e) => Some(e),
|
||
|
|
_ => None,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl From<RepositoryError> for ServiceError {
|
||
|
|
fn from(e: RepositoryError) -> Self {
|
||
|
|
ServiceError::Repository(e)
|
||
|
|
}
|
||
|
|
}
|