1use serde::{Deserialize, Serialize};
16use std::fmt;
17use uuid::Uuid;
18
19macro_rules! string_id_newtype {
24 ($(#[$meta:meta])* $vis:vis struct $name:ident;) => {
25 $(#[$meta])*
26 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
27 #[serde(transparent)]
28 $vis struct $name(String);
29
30 impl $name {
31 pub fn new(s: impl Into<String>) -> Self {
33 Self(s.into())
34 }
35
36 pub fn as_str(&self) -> &str {
38 &self.0
39 }
40 }
41
42 impl fmt::Display for $name {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.write_str(&self.0)
45 }
46 }
47
48 impl From<&str> for $name {
49 fn from(s: &str) -> Self {
50 Self(s.to_owned())
51 }
52 }
53
54 impl From<String> for $name {
55 fn from(s: String) -> Self {
56 Self(s)
57 }
58 }
59 };
60}
61
62string_id_newtype! {
67 pub struct ProfileId;
73}
74
75impl ProfileId {
76 pub fn new_v4() -> Self {
78 Self(Uuid::new_v4().to_string())
79 }
80}
81
82string_id_newtype! {
87 pub struct BucketId;
89}
90
91string_id_newtype! {
96 pub struct ObjectKey;
98}
99
100#[cfg(test)]
105mod tests {
106 use super::*;
107 use std::collections::HashSet;
108
109 #[test]
112 fn profile_id_new_v4_produces_valid_uuid() {
113 let id = ProfileId::new_v4();
114 let parsed =
115 Uuid::parse_str(id.as_str()).expect("ProfileId::new_v4 must yield a valid UUID");
116 assert_eq!(parsed.get_version_num(), 4, "must be a v4 UUID");
117 }
118
119 #[test]
120 fn profile_id_new_v4_produces_unique_values() {
121 let a = ProfileId::new_v4();
122 let b = ProfileId::new_v4();
123 assert_ne!(a, b, "two consecutive new_v4() calls must not collide");
124 }
125
126 #[test]
127 fn profile_id_from_str_round_trips() {
128 let raw = "my-profile";
129 let id = ProfileId::from(raw);
130 assert_eq!(id.as_str(), raw);
131 }
132
133 #[test]
134 fn profile_id_from_string_round_trips() {
135 let raw = String::from("my-profile");
136 let id = ProfileId::from(raw.clone());
137 assert_eq!(id.as_str(), &raw);
138 }
139
140 #[test]
141 fn profile_id_display_prints_inner() {
142 let id = ProfileId::new("display-test");
143 assert_eq!(id.to_string(), "display-test");
144 }
145
146 #[test]
147 fn profile_id_hash_works_in_hashset() {
148 let mut set: HashSet<ProfileId> = HashSet::new();
149 let id = ProfileId::new("abc");
150 set.insert(id.clone());
151 assert!(set.contains(&id));
152 assert_eq!(set.len(), 1);
153 set.insert(ProfileId::new("abc"));
155 assert_eq!(set.len(), 1);
156 }
157
158 #[test]
161 fn bucket_id_from_and_as_str() {
162 let id = BucketId::from("my-bucket");
163 assert_eq!(id.as_str(), "my-bucket");
164 }
165
166 #[test]
167 fn bucket_id_display() {
168 let id = BucketId::new("my-bucket");
169 assert_eq!(id.to_string(), "my-bucket");
170 }
171
172 #[test]
173 fn bucket_id_hash_works_in_hashset() {
174 let mut set: HashSet<BucketId> = HashSet::new();
175 set.insert(BucketId::new("bucket-a"));
176 set.insert(BucketId::new("bucket-b"));
177 assert_eq!(set.len(), 2);
178 set.insert(BucketId::new("bucket-a"));
179 assert_eq!(set.len(), 2);
180 }
181
182 #[test]
185 fn object_key_from_and_as_str() {
186 let key = ObjectKey::from("path/to/object.txt");
187 assert_eq!(key.as_str(), "path/to/object.txt");
188 }
189
190 #[test]
191 fn object_key_display() {
192 let key = ObjectKey::new("folder/file.bin");
193 assert_eq!(key.to_string(), "folder/file.bin");
194 }
195
196 #[test]
197 fn object_key_hash_works_in_hashset() {
198 let mut set: HashSet<ObjectKey> = HashSet::new();
199 set.insert(ObjectKey::new("key1"));
200 set.insert(ObjectKey::new("key2"));
201 set.insert(ObjectKey::new("key1")); assert_eq!(set.len(), 2);
203 }
204
205 #[test]
208 fn profile_id_serde_transparent() {
209 let id = ProfileId::new("serde-test");
210 let json = serde_json::to_string(&id).unwrap();
211 assert_eq!(json, r#""serde-test""#);
212 let back: ProfileId = serde_json::from_str(&json).unwrap();
213 assert_eq!(back, id);
214 }
215
216 #[test]
217 fn bucket_id_serde_transparent() {
218 let id = BucketId::new("my-bucket");
219 let json = serde_json::to_string(&id).unwrap();
220 assert_eq!(json, r#""my-bucket""#);
221 let back: BucketId = serde_json::from_str(&json).unwrap();
222 assert_eq!(back, id);
223 }
224
225 #[test]
226 fn object_key_serde_transparent() {
227 let key = ObjectKey::new("a/b/c.txt");
228 let json = serde_json::to_string(&key).unwrap();
229 assert_eq!(json, r#""a/b/c.txt""#);
230 let back: ObjectKey = serde_json::from_str(&json).unwrap();
231 assert_eq!(back, key);
232 }
233}