use crate::{
ResourceProvider, ScimError,
error::ScimResult,
operation_handler::{
core::{
OperationMetadata, ScimOperationHandler, ScimOperationRequest, ScimOperationResponse,
},
create_version_conflict_response,
},
resource::{
RequestContext, conditional_provider::VersionedResource, version::ConditionalResult,
},
};
use std::collections::HashMap;
pub async fn handle_create<P: ResourceProvider + Sync>(
handler: &ScimOperationHandler<P>,
request: ScimOperationRequest,
context: &RequestContext,
) -> ScimResult<ScimOperationResponse> {
let data = request.data.ok_or_else(|| {
ScimError::invalid_request("Missing data for create operation".to_string())
})?;
let resource = handler
.server()
.create_resource(&request.resource_type, data, context)
.await?;
let versioned_resource = VersionedResource::new(resource.clone());
let mut additional = HashMap::new();
additional.insert(
"version".to_string(),
serde_json::Value::String(versioned_resource.version().as_str().to_string()),
);
additional.insert(
"etag".to_string(),
serde_json::Value::String(versioned_resource.version().to_http_header()),
);
Ok(ScimOperationResponse {
success: true,
data: Some(resource.to_json()?),
error: None,
error_code: None,
metadata: OperationMetadata {
resource_type: Some(request.resource_type),
resource_id: resource.get_id().map(|s| s.to_string()),
resource_count: Some(1),
total_results: None,
request_id: context.request_id.clone(),
tenant_id: context.tenant_context.as_ref().map(|t| t.tenant_id.clone()),
schemas: Some(
resource
.schemas
.iter()
.map(|s| s.as_str().to_string())
.collect(),
),
additional,
},
})
}
pub async fn handle_get<P: ResourceProvider + Sync>(
handler: &ScimOperationHandler<P>,
request: ScimOperationRequest,
context: &RequestContext,
) -> ScimResult<ScimOperationResponse> {
let resource_id = request.resource_id.ok_or_else(|| {
ScimError::invalid_request("Missing resource_id for get operation".to_string())
})?;
let resource = handler
.server()
.get_resource(&request.resource_type, &resource_id, context)
.await?;
match resource {
Some(resource) => {
let versioned_resource = VersionedResource::new(resource.clone());
let mut additional = HashMap::new();
additional.insert(
"version".to_string(),
serde_json::Value::String(versioned_resource.version().as_str().to_string()),
);
additional.insert(
"etag".to_string(),
serde_json::Value::String(versioned_resource.version().to_http_header()),
);
Ok(ScimOperationResponse {
success: true,
data: Some(resource.to_json()?),
error: None,
error_code: None,
metadata: OperationMetadata {
resource_type: Some(request.resource_type),
resource_id: Some(resource_id),
resource_count: Some(1),
total_results: None,
request_id: context.request_id.clone(),
tenant_id: context.tenant_context.as_ref().map(|t| t.tenant_id.clone()),
schemas: Some(
resource
.schemas
.iter()
.map(|s| s.as_str().to_string())
.collect(),
),
additional,
},
})
}
None => Err(ScimError::resource_not_found(
request.resource_type,
resource_id,
)),
}
}
pub async fn handle_update<P: ResourceProvider + Sync>(
handler: &ScimOperationHandler<P>,
request: ScimOperationRequest,
context: &RequestContext,
) -> ScimResult<ScimOperationResponse> {
let resource_id = request.resource_id.ok_or_else(|| {
ScimError::invalid_request("Missing resource_id for update operation".to_string())
})?;
let data = request.data.ok_or_else(|| {
ScimError::invalid_request("Missing data for update operation".to_string())
})?;
if let Some(expected_version) = &request.expected_version {
match handler
.server()
.provider()
.conditional_update(
&request.resource_type,
&resource_id,
data,
expected_version,
context,
)
.await
.map_err(|e| ScimError::ProviderError(e.to_string()))?
{
ConditionalResult::Success(versioned_resource) => {
let mut additional = HashMap::new();
additional.insert(
"version".to_string(),
serde_json::Value::String(versioned_resource.version().as_str().to_string()),
);
additional.insert(
"etag".to_string(),
serde_json::Value::String(versioned_resource.version().to_http_header()),
);
Ok(ScimOperationResponse {
success: true,
data: Some(versioned_resource.resource().to_json()?),
error: None,
error_code: None,
metadata: OperationMetadata {
resource_type: Some(request.resource_type),
resource_id: Some(resource_id),
resource_count: Some(1),
total_results: None,
request_id: context.request_id.clone(),
tenant_id: context.tenant_context.as_ref().map(|t| t.tenant_id.clone()),
schemas: Some(
versioned_resource
.resource()
.schemas
.iter()
.map(|s| s.as_str().to_string())
.collect(),
),
additional,
},
})
}
ConditionalResult::VersionMismatch(conflict) => Ok(create_version_conflict_response(
conflict,
context.request_id.clone(),
Some(request.resource_type),
Some(resource_id),
)),
ConditionalResult::NotFound => Err(ScimError::resource_not_found(
request.resource_type,
resource_id,
)),
}
} else {
let resource = handler
.server()
.update_resource(&request.resource_type, &resource_id, data, context)
.await?;
let versioned_resource = VersionedResource::new(resource.clone());
let mut additional = HashMap::new();
additional.insert(
"version".to_string(),
serde_json::Value::String(versioned_resource.version().as_str().to_string()),
);
additional.insert(
"etag".to_string(),
serde_json::Value::String(versioned_resource.version().to_http_header()),
);
Ok(ScimOperationResponse {
success: true,
data: Some(resource.to_json()?),
error: None,
error_code: None,
metadata: OperationMetadata {
resource_type: Some(request.resource_type),
resource_id: Some(resource_id),
resource_count: Some(1),
total_results: None,
request_id: context.request_id.clone(),
tenant_id: context.tenant_context.as_ref().map(|t| t.tenant_id.clone()),
schemas: Some(
resource
.schemas
.iter()
.map(|s| s.as_str().to_string())
.collect(),
),
additional,
},
})
}
}
pub async fn handle_delete<P: ResourceProvider + Sync>(
handler: &ScimOperationHandler<P>,
request: ScimOperationRequest,
context: &RequestContext,
) -> ScimResult<ScimOperationResponse> {
let resource_id = request.resource_id.ok_or_else(|| {
ScimError::invalid_request("Missing resource_id for delete operation".to_string())
})?;
if let Some(expected_version) = &request.expected_version {
match handler
.server()
.provider()
.conditional_delete(
&request.resource_type,
&resource_id,
expected_version,
context,
)
.await
.map_err(|e| ScimError::ProviderError(e.to_string()))?
{
ConditionalResult::Success(_) => Ok(ScimOperationResponse {
success: true,
data: None,
error: None,
error_code: None,
metadata: OperationMetadata {
resource_type: Some(request.resource_type),
resource_id: Some(resource_id),
resource_count: None,
total_results: None,
request_id: context.request_id.clone(),
tenant_id: context.tenant_context.as_ref().map(|t| t.tenant_id.clone()),
schemas: None,
additional: HashMap::new(),
},
}),
ConditionalResult::VersionMismatch(conflict) => Ok(create_version_conflict_response(
conflict,
context.request_id.clone(),
Some(request.resource_type),
Some(resource_id),
)),
ConditionalResult::NotFound => Err(ScimError::resource_not_found(
request.resource_type,
resource_id,
)),
}
} else {
handler
.server()
.delete_resource(&request.resource_type, &resource_id, context)
.await?;
Ok(ScimOperationResponse {
success: true,
data: None,
error: None,
error_code: None,
metadata: OperationMetadata {
resource_type: Some(request.resource_type),
resource_id: Some(resource_id),
resource_count: None,
total_results: None,
request_id: context.request_id.clone(),
tenant_id: context.tenant_context.as_ref().map(|t| t.tenant_id.clone()),
schemas: None,
additional: HashMap::new(),
},
})
}
}
pub async fn handle_patch<P: ResourceProvider + Sync>(
handler: &ScimOperationHandler<P>,
request: ScimOperationRequest,
context: &RequestContext,
) -> ScimResult<ScimOperationResponse> {
let resource_id = request.resource_id.ok_or_else(|| {
ScimError::invalid_request("Missing resource_id for patch operation".to_string())
})?;
let data = request.data.ok_or_else(|| {
ScimError::invalid_request("Missing data for patch operation".to_string())
})?;
let resource = handler
.server()
.patch_resource(&request.resource_type, &resource_id, &data, context)
.await?;
let versioned_resource = VersionedResource::new(resource.clone());
let mut additional = HashMap::new();
additional.insert(
"version".to_string(),
serde_json::Value::String(versioned_resource.version().as_str().to_string()),
);
additional.insert(
"etag".to_string(),
serde_json::Value::String(versioned_resource.version().to_http_header()),
);
Ok(ScimOperationResponse {
success: true,
data: Some(resource.to_json()?),
error: None,
error_code: None,
metadata: OperationMetadata {
resource_type: Some(request.resource_type),
resource_id: Some(resource_id),
resource_count: Some(1),
total_results: None,
request_id: context.request_id.clone(),
tenant_id: context.tenant_context.as_ref().map(|t| t.tenant_id.clone()),
schemas: Some(
resource
.schemas
.iter()
.map(|s| s.as_str().to_string())
.collect(),
),
additional,
},
})
}