camel-component-keycloak 0.18.0

Keycloak integration for rust-camel — claim presets, URL builders (Admin API and event consumer in future phases)
Documentation
use std::fmt;
use std::str::FromStr;

use camel_api::CamelError;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AdminOperation {
    CreateUser,
    DeleteUser,
    GetUser,
    CreateRole,
    AssignRole,
    CreateClient,
    CreateRealm,
}

impl FromStr for AdminOperation {
    type Err = CamelError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "createUser" => Ok(Self::CreateUser),
            "deleteUser" => Ok(Self::DeleteUser),
            "getUser" => Ok(Self::GetUser),
            "createRole" => Ok(Self::CreateRole),
            "assignRole" => Ok(Self::AssignRole),
            "createClient" => Ok(Self::CreateClient),
            "createRealm" => Ok(Self::CreateRealm),
            other => Err(CamelError::InvalidUri(format!(
                "unknown keycloak admin operation: '{other}'"
            ))),
        }
    }
}

impl AdminOperation {
    pub fn http_method(&self) -> &str {
        match self {
            Self::CreateUser | Self::CreateRole | Self::CreateClient | Self::CreateRealm => "POST",
            Self::DeleteUser => "DELETE",
            Self::GetUser => "GET",
            Self::AssignRole => "POST",
        }
    }

    pub fn build_path(&self, realm: &str, user_id: Option<&str>) -> Result<String, CamelError> {
        match self {
            Self::CreateUser => Ok(format!("/admin/realms/{realm}/users")),
            Self::DeleteUser => {
                let uid = user_id.ok_or_else(|| {
                    CamelError::InvalidUri("deleteUser requires userId parameter".into())
                })?;
                Ok(format!("/admin/realms/{realm}/users/{uid}"))
            }
            Self::GetUser => {
                let uid = user_id.ok_or_else(|| {
                    CamelError::InvalidUri("getUser requires userId parameter".into())
                })?;
                Ok(format!("/admin/realms/{realm}/users/{uid}"))
            }
            Self::CreateRole => Ok(format!("/admin/realms/{realm}/roles")),
            Self::AssignRole => {
                let uid = user_id.ok_or_else(|| {
                    CamelError::InvalidUri("assignRole requires userId parameter".into())
                })?;
                Ok(format!(
                    "/admin/realms/{realm}/users/{uid}/role-mappings/realm"
                ))
            }
            Self::CreateClient => Ok(format!("/admin/realms/{realm}/clients")),
            Self::CreateRealm => Ok("/admin/realms".to_string()),
        }
    }
}

impl fmt::Display for AdminOperation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CreateUser => write!(f, "createUser"),
            Self::DeleteUser => write!(f, "deleteUser"),
            Self::GetUser => write!(f, "getUser"),
            Self::CreateRole => write!(f, "createRole"),
            Self::AssignRole => write!(f, "assignRole"),
            Self::CreateClient => write!(f, "createClient"),
            Self::CreateRealm => write!(f, "createRealm"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn admin_operation_from_str_all_variants() {
        let variants = [
            ("createUser", AdminOperation::CreateUser),
            ("deleteUser", AdminOperation::DeleteUser),
            ("getUser", AdminOperation::GetUser),
            ("createRole", AdminOperation::CreateRole),
            ("assignRole", AdminOperation::AssignRole),
            ("createClient", AdminOperation::CreateClient),
            ("createRealm", AdminOperation::CreateRealm),
        ];
        for (s, expected) in variants {
            let parsed: AdminOperation = s.parse().unwrap();
            assert_eq!(parsed, expected);
            assert_eq!(parsed.to_string(), s);
        }
    }

    #[test]
    fn admin_operation_from_str_unknown_returns_error() {
        let result: Result<AdminOperation, CamelError> = "bogus".parse();
        assert!(result.is_err());
        let err = result.unwrap_err();
        let msg = err.to_string();
        assert!(msg.contains("unknown keycloak admin operation: 'bogus'"));
    }

    #[test]
    fn admin_operation_http_methods() {
        assert_eq!(AdminOperation::CreateUser.http_method(), "POST");
        assert_eq!(AdminOperation::CreateRole.http_method(), "POST");
        assert_eq!(AdminOperation::CreateClient.http_method(), "POST");
        assert_eq!(AdminOperation::CreateRealm.http_method(), "POST");
        assert_eq!(AdminOperation::AssignRole.http_method(), "POST");
        assert_eq!(AdminOperation::DeleteUser.http_method(), "DELETE");
        assert_eq!(AdminOperation::GetUser.http_method(), "GET");
    }

    #[test]
    fn admin_operation_build_path_create_user() {
        let path = AdminOperation::CreateUser
            .build_path("myrealm", None)
            .unwrap();
        assert_eq!(path, "/admin/realms/myrealm/users");
    }

    #[test]
    fn admin_operation_build_path_delete_user_missing_id_returns_error() {
        let result = AdminOperation::DeleteUser.build_path("myrealm", None);
        assert!(result.is_err());
        let msg = result.unwrap_err().to_string();
        assert!(msg.contains("deleteUser requires userId parameter"));
    }

    #[test]
    fn admin_operation_build_path_create_realm_no_realm_in_path() {
        let path = AdminOperation::CreateRealm
            .build_path("ignored-realm", None)
            .unwrap();
        assert_eq!(path, "/admin/realms");
    }
}