scim_v2 0.4.0

A crate that provides utilities for working with the System for Cross-domain Identity Management (SCIM) version 2.0 protocol. (rfc7642, rfc7643, rfc7644)
Documentation
//Schema for group
use crate::{models::scim_schema::Meta, utils::error::SCIMError};
use serde::{Deserialize, Serialize, de::DeserializeOwned};

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Group<T = String> {
    pub schemas: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<T>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub external_id: Option<String>,
    pub display_name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub members: Option<Vec<Member<T>>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub meta: Option<Meta>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum MemberType {
    #[serde(rename = "User", alias = "USER", alias = "user")]
    User,
    #[serde(rename = "Group", alias = "GROUP", alias = "group")]
    Group,
}

#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Member<T = String> {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub value: Option<T>,
    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
    pub r#ref: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub r#type: Option<MemberType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub display: Option<String>,
}

impl<T> Group<T> {
    /// Validates a group.
    ///
    /// This function checks if the group has `schemas`, `id`, and `display_name`. If any of these fields are missing, it returns an error.
    ///
    /// # Arguments
    ///
    /// * `group` - A reference to a Group instance.
    ///
    /// # Returns
    ///
    /// * `Ok(())` - If the group is valid.
    /// * `Err(SCIMError::MissingRequiredField)` - If a required field is missing.
    ///
    /// # Example
    ///
    /// ```
    /// use scim_v2::models::group::Group;
    ///
    /// let group = Group {
    ///     schemas: vec!["urn:ietf:params:scim:schemas:core:2.0:Group".to_string()],
    ///     id: Some("e9e30dba-f08f-4109-8486-d5c6a331660a".to_string()),
    ///     external_id: None,
    ///     display_name: "Tour Guides".to_string(),
    ///     meta: None,
    ///     members: None
    /// };
    ///
    /// match group.validate() {
    ///     Ok(_) => println!("Group is valid."),
    ///     Err(e) => println!("Group is invalid: {}", e),
    /// }
    /// ```
    pub fn validate(&self) -> Result<(), SCIMError> {
        if self.schemas.is_empty() {
            return Err(SCIMError::MissingRequiredField("schemas".to_string()));
        }
        if self.display_name.is_empty() {
            return Err(SCIMError::MissingRequiredField("display_name".to_string()));
        }
        Ok(())
    }
}

impl<T> Group<T>
where
    T: Serialize,
{
    /// Serializes the `Group` instance to a JSON string, using the custom SCIMError for error handling.
    ///
    /// # Returns
    ///
    /// This method returns a `Result<String, SCIMError>`, where `Ok(String)` contains
    /// the JSON string representation of the `Group` instance, and `Err(SCIMError)` contains
    /// the custom error encountered during serialization.
    ///
    /// # Examples
    ///
    /// ```
    /// use scim_v2::models::group::Group;
    ///
    /// let group = Group {
    ///     schemas: vec!["urn:ietf:params:scim:schemas:core:2.0:Group".to_string()],
    ///     id: Some("e9e30dba-f08f-4109-8486-d5c6a331660a".to_string()),
    ///     external_id: None,
    ///     display_name: "Tour Guides".to_string(),
    ///     meta: None,
    ///     members: None
    /// };
    ///
    /// match group.serialize() {
    ///     Ok(json) => println!("Serialized User: {}", json),
    ///     Err(e) => println!("Serialization error: {}", e),
    /// }
    /// ```
    pub fn serialize(&self) -> Result<String, SCIMError> {
        serde_json::to_string(&self).map_err(SCIMError::SerializationError)
    }
}

impl<T> Group<T>
where
    T: DeserializeOwned,
{
    /// Deserializes a JSON string into a `Group` instance, using the custom SCIMError for error handling.
    ///
    /// # Parameters
    ///
    /// * `json` - A string slice that holds the JSON representation of a `Group`.
    ///
    /// # Returns
    ///
    /// This method returns a `Result<Group, SCIMError>`, where `Ok(Group)` is the deserialized `Group` instance,
    /// and `Err(SCIMError)` is the custom error encountered during deserialization.
    ///
    /// # Examples
    ///
    /// ```
    /// use scim_v2::models::group::Group;
    ///
    /// let group_json = r#"{"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "id": "e9e30dba-f08f-4109-8486-d5c6a331660a", "displayName": "Tour Guides"}"#;
    /// match Group::<uuid::Uuid>::deserialize(group_json) {
    ///     Ok(group) => println!("Deserialized Group: {:?}", group),
    ///     Err(e) => println!("Deserialization error: {}", e),
    /// }
    /// ```
    pub fn deserialize(json: &str) -> Result<Self, SCIMError> {
        serde_json::from_str(json).map_err(SCIMError::DeserializationError)
    }
}

#[cfg(test)]
mod tests {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn group_deserialization_succeeds_for_valid_full_json() {
        let json_data = r#"   {
             "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
             "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
             "displayName": "Tour Guides",
             "members": [
               {
                 "value": "2819c223-7f76-453a-919d-413861904646",
                 "$ref":
           "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
                 "display": "Babs Jensen"
               },
               {
                 "value": "902c246b-6245-4190-8e05-00816be7344a",
                 "$ref":
           "https://example.com/v2/Users/902c246b-6245-4190-8e05-00816be7344a",
                 "display": "Mandy Pepperidge"
               }
             ],
             "meta": {
               "resourceType": "Group",
               "created": "2010-01-23T04:56:22Z",
               "lastModified": "2011-05-13T04:42:34Z",
               "version": "W\/\"3694e05e9dff592\"",
               "location":
           "https://example.com/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a"
             }
           }"#;

        let group: Result<Group, serde_json::Error> = serde_json::from_str(json_data);

        if let Err(e) = &group {
            eprintln!("Deserialization failed: {:?}", e);
        }
        assert!(group.is_ok());
        let group = group.unwrap();
        assert_eq!(
            group.schemas,
            vec!["urn:ietf:params:scim:schemas:core:2.0:Group"]
        );
        assert_eq!(
            group.id,
            Some("e9e30dba-f08f-4109-8486-d5c6a331660a".into())
        );
        assert_eq!(group.display_name, "Tour Guides");

        // Check members
        assert_eq!(group.members.as_ref().unwrap().len(), 2);
        assert_eq!(
            group.members.as_ref().unwrap()[0].value,
            Some("2819c223-7f76-453a-919d-413861904646".to_string())
        );
        assert_eq!(
            group.members.as_ref().unwrap()[0].display,
            Some("Babs Jensen".to_string())
        );
        assert_eq!(
            group.members.as_ref().unwrap()[1].value,
            Some("902c246b-6245-4190-8e05-00816be7344a".to_string())
        );
        assert_eq!(
            group.members.as_ref().unwrap()[1].display,
            Some("Mandy Pepperidge".to_string())
        );

        // Check meta
        let meta = group.meta.unwrap();
        assert_eq!(meta.resource_type, Some("Group".to_string()));
        assert_eq!(meta.created, Some("2010-01-23T04:56:22Z".to_string()));
        assert_eq!(meta.last_modified, Some("2011-05-13T04:42:34Z".to_string()));
        assert_eq!(meta.version, Some("W/\"3694e05e9dff592\"".to_string()));
        assert_eq!(
            meta.location,
            Some("https://example.com/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a".to_string())
        );
    }

    #[test]
    fn group_deserialization_succeeds_for_valid_json() {
        let json_data = r#"{
            "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
            "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
            "displayName": "Tour Guides",
            "meta": {
               "resourceType": "Group",
               "created": "2010-01-23T04:56:22Z",
               "lastModified": "2011-05-13T04:42:34Z",
               "version": "W\/\"3694e05e9dff592\"",
               "location": "https://example.com/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a"
            }
        }"#;

        let group: Result<Group, serde_json::Error> = serde_json::from_str(json_data);

        if let Err(e) = &group {
            eprintln!("Deserialization failed: {:?}", e);
        }
        assert!(group.is_ok());
        let group = group.unwrap();
        assert_eq!(
            group.schemas,
            vec!["urn:ietf:params:scim:schemas:core:2.0:Group"]
        );
        assert_eq!(
            group.id,
            Some("e9e30dba-f08f-4109-8486-d5c6a331660a".into())
        );
        assert_eq!(group.display_name, "Tour Guides");
    }

    #[test]
    fn group_deserialization_fails_for_invalid_json() {
        let json_data = r#"{
            "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
            "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
            "displayName": 12345
        },
        "meta": {
               "resourceType": "Group",
               "created": "2010-01-23T04:56:22Z",
               "lastModified": "2011-05-13T04:42:34Z",
               "version": "W\/\"3694e05e9dff592\"",
               "location": "https://example.com/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a"
            }"#;

        let group: Result<Group, serde_json::Error> = serde_json::from_str(json_data);

        assert!(group.is_err());
    }

    #[test]
    fn group_deserialization_handles_missing_optional_fields() {
        let json_data = r#"{
            "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
            "id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
            "displayName": "Tour Guides"
        }"#;

        let group: Result<Group, serde_json::Error> = serde_json::from_str(json_data);

        assert!(group.is_ok());
        let group = group.unwrap();
        assert_eq!(
            group.schemas,
            vec!["urn:ietf:params:scim:schemas:core:2.0:Group"]
        );
        assert_eq!(
            group.id,
            Some("e9e30dba-f08f-4109-8486-d5c6a331660a".into())
        );
        assert_eq!(group.display_name, "Tour Guides");
        assert!(group.members.is_none());
        assert!(group.meta.is_none());
    }
}