1use crate::resource::{
43 ListQuery, RequestContext, Resource, ResourceProvider,
44 conditional_provider::VersionedResource,
45 version::{ConditionalResult, ScimVersion, VersionConflict},
46};
47use log::{debug, info, trace, warn};
48use serde_json::{Value, json};
49use std::collections::HashMap;
50use std::sync::Arc;
51use tokio::sync::RwLock;
52
53#[derive(Debug, Clone)]
58pub struct InMemoryProvider {
59 data: Arc<RwLock<HashMap<String, HashMap<String, HashMap<String, Resource>>>>>,
61 next_ids: Arc<RwLock<HashMap<String, HashMap<String, u64>>>>,
63}
64
65impl InMemoryProvider {
66 pub fn new() -> Self {
68 Self {
69 data: Arc::new(RwLock::new(HashMap::new())),
70 next_ids: Arc::new(RwLock::new(HashMap::new())),
71 }
72 }
73
74 fn effective_tenant_id(&self, context: &RequestContext) -> String {
78 context.tenant_id().unwrap_or("default").to_string()
79 }
80
81 async fn generate_resource_id(&self, tenant_id: &str, resource_type: &str) -> String {
83 let mut next_ids_guard = self.next_ids.write().await;
84 let tenant_ids = next_ids_guard
85 .entry(tenant_id.to_string())
86 .or_insert_with(HashMap::new);
87 let next_id = tenant_ids.entry(resource_type.to_string()).or_insert(1);
88
89 let id = next_id.to_string();
90 *next_id += 1;
91 id
92 }
93
94 async fn check_username_duplicate(
96 &self,
97 tenant_id: &str,
98 username: &str,
99 exclude_id: Option<&str>,
100 ) -> Result<(), InMemoryError> {
101 let data_guard = self.data.read().await;
102
103 if let Some(tenant_data) = data_guard.get(tenant_id) {
104 if let Some(users) = tenant_data.get("User") {
105 for (existing_id, existing_user) in users {
106 if Some(existing_id.as_str()) == exclude_id {
108 continue;
109 }
110
111 if let Some(existing_username) = existing_user.get_username() {
112 if existing_username == username {
113 return Err(InMemoryError::DuplicateAttribute {
114 resource_type: "User".to_string(),
115 attribute: "userName".to_string(),
116 value: username.to_string(),
117 tenant_id: tenant_id.to_string(),
118 });
119 }
120 }
121 }
122 }
123 }
124
125 Ok(())
126 }
127
128 fn add_scim_metadata(&self, mut resource: Resource) -> Resource {
130 if let Err(_e) = resource.create_meta("https://example.com/scim/v2") {
132 return resource;
133 }
134
135 if let Some(meta) = resource.get_meta().cloned() {
137 if let Some(id) = resource.get_id() {
138 let now = chrono::Utc::now();
139 let version = crate::resource::value_objects::Meta::generate_version(id, now);
140 if let Ok(meta_with_version) = meta.with_version(version) {
141 resource.set_meta(meta_with_version);
142 }
143 }
144 }
145
146 resource
147 }
148
149 pub async fn clear(&self) {
151 let mut data_guard = self.data.write().await;
152 let mut ids_guard = self.next_ids.write().await;
153 data_guard.clear();
154 ids_guard.clear();
155 }
156
157 pub async fn get_stats(&self) -> InMemoryStats {
159 let data_guard = self.data.read().await;
160
161 let mut tenant_count = 0;
162 let mut total_resources = 0;
163 let mut resource_types = std::collections::HashSet::new();
164
165 for (_tenant_id, tenant_data) in data_guard.iter() {
166 tenant_count += 1;
167 for (resource_type, resources) in tenant_data.iter() {
168 resource_types.insert(resource_type.clone());
169 total_resources += resources.len();
170 }
171 }
172
173 InMemoryStats {
174 tenant_count,
175 total_resources,
176 resource_type_count: resource_types.len(),
177 resource_types: resource_types.into_iter().collect(),
178 }
179 }
180
181 pub async fn list_resources_in_tenant(
183 &self,
184 tenant_id: &str,
185 resource_type: &str,
186 ) -> Vec<Resource> {
187 let data_guard = self.data.read().await;
188
189 data_guard
190 .get(tenant_id)
191 .and_then(|tenant_data| tenant_data.get(resource_type))
192 .map(|resources| resources.values().cloned().collect())
193 .unwrap_or_default()
194 }
195
196 async fn count_resources_for_tenant(&self, tenant_id: &str, resource_type: &str) -> usize {
198 let data_guard = self.data.read().await;
199 data_guard
200 .get(tenant_id)
201 .and_then(|tenant_data| tenant_data.get(resource_type))
202 .map(|resources| resources.len())
203 .unwrap_or(0)
204 }
205}
206
207impl Default for InMemoryProvider {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213#[derive(Debug, thiserror::Error)]
215pub enum InMemoryError {
216 #[error("Resource not found: {resource_type} with id '{id}' in tenant '{tenant_id}'")]
217 ResourceNotFound {
218 resource_type: String,
219 id: String,
220 tenant_id: String,
221 },
222
223 #[error(
224 "Duplicate attribute '{attribute}' with value '{value}' for {resource_type} in tenant '{tenant_id}'"
225 )]
226 DuplicateAttribute {
227 resource_type: String,
228 attribute: String,
229 value: String,
230 tenant_id: String,
231 },
232
233 #[error("Invalid resource data: {message}")]
234 InvalidData { message: String },
235
236 #[error("Query error: {message}")]
237 QueryError { message: String },
238
239 #[error("Internal error: {message}")]
240 Internal { message: String },
241
242 #[error("Invalid input: {message}")]
243 InvalidInput { message: String },
244
245 #[error("Resource not found: {resource_type} with id '{id}'")]
246 NotFound { resource_type: String, id: String },
247
248 #[error("Precondition failed: {message}")]
249 PreconditionFailed { message: String },
250}
251
252#[derive(Debug, Clone)]
254pub struct InMemoryStats {
255 pub tenant_count: usize,
256 pub total_resources: usize,
257 pub resource_type_count: usize,
258 pub resource_types: Vec<String>,
259}
260
261impl ResourceProvider for InMemoryProvider {
262 type Error = InMemoryError;
263
264 async fn create_resource(
265 &self,
266 resource_type: &str,
267 mut data: Value,
268 context: &RequestContext,
269 ) -> Result<Resource, Self::Error> {
270 let tenant_id = self.effective_tenant_id(context);
271
272 info!(
273 "Creating {} resource for tenant '{}' (request: '{}')",
274 resource_type, tenant_id, context.request_id
275 );
276 trace!(
277 "Create data: {}",
278 serde_json::to_string(&data).unwrap_or_else(|_| "invalid json".to_string())
279 );
280
281 context
283 .validate_operation("create")
284 .map_err(|e| InMemoryError::Internal { message: e })?;
285
286 if let Some(tenant_context) = &context.tenant_context {
288 if resource_type == "User" {
289 if let Some(max_users) = tenant_context.permissions.max_users {
290 let current_count = self.count_resources_for_tenant(&tenant_id, "User").await;
291 if current_count >= max_users {
292 return Err(InMemoryError::Internal {
293 message: format!(
294 "User limit exceeded: {}/{}",
295 current_count, max_users
296 ),
297 });
298 }
299 }
300 } else if resource_type == "Group" {
301 if let Some(max_groups) = tenant_context.permissions.max_groups {
302 let current_count = self.count_resources_for_tenant(&tenant_id, "Group").await;
303 if current_count >= max_groups {
304 return Err(InMemoryError::Internal {
305 message: format!(
306 "Group limit exceeded: {}/{}",
307 current_count, max_groups
308 ),
309 });
310 }
311 }
312 }
313 }
314
315 if data.get("id").is_none() {
317 let id = self.generate_resource_id(&tenant_id, resource_type).await;
318 if let Some(obj) = data.as_object_mut() {
319 obj.insert("id".to_string(), json!(id));
320 }
321 }
322
323 let resource = Resource::from_json(resource_type.to_string(), data).map_err(|e| {
325 InMemoryError::InvalidData {
326 message: format!("Failed to create resource: {}", e),
327 }
328 })?;
329
330 if resource_type == "User" {
332 if let Some(username) = resource.get_username() {
333 self.check_username_duplicate(&tenant_id, username, None)
334 .await?;
335 }
336 }
337
338 let resource_with_meta = self.add_scim_metadata(resource);
340 let resource_id = resource_with_meta.get_id().unwrap_or("unknown").to_string();
341
342 let mut data_guard = self.data.write().await;
344 data_guard
345 .entry(tenant_id.clone())
346 .or_insert_with(HashMap::new)
347 .entry(resource_type.to_string())
348 .or_insert_with(HashMap::new)
349 .insert(resource_id.clone(), resource_with_meta.clone());
350
351 Ok(resource_with_meta)
352 }
353
354 async fn get_resource(
355 &self,
356 resource_type: &str,
357 id: &str,
358 context: &RequestContext,
359 ) -> Result<Option<Resource>, Self::Error> {
360 let tenant_id = self.effective_tenant_id(context);
361
362 debug!(
363 "Getting {} resource with ID '{}' for tenant '{}' (request: '{}')",
364 resource_type, id, tenant_id, context.request_id
365 );
366
367 context
369 .validate_operation("read")
370 .map_err(|e| InMemoryError::Internal { message: e })?;
371
372 let data_guard = self.data.read().await;
373 let resource = data_guard
374 .get(&tenant_id)
375 .and_then(|tenant_data| tenant_data.get(resource_type))
376 .and_then(|type_data| type_data.get(id))
377 .cloned();
378
379 if resource.is_some() {
380 trace!("Resource found and returned");
381 } else {
382 debug!("Resource not found");
383 }
384
385 Ok(resource)
386 }
387
388 async fn update_resource(
389 &self,
390 resource_type: &str,
391 id: &str,
392 mut data: Value,
393 context: &RequestContext,
394 ) -> Result<Resource, Self::Error> {
395 let tenant_id = self.effective_tenant_id(context);
396
397 info!(
398 "Updating {} resource with ID '{}' for tenant '{}' (request: '{}')",
399 resource_type, id, tenant_id, context.request_id
400 );
401 trace!(
402 "Update data: {}",
403 serde_json::to_string(&data).unwrap_or_else(|_| "invalid json".to_string())
404 );
405
406 context
408 .validate_operation("update")
409 .map_err(|e| InMemoryError::Internal { message: e })?;
410
411 if let Some(obj) = data.as_object_mut() {
413 obj.insert("id".to_string(), json!(id));
414 }
415
416 let resource = Resource::from_json(resource_type.to_string(), data).map_err(|e| {
418 InMemoryError::InvalidData {
419 message: format!("Failed to update resource: {}", e),
420 }
421 })?;
422
423 if resource_type == "User" {
425 if let Some(username) = resource.get_username() {
426 self.check_username_duplicate(&tenant_id, username, Some(id))
427 .await?;
428 }
429 }
430
431 {
433 let data_guard = self.data.read().await;
434 let exists = data_guard
435 .get(&tenant_id)
436 .and_then(|tenant_data| tenant_data.get(resource_type))
437 .and_then(|type_data| type_data.get(id))
438 .is_some();
439
440 if !exists {
441 return Err(InMemoryError::ResourceNotFound {
442 resource_type: resource_type.to_string(),
443 id: id.to_string(),
444 tenant_id,
445 });
446 }
447 }
448
449 let resource_with_meta = self.add_scim_metadata(resource);
451
452 let mut data_guard = self.data.write().await;
454 data_guard
455 .get_mut(&tenant_id)
456 .and_then(|tenant_data| tenant_data.get_mut(resource_type))
457 .and_then(|type_data| type_data.insert(id.to_string(), resource_with_meta.clone()));
458
459 Ok(resource_with_meta)
460 }
461
462 async fn delete_resource(
463 &self,
464 resource_type: &str,
465 id: &str,
466 context: &RequestContext,
467 ) -> Result<(), Self::Error> {
468 let tenant_id = self.effective_tenant_id(context);
469
470 info!(
471 "Deleting {} resource with ID '{}' for tenant '{}' (request: '{}')",
472 resource_type, id, tenant_id, context.request_id
473 );
474
475 context
477 .validate_operation("delete")
478 .map_err(|e| InMemoryError::Internal { message: e })?;
479
480 let mut data_guard = self.data.write().await;
481 let removed = data_guard
482 .get_mut(&tenant_id)
483 .and_then(|tenant_data| tenant_data.get_mut(resource_type))
484 .and_then(|type_data| type_data.remove(id))
485 .is_some();
486
487 if !removed {
488 warn!(
489 "Attempted to delete non-existent {} resource with ID '{}' for tenant '{}'",
490 resource_type, id, tenant_id
491 );
492 return Err(InMemoryError::ResourceNotFound {
493 resource_type: resource_type.to_string(),
494 id: id.to_string(),
495 tenant_id,
496 });
497 }
498
499 debug!(
500 "Successfully deleted {} resource with ID '{}' for tenant '{}'",
501 resource_type, id, tenant_id
502 );
503 Ok(())
504 }
505
506 async fn list_resources(
507 &self,
508 resource_type: &str,
509 query: Option<&ListQuery>,
510 context: &RequestContext,
511 ) -> Result<Vec<Resource>, Self::Error> {
512 let tenant_id = self.effective_tenant_id(context);
513
514 debug!(
515 "Listing {} resources for tenant '{}' (request: '{}')",
516 resource_type, tenant_id, context.request_id
517 );
518
519 context
521 .validate_operation("list")
522 .map_err(|e| InMemoryError::Internal { message: e })?;
523
524 let data_guard = self.data.read().await;
525 let resources: Vec<Resource> = data_guard
526 .get(&tenant_id)
527 .and_then(|tenant_data| tenant_data.get(resource_type))
528 .map(|type_data| type_data.values().cloned().collect())
529 .unwrap_or_default();
530
531 let mut filtered_resources = resources;
533
534 if let Some(q) = query {
535 if let Some(start_index) = q.start_index {
537 let start = (start_index.saturating_sub(1)) as usize; if start < filtered_resources.len() {
539 filtered_resources = filtered_resources.into_iter().skip(start).collect();
540 } else {
541 filtered_resources = Vec::new();
542 }
543 }
544
545 if let Some(count) = q.count {
546 filtered_resources.truncate(count as usize);
547 }
548 }
549
550 debug!(
551 "Found {} {} resources for tenant '{}' (after filtering)",
552 filtered_resources.len(),
553 resource_type,
554 tenant_id
555 );
556
557 Ok(filtered_resources)
558 }
559
560 async fn find_resource_by_attribute(
561 &self,
562 resource_type: &str,
563 attribute: &str,
564 value: &Value,
565 context: &RequestContext,
566 ) -> Result<Option<Resource>, Self::Error> {
567 let tenant_id = self.effective_tenant_id(context);
568
569 let data_guard = self.data.read().await;
570 if let Some(tenant_data) = data_guard.get(&tenant_id) {
571 if let Some(type_data) = tenant_data.get(resource_type) {
572 for resource in type_data.values() {
573 let found_match = match attribute {
575 "userName" => {
576 if let Some(username) = resource.get_username() {
577 &Value::String(username.to_string()) == value
578 } else {
579 false
580 }
581 }
582 "id" => {
583 if let Some(id) = resource.get_id() {
584 &Value::String(id.to_string()) == value
585 } else {
586 false
587 }
588 }
589 _ => {
591 if let Some(attr_value) = resource.get_attribute(attribute) {
592 attr_value == value
593 } else {
594 false
595 }
596 }
597 };
598
599 if found_match {
600 return Ok(Some(resource.clone()));
601 }
602 }
603 }
604 }
605
606 Ok(None)
607 }
608
609 async fn resource_exists(
610 &self,
611 resource_type: &str,
612 id: &str,
613 context: &RequestContext,
614 ) -> Result<bool, Self::Error> {
615 let tenant_id = self.effective_tenant_id(context);
616
617 let data_guard = self.data.read().await;
618 let exists = data_guard
619 .get(&tenant_id)
620 .and_then(|tenant_data| tenant_data.get(resource_type))
621 .and_then(|type_data| type_data.get(id))
622 .is_some();
623
624 Ok(exists)
625 }
626
627 async fn patch_resource(
628 &self,
629 resource_type: &str,
630 id: &str,
631 patch_request: &Value,
632 context: &RequestContext,
633 ) -> Result<Resource, Self::Error> {
634 let tenant_id = self.effective_tenant_id(context);
635
636 let operations = patch_request
638 .get("Operations")
639 .and_then(|ops| ops.as_array())
640 .ok_or(InMemoryError::InvalidInput {
641 message: "PATCH request must contain Operations array".to_string(),
642 })?;
643
644 if operations.is_empty() {
646 return Err(InMemoryError::InvalidInput {
647 message: "Operations array cannot be empty".to_string(),
648 });
649 }
650
651 if let Some(request_etag) = patch_request.get("etag").and_then(|e| e.as_str()) {
653 let data_guard_read = self.data.read().await;
655 let tenant_data = data_guard_read
656 .get(&tenant_id)
657 .ok_or(InMemoryError::NotFound {
658 resource_type: resource_type.to_string(),
659 id: id.to_string(),
660 })?;
661
662 let type_data = tenant_data
663 .get(resource_type)
664 .ok_or(InMemoryError::NotFound {
665 resource_type: resource_type.to_string(),
666 id: id.to_string(),
667 })?;
668
669 let current_resource = type_data.get(id).ok_or(InMemoryError::NotFound {
670 resource_type: resource_type.to_string(),
671 id: id.to_string(),
672 })?;
673
674 if let Some(meta) = current_resource.get_meta() {
676 if let Some(current_etag) = meta.version() {
677 if request_etag != current_etag {
678 return Err(InMemoryError::PreconditionFailed {
679 message: format!(
680 "ETag mismatch: provided '{}', current '{}'",
681 request_etag, current_etag
682 ),
683 });
684 }
685 }
686 }
687 drop(data_guard_read);
688 }
689
690 let mut data_guard = self.data.write().await;
692 let tenant_data = data_guard
693 .get_mut(&tenant_id)
694 .ok_or(InMemoryError::NotFound {
695 resource_type: resource_type.to_string(),
696 id: id.to_string(),
697 })?;
698
699 let type_data = tenant_data
700 .get_mut(resource_type)
701 .ok_or(InMemoryError::NotFound {
702 resource_type: resource_type.to_string(),
703 id: id.to_string(),
704 })?;
705
706 let resource = type_data.get_mut(id).ok_or(InMemoryError::NotFound {
707 resource_type: resource_type.to_string(),
708 id: id.to_string(),
709 })?;
710
711 let mut resource_data = resource.to_json().map_err(|_| InMemoryError::Internal {
713 message: "Failed to serialize resource for patching".to_string(),
714 })?;
715
716 for operation in operations {
717 if let Some(path) = operation.get("path").and_then(|v| v.as_str()) {
719 self.validate_path_not_readonly(path)?;
720 self.validate_path_exists(path, resource_type)?;
721 }
722 self.apply_patch_operation(&mut resource_data, operation)?;
723 }
724
725 let now = chrono::Utc::now().to_rfc3339();
727 if let Some(meta) = resource_data.get_mut("meta") {
728 if let Some(meta_obj) = meta.as_object_mut() {
729 meta_obj.insert("lastModified".to_string(), Value::String(now));
730 }
731 }
732
733 let updated_resource = Resource::from_json(resource_type.to_string(), resource_data)
735 .map_err(|_| InMemoryError::Internal {
736 message: "Failed to create resource from patched data".to_string(),
737 })?;
738
739 *resource = updated_resource.clone();
741
742 debug!(
743 "Patched resource {} with id {} in tenant {}",
744 resource_type, id, tenant_id
745 );
746
747 Ok(updated_resource)
748 }
749
750 fn apply_patch_operation(
752 &self,
753 resource_data: &mut Value,
754 operation: &Value,
755 ) -> Result<(), Self::Error> {
756 let op =
757 operation
758 .get("op")
759 .and_then(|v| v.as_str())
760 .ok_or(InMemoryError::InvalidInput {
761 message: "PATCH operation must have 'op' field".to_string(),
762 })?;
763
764 let path = operation.get("path").and_then(|v| v.as_str());
765 let value = operation.get("value");
766
767 match op.to_lowercase().as_str() {
768 "add" => self.apply_add_operation(resource_data, path, value),
769 "remove" => self.apply_remove_operation(resource_data, path),
770 "replace" => self.apply_replace_operation(resource_data, path, value),
771 _ => Err(InMemoryError::InvalidInput {
772 message: format!("Unsupported PATCH operation: {}", op),
773 }),
774 }
775 }
776}
777
778impl InMemoryProvider {
779 fn apply_add_operation(
781 &self,
782 resource_data: &mut Value,
783 path: Option<&str>,
784 value: Option<&Value>,
785 ) -> Result<(), InMemoryError> {
786 let value = value.ok_or(InMemoryError::InvalidInput {
787 message: "ADD operation requires a value".to_string(),
788 })?;
789
790 match path {
791 Some(path_str) => {
792 self.set_value_at_path(resource_data, path_str, value.clone())?;
793 }
794 None => {
795 if let (Some(current_obj), Some(value_obj)) =
797 (resource_data.as_object_mut(), value.as_object())
798 {
799 for (key, val) in value_obj {
800 current_obj.insert(key.clone(), val.clone());
801 }
802 }
803 }
804 }
805 Ok(())
806 }
807
808 fn apply_remove_operation(
810 &self,
811 resource_data: &mut Value,
812 path: Option<&str>,
813 ) -> Result<(), InMemoryError> {
814 if let Some(path_str) = path {
815 self.remove_value_at_path(resource_data, path_str)?;
816 }
817 Ok(())
818 }
819
820 fn apply_replace_operation(
822 &self,
823 resource_data: &mut Value,
824 path: Option<&str>,
825 value: Option<&Value>,
826 ) -> Result<(), InMemoryError> {
827 let value = value.ok_or(InMemoryError::InvalidInput {
828 message: "REPLACE operation requires a value".to_string(),
829 })?;
830
831 match path {
832 Some(path_str) => {
833 self.set_value_at_path(resource_data, path_str, value.clone())?;
834 }
835 None => {
836 *resource_data = value.clone();
838 }
839 }
840 Ok(())
841 }
842
843 fn set_value_at_path(
845 &self,
846 data: &mut Value,
847 path: &str,
848 value: Value,
849 ) -> Result<(), InMemoryError> {
850 let parts: Vec<&str> = path.split('.').collect();
851
852 if parts.len() == 1 {
853 if let Some(obj) = data.as_object_mut() {
855 let attribute_name = parts[0];
856
857 if Self::is_multivalued_attribute(attribute_name) {
859 if let Some(existing) = obj.get_mut(attribute_name) {
860 if let Some(existing_array) = existing.as_array_mut() {
861 if value.is_array() {
863 obj.insert(attribute_name.to_string(), value);
864 } else {
865 existing_array.push(value);
867 }
868 return Ok(());
869 }
870 }
871 let new_array = if value.is_array() {
873 value
874 } else {
875 json!([value])
876 };
877 obj.insert(attribute_name.to_string(), new_array);
878 } else {
879 obj.insert(attribute_name.to_string(), value);
881 }
882 }
883 return Ok(());
884 }
885
886 let mut current = data;
888
889 for part in &parts[..parts.len() - 1] {
890 if let Some(obj) = current.as_object_mut() {
891 let entry = obj
892 .entry(part.to_string())
893 .or_insert_with(|| Value::Object(serde_json::Map::new()));
894 current = entry;
895 } else {
896 return Err(InMemoryError::InvalidInput {
897 message: format!(
898 "Cannot navigate path '{}' - intermediate value is not an object",
899 path
900 ),
901 });
902 }
903 }
904
905 if let Some(obj) = current.as_object_mut() {
907 obj.insert(parts.last().unwrap().to_string(), value);
908 } else {
909 return Err(InMemoryError::InvalidInput {
910 message: format!(
911 "Cannot set value at path '{}' - target is not an object",
912 path
913 ),
914 });
915 }
916
917 Ok(())
918 }
919
920 fn remove_value_at_path(&self, data: &mut Value, path: &str) -> Result<(), InMemoryError> {
922 let parts: Vec<&str> = path.split('.').collect();
923
924 if parts.len() == 1 {
925 if let Some(obj) = data.as_object_mut() {
927 obj.remove(parts[0]);
928 }
929 return Ok(());
930 }
931
932 let mut current = data;
934
935 for part in &parts[..parts.len() - 1] {
936 if let Some(obj) = current.as_object_mut() {
937 match obj.get_mut(*part) {
939 Some(value) => current = value,
940 None => return Ok(()), }
942 } else {
943 return Err(InMemoryError::InvalidInput {
944 message: format!(
945 "Cannot navigate path '{}' - intermediate value is not an object",
946 path
947 ),
948 });
949 }
950 }
951
952 if let Some(obj) = current.as_object_mut() {
954 obj.remove(*parts.last().unwrap());
955 }
956
957 Ok(())
958 }
959
960 fn validate_path_not_readonly(&self, path: &str) -> Result<(), InMemoryError> {
962 let readonly_paths = [
963 "id",
964 "meta.created",
965 "meta.resourceType",
966 "meta.location",
967 "schemas",
968 ];
969
970 for readonly_path in &readonly_paths {
971 if path == *readonly_path || path.starts_with(&format!("{}.", readonly_path)) {
972 return Err(InMemoryError::InvalidInput {
973 message: format!("Cannot modify readonly attribute: {}", path),
974 });
975 }
976 }
977
978 Ok(())
979 }
980
981 fn validate_path_exists(&self, path: &str, resource_type: &str) -> Result<(), InMemoryError> {
983 if path.contains('[') && !path.contains(']') {
985 return Err(InMemoryError::InvalidInput {
986 message: format!(
987 "Invalid path for {} resource: {} (malformed filter syntax)",
988 resource_type, path
989 ),
990 });
991 }
992
993 let obviously_invalid_prefixes = ["nonexistent.", "invalid.", "required."];
996
997 for invalid_prefix in &obviously_invalid_prefixes {
998 if path.starts_with(invalid_prefix) {
999 return Err(InMemoryError::InvalidInput {
1000 message: format!("Invalid path for {} resource: {}", resource_type, path),
1001 });
1002 }
1003 }
1004
1005 let obviously_invalid_patterns = [
1007 "nonexistent.invalid",
1008 "invalid.nonexistent",
1009 "nonexistent.field.path",
1010 "required.field.id",
1011 ];
1012
1013 for invalid_pattern in &obviously_invalid_patterns {
1014 if path == *invalid_pattern {
1015 return Err(InMemoryError::InvalidInput {
1016 message: format!("Invalid path for {} resource: {}", resource_type, path),
1017 });
1018 }
1019 }
1020
1021 Ok(())
1024 }
1025
1026 fn is_multivalued_attribute(attribute_name: &str) -> bool {
1028 matches!(
1029 attribute_name,
1030 "emails" | "phoneNumbers" | "addresses" | "groups" | "members"
1031 )
1032 }
1033}
1034
1035impl InMemoryProvider {
1037 pub async fn conditional_update(
1038 &self,
1039 resource_type: &str,
1040 id: &str,
1041 data: Value,
1042 expected_version: &ScimVersion,
1043 context: &RequestContext,
1044 ) -> Result<ConditionalResult<VersionedResource>, InMemoryError> {
1045 let tenant_id = context.tenant_id().unwrap_or("default");
1046
1047 let mut store = self.data.write().await;
1048 let tenant_data = store
1049 .entry(tenant_id.to_string())
1050 .or_insert_with(HashMap::new);
1051 let type_data = tenant_data
1052 .entry(resource_type.to_string())
1053 .or_insert_with(HashMap::new);
1054
1055 let existing_resource = match type_data.get(id) {
1057 Some(resource) => resource,
1058 None => return Ok(ConditionalResult::NotFound),
1059 };
1060
1061 let current_version = VersionedResource::new(existing_resource.clone())
1063 .version()
1064 .clone();
1065
1066 if !current_version.matches(expected_version) {
1068 let conflict = VersionConflict::new(
1069 expected_version.clone(),
1070 current_version,
1071 format!(
1072 "Resource {}/{} was modified by another client",
1073 resource_type, id
1074 ),
1075 );
1076 return Ok(ConditionalResult::VersionMismatch(conflict));
1077 }
1078
1079 let mut updated_resource =
1081 Resource::from_json(resource_type.to_string(), data).map_err(|e| {
1082 InMemoryError::InvalidData {
1083 message: format!("Failed to update resource: {}", e),
1084 }
1085 })?;
1086
1087 if let Some(original_id) = existing_resource.get_id() {
1089 updated_resource
1090 .set_id(original_id)
1091 .map_err(|e| InMemoryError::InvalidData {
1092 message: format!("Failed to set ID: {}", e),
1093 })?;
1094 }
1095
1096 type_data.insert(id.to_string(), updated_resource.clone());
1097 Ok(ConditionalResult::Success(VersionedResource::new(
1098 updated_resource,
1099 )))
1100 }
1101
1102 pub async fn conditional_delete(
1103 &self,
1104 resource_type: &str,
1105 id: &str,
1106 expected_version: &ScimVersion,
1107 context: &RequestContext,
1108 ) -> Result<ConditionalResult<()>, InMemoryError> {
1109 let tenant_id = context.tenant_id().unwrap_or("default");
1110
1111 let mut store = self.data.write().await;
1112 let tenant_data = store
1113 .entry(tenant_id.to_string())
1114 .or_insert_with(HashMap::new);
1115 let type_data = tenant_data
1116 .entry(resource_type.to_string())
1117 .or_insert_with(HashMap::new);
1118
1119 let existing_resource = match type_data.get(id) {
1121 Some(resource) => resource,
1122 None => return Ok(ConditionalResult::NotFound),
1123 };
1124
1125 let current_version = VersionedResource::new(existing_resource.clone())
1127 .version()
1128 .clone();
1129
1130 if !current_version.matches(expected_version) {
1132 let conflict = VersionConflict::new(
1133 expected_version.clone(),
1134 current_version,
1135 format!(
1136 "Resource {}/{} was modified by another client",
1137 resource_type, id
1138 ),
1139 );
1140 return Ok(ConditionalResult::VersionMismatch(conflict));
1141 }
1142
1143 type_data.remove(id);
1145 Ok(ConditionalResult::Success(()))
1146 }
1147
1148 pub async fn conditional_patch_resource(
1149 &self,
1150 resource_type: &str,
1151 id: &str,
1152 patch_request: &Value,
1153 expected_version: &ScimVersion,
1154 context: &RequestContext,
1155 ) -> Result<ConditionalResult<VersionedResource>, InMemoryError> {
1156 let tenant_id = context.tenant_id().unwrap_or("default");
1157
1158 let mut store = self.data.write().await;
1159 let tenant_data = store
1160 .entry(tenant_id.to_string())
1161 .or_insert_with(HashMap::new);
1162 let type_data = tenant_data
1163 .entry(resource_type.to_string())
1164 .or_insert_with(HashMap::new);
1165
1166 let existing_resource = match type_data.get(id) {
1168 Some(resource) => resource,
1169 None => return Ok(ConditionalResult::NotFound),
1170 };
1171
1172 let current_version = VersionedResource::new(existing_resource.clone())
1174 .version()
1175 .clone();
1176
1177 if !current_version.matches(expected_version) {
1179 let conflict = VersionConflict::new(
1180 expected_version.clone(),
1181 current_version,
1182 format!(
1183 "Resource {}/{} was modified by another client",
1184 resource_type, id
1185 ),
1186 );
1187 return Ok(ConditionalResult::VersionMismatch(conflict));
1188 }
1189
1190 let operations = patch_request
1192 .get("Operations")
1193 .and_then(|ops| ops.as_array())
1194 .ok_or_else(|| InMemoryError::InvalidData {
1195 message: "Invalid patch request: missing Operations array".to_string(),
1196 })?;
1197
1198 let mut modified_data =
1199 existing_resource
1200 .to_json()
1201 .map_err(|e| InMemoryError::InvalidData {
1202 message: format!("Failed to serialize existing resource: {}", e),
1203 })?;
1204
1205 for operation in operations {
1207 let op = operation
1208 .get("op")
1209 .and_then(|v| v.as_str())
1210 .ok_or_else(|| InMemoryError::InvalidData {
1211 message: "Missing 'op' field in patch operation".to_string(),
1212 })?;
1213
1214 let path = operation
1215 .get("path")
1216 .and_then(|v| v.as_str())
1217 .ok_or_else(|| InMemoryError::InvalidData {
1218 message: "Missing 'path' field in patch operation".to_string(),
1219 })?;
1220
1221 match op.to_lowercase().as_str() {
1222 "add" | "replace" => {
1223 if let Some(value) = operation.get("value") {
1224 if let Some(obj) = modified_data.as_object_mut() {
1226 obj.insert(path.to_string(), value.clone());
1227 }
1228 }
1229 }
1230 "remove" => {
1231 if let Some(obj) = modified_data.as_object_mut() {
1232 obj.remove(path);
1233 }
1234 }
1235 _ => {
1236 return Err(InMemoryError::InvalidData {
1237 message: format!("Unsupported patch operation: {}", op),
1238 });
1239 }
1240 }
1241 }
1242
1243 let mut updated_resource = Resource::from_json(resource_type.to_string(), modified_data)
1245 .map_err(|e| InMemoryError::InvalidData {
1246 message: format!("Failed to create updated resource: {}", e),
1247 })?;
1248
1249 if let Some(original_id) = existing_resource.get_id() {
1251 updated_resource
1252 .set_id(original_id)
1253 .map_err(|e| InMemoryError::InvalidData {
1254 message: format!("Failed to set ID: {}", e),
1255 })?;
1256 }
1257
1258 updated_resource.update_meta();
1260
1261 type_data.insert(id.to_string(), updated_resource.clone());
1263 Ok(ConditionalResult::Success(VersionedResource::new(
1264 updated_resource,
1265 )))
1266 }
1267
1268 pub async fn get_versioned_resource(
1269 &self,
1270 resource_type: &str,
1271 id: &str,
1272 context: &RequestContext,
1273 ) -> Result<Option<VersionedResource>, InMemoryError> {
1274 match self.get_resource(resource_type, id, context).await? {
1275 Some(resource) => Ok(Some(VersionedResource::new(resource))),
1276 None => Ok(None),
1277 }
1278 }
1279
1280 pub async fn create_versioned_resource(
1281 &self,
1282 resource_type: &str,
1283 data: Value,
1284 context: &RequestContext,
1285 ) -> Result<VersionedResource, InMemoryError> {
1286 let resource = self.create_resource(resource_type, data, context).await?;
1287 Ok(VersionedResource::new(resource))
1288 }
1289}