use std::num::NonZeroU32;
use chrono::{DateTime, NaiveDate, Utc};
use uuid::Uuid;
use crate::{PushStoreError, PushStoreFuture, PushValidationError};
pub const MAX_DELIVERY_KIND_LENGTH: usize = 64;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DeliveryKind(String);
impl DeliveryKind {
pub fn new(kind: impl Into<String>) -> Result<Self, PushValidationError> {
let kind = kind.into();
let starts_well = kind
.bytes()
.next()
.is_some_and(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit());
let valid = starts_well
&& kind.len() <= MAX_DELIVERY_KIND_LENGTH
&& kind.bytes().all(|byte| {
byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"_.-".contains(&byte)
});
if valid {
Ok(Self(kind))
} else {
Err(PushValidationError::DeliveryKind)
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct DeliveryClaim {
pub owner_id: Uuid,
pub local_date: NaiveDate,
pub kind: DeliveryKind,
}
impl DeliveryClaim {
#[must_use]
pub const fn new(owner_id: Uuid, local_date: NaiveDate, kind: DeliveryKind) -> Self {
Self {
owner_id,
local_date,
kind,
}
}
}
pub trait DeliveryClaimStore: Send + Sync {
fn claim(
&self,
claim: DeliveryClaim,
claimed_at: DateTime<Utc>,
) -> PushStoreFuture<'_, Result<bool, PushStoreError>>;
fn release(&self, claim: DeliveryClaim) -> PushStoreFuture<'_, Result<bool, PushStoreError>>;
fn purge(
&self,
before: NaiveDate,
limit: NonZeroU32,
) -> PushStoreFuture<'_, Result<u64, PushStoreError>>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_kind_is_a_short_lowercase_identifier() {
for valid in ["training_reminder", "weekly-summary", "daily.v2", "7day"] {
assert!(DeliveryKind::new(valid).is_ok(), "{valid}");
}
assert!(DeliveryKind::new("a".repeat(MAX_DELIVERY_KIND_LENGTH)).is_ok());
for invalid in [
String::new(),
"_leading".to_owned(),
"Upper".to_owned(),
"with space".to_owned(),
"a".repeat(MAX_DELIVERY_KIND_LENGTH + 1),
] {
assert_eq!(
DeliveryKind::new(invalid),
Err(PushValidationError::DeliveryKind)
);
}
}
}