use super::conditional_provider::VersionedResource;
use super::core::{ListQuery, RequestContext, Resource};
use super::version::{ConditionalResult, ScimVersion};
use serde_json::Value;
use std::future::Future;
pub trait ResourceProvider {
type Error: std::error::Error + Send + Sync + 'static;
fn create_resource(
&self,
resource_type: &str,
data: Value,
context: &RequestContext,
) -> impl Future<Output = Result<Resource, Self::Error>> + Send;
fn get_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<Option<Resource>, Self::Error>> + Send;
fn update_resource(
&self,
resource_type: &str,
id: &str,
data: Value,
context: &RequestContext,
) -> impl Future<Output = Result<Resource, Self::Error>> + Send;
fn delete_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn list_resources(
&self,
resource_type: &str,
_query: Option<&ListQuery>,
context: &RequestContext,
) -> impl Future<Output = Result<Vec<Resource>, Self::Error>> + Send;
fn find_resource_by_attribute(
&self,
resource_type: &str,
attribute: &str,
value: &Value,
context: &RequestContext,
) -> impl Future<Output = Result<Option<Resource>, Self::Error>> + Send;
fn resource_exists(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<bool, Self::Error>> + Send;
fn conditional_update(
&self,
resource_type: &str,
id: &str,
data: Value,
expected_version: &ScimVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<VersionedResource>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(current_resource) => {
let current_versioned = VersionedResource::new(current_resource);
if current_versioned.version().matches(expected_version) {
let updated = self
.update_resource(resource_type, id, data, context)
.await?;
Ok(ConditionalResult::Success(VersionedResource::new(updated)))
} else {
Ok(ConditionalResult::VersionMismatch(
super::version::VersionConflict::standard_message(
expected_version.clone(),
current_versioned.version().clone(),
),
))
}
}
None => Ok(ConditionalResult::NotFound),
}
}
}
fn conditional_delete(
&self,
resource_type: &str,
id: &str,
expected_version: &ScimVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<()>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(current_resource) => {
let current_versioned = VersionedResource::new(current_resource);
if current_versioned.version().matches(expected_version) {
self.delete_resource(resource_type, id, context).await?;
Ok(ConditionalResult::Success(()))
} else {
Ok(ConditionalResult::VersionMismatch(
super::version::VersionConflict::standard_message(
expected_version.clone(),
current_versioned.version().clone(),
),
))
}
}
None => Ok(ConditionalResult::NotFound),
}
}
}
fn get_versioned_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<Option<VersionedResource>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(resource) => Ok(Some(VersionedResource::new(resource))),
None => Ok(None),
}
}
}
fn patch_resource(
&self,
resource_type: &str,
id: &str,
patch_request: &Value,
context: &RequestContext,
) -> impl Future<Output = Result<Resource, Self::Error>> + Send
where
Self: Sync,
{
async move {
let current = self
.get_resource(resource_type, id, context)
.await?
.ok_or_else(|| {
unreachable!("Resource not found - providers must handle this case")
})?;
let operations = patch_request
.get("Operations")
.and_then(|ops| ops.as_array())
.ok_or_else(|| {
unreachable!("Invalid patch request - providers must handle this case")
})?;
let mut modified_data = current.to_json().map_err(|_| {
unreachable!("Failed to serialize resource - providers must handle this case")
})?;
for operation in operations {
self.apply_patch_operation(&mut modified_data, operation)?;
}
self.update_resource(resource_type, id, modified_data, context)
.await
}
}
fn apply_patch_operation(
&self,
_resource_data: &mut Value,
_operation: &Value,
) -> Result<(), Self::Error> {
Ok(())
}
}
pub trait ResourceProviderExt: ResourceProvider {
fn create_single_tenant(
&self,
resource_type: &str,
data: Value,
request_id: Option<String>,
) -> impl Future<Output = Result<Resource, Self::Error>> + Send
where
Self: Sync,
{
async move {
let context = match request_id {
Some(id) => RequestContext::new(id),
None => RequestContext::with_generated_id(),
};
self.create_resource(resource_type, data, &context).await
}
}
fn create_multi_tenant(
&self,
tenant_id: &str,
resource_type: &str,
data: Value,
request_id: Option<String>,
) -> impl Future<Output = Result<Resource, Self::Error>> + Send
where
Self: Sync,
{
async move {
use super::core::TenantContext;
let tenant_context = TenantContext {
tenant_id: tenant_id.to_string(),
client_id: "default-client".to_string(),
permissions: Default::default(),
isolation_level: Default::default(),
};
let context = match request_id {
Some(id) => RequestContext::with_tenant(id, tenant_context),
None => RequestContext::with_tenant_generated_id(tenant_context),
};
self.create_resource(resource_type, data, &context).await
}
}
fn get_single_tenant(
&self,
resource_type: &str,
id: &str,
request_id: Option<String>,
) -> impl Future<Output = Result<Option<Resource>, Self::Error>> + Send
where
Self: Sync,
{
async move {
let context = match request_id {
Some(req_id) => RequestContext::new(req_id),
None => RequestContext::with_generated_id(),
};
self.get_resource(resource_type, id, &context).await
}
}
fn get_multi_tenant(
&self,
tenant_id: &str,
resource_type: &str,
id: &str,
request_id: Option<String>,
) -> impl Future<Output = Result<Option<Resource>, Self::Error>> + Send
where
Self: Sync,
{
async move {
use super::core::TenantContext;
let tenant_context = TenantContext {
tenant_id: tenant_id.to_string(),
client_id: "default-client".to_string(),
permissions: Default::default(),
isolation_level: Default::default(),
};
let context = match request_id {
Some(req_id) => RequestContext::with_tenant(req_id, tenant_context),
None => RequestContext::with_tenant_generated_id(tenant_context),
};
self.get_resource(resource_type, id, &context).await
}
}
}
impl<T: ResourceProvider> ResourceProviderExt for T {}