use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
#[derive(Debug, Clone)]
pub struct RateLimitResult {
pub allowed: bool,
pub current_count: f64,
pub limit: i32,
pub remaining: i32,
pub reset_at: OffsetDateTime,
}
impl RateLimitResult {
pub fn into_result(self) -> crate::Result<Self> {
if self.allowed {
Ok(self)
} else {
Err(crate::Error::RateLimitExceeded {
current: self.current_count,
limit: self.limit,
reset_at: self.reset_at,
})
}
}
pub fn retry_after(&self) -> i64 {
let now = OffsetDateTime::now_utc();
(self.reset_at - now).whole_seconds().max(0)
}
pub fn allowed_fallback(max_requests: i32, window_seconds: i32) -> Self {
Self {
allowed: true,
current_count: 0.0,
limit: max_requests,
remaining: max_requests,
reset_at: OffsetDateTime::now_utc()
+ time::Duration::seconds(i64::from(window_seconds)),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Priority {
Low = -100,
#[default]
Normal = 0,
High = 100,
Critical = 500,
}
impl Priority {
pub fn as_i32(self) -> i32 {
self as i32
}
pub fn from_i32(value: i32) -> Self {
match value {
v if v <= -100 => Priority::Low,
v if v <= 0 => Priority::Normal,
v if v <= 100 => Priority::High,
_ => Priority::Critical,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum JobStatus {
Pending,
Processing,
Completed,
Failed,
}
impl JobStatus {
pub fn parse(s: &str) -> Self {
match s {
"pending" => Self::Pending,
"processing" => Self::Processing,
"completed" => Self::Completed,
"failed" => Self::Failed,
_ => Self::Failed,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Pending => "pending",
Self::Processing => "processing",
Self::Completed => "completed",
Self::Failed => "failed",
}
}
}
#[derive(Debug, Clone)]
pub struct Job {
pub id: String,
pub queue_name: String,
pub payload: serde_json::Value,
pub attempts: i32,
pub max_attempts: i32,
pub created_at: OffsetDateTime,
}
#[derive(Debug, Clone)]
pub struct QueueStats {
pub queue_name: String,
pub pending: i64,
pub processing: i64,
pub completed: i64,
pub failed: i64,
}
impl QueueStats {
pub fn total(&self) -> i64 {
self.pending + self.processing + self.completed + self.failed
}
pub fn active(&self) -> i64 {
self.pending + self.processing
}
}
#[derive(Debug, Clone)]
pub struct TypedJob<T> {
pub id: String,
pub queue_name: String,
pub payload: T,
pub attempts: i32,
pub max_attempts: i32,
pub created_at: OffsetDateTime,
}
#[derive(Debug, Clone)]
pub struct Notification {
pub channel: String,
pub payload: String,
}
impl Notification {
pub fn decode<T: serde::de::DeserializeOwned>(&self) -> crate::Result<T> {
let value = serde_json::from_str(&self.payload)?;
Ok(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_priority_conversion() {
assert_eq!(Priority::Low.as_i32(), -100);
assert_eq!(Priority::Normal.as_i32(), 0);
assert_eq!(Priority::High.as_i32(), 100);
assert_eq!(Priority::Critical.as_i32(), 500);
assert_eq!(Priority::from_i32(-150), Priority::Low);
assert_eq!(Priority::from_i32(-50), Priority::Normal);
assert_eq!(Priority::from_i32(50), Priority::High);
assert_eq!(Priority::from_i32(1000), Priority::Critical);
}
#[test]
fn test_queue_stats() {
let stats = QueueStats {
queue_name: "test".to_string(),
pending: 10,
processing: 5,
completed: 100,
failed: 3,
};
assert_eq!(stats.total(), 118);
assert_eq!(stats.active(), 15);
}
#[test]
fn test_job_status_parse() {
assert_eq!(JobStatus::parse("pending"), JobStatus::Pending);
assert_eq!(JobStatus::parse("processing"), JobStatus::Processing);
assert_eq!(JobStatus::parse("completed"), JobStatus::Completed);
assert_eq!(JobStatus::parse("failed"), JobStatus::Failed);
assert_eq!(JobStatus::parse("unknown"), JobStatus::Failed);
}
}