1use std::error::Error;
4use std::fmt;
5use std::num::{NonZeroU8, NonZeroU64};
6
7use crate::{
8 DatabaseId, EventConsumerIdentityHash, QueryParameterHash, ReactiveModuleHash,
9 ReactiveOperationName, hash_event_consumer_identity,
10};
11
12pub const MAX_EVENT_CONSUMER_NAME_BYTES: usize = 64;
14pub const EVENT_LEASE_TOKEN_BYTES: usize = 32;
16pub const MAX_EVENT_DELIVERY_ATTEMPTS: u8 = 10;
18
19#[must_use]
21pub fn event_consumer_identity_hash(
22 database_id: DatabaseId,
23 module_hash: ReactiveModuleHash,
24 operation_name: &ReactiveOperationName,
25 parameter_hash: QueryParameterHash,
26 consumer_name: &EventConsumerName,
27) -> EventConsumerIdentityHash {
28 let mut bytes = Vec::with_capacity(
29 16 + 32 + 4 + operation_name.as_str().len() + 32 + 4 + consumer_name.as_bytes().len(),
30 );
31 bytes.extend_from_slice(database_id.as_bytes());
32 bytes.extend_from_slice(module_hash.as_bytes());
33 append_bytes(&mut bytes, operation_name.as_str().as_bytes());
34 bytes.extend_from_slice(parameter_hash.as_bytes());
35 append_bytes(&mut bytes, consumer_name.as_bytes());
36 hash_event_consumer_identity(&bytes)
37}
38
39fn append_bytes(output: &mut Vec<u8>, bytes: &[u8]) {
40 output.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
41 output.extend_from_slice(bytes);
42}
43
44#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
46pub struct EventConsumerName(String);
47
48impl EventConsumerName {
49 pub fn new(value: impl Into<String>) -> Result<Self, EventConsumerNameError> {
51 let value = value.into();
52 let bytes = value.as_bytes();
53 if bytes.is_empty()
54 || bytes.len() > MAX_EVENT_CONSUMER_NAME_BYTES
55 || !bytes[0].is_ascii_alphabetic()
56 || !bytes[1..]
57 .iter()
58 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
59 {
60 return Err(EventConsumerNameError);
61 }
62 Ok(Self(value))
63 }
64
65 #[must_use]
67 pub fn as_str(&self) -> &str {
68 &self.0
69 }
70
71 #[must_use]
73 pub fn as_bytes(&self) -> &[u8] {
74 self.0.as_bytes()
75 }
76}
77
78impl fmt::Debug for EventConsumerName {
79 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
80 formatter.write_str("EventConsumerName([REDACTED])")
81 }
82}
83
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub struct EventConsumerNameError;
87
88impl fmt::Display for EventConsumerNameError {
89 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90 formatter.write_str("invalid event consumer name")
91 }
92}
93
94impl Error for EventConsumerNameError {}
95
96#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
98pub struct EventConsumerRevision(NonZeroU64);
99
100impl EventConsumerRevision {
101 #[must_use]
103 pub const fn new(value: u64) -> Option<Self> {
104 match NonZeroU64::new(value) {
105 Some(value) => Some(Self(value)),
106 None => None,
107 }
108 }
109
110 #[must_use]
112 pub const fn first() -> Self {
113 Self(NonZeroU64::MIN)
114 }
115
116 #[must_use]
118 pub const fn get(self) -> u64 {
119 self.0.get()
120 }
121
122 #[must_use]
124 pub const fn checked_next(self) -> Option<Self> {
125 match self.0.get().checked_add(1) {
126 Some(value) => Self::new(value),
127 None => None,
128 }
129 }
130}
131
132#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
134pub struct EventDeliveryAttempt(NonZeroU8);
135
136impl EventDeliveryAttempt {
137 #[must_use]
139 pub const fn new(value: u8) -> Option<Self> {
140 match NonZeroU8::new(value) {
141 Some(value) if value.get() <= MAX_EVENT_DELIVERY_ATTEMPTS => Some(Self(value)),
142 _ => None,
143 }
144 }
145
146 #[must_use]
148 pub const fn first() -> Self {
149 Self(NonZeroU8::MIN)
150 }
151
152 #[must_use]
154 pub const fn get(self) -> u8 {
155 self.0.get()
156 }
157
158 #[must_use]
160 pub const fn checked_next(self) -> Option<Self> {
161 Self::new(self.0.get().saturating_add(1))
162 }
163}
164
165#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
167pub struct EventLeaseToken([u8; EVENT_LEASE_TOKEN_BYTES]);
168
169impl EventLeaseToken {
170 #[must_use]
172 pub const fn from_bytes(bytes: [u8; EVENT_LEASE_TOKEN_BYTES]) -> Self {
173 Self(bytes)
174 }
175
176 #[must_use]
178 pub const fn as_bytes(&self) -> &[u8; EVENT_LEASE_TOKEN_BYTES] {
179 &self.0
180 }
181}
182
183impl fmt::Debug for EventLeaseToken {
184 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
185 formatter.write_str("EventLeaseToken([REDACTED])")
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn consumer_name_grammar_is_exact() {
195 for accepted in ["a", "Worker_1", "worker-2", &"z".repeat(64)] {
196 assert!(EventConsumerName::new(accepted).is_ok(), "{accepted}");
197 }
198 for rejected in ["", "1worker", "has.dot", "has space", &"z".repeat(65)] {
199 assert!(EventConsumerName::new(rejected).is_err(), "{rejected}");
200 }
201 }
202
203 #[test]
204 fn revisions_and_attempts_never_wrap() {
205 assert_eq!(EventConsumerRevision::first().get(), 1);
206 assert!(
207 EventConsumerRevision::new(u64::MAX)
208 .expect("nonzero")
209 .checked_next()
210 .is_none()
211 );
212 assert_eq!(EventDeliveryAttempt::first().get(), 1);
213 assert_eq!(
214 EventDeliveryAttempt::new(10)
215 .expect("bounded")
216 .checked_next(),
217 None
218 );
219 assert!(EventDeliveryAttempt::new(11).is_none());
220 }
221}