pub trait ResourceProvider {
type Error: Error + Send + Sync + 'static;
// Required methods
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;
// Provided methods
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 { ... }
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 { ... }
fn get_versioned_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<Option<VersionedResource>, Self::Error>> + Send
where Self: Sync { ... }
}Expand description
Unified resource provider trait supporting both single and multi-tenant operations.
This trait provides a unified interface for SCIM resource operations that works for both single-tenant and multi-tenant scenarios:
- Single-tenant: Operations use RequestContext with tenant_context = None
- Multi-tenant: Operations use RequestContext with tenant_context = Some(…)
The provider implementation can check context.tenant_id() to determine
the effective tenant for the operation.
Required Associated Types§
Required Methods§
Sourcefn create_resource(
&self,
resource_type: &str,
data: Value,
context: &RequestContext,
) -> impl Future<Output = Result<Resource, Self::Error>> + Send
fn create_resource( &self, resource_type: &str, data: Value, context: &RequestContext, ) -> impl Future<Output = Result<Resource, Self::Error>> + Send
Create a resource for the tenant specified in the request context.
§Arguments
resource_type- The type of resource to create (e.g., “User”, “Group”)data- The resource data as JSONcontext- Request context containing tenant information (if multi-tenant)
§Returns
The created resource with any server-generated fields (id, metadata, etc.)
§Tenant Handling
- Single-tenant:
context.tenant_id()returnsNone - Multi-tenant:
context.tenant_id()returnsSome(tenant_id)
Sourcefn get_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<Option<Resource>, Self::Error>> + Send
fn get_resource( &self, resource_type: &str, id: &str, context: &RequestContext, ) -> impl Future<Output = Result<Option<Resource>, Self::Error>> + Send
Get a resource by ID from the tenant specified in the request context.
§Arguments
resource_type- The type of resource to retrieveid- The unique identifier of the resourcecontext- Request context containing tenant information (if multi-tenant)
§Returns
The resource if found, None if not found within the tenant scope
Sourcefn update_resource(
&self,
resource_type: &str,
id: &str,
data: Value,
context: &RequestContext,
) -> impl Future<Output = Result<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
Update a resource in the tenant specified in the request context.
§Arguments
resource_type- The type of resource to updateid- The unique identifier of the resourcedata- The updated resource data as JSONcontext- Request context containing tenant information (if multi-tenant)
§Returns
The updated resource
Sourcefn delete_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<(), Self::Error>> + Send
fn delete_resource( &self, resource_type: &str, id: &str, context: &RequestContext, ) -> impl Future<Output = Result<(), Self::Error>> + Send
Delete a resource from the tenant specified in the request context.
§Arguments
resource_type- The type of resource to deleteid- The unique identifier of the resourcecontext- Request context containing tenant information (if multi-tenant)
Sourcefn list_resources(
&self,
resource_type: &str,
_query: Option<&ListQuery>,
context: &RequestContext,
) -> impl Future<Output = Result<Vec<Resource>, Self::Error>> + Send
fn list_resources( &self, resource_type: &str, _query: Option<&ListQuery>, context: &RequestContext, ) -> impl Future<Output = Result<Vec<Resource>, Self::Error>> + Send
List resources from the tenant specified in the request context.
§Arguments
resource_type- The type of resources to listquery- Optional query parameters for filtering, sorting, paginationcontext- Request context containing tenant information (if multi-tenant)
§Returns
A vector of resources from the specified tenant
Sourcefn find_resource_by_attribute(
&self,
resource_type: &str,
attribute: &str,
value: &Value,
context: &RequestContext,
) -> impl Future<Output = Result<Option<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
Find a resource by attribute value within the tenant specified in the request context.
§Arguments
resource_type- The type of resource to searchattribute- The attribute name to search byvalue- The attribute value to search forcontext- Request context containing tenant information (if multi-tenant)
§Returns
The first matching resource, if found within the tenant scope
Sourcefn resource_exists(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<bool, Self::Error>> + Send
fn resource_exists( &self, resource_type: &str, id: &str, context: &RequestContext, ) -> impl Future<Output = Result<bool, Self::Error>> + Send
Check if a resource exists within the tenant specified in the request context.
§Arguments
resource_type- The type of resource to checkid- The unique identifier of the resourcecontext- Request context containing tenant information (if multi-tenant)
§Returns
True if the resource exists within the tenant scope, false otherwise
Provided Methods§
Sourcefn conditional_update(
&self,
resource_type: &str,
id: &str,
data: Value,
expected_version: &ScimVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<VersionedResource>, Self::Error>> + Sendwhere
Self: Sync,
fn conditional_update(
&self,
resource_type: &str,
id: &str,
data: Value,
expected_version: &ScimVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<VersionedResource>, Self::Error>> + Sendwhere
Self: Sync,
Conditionally update a resource if the version matches.
This operation will only succeed if the current resource version matches the expected version, preventing accidental overwriting of modified resources. This provides optimistic concurrency control for SCIM operations.
§ETag Concurrency Control
This method implements the core of ETag-based conditional operations:
- Fetches the current resource and its version
- Compares the current version with the expected version
- Only proceeds with the update if versions match
- Returns version conflict information if they don’t match
§Arguments
resource_type- The type of resource to updateid- The unique identifier of the resourcedata- The updated resource data as JSONexpected_version- The version the client expects the resource to havecontext- Request context containing tenant information
§Returns
Success(VersionedResource)- Update succeeded with new versionVersionMismatch(VersionConflict)- Resource was modified by another clientNotFound- Resource does not exist
§Default Implementation
The default implementation provides automatic conditional update support by checking the current resource version before performing the update. Providers can override this for more efficient implementations that perform version checking at the storage layer.
§Examples
use scim_server::resource::{
provider::ResourceProvider,
version::{ScimVersion, ConditionalResult},
conditional_provider::VersionedResource,
RequestContext,
};
use serde_json::json;
let context = RequestContext::with_generated_id();
let expected_version = ScimVersion::from_hash("abc123");
let update_data = json!({"userName": "new.name", "active": false});
match provider.conditional_update("User", "123", update_data, &expected_version, &context).await? {
ConditionalResult::Success(versioned_resource) => {
println!("Update successful, new version: {}",
versioned_resource.version().to_http_header());
},
ConditionalResult::VersionMismatch(conflict) => {
println!("Version conflict: expected {}, current {}",
conflict.expected, conflict.current);
},
ConditionalResult::NotFound => {
println!("Resource not found");
}
}Sourcefn conditional_delete(
&self,
resource_type: &str,
id: &str,
expected_version: &ScimVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<()>, Self::Error>> + Sendwhere
Self: Sync,
fn conditional_delete(
&self,
resource_type: &str,
id: &str,
expected_version: &ScimVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<()>, Self::Error>> + Sendwhere
Self: Sync,
Conditionally delete a resource if the version matches.
This operation will only succeed if the current resource version matches the expected version, preventing accidental deletion of modified resources. This is critical for maintaining data integrity in concurrent environments.
§ETag Concurrency Control
This method prevents accidental deletion of resources that have been modified by other clients:
- Fetches the current resource and its version
- Compares the current version with the expected version
- Only proceeds with the deletion if versions match
- Ensures the client is deleting the resource they intended to delete
§Arguments
resource_type- The type of resource to deleteid- The unique identifier of the resourceexpected_version- The version the client expects the resource to havecontext- Request context containing tenant information
§Returns
Success(())- Delete succeededVersionMismatch(VersionConflict)- Resource was modified by another clientNotFound- Resource does not exist
§Default Implementation
The default implementation provides automatic conditional delete support by checking the current resource version before performing the delete. Providers can override this for more efficient implementations that perform version checking at the storage layer.
§Examples
use scim_server::resource::{
provider::ResourceProvider,
version::{ScimVersion, ConditionalResult},
RequestContext,
};
let context = RequestContext::with_generated_id();
let expected_version = ScimVersion::from_hash("def456");
match provider.conditional_delete("User", "123", &expected_version, &context).await? {
ConditionalResult::Success(()) => {
println!("User deleted successfully");
},
ConditionalResult::VersionMismatch(conflict) => {
println!("Cannot delete: resource was modified. Expected {}, current {}",
conflict.expected, conflict.current);
},
ConditionalResult::NotFound => {
println!("User not found");
}
}Sourcefn get_versioned_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<Option<VersionedResource>, Self::Error>> + Sendwhere
Self: Sync,
fn get_versioned_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<Option<VersionedResource>, Self::Error>> + Sendwhere
Self: Sync,
Get a resource with its version information.
This is a convenience method that returns both the resource and its version
information wrapped in a VersionedResource. This is useful when you need
both the resource data and its version for subsequent conditional operations.
The default implementation calls the existing get_resource method and
automatically wraps the result in a VersionedResource with a computed version.
§Arguments
resource_type- The type of resource to retrieveid- The unique identifier of the resourcecontext- Request context containing tenant information
§Returns
The versioned resource if found, None if not found
§Examples
use scim_server::resource::{
provider::ResourceProvider,
RequestContext,
};
let context = RequestContext::with_generated_id();
if let Some(versioned_resource) = provider.get_versioned_resource("User", "123", &context).await? {
println!("Resource ID: {}", versioned_resource.resource().get_id().unwrap_or("unknown"));
println!("Resource version: {}", versioned_resource.version().to_http_header());
// Can use the version for subsequent conditional operations
let current_version = versioned_resource.version().clone();
// ... use current_version for conditional_update or conditional_delete
}Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.