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 {}