Expand description
Versioned resource types for SCIM resource versioning.
This module provides types for handling versioned SCIM resources that support conditional operations with version control. As of Phase 3, conditional operations are mandatory and built into the core ResourceProvider trait, ensuring all providers support ETag-based concurrency control.
§Mandatory Conditional Operations Architecture
The SCIM server library now requires all ResourceProvider implementations to support conditional operations. This design decision provides:
- Universal Concurrency Control: All resources automatically support ETag versioning
- Simplified Architecture: Single code path with consistent behavior
- Type Safety: Compile-time guarantees for version-aware operations
- Production Readiness: Built-in protection against lost updates
§Core Types
VersionedResource- Resource wrapper that includes automatic version computation
§Usage with Mandatory Conditional Operations
use scim_server::resource::{
provider::ResourceProvider,
conditional_provider::VersionedResource,
version::{ScimVersion, ConditionalResult},
core::{Resource, RequestContext},
};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug)]
struct MyError(String);
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for MyError {}
#[derive(Clone)]
struct MyProvider {
data: Arc<RwLock<HashMap<String, VersionedResource>>>,
}
impl ResourceProvider for MyProvider {
type Error = MyError;
// All providers must implement these core CRUD methods
async fn create_resource(&self, resource_type: &str, data: Value, context: &RequestContext) -> Result<Resource, Self::Error> {
let resource = Resource::from_json(resource_type.to_string(), data)
.map_err(|e| MyError(e.to_string()))?;
let mut store = self.data.write().await;
let id = resource.get_id().unwrap_or("generated-id").to_string();
let versioned = VersionedResource::new(resource.clone());
store.insert(id, versioned);
Ok(resource)
}
async fn get_resource(&self, _resource_type: &str, id: &str, _context: &RequestContext) -> Result<Option<Resource>, Self::Error> {
let store = self.data.read().await;
Ok(store.get(id).map(|v| v.resource().clone()))
}
// ... implement other required methods ...
// Conditional operations are MANDATORY - provided by default with automatic implementation
// Override these methods for optimized conditional operations at the storage layer:
// async fn conditional_update(&self, resource_type: &str, id: &str, data: Value,
// expected_version: &ScimVersion, context: &RequestContext)
// -> Result<ConditionalResult<VersionedResource>, Self::Error> {
// // Your database-level conditional update with version checking
// }
//
// async fn conditional_delete(&self, resource_type: &str, id: &str,
// expected_version: &ScimVersion, context: &RequestContext)
// -> Result<ConditionalResult<()>, Self::Error> {
// // Your database-level conditional delete with version checking
// }
}§Architectural Benefits
Making conditional operations mandatory provides several advantages:
§Simplified Codebase
- Single code path for all operations
- No optional/conditional provider detection
- Consistent behavior across all implementations
§Enhanced Type Safety
- Compile-time guarantees for version support
- No runtime checks for capability detection
- Clear API contracts for all providers
§Production Readiness
- Built-in concurrency control for all resources
- Automatic protection against lost updates
- Enterprise-grade data integrity guarantees
§Developer Experience
- Consistent APIs across all providers
- Clear documentation and examples
- Better IDE support and tooling
Structs§
- Versioned
Resource - A resource with its associated version information.