1use std::{collections::BTreeMap, marker::PhantomData, sync::Arc};
2
3use async_trait::async_trait;
4use serde::{Serialize, de::DeserializeOwned};
5use tokio::sync::RwLock;
6
7use super::{
8 Backend, BackendCapabilities, Capability, CollectionName, DocumentEntry, DocumentId,
9 DocumentInventory, DocumentPage, DocumentRepository, PolyglotError,
10};
11
12type DocumentKey = (String, String);
13
14pub struct MockDocumentStore<T> {
16 documents: Arc<RwLock<BTreeMap<DocumentKey, Vec<u8>>>>,
17 marker: PhantomData<fn() -> T>,
18}
19
20#[async_trait]
21impl<T> DocumentInventory<T> for MockDocumentStore<T>
22where
23 T: Serialize + DeserializeOwned + Send + Sync,
24{
25 async fn list_entries(
26 &self,
27 collection: &CollectionName,
28 page: DocumentPage,
29 ) -> Result<Vec<DocumentEntry<T>>, PolyglotError> {
30 let documents = self.documents.read().await;
31 let offset =
32 usize::try_from(page.offset()).map_err(|_| PolyglotError::InvalidIdentifier {
33 kind: "document page",
34 reason: "offset exceeds the target index range",
35 })?;
36 documents
37 .iter()
38 .filter(|((stored_collection, _), _)| stored_collection == collection.as_str())
39 .skip(offset)
40 .take(page.limit() as usize)
41 .map(|((_, id), encoded)| {
42 let id = DocumentId::new(id.clone())?;
43 let entity =
44 serde_json::from_slice(encoded).map_err(PolyglotError::serialization)?;
45 Ok(DocumentEntry::new(id, entity))
46 })
47 .collect()
48 }
49}
50
51impl<T> MockDocumentStore<T> {
52 pub fn new() -> Self {
54 Self {
55 documents: Arc::new(RwLock::new(BTreeMap::new())),
56 marker: PhantomData,
57 }
58 }
59
60 pub const fn capabilities() -> BackendCapabilities {
62 BackendCapabilities::new(Backend::Mock, &[Capability::Documents])
63 }
64}
65
66impl<T> Clone for MockDocumentStore<T> {
67 fn clone(&self) -> Self {
68 Self {
69 documents: Arc::clone(&self.documents),
70 marker: PhantomData,
71 }
72 }
73}
74
75impl<T> Default for MockDocumentStore<T> {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81#[async_trait]
82impl<T> DocumentRepository<T> for MockDocumentStore<T>
83where
84 T: Serialize + DeserializeOwned + Send + Sync,
85{
86 async fn create(
87 &self,
88 collection: &CollectionName,
89 id: &DocumentId,
90 entity: &T,
91 ) -> Result<(), PolyglotError> {
92 let key = key(collection, id);
93 let encoded = serde_json::to_vec(entity).map_err(PolyglotError::serialization)?;
94 let mut documents = self.documents.write().await;
95 if documents.contains_key(&key) {
96 return Err(PolyglotError::Conflict);
97 }
98 documents.insert(key, encoded);
99 Ok(())
100 }
101
102 async fn find(
103 &self,
104 collection: &CollectionName,
105 id: &DocumentId,
106 ) -> Result<Option<T>, PolyglotError> {
107 let documents = self.documents.read().await;
108 documents
109 .get(&key(collection, id))
110 .map(|encoded| serde_json::from_slice(encoded).map_err(PolyglotError::serialization))
111 .transpose()
112 }
113
114 async fn replace(
115 &self,
116 collection: &CollectionName,
117 id: &DocumentId,
118 entity: &T,
119 ) -> Result<(), PolyglotError> {
120 let key = key(collection, id);
121 let encoded = serde_json::to_vec(entity).map_err(PolyglotError::serialization)?;
122 let mut documents = self.documents.write().await;
123 let Some(stored) = documents.get_mut(&key) else {
124 return Err(PolyglotError::NotFound);
125 };
126 *stored = encoded;
127 Ok(())
128 }
129
130 async fn delete(
131 &self,
132 collection: &CollectionName,
133 id: &DocumentId,
134 ) -> Result<bool, PolyglotError> {
135 Ok(self
136 .documents
137 .write()
138 .await
139 .remove(&key(collection, id))
140 .is_some())
141 }
142
143 async fn list(
144 &self,
145 collection: &CollectionName,
146 page: DocumentPage,
147 ) -> Result<Vec<T>, PolyglotError> {
148 let documents = self.documents.read().await;
149 let offset =
150 usize::try_from(page.offset()).map_err(|_| PolyglotError::InvalidIdentifier {
151 kind: "document page",
152 reason: "offset exceeds the target index range",
153 })?;
154 documents
155 .iter()
156 .filter(|((stored_collection, _), _)| stored_collection == collection.as_str())
157 .skip(offset)
158 .take(page.limit() as usize)
159 .map(|(_, encoded)| {
160 serde_json::from_slice(encoded).map_err(PolyglotError::serialization)
161 })
162 .collect()
163 }
164}
165
166fn key(collection: &CollectionName, id: &DocumentId) -> DocumentKey {
167 (collection.as_str().to_owned(), id.as_str().to_owned())
168}
169
170#[cfg(test)]
171mod tests {
172 use serde::{Deserialize, Serialize};
173
174 use super::*;
175
176 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
177 struct Event {
178 sequence: u64,
179 label: String,
180 }
181
182 fn event(sequence: u64) -> Event {
183 Event {
184 sequence,
185 label: format!("event-{sequence}"),
186 }
187 }
188
189 #[tokio::test]
190 async fn provides_deterministic_document_crud() {
191 let store = MockDocumentStore::<Event>::new();
192 let collection = CollectionName::new("events").unwrap();
193 let first = DocumentId::new("01").unwrap();
194 let second = DocumentId::new("02").unwrap();
195
196 store.create(&collection, &second, &event(2)).await.unwrap();
197 store.create(&collection, &first, &event(1)).await.unwrap();
198 assert!(matches!(
199 store.create(&collection, &first, &event(3)).await,
200 Err(PolyglotError::Conflict)
201 ));
202
203 assert_eq!(
204 store.find(&collection, &first).await.unwrap(),
205 Some(event(1))
206 );
207 store
208 .replace(&collection, &first, &event(10))
209 .await
210 .unwrap();
211 assert_eq!(
212 store
213 .list(&collection, DocumentPage::new(0, 1).unwrap())
214 .await
215 .unwrap(),
216 vec![event(10)]
217 );
218 assert!(store.delete(&collection, &first).await.unwrap());
219 assert!(!store.delete(&collection, &first).await.unwrap());
220 assert!(matches!(
221 store.replace(&collection, &first, &event(1)).await,
222 Err(PolyglotError::NotFound)
223 ));
224 }
225}