1use std::sync::Mutex;
8
9use async_trait::async_trait;
10use lino_objects_codec::LinoValue;
11
12use crate::collection::apply_collection_query;
13use crate::problem::LinoHttpError;
14use crate::query::CollectionQuery;
15use crate::value::{as_object, get, merge, set};
16
17#[async_trait]
23pub trait Store: Send + Sync {
24 fn id_field(&self) -> &str {
26 "id"
27 }
28
29 async fn list(&self, query: &CollectionQuery) -> Result<LinoValue, LinoHttpError>;
31
32 async fn get(&self, identifier: &str) -> Result<Option<LinoValue>, LinoHttpError>;
34
35 async fn create(&self, body: &LinoValue) -> Result<LinoValue, LinoHttpError>;
37
38 async fn update(
40 &self,
41 identifier: &str,
42 body: &LinoValue,
43 ) -> Result<Option<LinoValue>, LinoHttpError>;
44
45 async fn patch(
47 &self,
48 identifier: &str,
49 body: &LinoValue,
50 ) -> Result<Option<LinoValue>, LinoHttpError>;
51
52 async fn remove(&self, identifier: &str) -> Result<bool, LinoHttpError>;
54}
55
56pub fn normalize_id(identifier: &str) -> LinoValue {
68 match identifier.parse::<i64>() {
69 Ok(number) if !identifier.is_empty() => LinoValue::Int(number),
70 _ => LinoValue::String(identifier.to_string()),
71 }
72}
73
74fn normalize_value_id(identifier: &LinoValue) -> LinoValue {
76 match identifier {
77 LinoValue::String(text) => normalize_id(text),
78 other => other.clone(),
79 }
80}
81
82pub fn id_to_string(identifier: &LinoValue) -> String {
84 crate::value::to_query_text(identifier)
85}
86
87#[derive(Debug, Default)]
92pub struct MemoryStore {
93 id_field: String,
94 state: Mutex<MemoryState>,
95}
96
97#[derive(Debug, Default)]
98struct MemoryState {
99 items: Vec<(LinoValue, LinoValue)>,
100 next_id: i64,
101}
102
103impl MemoryStore {
104 pub fn new() -> Self {
106 Self {
107 id_field: "id".to_string(),
108 state: Mutex::new(MemoryState {
109 items: Vec::new(),
110 next_id: 1,
111 }),
112 }
113 }
114
115 pub fn with_id_field<S: Into<String>>(id_field: S) -> Self {
117 Self {
118 id_field: id_field.into(),
119 ..Self::new()
120 }
121 }
122
123 pub fn seeded<I: IntoIterator<Item = LinoValue>>(items: I) -> Self {
135 let store = Self::new();
136 for item in items {
137 store.insert(&item);
138 }
139 store
140 }
141
142 pub fn insert(&self, body: &LinoValue) -> LinoValue {
144 let mut state = self.state.lock().expect("store is not poisoned");
145 let identifier = match get(body, &self.id_field) {
146 Some(LinoValue::Null) | None => {
147 let identifier = LinoValue::Int(state.next_id);
148 state.next_id += 1;
149 identifier
150 }
151 Some(provided) => normalize_value_id(provided),
152 };
153 if let LinoValue::Int(number) = identifier {
154 if number >= state.next_id {
155 state.next_id = number + 1;
156 }
157 }
158
159 let mut item = if matches!(body, LinoValue::Object(_)) {
160 body.clone()
161 } else {
162 LinoValue::Object(Vec::new())
163 };
164 set(&mut item, self.id_field.clone(), identifier.clone());
165
166 match state.items.iter_mut().find(|(key, _)| *key == identifier) {
167 Some(entry) => entry.1 = item.clone(),
168 None => state.items.push((identifier, item.clone())),
169 }
170 item
171 }
172
173 pub fn read(&self, identifier: &str) -> Option<LinoValue> {
175 let key = normalize_id(identifier);
176 let state = self.state.lock().expect("store is not poisoned");
177 state
178 .items
179 .iter()
180 .find(|(existing, _)| *existing == key)
181 .map(|(_, item)| item.clone())
182 }
183
184 pub fn items(&self) -> Vec<LinoValue> {
186 let state = self.state.lock().expect("store is not poisoned");
187 state.items.iter().map(|(_, item)| item.clone()).collect()
188 }
189
190 pub fn len(&self) -> usize {
192 self.state
193 .lock()
194 .expect("store is not poisoned")
195 .items
196 .len()
197 }
198
199 pub fn is_empty(&self) -> bool {
201 self.len() == 0
202 }
203
204 pub fn clear(&self) {
206 let mut state = self.state.lock().expect("store is not poisoned");
207 state.items.clear();
208 state.next_id = 1;
209 }
210
211 fn replace(&self, identifier: &str, item: LinoValue) -> Option<LinoValue> {
213 let key = normalize_id(identifier);
214 let mut state = self.state.lock().expect("store is not poisoned");
215 let entry = state
216 .items
217 .iter_mut()
218 .find(|(existing, _)| *existing == key)?;
219 entry.1 = item.clone();
220 Some(item)
221 }
222}
223
224#[async_trait]
225impl Store for MemoryStore {
226 fn id_field(&self) -> &str {
227 &self.id_field
228 }
229
230 async fn list(&self, query: &CollectionQuery) -> Result<LinoValue, LinoHttpError> {
231 Ok(apply_collection_query(&self.items(), query))
232 }
233
234 async fn get(&self, identifier: &str) -> Result<Option<LinoValue>, LinoHttpError> {
235 Ok(self.read(identifier))
236 }
237
238 async fn create(&self, body: &LinoValue) -> Result<LinoValue, LinoHttpError> {
239 Ok(self.insert(body))
240 }
241
242 async fn update(
243 &self,
244 identifier: &str,
245 body: &LinoValue,
246 ) -> Result<Option<LinoValue>, LinoHttpError> {
247 if self.read(identifier).is_none() {
248 return Ok(None);
249 }
250 let mut item = if as_object(body).is_some() {
251 body.clone()
252 } else {
253 LinoValue::Object(Vec::new())
254 };
255 set(&mut item, self.id_field.clone(), normalize_id(identifier));
256 Ok(self.replace(identifier, item))
257 }
258
259 async fn patch(
260 &self,
261 identifier: &str,
262 body: &LinoValue,
263 ) -> Result<Option<LinoValue>, LinoHttpError> {
264 let Some(existing) = self.read(identifier) else {
265 return Ok(None);
266 };
267 let mut item = merge(&existing, body);
268 set(&mut item, self.id_field.clone(), normalize_id(identifier));
269 Ok(self.replace(identifier, item))
270 }
271
272 async fn remove(&self, identifier: &str) -> Result<bool, LinoHttpError> {
273 let key = normalize_id(identifier);
274 let mut state = self.state.lock().expect("store is not poisoned");
275 match state
276 .items
277 .iter()
278 .position(|(existing, _)| *existing == key)
279 {
280 Some(index) => {
281 state.items.remove(index);
282 Ok(true)
283 }
284 None => Ok(false),
285 }
286 }
287}