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