use std::path::Path;
use serde_json::Value;
use crate::client::CellosClient;
use crate::exit::{CtlError, CtlResult};
use crate::model::Formation;
pub async fn run(client: &CellosClient, path: &Path) -> CtlResult<()> {
let yaml = std::fs::read_to_string(path)
.map_err(|e| CtlError::usage(format!("read {}: {e}", path.display())))?;
let body: Value = serde_yaml::from_str(&yaml)?;
validate_formation_spec(&body, path)?;
let created: Formation = client.post_json("/v1/formations", &body).await?;
let name = if !created.name.is_empty() {
&created.name
} else {
&created.id
};
println!("formation/{name} created (state={})", created.state);
Ok(())
}
fn validate_formation_spec(v: &Value, path: &Path) -> CtlResult<()> {
let Some(obj) = v.as_object() else {
return Err(CtlError::validation(format!(
"{}: top-level must be a YAML mapping",
path.display()
)));
};
if let Some(kind) = obj.get("kind").and_then(|k| k.as_str()) {
if kind != "Formation" {
return Err(CtlError::validation(format!(
"{}: kind must be \"Formation\" (got \"{kind}\")",
path.display()
)));
}
}
let has_name = obj.get("name").and_then(|n| n.as_str()).is_some()
|| obj
.get("metadata")
.and_then(|m| m.get("name"))
.and_then(|n| n.as_str())
.is_some();
if !has_name {
return Err(CtlError::validation(format!(
"{}: missing required field `name` (or `metadata.name`)",
path.display()
)));
}
Ok(())
}