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