sqry_core/graph/unified/node/
id.rs1use std::fmt;
25use std::hash::{Hash, Hasher};
26
27use serde::{Deserialize, Serialize};
28
29#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct GenerationOverflowError {
36 pub index: u32,
38 pub generation: u64,
40}
41
42impl fmt::Display for GenerationOverflowError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(
45 f,
46 "generation overflow at index {}: generation {} would exceed MAX_GENERATION",
47 self.index, self.generation
48 )
49 }
50}
51
52impl std::error::Error for GenerationOverflowError {}
53
54#[derive(Clone, Copy, Serialize, Deserialize, Ord, PartialOrd)]
83pub struct NodeId {
84 index: u32,
86 generation: u64,
88}
89
90impl NodeId {
91 pub const INVALID: NodeId = NodeId {
95 index: u32::MAX,
96 generation: 0,
97 };
98
99 pub const MAX_GENERATION: u64 = u64::MAX / 2;
104
105 #[inline]
120 #[must_use]
121 pub const fn new(index: u32, generation: u64) -> Self {
122 Self { index, generation }
123 }
124
125 #[inline]
127 #[must_use]
128 pub const fn index(self) -> u32 {
129 self.index
130 }
131
132 #[inline]
134 #[must_use]
135 pub const fn generation(self) -> u64 {
136 self.generation
137 }
138
139 #[inline]
148 #[must_use]
149 pub const fn is_invalid(self) -> bool {
150 self.index == u32::MAX
151 }
152
153 #[inline]
158 #[must_use]
159 pub const fn is_valid(self) -> bool {
160 self.index != u32::MAX
161 }
162
163 pub fn try_increment_generation(self) -> Result<u64, GenerationOverflowError> {
179 let next = self
180 .generation
181 .checked_add(1)
182 .ok_or(GenerationOverflowError {
183 index: self.index,
184 generation: self.generation,
185 })?;
186
187 if next > Self::MAX_GENERATION {
188 return Err(GenerationOverflowError {
189 index: self.index,
190 generation: self.generation,
191 });
192 }
193
194 Ok(next)
195 }
196
197 #[inline]
202 #[must_use]
203 pub const fn is_near_overflow(self) -> bool {
204 self.generation > Self::MAX_GENERATION.saturating_sub(1000)
205 }
206}
207
208impl PartialEq for NodeId {
209 #[inline]
210 fn eq(&self, other: &Self) -> bool {
211 self.index == other.index && self.generation == other.generation
212 }
213}
214
215impl Eq for NodeId {}
216
217impl Hash for NodeId {
218 #[inline]
219 fn hash<H: Hasher>(&self, state: &mut H) {
220 self.index.hash(state);
222 self.generation.hash(state);
223 }
224}
225
226impl fmt::Debug for NodeId {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 if self.is_invalid() {
229 write!(f, "NodeId(INVALID)")
230 } else {
231 write!(f, "NodeId({}:{})", self.index, self.generation)
232 }
233 }
234}
235
236impl fmt::Display for NodeId {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 if self.is_invalid() {
239 write!(f, "INVALID")
240 } else {
241 write!(f, "{}:{}", self.index, self.generation)
242 }
243 }
244}
245
246impl Default for NodeId {
247 #[inline]
249 fn default() -> Self {
250 Self::INVALID
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_node_id_creation() {
260 let id = NodeId::new(42, 1);
261 assert_eq!(id.index(), 42);
262 assert_eq!(id.generation(), 1);
263 assert!(!id.is_invalid());
264 assert!(id.is_valid());
265 }
266
267 #[test]
268 fn test_node_id_invalid_sentinel() {
269 assert!(NodeId::INVALID.is_invalid());
270 assert!(!NodeId::INVALID.is_valid());
271 assert_eq!(NodeId::INVALID.index(), u32::MAX);
272 assert_eq!(NodeId::INVALID.generation(), 0);
273 }
274
275 #[test]
276 fn test_node_id_default() {
277 let default_id: NodeId = NodeId::default();
278 assert_eq!(default_id, NodeId::INVALID);
279 }
280
281 #[test]
282 fn test_node_id_equality() {
283 let id1 = NodeId::new(5, 10);
284 let id2 = NodeId::new(5, 10);
285 let id3 = NodeId::new(5, 11);
286 let id4 = NodeId::new(6, 10);
287
288 assert_eq!(id1, id2);
289 assert_ne!(id1, id3); assert_ne!(id1, id4); }
292
293 #[test]
294 fn test_node_id_hash() {
295 use std::collections::HashSet;
296
297 let mut set = HashSet::new();
298 set.insert(NodeId::new(1, 1));
299 set.insert(NodeId::new(1, 2));
300 set.insert(NodeId::new(2, 1));
301
302 assert!(set.contains(&NodeId::new(1, 1)));
303 assert!(set.contains(&NodeId::new(1, 2)));
304 assert!(set.contains(&NodeId::new(2, 1)));
305 assert!(!set.contains(&NodeId::new(3, 1)));
306 assert_eq!(set.len(), 3);
307 }
308
309 #[test]
310 #[allow(clippy::clone_on_copy)] fn test_node_id_copy_clone() {
312 let id = NodeId::new(10, 20);
313 let copied = id;
314 let cloned = id.clone();
315
316 assert_eq!(id, copied);
317 assert_eq!(id, cloned);
318 }
319
320 #[test]
321 fn test_generation_increment_success() {
322 let id = NodeId::new(5, 1);
323 let next_gen = id.try_increment_generation().unwrap();
324 assert_eq!(next_gen, 2);
325 }
326
327 #[test]
328 fn test_generation_increment_at_max() {
329 let id = NodeId::new(5, NodeId::MAX_GENERATION);
330 let result = id.try_increment_generation();
331 assert!(result.is_err());
332
333 let err = result.unwrap_err();
334 assert_eq!(err.index, 5);
335 assert_eq!(err.generation, NodeId::MAX_GENERATION);
336 }
337
338 #[test]
339 fn test_generation_overflow_at_u64_max() {
340 let id = NodeId::new(5, u64::MAX);
341 let result = id.try_increment_generation();
342 assert!(result.is_err());
343 }
344
345 #[test]
346 fn test_is_near_overflow() {
347 let safe_id = NodeId::new(5, 1000);
348 assert!(!safe_id.is_near_overflow());
349
350 let near_limit = NodeId::new(5, NodeId::MAX_GENERATION - 500);
351 assert!(near_limit.is_near_overflow());
352
353 let at_limit = NodeId::new(5, NodeId::MAX_GENERATION);
354 assert!(at_limit.is_near_overflow());
355 }
356
357 #[test]
358 fn test_debug_display_format() {
359 let id = NodeId::new(42, 7);
360 assert_eq!(format!("{id:?}"), "NodeId(42:7)");
361 assert_eq!(format!("{id}"), "42:7");
362
363 assert_eq!(format!("{:?}", NodeId::INVALID), "NodeId(INVALID)");
364 assert_eq!(format!("{}", NodeId::INVALID), "INVALID");
365 }
366
367 #[test]
368 fn test_serde_roundtrip() {
369 let original = NodeId::new(123, 456);
370
371 let json = serde_json::to_string(&original).unwrap();
373 let deserialized: NodeId = serde_json::from_str(&json).unwrap();
374 assert_eq!(original, deserialized);
375
376 let bytes = postcard::to_allocvec(&original).unwrap();
378 let deserialized: NodeId = postcard::from_bytes(&bytes).unwrap();
379 assert_eq!(original, deserialized);
380 }
381
382 #[test]
383 fn test_size_of_node_id() {
384 assert!(std::mem::size_of::<NodeId>() <= 16);
387 assert!(std::mem::size_of::<NodeId>() >= 12);
388 }
389}