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 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 todo!("NIY")
75 }
76
77 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 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 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 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 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 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 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 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 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 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 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}