1use std::{
2 collections::{HashMap, HashSet},
3 sync::{Mutex, OnceLock},
4};
5
6trait CheckUuid {
7 fn id(&self) -> &uuid::Uuid;
8 fn to_string(&self) -> String;
9}
10static KNOWN_UUIDS: OnceLock<Mutex<HashMap<uuid::Uuid, Vec<Box<dyn CheckUuid + Send + Sync>>>>> =
11 OnceLock::new();
12
13fn normalized_uuid_input(s: &str) -> &str {
14 s.trim_matches(|c: char| c.is_ascii_whitespace() || c == '\0')
15}
16
17fn check_uuid(id: impl CheckUuid + Send + Sync + 'static) {
18 let mut map = KNOWN_UUIDS
20 .get_or_init(|| Mutex::new(HashMap::new()))
21 .lock()
22 .unwrap();
23 if let Some(existing) = map.get(id.id()) {
25 let mut types = HashSet::new();
27 for existing_id in existing {
28 types.insert(existing_id.to_string());
29 }
30
31 if types.len() > 1 {
32 log::warn!(
33 "UUIDs with different types found: {}. Already registered as: {}",
34 id.to_string(),
35 types.into_iter().collect::<Vec<_>>().join(", ")
36 );
37 }
38 }
39
40 map.entry(id.id().clone()).or_default().push(Box::new(id));
42}
43
44macro_rules! implement_uuid {
45 ($name:ident) => {
46 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
47 pub struct $name(uuid::Uuid);
48
49 impl $name {
50 pub fn new(id: uuid::Uuid) -> Self {
51 let id = Self(id);
52 check_uuid(id.clone());
53 id
54 }
55
56 pub fn from_str(s: &str) -> crate::error::Result<Self> {
57 let normalized = normalized_uuid_input(s);
58 let parsed = uuid::Uuid::parse_str(normalized).map_err(|e| {
59 log::error!(
60 "Failed to parse UUID from string '{}'(normalized '{}'): {}",
61 s.escape_debug(),
62 normalized.escape_debug(),
63 e
64 );
65 e
66 })?;
67 let id = Self(parsed);
68 check_uuid(id.clone());
69 Ok(id)
70 }
71
72 pub fn from_byte_str(s: &[u8]) -> crate::error::Result<Self> {
73 let s =
74 std::str::from_utf8(s).map_err(|e| crate::error::Error::UuidInvalidUtf8(e))?;
75 Self::from_str(s)
76 }
77
78 pub fn to_simple_string(&self) -> String {
79 self.0.simple().to_string()
80 }
81
82 pub fn to_hyphenated_string(&self) -> String {
83 self.0.hyphenated().to_string()
84 }
85 }
86
87 impl std::fmt::Display for $name {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(f, "{}({})", stringify!($name), self.0.to_string())
90 }
91 }
92
93 impl<'de> serde::Deserialize<'de> for $name {
94 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95 where
96 D: serde::Deserializer<'de>,
97 {
98 let id: uuid::Uuid = serde::Deserialize::deserialize(deserializer)?;
99 Ok(Self(id))
100 }
101 }
102
103 impl CheckUuid for $name {
104 fn id(&self) -> &uuid::Uuid {
105 &self.0
106 }
107
108 fn to_string(&self) -> String {
109 format!("{}({})", stringify!($name), self.0.to_string())
110 }
111 }
112 };
113}
114
115implement_uuid!(NoteUuid);
116implement_uuid!(VirtualPageUuid);
117implement_uuid!(VirtualDocUuid);
118implement_uuid!(StrokeUuid);
119implement_uuid!(PageUuid);
120implement_uuid!(PageModelUuid);
121implement_uuid!(PenUuid);
122implement_uuid!(ShapeGroupUuid);
123implement_uuid!(PointsUuid);
124implement_uuid!(ResourceUuid);
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub enum PenId {
128 Uuid(PenUuid),
129 Id(u32),
130}
131
132impl PenId {
133 pub fn from_uuid(uuid: PenUuid) -> Self {
134 Self::Uuid(uuid)
135 }
136
137 pub fn from_id(id: u32) -> Self {
138 Self::Id(id)
139 }
140}
141
142impl std::fmt::Display for PenId {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 match self {
145 Self::Uuid(uuid) => write!(f, "{}", uuid),
146 Self::Id(id) => write!(f, "PenId({})", id),
147 }
148 }
149}
150
151impl<'de> serde::Deserialize<'de> for PenId {
152 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153 where
154 D: serde::Deserializer<'de>,
155 {
156 let id: String = serde::Deserialize::deserialize(deserializer)?;
157 let trimmed = id.trim();
158
159 if let Ok(num) = trimmed.parse::<u32>() {
161 return Ok(Self::from_id(num));
162 }
163
164 let uuid = PenUuid::from_str(trimmed).map_err(serde::de::Error::custom)?;
165 Ok(Self::from_uuid(uuid))
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub struct LayerId(u32);
171
172impl LayerId {
173 pub fn new(id: u32) -> Self {
174 Self(id)
175 }
176
177 pub fn value(&self) -> u32 {
178 self.0
179 }
180}
181
182impl std::fmt::Display for LayerId {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 write!(f, "LayerId({})", self.0)
185 }
186}
187
188impl<'de> serde::Deserialize<'de> for LayerId {
189 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
190 where
191 D: serde::Deserializer<'de>,
192 {
193 let id: u32 = serde::Deserialize::deserialize(deserializer)?;
194 Ok(Self(id))
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::{NoteUuid, PenId, StrokeUuid};
201
202 #[test]
203 fn uuid_from_str_accepts_whitespace_padding() {
204 let id = NoteUuid::from_str(" 7a960ca753b0420ea2d5b88d57f7bf62 ").unwrap();
205 assert_eq!(id.to_simple_string(), "7a960ca753b0420ea2d5b88d57f7bf62");
206 }
207
208 #[test]
209 fn uuid_from_str_accepts_hyphenated_uuid() {
210 let id = NoteUuid::from_str("7a960ca7-53b0-420e-a2d5-b88d57f7bf62").unwrap();
211 assert_eq!(
212 id.to_hyphenated_string(),
213 "7a960ca7-53b0-420e-a2d5-b88d57f7bf62"
214 );
215 }
216
217 #[test]
218 fn uuid_from_byte_str_accepts_null_and_whitespace_padding() {
219 let raw = b"92c1ab73-4ec1-4f70-907a-dc11dcb0806d\0\0 ";
220 let id = StrokeUuid::from_byte_str(raw).unwrap();
221 assert_eq!(
222 id.to_hyphenated_string(),
223 "92c1ab73-4ec1-4f70-907a-dc11dcb0806d"
224 );
225 }
226
227 #[test]
228 fn pen_id_deserializes_uuid_forms_and_numeric_ids() {
229 let simple: PenId = serde_json::from_str("\"abb5f970105848349b5dfa80d7b0fa5b\"").unwrap();
230 assert!(matches!(simple, PenId::Uuid(_)));
231
232 let hyphenated: PenId =
233 serde_json::from_str("\" abb5f970-1058-4834-9b5d-fa80d7b0fa5b \"").unwrap();
234 assert!(matches!(hyphenated, PenId::Uuid(_)));
235
236 let numeric: PenId = serde_json::from_str("\" 1 \"").unwrap();
237 assert_eq!(numeric, PenId::Id(1));
238 }
239}