use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::plan::{PackRemoveSelection, TenantSelection};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BundleDeployRequest {
pub bundle_path: PathBuf,
#[serde(default)]
pub bundle_name: Option<String>,
#[serde(default)]
pub pack_refs: Vec<String>,
#[serde(default)]
pub tenants: Vec<TenantSelection>,
#[serde(default)]
pub answers: Value,
#[serde(default)]
pub dry_run: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BundleRemoveRequest {
pub bundle_path: PathBuf,
#[serde(default)]
pub packs: Vec<PackRemoveSelection>,
#[serde(default)]
pub providers: Vec<String>,
#[serde(default)]
pub tenants: Vec<TenantSelection>,
#[serde(default)]
pub dry_run: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct QaSpecRequest {
pub bundle_path: PathBuf,
pub provider_id: String,
#[serde(default = "default_locale")]
pub locale: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct QaValidateRequest {
pub bundle_path: PathBuf,
pub provider_id: String,
pub answers: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct QaSubmitRequest {
pub bundle_path: PathBuf,
pub provider_id: String,
pub tenant: String,
#[serde(default)]
pub team: Option<String>,
pub answers: Value,
#[serde(default)]
pub reload: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AdminResponse<T: Serialize> {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl<T: Serialize> AdminResponse<T> {
pub fn ok(data: T) -> Self {
Self {
success: true,
data: Some(data),
error: None,
}
}
pub fn err(message: impl Into<String>) -> Self {
Self {
success: false,
data: None,
error: Some(message.into()),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BundleStatusResponse {
pub bundle_path: PathBuf,
pub status: BundleStatus,
pub pack_count: usize,
pub tenant_count: usize,
pub provider_count: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BundleStatus {
Active,
Inactive,
Deploying,
Stopping,
Removing,
Error,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BundleListResponse {
pub bundles: Vec<BundleStatusResponse>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BundleStartRequest {
pub bundle_path: PathBuf,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BundleStopRequest {
pub bundle_path: PathBuf,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BundleUpdateRequest {
pub bundle_path: PathBuf,
#[serde(default)]
pub pack_refs: Vec<String>,
#[serde(default)]
pub tenants: Vec<TenantSelection>,
#[serde(default)]
pub answers: Value,
#[serde(default)]
pub dry_run: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AdminClientEntry {
pub client_cn: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AdminClientListResponse {
pub admins: Vec<AdminClientEntry>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AdminClientAddRequest {
pub bundle_path: PathBuf,
pub client_cn: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AdminClientRemoveRequest {
pub bundle_path: PathBuf,
pub client_cn: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum AdminRequest {
Deploy(BundleDeployRequest),
Remove(BundleRemoveRequest),
QaSpec(QaSpecRequest),
QaValidate(QaValidateRequest),
QaSubmit(QaSubmitRequest),
Status { bundle_path: PathBuf },
List,
}
fn default_locale() -> String {
"en".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn admin_response_ok() {
let resp = AdminResponse::ok("hello");
assert!(resp.success);
assert_eq!(resp.data.unwrap(), "hello");
assert!(resp.error.is_none());
}
#[test]
fn admin_response_err() {
let resp = AdminResponse::<()>::err("bad request");
assert!(!resp.success);
assert!(resp.data.is_none());
assert_eq!(resp.error.unwrap(), "bad request");
}
#[test]
fn deploy_request_serde_roundtrip() {
let req = BundleDeployRequest {
bundle_path: PathBuf::from("/tmp/bundle"),
bundle_name: Some("test".into()),
pack_refs: vec!["oci://test:latest".into()],
tenants: vec![],
answers: Value::Object(Default::default()),
dry_run: false,
};
let json = serde_json::to_string(&req).unwrap();
let parsed: BundleDeployRequest = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.bundle_path, PathBuf::from("/tmp/bundle"));
}
#[test]
fn admin_request_tagged_enum() {
let json = r#"{"action":"list"}"#;
let req: AdminRequest = serde_json::from_str(json).unwrap();
assert!(matches!(req, AdminRequest::List));
}
#[test]
fn bundle_status_serde() {
let status = BundleStatus::Active;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"active\"");
}
}