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