use crate::{
ResourceProvider, ScimServer,
resource::version::RawVersion,
resource::{RequestContext, TenantContext},
};
use log::{debug, info, warn};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
pub struct ScimOperationHandler<P: ResourceProvider> {
pub(super) server: ScimServer<P>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ScimOperationRequest {
pub operation: ScimOperationType,
pub resource_type: String,
pub resource_id: Option<String>,
pub data: Option<Value>,
pub query: Option<ScimQuery>,
pub tenant_context: Option<TenantContext>,
pub request_id: Option<String>,
pub expected_version: Option<RawVersion>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ScimOperationType {
Create,
Get,
Update,
Patch,
Delete,
List,
Search,
GetSchemas,
GetSchema,
Exists,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ScimQuery {
pub count: Option<usize>,
pub start_index: Option<usize>,
pub filter: Option<String>,
pub attributes: Option<Vec<String>>,
pub excluded_attributes: Option<Vec<String>>,
pub search_attribute: Option<String>,
pub search_value: Option<Value>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ScimOperationResponse {
pub success: bool,
pub data: Option<Value>,
pub error: Option<String>,
pub error_code: Option<String>,
pub metadata: OperationMetadata,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OperationMetadata {
pub resource_type: Option<String>,
pub resource_id: Option<String>,
pub resource_count: Option<usize>,
pub total_results: Option<usize>,
pub request_id: String,
pub tenant_id: Option<String>,
pub schemas: Option<Vec<String>>,
pub additional: HashMap<String, Value>,
}
impl<P: ResourceProvider + Sync> ScimOperationHandler<P> {
pub fn new(server: ScimServer<P>) -> Self {
Self { server }
}
pub async fn handle_operation(&self, request: ScimOperationRequest) -> ScimOperationResponse {
let request_id = request
.request_id
.clone()
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
info!(
"SCIM operation handler processing {:?} for {} (request: '{}')",
request.operation, request.resource_type, request_id
);
let context = self.create_request_context(&request, &request_id);
let result = match request.operation {
ScimOperationType::Create => {
super::handlers::crud::handle_create(self, request, &context).await
}
ScimOperationType::Get => {
super::handlers::crud::handle_get(self, request, &context).await
}
ScimOperationType::Update => {
super::handlers::crud::handle_update(self, request, &context).await
}
ScimOperationType::Patch => {
super::handlers::crud::handle_patch(self, request, &context).await
}
ScimOperationType::Delete => {
super::handlers::crud::handle_delete(self, request, &context).await
}
ScimOperationType::List => {
super::handlers::query::handle_list(self, request, &context).await
}
ScimOperationType::Search => {
super::handlers::query::handle_search(self, request, &context).await
}
ScimOperationType::GetSchemas => {
super::handlers::schema::handle_get_schemas(self, request, &context).await
}
ScimOperationType::GetSchema => {
super::handlers::schema::handle_get_schema(self, request, &context).await
}
ScimOperationType::Exists => {
super::handlers::utility::handle_exists(self, request, &context).await
}
};
match &result {
Ok(_) => {
debug!(
"SCIM operation handler completed successfully (request: '{}')",
request_id
);
}
Err(e) => {
warn!(
"SCIM operation handler failed: {} (request: '{}')",
e, request_id
);
}
}
result.unwrap_or_else(|e| super::errors::create_error_response(e, request_id))
}
pub(super) fn create_request_context(
&self,
request: &ScimOperationRequest,
request_id: &str,
) -> RequestContext {
match &request.tenant_context {
Some(tenant_ctx) => {
RequestContext::with_tenant(request_id.to_string(), tenant_ctx.clone())
}
None => RequestContext::new(request_id.to_string()),
}
}
pub(super) fn server(&self) -> &ScimServer<P> {
&self.server
}
}