1use chrono::Utc;
2use std::fmt;
3use std::str::FromStr;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub struct SessionId(pub(crate) String);
7
8impl SessionId {
9 pub fn new() -> Self {
10 let ts = Utc::now().format("%Y-%m-%dT%H-%M-%S-%3fZ").to_string();
11 let suffix: String = uuid::Uuid::new_v4()
12 .to_string()
13 .chars()
14 .filter(|c| c.is_ascii_alphanumeric())
15 .take(8)
16 .collect();
17 Self(format!("{ts}-{suffix}"))
18 }
19
20 pub fn as_str(&self) -> &str {
21 &self.0
22 }
23}
24
25impl Default for SessionId {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl fmt::Display for SessionId {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 f.write_str(&self.0)
34 }
35}
36
37#[derive(Debug, thiserror::Error)]
38#[error("invalid session id: {0}")]
39pub struct InvalidSessionId(pub String);
40
41impl FromStr for SessionId {
42 type Err = InvalidSessionId;
43 fn from_str(s: &str) -> Result<Self, Self::Err> {
44 if s.is_empty()
45 || s.len() > 64
46 || !s
47 .chars()
48 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == 'T' || c == 'Z')
49 {
50 return Err(InvalidSessionId(s.to_string()));
51 }
52 Ok(Self(s.to_string()))
53 }
54}