1use serde::{Deserialize, Serialize};
2
3pub const STATUS_TARGET: &str = "/status";
4pub const STATUS_CHANGED_TARGET: &str = "/status/changed";
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "camelCase")]
8pub enum IdentityPhase {
9 Unknown,
10 Uncommissioned,
11 Commissioning,
12 Commissioned,
13 Stale,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(rename_all = "camelCase")]
18pub enum HubPhase {
19 Unknown,
20 WaitingDiscovery,
21 Resolved,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25#[serde(rename_all = "camelCase")]
26pub enum ConnectionPhase {
27 Disconnected,
28 Connecting,
29 Authenticating,
30 Authenticated,
31 Connected,
32 Retrying,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
36#[serde(rename_all = "camelCase")]
37pub enum RuntimePhase {
38 Starting,
39 Ready,
40 Retrying,
41 Restarting,
42 Failed,
43 Corrupted,
44 Stopping,
45 Stopped,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49#[serde(rename_all = "camelCase")]
50pub enum ProcessPhase {
51 Unknown,
52 Starting,
53 Running,
54 Restarting,
55 Stopping,
56 Failed,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62#[serde(rename_all = "camelCase")]
63pub enum EffectivePhase {
64 ProcessStarting,
65 Uncommissioned,
66 Commissioning,
67 WaitingForHub,
68 Connecting,
69 Authenticating,
70 Online,
71 PartiallyOnline,
72 Degraded,
73 Retrying,
74 Failed,
75 Stopping,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
81#[serde(rename_all = "camelCase")]
82pub struct InstanceSnapshot {
83 pub node_id: String,
84 pub runtime_id: String,
85 pub hub_id: String,
86 pub tenant_id: String,
87 pub scope_id: String,
88 pub identity: IdentityPhase,
89 pub hub: HubPhase,
90 pub connection: ConnectionPhase,
91 pub runtime: RuntimePhase,
92 pub runtime_generation: u64,
93 pub connection_generation: u64,
94 pub revision: u64,
95 pub effective: EffectivePhase,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub runtime_reason: Option<String>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub connection_reason: Option<String>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub reason: Option<String>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub runtime_retry_after_ms: Option<u64>,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub connection_retry_after_ms: Option<u64>,
106 pub updated_at_ms: i64,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
112#[serde(rename_all = "camelCase")]
113pub struct RuntimeSnapshot {
114 pub runtime_id: String,
115 pub runtime: RuntimePhase,
116 pub generation: u64,
117 pub revision: u64,
118 #[serde(default)]
119 pub member_node_ids: Vec<String>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub reason: Option<String>,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub retry_after_ms: Option<u64>,
124 pub updated_at_ms: i64,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129#[serde(rename_all = "camelCase")]
130pub struct StatusPayload {
131 pub contract: String,
132 pub service_id: String,
133 pub node_type: String,
134 pub process_generation: String,
135 pub revision: u64,
136 pub process: ProcessPhase,
137 pub instance: InstanceSnapshot,
138 pub runtime: RuntimeSnapshot,
139 pub updated_at_ms: i64,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
144#[serde(rename_all = "camelCase")]
145pub struct InstanceStatus {
146 pub contract: String,
147 pub service_id: String,
148 pub node_type: String,
149 pub process_generation: String,
150 pub revision: u64,
151 pub process: ProcessPhase,
152 pub instance: InstanceSnapshot,
153 pub updated_at_ms: i64,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
158#[serde(rename_all = "camelCase")]
159pub struct RuntimeStatus {
160 pub contract: String,
161 pub service_id: String,
162 pub node_type: String,
163 pub process_generation: String,
164 pub revision: u64,
165 pub runtime: RuntimeSnapshot,
166 pub updated_at_ms: i64,
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn status_wire_shape_is_camel_case_and_additive() {
175 let value = serde_json::json!({
176 "contract": crate::node::STATUS_CONTRACT,
177 "serviceId": "camera",
178 "nodeType": "camera",
179 "processGeneration": "process-1",
180 "revision": 2,
181 "process": "running",
182 "instance": {
183 "nodeId": "node-1",
184 "runtimeId": "instance:node-1",
185 "hubId": "hub-1",
186 "tenantId": "tenant-1",
187 "scopeId": "scope-1",
188 "identity": "commissioned",
189 "hub": "resolved",
190 "connection": "connected",
191 "runtime": "ready",
192 "runtimeGeneration": 3,
193 "connectionGeneration": 4,
194 "revision": 5,
195 "effective": "online",
196 "updatedAtMs": 6,
197 "future": true
198 },
199 "runtime": {
200 "runtimeId": "instance:node-1",
201 "runtime": "ready",
202 "generation": 3,
203 "revision": 7,
204 "memberNodeIds": ["node-1"],
205 "updatedAtMs": 8
206 },
207 "updatedAtMs": 9,
208 "future": true
209 });
210 let status: StatusPayload = serde_json::from_value(value).unwrap();
211 assert_eq!(status.instance.node_id, "node-1");
212 assert_eq!(status.runtime.runtime_id, "instance:node-1");
213 }
214}