Skip to main content

cellos_ctl/
model.rs

1//! Wire-level types for the cellos-server HTTP API.
2//!
3//! These mirror the projector's response shape. cellctl is a thin client — these
4//! types exist only to deserialize responses and re-serialize for `--output json`.
5//! No client-side derivations, no state caching.
6//!
7//! Fields are flexible (`#[serde(default)]`) so that adding fields server-side
8//! does not break older clients. This is the same compatibility contract kubectl
9//! has with the kube-apiserver.
10
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14/// Cell view returned by `GET /v1/cells` / `GET /v1/cells/<id>`.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Cell {
17    #[serde(default)]
18    pub id: String,
19    #[serde(default)]
20    pub name: String,
21    #[serde(default)]
22    pub formation_id: Option<String>,
23    /// PENDING | ADMITTED | RUNNING | COMPLETED | FAILED | KILLED
24    #[serde(default)]
25    pub state: String,
26    #[serde(default)]
27    pub image: Option<String>,
28    #[serde(default)]
29    pub critical: Option<bool>,
30    #[serde(default)]
31    pub created_at: Option<String>,
32    #[serde(default)]
33    pub started_at: Option<String>,
34    #[serde(default)]
35    pub finished_at: Option<String>,
36    /// Most-recent CloudEvent outcome, if known.
37    #[serde(default)]
38    pub outcome: Option<String>,
39    /// Arbitrary extra fields the server may attach.
40    #[serde(flatten, default)]
41    pub extra: serde_json::Map<String, Value>,
42}
43
44/// Formation view returned by `GET /v1/formations` / `GET /v1/formations/<id>`.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Formation {
47    #[serde(default)]
48    pub id: String,
49    #[serde(default)]
50    pub name: String,
51    /// PENDING | LAUNCHING | RUNNING | DEGRADED | COMPLETED | FAILED
52    #[serde(default)]
53    pub state: String,
54    #[serde(default)]
55    pub tenant: Option<String>,
56    #[serde(default)]
57    pub cells: Vec<Cell>,
58    #[serde(default)]
59    pub created_at: Option<String>,
60    #[serde(default)]
61    pub updated_at: Option<String>,
62    #[serde(flatten, default)]
63    pub extra: serde_json::Map<String, Value>,
64}
65
66/// CloudEvent envelope — projector emits these on the stream and on
67/// `GET /v1/cells/<id>/events` plus the `/ws/events` socket.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct CloudEvent {
70    #[serde(default, alias = "specversion")]
71    pub spec_version: Option<String>,
72    #[serde(default)]
73    pub id: Option<String>,
74    #[serde(default)]
75    pub source: Option<String>,
76    #[serde(default, alias = "type")]
77    pub event_type: Option<String>,
78    #[serde(default)]
79    pub time: Option<String>,
80    #[serde(default)]
81    pub subject: Option<String>,
82    #[serde(default)]
83    pub data: Option<Value>,
84    #[serde(flatten, default)]
85    pub extra: serde_json::Map<String, Value>,
86}
87
88/// Server response envelope for list endpoints. Some endpoints return a bare
89/// array; others wrap in `{"items": [...]}`. `List<T>` accepts either via custom deserialize.
90#[derive(Debug, Clone, Serialize)]
91pub struct List<T> {
92    pub items: Vec<T>,
93}
94
95impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for List<T> {
96    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
97        #[derive(Deserialize)]
98        #[serde(untagged)]
99        enum Wire<T> {
100            Bare(Vec<T>),
101            Wrapped { items: Vec<T> },
102        }
103        Ok(match Wire::<T>::deserialize(d)? {
104            Wire::Bare(items) => List { items },
105            Wire::Wrapped { items } => List { items },
106        })
107    }
108}