scim_server/resource/value_objects/
resource_id.rs1use 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub struct ResourceId(String);
45
46impl ResourceId {
47 pub fn new(value: String) -> ValidationResult<Self> {
74 Self::validate_format(&value)?;
75 Ok(Self(value))
76 }
77
78 pub fn as_str(&self) -> &str {
95 &self.0
96 }
97
98 pub fn into_string(self) -> String {
116 self.0
117 }
118
119 fn validate_format(value: &str) -> ValidationResult<()> {
133 if value.is_empty() {
135 return Err(ValidationError::EmptyId);
136 }
137
138 Ok(())
146 }
147}
148
149impl fmt::Display for ResourceId {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 write!(f, "{}", self.0)
152 }
153}
154
155impl Serialize for ResourceId {
156 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157 where
158 S: Serializer,
159 {
160 self.0.serialize(serializer)
161 }
162}
163
164impl<'de> Deserialize<'de> for ResourceId {
165 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
166 where
167 D: Deserializer<'de>,
168 {
169 let value = String::deserialize(deserializer)?;
170 Self::new(value).map_err(serde::de::Error::custom)
171 }
172}
173
174impl TryFrom<String> for ResourceId {
176 type Error = ValidationError;
177
178 fn try_from(value: String) -> ValidationResult<Self> {
179 Self::new(value)
180 }
181}
182
183impl TryFrom<&str> for ResourceId {
185 type Error = ValidationError;
186
187 fn try_from(value: &str) -> ValidationResult<Self> {
188 Self::new(value.to_string())
189 }
190}
191
192impl ValueObject for ResourceId {
193 fn attribute_type(&self) -> AttributeType {
194 AttributeType::String
195 }
196
197 fn attribute_name(&self) -> &str {
198 "id"
199 }
200
201 fn to_json(&self) -> ValidationResult<Value> {
202 Ok(Value::String(self.0.clone()))
203 }
204
205 fn validate_against_schema(&self, definition: &AttributeDefinition) -> ValidationResult<()> {
206 if definition.data_type != AttributeType::String {
207 return Err(ValidationError::InvalidAttributeType {
208 attribute: definition.name.clone(),
209 expected: "string".to_string(),
210 actual: format!("{:?}", definition.data_type),
211 });
212 }
213
214 if definition.name != "id" {
215 return Err(ValidationError::InvalidAttributeName {
216 actual: definition.name.clone(),
217 expected: "id".to_string(),
218 });
219 }
220
221 Ok(())
222 }
223
224 fn as_json_value(&self) -> Value {
225 Value::String(self.0.clone())
226 }
227
228 fn supports_definition(&self, definition: &AttributeDefinition) -> bool {
229 definition.data_type == AttributeType::String && definition.name == "id"
230 }
231
232 fn clone_boxed(&self) -> Box<dyn ValueObject> {
233 Box::new(self.clone())
234 }
235
236 fn as_any(&self) -> &dyn Any {
237 self
238 }
239}
240
241impl SchemaConstructible for ResourceId {
242 fn from_schema_and_value(
243 definition: &AttributeDefinition,
244 value: &Value,
245 ) -> ValidationResult<Self> {
246 if definition.name != "id" || definition.data_type != AttributeType::String {
247 return Err(ValidationError::UnsupportedAttributeType {
248 attribute: definition.name.clone(),
249 type_name: format!("{:?}", definition.data_type),
250 });
251 }
252
253 if let Some(id_str) = value.as_str() {
254 Self::new(id_str.to_string())
255 } else {
256 Err(ValidationError::InvalidAttributeType {
257 attribute: definition.name.clone(),
258 expected: "string".to_string(),
259 actual: "non-string".to_string(),
260 })
261 }
262 }
263
264 fn can_construct_from(definition: &AttributeDefinition) -> bool {
265 definition.name == "id" && definition.data_type == AttributeType::String
266 }
267
268 fn constructor_priority() -> u8 {
269 100 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use crate::schema::types::{Mutability, Uniqueness};
277 use serde_json;
278
279 #[test]
280 fn test_valid_resource_id() {
281 let id = ResourceId::new("2819c223-7f76-453a-919d-413861904646".to_string());
282 assert!(id.is_ok());
283
284 let id = id.unwrap();
285 assert_eq!(id.as_str(), "2819c223-7f76-453a-919d-413861904646");
286 }
287
288 #[test]
289 fn test_empty_resource_id() {
290 let result = ResourceId::new("".to_string());
291 assert!(result.is_err());
292
293 match result.unwrap_err() {
294 ValidationError::EmptyId => {} other => panic!("Expected EmptyId error, got: {:?}", other),
296 }
297 }
298
299 #[test]
300 fn test_simple_string_id() {
301 let id = ResourceId::new("user-123".to_string());
302 assert!(id.is_ok());
303
304 let id = id.unwrap();
305 assert_eq!(id.as_str(), "user-123");
306 }
307
308 #[test]
309 fn test_into_string() {
310 let id = ResourceId::new("test-id".to_string()).unwrap();
311 let string_value = id.into_string();
312 assert_eq!(string_value, "test-id");
313 }
314
315 #[test]
316 fn test_display() {
317 let id = ResourceId::new("display-test".to_string()).unwrap();
318 assert_eq!(format!("{}", id), "display-test");
319 }
320
321 #[test]
322 fn test_serialization() {
323 let id = ResourceId::new("serialize-test".to_string()).unwrap();
324 let json = serde_json::to_string(&id).unwrap();
325 assert_eq!(json, "\"serialize-test\"");
326 }
327
328 #[test]
329 fn test_deserialization_valid() {
330 let json = "\"deserialize-test\"";
331 let id: ResourceId = serde_json::from_str(json).unwrap();
332 assert_eq!(id.as_str(), "deserialize-test");
333 }
334
335 #[test]
336 fn test_deserialization_invalid() {
337 let json = "\"\""; let result: Result<ResourceId, _> = serde_json::from_str(json);
339 assert!(result.is_err());
340 }
341
342 #[test]
343 fn test_try_from_string() {
344 let result = ResourceId::try_from("try-from-test".to_string());
345 assert!(result.is_ok());
346 assert_eq!(result.unwrap().as_str(), "try-from-test");
347
348 let empty_result = ResourceId::try_from("".to_string());
349 assert!(empty_result.is_err());
350 }
351
352 #[test]
353 fn test_try_from_str() {
354 let result = ResourceId::try_from("try-from-str-test");
355 assert!(result.is_ok());
356 assert_eq!(result.unwrap().as_str(), "try-from-str-test");
357
358 let empty_result = ResourceId::try_from("");
359 assert!(empty_result.is_err());
360 }
361
362 #[test]
363 fn test_equality() {
364 let id1 = ResourceId::new("same-id".to_string()).unwrap();
365 let id2 = ResourceId::new("same-id".to_string()).unwrap();
366 let id3 = ResourceId::new("different-id".to_string()).unwrap();
367
368 assert_eq!(id1, id2);
369 assert_ne!(id1, id3);
370 }
371
372 #[test]
373 fn test_hash() {
374 use std::collections::HashMap;
375
376 let id1 = ResourceId::new("hash-test-1".to_string()).unwrap();
377 let id2 = ResourceId::new("hash-test-2".to_string()).unwrap();
378
379 let mut map = HashMap::new();
380 map.insert(id1.clone(), "value1");
381 map.insert(id2.clone(), "value2");
382
383 assert_eq!(map.get(&id1), Some(&"value1"));
384 assert_eq!(map.get(&id2), Some(&"value2"));
385 }
386
387 #[test]
388 fn test_clone() {
389 let id = ResourceId::new("clone-test".to_string()).unwrap();
390 let cloned = id.clone();
391
392 assert_eq!(id, cloned);
393 assert_eq!(id.as_str(), cloned.as_str());
394 }
395
396 #[test]
397 fn test_value_object_trait() {
398 let id = ResourceId::new("test-id".to_string()).unwrap();
399
400 assert_eq!(id.attribute_type(), AttributeType::String);
401 assert_eq!(id.attribute_name(), "id");
402 assert_eq!(id.as_json_value(), Value::String("test-id".to_string()));
403
404 let json_result = id.to_json().unwrap();
405 assert_eq!(json_result, Value::String("test-id".to_string()));
406 }
407
408 #[test]
409 fn test_schema_constructible_trait() {
410 let definition = AttributeDefinition {
411 name: "id".to_string(),
412 data_type: AttributeType::String,
413 multi_valued: false,
414 required: true,
415 case_exact: true,
416 mutability: Mutability::ReadOnly,
417 uniqueness: Uniqueness::Server,
418 canonical_values: vec![],
419 sub_attributes: vec![],
420 returned: None,
421 };
422
423 let value = Value::String("test-id".to_string());
424 let result = ResourceId::from_schema_and_value(&definition, &value);
425 assert!(result.is_ok());
426 assert_eq!(result.unwrap().as_str(), "test-id");
427
428 assert!(ResourceId::can_construct_from(&definition));
430
431 let mut wrong_def = definition.clone();
433 wrong_def.name = "userName".to_string();
434 assert!(!ResourceId::can_construct_from(&wrong_def));
435 }
436
437 #[test]
438 fn test_validate_against_schema() {
439 let id = ResourceId::new("test-id".to_string()).unwrap();
440
441 let valid_definition = AttributeDefinition {
442 name: "id".to_string(),
443 data_type: AttributeType::String,
444 multi_valued: false,
445 required: true,
446 case_exact: true,
447 mutability: Mutability::ReadOnly,
448 uniqueness: Uniqueness::Server,
449 canonical_values: vec![],
450 sub_attributes: vec![],
451 returned: None,
452 };
453
454 assert!(id.validate_against_schema(&valid_definition).is_ok());
455
456 let mut invalid_def = valid_definition.clone();
458 invalid_def.data_type = AttributeType::Integer;
459 assert!(id.validate_against_schema(&invalid_def).is_err());
460
461 invalid_def.name = "userName".to_string();
463 invalid_def.data_type = AttributeType::String;
464 assert!(id.validate_against_schema(&invalid_def).is_err());
465 }
466}