use thiserror::Error;
#[derive(Debug, Error)]
pub enum RustQueueError {
#[error("Queue '{0}' not found")]
QueueNotFound(String),
#[error("Job '{0}' not found")]
JobNotFound(String),
#[error("Schedule not found: {0}")]
ScheduleNotFound(String),
#[error("Job is in invalid state '{current}' for operation (expected: {expected})")]
InvalidState {
current: String,
expected: String,
},
#[error("Duplicate unique key '{0}'")]
DuplicateKey(String),
#[error("Queue '{0}' is paused")]
QueuePaused(String),
#[error("Rate limit exceeded")]
RateLimited,
#[error("Unauthorized")]
Unauthorized,
#[error("Validation error: {0}")]
ValidationError(String),
#[error("Internal error: {0}")]
Internal(#[from] anyhow::Error),
}
impl RustQueueError {
pub fn error_code(&self) -> &'static str {
match self {
Self::QueueNotFound(_) => "QUEUE_NOT_FOUND",
Self::JobNotFound(_) => "JOB_NOT_FOUND",
Self::ScheduleNotFound(_) => "SCHEDULE_NOT_FOUND",
Self::InvalidState { .. } => "INVALID_STATE",
Self::DuplicateKey(_) => "DUPLICATE_KEY",
Self::QueuePaused(_) => "QUEUE_PAUSED",
Self::RateLimited => "RATE_LIMITED",
Self::Unauthorized => "UNAUTHORIZED",
Self::ValidationError(_) => "VALIDATION_ERROR",
Self::Internal(_) => "INTERNAL_ERROR",
}
}
pub fn http_status(&self) -> u16 {
match self {
Self::QueueNotFound(_) => 404,
Self::JobNotFound(_) => 404,
Self::ScheduleNotFound(_) => 404,
Self::InvalidState { .. } => 409,
Self::DuplicateKey(_) => 409,
Self::QueuePaused(_) => 503,
Self::RateLimited => 429,
Self::Unauthorized => 401,
Self::ValidationError(_) => 400,
Self::Internal(_) => 500,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_codes_serialize() {
let err = RustQueueError::QueueNotFound("emails".into());
assert_eq!(err.error_code(), "QUEUE_NOT_FOUND");
assert_eq!(err.http_status(), 404);
let err = RustQueueError::JobNotFound("abc-123".into());
assert_eq!(err.error_code(), "JOB_NOT_FOUND");
assert_eq!(err.http_status(), 404);
let err = RustQueueError::InvalidState {
current: "completed".into(),
expected: "active".into(),
};
assert_eq!(err.error_code(), "INVALID_STATE");
assert_eq!(err.http_status(), 409);
let err = RustQueueError::DuplicateKey("user-42-welcome".into());
assert_eq!(err.error_code(), "DUPLICATE_KEY");
assert_eq!(err.http_status(), 409);
let err = RustQueueError::QueuePaused("notifications".into());
assert_eq!(err.error_code(), "QUEUE_PAUSED");
assert_eq!(err.http_status(), 503);
let err = RustQueueError::RateLimited;
assert_eq!(err.error_code(), "RATE_LIMITED");
assert_eq!(err.http_status(), 429);
let err = RustQueueError::Unauthorized;
assert_eq!(err.error_code(), "UNAUTHORIZED");
assert_eq!(err.http_status(), 401);
let err = RustQueueError::ValidationError("payload too large".into());
assert_eq!(err.error_code(), "VALIDATION_ERROR");
assert_eq!(err.http_status(), 400);
let err = RustQueueError::Internal(anyhow::anyhow!("disk full"));
assert_eq!(err.error_code(), "INTERNAL_ERROR");
assert_eq!(err.http_status(), 500);
}
#[test]
fn test_error_display() {
assert_eq!(
RustQueueError::QueueNotFound("emails".into()).to_string(),
"Queue 'emails' not found"
);
assert_eq!(
RustQueueError::JobNotFound("abc-123".into()).to_string(),
"Job 'abc-123' not found"
);
assert_eq!(
RustQueueError::InvalidState {
current: "completed".into(),
expected: "active".into(),
}
.to_string(),
"Job is in invalid state 'completed' for operation (expected: active)"
);
assert_eq!(
RustQueueError::DuplicateKey("user-42-welcome".into()).to_string(),
"Duplicate unique key 'user-42-welcome'"
);
assert_eq!(
RustQueueError::QueuePaused("notifications".into()).to_string(),
"Queue 'notifications' is paused"
);
assert_eq!(
RustQueueError::RateLimited.to_string(),
"Rate limit exceeded"
);
assert_eq!(RustQueueError::Unauthorized.to_string(), "Unauthorized");
assert_eq!(
RustQueueError::ValidationError("payload too large".into()).to_string(),
"Validation error: payload too large"
);
assert_eq!(
RustQueueError::Internal(anyhow::anyhow!("disk full")).to_string(),
"Internal error: disk full"
);
}
}