Skip to main content

nodedb_types/
id.rs

1//! Core identifier types used across NodeDB Origin and Lite.
2//!
3//! Strong typing prevents mixing up raw integers/strings. All IDs are
4//! `serde` + `rkyv` serializable and safe for WASM targets.
5
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10/// Identifies a tenant. All data is tenant-scoped by construction.
11#[derive(
12    Debug,
13    Clone,
14    Copy,
15    PartialEq,
16    Eq,
17    Hash,
18    Serialize,
19    Deserialize,
20    zerompk::ToMessagePack,
21    zerompk::FromMessagePack,
22    rkyv::Archive,
23    rkyv::Serialize,
24    rkyv::Deserialize,
25)]
26pub struct TenantId(u32);
27
28impl TenantId {
29    pub const fn new(id: u32) -> Self {
30        Self(id)
31    }
32
33    pub const fn as_u32(self) -> u32 {
34        self.0
35    }
36}
37
38impl fmt::Display for TenantId {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "tenant:{}", self.0)
41    }
42}
43
44/// Identifies a collection (table/namespace).
45#[derive(
46    Debug,
47    Clone,
48    PartialEq,
49    Eq,
50    Hash,
51    Serialize,
52    Deserialize,
53    rkyv::Archive,
54    rkyv::Serialize,
55    rkyv::Deserialize,
56)]
57pub struct CollectionId(String);
58
59impl CollectionId {
60    pub fn new(id: impl Into<String>) -> Self {
61        Self(id.into())
62    }
63
64    pub fn as_str(&self) -> &str {
65        &self.0
66    }
67}
68
69impl fmt::Display for CollectionId {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        f.write_str(&self.0)
72    }
73}
74
75/// Identifies a document/row across all engines.
76#[derive(
77    Debug,
78    Clone,
79    PartialEq,
80    Eq,
81    Hash,
82    Serialize,
83    Deserialize,
84    rkyv::Archive,
85    rkyv::Serialize,
86    rkyv::Deserialize,
87)]
88pub struct DocumentId(String);
89
90impl DocumentId {
91    pub fn new(id: impl Into<String>) -> Self {
92        Self(id.into())
93    }
94
95    pub fn as_str(&self) -> &str {
96        &self.0
97    }
98}
99
100impl fmt::Display for DocumentId {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        f.write_str(&self.0)
103    }
104}
105
106/// Identifies a graph node. Separate from `DocumentId` because graph nodes
107/// can exist independently of documents (e.g., concept nodes in a knowledge graph).
108#[derive(
109    Debug,
110    Clone,
111    PartialEq,
112    Eq,
113    Hash,
114    Serialize,
115    Deserialize,
116    rkyv::Archive,
117    rkyv::Serialize,
118    rkyv::Deserialize,
119    zerompk::ToMessagePack,
120    zerompk::FromMessagePack,
121)]
122pub struct NodeId(String);
123
124impl NodeId {
125    pub fn new(id: impl Into<String>) -> Self {
126        Self(id.into())
127    }
128
129    pub fn as_str(&self) -> &str {
130        &self.0
131    }
132}
133
134impl fmt::Display for NodeId {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        f.write_str(&self.0)
137    }
138}
139
140/// Identifies a graph edge. Returned by `graph_insert_edge`.
141#[derive(
142    Debug,
143    Clone,
144    PartialEq,
145    Eq,
146    Hash,
147    Serialize,
148    Deserialize,
149    rkyv::Archive,
150    rkyv::Serialize,
151    rkyv::Deserialize,
152)]
153pub struct EdgeId(String);
154
155impl EdgeId {
156    pub fn new(id: impl Into<String>) -> Self {
157        Self(id.into())
158    }
159
160    pub fn as_str(&self) -> &str {
161        &self.0
162    }
163
164    /// Generate an edge ID from source, target, and label.
165    pub fn from_components(src: &str, dst: &str, label: &str) -> Self {
166        Self(format!("{src}--{label}-->{dst}"))
167    }
168}
169
170impl fmt::Display for EdgeId {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        f.write_str(&self.0)
173    }
174}
175
176/// Identifies a shape subscription (globally unique per Origin).
177#[derive(
178    Debug,
179    Clone,
180    PartialEq,
181    Eq,
182    Hash,
183    Serialize,
184    Deserialize,
185    rkyv::Archive,
186    rkyv::Serialize,
187    rkyv::Deserialize,
188)]
189pub struct ShapeId(String);
190
191impl ShapeId {
192    pub fn new(id: impl Into<String>) -> Self {
193        Self(id.into())
194    }
195
196    pub fn as_str(&self) -> &str {
197        &self.0
198    }
199}
200
201impl fmt::Display for ShapeId {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        f.write_str(&self.0)
204    }
205}
206
207impl From<String> for ShapeId {
208    fn from(s: String) -> Self {
209        Self(s)
210    }
211}
212
213impl From<&str> for ShapeId {
214    fn from(s: &str) -> Self {
215        Self(s.to_owned())
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn tenant_id_display() {
225        let t = TenantId::new(42);
226        assert_eq!(t.to_string(), "tenant:42");
227        assert_eq!(t.as_u32(), 42);
228    }
229
230    #[test]
231    fn collection_id() {
232        let c = CollectionId::new("embeddings");
233        assert_eq!(c.as_str(), "embeddings");
234        assert_eq!(c.to_string(), "embeddings");
235    }
236
237    #[test]
238    fn document_id_str() {
239        let d = DocumentId::new("doc-abc-123");
240        assert_eq!(d.as_str(), "doc-abc-123");
241    }
242
243    #[test]
244    fn node_id() {
245        let n = NodeId::new("concept:rust");
246        assert_eq!(n.as_str(), "concept:rust");
247    }
248
249    #[test]
250    fn edge_id_from_components() {
251        let e = EdgeId::from_components("alice", "bob", "KNOWS");
252        assert_eq!(e.as_str(), "alice--KNOWS-->bob");
253    }
254
255    #[test]
256    fn shape_id_from_str() {
257        let s = ShapeId::from("shape-001");
258        assert_eq!(s.as_str(), "shape-001");
259    }
260
261    #[test]
262    fn serde_roundtrip() {
263        let tid = TenantId::new(7);
264        let json = sonic_rs::to_string(&tid).unwrap();
265        let decoded: TenantId = sonic_rs::from_str(&json).unwrap();
266        assert_eq!(tid, decoded);
267    }
268}