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 Deploying,
147 Removing,
148 Error,
149}
150
151#[derive(Clone, Debug, Serialize, Deserialize)]
153#[serde(tag = "action", rename_all = "snake_case")]
154pub enum AdminRequest {
155 Deploy(BundleDeployRequest),
156 Remove(BundleRemoveRequest),
157 QaSpec(QaSpecRequest),
158 QaValidate(QaValidateRequest),
159 QaSubmit(QaSubmitRequest),
160 Status { bundle_path: PathBuf },
161 List,
162}
163
164fn default_locale() -> String {
165 "en".to_string()
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn admin_response_ok() {
174 let resp = AdminResponse::ok("hello");
175 assert!(resp.success);
176 assert_eq!(resp.data.unwrap(), "hello");
177 assert!(resp.error.is_none());
178 }
179
180 #[test]
181 fn admin_response_err() {
182 let resp = AdminResponse::<()>::err("bad request");
183 assert!(!resp.success);
184 assert!(resp.data.is_none());
185 assert_eq!(resp.error.unwrap(), "bad request");
186 }
187
188 #[test]
189 fn deploy_request_serde_roundtrip() {
190 let req = BundleDeployRequest {
191 bundle_path: PathBuf::from("/tmp/bundle"),
192 bundle_name: Some("test".into()),
193 pack_refs: vec!["oci://test:latest".into()],
194 tenants: vec![],
195 answers: Value::Object(Default::default()),
196 dry_run: false,
197 };
198 let json = serde_json::to_string(&req).unwrap();
199 let parsed: BundleDeployRequest = serde_json::from_str(&json).unwrap();
200 assert_eq!(parsed.bundle_path, PathBuf::from("/tmp/bundle"));
201 }
202
203 #[test]
204 fn admin_request_tagged_enum() {
205 let json = r#"{"action":"list"}"#;
206 let req: AdminRequest = serde_json::from_str(json).unwrap();
207 assert!(matches!(req, AdminRequest::List));
208 }
209
210 #[test]
211 fn bundle_status_serde() {
212 let status = BundleStatus::Active;
213 let json = serde_json::to_string(&status).unwrap();
214 assert_eq!(json, "\"active\"");
215 }
216}