Skip to main content

auric_runtime/data/
store.rs

1use super::{Adapter, FindAllOptions, FindOptions, QueryRecordOptions};
2use anyhow::{Result, anyhow};
3use jsonapi_core::{Resource, ResourceObject};
4use pluralizer::pluralize;
5use serde_json::Value;
6use std::collections::HashMap;
7use std::sync::Arc;
8
9#[derive(Default)]
10pub struct Store {
11    adapters: HashMap<String, Arc<dyn Adapter + Send + Sync>>,
12    cache: HashMap<String, HashMap<String, Resource>>,
13}
14
15impl Store {
16    pub fn new(adapters: HashMap<String, Arc<dyn Adapter + Send + Sync>>) -> Self {
17        Self {
18            adapters,
19            cache: HashMap::new(),
20        }
21    }
22
23    /// Returns an instance of the adapter for the given model name
24    pub fn adapter_for(&self, model_name: &str) -> Result<Arc<dyn Adapter>> {
25        // Find an adapter for the model name
26        if let Some(adapter) = self.adapters.get(model_name) {
27            return Ok(adapter.clone());
28        }
29
30        // Default to an application adapter
31        const APPLICATION: &str = "application";
32        if model_name != APPLICATION
33            && let Some(adapter) = self.adapters.get(APPLICATION)
34        {
35            return Ok(adapter.clone());
36        }
37
38        Err(anyhow!("No such adapter"))
39    }
40
41    /// Create a new record in the current store
42    pub async fn create_record<M>(&self, model: &M) -> Result<M>
43    where
44        M: ResourceObject,
45    {
46        let model_name = pluralize(model.resource_type(), 1, false);
47        let adapter = self.adapter_for(&model_name)?;
48        let resource: Resource = serde_json::from_value(serde_json::to_value(model)?)?;
49        let finalized_resource = adapter.create_record(resource).await?;
50        let json = serde_json::to_string(&finalized_resource)?;
51        let model: M = serde_json::from_str(&json)?;
52        Ok(model)
53    }
54
55    /// Delete a record
56    pub async fn delete_record<M>(&self, model: &M) -> Result<()>
57    where
58        M: ResourceObject,
59    {
60        let model_name = pluralize(model.resource_type(), 1, false);
61        let adapter = self.adapter_for(&model_name)?;
62        let resource: Resource = serde_json::from_value(serde_json::to_value(model)?)?;
63        adapter.delete_record(&resource).await?;
64        Ok(())
65    }
66
67    /// Find all the records for the given resource type
68    pub async fn find_all<M>(&self, resource_type: &str, _options: Option<FindAllOptions>) -> Result<Vec<M>>
69    where
70        M: ResourceObject,
71    {
72        assert_eq!(resource_type, pluralize(resource_type, 1, false));
73        let _adapter = self.adapter_for(resource_type)?;
74        todo!("NIY")
75    }
76
77    /// Find a single record
78    pub async fn find_record<M>(&self, resource_type: &str, id: &str, _options: Option<FindOptions>) -> Result<M>
79    where
80        M: ResourceObject,
81    {
82        assert_eq!(resource_type, pluralize(resource_type, 1, false));
83        let adapter = self.adapter_for(resource_type)?;
84        let collection = pluralize(resource_type, 2, false);
85        let resource = adapter.find_record(&collection, id).await?;
86        let json = serde_json::to_string(&resource)?;
87        let model: M = serde_json::from_str(&json)?;
88        Ok(model)
89    }
90
91    /// Check for all records of a given resource type, without triggering a fetch
92    pub fn peek_all<M>(&self, resource_type: &str) -> Result<Vec<M>>
93    where
94        M: ResourceObject,
95    {
96        assert_eq!(resource_type, pluralize(resource_type, 1, false));
97        let collection = pluralize(resource_type, 2, false);
98        match self.cache.get(&collection) {
99            None => Ok(vec![]),
100            Some(resources) => {
101                let mut v = Vec::new();
102                for resource in resources.values() {
103                    let json = serde_json::to_string(resource)?;
104                    let model: M = serde_json::from_str(&json)?;
105                    v.push(model);
106                }
107                Ok(v)
108            }
109        }
110    }
111
112    /// Check for a single record without triggering a fetch
113    pub fn peek_record<M>(&self, resource_type: &str, id: &str) -> Result<Option<M>>
114    where
115        M: ResourceObject,
116    {
117        assert_eq!(resource_type, pluralize(resource_type, 1, false));
118        let collection = pluralize(resource_type, 2, false);
119        match self.cache.get(&collection) {
120            None => Ok(None),
121            Some(resources) => match resources.get(id) {
122                None => Ok(None),
123                Some(resource) => {
124                    let json = serde_json::to_string(resource)?;
125                    let model: M = serde_json::from_str(&json)?;
126                    Ok(Some(model))
127                }
128            },
129        }
130    }
131
132    /// Push a model into the store
133    pub fn push<M>(&mut self, model: M) -> Result<()>
134    where
135        M: ResourceObject,
136    {
137        let collection = model.resource_type();
138        let id = model.resource_id().unwrap_or_default().to_string();
139        let resource: Resource = serde_json::from_value(serde_json::to_value(&model)?)?;
140        match self.cache.get_mut(collection) {
141            None => {
142                self.cache
143                    .insert(collection.to_string(), HashMap::from([(id, resource)]));
144            }
145            Some(map) => {
146                map.insert(id, resource);
147            }
148        }
149        Ok(())
150    }
151
152    /// Delegate a query to the adapter
153    pub async fn query<M>(&self, resource_type: &str, query: Value) -> Result<Vec<M>>
154    where
155        M: ResourceObject,
156    {
157        assert_eq!(resource_type, pluralize(resource_type, 1, false));
158        let adapter = self.adapter_for(resource_type)?;
159        let resources = adapter.query(resource_type, query).await?;
160        let mut models = Vec::with_capacity(resources.len());
161        for resource in resources {
162            let json = serde_json::to_string(&resource)?;
163            let model: M = serde_json::from_str(&json)?;
164            models.push(model);
165        }
166        Ok(models)
167    }
168
169    /// Delegate the request for a single record to the adapter
170    pub async fn query_record<M>(
171        &self,
172        resource_type: &str,
173        query: Value,
174        options: Option<QueryRecordOptions>,
175    ) -> Result<Option<M>>
176    where
177        M: ResourceObject,
178    {
179        assert_eq!(resource_type, pluralize(resource_type, 1, false));
180        let adapter = self.adapter_for(resource_type)?;
181        let resource = adapter
182            .query_record(resource_type, query, options.unwrap_or_default())
183            .await?;
184        let model: Option<M> = match resource {
185            None => None,
186            Some(resource) => {
187                let json = serde_json::to_string(&resource)?;
188                serde_json::from_str(&json)?
189            }
190        };
191        Ok(model)
192    }
193
194    /// Unloads all records in the store for the given resource type
195    pub fn unload_all(&mut self, resource_type: &str) -> Result<()> {
196        assert_eq!(resource_type, pluralize(resource_type, 1, false));
197        let collection = pluralize(resource_type, 2, false);
198        if let Some(map) = self.cache.get_mut(&collection) {
199            map.clear();
200        }
201        Ok(())
202    }
203
204    /// Unload a single record from the store
205    pub fn unload_record<M>(&mut self, model: &M) -> Result<()>
206    where
207        M: ResourceObject,
208    {
209        let collection = model.resource_type();
210        if let Some(map) = self.cache.get_mut(collection) {
211            let id = model.resource_id().unwrap_or_default().to_string();
212            map.remove(id.as_str());
213        }
214        Ok(())
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::Store;
221    use crate::Config;
222    use crate::data::adapters::RestAdapter;
223    use crate::data::{Adapter, QueryRecordOptions};
224    use async_trait::async_trait;
225    use httptest::matchers::request;
226    use httptest::responders::{json_encoded, status_code};
227    use httptest::{Expectation, Server};
228    use jsonapi_core::{JsonApi, Resource};
229    use serde_json::{Value, json};
230    use std::collections::HashMap;
231    use std::sync::Arc;
232
233    #[derive(Clone, Debug, JsonApi, PartialEq)]
234    #[jsonapi(type = "customers")]
235    struct Customer {
236        #[jsonapi(id)]
237        id: String,
238    }
239
240    #[tokio::test]
241    async fn cannot_peek_non_existing() {
242        let store = Store::default();
243        let maybe_customer = store
244            .peek_record::<Customer>("customer", "123")
245            .expect("Expected to peek customer record");
246        assert_eq!(maybe_customer, None);
247    }
248
249    #[tokio::test]
250    async fn can_push_then_peek_record() {
251        let mut store = Store::default();
252        let customer = Customer { id: "123".to_string() };
253        store.push(customer.clone()).expect("Expected to push customer record");
254        let maybe_customer = store
255            .peek_record::<Customer>("customer", "123")
256            .expect("Expected to peek customer record");
257        assert_eq!(maybe_customer, Some(customer));
258    }
259
260    #[tokio::test]
261    async fn can_push_then_peek_all() {
262        let mut store = Store::default();
263        let customer_1 = Customer { id: "123".to_string() };
264        store
265            .push(customer_1.clone())
266            .expect("Expected to push customer record");
267        let customer_2 = Customer { id: "456".to_string() };
268        store
269            .push(customer_2.clone())
270            .expect("Expected to push customer record");
271        let customers = store
272            .peek_all::<Customer>("customer")
273            .expect("Expected to peek at customer records");
274        assert_eq!(customers.len(), 2);
275    }
276
277    #[tokio::test]
278    async fn can_push_then_unload_all() {
279        let mut store = Store::default();
280        let customer = Customer { id: "123".to_string() };
281        store.push(customer.clone()).expect("Expected to push customer record");
282        assert!(
283            store
284                .peek_record::<Customer>("customer", "123")
285                .expect("Expected to peek customer record")
286                .is_some()
287        );
288        store
289            .unload_all("customer")
290            .expect("Expected to unload all customer records");
291        assert!(
292            store
293                .peek_record::<Customer>("customer", "123")
294                .expect("Expected to peek customer record")
295                .is_none()
296        );
297    }
298
299    #[tokio::test]
300    async fn can_push_then_unload_record() {
301        let mut store = Store::default();
302        let customer_1 = Customer { id: "123".to_string() };
303        let customer_2 = Customer { id: "456".to_string() };
304        store
305            .push(customer_1.clone())
306            .expect("Expected to push customer record");
307        store
308            .push(customer_2.clone())
309            .expect("Expected to push customer record");
310        assert!(
311            store
312                .peek_record::<Customer>("customer", "123")
313                .expect("Expected to peek customer record")
314                .is_some()
315        );
316        assert!(
317            store
318                .peek_record::<Customer>("customer", "456")
319                .expect("Expected to peek customer record")
320                .is_some()
321        );
322        store
323            .unload_record(&customer_2)
324            .expect("Expected to unload customer record");
325        assert!(
326            store
327                .peek_record::<Customer>("customer", "123")
328                .expect("Expected to peek customer record")
329                .is_some()
330        );
331        assert!(
332            store
333                .peek_record::<Customer>("customer", "456")
334                .expect("Expected to peek customer record")
335                .is_none()
336        );
337    }
338
339    #[tokio::test]
340    async fn cannot_find_non_existing_adapter() {
341        let store = Store::default();
342        let maybe_adapter = store.adapter_for("foo");
343        assert!(maybe_adapter.is_err());
344    }
345
346    #[tokio::test]
347    async fn can_find_adapter() {
348        #[derive(Default)]
349        struct FooAdapter {}
350        #[async_trait(?Send)]
351        impl Adapter for FooAdapter {
352            fn init(&mut self, _config: &Config) -> anyhow::Result<()> {
353                Ok(())
354            }
355            async fn create_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
356                todo!()
357            }
358            async fn delete_record(&self, _resource: &Resource) -> anyhow::Result<()> {
359                todo!()
360            }
361            async fn find_record(&self, _resource_type: &str, _id: &str) -> anyhow::Result<Resource> {
362                todo!()
363            }
364            async fn query(&self, _resource_type: &str, _query: Value) -> anyhow::Result<Vec<Resource>> {
365                todo!()
366            }
367            async fn query_record(
368                &self,
369                _resource_type: &str,
370                _query: Value,
371                _options: QueryRecordOptions,
372            ) -> anyhow::Result<Option<Resource>> {
373                todo!()
374            }
375            async fn update_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
376                todo!()
377            }
378        }
379        const MODEL: &str = "foo";
380        let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(FooAdapter::default());
381        let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
382        let store = Store::new(adapters);
383        let maybe_adapter = store.adapter_for(MODEL);
384        assert!(maybe_adapter.is_ok());
385    }
386
387    #[tokio::test]
388    async fn can_default_to_application_adapter() {
389        #[derive(Default)]
390        struct ApplicationAdapter {}
391        #[async_trait(?Send)]
392        impl Adapter for ApplicationAdapter {
393            fn init(&mut self, _config: &Config) -> anyhow::Result<()> {
394                Ok(())
395            }
396            async fn create_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
397                todo!()
398            }
399            async fn delete_record(&self, _resource: &Resource) -> anyhow::Result<()> {
400                todo!()
401            }
402            async fn find_record(&self, _resource_type: &str, _id: &str) -> anyhow::Result<Resource> {
403                todo!()
404            }
405            async fn query(&self, _resource_type: &str, _query: Value) -> anyhow::Result<Vec<Resource>> {
406                todo!()
407            }
408            async fn query_record(
409                &self,
410                _resource_type: &str,
411                _query: Value,
412                _options: QueryRecordOptions,
413            ) -> anyhow::Result<Option<Resource>> {
414                todo!()
415            }
416            async fn update_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
417                todo!()
418            }
419        }
420        const MODEL: &str = "application";
421        let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(ApplicationAdapter::default());
422        let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
423        let store = Store::new(adapters);
424        let maybe_adapter = store.adapter_for("foo");
425        assert!(maybe_adapter.is_ok());
426    }
427
428    #[tokio::test]
429    async fn can_create_record() {
430        // Setup mock server and expections for a find Customer operation
431        let server = Server::run();
432        server.expect(
433            Expectation::matching(request::method_path("POST", "/v1/customers")).respond_with(json_encoded(
434                json!({"customer": {"id": "123", "name": "Acme Widgets 2"}}),
435            )),
436        );
437        let server_url = server.url_str("/v1");
438
439        #[derive(Clone, JsonApi)]
440        #[jsonapi(type = "customers")]
441        struct Customer {
442            #[jsonapi(id)]
443            id: String,
444            name: String,
445        }
446
447        let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
448        const MODEL: &str = "customer";
449        let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
450        let store = Store::new(adapters);
451        let _ = store.adapter_for(MODEL);
452        let customer = Customer {
453            id: String::new(),
454            name: "Acme Widgets".to_string(),
455        };
456        let finalized_customer: Customer = store.create_record(&customer).await.expect("Expected to find customer");
457        assert_eq!(finalized_customer.id, "123".to_string());
458        assert_eq!(finalized_customer.name, "Acme Widgets 2".to_string());
459    }
460
461    #[tokio::test]
462    async fn can_delete_record() {
463        // Setup mock server and expections for a find Customer operation
464        let server = Server::run();
465        server.expect(
466            Expectation::matching(request::method_path("DELETE", "/v1/customers/123")).respond_with(status_code(200)),
467        );
468        let server_url = server.url_str("/v1");
469
470        #[derive(Clone, JsonApi)]
471        #[jsonapi(type = "customers")]
472        struct Customer {
473            #[jsonapi(id)]
474            id: String,
475            name: String,
476        }
477
478        let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
479        const MODEL: &str = "customer";
480        let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
481        let store = Store::new(adapters);
482        let _ = store.adapter_for(MODEL);
483        let customer = Customer {
484            id: "123".to_string(),
485            name: "Acme Widgets".to_string(),
486        };
487        store.delete_record(&customer).await.expect("Epected to delete record");
488    }
489
490    #[tokio::test]
491    async fn can_find_record() {
492        // Setup mock server and expections for a find Customer operation
493        let server = Server::run();
494        server.expect(
495            Expectation::matching(request::method_path("GET", "/v1/customers/123"))
496                .respond_with(json_encoded(json!({"customer": {"id": "123", "name": "Acme Widgets"}}))),
497        );
498        let server_url = server.url_str("/v1");
499
500        #[derive(Debug, JsonApi)]
501        #[jsonapi(type = "customers")]
502        struct Customer {
503            #[jsonapi(id)]
504            id: String,
505            name: String,
506        }
507
508        let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
509        const MODEL: &str = "customer";
510        let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
511        let store = Store::new(adapters);
512        let _ = store.adapter_for(MODEL);
513        let customer: Customer = store
514            .find_record(MODEL, "123", None)
515            .await
516            .expect("Expected to find customer");
517        assert_eq!(customer.id, "123".to_string());
518        assert_eq!(customer.name, "Acme Widgets".to_string());
519    }
520}