sqry_core/graph/unified/string/
id.rs1use std::fmt;
13use std::hash::Hash;
14
15use serde::{Deserialize, Serialize};
16
17#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
37pub struct StringId(u32);
38
39impl StringId {
40 pub const LOCAL_TAG_BIT: u32 = 1 << 31;
48
49 pub const INVALID: StringId = StringId(u32::MAX);
51
52 #[inline]
64 #[must_use]
65 pub const fn new(index: u32) -> Self {
66 Self(index)
67 }
68
69 #[inline]
74 #[must_use]
75 pub const fn new_local(local_index: u32) -> Self {
76 Self(local_index | Self::LOCAL_TAG_BIT)
77 }
78
79 #[inline]
81 #[must_use]
82 pub const fn is_local(self) -> bool {
83 !self.is_invalid() && (self.0 & Self::LOCAL_TAG_BIT) != 0
84 }
85
86 #[inline]
88 #[must_use]
89 pub const fn local_index(self) -> Option<u32> {
90 if self.is_local() {
91 Some(self.0 & !Self::LOCAL_TAG_BIT)
92 } else {
93 None
94 }
95 }
96
97 #[inline]
99 #[must_use]
100 pub const fn index(self) -> u32 {
101 self.0
102 }
103
104 #[inline]
106 #[must_use]
107 pub const fn as_usize(self) -> usize {
108 self.0 as usize
109 }
110
111 #[inline]
113 #[must_use]
114 pub const fn is_invalid(self) -> bool {
115 self.0 == u32::MAX
116 }
117
118 #[inline]
120 #[must_use]
121 pub const fn is_valid(self) -> bool {
122 self.0 != u32::MAX
123 }
124}
125
126impl fmt::Debug for StringId {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 if self.is_invalid() {
129 write!(f, "StringId(INVALID)")
130 } else if self.is_local() {
131 write!(f, "StringId(local:{})", self.local_index().unwrap_or(0))
132 } else {
133 write!(f, "StringId({})", self.0)
134 }
135 }
136}
137
138impl fmt::Display for StringId {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 if self.is_invalid() {
141 write!(f, "INVALID")
142 } else if self.is_local() {
143 write!(f, "local:{}", self.local_index().unwrap_or(0))
144 } else {
145 write!(f, "str:{}", self.0)
146 }
147 }
148}
149
150impl Default for StringId {
151 #[inline]
153 fn default() -> Self {
154 Self::INVALID
155 }
156}
157
158impl From<u32> for StringId {
159 #[inline]
160 fn from(index: u32) -> Self {
161 Self(index)
162 }
163}
164
165impl From<usize> for StringId {
166 #[inline]
167 fn from(index: usize) -> Self {
168 Self(u32::try_from(index).unwrap_or(u32::MAX))
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_string_id_creation() {
178 let id = StringId::new(42);
179 assert_eq!(id.index(), 42);
180 assert_eq!(id.as_usize(), 42);
181 assert!(!id.is_invalid());
182 assert!(id.is_valid());
183 }
184
185 #[test]
186 fn test_string_id_invalid_sentinel() {
187 assert!(StringId::INVALID.is_invalid());
188 assert!(!StringId::INVALID.is_valid());
189 assert_eq!(StringId::INVALID.index(), u32::MAX);
190 }
191
192 #[test]
193 fn test_string_id_default() {
194 let default_id: StringId = StringId::default();
195 assert_eq!(default_id, StringId::INVALID);
196 }
197
198 #[test]
199 fn test_string_id_equality() {
200 let id1 = StringId::new(5);
201 let id2 = StringId::new(5);
202 let id3 = StringId::new(6);
203
204 assert_eq!(id1, id2);
205 assert_ne!(id1, id3);
206 }
207
208 #[test]
209 fn test_string_id_hash() {
210 use std::collections::HashSet;
211
212 let mut set = HashSet::new();
213 set.insert(StringId::new(1));
214 set.insert(StringId::new(2));
215 set.insert(StringId::new(3));
216
217 assert!(set.contains(&StringId::new(1)));
218 assert!(!set.contains(&StringId::new(4)));
219 assert_eq!(set.len(), 3);
220 }
221
222 #[test]
223 fn test_string_id_from() {
224 let from_u32: StringId = 42u32.into();
225 assert_eq!(from_u32.index(), 42);
226
227 let from_usize: StringId = 42usize.into();
228 assert_eq!(from_usize.index(), 42);
229 }
230
231 #[test]
232 fn test_debug_display_format() {
233 let id = StringId::new(42);
234 assert_eq!(format!("{id:?}"), "StringId(42)");
235 assert_eq!(format!("{id}"), "str:42");
236
237 assert_eq!(format!("{:?}", StringId::INVALID), "StringId(INVALID)");
238 assert_eq!(format!("{}", StringId::INVALID), "INVALID");
239 }
240
241 #[test]
242 fn test_serde_roundtrip() {
243 let original = StringId::new(123);
244
245 let json = serde_json::to_string(&original).unwrap();
247 let deserialized: StringId = serde_json::from_str(&json).unwrap();
248 assert_eq!(original, deserialized);
249
250 let bytes = postcard::to_allocvec(&original).unwrap();
252 let deserialized: StringId = postcard::from_bytes(&bytes).unwrap();
253 assert_eq!(original, deserialized);
254 }
255
256 #[test]
257 fn test_size_of_string_id() {
258 assert_eq!(std::mem::size_of::<StringId>(), 4);
260 }
261
262 #[test]
263 #[allow(clippy::clone_on_copy)] fn test_copy_clone() {
265 let id = StringId::new(10);
266 let copied = id;
267 let cloned = id.clone();
268
269 assert_eq!(id, copied);
270 assert_eq!(id, cloned);
271 }
272}