scim_server/resource/
serialization.rs

1//! Serialization and deserialization implementations for SCIM resources.
2//!
3//! This module provides Serde implementations for the Resource struct,
4//! enabling seamless JSON serialization/deserialization while maintaining
5//! type safety for core attributes.
6
7use crate::resource::resource::Resource;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use serde_json::Value;
10
11impl Serialize for Resource {
12    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
13    where
14        S: Serializer,
15    {
16        self.to_json()
17            .map_err(serde::ser::Error::custom)?
18            .serialize(serializer)
19    }
20}
21
22impl<'de> Deserialize<'de> for Resource {
23    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
24    where
25        D: Deserializer<'de>,
26    {
27        let value = Value::deserialize(deserializer)?;
28
29        // We need a resource type to properly parse, but JSON doesn't contain it
30        // For now, we'll extract it from the schema URIs or use a default
31        let resource_type = if let Some(obj) = value.as_object() {
32            if let Some(schemas) = obj.get("schemas").and_then(|s| s.as_array()) {
33                if let Some(first_schema) = schemas.first().and_then(|s| s.as_str()) {
34                    if first_schema.contains("User") {
35                        "User".to_string()
36                    } else if first_schema.contains("Group") {
37                        "Group".to_string()
38                    } else {
39                        "Resource".to_string()
40                    }
41                } else {
42                    "Resource".to_string()
43                }
44            } else {
45                "Resource".to_string()
46            }
47        } else {
48            return Err(serde::de::Error::custom("Resource must be a JSON object"));
49        };
50
51        Self::from_json(resource_type, value)
52            .map_err(|e| serde::de::Error::custom(format!("Validation error: {}", e)))
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use crate::resource::value_objects::{ResourceId, SchemaUri, UserName};
60    use serde_json::json;
61
62    #[test]
63    fn test_resource_serialization() {
64        let resource = Resource::new(
65            "User".to_string(),
66            Some(ResourceId::new("123".to_string()).unwrap()),
67            vec![SchemaUri::new("urn:ietf:params:scim:schemas:core:2.0:User".to_string()).unwrap()],
68            None,
69            Some(UserName::new("jdoe".to_string()).unwrap()),
70            serde_json::Map::new(),
71        );
72
73        let serialized = serde_json::to_string(&resource).unwrap();
74        assert!(serialized.contains("\"id\":\"123\""));
75        assert!(serialized.contains("\"userName\":\"jdoe\""));
76    }
77
78    #[test]
79    fn test_resource_deserialization() {
80        let json_data = json!({
81            "id": "123",
82            "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
83            "userName": "jdoe",
84            "displayName": "John Doe"
85        });
86
87        let resource: Resource = serde_json::from_value(json_data).unwrap();
88        assert_eq!(resource.get_id(), Some("123"));
89        assert_eq!(resource.get_username(), Some("jdoe"));
90        assert_eq!(resource.resource_type, "User");
91    }
92
93    #[test]
94    fn test_round_trip_serialization() {
95        let original_json = json!({
96            "id": "456",
97            "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
98            "displayName": "Test Group",
99            "members": []
100        });
101
102        let resource: Resource = serde_json::from_value(original_json.clone()).unwrap();
103        let serialized = serde_json::to_value(&resource).unwrap();
104
105        assert_eq!(serialized["id"], "456");
106        assert_eq!(serialized["displayName"], "Test Group");
107        assert!(serialized["schemas"].is_array());
108    }
109}