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