scim_server/providers/standard/
standard.rs

1//! Standard resource provider implementation with pluggable storage.
2//!
3//! This module provides a production-ready implementation of the ResourceProvider
4//! trait that separates SCIM protocol logic from storage concerns through the
5//! StorageProvider interface.
6//!
7//! # Features
8//!
9//! * Pluggable storage backends through the StorageProvider trait
10//! * Complete SCIM protocol logic preservation
11//! * Automatic tenant isolation when tenant context is provided
12//! * Fallback to "default" tenant for single-tenant operations
13//! * Comprehensive error handling
14//! * Resource metadata tracking (created/updated timestamps)
15//! * Duplicate detection for userName attributes
16//!
17//! # Example Usage
18//!
19//! ```rust
20//! use scim_server::providers::StandardResourceProvider;
21//! use scim_server::storage::InMemoryStorage;
22//! use scim_server::resource::{RequestContext, TenantContext};
23//! use scim_server::providers::ResourceProvider;
24//! use serde_json::json;
25//!
26//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
27//! let storage = InMemoryStorage::new();
28//! let provider = StandardResourceProvider::new(storage);
29//!
30//! // Single-tenant operation
31//! let single_context = RequestContext::with_generated_id();
32//! let user_data = json!({
33//!     "userName": "john.doe",
34//!     "displayName": "John Doe"
35//! });
36//! let user = provider.create_resource("User", user_data.clone(), &single_context).await?;
37//!
38//! // Multi-tenant operation
39//! let tenant_context = TenantContext::new("tenant1".to_string(), "client1".to_string());
40//! let multi_context = RequestContext::with_tenant_generated_id(tenant_context);
41//! let tenant_user = provider.create_resource("User", user_data, &multi_context).await?;
42//! # Ok(())
43//! # }
44//! ```
45
46use crate::providers::ProviderError;
47use crate::providers::ResourceProvider;
48use crate::providers::helpers::{
49    metadata::ScimMetadataManager, patch::ScimPatchOperations, tenant::MultiTenantProvider,
50};
51use crate::resource::{
52    ListQuery, RequestContext, Resource, version::RawVersion, versioned::VersionedResource,
53};
54use crate::storage::ProviderStats;
55use crate::storage::{StorageKey, StorageProvider};
56use log::{debug, info, trace, warn};
57use serde_json::{Value, json};
58
59/// Standard resource provider with pluggable storage backend.
60///
61/// This provider separates SCIM protocol logic from storage concerns by delegating
62/// data persistence to a StorageProvider implementation while handling all SCIM-specific
63/// business logic, validation, and metadata management.
64#[derive(Debug, Clone)]
65pub struct StandardResourceProvider<S: StorageProvider> {
66    // Pluggable storage backend
67    storage: S,
68}
69
70impl<S: StorageProvider> StandardResourceProvider<S> {
71    /// Create a new standard provider with the given storage backend.
72    pub fn new(storage: S) -> Self {
73        Self { storage }
74    }
75
76    /// Check for duplicate userName in User resources within the same tenant.
77    async fn check_username_duplicate(
78        &self,
79        tenant_id: &str,
80        username: &str,
81        exclude_id: Option<&str>,
82    ) -> Result<(), ProviderError> {
83        let prefix = StorageKey::prefix(tenant_id, "User");
84        let matches = self
85            .storage
86            .find_by_attribute(prefix, "userName", username)
87            .await
88            .map_err(|e| ProviderError::Internal {
89                message: format!("Storage error during username check: {}", e),
90            })?;
91
92        for (key, _data) in matches {
93            // Skip the resource we're updating
94            if Some(key.resource_id()) != exclude_id {
95                return Err(ProviderError::DuplicateAttribute {
96                    resource_type: "User".to_string(),
97                    attribute: "userName".to_string(),
98                    value: username.to_string(),
99                    tenant_id: tenant_id.to_string(),
100                });
101            }
102        }
103
104        Ok(())
105    }
106
107    /// Clear all data from storage.
108    ///
109    /// Removes all resources from all tenants by delegating to the storage backend's
110    /// clear operation. This method provides a consistent interface for clearing data
111    /// regardless of the underlying storage implementation.
112    ///
113    /// # Behavior
114    ///
115    /// - Delegates to [`StorageProvider::clear`] for actual data removal
116    /// - Logs warnings if the clear operation fails
117    /// - Primarily intended for testing scenarios
118    /// - After successful clearing, [`get_stats`] should report zero resources
119    ///
120    /// # Examples
121    ///
122    /// ```rust
123    /// use scim_server::providers::StandardResourceProvider;
124    /// use scim_server::storage::InMemoryStorage;
125    ///
126    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
127    /// let storage = InMemoryStorage::new();
128    /// let provider = StandardResourceProvider::new(storage);
129    ///
130    /// // ... create some resources ...
131    /// provider.clear().await;
132    ///
133    /// let stats = provider.get_stats().await;
134    /// assert_eq!(stats.total_resources, 0);
135    /// # Ok(())
136    /// # }
137    /// ```
138    ///
139    /// [`StorageProvider::clear`]: crate::storage::StorageProvider::clear
140    /// [`get_stats`]: Self::get_stats
141    pub async fn clear(&self) {
142        // Delegate to storage backend for proper clearing
143        if let Err(e) = self.storage.clear().await {
144            warn!("Failed to clear storage: {:?}", e);
145        }
146    }
147
148    /// Get comprehensive statistics about stored data across all tenants.
149    ///
150    /// Dynamically discovers all tenants and resource types from storage to provide
151    /// accurate statistics without relying on hardcoded patterns. This method uses
152    /// the storage provider's discovery capabilities to enumerate actual data.
153    ///
154    /// # Returns
155    ///
156    /// [`ProviderStats`] containing:
157    /// - `tenant_count`: Number of tenants with at least one resource
158    /// - `total_resources`: Sum of all resources across all tenants and types
159    /// - `resource_type_count`: Number of distinct resource types found
160    /// - `resource_types`: List of all resource type names
161    ///
162    /// # Errors
163    ///
164    /// This method handles storage errors gracefully by using default values
165    /// (empty collections) when discovery operations fail.
166    ///
167    /// # Examples
168    ///
169    /// ```rust
170    /// use scim_server::providers::StandardResourceProvider;
171    /// use scim_server::storage::InMemoryStorage;
172    /// use scim_server::resource::{RequestContext, TenantContext};
173    ///
174    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
175    /// let storage = InMemoryStorage::new();
176    /// let provider = StandardResourceProvider::new(storage);
177    ///
178    /// // ... create resources in multiple tenants ...
179    ///
180    /// let stats = provider.get_stats().await;
181    /// println!("Total resources: {}", stats.total_resources);
182    /// println!("Active tenants: {}", stats.tenant_count);
183    /// println!("Resource types: {:?}", stats.resource_types);
184    /// # Ok(())
185    /// # }
186    /// ```
187    pub async fn get_stats(&self) -> ProviderStats {
188        // Dynamically discover all tenants and resource types from storage
189        let tenants = self.storage.list_tenants().await.unwrap_or_default();
190        let resource_types = self
191            .storage
192            .list_all_resource_types()
193            .await
194            .unwrap_or_default();
195
196        let mut total_resources = 0;
197
198        // Count total resources across all tenants and resource types
199        for tenant_id in &tenants {
200            for resource_type in &resource_types {
201                let prefix = StorageKey::prefix(tenant_id, resource_type);
202                if let Ok(count) = self.storage.count(prefix).await {
203                    total_resources += count;
204                }
205            }
206        }
207
208        ProviderStats {
209            tenant_count: tenants.len(),
210            total_resources,
211            resource_type_count: resource_types.len(),
212            resource_types,
213        }
214    }
215
216    /// List all resources of a specific type in a tenant.
217    pub async fn list_resources_in_tenant(
218        &self,
219        tenant_id: &str,
220        resource_type: &str,
221    ) -> Vec<Resource> {
222        let prefix = StorageKey::prefix(tenant_id, resource_type);
223        match self.storage.list(prefix, 0, usize::MAX).await {
224            Ok(storage_results) => {
225                let mut resources = Vec::new();
226                for (_key, data) in storage_results {
227                    match Resource::from_json(resource_type.to_string(), data) {
228                        Ok(resource) => resources.push(resource),
229                        Err(e) => {
230                            warn!(
231                                "Failed to deserialize resource in list_resources_in_tenant: {}",
232                                e
233                            );
234                        }
235                    }
236                }
237                resources
238            }
239            Err(e) => {
240                warn!("Storage error in list_resources_in_tenant: {}", e);
241                Vec::new()
242            }
243        }
244    }
245
246    /// Count resources of a specific type for a tenant (used for limit checking).
247    async fn count_resources_for_tenant(&self, tenant_id: &str, resource_type: &str) -> usize {
248        let prefix = StorageKey::prefix(tenant_id, resource_type);
249        match self.storage.count(prefix).await {
250            Ok(count) => count,
251            Err(e) => {
252                warn!("Storage error in count_resources_for_tenant: {}", e);
253                0
254            }
255        }
256    }
257}
258
259// Note: No Default implementation for StandardResourceProvider as it requires storage parameter
260
261// Reuse error and stats types from the in_memory module for compatibility
262
263// Helper traits are automatically implemented via blanket implementations
264// since StandardResourceProvider implements ResourceProvider and ProviderError implements From<String>
265
266impl<S: StorageProvider> ResourceProvider for StandardResourceProvider<S> {
267    type Error = ProviderError;
268
269    async fn create_resource(
270        &self,
271        resource_type: &str,
272        mut data: Value,
273        context: &RequestContext,
274    ) -> Result<VersionedResource, Self::Error> {
275        let tenant_id = self.effective_tenant_id(context);
276
277        info!(
278            "Creating {} resource for tenant '{}' (request: '{}')",
279            resource_type, tenant_id, context.request_id
280        );
281        trace!(
282            "Create data: {}",
283            serde_json::to_string(&data).unwrap_or_else(|_| "invalid json".to_string())
284        );
285
286        // Check permissions first
287        context
288            .validate_operation("create")
289            .map_err(|e| ProviderError::Internal { message: e })?;
290
291        // Check resource limits if this is a multi-tenant context
292        if let Some(tenant_context) = &context.tenant_context {
293            if resource_type == "User" {
294                if let Some(max_users) = tenant_context.permissions.max_users {
295                    let current_count = self.count_resources_for_tenant(&tenant_id, "User").await;
296                    if current_count >= max_users {
297                        return Err(ProviderError::Internal {
298                            message: format!(
299                                "User limit exceeded: {}/{}",
300                                current_count, max_users
301                            ),
302                        });
303                    }
304                }
305            } else if resource_type == "Group" {
306                if let Some(max_groups) = tenant_context.permissions.max_groups {
307                    let current_count = self.count_resources_for_tenant(&tenant_id, "Group").await;
308                    if current_count >= max_groups {
309                        return Err(ProviderError::Internal {
310                            message: format!(
311                                "Group limit exceeded: {}/{}",
312                                current_count, max_groups
313                            ),
314                        });
315                    }
316                }
317            }
318        }
319
320        // Generate ID if not provided
321        if data.get("id").is_none() {
322            let id = self.generate_tenant_resource_id(&tenant_id, resource_type);
323            if let Some(obj) = data.as_object_mut() {
324                obj.insert("id".to_string(), json!(id));
325            }
326        }
327
328        // Create resource
329        let resource = Resource::from_json(resource_type.to_string(), data).map_err(|e| {
330            ProviderError::InvalidData {
331                message: format!("Failed to create resource: {}", e),
332            }
333        })?;
334
335        // Check for duplicate userName if this is a User resource
336        if resource_type == "User" {
337            if let Some(username) = resource.get_username() {
338                self.check_username_duplicate(&tenant_id, username, None)
339                    .await?;
340            }
341        }
342
343        // Add metadata using ScimMetadataManager trait
344        let mut resource_with_meta = resource;
345        self.add_creation_metadata(&mut resource_with_meta, "https://example.com/scim/v2")
346            .map_err(|e| ProviderError::Internal {
347                message: format!("Failed to add metadata: {}", e),
348            })?;
349        let resource_id = resource_with_meta.get_id().unwrap_or("unknown").to_string();
350
351        // Store resource using storage provider
352        let key = StorageKey::new(&tenant_id, resource_type, &resource_id);
353        let stored_data = self
354            .storage
355            .put(
356                key,
357                resource_with_meta
358                    .to_json()
359                    .map_err(|e| ProviderError::Internal {
360                        message: format!("Failed to serialize resource: {}", e),
361                    })?,
362            )
363            .await
364            .map_err(|e| ProviderError::Internal {
365                message: format!("Storage error during create: {}", e),
366            })?;
367
368        // Return the resource as stored, wrapped in VersionedResource
369        let resource =
370            Resource::from_json(resource_type.to_string(), stored_data).map_err(|e| {
371                ProviderError::InvalidData {
372                    message: format!("Failed to deserialize stored resource: {}", e),
373                }
374            })?;
375
376        Ok(VersionedResource::new(resource))
377    }
378
379    async fn get_resource(
380        &self,
381        resource_type: &str,
382        id: &str,
383        context: &RequestContext,
384    ) -> Result<Option<VersionedResource>, Self::Error> {
385        let tenant_id = self.effective_tenant_id(context);
386
387        debug!(
388            "Getting {} resource with ID '{}' for tenant '{}' (request: '{}')",
389            resource_type, id, tenant_id, context.request_id
390        );
391
392        // Check permissions first
393        context
394            .validate_operation("read")
395            .map_err(|e| ProviderError::Internal { message: e })?;
396
397        let key = StorageKey::new(&tenant_id, resource_type, id);
398        let resource_data = self
399            .storage
400            .get(key)
401            .await
402            .map_err(|e| ProviderError::Internal {
403                message: format!("Storage error during get: {}", e),
404            })?;
405
406        let resource = match resource_data {
407            Some(data) => {
408                let resource =
409                    Resource::from_json(resource_type.to_string(), data).map_err(|e| {
410                        ProviderError::InvalidData {
411                            message: format!("Failed to deserialize resource: {}", e),
412                        }
413                    })?;
414                trace!("Resource found and returned");
415                Some(VersionedResource::new(resource))
416            }
417            None => {
418                debug!("Resource not found");
419                None
420            }
421        };
422
423        Ok(resource)
424    }
425
426    async fn update_resource(
427        &self,
428        resource_type: &str,
429        id: &str,
430        mut data: Value,
431        expected_version: Option<&RawVersion>,
432        context: &RequestContext,
433    ) -> Result<VersionedResource, Self::Error> {
434        let tenant_id = self.effective_tenant_id(context);
435
436        info!(
437            "Updating {} resource with ID '{}' for tenant '{}' (request: '{}')",
438            resource_type, id, tenant_id, context.request_id
439        );
440        trace!(
441            "Update data: {}",
442            serde_json::to_string(&data).unwrap_or_else(|_| "invalid json".to_string())
443        );
444
445        // Check permissions first
446        context
447            .validate_operation("update")
448            .map_err(|e| ProviderError::Internal { message: e })?;
449
450        // Handle version checking if expected_version is provided
451        if let Some(expected_version) = expected_version {
452            // Get current resource to check version
453            let key = StorageKey::new(&tenant_id, resource_type, id);
454            match self.storage.get(key.clone()).await {
455                Ok(Some(current_data)) => {
456                    // Parse current resource to extract version
457                    let current_resource =
458                        Resource::from_json(resource_type.to_string(), current_data.clone())
459                            .map_err(|e| ProviderError::InvalidInput {
460                                message: format!("Failed to deserialize stored resource: {}", e),
461                            })?;
462
463                    // Check if version matches
464                    let current_version = VersionedResource::new(current_resource.clone())
465                        .version()
466                        .clone();
467
468                    if &current_version != expected_version {
469                        return Err(ProviderError::PreconditionFailed {
470                            message: format!(
471                                "Version mismatch: expected {}, got {}",
472                                expected_version.as_str(),
473                                current_version.as_str()
474                            ),
475                        });
476                    }
477                }
478                Ok(None) => {
479                    return Err(ProviderError::NotFound {
480                        resource_type: resource_type.to_string(),
481                        id: id.to_string(),
482                    });
483                }
484                Err(_) => {
485                    return Err(ProviderError::Internal {
486                        message: "Failed to retrieve resource for version check".to_string(),
487                    });
488                }
489            }
490        }
491
492        // Ensure ID is set correctly
493        if let Some(obj) = data.as_object_mut() {
494            obj.insert("id".to_string(), json!(id));
495        }
496
497        // Create updated resource
498        let resource = Resource::from_json(resource_type.to_string(), data).map_err(|e| {
499            ProviderError::InvalidData {
500                message: format!("Failed to update resource: {}", e),
501            }
502        })?;
503
504        // Check for duplicate userName if this is a User resource
505        if resource_type == "User" {
506            if let Some(username) = resource.get_username() {
507                self.check_username_duplicate(&tenant_id, username, Some(id))
508                    .await?;
509            }
510        }
511
512        // Verify resource exists using storage provider
513        let key = StorageKey::new(&tenant_id, resource_type, id);
514        let exists =
515            self.storage
516                .exists(key.clone())
517                .await
518                .map_err(|e| ProviderError::Internal {
519                    message: format!("Storage error during existence check: {}", e),
520                })?;
521
522        if !exists {
523            return Err(ProviderError::ResourceNotFound {
524                resource_type: resource_type.to_string(),
525                id: id.to_string(),
526                tenant_id,
527            });
528        }
529
530        // Add metadata using ScimMetadataManager trait (preserve created time, update modified time)
531        let mut resource_with_meta = resource;
532        self.update_modification_metadata(&mut resource_with_meta)
533            .map_err(|e| ProviderError::Internal {
534                message: format!("Failed to update metadata: {}", e),
535            })?;
536
537        // Store updated resource using storage provider
538        let stored_data = self
539            .storage
540            .put(
541                key,
542                resource_with_meta
543                    .to_json()
544                    .map_err(|e| ProviderError::Internal {
545                        message: format!("Failed to serialize resource: {}", e),
546                    })?,
547            )
548            .await
549            .map_err(|e| ProviderError::Internal {
550                message: format!("Storage error during update: {}", e),
551            })?;
552
553        // Return the updated resource as stored, wrapped in VersionedResource
554        let resource =
555            Resource::from_json(resource_type.to_string(), stored_data).map_err(|e| {
556                ProviderError::InvalidData {
557                    message: format!("Failed to deserialize updated resource: {}", e),
558                }
559            })?;
560
561        Ok(VersionedResource::new(resource))
562    }
563
564    async fn delete_resource(
565        &self,
566        resource_type: &str,
567        id: &str,
568        expected_version: Option<&RawVersion>,
569        context: &RequestContext,
570    ) -> Result<(), Self::Error> {
571        let tenant_id = self.effective_tenant_id(context);
572
573        info!(
574            "Deleting {} resource with ID '{}' for tenant '{}' (request: '{}')",
575            resource_type, id, tenant_id, context.request_id
576        );
577
578        // Check permissions first
579        context
580            .validate_operation("delete")
581            .map_err(|e| ProviderError::Internal { message: e })?;
582
583        // Handle version checking if expected_version is provided
584        if let Some(expected_version) = expected_version {
585            // Get current resource to check version
586            let key = StorageKey::new(&tenant_id, resource_type, id);
587            match self.storage.get(key.clone()).await {
588                Ok(Some(current_data)) => {
589                    // Parse current resource to extract version
590                    let current_resource =
591                        Resource::from_json(resource_type.to_string(), current_data.clone())
592                            .map_err(|e| ProviderError::InvalidInput {
593                                message: format!("Failed to deserialize stored resource: {}", e),
594                            })?;
595
596                    // Check if version matches
597                    let current_version = VersionedResource::new(current_resource.clone())
598                        .version()
599                        .clone();
600
601                    if &current_version != expected_version {
602                        return Err(ProviderError::PreconditionFailed {
603                            message: format!(
604                                "Version mismatch: expected {}, got {}",
605                                expected_version.as_str(),
606                                current_version.as_str()
607                            ),
608                        });
609                    }
610                }
611                Ok(None) => {
612                    return Err(ProviderError::NotFound {
613                        resource_type: resource_type.to_string(),
614                        id: id.to_string(),
615                    });
616                }
617                Err(_) => {
618                    return Err(ProviderError::Internal {
619                        message: "Failed to retrieve resource for version check".to_string(),
620                    });
621                }
622            }
623        }
624
625        // Delete resource using storage provider
626        let key = StorageKey::new(&tenant_id, resource_type, id);
627        let removed = self
628            .storage
629            .delete(key)
630            .await
631            .map_err(|e| ProviderError::Internal {
632                message: format!("Storage error during delete: {}", e),
633            })?;
634
635        if !removed {
636            warn!(
637                "Attempted to delete non-existent {} resource with ID '{}' for tenant '{}'",
638                resource_type, id, tenant_id
639            );
640            return Err(ProviderError::ResourceNotFound {
641                resource_type: resource_type.to_string(),
642                id: id.to_string(),
643                tenant_id,
644            });
645        }
646
647        debug!(
648            "Successfully deleted {} resource with ID '{}' for tenant '{}'",
649            resource_type, id, tenant_id
650        );
651        Ok(())
652    }
653
654    async fn list_resources(
655        &self,
656        resource_type: &str,
657        query: Option<&ListQuery>,
658        context: &RequestContext,
659    ) -> Result<Vec<VersionedResource>, Self::Error> {
660        let tenant_id = self.effective_tenant_id(context);
661
662        debug!(
663            "Listing {} resources for tenant '{}' (request: '{}')",
664            resource_type, tenant_id, context.request_id
665        );
666
667        // Check permissions first
668        context
669            .validate_operation("list")
670            .map_err(|e| ProviderError::Internal { message: e })?;
671
672        // List resources using storage provider
673        let prefix = StorageKey::prefix(&tenant_id, resource_type);
674        let storage_results = self
675            .storage
676            .list(prefix, 0, usize::MAX) // Get all resources for now, apply pagination later
677            .await
678            .map_err(|e| ProviderError::Internal {
679                message: format!("Storage error during list: {}", e),
680            })?;
681
682        // Convert storage results to VersionedResource objects
683        let mut resources = Vec::new();
684        for (_key, data) in storage_results {
685            match Resource::from_json(resource_type.to_string(), data) {
686                Ok(resource) => resources.push(VersionedResource::new(resource)),
687                Err(e) => {
688                    warn!("Failed to deserialize resource during list: {}", e);
689                    // Continue with other resources instead of failing entirely
690                }
691            }
692        }
693
694        // Apply simple filtering and pagination if query is provided
695        let mut filtered_resources = resources;
696
697        if let Some(q) = query {
698            // Apply start_index and count for pagination
699            if let Some(start_index) = q.start_index {
700                let start = (start_index.saturating_sub(1)) as usize; // SCIM uses 1-based indexing
701                if start < filtered_resources.len() {
702                    filtered_resources = filtered_resources.into_iter().skip(start).collect();
703                } else {
704                    filtered_resources = Vec::new();
705                }
706            }
707
708            if let Some(count) = q.count {
709                filtered_resources.truncate(count as usize);
710            }
711        }
712
713        debug!(
714            "Found {} {} resources for tenant '{}' (after filtering)",
715            filtered_resources.len(),
716            resource_type,
717            tenant_id
718        );
719
720        Ok(filtered_resources)
721    }
722
723    async fn find_resources_by_attribute(
724        &self,
725        resource_type: &str,
726        attribute_name: &str,
727        attribute_value: &str,
728        context: &RequestContext,
729    ) -> Result<Vec<VersionedResource>, Self::Error> {
730        let tenant_id = self.effective_tenant_id(context);
731
732        // Find resource by attribute using storage provider
733        let prefix = StorageKey::prefix(&tenant_id, resource_type);
734
735        let matches = self
736            .storage
737            .find_by_attribute(prefix, attribute_name, attribute_value)
738            .await
739            .map_err(|e| ProviderError::Internal {
740                message: format!("Storage error during find by attribute: {}", e),
741            })?;
742
743        // Return all matches as VersionedResources
744        let mut results = Vec::new();
745        for (_key, data) in matches {
746            match Resource::from_json(resource_type.to_string(), data) {
747                Ok(resource) => results.push(VersionedResource::new(resource)),
748                Err(e) => {
749                    warn!("Failed to deserialize resource during find: {}", e);
750                    continue;
751                }
752            }
753        }
754
755        Ok(results)
756    }
757
758    async fn patch_resource(
759        &self,
760        resource_type: &str,
761        id: &str,
762        patch_request: &Value,
763        expected_version: Option<&RawVersion>,
764        context: &RequestContext,
765    ) -> Result<VersionedResource, Self::Error> {
766        let tenant_id = self.effective_tenant_id(context);
767
768        // Handle version checking if expected_version is provided
769        if let Some(expected_version) = expected_version {
770            // Get current resource to check version
771            let key = StorageKey::new(&tenant_id, resource_type, id);
772            match self.storage.get(key.clone()).await {
773                Ok(Some(current_data)) => {
774                    // Parse current resource to extract version
775                    let current_resource =
776                        Resource::from_json(resource_type.to_string(), current_data.clone())
777                            .map_err(|e| ProviderError::InvalidInput {
778                                message: format!("Failed to deserialize stored resource: {}", e),
779                            })?;
780
781                    // Check if version matches
782                    let current_version = VersionedResource::new(current_resource.clone())
783                        .version()
784                        .clone();
785
786                    if &current_version != expected_version {
787                        return Err(ProviderError::PreconditionFailed {
788                            message: format!(
789                                "Version mismatch: expected {}, got {}",
790                                expected_version.as_str(),
791                                current_version.as_str()
792                            ),
793                        });
794                    }
795                }
796                Ok(None) => {
797                    return Err(ProviderError::NotFound {
798                        resource_type: resource_type.to_string(),
799                        id: id.to_string(),
800                    });
801                }
802                Err(_) => {
803                    return Err(ProviderError::Internal {
804                        message: "Failed to retrieve resource for version check".to_string(),
805                    });
806                }
807            }
808        }
809
810        // Regular patch without version checking - get current resource
811        let current_resource = self
812            .get_resource(resource_type, id, context)
813            .await?
814            .ok_or_else(|| ProviderError::NotFound {
815                resource_type: resource_type.to_string(),
816                id: id.to_string(),
817            })?;
818
819        // Convert to JSON for patching
820        let mut resource_data =
821            current_resource
822                .resource()
823                .to_json()
824                .map_err(|e| ProviderError::Internal {
825                    message: format!("Failed to serialize resource for patching: {}", e),
826                })?;
827
828        // Apply patch operations using helper trait
829        if let Some(operations) = patch_request.get("Operations") {
830            if let Some(ops_array) = operations.as_array() {
831                for operation in ops_array {
832                    self.apply_patch_operation(&mut resource_data, operation)?;
833                }
834            }
835        }
836
837        // Parse back to Resource
838        let patched_resource = Resource::from_json(resource_type.to_string(), resource_data)
839            .map_err(|e| ProviderError::InvalidData {
840                message: format!("Failed to create patched resource: {}", e),
841            })?;
842
843        // Store the patched resource
844        let key = StorageKey::new(&tenant_id, resource_type, id);
845        let patched_json = patched_resource
846            .to_json()
847            .map_err(|e| ProviderError::Internal {
848                message: format!("Failed to serialize patched resource: {}", e),
849            })?;
850
851        self.storage
852            .put(key, patched_json)
853            .await
854            .map_err(|e| ProviderError::Internal {
855                message: format!("Storage error during patch: {}", e),
856            })?;
857
858        Ok(VersionedResource::new(patched_resource))
859    }
860
861    async fn resource_exists(
862        &self,
863        resource_type: &str,
864        id: &str,
865        context: &RequestContext,
866    ) -> Result<bool, Self::Error> {
867        let tenant_id = self.effective_tenant_id(context);
868
869        let key = StorageKey::new(&tenant_id, resource_type, id);
870        self.storage
871            .exists(key)
872            .await
873            .map_err(|e| ProviderError::Internal {
874                message: format!("Storage error during exists check: {}", e),
875            })
876    }
877}