1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7macro_rules! identity_id {
8 ($Name:ident, $doc:literal) => {
9 #[doc = $doc]
10 #[derive(
15 Clone,
16 Copy,
17 Debug,
18 Deserialize,
19 Eq,
20 Hash,
21 Ord,
22 PartialEq,
23 PartialOrd,
24 rkyv::Archive,
25 rkyv::Deserialize,
26 rkyv::Serialize,
27 Serialize,
28 )]
29 #[repr(transparent)]
30 pub struct $Name(u64);
31
32 impl $Name {
33 #[doc = concat!("Construct a `", stringify!($Name), "` from a raw `u64`.")]
34 #[must_use]
35 pub const fn new(raw: u64) -> Self {
36 Self(raw)
37 }
38
39 #[doc = concat!("Return the raw `u64` value of this `", stringify!($Name), "`.")]
40 #[must_use]
41 pub const fn get(self) -> u64 {
42 self.0
43 }
44
45 pub const TOMBSTONE: Self = Self(0);
47 }
48
49 impl fmt::Display for $Name {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 write!(f, "{}({})", stringify!($Name), self.0)
52 }
53 }
54 };
55}
56
57identity_id!(NodeId, "Graph-scoped node identifier.");
58identity_id!(EdgeId, "Graph-scoped edge identifier.");
59identity_id!(GraphId, "Catalog-scoped graph identifier.");
60identity_id!(BindingTableId, "Request-scoped binding-table identifier.");
61identity_id!(RecordTypeId, "Graph-type-scoped record-type identifier.");
62
63#[cfg(test)]
64mod tests {
65 use proptest::prelude::*;
66 use rstest::rstest;
67
68 use super::*;
69
70 #[rstest]
71 #[case(NodeId::TOMBSTONE.get())]
72 #[case(EdgeId::TOMBSTONE.get())]
73 #[case(GraphId::TOMBSTONE.get())]
74 #[case(BindingTableId::TOMBSTONE.get())]
75 #[case(RecordTypeId::TOMBSTONE.get())]
76 fn tombstone_is_zero(#[case] raw: u64) {
77 assert_eq!(raw, 0);
78 }
79
80 #[test]
81 fn identity_types_are_eight_bytes() {
82 assert_eq!(std::mem::size_of::<NodeId>(), 8);
83 assert_eq!(std::mem::size_of::<EdgeId>(), 8);
84 assert_eq!(std::mem::size_of::<GraphId>(), 8);
85 assert_eq!(std::mem::size_of::<BindingTableId>(), 8);
86 assert_eq!(std::mem::size_of::<RecordTypeId>(), 8);
87 }
88
89 #[test]
90 fn display_includes_type_name_and_value() {
91 assert_eq!(NodeId::new(42).to_string(), "NodeId(42)");
92 }
93
94 #[test]
95 fn identity_types_rkyv_round_trip() {
96 macro_rules! assert_round_trip {
97 ($ty:ident, $raw:expr) => {{
98 let value = $ty::new($raw);
99 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&value).unwrap();
100 let round: $ty = rkyv::from_bytes::<$ty, rkyv::rancor::Error>(&bytes).unwrap();
101 assert_eq!(round, value);
102 }};
103 }
104
105 assert_round_trip!(NodeId, 1);
106 assert_round_trip!(EdgeId, 2);
107 assert_round_trip!(GraphId, 3);
108 assert_round_trip!(BindingTableId, 4);
109 assert_round_trip!(RecordTypeId, 5);
110 }
111
112 proptest! {
113 #[test]
114 fn node_id_round_trips(raw in any::<u64>()) {
115 prop_assert_eq!(NodeId::new(raw).get(), raw);
116 }
117
118 #[test]
119 fn edge_id_order_matches_raw_values(a in any::<u64>(), b in any::<u64>()) {
120 prop_assert_eq!(EdgeId::new(a).cmp(&EdgeId::new(b)), a.cmp(&b));
121 }
122 }
123}