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<B: AsRef<str>>(&self, bucket: B) -> Result<Arc<dyn Adapter + Send + Sync>> {
25 let model_name = pluralize(bucket.as_ref(), 1, false);
27 if let Some(adapter) = self.adapters.get(&model_name) {
28 return Ok(adapter.clone());
29 }
30
31 const APPLICATION: &str = "application";
33 if model_name != APPLICATION
34 && let Some(adapter) = self.adapters.get(APPLICATION)
35 {
36 return Ok(adapter.clone());
37 }
38
39 Err(anyhow!("No such adapter"))
40 }
41
42 pub async fn create_record<M>(&self, model: &M) -> Result<M>
44 where
45 M: ResourceObject,
46 {
47 let model_name = pluralize(model.resource_type(), 1, false);
48 let adapter = self.adapter_for(&model_name)?;
49 let resource: Resource = serde_json::from_value(serde_json::to_value(model)?)?;
50 let finalized_resource = adapter.create_record(resource).await?;
51 let json = serde_json::to_string(&finalized_resource)?;
52 let model: M = serde_json::from_str(&json)?;
53 Ok(model)
54 }
55
56 pub async fn delete_record<M>(&self, model: &M) -> Result<()>
58 where
59 M: ResourceObject,
60 {
61 let model_name = pluralize(model.resource_type(), 1, false);
62 let adapter = self.adapter_for(&model_name)?;
63 let resource: Resource = serde_json::from_value(serde_json::to_value(model)?)?;
64 adapter.delete_record(&resource).await?;
65 Ok(())
66 }
67
68 pub async fn find_all<B: AsRef<str>, M>(&self, bucket: B, _options: Option<FindAllOptions>) -> Result<Vec<M>>
70 where
71 M: ResourceObject,
72 {
73 let collection = bucket.as_ref();
74 let model_name = pluralize(collection, 1, false);
75 let adapter = self.adapter_for(&model_name)?;
76 let query = json!({});
77 let resources = adapter.query(collection, query).await?;
78 let json = serde_json::to_string(&resources)?;
79 let models: Vec<M> = serde_json::from_str(&json)?;
80 Ok(models)
81 }
82
83 pub async fn find_record<B: AsRef<str>, M>(&self, bucket: B, id: &str, _options: Option<FindOptions>) -> Result<M>
85 where
86 M: ResourceObject,
87 {
88 let collection = bucket.as_ref();
89 let model_name = pluralize(collection, 1, false);
90 let adapter = self.adapter_for(&model_name)?;
91 let resource = adapter.find_record(collection, id).await?;
92 let json = serde_json::to_string(&resource)?;
93 let model: M = serde_json::from_str(&json)?;
94 Ok(model)
95 }
96
97 pub fn peek_all<B: AsRef<str>, M>(&self, bucket: B) -> Result<Vec<M>>
99 where
100 M: ResourceObject,
101 {
102 let collection = bucket.as_ref();
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<B: AsRef<str>, M>(&self, bucket: B, id: &str) -> Result<Option<M>>
119 where
120 M: ResourceObject,
121 {
122 let collection = bucket.as_ref();
123 match self.cache.get(collection) {
124 None => Ok(None),
125 Some(resources) => match resources.get(id) {
126 None => Ok(None),
127 Some(resource) => {
128 let json = serde_json::to_string(resource)?;
129 let model: M = serde_json::from_str(&json)?;
130 Ok(Some(model))
131 }
132 },
133 }
134 }
135
136 pub fn push<M>(&mut self, model: M) -> Result<()>
138 where
139 M: ResourceObject,
140 {
141 let collection = model.resource_type();
142 let id = model.resource_id().unwrap_or_default().to_string();
143 let resource: Resource = serde_json::from_value(serde_json::to_value(&model)?)?;
144 match self.cache.get_mut(collection) {
145 None => {
146 self.cache
147 .insert(collection.to_string(), HashMap::from([(id, resource)]));
148 }
149 Some(map) => {
150 map.insert(id, resource);
151 }
152 }
153 Ok(())
154 }
155
156 pub async fn query<B: AsRef<str>, M>(&self, bucket: B, query: Value) -> Result<Vec<M>>
158 where
159 M: ResourceObject,
160 {
161 let collection = bucket.as_ref();
162 let model_name = pluralize(collection, 1, false);
163 let adapter = self.adapter_for(&model_name)?;
164 let resources = adapter.query(collection, 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<B: AsRef<str>, M>(
176 &self,
177 bucket: B,
178 query: Value,
179 options: Option<QueryRecordOptions>,
180 ) -> Result<Option<M>>
181 where
182 M: ResourceObject,
183 {
184 let collection = bucket.as_ref();
185 let model_name = pluralize(collection, 1, false);
186 let adapter = self.adapter_for(&model_name)?;
187 let resource = adapter
188 .query_record(collection, query, options.unwrap_or_default())
189 .await?;
190 let model: Option<M> = match resource {
191 None => None,
192 Some(resource) => {
193 let json = serde_json::to_string(&resource)?;
194 serde_json::from_str(&json)?
195 }
196 };
197 Ok(model)
198 }
199
200 pub fn unload_all<B: AsRef<str>>(&mut self, bucket: B) -> Result<()> {
202 let collection = bucket.as_ref();
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 use strum_macros::AsRefStr;
238
239 #[derive(AsRefStr, Debug)]
240 enum Bucket {
241 #[strum(serialize = "customers")]
242 Customers,
243 }
244
245 #[derive(Clone, Debug, JsonApi, PartialEq)]
246 #[jsonapi(type = "customers")]
247 struct Customer {
248 #[jsonapi(id)]
249 id: String,
250 }
251
252 #[tokio::test]
253 async fn cannot_peek_non_existing() {
254 let store = Store::default();
255 let maybe_customer = store
256 .peek_record::<_, Customer>(Bucket::Customers, "123")
257 .expect("Expected to peek customer record");
258 assert_eq!(maybe_customer, None);
259 }
260
261 #[tokio::test]
262 async fn can_push_then_peek_record() {
263 let mut store = Store::default();
264 let customer = Customer { id: "123".to_string() };
265 store.push(customer.clone()).expect("Expected to push customer record");
266 let maybe_customer = store
267 .peek_record::<_, Customer>(Bucket::Customers, "123")
268 .expect("Expected to peek customer record");
269 assert_eq!(maybe_customer, Some(customer));
270 }
271
272 #[tokio::test]
273 async fn can_push_then_peek_all() {
274 let mut store = Store::default();
275 let customer_1 = Customer { id: "123".to_string() };
276 store
277 .push(customer_1.clone())
278 .expect("Expected to push customer record");
279 let customer_2 = Customer { id: "456".to_string() };
280 store
281 .push(customer_2.clone())
282 .expect("Expected to push customer record");
283 let customers = store
284 .peek_all::<_, Customer>(Bucket::Customers)
285 .expect("Expected to peek at customer records");
286 assert_eq!(customers.len(), 2);
287 }
288
289 #[tokio::test]
290 async fn can_push_then_unload_all() {
291 let mut store = Store::default();
292 let customer = Customer { id: "123".to_string() };
293 store.push(customer.clone()).expect("Expected to push customer record");
294 assert!(
295 store
296 .peek_record::<_, Customer>(Bucket::Customers, "123")
297 .expect("Expected to peek customer record")
298 .is_some()
299 );
300 store
301 .unload_all(Bucket::Customers)
302 .expect("Expected to unload all customer records");
303 assert!(
304 store
305 .peek_record::<_, Customer>(Bucket::Customers, "123")
306 .expect("Expected to peek customer record")
307 .is_none()
308 );
309 }
310
311 #[tokio::test]
312 async fn can_push_then_unload_record() {
313 let mut store = Store::default();
314 let customer_1 = Customer { id: "123".to_string() };
315 let customer_2 = Customer { id: "456".to_string() };
316 store
317 .push(customer_1.clone())
318 .expect("Expected to push customer record");
319 store
320 .push(customer_2.clone())
321 .expect("Expected to push customer record");
322 assert!(
323 store
324 .peek_record::<_, Customer>(Bucket::Customers, "123")
325 .expect("Expected to peek customer record")
326 .is_some()
327 );
328 assert!(
329 store
330 .peek_record::<_, Customer>(Bucket::Customers, "456")
331 .expect("Expected to peek customer record")
332 .is_some()
333 );
334 store
335 .unload_record(&customer_2)
336 .expect("Expected to unload customer record");
337 assert!(
338 store
339 .peek_record::<_, Customer>(Bucket::Customers, "123")
340 .expect("Expected to peek customer record")
341 .is_some()
342 );
343 assert!(
344 store
345 .peek_record::<_, Customer>(Bucket::Customers, "456")
346 .expect("Expected to peek customer record")
347 .is_none()
348 );
349 }
350
351 #[tokio::test]
352 async fn cannot_find_non_existing_adapter() {
353 let store = Store::default();
354 let maybe_adapter = store.adapter_for("foo");
355 assert!(maybe_adapter.is_err());
356 }
357
358 #[tokio::test]
359 async fn can_find_adapter() {
360 #[derive(Default)]
361 struct FooAdapter {}
362 #[async_trait(?Send)]
363 impl Adapter for FooAdapter {
364 fn init(&mut self, _config: &Config) -> anyhow::Result<()> {
365 Ok(())
366 }
367 async fn create_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
368 todo!()
369 }
370 async fn delete_record(&self, _resource: &Resource) -> anyhow::Result<()> {
371 todo!()
372 }
373 async fn find_record(&self, _resource_type: &str, _id: &str) -> anyhow::Result<Resource> {
374 todo!()
375 }
376 async fn query(&self, _resource_type: &str, _query: Value) -> anyhow::Result<Vec<Resource>> {
377 todo!()
378 }
379 async fn query_record(
380 &self,
381 _resource_type: &str,
382 _query: Value,
383 _options: QueryRecordOptions,
384 ) -> anyhow::Result<Option<Resource>> {
385 todo!()
386 }
387 async fn update_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
388 todo!()
389 }
390 }
391 const MODEL: &str = "foo";
392 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(FooAdapter::default());
393 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
394 let store = Store::new(adapters);
395 let maybe_adapter = store.adapter_for(MODEL);
396 assert!(maybe_adapter.is_ok());
397 }
398
399 #[tokio::test]
400 async fn can_default_to_application_adapter() {
401 #[derive(Default)]
402 struct ApplicationAdapter {}
403 #[async_trait(?Send)]
404 impl Adapter for ApplicationAdapter {
405 fn init(&mut self, _config: &Config) -> anyhow::Result<()> {
406 Ok(())
407 }
408 async fn create_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
409 todo!()
410 }
411 async fn delete_record(&self, _resource: &Resource) -> anyhow::Result<()> {
412 todo!()
413 }
414 async fn find_record(&self, _resource_type: &str, _id: &str) -> anyhow::Result<Resource> {
415 todo!()
416 }
417 async fn query(&self, _resource_type: &str, _query: Value) -> anyhow::Result<Vec<Resource>> {
418 todo!()
419 }
420 async fn query_record(
421 &self,
422 _resource_type: &str,
423 _query: Value,
424 _options: QueryRecordOptions,
425 ) -> anyhow::Result<Option<Resource>> {
426 todo!()
427 }
428 async fn update_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
429 todo!()
430 }
431 }
432 const MODEL: &str = "application";
433 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(ApplicationAdapter::default());
434 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
435 let store = Store::new(adapters);
436 let maybe_adapter = store.adapter_for("foo");
437 assert!(maybe_adapter.is_ok());
438 }
439
440 #[tokio::test]
441 async fn can_create_record() {
442 let server = Server::run();
444 server.expect(
445 Expectation::matching(request::method_path("POST", "/v1/customers")).respond_with(json_encoded(
446 json!({"customer": {"id": "123", "name": "Acme Widgets 2"}}),
447 )),
448 );
449 let server_url = server.url_str("/v1");
450
451 #[derive(Clone, JsonApi)]
452 #[jsonapi(type = "customers")]
453 struct Customer {
454 #[jsonapi(id)]
455 id: String,
456 name: String,
457 }
458
459 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
460 const MODEL: &str = "customer";
461 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
462 let store = Store::new(adapters);
463 let _ = store.adapter_for(MODEL);
464 let customer = Customer {
465 id: String::new(),
466 name: "Acme Widgets".to_string(),
467 };
468 let finalized_customer: Customer = store.create_record(&customer).await.expect("Expected to find customer");
469 assert_eq!(finalized_customer.id, "123".to_string());
470 assert_eq!(finalized_customer.name, "Acme Widgets 2".to_string());
471 }
472
473 #[tokio::test]
474 async fn can_delete_record() {
475 let server = Server::run();
477 server.expect(
478 Expectation::matching(request::method_path("DELETE", "/v1/customers/123")).respond_with(status_code(200)),
479 );
480 let server_url = server.url_str("/v1");
481
482 #[derive(Clone, JsonApi)]
483 #[jsonapi(type = "customers")]
484 struct Customer {
485 #[jsonapi(id)]
486 id: String,
487 name: String,
488 }
489
490 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
491 const MODEL: &str = "customer";
492 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
493 let store = Store::new(adapters);
494 let _ = store.adapter_for(MODEL);
495 let customer = Customer {
496 id: "123".to_string(),
497 name: "Acme Widgets".to_string(),
498 };
499 store.delete_record(&customer).await.expect("Epected to delete record");
500 }
501
502 #[tokio::test]
503 async fn can_find_all() {
504 let server = Server::run();
506 server.expect(
507 Expectation::matching(request::method_path("GET", "/v1/customers")).respond_with(json_encoded(
508 json!({"customers": [
509 {"id": "123", "name": "Acme Widgets"},
510 {"id": "456", "name": "Standard Paper Supplies, Inc."},
511 ]}),
512 )),
513 );
514 let server_url = server.url_str("/v1");
515
516 #[derive(Debug, JsonApi)]
517 #[jsonapi(type = "customers")]
518 struct Customer {
519 #[jsonapi(id)]
520 id: String,
521 name: String,
522 }
523
524 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
525 const MODEL: &str = "customer";
526 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
527 let store = Store::new(adapters);
528 let _ = store.adapter_for(MODEL);
529 let customers: Vec<Customer> = store
530 .find_all(Bucket::Customers, None)
531 .await
532 .expect("Expected to find customers");
533 assert_eq!(customers.len(), 2);
534 }
535
536 #[tokio::test]
537 async fn can_find_record() {
538 let server = Server::run();
540 server.expect(
541 Expectation::matching(request::method_path("GET", "/v1/customers/123"))
542 .respond_with(json_encoded(json!({"customer": {"id": "123", "name": "Acme Widgets"}}))),
543 );
544 let server_url = server.url_str("/v1");
545
546 #[derive(Debug, JsonApi)]
547 #[jsonapi(type = "customers")]
548 struct Customer {
549 #[jsonapi(id)]
550 id: String,
551 name: String,
552 }
553
554 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
555 const MODEL: &str = "customer";
556 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
557 let store = Store::new(adapters);
558 let _ = store.adapter_for(MODEL);
559 let customer: Customer = store
560 .find_record(Bucket::Customers, "123", None)
561 .await
562 .expect("Expected to find customer");
563 assert_eq!(customer.id, "123".to_string());
564 assert_eq!(customer.name, "Acme Widgets".to_string());
565 }
566}