1use std::borrow::Borrow;
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10use super::error::{IdError, validate};
11
12#[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)]
25pub struct ShapeId(String);
26
27impl ShapeId {
28 pub fn try_new(id: impl Into<String>) -> Result<Self, IdError> {
33 let s = id.into();
34 validate(&s)?;
35 Ok(Self(s))
36 }
37
38 pub fn from_validated(id: String) -> Self {
42 Self(id)
43 }
44
45 pub fn as_str(&self) -> &str {
46 &self.0
47 }
48}
49
50impl fmt::Display for ShapeId {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 f.write_str(&self.0)
53 }
54}
55
56impl Borrow<str> for ShapeId {
57 fn borrow(&self) -> &str {
58 &self.0
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::super::error::ID_MAX_LEN;
65 use super::*;
66
67 #[test]
68 fn try_new_accepts_valid() {
69 let s = ShapeId::try_new("shape-001").expect("valid");
70 assert_eq!(s.as_str(), "shape-001");
71 }
72
73 #[test]
74 fn try_new_rejects_empty() {
75 assert_eq!(ShapeId::try_new(""), Err(IdError::Empty));
76 }
77
78 #[test]
79 fn try_new_rejects_too_long() {
80 let long = "x".repeat(ID_MAX_LEN + 1);
81 assert!(matches!(
82 ShapeId::try_new(long),
83 Err(IdError::TooLong { .. })
84 ));
85 }
86
87 #[test]
88 fn try_new_rejects_nul() {
89 assert_eq!(ShapeId::try_new("ab\0cd"), Err(IdError::ContainsNul));
90 }
91
92 #[test]
93 fn try_new_accepts_max_length() {
94 let exact = "a".repeat(ID_MAX_LEN);
95 assert!(ShapeId::try_new(exact).is_ok());
96 }
97
98 #[test]
99 fn try_new_accepts_unicode() {
100 assert!(ShapeId::try_new("形状:001").is_ok());
101 }
102
103 #[test]
104 fn from_validated_does_not_validate() {
105 let oversized = "z".repeat(ID_MAX_LEN * 2);
106 let s = ShapeId::from_validated(oversized.clone());
107 assert_eq!(s.as_str(), oversized);
108 }
109}