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 BundleRemoveRequest {
41 pub bundle_path: PathBuf,
43 #[serde(default)]
45 pub packs: Vec<PackRemoveSelection>,
46 #[serde(default)]
48 pub providers: Vec<String>,
49 #[serde(default)]
51 pub tenants: Vec<TenantSelection>,
52 #[serde(default)]
54 pub dry_run: bool,
55}
56
57#[derive(Clone, Debug, Serialize, Deserialize)]
61pub struct QaSpecRequest {
62 pub bundle_path: PathBuf,
64 pub provider_id: String,
66 #[serde(default = "default_locale")]
68 pub locale: String,
69}
70
71#[derive(Clone, Debug, Serialize, Deserialize)]
73pub struct QaValidateRequest {
74 pub bundle_path: PathBuf,
76 pub provider_id: String,
78 pub answers: Value,
80}
81
82#[derive(Clone, Debug, Serialize, Deserialize)]
84pub struct QaSubmitRequest {
85 pub bundle_path: PathBuf,
87 pub provider_id: String,
89 pub tenant: String,
91 #[serde(default)]
93 pub team: Option<String>,
94 pub answers: Value,
96 #[serde(default)]
98 pub reload: bool,
99}
100
101#[derive(Clone, Debug, Serialize, Deserialize)]
105pub struct AdminResponse<T: Serialize> {
106 pub success: bool,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub data: Option<T>,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub error: Option<String>,
111}
112
113impl<T: Serialize> AdminResponse<T> {
114 pub fn ok(data: T) -> Self {
115 Self {
116 success: true,
117 data: Some(data),
118 error: None,
119 }
120 }
121
122 pub fn err(message: impl Into<String>) -> Self {
123 Self {
124 success: false,
125 data: None,
126 error: Some(message.into()),
127 }
128 }
129}
130
131#[derive(Clone, Debug, Serialize, Deserialize)]
133pub struct BundleStatusResponse {
134 pub bundle_path: PathBuf,
135 pub status: BundleStatus,
136 pub pack_count: usize,
137 pub tenant_count: usize,
138 pub provider_count: usize,
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum BundleStatus {
145 Active,
146 Inactive,
147 Deploying,
148 Stopping,
149 Removing,
150 Error,
151}
152
153#[derive(Clone, Debug, Serialize, Deserialize)]
155pub struct BundleListResponse {
156 pub bundles: Vec<BundleStatusResponse>,
157}
158
159#[derive(Clone, Debug, Serialize, Deserialize)]
161pub struct BundleStartRequest {
162 pub bundle_path: PathBuf,
163}
164
165#[derive(Clone, Debug, Serialize, Deserialize)]
167pub struct BundleStopRequest {
168 pub bundle_path: PathBuf,
169}
170
171#[derive(Clone, Debug, Serialize, Deserialize)]
173pub struct BundleUpdateRequest {
174 pub bundle_path: PathBuf,
176 #[serde(default)]
178 pub pack_refs: Vec<String>,
179 #[serde(default)]
181 pub tenants: Vec<TenantSelection>,
182 #[serde(default)]
184 pub answers: Value,
185 #[serde(default)]
187 pub dry_run: bool,
188}
189
190#[derive(Clone, Debug, Serialize, Deserialize)]
192pub struct AdminClientEntry {
193 pub client_cn: String,
194}
195
196#[derive(Clone, Debug, Serialize, Deserialize)]
198pub struct AdminClientListResponse {
199 pub admins: Vec<AdminClientEntry>,
200}
201
202#[derive(Clone, Debug, Serialize, Deserialize)]
204pub struct AdminClientAddRequest {
205 pub bundle_path: PathBuf,
206 pub client_cn: String,
207}
208
209#[derive(Clone, Debug, Serialize, Deserialize)]
211pub struct AdminClientRemoveRequest {
212 pub bundle_path: PathBuf,
213 pub client_cn: String,
214}
215
216#[derive(Clone, Debug, Serialize, Deserialize)]
218#[serde(tag = "action", rename_all = "snake_case")]
219pub enum AdminRequest {
220 Deploy(BundleDeployRequest),
221 Remove(BundleRemoveRequest),
222 QaSpec(QaSpecRequest),
223 QaValidate(QaValidateRequest),
224 QaSubmit(QaSubmitRequest),
225 Status { bundle_path: PathBuf },
226 List,
227}
228
229fn default_locale() -> String {
230 "en".to_string()
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn admin_response_ok() {
239 let resp = AdminResponse::ok("hello");
240 assert!(resp.success);
241 assert_eq!(resp.data.unwrap(), "hello");
242 assert!(resp.error.is_none());
243 }
244
245 #[test]
246 fn admin_response_err() {
247 let resp = AdminResponse::<()>::err("bad request");
248 assert!(!resp.success);
249 assert!(resp.data.is_none());
250 assert_eq!(resp.error.unwrap(), "bad request");
251 }
252
253 #[test]
254 fn deploy_request_serde_roundtrip() {
255 let req = BundleDeployRequest {
256 bundle_path: PathBuf::from("/tmp/bundle"),
257 bundle_name: Some("test".into()),
258 pack_refs: vec!["oci://test:latest".into()],
259 tenants: vec![],
260 answers: Value::Object(Default::default()),
261 dry_run: false,
262 };
263 let json = serde_json::to_string(&req).unwrap();
264 let parsed: BundleDeployRequest = serde_json::from_str(&json).unwrap();
265 assert_eq!(parsed.bundle_path, PathBuf::from("/tmp/bundle"));
266 }
267
268 #[test]
269 fn admin_request_tagged_enum() {
270 let json = r#"{"action":"list"}"#;
271 let req: AdminRequest = serde_json::from_str(json).unwrap();
272 assert!(matches!(req, AdminRequest::List));
273 }
274
275 #[test]
276 fn bundle_status_serde() {
277 let status = BundleStatus::Active;
278 let json = serde_json::to_string(&status).unwrap();
279 assert_eq!(json, "\"active\"");
280 }
281}