Skip to main content

rullst_orm/polyglot/
mock.rs

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
14/// Deterministic offline document adapter used directly or by remote drivers.
15pub 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    /// Creates an empty offline store.
53    pub fn new() -> Self {
54        Self {
55            documents: Arc::new(RwLock::new(BTreeMap::new())),
56            marker: PhantomData,
57        }
58    }
59
60    /// Declares the adapter's bounded behavior.
61    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}