snops_common/state/
id.rs

1use std::str::FromStr;
2
3use rand::RngCore;
4use serde::de::Error;
5
6use super::INTERNED_ID_REGEX;
7use crate::{format::DataFormat, INTERN};
8
9#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
10pub struct InternedId(lasso::Spur);
11
12impl InternedId {
13    pub fn rand() -> Self {
14        let id = rand::thread_rng().next_u32();
15        Self(INTERN.get_or_intern(format!("unknown-{}", id)))
16    }
17
18    pub fn into_inner(self) -> u32 {
19        self.0.into_inner().get()
20    }
21
22    pub fn is_match(s: &str) -> bool {
23        INTERNED_ID_REGEX.is_match(s)
24    }
25
26    pub fn compute_id() -> Self {
27        Self(INTERN.get_or_intern("compute"))
28    }
29}
30
31impl Default for InternedId {
32    fn default() -> Self {
33        Self(INTERN.get_or_intern("default"))
34    }
35}
36
37impl std::cmp::PartialOrd for InternedId {
38    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
39        Some(std::convert::AsRef::<str>::as_ref(self).cmp(other.as_ref()))
40    }
41}
42
43impl Ord for InternedId {
44    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
45        std::convert::AsRef::<str>::as_ref(self).cmp(other.as_ref())
46    }
47}
48
49/// To prevent the risk of memory leaking agent/env ids that are not used, we
50/// check if the id is interned before from-stringing it
51pub fn id_or_none<T: FromStr>(s: &str) -> Option<T> {
52    if !INTERN.contains(s) {
53        return None;
54    }
55    T::from_str(s).ok()
56}
57
58impl FromStr for InternedId {
59    type Err = String;
60
61    fn from_str(s: &str) -> Result<Self, Self::Err> {
62        if !InternedId::is_match(s) {
63            return Err(format!(
64                "invalid {} expected pattern [A-Za-z0-9][A-Za-z0-9\\-_.]{{,63}}",
65                stringify!(InternedId)
66            ));
67        }
68
69        Ok(InternedId(INTERN.get_or_intern(s)))
70    }
71}
72
73impl<'de> serde::Deserialize<'de> for InternedId {
74    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75    where
76        D: serde::Deserializer<'de>,
77    {
78        let s = String::deserialize(deserializer)?;
79        Self::from_str(&s).map_err(D::Error::custom)
80    }
81}
82
83impl std::fmt::Display for InternedId {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        write!(f, "{}", INTERN.resolve(&self.0))
86    }
87}
88
89impl AsRef<str> for InternedId {
90    fn as_ref(&self) -> &str {
91        INTERN.resolve(&self.0)
92    }
93}
94
95impl AsRef<[u8]> for InternedId {
96    fn as_ref(&self) -> &[u8] {
97        INTERN.resolve(&self.0).as_bytes()
98    }
99}
100
101impl serde::Serialize for InternedId {
102    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103    where
104        S: serde::Serializer,
105    {
106        serializer.serialize_str(self.as_ref())
107    }
108}
109
110impl DataFormat for InternedId {
111    type Header = ();
112    const LATEST_HEADER: Self::Header = ();
113
114    fn write_data<W: std::io::prelude::Write>(
115        &self,
116        writer: &mut W,
117    ) -> Result<usize, crate::format::DataWriteError> {
118        self.0.write_data(writer)
119    }
120
121    fn read_data<R: std::io::prelude::Read>(
122        reader: &mut R,
123        _header: &Self::Header,
124    ) -> Result<Self, crate::format::DataReadError> {
125        Ok(InternedId(lasso::Spur::read_data(reader, &())?))
126    }
127}
128
129#[cfg(test)]
130mod test {
131    use super::*;
132
133    #[test]
134    fn test_interned_id() {
135        let id = InternedId::rand();
136        let s = id.to_string();
137        let id2 = InternedId::from_str(&s).unwrap();
138        assert_eq!(id, id2);
139    }
140
141    #[test]
142    fn test_interned_id_dataformat() {
143        let id = InternedId::rand();
144        let mut buf = Vec::new();
145        id.write_data(&mut buf).unwrap();
146        let id2 = InternedId::read_data(&mut buf.as_slice(), &()).unwrap();
147        assert_eq!(id, id2);
148    }
149}