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 pub fn adapter_for(&self, model_name: &str) -> Result<Arc<dyn Adapter>> {
25 if let Some(adapter) = self.adapters.get(model_name) {
27 return Ok(adapter.clone());
28 }
29
30 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}