use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cell {
#[serde(default)]
pub id: String,
#[serde(default)]
pub name: String,
#[serde(default)]
pub formation_id: Option<String>,
#[serde(default)]
pub state: String,
#[serde(default)]
pub image: Option<String>,
#[serde(default)]
pub critical: Option<bool>,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub started_at: Option<String>,
#[serde(default)]
pub finished_at: Option<String>,
#[serde(default)]
pub outcome: Option<String>,
#[serde(flatten, default)]
pub extra: serde_json::Map<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Formation {
#[serde(default)]
pub id: String,
#[serde(default)]
pub name: String,
#[serde(default, alias = "status")]
pub state: String,
#[serde(default)]
pub tenant: Option<String>,
#[serde(default)]
pub cells: Vec<Cell>,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub updated_at: Option<String>,
#[serde(flatten, default)]
pub extra: serde_json::Map<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloudEvent {
#[serde(default, alias = "specversion")]
pub spec_version: Option<String>,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub source: Option<String>,
#[serde(default, alias = "type")]
pub event_type: Option<String>,
#[serde(default)]
pub time: Option<String>,
#[serde(default)]
pub subject: Option<String>,
#[serde(default)]
pub data: Option<Value>,
#[serde(flatten, default)]
pub extra: serde_json::Map<String, Value>,
}
#[derive(Debug, Clone, Serialize)]
pub struct FormationsSnapshot {
pub formations: Vec<Formation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<u64>,
}
impl<'de> serde::Deserialize<'de> for FormationsSnapshot {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let raw = serde_json::Value::deserialize(d)?;
let (formations, cursor) = decode_snapshot::<D, Formation>(raw, "formations")?;
Ok(FormationsSnapshot { formations, cursor })
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CellsSnapshot {
pub cells: Vec<Cell>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<u64>,
}
impl<'de> serde::Deserialize<'de> for CellsSnapshot {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let raw = serde_json::Value::deserialize(d)?;
let (cells, cursor) = decode_snapshot::<D, Cell>(raw, "cells")?;
Ok(CellsSnapshot { cells, cursor })
}
}
fn decode_snapshot<'de, D, T>(
raw: serde_json::Value,
canonical: &'static str,
) -> Result<(Vec<T>, Option<u64>), D::Error>
where
D: serde::Deserializer<'de>,
T: serde::de::DeserializeOwned,
{
use serde::de::Error as _;
if let serde_json::Value::Array(_) = &raw {
let items: Vec<T> = serde_json::from_value(raw).map_err(D::Error::custom)?;
return Ok((items, None));
}
let obj = raw.as_object().ok_or_else(|| {
D::Error::custom(format!(
"expected array or object envelope for {canonical} snapshot",
))
})?;
const LIST_KEYS: &[&str] = &["formations", "cells", "items"];
let present: Vec<&&str> = LIST_KEYS.iter().filter(|k| obj.contains_key(**k)).collect();
if present.len() > 1 {
let keys: Vec<&str> = present.iter().map(|k| **k).collect();
return Err(D::Error::custom(format!(
"ambiguous {canonical} snapshot envelope: multiple list keys present ({keys:?}); \
refusing to silently pick one (MED-CTL-001-A)",
)));
}
let key = if obj.contains_key(canonical) {
canonical
} else if obj.contains_key("items") {
"items"
} else {
return Ok((Vec::new(), obj.get("cursor").and_then(|v| v.as_u64())));
};
let list_value = obj.get(key).cloned().unwrap_or(serde_json::Value::Null);
let items: Vec<T> = serde_json::from_value(list_value).map_err(D::Error::custom)?;
let cursor = obj.get("cursor").and_then(|v| v.as_u64());
Ok((items, cursor))
}