Refactor error handling in IAM and CMS crates

- Introduced `RepositoryError` and `ServiceError` enums in both IAM and CMS domains for better error management.
- Updated domain traits and services to return specific error types instead of `anyhow::Result`.
- Enhanced session and OAuth repository implementations to handle errors more explicitly.
- Refactored session service methods to return `Result<T, ServiceError>` for improved error handling.
- Updated HTTP handlers to utilize the new error types.
- Modified password hashing functions to run in a blocking context using `tokio::task::spawn_blocking`.
- Added tests for new error handling mechanisms and async password functions.
This commit is contained in:
asepharyana
2026-07-20 06:14:23 +07:00
parent 5aaedbf787
commit ab1a54b72e
47 changed files with 630 additions and 347 deletions
@@ -19,11 +19,13 @@
//!
//! - `FileSystemSessionLockRepository` — stateless singleton implementing
//! `SessionLockRepository`
use std::convert::TryInto;
use std::fs;
use std::io::Write;
use std::path::Path;
use tracing;
use crate::domain::error::RepositoryError;
use crate::domain::repository::SessionLockRepository;
/// Concrete filesystem session-lock repository, using a PID file
@@ -39,7 +41,7 @@ impl FileSystemSessionLockRepository {
}
impl SessionLockRepository for FileSystemSessionLockRepository {
fn try_lock(&self, session_dir: &Path) -> anyhow::Result<bool> {
fn try_lock(&self, session_dir: &Path) -> Result<bool, RepositoryError> {
let path = session_dir.join(".lock");
let pid = std::process::id();
@@ -49,15 +51,15 @@ impl SessionLockRepository for FileSystemSessionLockRepository {
.open(&path)
{
Ok(mut file) => {
write!(file, "{pid}")?;
file.sync_all()?;
write!(file, "{pid}")?; // → RepositoryError via From<io::Error>
file.sync_all()?; // → RepositoryError via From<io::Error>
tracing::debug!(path = %path.display(), pid, "session lock acquired");
return Ok(true);
}
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
tracing::debug!(path = %path.display(), "lock file exists, checking staleness");
}
Err(e) => return Err(e.into()),
Err(e) => return Err(RepositoryError::Io(e)),
}
let content = fs::read_to_string(&path).unwrap_or_default();
@@ -75,18 +77,18 @@ impl SessionLockRepository for FileSystemSessionLockRepository {
.create(true)
.truncate(true)
.write(true)
.open(&tmp)?;
write!(tmp_file, "{pid}")?;
tmp_file.sync_all()?;
.open(&tmp)?; // → RepositoryError via From<io::Error>
write!(tmp_file, "{pid}")?; // → RepositoryError
tmp_file.sync_all()?; // → RepositoryError
}
fs::rename(&tmp, &path)?;
fs::rename(&tmp, &path)?; // → RepositoryError
if let Some(parent) = path.parent() {
let _ = fs::File::open(parent).and_then(|d| d.sync_all());
}
Ok(true)
}
fn unlock(&self, session_dir: &Path) -> anyhow::Result<()> {
fn unlock(&self, session_dir: &Path) -> Result<(), RepositoryError> {
let path = session_dir.join(".lock");
let _ = fs::remove_file(path);
Ok(())
@@ -95,7 +97,9 @@ impl SessionLockRepository for FileSystemSessionLockRepository {
fn is_alive(&self, pid: u32) -> bool {
// SAFETY: `libc::kill(pid, 0)` sends no signal; it only probes
// whether the process exists and is signalable by us.
if unsafe { libc::kill(pid as i32, 0) != 0 } {
// PIDs on Linux fit in i32 (default pid_max ≈ 4 million).
let pid_signed: i32 = pid.try_into().unwrap_or(0);
if unsafe { libc::kill(pid_signed, 0) != 0 } {
return false;
}
let proc_exe = std::path::PathBuf::from(format!("/proc/{pid}/exe"));