1use std::fmt;
6
7use serde::{Deserialize, Serialize};
8
9use super::error::{IdError, validate};
10
11#[derive(
14 Debug,
15 Clone,
16 PartialEq,
17 Eq,
18 Hash,
19 Serialize,
20 Deserialize,
21 rkyv::Archive,
22 rkyv::Serialize,
23 rkyv::Deserialize,
24 zerompk::ToMessagePack,
25 zerompk::FromMessagePack,
26)]
27pub struct NodeId(String);
28
29impl NodeId {
30 pub fn try_new(id: impl Into<String>) -> Result<Self, IdError> {
35 let s = id.into();
36 validate(&s)?;
37 Ok(Self(s))
38 }
39
40 pub fn from_validated(id: String) -> Self {
44 Self(id)
45 }
46
47 pub fn as_str(&self) -> &str {
48 &self.0
49 }
50}
51
52impl fmt::Display for NodeId {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.write_str(&self.0)
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::super::error::ID_MAX_LEN;
61 use super::*;
62
63 #[test]
64 fn try_new_accepts_valid() {
65 let n = NodeId::try_new("concept:rust").expect("valid");
66 assert_eq!(n.as_str(), "concept:rust");
67 }
68
69 #[test]
70 fn try_new_rejects_empty() {
71 assert_eq!(NodeId::try_new(""), Err(IdError::Empty));
72 }
73
74 #[test]
75 fn try_new_rejects_too_long() {
76 let long = "x".repeat(ID_MAX_LEN + 1);
77 assert!(matches!(
78 NodeId::try_new(long),
79 Err(IdError::TooLong { .. })
80 ));
81 }
82
83 #[test]
84 fn try_new_rejects_nul() {
85 assert_eq!(NodeId::try_new("ab\0cd"), Err(IdError::ContainsNul));
86 }
87
88 #[test]
89 fn try_new_accepts_max_length() {
90 let exact = "a".repeat(ID_MAX_LEN);
91 assert!(NodeId::try_new(exact).is_ok());
92 }
93
94 #[test]
95 fn try_new_accepts_unicode() {
96 assert!(NodeId::try_new("节点:rust").is_ok());
97 }
98
99 #[test]
100 fn from_validated_does_not_validate() {
101 let oversized = "z".repeat(ID_MAX_LEN * 2);
102 let n = NodeId::from_validated(oversized.clone());
103 assert_eq!(n.as_str(), oversized);
104 }
105}