uqa_core/notifications/
identity.rs1use std::{fmt, str::FromStr};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
13pub enum InvalidNotificationIdentity {
14 #[error("notification epoch must be a canonical UUID version 4")]
15 Epoch,
16 #[error("notification request identity must contain 1 to 128 ASCII letters, digits, - or _")]
17 RequestId,
18}
19
20#[derive(Clone, Copy, PartialEq, Eq, Hash)]
24pub struct NotificationEpoch([u8; 16]);
25
26impl NotificationEpoch {
27 pub const fn from_bytes(bytes: [u8; 16]) -> Result<Self, InvalidNotificationIdentity> {
28 if bytes[6] >> 4 != 4 || bytes[8] >> 6 != 2 {
29 return Err(InvalidNotificationIdentity::Epoch);
30 }
31 Ok(Self(bytes))
32 }
33
34 pub const fn as_bytes(&self) -> &[u8; 16] {
35 &self.0
36 }
37}
38
39impl FromStr for NotificationEpoch {
40 type Err = InvalidNotificationIdentity;
41
42 fn from_str(value: &str) -> Result<Self, Self::Err> {
43 let input = value.as_bytes();
44 if input.len() != 36 {
45 return Err(InvalidNotificationIdentity::Epoch);
46 }
47 let mut bytes = [0; 16];
48 let mut offset = 0;
49 for (index, byte) in bytes.iter_mut().enumerate() {
50 if matches!(index, 4 | 6 | 8 | 10) {
51 if input[offset] != b'-' {
52 return Err(InvalidNotificationIdentity::Epoch);
53 }
54 offset += 1;
55 }
56 let digit = |value| match value {
57 b'0'..=b'9' => Ok(value - b'0'),
58 b'a'..=b'f' => Ok(value - b'a' + 10),
59 _ => Err(InvalidNotificationIdentity::Epoch),
60 };
61 *byte = digit(input[offset])? << 4 | digit(input[offset + 1])?;
62 offset += 2;
63 }
64 Self::from_bytes(bytes)
65 }
66}
67
68impl fmt::Display for NotificationEpoch {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 for (index, byte) in self.0.iter().enumerate() {
71 if matches!(index, 4 | 6 | 8 | 10) {
72 f.write_str("-")?;
73 }
74 write!(f, "{byte:02x}")?;
75 }
76 Ok(())
77 }
78}
79
80impl fmt::Debug for NotificationEpoch {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 fmt::Display::fmt(self, f)
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Hash)]
88pub struct NotificationRequestId(Box<str>);
89
90impl NotificationRequestId {
91 pub fn new(value: &str) -> Result<Self, InvalidNotificationIdentity> {
92 if value.is_empty()
93 || value.len() > 128
94 || !value
95 .bytes()
96 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
97 {
98 return Err(InvalidNotificationIdentity::RequestId);
99 }
100 Ok(Self(value.into()))
101 }
102
103 pub fn as_str(&self) -> &str {
104 &self.0
105 }
106}
107
108impl fmt::Display for NotificationRequestId {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.write_str(self.as_str())
111 }
112}