1use std::path::PathBuf;
8
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::plan::{PackRemoveSelection, TenantSelection};
13
14#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct BundleDeployRequest {
19 pub bundle_path: PathBuf,
21 #[serde(default)]
23 pub bundle_name: Option<String>,
24 #[serde(default)]
26 pub pack_refs: Vec<String>,
27 #[serde(default)]
29 pub tenants: Vec<TenantSelection>,
30 #[serde(default)]
32 pub answers: Value,
33 #[serde(default)]
35 pub dry_run: bool,
36}
37
38#[derive(Clone, Debug, Serialize, Deserialize)]
40pub struct BundleUpdateRequest {
41 pub bundle_path: PathBuf,
43 #[serde(default)]
45 pub bundle_name: Option<String>,
46 #[serde(default)]
48 pub pack_refs: Vec<String>,
49 #[serde(default)]
51 pub tenants: Vec<TenantSelection>,
52 #[serde(default)]
54 pub answers: Value,
55 #[serde(default)]
57 pub dry_run: bool,
58}
59
60#[derive(Clone, Debug, Serialize, Deserialize)]
62pub struct BundleStartRequest {
63 pub bundle_path: PathBuf,
65}
66
67#[derive(Clone, Debug, Serialize, Deserialize)]
69pub struct BundleStopRequest {
70 pub bundle_path: PathBuf,
72}
73
74#[derive(Clone, Debug, Serialize, Deserialize)]
76pub struct AdminClientAddRequest {
77 pub bundle_path: PathBuf,
79 pub client_cn: String,
81}
82
83#[derive(Clone, Debug, Serialize, Deserialize)]
85pub struct AdminClientRemoveRequest {
86 pub bundle_path: PathBuf,
88 pub client_cn: String,
90}
91
92#[derive(Clone, Debug, Serialize, Deserialize)]
94pub struct BundleRemoveRequest {
95 pub bundle_path: PathBuf,
97 #[serde(default)]
99 pub packs: Vec<PackRemoveSelection>,
100 #[serde(default)]
102 pub providers: Vec<String>,
103 #[serde(default)]
105 pub tenants: Vec<TenantSelection>,
106 #[serde(default)]
108 pub dry_run: bool,
109}
110
111#[derive(Clone, Debug, Serialize, Deserialize)]
115pub struct QaSpecRequest {
116 pub bundle_path: PathBuf,
118 pub provider_id: String,
120 #[serde(default = "default_locale")]
122 pub locale: String,
123}
124
125#[derive(Clone, Debug, Serialize, Deserialize)]
127pub struct QaValidateRequest {
128 pub bundle_path: PathBuf,
130 pub provider_id: String,
132 pub answers: Value,
134}
135
136#[derive(Clone, Debug, Serialize, Deserialize)]
138pub struct QaSubmitRequest {
139 pub bundle_path: PathBuf,
141 pub provider_id: String,
143 pub tenant: String,
145 #[serde(default)]
147 pub team: Option<String>,
148 pub answers: Value,
150 #[serde(default)]
152 pub reload: bool,
153}
154
155#[derive(Clone, Debug, Serialize, Deserialize)]
159pub struct AdminResponse<T: Serialize> {
160 pub success: bool,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub data: Option<T>,
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub error: Option<String>,
165}
166
167impl<T: Serialize> AdminResponse<T> {
168 pub fn ok(data: T) -> Self {
169 Self {
170 success: true,
171 data: Some(data),
172 error: None,
173 }
174 }
175
176 pub fn err(message: impl Into<String>) -> Self {
177 Self {
178 success: false,
179 data: None,
180 error: Some(message.into()),
181 }
182 }
183}
184
185#[derive(Clone, Debug, Serialize, Deserialize)]
187pub struct BundleStatusResponse {
188 pub bundle_path: PathBuf,
189 pub status: BundleStatus,
190 pub pack_count: usize,
191 pub tenant_count: usize,
192 pub provider_count: usize,
193}
194
195#[derive(Clone, Debug, Serialize, Deserialize)]
197pub struct BundleListResponse {
198 pub bundles: Vec<BundleStatusResponse>,
199}
200
201#[derive(Clone, Debug, Serialize, Deserialize)]
203pub struct AdminClientEntry {
204 pub client_cn: String,
205}
206
207#[derive(Clone, Debug, Serialize, Deserialize)]
209pub struct AdminClientListResponse {
210 pub admins: Vec<AdminClientEntry>,
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
215#[serde(rename_all = "snake_case")]
216pub enum BundleStatus {
217 Inactive,
218 Active,
219 Deploying,
220 Updating,
221 Stopping,
222 Stopped,
223 Removing,
224 Error,
225}
226
227#[derive(Clone, Debug, Serialize, Deserialize)]
229#[serde(tag = "action", rename_all = "snake_case")]
230pub enum AdminRequest {
231 Deploy(BundleDeployRequest),
232 Update(BundleUpdateRequest),
233 Remove(BundleRemoveRequest),
234 Start(BundleStartRequest),
235 Stop(BundleStopRequest),
236 AddAdminClient(AdminClientAddRequest),
237 RemoveAdminClient(AdminClientRemoveRequest),
238 QaSpec(QaSpecRequest),
239 QaValidate(QaValidateRequest),
240 QaSubmit(QaSubmitRequest),
241 Status { bundle_path: PathBuf },
242 List,
243}
244
245fn default_locale() -> String {
246 "en".to_string()
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn admin_response_ok() {
255 let resp = AdminResponse::ok("hello");
256 assert!(resp.success);
257 assert_eq!(resp.data.unwrap(), "hello");
258 assert!(resp.error.is_none());
259 }
260
261 #[test]
262 fn admin_response_err() {
263 let resp = AdminResponse::<()>::err("bad request");
264 assert!(!resp.success);
265 assert!(resp.data.is_none());
266 assert_eq!(resp.error.unwrap(), "bad request");
267 }
268
269 #[test]
270 fn deploy_request_serde_roundtrip() {
271 let req = BundleDeployRequest {
272 bundle_path: PathBuf::from("/tmp/bundle"),
273 bundle_name: Some("test".into()),
274 pack_refs: vec!["oci://test:latest".into()],
275 tenants: vec![],
276 answers: Value::Object(Default::default()),
277 dry_run: false,
278 };
279 let json = serde_json::to_string(&req).unwrap();
280 let parsed: BundleDeployRequest = serde_json::from_str(&json).unwrap();
281 assert_eq!(parsed.bundle_path, PathBuf::from("/tmp/bundle"));
282 }
283
284 #[test]
285 fn update_request_serde_roundtrip() {
286 let req = BundleUpdateRequest {
287 bundle_path: PathBuf::from("/tmp/bundle"),
288 bundle_name: Some("test".into()),
289 pack_refs: vec!["oci://test:latest".into()],
290 tenants: vec![],
291 answers: Value::Object(Default::default()),
292 dry_run: true,
293 };
294 let json = serde_json::to_string(&req).unwrap();
295 let parsed: BundleUpdateRequest = serde_json::from_str(&json).unwrap();
296 assert_eq!(parsed.bundle_path, PathBuf::from("/tmp/bundle"));
297 assert!(parsed.dry_run);
298 }
299
300 #[test]
301 fn admin_request_tagged_enum() {
302 let json = r#"{"action":"list"}"#;
303 let req: AdminRequest = serde_json::from_str(json).unwrap();
304 assert!(matches!(req, AdminRequest::List));
305 }
306
307 #[test]
308 fn bundle_status_serde() {
309 let status = BundleStatus::Active;
310 let json = serde_json::to_string(&status).unwrap();
311 assert_eq!(json, "\"active\"");
312 }
313
314 #[test]
315 fn bundle_status_stopped_serde() {
316 let status = BundleStatus::Stopped;
317 let json = serde_json::to_string(&status).unwrap();
318 assert_eq!(json, "\"stopped\"");
319 }
320}