u64_id/
lib.rs

1use core::fmt;
2
3/// An ID for simply applications, implemented as a wrapper around [`u64`]s.
4///
5/// ## Valid Range
6///
7/// The valid range of ids is given by [`U64Id::VALID_RANGE`]. Essentially,
8/// this range is `..(u64::MAX - 128)`.
9///
10/// ## Collisions
11///
12/// Ids are made entirely randomly, so a collision is possible, but unbelievably
13/// unlikely, since just a simply u64 has enough bitspace in it to prevent a collision.
14#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
15#[repr(transparent)]
16pub struct U64Id(pub u64);
17
18impl U64Id {
19    /// Returns the forbidden `null` id. Applications can use this as an Id
20    /// which will never be made.
21    pub const NULL: U64Id = U64Id(u64::MAX);
22
23    /// Returns the valid range for [`U64Id`]s. Nothing prevents an Id from
24    /// being generated outside of this range, so it should not be relied upon
25    /// for safety.
26    ///
27    /// The amount of space at the top was randomly determined to be useful.
28    pub const VALID_RANGE: core::ops::Range<u64> = 0..(u64::MAX - 128);
29
30    /// Creates a new, random AssetId, seeded cheaply from thread_rng.
31    ///
32    /// To avoid calling this internal function repeatedly, consider making
33    /// a `U64Id` directly.
34    #[cfg(feature = "rand")]
35    pub fn new() -> Self {
36        use rand::Rng;
37
38        Self(rand::rng().random_range(Self::VALID_RANGE))
39    }
40
41    /// Checks if the asset is the `null` ID.
42    pub const fn is_null(self) -> bool {
43        self.0 == u64::MAX
44    }
45}
46
47#[cfg(feature = "rand")]
48impl Default for U64Id {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl fmt::Display for U64Id {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        write!(f, "{:x}", self.0)
57    }
58}
59impl fmt::LowerHex for U64Id {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        let val = self.0;
62
63        fmt::LowerHex::fmt(&val, f)
64    }
65}
66impl fmt::UpperHex for U64Id {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        let val = self.0;
69
70        fmt::UpperHex::fmt(&val, f)
71    }
72}
73
74#[cfg(feature = "serde")]
75impl serde::Serialize for U64Id {
76    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
77    where
78        S: serde::Serializer,
79    {
80        // we serialize the number as a string with lowercase hex formatting by default
81        serializer.serialize_str(&format!("{:x}", self.0))
82    }
83}
84
85#[cfg(feature = "serde")]
86impl<'de> serde::Deserialize<'de> for U64Id {
87    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88    where
89        D: serde::Deserializer<'de>,
90    {
91        struct AssetIdVisitor;
92        impl<'de> serde::de::Visitor<'de> for AssetIdVisitor {
93            type Value = u64;
94
95            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96                formatter.write_str("a hex-encoded integer between 0 and 2^64 - 1")
97            }
98
99            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
100            where
101                E: serde::de::Error,
102            {
103                u64::from_str_radix(v, 16).map_err(|_| {
104                    serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self)
105                })
106            }
107
108            // we can also deserialize a u64! This can be nice. Yes. It is nice.
109            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
110            where
111                E: serde::de::Error,
112            {
113                u64::from_str_radix(&v.to_string(), 16).map_err(|_| {
114                    serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self)
115                })
116            }
117
118            // we can also deserialize an i64! This can be nice. Yes. It is nice.
119            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
120            where
121                E: serde::de::Error,
122            {
123                u64::from_str_radix(&v.to_string(), 16).map_err(|_| {
124                    serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self)
125                })
126            }
127        }
128
129        if deserializer.is_human_readable() {
130            deserializer.deserialize_any(AssetIdVisitor).map(U64Id)
131        } else {
132            deserializer.deserialize_str(AssetIdVisitor).map(U64Id)
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn null_tests() {
143        let asset = U64Id::NULL;
144        assert!(asset.is_null());
145    }
146
147    #[cfg(feature = "serde")]
148    #[test]
149    fn basic_serde() {
150        assert_eq!(
151            serde_json::from_str::<U64Id>("12345").unwrap(),
152            U64Id(74565)
153        );
154
155        assert_eq!(
156            serde_json::from_str::<U64Id>("\"a12b345\"").unwrap(),
157            U64Id(168997701)
158        );
159    }
160
161    #[cfg(feature = "serde")]
162    #[test]
163    fn serde_cycle() {
164        let input = "\"123454321\"";
165        let output = serde_json::from_str::<U64Id>(input).unwrap();
166        let input_again = serde_json::to_string(&output).unwrap();
167
168        assert_eq!(input, input_again);
169    }
170
171    #[cfg(feature = "serde")]
172    #[test]
173    fn serde_cycle_around() {
174        let input = U64Id(12345321);
175        let output = serde_json::to_string::<U64Id>(&input).unwrap();
176        let input_again = serde_json::from_str(&output).unwrap();
177
178        assert_eq!(input, input_again);
179    }
180}