use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[non_exhaustive]
pub enum ErrorCategory {
BadRequest,
Unauthorized,
Forbidden,
NotFound,
UnprocessableEntity,
Conflict,
RateLimited,
Timeout,
Unavailable,
Internal,
}
impl ErrorCategory {
pub fn http_status(&self) -> u16 {
match self {
ErrorCategory::BadRequest => 400,
ErrorCategory::Unauthorized => 401,
ErrorCategory::Forbidden => 403,
ErrorCategory::NotFound => 404,
ErrorCategory::UnprocessableEntity => 422,
ErrorCategory::Conflict => 409,
ErrorCategory::RateLimited => 429,
ErrorCategory::Timeout => 504,
ErrorCategory::Unavailable => 503,
ErrorCategory::Internal => 500,
}
}
pub fn is_retryable(&self) -> bool {
matches!(
self,
ErrorCategory::RateLimited | ErrorCategory::Timeout | ErrorCategory::Unavailable
)
}
pub fn is_client_error(&self) -> bool {
let status = self.http_status();
(400..500).contains(&status)
}
pub fn as_str(&self) -> &'static str {
match self {
ErrorCategory::BadRequest => "bad_request",
ErrorCategory::Unauthorized => "unauthorized",
ErrorCategory::Forbidden => "forbidden",
ErrorCategory::NotFound => "not_found",
ErrorCategory::UnprocessableEntity => "unprocessable_entity",
ErrorCategory::Conflict => "conflict",
ErrorCategory::RateLimited => "rate_limited",
ErrorCategory::Timeout => "timeout",
ErrorCategory::Unavailable => "unavailable",
ErrorCategory::Internal => "internal",
}
}
}
impl fmt::Display for ErrorCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_and_retryability_align() {
assert_eq!(ErrorCategory::NotFound.http_status(), 404);
assert!(ErrorCategory::NotFound.is_client_error());
assert!(!ErrorCategory::NotFound.is_retryable());
assert_eq!(ErrorCategory::Unavailable.http_status(), 503);
assert!(!ErrorCategory::Unavailable.is_client_error());
assert!(ErrorCategory::Unavailable.is_retryable());
assert!(!ErrorCategory::Internal.is_retryable());
}
}