mlua_swarm/store/enhance_setting/
mod.rs1pub mod sqlite;
13pub use sqlite::SqliteEnhanceSettingStore;
14
15use crate::enhance::setting::EnhanceSetting;
16use async_trait::async_trait;
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19use std::sync::Mutex;
20use thiserror::Error;
21
22#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
24pub struct EnhanceSettingId(pub String);
25
26impl EnhanceSettingId {
27 pub fn new(s: impl Into<String>) -> Self {
29 Self(s.into())
30 }
31
32 pub fn default_id() -> Self {
34 Self("default".into())
35 }
36
37 pub fn as_str(&self) -> &str {
39 &self.0
40 }
41}
42
43impl std::fmt::Display for EnhanceSettingId {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 f.write_str(&self.0)
46 }
47}
48
49#[derive(Debug, Error)]
51pub enum EnhanceSettingStoreError {
52 #[error("not found: {0}")]
54 NotFound(EnhanceSettingId),
55 #[error("other: {0}")]
58 Other(String),
59}
60
61#[async_trait]
63pub trait EnhanceSettingStore: Send + Sync {
64 fn name(&self) -> &str;
66
67 async fn get(&self, id: &EnhanceSettingId) -> Result<EnhanceSetting, EnhanceSettingStoreError>;
69
70 async fn put(
72 &self,
73 id: &EnhanceSettingId,
74 setting: EnhanceSetting,
75 ) -> Result<(), EnhanceSettingStoreError>;
76
77 async fn delete(&self, id: &EnhanceSettingId) -> Result<(), EnhanceSettingStoreError>;
79
80 async fn list(&self) -> Result<Vec<EnhanceSettingId>, EnhanceSettingStoreError>;
82}
83
84#[derive(Default)]
87pub struct InMemoryEnhanceSettingStore {
88 inner: Mutex<HashMap<EnhanceSettingId, EnhanceSetting>>,
89}
90
91impl InMemoryEnhanceSettingStore {
92 pub fn new() -> Self {
94 Self::default()
95 }
96}
97
98#[async_trait]
99impl EnhanceSettingStore for InMemoryEnhanceSettingStore {
100 fn name(&self) -> &str {
101 "in-memory"
102 }
103
104 async fn get(&self, id: &EnhanceSettingId) -> Result<EnhanceSetting, EnhanceSettingStoreError> {
105 self.inner
106 .lock()
107 .unwrap()
108 .get(id)
109 .cloned()
110 .ok_or_else(|| EnhanceSettingStoreError::NotFound(id.clone()))
111 }
112
113 async fn put(
114 &self,
115 id: &EnhanceSettingId,
116 setting: EnhanceSetting,
117 ) -> Result<(), EnhanceSettingStoreError> {
118 self.inner.lock().unwrap().insert(id.clone(), setting);
119 Ok(())
120 }
121
122 async fn delete(&self, id: &EnhanceSettingId) -> Result<(), EnhanceSettingStoreError> {
123 if self.inner.lock().unwrap().remove(id).is_none() {
124 return Err(EnhanceSettingStoreError::NotFound(id.clone()));
125 }
126 Ok(())
127 }
128
129 async fn list(&self) -> Result<Vec<EnhanceSettingId>, EnhanceSettingStoreError> {
130 Ok(self.inner.lock().unwrap().keys().cloned().collect())
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::application::VersionSelector;
138 use crate::blueprint::store::BlueprintId;
139 use crate::enhance::setting::EnhanceSettingMeta;
140
141 fn dummy_setting(id: &str, bp: &str) -> EnhanceSetting {
142 EnhanceSetting {
143 id: id.into(),
144 blueprint_id: BlueprintId::new(bp.to_string()),
145 ttl_secs: 10,
146 version: VersionSelector::default(),
147 verifier_axes: vec!["des".into()],
148 meta: EnhanceSettingMeta::default(),
149 }
150 }
151
152 #[test]
153 fn enhance_setting_id_default_is_default_literal() {
154 assert_eq!(EnhanceSettingId::default_id().as_str(), "default");
155 }
156
157 #[test]
158 fn enhance_setting_id_display_is_inner_string() {
159 let id = EnhanceSettingId::new("foo");
160 assert_eq!(format!("{id}"), "foo");
161 }
162
163 #[tokio::test]
164 async fn inmemory_put_then_get_returns_same_setting() {
165 let store = InMemoryEnhanceSettingStore::new();
166 let id = EnhanceSettingId::new("s1");
167 let s = dummy_setting("s1", "bp-1");
168 store.put(&id, s.clone()).await.unwrap();
169 let got = store.get(&id).await.unwrap();
170 assert_eq!(got.id, "s1");
171 assert_eq!(got.blueprint_id.as_str(), "bp-1");
172 }
173
174 #[tokio::test]
175 async fn inmemory_get_missing_returns_not_found() {
176 let store = InMemoryEnhanceSettingStore::new();
177 let err = store.get(&EnhanceSettingId::new("nope")).await.unwrap_err();
178 assert!(matches!(err, EnhanceSettingStoreError::NotFound(_)));
179 }
180
181 #[tokio::test]
182 async fn inmemory_delete_missing_returns_not_found() {
183 let store = InMemoryEnhanceSettingStore::new();
184 let err = store
185 .delete(&EnhanceSettingId::new("nope"))
186 .await
187 .unwrap_err();
188 assert!(matches!(err, EnhanceSettingStoreError::NotFound(_)));
189 }
190
191 #[tokio::test]
192 async fn inmemory_put_then_delete_then_get_is_not_found() {
193 let store = InMemoryEnhanceSettingStore::new();
194 let id = EnhanceSettingId::new("s2");
195 store.put(&id, dummy_setting("s2", "bp-x")).await.unwrap();
196 store.delete(&id).await.unwrap();
197 assert!(matches!(
198 store.get(&id).await.unwrap_err(),
199 EnhanceSettingStoreError::NotFound(_)
200 ));
201 }
202
203 #[tokio::test]
204 async fn inmemory_list_returns_all_inserted_ids() {
205 let store = InMemoryEnhanceSettingStore::new();
206 store
207 .put(&EnhanceSettingId::new("a"), dummy_setting("a", "bp-a"))
208 .await
209 .unwrap();
210 store
211 .put(&EnhanceSettingId::new("b"), dummy_setting("b", "bp-b"))
212 .await
213 .unwrap();
214 let mut ids: Vec<String> = store
215 .list()
216 .await
217 .unwrap()
218 .into_iter()
219 .map(|i| i.0)
220 .collect();
221 ids.sort();
222 assert_eq!(ids, vec!["a", "b"]);
223 }
224
225 #[tokio::test]
226 async fn inmemory_put_overwrites_existing_setting() {
227 let store = InMemoryEnhanceSettingStore::new();
228 let id = EnhanceSettingId::new("s3");
229 store.put(&id, dummy_setting("s3", "bp-old")).await.unwrap();
230 store.put(&id, dummy_setting("s3", "bp-new")).await.unwrap();
231 let got = store.get(&id).await.unwrap();
232 assert_eq!(got.blueprint_id.as_str(), "bp-new");
233 }
234
235 #[tokio::test]
236 async fn inmemory_name_is_in_memory() {
237 let store = InMemoryEnhanceSettingStore::new();
238 assert_eq!(store.name(), "in-memory");
239 }
240}