use crate::error::ScimError;
use crate::provider_capabilities::{
CapabilityDiscovery, CapabilityIntrospectable, ProviderCapabilities,
};
use crate::providers::ResourceProvider;
use crate::resource::{ResourceHandler, ScimOperation};
use crate::schema::SchemaRegistry;
use crate::schema_discovery::ServiceProviderConfig;
use crate::scim_server::builder::ScimServerConfig;
use std::collections::HashMap;
use std::sync::Arc;
pub struct ScimServer<P> {
pub(super) provider: P,
pub(super) schema_registry: SchemaRegistry,
pub(super) resource_handlers: HashMap<String, Arc<ResourceHandler>>, pub(super) supported_operations: HashMap<String, Vec<ScimOperation>>, pub(super) config: ScimServerConfig,
}
impl<P: ResourceProvider> ScimServer<P> {
pub fn new(provider: P) -> Result<Self, ScimError> {
Self::with_config(provider, ScimServerConfig::default())
}
pub fn with_config(provider: P, config: ScimServerConfig) -> Result<Self, ScimError> {
let schema_registry = SchemaRegistry::new()
.map_err(|e| ScimError::internal(format!("Failed to create schema registry: {}", e)))?;
Ok(Self {
provider,
schema_registry,
resource_handlers: HashMap::new(),
supported_operations: HashMap::new(),
config,
})
}
pub fn discover_capabilities(&self) -> Result<ProviderCapabilities, ScimError> {
CapabilityDiscovery::discover_capabilities(
&self.schema_registry,
&self.resource_handlers,
&self.supported_operations,
&self.provider,
)
}
pub fn discover_capabilities_with_introspection(
&self,
) -> Result<ProviderCapabilities, ScimError>
where
P: CapabilityIntrospectable,
{
CapabilityDiscovery::discover_capabilities_with_introspection(
&self.schema_registry,
&self.resource_handlers,
&self.supported_operations,
&self.provider,
)
}
pub fn get_service_provider_config(&self) -> Result<ServiceProviderConfig, ScimError> {
let capabilities = self.discover_capabilities()?;
Ok(CapabilityDiscovery::generate_service_provider_config(
&capabilities,
))
}
pub fn get_service_provider_config_with_introspection(
&self,
) -> Result<ServiceProviderConfig, ScimError>
where
P: CapabilityIntrospectable,
{
let capabilities = self.discover_capabilities_with_introspection()?;
Ok(CapabilityDiscovery::generate_service_provider_config(
&capabilities,
))
}
pub fn supports_operation(&self, resource_type: &str, operation: &ScimOperation) -> bool {
self.supported_operations
.get(resource_type)
.map(|ops| ops.contains(operation))
.unwrap_or(false)
}
pub fn provider(&self) -> &P {
&self.provider
}
pub fn config(&self) -> &ScimServerConfig {
&self.config
}
pub fn generate_ref_url(
&self,
tenant_id: Option<&str>,
resource_type: &str,
resource_id: &str,
) -> Result<String, ScimError> {
self.config
.generate_ref_url(tenant_id, resource_type, resource_id)
}
pub fn inject_ref_fields(
&self,
resource_json: &mut serde_json::Value,
tenant_id: Option<&str>,
) -> Result<(), ScimError> {
if let Some(members_array) = resource_json.get_mut("members") {
if let Some(members) = members_array.as_array_mut() {
for member in members {
if let Some(member_obj) = member.as_object_mut() {
if let (Some(member_id), Some(member_type)) = (
member_obj.get("value").and_then(|v| v.as_str()),
member_obj.get("type").and_then(|v| v.as_str()),
) {
let resource_type = match member_type {
"User" => "Users",
"Group" => "Groups",
_ => member_type, };
let ref_url =
self.generate_ref_url(tenant_id, resource_type, member_id)?;
member_obj
.insert("$ref".to_string(), serde_json::Value::String(ref_url));
}
}
}
}
}
if let Some(groups_array) = resource_json.get_mut("groups") {
if let Some(groups) = groups_array.as_array_mut() {
for group in groups {
if let Some(group_obj) = group.as_object_mut() {
if let Some(group_id) = group_obj.get("value").and_then(|v| v.as_str()) {
let ref_url = self.generate_ref_url(tenant_id, "Groups", group_id)?;
group_obj
.insert("$ref".to_string(), serde_json::Value::String(ref_url));
}
}
}
}
}
Ok(())
}
pub fn inject_location_field(
&self,
resource_json: &mut serde_json::Value,
tenant_id: Option<&str>,
) -> Result<(), ScimError> {
let resource_id = resource_json
.get("id")
.and_then(|id| id.as_str())
.map(|s| s.to_string());
if let Some(meta_obj) = resource_json
.get_mut("meta")
.and_then(|m| m.as_object_mut())
{
if let (Some(resource_type), Some(resource_id)) = (
meta_obj.get("resourceType").and_then(|rt| rt.as_str()),
resource_id.as_deref(),
) {
let resource_type_plural = match resource_type {
"User" => "Users",
"Group" => "Groups",
_ => resource_type, };
let location_url =
self.generate_ref_url(tenant_id, resource_type_plural, resource_id)?;
meta_obj.insert(
"location".to_string(),
serde_json::Value::String(location_url),
);
}
}
Ok(())
}
pub fn serialize_resource_with_refs(
&self,
resource: &crate::resource::Resource,
tenant_id: Option<&str>,
) -> Result<serde_json::Value, ScimError> {
let mut json = resource
.to_json()
.map_err(|e| ScimError::internal(format!("Failed to serialize resource: {}", e)))?;
self.inject_ref_fields(&mut json, tenant_id)?;
self.inject_location_field(&mut json, tenant_id)?;
Ok(json)
}
}