baukit_push/
delivery_claim.rs1use chrono::{DateTime, NaiveDate, Utc};
4use uuid::Uuid;
5
6use crate::{PushStoreError, PushStoreFuture, PushValidationError};
7
8pub const MAX_DELIVERY_KIND_LENGTH: usize = 64;
10
11#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
16pub struct DeliveryKind(String);
17
18impl DeliveryKind {
19 pub fn new(kind: impl Into<String>) -> Result<Self, PushValidationError> {
25 let kind = kind.into();
26 let starts_well = kind
27 .bytes()
28 .next()
29 .is_some_and(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit());
30 let valid = starts_well
31 && kind.len() <= MAX_DELIVERY_KIND_LENGTH
32 && kind.bytes().all(|byte| {
33 byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"_.-".contains(&byte)
34 });
35 if valid {
36 Ok(Self(kind))
37 } else {
38 Err(PushValidationError::DeliveryKind)
39 }
40 }
41
42 #[must_use]
44 pub fn as_str(&self) -> &str {
45 &self.0
46 }
47}
48
49#[derive(Clone, Debug, Eq, Hash, PartialEq)]
51pub struct DeliveryClaim {
52 pub owner_id: Uuid,
54 pub local_date: NaiveDate,
56 pub kind: DeliveryKind,
58}
59
60impl DeliveryClaim {
61 #[must_use]
63 pub const fn new(owner_id: Uuid, local_date: NaiveDate, kind: DeliveryKind) -> Self {
64 Self {
65 owner_id,
66 local_date,
67 kind,
68 }
69 }
70}
71
72pub trait DeliveryClaimStore: Send + Sync {
79 fn claim(
81 &self,
82 claim: DeliveryClaim,
83 claimed_at: DateTime<Utc>,
84 ) -> PushStoreFuture<'_, Result<bool, PushStoreError>>;
85
86 fn release(&self, claim: DeliveryClaim) -> PushStoreFuture<'_, Result<bool, PushStoreError>>;
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn a_kind_is_a_short_lowercase_identifier() {
96 for valid in ["training_reminder", "weekly-summary", "daily.v2", "7day"] {
97 assert!(DeliveryKind::new(valid).is_ok(), "{valid}");
98 }
99 assert!(DeliveryKind::new("a".repeat(MAX_DELIVERY_KIND_LENGTH)).is_ok());
100 for invalid in [
101 String::new(),
102 "_leading".to_owned(),
103 "Upper".to_owned(),
104 "with space".to_owned(),
105 "a".repeat(MAX_DELIVERY_KIND_LENGTH + 1),
106 ] {
107 assert_eq!(
108 DeliveryKind::new(invalid),
109 Err(PushValidationError::DeliveryKind)
110 );
111 }
112 }
113}