scim_server/providers/
provider.rs

1//! Resource provider trait for implementing SCIM data access.
2//!
3//! This module defines the core trait that users must implement to provide
4//! data storage and retrieval for SCIM resources. Supports both single-tenant
5//! and multi-tenant operations with optional version-aware concurrency control.
6//!
7//! # Key Types
8//!
9//! - [`ResourceProvider`] - Main trait for implementing storage backends
10//!
11//! # Examples
12//!
13//! ```rust
14//! use scim_server::providers::ResourceProvider;
15//!
16//! struct MyProvider;
17//! // Implement ResourceProvider for your storage backend
18//! ```
19
20use crate::resource::{
21    ListQuery, RequestContext, version::RawVersion, versioned::VersionedResource,
22};
23use serde_json::Value;
24use std::future::Future;
25
26/// Unified resource provider trait supporting both single and multi-tenant operations.
27///
28/// This trait provides a unified interface for SCIM resource operations that works
29/// for both single-tenant and multi-tenant scenarios:
30///
31/// - **Single-tenant**: Operations use RequestContext with tenant_context = None
32/// - **Multi-tenant**: Operations use RequestContext with tenant_context = Some(...)
33///
34/// The provider implementation can check `context.tenant_id()` to determine
35/// the effective tenant for the operation.
36///
37/// # Version Control
38///
39/// All operations support optional version control through the `expected_version` parameter.
40/// When provided, operations perform optimistic concurrency control to prevent lost updates.
41/// When `None`, operations proceed without version checking.
42pub trait ResourceProvider {
43    /// Error type returned by all provider operations
44    type Error: std::error::Error + Send + Sync + 'static;
45
46    /// Create a resource for the tenant specified in the request context.
47    ///
48    /// # Arguments
49    /// * `resource_type` - The type of resource to create (e.g., "User", "Group")
50    /// * `data` - The resource data as JSON
51    /// * `context` - Request context containing tenant information (if multi-tenant)
52    ///
53    /// # Returns
54    /// The created resource with version information and any server-generated fields
55    ///
56    /// # Tenant Handling
57    /// - Single-tenant: `context.tenant_id()` returns `None`
58    /// - Multi-tenant: `context.tenant_id()` returns `Some(tenant_id)`
59    fn create_resource(
60        &self,
61        resource_type: &str,
62        data: Value,
63        context: &RequestContext,
64    ) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send;
65
66    /// Get a resource by ID from the tenant specified in the request context.
67    ///
68    /// # Arguments
69    /// * `resource_type` - The type of resource to retrieve
70    /// * `id` - The unique identifier of the resource
71    /// * `context` - Request context containing tenant information (if multi-tenant)
72    ///
73    /// # Returns
74    /// The resource with version information if found, None if not found within the tenant scope
75    fn get_resource(
76        &self,
77        resource_type: &str,
78        id: &str,
79        context: &RequestContext,
80    ) -> impl Future<Output = Result<Option<VersionedResource>, Self::Error>> + Send;
81
82    /// Update a resource in the tenant specified in the request context.
83    ///
84    /// Supports both regular updates and conditional updates with version checking.
85    /// When `expected_version` is provided, performs optimistic concurrency control.
86    ///
87    /// # Arguments
88    /// * `resource_type` - The type of resource to update
89    /// * `id` - The unique identifier of the resource
90    /// * `data` - The updated resource data as JSON
91    /// * `expected_version` - Optional expected version for conditional updates
92    /// * `context` - Request context containing tenant information (if multi-tenant)
93    ///
94    /// # Returns
95    /// - When `expected_version` is `None`: `Ok(VersionedResource)` with the updated resource
96    /// - When `expected_version` is `Some`: `Ok(VersionedResource)` on success, or error on version mismatch
97    fn update_resource(
98        &self,
99        resource_type: &str,
100        id: &str,
101        data: Value,
102        expected_version: Option<&RawVersion>,
103        context: &RequestContext,
104    ) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send;
105
106    /// Delete a resource from the tenant specified in the request context.
107    ///
108    /// Supports both regular deletes and conditional deletes with version checking.
109    /// When `expected_version` is provided, performs optimistic concurrency control.
110    ///
111    /// # Arguments
112    /// * `resource_type` - The type of resource to delete
113    /// * `id` - The unique identifier of the resource
114    /// * `expected_version` - Optional expected version for conditional deletes
115    /// * `context` - Request context containing tenant information (if multi-tenant)
116    ///
117    /// # Returns
118    /// - When `expected_version` is `None`: `Ok(())` on successful deletion
119    /// - When `expected_version` is `Some`: `Ok(())` on success, or error on version mismatch
120    fn delete_resource(
121        &self,
122        resource_type: &str,
123        id: &str,
124        expected_version: Option<&RawVersion>,
125        context: &RequestContext,
126    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
127
128    /// List resources from the tenant specified in the request context.
129    ///
130    /// # Arguments
131    /// * `resource_type` - The type of resources to list
132    /// * `query` - Optional query parameters for filtering, sorting, pagination
133    /// * `context` - Request context containing tenant information (if multi-tenant)
134    ///
135    /// # Returns
136    /// A vector of resources with version information from the specified tenant
137    fn list_resources(
138        &self,
139        resource_type: &str,
140        _query: Option<&ListQuery>,
141        context: &RequestContext,
142    ) -> impl Future<Output = Result<Vec<VersionedResource>, Self::Error>> + Send;
143
144    /// Find resources by attribute value within the tenant specified in the request context.
145    ///
146    /// # Arguments
147    /// * `resource_type` - The type of resources to search
148    /// * `attribute_name` - The attribute name to search by (e.g., "userName")
149    /// * `attribute_value` - The attribute value to match
150    /// * `context` - Request context containing tenant information (if multi-tenant)
151    ///
152    /// # Returns
153    /// A vector of matching resources with version information from the specified tenant
154    fn find_resources_by_attribute(
155        &self,
156        resource_type: &str,
157        attribute_name: &str,
158        attribute_value: &str,
159        context: &RequestContext,
160    ) -> impl Future<Output = Result<Vec<VersionedResource>, Self::Error>> + Send;
161
162    /// Apply a PATCH operation to a resource.
163    ///
164    /// Supports both regular patches and conditional patches with version checking.
165    /// When `expected_version` is provided, performs optimistic concurrency control.
166    ///
167    /// # Arguments
168    /// * `resource_type` - The type of resource to patch
169    /// * `id` - The unique identifier of the resource
170    /// * `patch_request` - The PATCH operations as JSON
171    /// * `expected_version` - Optional expected version for conditional patches
172    /// * `context` - Request context containing tenant information
173    ///
174    /// # Returns
175    /// - When `expected_version` is `None`: `Ok(VersionedResource)` with the patched resource
176    /// - When `expected_version` is `Some`: `Ok(VersionedResource)` on success, or error on version mismatch
177    fn patch_resource(
178        &self,
179        resource_type: &str,
180        id: &str,
181        patch_request: &Value,
182        expected_version: Option<&RawVersion>,
183        context: &RequestContext,
184    ) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send;
185
186    /// Check if a resource exists within the tenant specified in the request context.
187    ///
188    /// # Arguments
189    /// * `resource_type` - The type of resource to check
190    /// * `id` - The unique identifier of the resource
191    /// * `context` - Request context containing tenant information (if multi-tenant)
192    ///
193    /// # Returns
194    /// True if the resource exists in the tenant scope, false otherwise
195    fn resource_exists(
196        &self,
197        resource_type: &str,
198        id: &str,
199        context: &RequestContext,
200    ) -> impl Future<Output = Result<bool, Self::Error>> + Send;
201}
202
203/// Extension trait providing convenience methods for ResourceProvider implementations.
204///
205/// This trait provides single-tenant convenience methods that automatically create
206/// the appropriate RequestContext for common scenarios.
207pub trait ResourceProviderExt: ResourceProvider {
208    /// Convenience method for single-tenant resource creation.
209    ///
210    /// Creates a RequestContext with no tenant information and calls create_resource.
211    fn create_single_tenant(
212        &self,
213        resource_type: &str,
214        data: Value,
215        request_id: Option<String>,
216    ) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send
217    where
218        Self: Sync,
219    {
220        async move {
221            let context = match request_id {
222                Some(id) => RequestContext::new(id),
223                None => RequestContext::with_generated_id(),
224            };
225
226            self.create_resource(resource_type, data, &context).await
227        }
228    }
229
230    /// Convenience method for single-tenant resource retrieval.
231    fn get_single_tenant(
232        &self,
233        resource_type: &str,
234        id: &str,
235        request_id: Option<String>,
236    ) -> impl Future<Output = Result<Option<VersionedResource>, Self::Error>> + Send
237    where
238        Self: Sync,
239    {
240        async move {
241            let context = match request_id {
242                Some(id) => RequestContext::new(id),
243                None => RequestContext::with_generated_id(),
244            };
245
246            self.get_resource(resource_type, id, &context).await
247        }
248    }
249
250    /// Convenience method for single-tenant resource update.
251    fn update_single_tenant(
252        &self,
253        resource_type: &str,
254        id: &str,
255        data: Value,
256        expected_version: Option<&RawVersion>,
257        request_id: Option<String>,
258    ) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send
259    where
260        Self: Sync,
261    {
262        async move {
263            let context = match request_id {
264                Some(id) => RequestContext::new(id),
265                None => RequestContext::with_generated_id(),
266            };
267
268            self.update_resource(resource_type, id, data, expected_version, &context)
269                .await
270        }
271    }
272
273    /// Convenience method for single-tenant resource deletion.
274    fn delete_single_tenant(
275        &self,
276        resource_type: &str,
277        id: &str,
278        expected_version: Option<&RawVersion>,
279        request_id: Option<String>,
280    ) -> impl Future<Output = Result<(), Self::Error>> + Send
281    where
282        Self: Sync,
283    {
284        async move {
285            let context = match request_id {
286                Some(id) => RequestContext::new(id),
287                None => RequestContext::with_generated_id(),
288            };
289
290            self.delete_resource(resource_type, id, expected_version, &context)
291                .await
292        }
293    }
294}
295
296// Automatically implement ResourceProviderExt for all ResourceProvider implementations
297impl<T: ResourceProvider> ResourceProviderExt for T {}