scim_server/resource/value_objects/
user_name.rs

1//! UserName value object for SCIM user identifiers.
2//!
3//! This module provides a type-safe wrapper around user names with built-in validation.
4//! User names are fundamental identifiers in SCIM that must be unique and follow specific rules.
5
6use crate::error::{ValidationError, ValidationResult};
7use crate::resource::value_objects::value_object_trait::{SchemaConstructible, ValueObject};
8use crate::schema::types::{AttributeDefinition, AttributeType};
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use serde_json::Value;
11use std::any::Any;
12use std::fmt;
13
14/// A validated SCIM user name.
15///
16/// UserName represents a unique identifier for a SCIM user. It enforces
17/// validation rules at construction time, ensuring that only valid user names
18/// can exist in the system.
19///
20/// ## Validation Rules
21///
22/// - Must not be empty
23/// - Must be a valid string
24/// - Future: May include character set restrictions, length limits, etc.
25///
26/// ## Examples
27///
28/// ```rust
29/// use scim_server::resource::value_objects::UserName;
30///
31/// fn main() -> Result<(), Box<dyn std::error::Error>> {
32///     // Valid user name
33///     let username = UserName::new("bjensen@example.com".to_string())?;
34///     println!("User name: {}", username.as_str());
35///
36///     // Invalid user name - returns ValidationError
37///     let invalid = UserName::new("".to_string()); // Error
38///     assert!(invalid.is_err());
39///
40///     Ok(())
41/// }
42/// ```
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub struct UserName(String);
45
46impl UserName {
47    /// Create a new UserName with validation.
48    ///
49    /// This is the primary constructor that enforces all validation rules.
50    /// Use this method when creating UserName instances from untrusted input.
51    ///
52    /// # Arguments
53    ///
54    /// * `value` - The string value to validate and wrap
55    ///
56    /// # Returns
57    ///
58    /// * `Ok(UserName)` - If the value is valid
59    /// * `Err(ValidationError)` - If the value violates validation rules
60    pub fn new(value: String) -> ValidationResult<Self> {
61        Self::validate_format(&value)?;
62        Ok(Self(value))
63    }
64
65    /// Get the string representation of the UserName.
66    pub fn as_str(&self) -> &str {
67        &self.0
68    }
69
70    /// Get the owned string value of the UserName.
71    pub fn into_string(self) -> String {
72        self.0
73    }
74
75    /// Validate the format of a user name string.
76    ///
77    /// This function contains validation logic for user names.
78    /// Currently implements basic validation that can be enhanced in the future.
79    fn validate_format(value: &str) -> ValidationResult<()> {
80        // User name should not be empty
81        if value.is_empty() {
82            return Err(ValidationError::MissingRequiredAttribute {
83                attribute: "userName".to_string(),
84            });
85        }
86
87        // TODO: Add more sophisticated user name validation if needed
88        // For now, we accept any non-empty string as a valid user name
89        // Future enhancements might include:
90        // - Email format validation if email-based usernames are required
91        // - Character set restrictions (alphanumeric + specific symbols)
92        // - Length limits (min/max)
93        // - Reserved name checking
94        // - Case sensitivity rules
95
96        Ok(())
97    }
98}
99
100impl fmt::Display for UserName {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        write!(f, "{}", self.0)
103    }
104}
105
106impl Serialize for UserName {
107    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108    where
109        S: Serializer,
110    {
111        self.0.serialize(serializer)
112    }
113}
114
115impl<'de> Deserialize<'de> for UserName {
116    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
117    where
118        D: Deserializer<'de>,
119    {
120        let value = String::deserialize(deserializer)?;
121        Self::new(value).map_err(serde::de::Error::custom)
122    }
123}
124
125impl TryFrom<String> for UserName {
126    type Error = ValidationError;
127
128    fn try_from(value: String) -> ValidationResult<Self> {
129        Self::new(value)
130    }
131}
132
133impl TryFrom<&str> for UserName {
134    type Error = ValidationError;
135
136    fn try_from(value: &str) -> ValidationResult<Self> {
137        Self::new(value.to_string())
138    }
139}
140
141impl ValueObject for UserName {
142    fn attribute_type(&self) -> AttributeType {
143        AttributeType::String
144    }
145
146    fn attribute_name(&self) -> &str {
147        "userName"
148    }
149
150    fn to_json(&self) -> ValidationResult<Value> {
151        Ok(Value::String(self.0.clone()))
152    }
153
154    fn validate_against_schema(&self, definition: &AttributeDefinition) -> ValidationResult<()> {
155        if definition.data_type != AttributeType::String {
156            return Err(ValidationError::InvalidAttributeType {
157                attribute: definition.name.clone(),
158                expected: "string".to_string(),
159                actual: format!("{:?}", definition.data_type),
160            });
161        }
162
163        if definition.name != "userName" {
164            return Err(ValidationError::InvalidAttributeName {
165                actual: definition.name.clone(),
166                expected: "userName".to_string(),
167            });
168        }
169
170        Ok(())
171    }
172
173    fn as_json_value(&self) -> Value {
174        Value::String(self.0.clone())
175    }
176
177    fn supports_definition(&self, definition: &AttributeDefinition) -> bool {
178        definition.data_type == AttributeType::String && definition.name == "userName"
179    }
180
181    fn clone_boxed(&self) -> Box<dyn ValueObject> {
182        Box::new(self.clone())
183    }
184
185    fn as_any(&self) -> &dyn Any {
186        self
187    }
188}
189
190impl SchemaConstructible for UserName {
191    fn from_schema_and_value(
192        definition: &AttributeDefinition,
193        value: &Value,
194    ) -> ValidationResult<Self> {
195        if definition.name != "userName" || definition.data_type != AttributeType::String {
196            return Err(ValidationError::UnsupportedAttributeType {
197                attribute: definition.name.clone(),
198                type_name: format!("{:?}", definition.data_type),
199            });
200        }
201
202        if let Some(username_str) = value.as_str() {
203            Self::new(username_str.to_string())
204        } else {
205            Err(ValidationError::InvalidAttributeType {
206                attribute: definition.name.clone(),
207                expected: "string".to_string(),
208                actual: "non-string".to_string(),
209            })
210        }
211    }
212
213    fn can_construct_from(definition: &AttributeDefinition) -> bool {
214        definition.name == "userName" && definition.data_type == AttributeType::String
215    }
216
217    fn constructor_priority() -> u8 {
218        100 // High priority for exact name match
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use serde_json;
226
227    #[test]
228    fn test_valid_user_name_email() {
229        let username = UserName::new("bjensen@example.com".to_string());
230        assert!(username.is_ok());
231        assert_eq!(username.unwrap().as_str(), "bjensen@example.com");
232    }
233
234    #[test]
235    fn test_valid_user_name_simple() {
236        let username = UserName::new("bjensen".to_string());
237        assert!(username.is_ok());
238        assert_eq!(username.unwrap().as_str(), "bjensen");
239    }
240
241    #[test]
242    fn test_valid_user_name_with_numbers() {
243        let username = UserName::new("user123".to_string());
244        assert!(username.is_ok());
245        assert_eq!(username.unwrap().as_str(), "user123");
246    }
247
248    #[test]
249    fn test_empty_user_name() {
250        let result = UserName::new("".to_string());
251        assert!(result.is_err());
252
253        match result.unwrap_err() {
254            ValidationError::MissingRequiredAttribute { attribute } => {
255                assert_eq!(attribute, "userName");
256            }
257            other => panic!("Expected MissingRequiredAttribute error, got: {:?}", other),
258        }
259    }
260
261    #[test]
262    fn test_into_string() {
263        let username = UserName::new("test-username".to_string()).unwrap();
264        let string_value = username.into_string();
265        assert_eq!(string_value, "test-username");
266    }
267
268    #[test]
269    fn test_display() {
270        let username = UserName::new("display-test".to_string()).unwrap();
271        assert_eq!(format!("{}", username), "display-test");
272    }
273
274    #[test]
275    fn test_serialization() {
276        let username = UserName::new("serialize-test@example.com".to_string()).unwrap();
277        let json = serde_json::to_string(&username).unwrap();
278        assert_eq!(json, "\"serialize-test@example.com\"");
279    }
280
281    #[test]
282    fn test_deserialization_valid() {
283        let json = "\"deserialize-test@example.com\"";
284        let username: UserName = serde_json::from_str(json).unwrap();
285        assert_eq!(username.as_str(), "deserialize-test@example.com");
286    }
287
288    #[test]
289    fn test_deserialization_invalid() {
290        let json = "\"\""; // Empty string
291        let result: Result<UserName, _> = serde_json::from_str(json);
292        assert!(result.is_err());
293    }
294
295    #[test]
296    fn test_try_from_string() {
297        let result = UserName::try_from("try-from-test".to_string());
298        assert!(result.is_ok());
299        assert_eq!(result.unwrap().as_str(), "try-from-test");
300
301        let empty_result = UserName::try_from("".to_string());
302        assert!(empty_result.is_err());
303    }
304
305    #[test]
306    fn test_try_from_str() {
307        let result = UserName::try_from("try-from-str-test");
308        assert!(result.is_ok());
309        assert_eq!(result.unwrap().as_str(), "try-from-str-test");
310
311        let empty_result = UserName::try_from("");
312        assert!(empty_result.is_err());
313    }
314
315    #[test]
316    fn test_equality() {
317        let username1 = UserName::new("same-username".to_string()).unwrap();
318        let username2 = UserName::new("same-username".to_string()).unwrap();
319        let username3 = UserName::new("different-username".to_string()).unwrap();
320
321        assert_eq!(username1, username2);
322        assert_ne!(username1, username3);
323    }
324
325    #[test]
326    fn test_hash() {
327        use std::collections::HashMap;
328
329        let username1 = UserName::new("hash-test-1".to_string()).unwrap();
330        let username2 = UserName::new("hash-test-2".to_string()).unwrap();
331
332        let mut map = HashMap::new();
333        map.insert(username1.clone(), "value1");
334        map.insert(username2.clone(), "value2");
335
336        assert_eq!(map.get(&username1), Some(&"value1"));
337        assert_eq!(map.get(&username2), Some(&"value2"));
338    }
339
340    #[test]
341    fn test_clone() {
342        let username = UserName::new("clone-test".to_string()).unwrap();
343        let cloned = username.clone();
344
345        assert_eq!(username, cloned);
346        assert_eq!(username.as_str(), cloned.as_str());
347    }
348}