Refactor session ID handling and improve error management

- Introduced `SessionId` newtype for validated session identifiers, ensuring safety against path traversal attacks.
- Updated session repository methods to accept `SessionId` instead of raw strings, enhancing type safety.
- Removed redundant error handling in repository methods by leveraging the new `Error` type from `zesdex_utils`.
- Simplified atomic JSON write operations by eliminating unnecessary error conversions.
- Enhanced integer casting with a new `CastOr` trait for safer narrowing conversions.
- Removed deprecated error handling code and consolidated error types across the codebase.
- Updated HTTP handlers to utilize the new session ID validation, improving overall robustness.
This commit is contained in:
asepharyana
2026-07-20 06:39:30 +07:00
parent ab1a54b72e
commit e9a8e93c83
39 changed files with 413 additions and 366 deletions
+17 -92
View File
@@ -1,116 +1,41 @@
//! 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`.
//! The `RepositoryError` type is re-exported from `zesdex_utils::Error`,
//! which has all needed variants: `NotFound`, `Conflict`, `Io`, `Serde`,
//! `InvalidId`, `Other`, etc.
//!
//! `From` impls are generated by `thiserror::Error` derive macros.
//! Anyhow's blanket `From<E: StdError + Send + Sync + 'static>`
//! covers conversion to `anyhow::Error` for downstream code.
use std::fmt;
// ---------------------------------------------------------------------------
// RepositoryError
// RepositoryError (type alias)
// ---------------------------------------------------------------------------
/// 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),
}
/// Re-export shared repository error from `zesdex_utils`.
pub use zesdex_utils::Error as RepositoryError;
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)
}
}
// `From<RepositoryError> for anyhow::Error` is covered by anyhow's blanket
// `impl<E: StdError + Send + Sync + 'static> From<E> for Error` — no
// explicit impl needed.
// ---------------------------------------------------------------------------
// ServiceError
// ---------------------------------------------------------------------------
/// Errors from service / use-case operations in the CMS domain.
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum ServiceError {
/// A repository operation failed.
Repository(RepositoryError),
#[error("repository error: {0}")]
Repository(#[from] RepositoryError),
/// The provided input is invalid.
#[error("invalid input: {0}")]
InvalidInput(String),
/// A generic error with a message.
#[error("{0}")]
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)
}
}
// `From<ServiceError> for anyhow::Error` is covered by anyhow's blanket impl.