Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,14 @@ utoipa = "5"
<details>
<summary><b>Conversions</b></summary>

- `std::io::Error` → Internal
- `String` → BadRequest
- `sqlx::Error` → NotFound/Database
- `redis::RedisError` → Service
- `reqwest::Error` → Timeout/Network/ExternalApi
- `validator::ValidationErrors` → Validation
- `config::ConfigError` → Config
- `tokio::time::error::Elapsed` → Timeout
- `std::io::Error` → Internal
- `String` → BadRequest
- `sqlx::Error` → NotFound/Database
- `redis::RedisError` → Cache
- `reqwest::Error` → Timeout/Network/ExternalApi
- `validator::ValidationErrors` → Validation
- `config::ConfigError` → Config
- `tokio::time::error::Elapsed` → Timeout

</details>

Expand Down
34 changes: 25 additions & 9 deletions src/convert/redis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
//!
//! ## Mapping
//!
//! All Redis client errors are mapped to `AppErrorKind::Service`.
//! All Redis client errors are mapped to `AppErrorKind::Cache`.
//! The full error string from the driver is preserved in `message` for logs
//! and JSON payloads (if applicable).
//!
//! This categorization treats Redis as an infrastructure/service dependency.
//! If you need a stricter taxonomy (e.g. `Cache` vs `Queue`), introduce
//! dedicated `AppErrorKind` variants and adjust the mapping accordingly.
//! This categorization treats Redis as a cache infrastructure dependency.
//! If you need a different taxonomy (e.g. distinguishing caches from queues),
//! introduce dedicated `AppErrorKind` variants and adjust the mapping
//! accordingly.
//!
//! ## Example
//!
Expand All @@ -26,7 +27,7 @@
//! let dummy = RedisError::from((redis::ErrorKind::IoError, "connection lost"));
//! let app_err = handle_cache_error(dummy);
//!
//! assert!(matches!(app_err.kind, AppErrorKind::Service));
//! assert!(matches!(app_err.kind, AppErrorKind::Cache));
//! ```

#[cfg(feature = "redis")]
Expand All @@ -35,15 +36,30 @@ use redis::RedisError;
#[cfg(feature = "redis")]
use crate::AppError;

/// Map any [`redis::RedisError`] into an [`AppError`] with kind `Service`.
/// Map any [`redis::RedisError`] into an [`AppError`] with kind `Cache`.
///
/// Rationale: Redis is treated as a backend service/cache dependency.
/// Rationale: Redis is treated as a backend cache dependency.
/// Detailed driver errors are kept in the message for diagnostics.
#[cfg(feature = "redis")]
#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
impl From<RedisError> for AppError {
fn from(err: RedisError) -> Self {
// Infrastructure/cache issue -> service-level error for now
AppError::service(format!("Redis error: {err}"))
// Infrastructure cache issue -> cache-level error
AppError::cache(format!("Redis error: {err}"))
}
}

#[cfg(all(test, feature = "redis"))]
mod tests {
use redis::ErrorKind;

use super::*;
use crate::AppErrorKind;

#[test]
fn maps_to_cache_kind() {
let redis_err = RedisError::from((ErrorKind::IoError, "boom"));
let app_err: AppError = redis_err.into();
assert!(matches!(app_err.kind, AppErrorKind::Cache));
}
}