1use serde::Serialize;
10use serde::de::DeserializeOwned;
11use serde_json::json;
12
13use crate::engine::{EngineError, EngineResult};
14use crate::storage::{StorageBackend, StorageEntry};
15
16pub struct ConfigRoleStore {
18 pub config_prefix: &'static str,
19 pub role_prefix: &'static str,
20}
21
22impl ConfigRoleStore {
23 pub const fn new(config_prefix: &'static str, role_prefix: &'static str) -> Self {
24 Self {
25 config_prefix,
26 role_prefix,
27 }
28 }
29
30 pub async fn load_config<C: DeserializeOwned>(
31 &self,
32 storage: &dyn StorageBackend,
33 name: &str,
34 ) -> EngineResult<Option<C>> {
35 load(storage, self.config_prefix, name).await
36 }
37
38 pub async fn load_role<R: DeserializeOwned>(
39 &self,
40 storage: &dyn StorageBackend,
41 name: &str,
42 ) -> EngineResult<Option<R>> {
43 load(storage, self.role_prefix, name).await
44 }
45
46 pub async fn require_role<R: DeserializeOwned>(
49 &self,
50 storage: &dyn StorageBackend,
51 name: &str,
52 ) -> EngineResult<R> {
53 self.load_role(storage, name).await?.ok_or(EngineError::NotFound)
54 }
55
56 pub async fn require_config<C: DeserializeOwned>(
57 &self,
58 storage: &dyn StorageBackend,
59 name: &str,
60 ) -> EngineResult<C> {
61 self.load_config(storage, name).await?.ok_or_else(|| {
62 EngineError::InvalidRequest(format!("unknown config '{name}' — POST it first"))
63 })
64 }
65
66 pub async fn handle_read<R: DeserializeOwned + Serialize>(
70 &self,
71 storage: &dyn StorageBackend,
72 path: &str,
73 ) -> EngineResult<serde_json::Value> {
74 if let Some(name) = path.strip_prefix("roles/") {
75 let role: R = self.require_role(storage, name).await?;
76 serde_json::to_value(role).map_err(|e| EngineError::Other(e.to_string()))
77 } else if let Some(name) = path.strip_prefix("config/") {
78 let exists = storage.get(&format!("{}{name}", self.config_prefix)).await?.is_some();
79 if exists {
80 Ok(json!({
81 "configured": true,
82 "note": "config is write-only — it holds this engine's root \
83 credential and is never returned. POST to replace it.",
84 }))
85 } else {
86 Err(EngineError::NotFound)
87 }
88 } else {
89 Err(EngineError::InvalidRequest(
90 "expected config/{name} or roles/{name}".into(),
91 ))
92 }
93 }
94
95 pub async fn handle_write<C, R>(
96 &self,
97 storage: &dyn StorageBackend,
98 path: &str,
99 data: serde_json::Value,
100 ) -> EngineResult<()>
101 where
102 C: DeserializeOwned + Serialize,
103 R: DeserializeOwned + Serialize,
104 {
105 if let Some(name) = path.strip_prefix("config/") {
106 let config: C =
107 serde_json::from_value(data).map_err(|e| EngineError::InvalidRequest(e.to_string()))?;
108 save(storage, self.config_prefix, name, &config).await
109 } else if let Some(name) = path.strip_prefix("roles/") {
110 let role: R =
111 serde_json::from_value(data).map_err(|e| EngineError::InvalidRequest(e.to_string()))?;
112 save(storage, self.role_prefix, name, &role).await
113 } else {
114 Err(EngineError::InvalidRequest(
115 "expected config/{name} or roles/{name}".into(),
116 ))
117 }
118 }
119
120 pub async fn handle_delete(
121 &self,
122 storage: &dyn StorageBackend,
123 path: &str,
124 ) -> EngineResult<()> {
125 if let Some(name) = path.strip_prefix("config/") {
126 storage.delete(&format!("{}{name}", self.config_prefix)).await?;
127 Ok(())
128 } else if let Some(name) = path.strip_prefix("roles/") {
129 storage.delete(&format!("{}{name}", self.role_prefix)).await?;
130 Ok(())
131 } else {
132 Err(EngineError::InvalidRequest(
133 "expected config/{name} or roles/{name}".into(),
134 ))
135 }
136 }
137
138 pub async fn handle_list(
139 &self,
140 storage: &dyn StorageBackend,
141 prefix: &str,
142 ) -> EngineResult<Vec<String>> {
143 let (storage_prefix, rest) = if let Some(rest) = prefix.strip_prefix("roles/") {
144 (self.role_prefix, rest)
145 } else if let Some(rest) = prefix.strip_prefix("config/") {
146 (self.config_prefix, rest)
147 } else {
148 return Err(EngineError::InvalidRequest(
149 "expected config/ or roles/ prefix".into(),
150 ));
151 };
152 let keys = storage.list(&format!("{storage_prefix}{rest}")).await?;
153 Ok(keys
154 .into_iter()
155 .filter_map(|k| k.strip_prefix(storage_prefix).map(|s| s.to_string()))
156 .collect())
157 }
158}
159
160async fn load<T: DeserializeOwned>(
161 storage: &dyn StorageBackend,
162 prefix: &str,
163 name: &str,
164) -> EngineResult<Option<T>> {
165 let Some(entry) = storage.get(&format!("{prefix}{name}")).await? else {
166 return Ok(None);
167 };
168 Ok(Some(
169 serde_json::from_slice(&entry.value).map_err(|e| EngineError::Other(e.to_string()))?,
170 ))
171}
172
173async fn save<T: Serialize>(
174 storage: &dyn StorageBackend,
175 prefix: &str,
176 name: &str,
177 value: &T,
178) -> EngineResult<()> {
179 let bytes = serde_json::to_vec(value).map_err(|e| EngineError::Other(e.to_string()))?;
180 storage
181 .put(
182 &format!("{prefix}{name}"),
183 StorageEntry {
184 value: bytes,
185 expires_at: None,
186 },
187 )
188 .await?;
189 Ok(())
190}