1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use std::collections::HashMap;

use crate::application::{ReadRepository, Repository};
use crate::domain::{AggregateRoot, Entity};

/// An in-memory implementation of [Repository], using a [HashMap].
///
/// # Examples
///
/// ```
/// use ddd_rs::application::{ReadRepository, Repository};
/// use ddd_rs::infrastructure::InMemoryRepository;
///
/// // By definition, only `AggregateRoot`s have repositories.
/// //
/// // Common entities must be retrieved and persisted through their associated aggregate roots.
/// #[derive(ddd_rs::AggregateRoot, ddd_rs::Entity, Clone)]
/// struct MyEntity {
///     #[entity(id)]
///     id: u32,
///     my_field: String,
/// }
///
/// impl MyEntity {
///     pub fn new(id: u32, my_field: impl ToString) -> Self {
///         Self {
///             id,
///             my_field: my_field.to_string(),
///         }
///     }
/// }
///
/// # tokio_test::block_on(async {
/// let repository: InMemoryRepository<MyEntity> = InMemoryRepository::new();
///
/// // Add some entities to the repository.
/// repository.add(MyEntity::new(1, "foo")).await.unwrap();
/// repository.add(MyEntity::new(2, "bar")).await.unwrap();
/// repository.add(MyEntity::new(3, "baz")).await.unwrap();
///
/// // Attempt to retrieve an entity by its ID.
/// let my_entity_2 = repository.get_by_id(2).await.unwrap();
///
/// assert!(my_entity_2.is_some());
/// assert_eq!(my_entity_2.as_ref().map(|e| e.my_field.as_str()), Some("bar"));
///
/// let mut my_entity_2 = my_entity_2.unwrap();
///
/// // Update the entity, then persist its changes.
/// my_entity_2.my_field = "qux".to_string();
///
/// let my_entity_2 = repository.update(my_entity_2).await.unwrap();
///
/// assert_eq!(my_entity_2.my_field.as_str(), "qux");
///
/// // Delete the entity permanently.
/// repository.delete(my_entity_2).await.unwrap();
///
/// // Assert it no longer exists.
/// assert!(!repository.exists(2).await.unwrap());
/// # })
/// ```
pub struct InMemoryRepository<T: AggregateRoot> {
    entities: std::sync::RwLock<HashMap<<T as Entity>::Id, T>>,
}

impl<T: AggregateRoot> InMemoryRepository<T> {
    /// Creates a new [InMemoryRepository].
    pub fn new() -> Self {
        Self {
            entities: std::sync::RwLock::new(HashMap::new()),
        }
    }
}

impl<T: AggregateRoot> Default for InMemoryRepository<T> {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait::async_trait]
impl<T: AggregateRoot + Clone> ReadRepository<T> for InMemoryRepository<T>
where
    <T as Entity>::Id: std::hash::Hash + Eq,
{
    async fn get_by_id(&self, id: <T as Entity>::Id) -> crate::Result<Option<T>> {
        let ro_entities = self.entities.read().unwrap();

        let entity = ro_entities.get(&id).cloned();

        Ok(entity)
    }

    async fn list(&self, skip: usize, take: usize) -> crate::Result<Vec<T>> {
        let ro_entities = self.entities.read().unwrap();

        let entities = ro_entities
            .values()
            .skip(skip)
            .take(take)
            .cloned()
            .collect();

        Ok(entities)
    }

    async fn count(&self) -> crate::Result<usize> {
        let ro_entities = self.entities.read().unwrap();

        Ok(ro_entities.len())
    }
}

#[async_trait::async_trait]
impl<T: AggregateRoot + Clone> Repository<T> for InMemoryRepository<T>
where
    <T as Entity>::Id: std::hash::Hash + Eq,
{
    async fn add(&self, entity: T) -> crate::Result<T> {
        let mut wo_entities = self.entities.write().unwrap();

        wo_entities.insert(entity.id().clone(), entity.clone());

        Ok(entity)
    }

    async fn update(&self, entity: T) -> crate::Result<T> {
        self.add(entity).await
    }

    async fn delete(&self, entity: T) -> crate::Result<()> {
        let mut wo_entities = self.entities.write().unwrap();

        wo_entities.remove(entity.id());

        Ok(())
    }
}