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 attributes: Value = serde_json::to_value(&model)?;
49 let resource = Resource {
50 type_: model.resource_type().to_string(),
51 id: model.resource_id().map(|id| id.to_string()),
52 lid: None,
53 attributes,
54 relationships: Default::default(),
55 links: None,
56 meta: None,
57 };
58 let finalized_resource = adapter.create_record(resource).await?;
59 let json = serde_json::to_string(&finalized_resource)?;
60 let model: M = serde_json::from_str(&json)?;
61 Ok(model)
62 }
63
64 pub async fn delete_record<M>(&self, model: M) -> Result<()>
66 where
67 M: ResourceObject,
68 {
69 let model_name = pluralize(model.resource_type(), 1, false);
70 let adapter = self.adapter_for(&model_name)?;
71 let attributes: Value = serde_json::to_value(&model)?;
72 let resource = Resource {
73 type_: model.resource_type().to_string(),
74 id: model.resource_id().map(|id| id.to_string()),
75 lid: None,
76 attributes,
77 relationships: Default::default(),
78 links: None,
79 meta: None,
80 };
81 adapter.delete_record(&resource).await?;
82 Ok(())
83 }
84
85 pub async fn find_all<M>(&self, resource_type: &str, _options: Option<FindAllOptions>) -> Result<Vec<M>>
87 where
88 M: ResourceObject,
89 {
90 assert_eq!(resource_type, pluralize(resource_type, 1, false));
91 let _adapter = self.adapter_for(resource_type)?;
92 todo!("NIY")
93 }
94
95 pub async fn find_record<M>(&self, resource_type: &str, id: &str, _options: Option<FindOptions>) -> Result<M>
97 where
98 M: ResourceObject,
99 {
100 assert_eq!(resource_type, pluralize(resource_type, 1, false));
101 let adapter = self.adapter_for(resource_type)?;
102 let collection = pluralize(resource_type, 2, false);
103 let resource = adapter.find_record(&collection, id).await?;
104 let json = serde_json::to_string(&resource)?;
105 let model: M = serde_json::from_str(&json)?;
106 Ok(model)
107 }
108
109 pub fn peek_record<M>(&self, resource_type: &str, id: &str) -> Result<Option<M>>
111 where
112 M: ResourceObject,
113 {
114 assert_eq!(resource_type, pluralize(resource_type, 1, false));
115 let collection = pluralize(resource_type, 2, false);
116 match self.cache.get(&collection) {
117 None => Ok(None),
118 Some(resources) => match resources.get(id) {
119 None => Ok(None),
120 Some(resource) => {
121 let json = serde_json::to_string(resource)?;
122 let model: M = serde_json::from_str(&json)?;
123 Ok(Some(model))
124 }
125 },
126 }
127 }
128
129 pub fn push<M>(&mut self, model: M) -> Result<()>
131 where
132 M: ResourceObject,
133 {
134 let collection = model.resource_type();
135 let id = model.resource_id().unwrap_or_default().to_string();
136 let json = serde_json::to_string(&model)?;
137 let resource: Resource = serde_json::from_str(&json)?;
138 match self.cache.get_mut(collection) {
139 None => {
140 self.cache
141 .insert(collection.to_string(), HashMap::from([(id, resource)]));
142 }
143 Some(map) => {
144 map.insert(id, resource);
145 }
146 }
147 Ok(())
148 }
149
150 pub async fn query<M>(&self, resource_type: &str, _query: serde_json::Value) -> Result<Vec<M>>
152 where
153 M: ResourceObject,
154 {
155 assert_eq!(resource_type, pluralize(resource_type, 1, false));
156 let _adapter = self.adapter_for(resource_type)?;
157 todo!("NIY")
158 }
159
160 pub async fn query_record<M>(
162 &self,
163 resource_type: &str,
164 _query: serde_json::Value,
165 _options: Option<QueryRecordOptions>,
166 ) -> Result<Vec<M>>
167 where
168 M: ResourceObject,
169 {
170 assert_eq!(resource_type, pluralize(resource_type, 1, false));
171 let _adapter = self.adapter_for(resource_type)?;
172 todo!("NIY")
173 }
174
175 pub fn unload_all(&mut self, resource_type: &str) -> Result<()> {
177 assert_eq!(resource_type, pluralize(resource_type, 1, false));
178 let collection = pluralize(resource_type, 2, false);
179 if let Some(map) = self.cache.get_mut(&collection) {
180 map.clear();
181 }
182 Ok(())
183 }
184
185 pub fn unload_record<M>(&mut self, model: &M) -> Result<()>
187 where
188 M: ResourceObject,
189 {
190 let collection = model.resource_type();
191 if let Some(map) = self.cache.get_mut(collection) {
192 let id = model.resource_id().unwrap_or_default().to_string();
193 map.remove(id.as_str());
194 }
195 Ok(())
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::Store;
202 use crate::data::adapters::RestAdapter;
203 use crate::data::{Adapter, QueryRecordOptions};
204 use async_trait::async_trait;
205 use httptest::matchers::request;
206 use httptest::responders::{json_encoded, status_code};
207 use httptest::{Expectation, Server};
208 use jsonapi_core::{JsonApi, Resource};
209 use serde_json::{Value, json};
210 use std::collections::HashMap;
211 use std::sync::Arc;
212
213 #[derive(Clone, Debug, JsonApi, PartialEq)]
214 #[jsonapi(type = "customers")]
215 struct Customer {
216 #[jsonapi(id)]
217 id: String,
218 }
219
220 #[tokio::test]
221 async fn cannot_peek_non_existing() {
222 let store = Store::default();
223 let maybe_customer = store.peek_record::<Customer>("customer", "123").unwrap();
224 assert_eq!(maybe_customer, None);
225 }
226
227 #[tokio::test]
228 async fn can_push_then_peek_record() {
229 let mut store = Store::default();
230 let customer = Customer { id: "123".to_string() };
231 store.push(customer.clone()).unwrap();
232 let maybe_customer = store.peek_record::<Customer>("customer", "123").unwrap();
233 assert_eq!(maybe_customer, Some(customer));
234 }
235
236 #[tokio::test]
237 async fn can_push_then_unload_all() {
238 let mut store = Store::default();
239 let customer = Customer { id: "123".to_string() };
240 store.push(customer.clone()).unwrap();
241 assert!(store.peek_record::<Customer>("customer", "123").unwrap().is_some());
242 store.unload_all("customer").unwrap();
243 assert!(store.peek_record::<Customer>("customer", "123").unwrap().is_none());
244 }
245
246 #[tokio::test]
247 async fn can_push_then_unload_record() {
248 let mut store = Store::default();
249 let customer_1 = Customer { id: "123".to_string() };
250 let customer_2 = Customer { id: "456".to_string() };
251 store.push(customer_1.clone()).unwrap();
252 store.push(customer_2.clone()).unwrap();
253 assert!(store.peek_record::<Customer>("customer", "123").unwrap().is_some());
254 assert!(store.peek_record::<Customer>("customer", "456").unwrap().is_some());
255 store.unload_record(&customer_2).unwrap();
256 assert!(store.peek_record::<Customer>("customer", "123").unwrap().is_some());
257 assert!(store.peek_record::<Customer>("customer", "456").unwrap().is_none());
258 }
259
260 #[tokio::test]
261 async fn cannot_find_non_existing_adapter() {
262 let store = Store::default();
263 let maybe_adapter = store.adapter_for("foo");
264 assert!(maybe_adapter.is_err());
265 }
266
267 #[tokio::test]
268 async fn can_find_adapter() {
269 #[derive(Default)]
270 struct FooAdapter {}
271 #[async_trait(?Send)]
272 impl Adapter for FooAdapter {
273 async fn create_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
274 todo!()
275 }
276 async fn delete_record(&self, _resource: &Resource) -> anyhow::Result<()> {
277 todo!()
278 }
279 async fn find_record(&self, _resource_type: &str, _id: &str) -> anyhow::Result<Resource> {
280 todo!()
281 }
282 async fn query(&self, _resource_type: &str, _query: Value) -> anyhow::Result<Vec<Resource>> {
283 todo!()
284 }
285 async fn query_record(
286 &self,
287 _resource_type: &str,
288 _query: Value,
289 _options: QueryRecordOptions,
290 ) -> anyhow::Result<Resource> {
291 todo!()
292 }
293 async fn update_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
294 todo!()
295 }
296 }
297 const MODEL: &str = "foo";
298 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(FooAdapter::default());
299 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
300 let store = Store::new(adapters);
301 let maybe_adapter = store.adapter_for(MODEL);
302 assert!(maybe_adapter.is_ok());
303 }
304
305 #[tokio::test]
306 async fn can_default_to_application_adapter() {
307 #[derive(Default)]
308 struct ApplicationAdapter {}
309 #[async_trait(?Send)]
310 impl Adapter for ApplicationAdapter {
311 async fn create_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
312 todo!()
313 }
314 async fn delete_record(&self, _resource: &Resource) -> anyhow::Result<()> {
315 todo!()
316 }
317 async fn find_record(&self, _resource_type: &str, _id: &str) -> anyhow::Result<Resource> {
318 todo!()
319 }
320 async fn query(&self, _resource_type: &str, _query: Value) -> anyhow::Result<Vec<Resource>> {
321 todo!()
322 }
323 async fn query_record(
324 &self,
325 _resource_type: &str,
326 _query: Value,
327 _options: QueryRecordOptions,
328 ) -> anyhow::Result<Resource> {
329 todo!()
330 }
331 async fn update_record(&self, _resource: Resource) -> anyhow::Result<Resource> {
332 todo!()
333 }
334 }
335 const MODEL: &str = "application";
336 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(ApplicationAdapter::default());
337 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
338 let store = Store::new(adapters);
339 let maybe_adapter = store.adapter_for("foo");
340 assert!(maybe_adapter.is_ok());
341 }
342
343 #[tokio::test]
344 async fn can_create_record() {
345 let server = Server::run();
347 server.expect(
348 Expectation::matching(request::method_path("POST", "/v1/customers")).respond_with(json_encoded(
349 json!({"customer": {"id": "123", "name": "Acme Widgets 2"}}),
350 )),
351 );
352 let server_url = server.url_str("/v1");
353
354 #[derive(Clone, JsonApi)]
355 #[jsonapi(type = "customers")]
356 struct Customer {
357 #[jsonapi(id)]
358 id: String,
359 name: String,
360 }
361
362 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
363 const MODEL: &str = "customer";
364 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
365 let store = Store::new(adapters);
366 let _ = store.adapter_for(MODEL);
367 let customer = Customer {
368 id: String::new(),
369 name: "Acme Widgets".to_string(),
370 };
371 let finalized_customer: Customer = store.create_record(customer.clone()).await.unwrap();
372 assert_eq!(finalized_customer.id, "123".to_string());
373 assert_eq!(finalized_customer.name, "Acme Widgets 2".to_string());
374 }
375
376 #[tokio::test]
377 async fn can_delete_record() {
378 let server = Server::run();
380 server.expect(
381 Expectation::matching(request::method_path("DELETE", "/v1/customers/123")).respond_with(status_code(200)),
382 );
383 let server_url = server.url_str("/v1");
384
385 #[derive(Clone, JsonApi)]
386 #[jsonapi(type = "customers")]
387 struct Customer {
388 #[jsonapi(id)]
389 id: String,
390 name: String,
391 }
392
393 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
394 const MODEL: &str = "customer";
395 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
396 let store = Store::new(adapters);
397 let _ = store.adapter_for(MODEL);
398 let customer = Customer {
399 id: "123".to_string(),
400 name: "Acme Widgets".to_string(),
401 };
402 store.delete_record(customer).await.unwrap();
403 }
404
405 #[tokio::test]
406 async fn can_find_record() {
407 let server = Server::run();
409 server.expect(
410 Expectation::matching(request::method_path("GET", "/v1/customers/123"))
411 .respond_with(json_encoded(json!({"customer": {"id": "123", "name": "Acme Widgets"}}))),
412 );
413 let server_url = server.url_str("/v1");
414
415 #[derive(Debug, JsonApi)]
416 #[jsonapi(type = "customers")]
417 struct Customer {
418 #[jsonapi(id)]
419 id: String,
420 name: String,
421 }
422
423 let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
424 const MODEL: &str = "customer";
425 let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
426 let store = Store::new(adapters);
427 let _ = store.adapter_for(MODEL);
428 let customer: Customer = store.find_record(MODEL, "123", None).await.unwrap();
429 assert_eq!(customer.id, "123".to_string());
430 assert_eq!(customer.name, "Acme Widgets".to_string());
431 }
432}