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 let now = chrono::Utc::now().to_rfc3339();
131 resource.add_metadata("/scim/v2", &now, &now);
132 resource
133 }
134
135 pub async fn clear(&self) {
137 let mut data_guard = self.data.write().await;
138 let mut ids_guard = self.next_ids.write().await;
139 data_guard.clear();
140 ids_guard.clear();
141 }
142
143 pub async fn get_stats(&self) -> InMemoryStats {
145 let data_guard = self.data.read().await;
146
147 let mut tenant_count = 0;
148 let mut total_resources = 0;
149 let mut resource_types = std::collections::HashSet::new();
150
151 for (_tenant_id, tenant_data) in data_guard.iter() {
152 tenant_count += 1;
153 for (resource_type, resources) in tenant_data.iter() {
154 resource_types.insert(resource_type.clone());
155 total_resources += resources.len();
156 }
157 }
158
159 InMemoryStats {
160 tenant_count,
161 total_resources,
162 resource_type_count: resource_types.len(),
163 resource_types: resource_types.into_iter().collect(),
164 }
165 }
166
167 pub async fn list_resources_in_tenant(
169 &self,
170 tenant_id: &str,
171 resource_type: &str,
172 ) -> Vec<Resource> {
173 let data_guard = self.data.read().await;
174
175 data_guard
176 .get(tenant_id)
177 .and_then(|tenant_data| tenant_data.get(resource_type))
178 .map(|resources| resources.values().cloned().collect())
179 .unwrap_or_default()
180 }
181
182 async fn count_resources_for_tenant(&self, tenant_id: &str, resource_type: &str) -> usize {
184 let data_guard = self.data.read().await;
185 data_guard
186 .get(tenant_id)
187 .and_then(|tenant_data| tenant_data.get(resource_type))
188 .map(|resources| resources.len())
189 .unwrap_or(0)
190 }
191}
192
193impl Default for InMemoryProvider {
194 fn default() -> Self {
195 Self::new()
196 }
197}
198
199#[derive(Debug, thiserror::Error)]
201pub enum InMemoryError {
202 #[error("Resource not found: {resource_type} with id '{id}' in tenant '{tenant_id}'")]
203 ResourceNotFound {
204 resource_type: String,
205 id: String,
206 tenant_id: String,
207 },
208
209 #[error(
210 "Duplicate attribute '{attribute}' with value '{value}' for {resource_type} in tenant '{tenant_id}'"
211 )]
212 DuplicateAttribute {
213 resource_type: String,
214 attribute: String,
215 value: String,
216 tenant_id: String,
217 },
218
219 #[error("Invalid resource data: {message}")]
220 InvalidData { message: String },
221
222 #[error("Query error: {message}")]
223 QueryError { message: String },
224
225 #[error("Internal error: {message}")]
226 Internal { message: String },
227}
228
229#[derive(Debug, Clone)]
231pub struct InMemoryStats {
232 pub tenant_count: usize,
233 pub total_resources: usize,
234 pub resource_type_count: usize,
235 pub resource_types: Vec<String>,
236}
237
238impl ResourceProvider for InMemoryProvider {
239 type Error = InMemoryError;
240
241 async fn create_resource(
242 &self,
243 resource_type: &str,
244 mut data: Value,
245 context: &RequestContext,
246 ) -> Result<Resource, Self::Error> {
247 let tenant_id = self.effective_tenant_id(context);
248
249 info!(
250 "Creating {} resource for tenant '{}' (request: '{}')",
251 resource_type, tenant_id, context.request_id
252 );
253 trace!(
254 "Create data: {}",
255 serde_json::to_string(&data).unwrap_or_else(|_| "invalid json".to_string())
256 );
257
258 context
260 .validate_operation("create")
261 .map_err(|e| InMemoryError::Internal { message: e })?;
262
263 if let Some(tenant_context) = &context.tenant_context {
265 if resource_type == "User" {
266 if let Some(max_users) = tenant_context.permissions.max_users {
267 let current_count = self.count_resources_for_tenant(&tenant_id, "User").await;
268 if current_count >= max_users {
269 return Err(InMemoryError::Internal {
270 message: format!(
271 "User limit exceeded: {}/{}",
272 current_count, max_users
273 ),
274 });
275 }
276 }
277 } else if resource_type == "Group" {
278 if let Some(max_groups) = tenant_context.permissions.max_groups {
279 let current_count = self.count_resources_for_tenant(&tenant_id, "Group").await;
280 if current_count >= max_groups {
281 return Err(InMemoryError::Internal {
282 message: format!(
283 "Group limit exceeded: {}/{}",
284 current_count, max_groups
285 ),
286 });
287 }
288 }
289 }
290 }
291
292 if data.get("id").is_none() {
294 let id = self.generate_resource_id(&tenant_id, resource_type).await;
295 if let Some(obj) = data.as_object_mut() {
296 obj.insert("id".to_string(), json!(id));
297 }
298 }
299
300 let resource = Resource::from_json(resource_type.to_string(), data).map_err(|e| {
302 InMemoryError::InvalidData {
303 message: format!("Failed to create resource: {}", e),
304 }
305 })?;
306
307 if resource_type == "User" {
309 if let Some(username) = resource.get_username() {
310 self.check_username_duplicate(&tenant_id, username, None)
311 .await?;
312 }
313 }
314
315 let resource_with_meta = self.add_scim_metadata(resource);
317 let resource_id = resource_with_meta.get_id().unwrap_or("unknown").to_string();
318
319 let mut data_guard = self.data.write().await;
321 data_guard
322 .entry(tenant_id.clone())
323 .or_insert_with(HashMap::new)
324 .entry(resource_type.to_string())
325 .or_insert_with(HashMap::new)
326 .insert(resource_id.clone(), resource_with_meta.clone());
327
328 Ok(resource_with_meta)
329 }
330
331 async fn get_resource(
332 &self,
333 resource_type: &str,
334 id: &str,
335 context: &RequestContext,
336 ) -> Result<Option<Resource>, Self::Error> {
337 let tenant_id = self.effective_tenant_id(context);
338
339 debug!(
340 "Getting {} resource with ID '{}' for tenant '{}' (request: '{}')",
341 resource_type, id, tenant_id, context.request_id
342 );
343
344 context
346 .validate_operation("read")
347 .map_err(|e| InMemoryError::Internal { message: e })?;
348
349 let data_guard = self.data.read().await;
350 let resource = data_guard
351 .get(&tenant_id)
352 .and_then(|tenant_data| tenant_data.get(resource_type))
353 .and_then(|type_data| type_data.get(id))
354 .cloned();
355
356 if resource.is_some() {
357 trace!("Resource found and returned");
358 } else {
359 debug!("Resource not found");
360 }
361
362 Ok(resource)
363 }
364
365 async fn update_resource(
366 &self,
367 resource_type: &str,
368 id: &str,
369 mut data: Value,
370 context: &RequestContext,
371 ) -> Result<Resource, Self::Error> {
372 let tenant_id = self.effective_tenant_id(context);
373
374 info!(
375 "Updating {} resource with ID '{}' for tenant '{}' (request: '{}')",
376 resource_type, id, tenant_id, context.request_id
377 );
378 trace!(
379 "Update data: {}",
380 serde_json::to_string(&data).unwrap_or_else(|_| "invalid json".to_string())
381 );
382
383 context
385 .validate_operation("update")
386 .map_err(|e| InMemoryError::Internal { message: e })?;
387
388 if let Some(obj) = data.as_object_mut() {
390 obj.insert("id".to_string(), json!(id));
391 }
392
393 let resource = Resource::from_json(resource_type.to_string(), data).map_err(|e| {
395 InMemoryError::InvalidData {
396 message: format!("Failed to update resource: {}", e),
397 }
398 })?;
399
400 if resource_type == "User" {
402 if let Some(username) = resource.get_username() {
403 self.check_username_duplicate(&tenant_id, username, Some(id))
404 .await?;
405 }
406 }
407
408 {
410 let data_guard = self.data.read().await;
411 let exists = data_guard
412 .get(&tenant_id)
413 .and_then(|tenant_data| tenant_data.get(resource_type))
414 .and_then(|type_data| type_data.get(id))
415 .is_some();
416
417 if !exists {
418 return Err(InMemoryError::ResourceNotFound {
419 resource_type: resource_type.to_string(),
420 id: id.to_string(),
421 tenant_id,
422 });
423 }
424 }
425
426 let resource_with_meta = self.add_scim_metadata(resource);
428
429 let mut data_guard = self.data.write().await;
431 data_guard
432 .get_mut(&tenant_id)
433 .and_then(|tenant_data| tenant_data.get_mut(resource_type))
434 .and_then(|type_data| type_data.insert(id.to_string(), resource_with_meta.clone()));
435
436 Ok(resource_with_meta)
437 }
438
439 async fn delete_resource(
440 &self,
441 resource_type: &str,
442 id: &str,
443 context: &RequestContext,
444 ) -> Result<(), Self::Error> {
445 let tenant_id = self.effective_tenant_id(context);
446
447 info!(
448 "Deleting {} resource with ID '{}' for tenant '{}' (request: '{}')",
449 resource_type, id, tenant_id, context.request_id
450 );
451
452 context
454 .validate_operation("delete")
455 .map_err(|e| InMemoryError::Internal { message: e })?;
456
457 let mut data_guard = self.data.write().await;
458 let removed = data_guard
459 .get_mut(&tenant_id)
460 .and_then(|tenant_data| tenant_data.get_mut(resource_type))
461 .and_then(|type_data| type_data.remove(id))
462 .is_some();
463
464 if !removed {
465 warn!(
466 "Attempted to delete non-existent {} resource with ID '{}' for tenant '{}'",
467 resource_type, id, tenant_id
468 );
469 return Err(InMemoryError::ResourceNotFound {
470 resource_type: resource_type.to_string(),
471 id: id.to_string(),
472 tenant_id,
473 });
474 }
475
476 debug!(
477 "Successfully deleted {} resource with ID '{}' for tenant '{}'",
478 resource_type, id, tenant_id
479 );
480 Ok(())
481 }
482
483 async fn list_resources(
484 &self,
485 resource_type: &str,
486 query: Option<&ListQuery>,
487 context: &RequestContext,
488 ) -> Result<Vec<Resource>, Self::Error> {
489 let tenant_id = self.effective_tenant_id(context);
490
491 debug!(
492 "Listing {} resources for tenant '{}' (request: '{}')",
493 resource_type, tenant_id, context.request_id
494 );
495
496 context
498 .validate_operation("list")
499 .map_err(|e| InMemoryError::Internal { message: e })?;
500
501 let data_guard = self.data.read().await;
502 let resources: Vec<Resource> = data_guard
503 .get(&tenant_id)
504 .and_then(|tenant_data| tenant_data.get(resource_type))
505 .map(|type_data| type_data.values().cloned().collect())
506 .unwrap_or_default();
507
508 let mut filtered_resources = resources;
510
511 if let Some(q) = query {
512 if let Some(start_index) = q.start_index {
514 let start = (start_index.saturating_sub(1)) as usize; if start < filtered_resources.len() {
516 filtered_resources = filtered_resources.into_iter().skip(start).collect();
517 } else {
518 filtered_resources = Vec::new();
519 }
520 }
521
522 if let Some(count) = q.count {
523 filtered_resources.truncate(count as usize);
524 }
525 }
526
527 debug!(
528 "Found {} {} resources for tenant '{}' (after filtering)",
529 filtered_resources.len(),
530 resource_type,
531 tenant_id
532 );
533
534 Ok(filtered_resources)
535 }
536
537 async fn find_resource_by_attribute(
538 &self,
539 resource_type: &str,
540 attribute: &str,
541 value: &Value,
542 context: &RequestContext,
543 ) -> Result<Option<Resource>, Self::Error> {
544 let tenant_id = self.effective_tenant_id(context);
545
546 let data_guard = self.data.read().await;
547 if let Some(tenant_data) = data_guard.get(&tenant_id) {
548 if let Some(type_data) = tenant_data.get(resource_type) {
549 for resource in type_data.values() {
550 let found_match = match attribute {
552 "userName" => {
553 if let Some(username) = resource.get_username() {
554 &Value::String(username.to_string()) == value
555 } else {
556 false
557 }
558 }
559 "id" => {
560 if let Some(id) = resource.get_id() {
561 &Value::String(id.to_string()) == value
562 } else {
563 false
564 }
565 }
566 _ => {
568 if let Some(attr_value) = resource.get_attribute(attribute) {
569 attr_value == value
570 } else {
571 false
572 }
573 }
574 };
575
576 if found_match {
577 return Ok(Some(resource.clone()));
578 }
579 }
580 }
581 }
582
583 Ok(None)
584 }
585
586 async fn resource_exists(
587 &self,
588 resource_type: &str,
589 id: &str,
590 context: &RequestContext,
591 ) -> Result<bool, Self::Error> {
592 let tenant_id = self.effective_tenant_id(context);
593
594 let data_guard = self.data.read().await;
595 let exists = data_guard
596 .get(&tenant_id)
597 .and_then(|tenant_data| tenant_data.get(resource_type))
598 .and_then(|type_data| type_data.get(id))
599 .is_some();
600
601 Ok(exists)
602 }
603}
604
605impl InMemoryProvider {
607 pub async fn conditional_update(
608 &self,
609 resource_type: &str,
610 id: &str,
611 data: Value,
612 expected_version: &ScimVersion,
613 context: &RequestContext,
614 ) -> Result<ConditionalResult<VersionedResource>, InMemoryError> {
615 let tenant_id = context.tenant_id().unwrap_or("default");
616
617 let mut store = self.data.write().await;
618 let tenant_data = store
619 .entry(tenant_id.to_string())
620 .or_insert_with(HashMap::new);
621 let type_data = tenant_data
622 .entry(resource_type.to_string())
623 .or_insert_with(HashMap::new);
624
625 let existing_resource = match type_data.get(id) {
627 Some(resource) => resource,
628 None => return Ok(ConditionalResult::NotFound),
629 };
630
631 let current_version = VersionedResource::new(existing_resource.clone())
633 .version()
634 .clone();
635
636 if !current_version.matches(expected_version) {
638 let conflict = VersionConflict::new(
639 expected_version.clone(),
640 current_version,
641 format!(
642 "Resource {}/{} was modified by another client",
643 resource_type, id
644 ),
645 );
646 return Ok(ConditionalResult::VersionMismatch(conflict));
647 }
648
649 let mut updated_resource =
651 Resource::from_json(resource_type.to_string(), data).map_err(|e| {
652 InMemoryError::InvalidData {
653 message: format!("Failed to update resource: {}", e),
654 }
655 })?;
656
657 if let Some(original_id) = existing_resource.get_id() {
659 updated_resource
660 .set_id(original_id)
661 .map_err(|e| InMemoryError::InvalidData {
662 message: format!("Failed to set ID: {}", e),
663 })?;
664 }
665
666 type_data.insert(id.to_string(), updated_resource.clone());
667 Ok(ConditionalResult::Success(VersionedResource::new(
668 updated_resource,
669 )))
670 }
671
672 pub async fn conditional_delete(
673 &self,
674 resource_type: &str,
675 id: &str,
676 expected_version: &ScimVersion,
677 context: &RequestContext,
678 ) -> Result<ConditionalResult<()>, InMemoryError> {
679 let tenant_id = context.tenant_id().unwrap_or("default");
680
681 let mut store = self.data.write().await;
682 let tenant_data = store
683 .entry(tenant_id.to_string())
684 .or_insert_with(HashMap::new);
685 let type_data = tenant_data
686 .entry(resource_type.to_string())
687 .or_insert_with(HashMap::new);
688
689 let existing_resource = match type_data.get(id) {
691 Some(resource) => resource,
692 None => return Ok(ConditionalResult::NotFound),
693 };
694
695 let current_version = VersionedResource::new(existing_resource.clone())
697 .version()
698 .clone();
699
700 if !current_version.matches(expected_version) {
702 let conflict = VersionConflict::new(
703 expected_version.clone(),
704 current_version,
705 format!(
706 "Resource {}/{} was modified by another client",
707 resource_type, id
708 ),
709 );
710 return Ok(ConditionalResult::VersionMismatch(conflict));
711 }
712
713 type_data.remove(id);
715 Ok(ConditionalResult::Success(()))
716 }
717
718 pub async fn get_versioned_resource(
719 &self,
720 resource_type: &str,
721 id: &str,
722 context: &RequestContext,
723 ) -> Result<Option<VersionedResource>, InMemoryError> {
724 match self.get_resource(resource_type, id, context).await? {
725 Some(resource) => Ok(Some(VersionedResource::new(resource))),
726 None => Ok(None),
727 }
728 }
729
730 pub async fn create_versioned_resource(
731 &self,
732 resource_type: &str,
733 data: Value,
734 context: &RequestContext,
735 ) -> Result<VersionedResource, InMemoryError> {
736 let resource = self.create_resource(resource_type, data, context).await?;
737 Ok(VersionedResource::new(resource))
738 }
739}