1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[serde(transparent)]
35pub struct TenantId(String);
36
37impl TenantId {
38 pub fn new(id: impl Into<String>) -> Self {
52 Self(id.into())
53 }
54
55 pub fn as_str(&self) -> &str {
66 &self.0
67 }
68
69 pub fn into_inner(self) -> String {
81 self.0
82 }
83
84 pub fn is_valid(&self) -> bool {
102 !self.0.is_empty()
103 && self.0.len() <= 255
104 && self
105 .0
106 .chars()
107 .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
108 }
109}
110
111impl fmt::Display for TenantId {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "{}", self.0)
114 }
115}
116
117impl AsRef<str> for TenantId {
118 fn as_ref(&self) -> &str {
119 &self.0
120 }
121}
122
123impl From<String> for TenantId {
124 fn from(s: String) -> Self {
125 Self(s)
126 }
127}
128
129impl From<&str> for TenantId {
130 fn from(s: &str) -> Self {
131 Self(s.to_string())
132 }
133}
134
135impl From<TenantId> for String {
136 fn from(tenant_id: TenantId) -> Self {
137 tenant_id.0
138 }
139}
140
141impl PartialEq<str> for TenantId {
143 fn eq(&self, other: &str) -> bool {
144 self.0 == other
145 }
146}
147
148impl PartialEq<&str> for TenantId {
149 fn eq(&self, other: &&str) -> bool {
150 self.0 == *other
151 }
152}
153
154impl PartialEq<String> for TenantId {
155 fn eq(&self, other: &String) -> bool {
156 &self.0 == other
157 }
158}
159
160impl PartialEq<TenantId> for String {
161 fn eq(&self, other: &TenantId) -> bool {
162 self == &other.0
163 }
164}
165
166impl PartialEq<TenantId> for &str {
167 fn eq(&self, other: &TenantId) -> bool {
168 *self == other.0.as_str()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_tenant_id_creation() {
178 let tenant_id = TenantId::new("tenant_123");
179 assert_eq!(tenant_id.as_str(), "tenant_123");
180 assert_eq!(tenant_id.to_string(), "tenant_123");
181 }
182
183 #[test]
184 fn test_tenant_id_clone() {
185 let tenant_id = TenantId::new("tenant_123");
186 let cloned = tenant_id.clone();
187 assert_eq!(tenant_id, cloned);
188 }
189
190 #[test]
191 fn test_tenant_id_from_string() {
192 let s = String::from("tenant_123");
193 let tenant_id: TenantId = s.into();
194 assert_eq!(tenant_id.as_str(), "tenant_123");
195 }
196
197 #[test]
198 fn test_tenant_id_from_str() {
199 let tenant_id: TenantId = "tenant_123".into();
200 assert_eq!(tenant_id.as_str(), "tenant_123");
201 }
202
203 #[test]
204 fn test_tenant_id_into_string() {
205 let tenant_id = TenantId::new("tenant_123");
206 let s: String = tenant_id.into();
207 assert_eq!(s, "tenant_123");
208 }
209
210 #[test]
211 fn test_tenant_id_validation() {
212 assert!(TenantId::new("tenant_123").is_valid());
214 assert!(TenantId::new("tenant-abc-123").is_valid());
215 assert!(TenantId::new("a").is_valid());
216 assert!(TenantId::new("123").is_valid());
217 assert!(TenantId::new("tenant_with_underscores").is_valid());
218 assert!(TenantId::new("tenant-with-hyphens").is_valid());
219
220 assert!(!TenantId::new("").is_valid());
222 assert!(!TenantId::new("tenant with spaces").is_valid());
223 assert!(!TenantId::new("tenant@example.com").is_valid());
224 assert!(!TenantId::new("tenant/path").is_valid());
225 assert!(!TenantId::new("x".repeat(256)).is_valid());
226 }
227
228 #[test]
229 fn test_tenant_id_equality() {
230 let tenant_id1 = TenantId::new("tenant_123");
231 let tenant_id2 = TenantId::new("tenant_123");
232 let tenant_id3 = TenantId::new("tenant_456");
233
234 assert_eq!(tenant_id1, tenant_id2);
235 assert_ne!(tenant_id1, tenant_id3);
236 }
237
238 #[test]
239 fn test_tenant_id_serialization() {
240 let tenant_id = TenantId::new("tenant_123");
241 let json = serde_json::to_string(&tenant_id).unwrap();
242 assert_eq!(json, r#""tenant_123""#);
243
244 let deserialized: TenantId = serde_json::from_str(&json).unwrap();
245 assert_eq!(deserialized, tenant_id);
246 }
247
248 #[test]
249 fn test_tenant_id_hash() {
250 use std::collections::HashMap;
251
252 let mut map = HashMap::new();
253 let tenant_id = TenantId::new("tenant_123");
254 map.insert(tenant_id.clone(), "value");
255
256 assert_eq!(map.get(&tenant_id), Some(&"value"));
257 }
258
259 #[test]
260 fn test_tenant_id_string_comparison() {
261 let tenant_id = TenantId::new("tenant_123");
262 let string = String::from("tenant_123");
263 let str_ref = "tenant_123";
264
265 assert_eq!(tenant_id, string);
267 assert_eq!(string, tenant_id);
268
269 assert_eq!(tenant_id, str_ref);
271 assert_eq!(str_ref, tenant_id);
272
273 assert!(tenant_id == *str_ref);
275
276 assert_ne!(tenant_id, "different_tenant");
278 assert_ne!(String::from("different_tenant"), tenant_id);
279 }
280}