chie_shared/types/
ids.rs

1//! Strongly-typed ID wrappers for better type safety
2//!
3//! This module provides newtype wrappers around string IDs to prevent mixing up
4//! different types of identifiers (e.g., passing a peer ID where a content ID is expected).
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Content identifier (CID) - IPFS content hash
10///
11/// A strongly-typed wrapper around content identifiers to prevent mixing with other ID types.
12///
13/// # Examples
14///
15/// ```
16/// use chie_shared::ContentId;
17///
18/// let cid = ContentId::new("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi");
19/// assert_eq!(cid.as_str(), "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi");
20/// ```
21#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
22#[serde(transparent)]
23pub struct ContentId(String);
24
25impl ContentId {
26    /// Create a new `ContentId`
27    #[inline]
28    #[must_use]
29    pub fn new(id: impl Into<String>) -> Self {
30        Self(id.into())
31    }
32
33    /// Get the inner string reference
34    #[inline]
35    #[must_use]
36    pub fn as_str(&self) -> &str {
37        &self.0
38    }
39
40    /// Consume and return the inner string
41    #[inline]
42    #[must_use]
43    pub fn into_inner(self) -> String {
44        self.0
45    }
46
47    /// Get a short display format (first 8 characters)
48    #[inline]
49    #[must_use]
50    pub fn short(&self) -> &str {
51        if self.0.len() > 8 {
52            &self.0[..8]
53        } else {
54            &self.0
55        }
56    }
57}
58
59impl fmt::Display for ContentId {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(f, "{}", self.0)
62    }
63}
64
65impl From<String> for ContentId {
66    fn from(s: String) -> Self {
67        Self(s)
68    }
69}
70
71impl From<&str> for ContentId {
72    fn from(s: &str) -> Self {
73        Self(s.to_string())
74    }
75}
76
77impl AsRef<str> for ContentId {
78    fn as_ref(&self) -> &str {
79        &self.0
80    }
81}
82
83/// Peer identifier - libp2p peer ID
84///
85/// A strongly-typed wrapper around peer identifiers to prevent mixing with other ID types.
86///
87/// # Examples
88///
89/// ```
90/// use chie_shared::PeerId;
91///
92/// let peer_id = PeerId::new("12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhfGFRHPZ");
93/// assert_eq!(peer_id.as_str(), "12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhfGFRHPZ");
94/// ```
95#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
96#[serde(transparent)]
97pub struct PeerId(String);
98
99impl PeerId {
100    /// Create a new `PeerId`
101    #[inline]
102    #[must_use]
103    pub fn new(id: impl Into<String>) -> Self {
104        Self(id.into())
105    }
106
107    /// Get the inner string reference
108    #[inline]
109    #[must_use]
110    pub fn as_str(&self) -> &str {
111        &self.0
112    }
113
114    /// Consume and return the inner string
115    #[inline]
116    #[must_use]
117    pub fn into_inner(self) -> String {
118        self.0
119    }
120
121    /// Get a short display format (first 8 characters after prefix)
122    #[inline]
123    #[must_use]
124    pub fn short(&self) -> &str {
125        // Handle common peer ID prefixes (Qm, 12D3, bafz)
126        let start_idx = if self.0.starts_with("12D3") {
127            4
128        } else if self.0.starts_with("Qm") || self.0.starts_with("bafz") {
129            2
130        } else {
131            0
132        };
133
134        let end_idx = (start_idx + 8).min(self.0.len());
135        if start_idx < self.0.len() {
136            &self.0[start_idx..end_idx]
137        } else {
138            &self.0
139        }
140    }
141}
142
143impl fmt::Display for PeerId {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        write!(f, "{}", self.0)
146    }
147}
148
149impl From<String> for PeerId {
150    fn from(s: String) -> Self {
151        Self(s)
152    }
153}
154
155impl From<&str> for PeerId {
156    fn from(s: &str) -> Self {
157        Self(s.to_string())
158    }
159}
160
161impl AsRef<str> for PeerId {
162    fn as_ref(&self) -> &str {
163        &self.0
164    }
165}
166
167/// Bandwidth proof identifier
168///
169/// A strongly-typed wrapper around proof identifiers to prevent mixing with other ID types.
170///
171/// # Examples
172///
173/// ```
174/// use chie_shared::ProofId;
175///
176/// let proof_id = ProofId::new("proof_abc123");
177/// assert_eq!(proof_id.as_str(), "proof_abc123");
178/// ```
179#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
180#[serde(transparent)]
181pub struct ProofId(String);
182
183impl ProofId {
184    /// Create a new `ProofId`
185    #[inline]
186    #[must_use]
187    pub fn new(id: impl Into<String>) -> Self {
188        Self(id.into())
189    }
190
191    /// Get the inner string reference
192    #[inline]
193    #[must_use]
194    pub fn as_str(&self) -> &str {
195        &self.0
196    }
197
198    /// Consume and return the inner string
199    #[inline]
200    #[must_use]
201    pub fn into_inner(self) -> String {
202        self.0
203    }
204}
205
206impl fmt::Display for ProofId {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        write!(f, "{}", self.0)
209    }
210}
211
212impl From<String> for ProofId {
213    fn from(s: String) -> Self {
214        Self(s)
215    }
216}
217
218impl From<&str> for ProofId {
219    fn from(s: &str) -> Self {
220        Self(s.to_string())
221    }
222}
223
224impl AsRef<str> for ProofId {
225    fn as_ref(&self) -> &str {
226        &self.0
227    }
228}
229
230/// User identifier
231///
232/// A strongly-typed wrapper around user identifiers to prevent mixing with other ID types.
233#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
234#[serde(transparent)]
235pub struct UserId(String);
236
237impl UserId {
238    /// Create a new `UserId`
239    #[inline]
240    #[must_use]
241    pub fn new(id: impl Into<String>) -> Self {
242        Self(id.into())
243    }
244
245    /// Get the inner string reference
246    #[inline]
247    #[must_use]
248    pub fn as_str(&self) -> &str {
249        &self.0
250    }
251
252    /// Consume and return the inner string
253    #[inline]
254    #[must_use]
255    pub fn into_inner(self) -> String {
256        self.0
257    }
258}
259
260impl fmt::Display for UserId {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        write!(f, "{}", self.0)
263    }
264}
265
266impl From<String> for UserId {
267    fn from(s: String) -> Self {
268        Self(s)
269    }
270}
271
272impl From<&str> for UserId {
273    fn from(s: &str) -> Self {
274        Self(s.to_string())
275    }
276}
277
278impl AsRef<str> for UserId {
279    fn as_ref(&self) -> &str {
280        &self.0
281    }
282}
283
284/// Transaction identifier for tracking payments and rewards
285#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
286#[serde(transparent)]
287pub struct TransactionId(String);
288
289impl TransactionId {
290    /// Create a new `TransactionId`
291    #[inline]
292    #[must_use]
293    pub fn new(id: impl Into<String>) -> Self {
294        Self(id.into())
295    }
296
297    /// Get the inner string reference
298    #[inline]
299    #[must_use]
300    pub fn as_str(&self) -> &str {
301        &self.0
302    }
303
304    /// Consume and return the inner string
305    #[inline]
306    #[must_use]
307    pub fn into_inner(self) -> String {
308        self.0
309    }
310}
311
312impl fmt::Display for TransactionId {
313    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314        write!(f, "{}", self.0)
315    }
316}
317
318impl From<String> for TransactionId {
319    fn from(s: String) -> Self {
320        Self(s)
321    }
322}
323
324impl From<&str> for TransactionId {
325    fn from(s: &str) -> Self {
326        Self(s.to_string())
327    }
328}
329
330impl AsRef<str> for TransactionId {
331    fn as_ref(&self) -> &str {
332        &self.0
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn test_content_id_creation() {
342        let cid = ContentId::new("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi");
343        assert_eq!(
344            cid.as_str(),
345            "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
346        );
347    }
348
349    #[test]
350    fn test_content_id_short() {
351        let cid = ContentId::new("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi");
352        assert_eq!(cid.short(), "bafybeig");
353    }
354
355    #[test]
356    fn test_content_id_display() {
357        let cid = ContentId::new("test_cid");
358        assert_eq!(cid.to_string(), "test_cid");
359    }
360
361    #[test]
362    fn test_content_id_from_string() {
363        let cid: ContentId = "test_cid".into();
364        assert_eq!(cid.as_str(), "test_cid");
365    }
366
367    #[test]
368    fn test_peer_id_creation() {
369        let peer = PeerId::new("12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhfGFRHPZ");
370        assert_eq!(
371            peer.as_str(),
372            "12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhfGFRHPZ"
373        );
374    }
375
376    #[test]
377    fn test_peer_id_short() {
378        let peer = PeerId::new("12D3KooWRBhwfeP2Y4TCx1SM6s9rUoHhR5STiGwxBhfGFRHPZ");
379        assert_eq!(peer.short(), "KooWRBhw");
380    }
381
382    #[test]
383    fn test_peer_id_short_qm_prefix() {
384        let peer = PeerId::new("QmTest123456789");
385        assert_eq!(peer.short(), "Test1234");
386    }
387
388    #[test]
389    fn test_proof_id_creation() {
390        let proof = ProofId::new("proof_abc123");
391        assert_eq!(proof.as_str(), "proof_abc123");
392    }
393
394    #[test]
395    fn test_user_id_creation() {
396        let user = UserId::new("user_123");
397        assert_eq!(user.as_str(), "user_123");
398    }
399
400    #[test]
401    fn test_transaction_id_creation() {
402        let tx = TransactionId::new("tx_abc123");
403        assert_eq!(tx.as_str(), "tx_abc123");
404    }
405
406    #[test]
407    fn test_content_id_serde() {
408        let cid = ContentId::new("test_cid");
409        let json = serde_json::to_string(&cid).unwrap();
410        assert_eq!(json, "\"test_cid\"");
411
412        let decoded: ContentId = serde_json::from_str(&json).unwrap();
413        assert_eq!(decoded, cid);
414    }
415
416    #[test]
417    fn test_peer_id_as_ref() {
418        let peer = PeerId::new("test_peer");
419        let s: &str = peer.as_ref();
420        assert_eq!(s, "test_peer");
421    }
422
423    #[test]
424    fn test_ids_equality() {
425        let cid1 = ContentId::new("same_id");
426        let cid2 = ContentId::new("same_id");
427        let cid3 = ContentId::new("different_id");
428
429        assert_eq!(cid1, cid2);
430        assert_ne!(cid1, cid3);
431    }
432
433    #[test]
434    fn test_ids_hash_map() {
435        use std::collections::HashMap;
436
437        let mut map = HashMap::new();
438        let cid = ContentId::new("test");
439        map.insert(cid.clone(), 42);
440
441        assert_eq!(map.get(&cid), Some(&42));
442    }
443}