1use serde::{Deserialize, Serialize};
12
13use super::HIVE_DEFAULT_URL;
14use crate::UtcTimestamp;
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct HiveStatusInstanceV1 {
20 pub id: String,
21 pub label: String,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(deny_unknown_fields)]
27pub struct HiveStatusOrgV1 {
28 pub name: String,
29}
30
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(deny_unknown_fields)]
34pub struct HiveStatusDraftsV1 {
35 pub queued: u32,
37 pub refused: u32,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct HiveStatusFilterPackV1 {
46 pub pack_id: String,
47 pub version: u64,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum HiveBusControlActionV1 {
54 Hold,
55 Stop,
56 Veto,
57 #[serde(other)]
58 Unknown,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(deny_unknown_fields)]
64pub struct HiveStatusBusControlV1 {
65 pub target_agent: Option<String>,
67 pub action: HiveBusControlActionV1,
68 pub target_ref: Option<String>,
70 pub reason: String,
71 pub issued_by: String,
72 pub since: UtcTimestamp,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum HiveBusControlsInactiveV1 {
79 NotEnrolled,
80 LeaseLapsed,
81 #[serde(other)]
82 Unknown,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(deny_unknown_fields)]
88pub struct HiveCliStatusV1 {
89 pub available: bool,
91 pub connected: bool,
93 pub instance: Option<HiveStatusInstanceV1>,
94 pub org: Option<HiveStatusOrgV1>,
95 pub last_seen: Option<UtcTimestamp>,
97 pub url: String,
99 #[serde(default)]
101 pub drafts: HiveStatusDraftsV1,
102 #[serde(default)]
104 pub filter_packs: Vec<HiveStatusFilterPackV1>,
105 #[serde(default)]
107 pub policy_refusal: Option<String>,
108 #[serde(default)]
111 pub web_url: Option<String>,
112 #[serde(default, skip_serializing_if = "Vec::is_empty")]
115 pub bus_controls: Vec<HiveStatusBusControlV1>,
116 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub bus_controls_inactive: Option<HiveBusControlsInactiveV1>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub metrics_v2: Option<HiveStatusMetricsV2>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub vault_mismatch: Option<HiveStatusVaultMismatchV1>,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub managed: Option<HiveStatusManagedV1>,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137#[serde(deny_unknown_fields)]
138pub struct HiveStatusManagedV1 {
139 pub carrier: super::managed::ManagedCarrierKindV1,
140 pub location: String,
142 pub members: Vec<HiveStatusManagedMemberV1>,
144 pub digest: Option<String>,
146 pub error: Option<String>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(deny_unknown_fields)]
154pub struct HiveStatusManagedMemberV1 {
155 pub member: String,
156 pub value: String,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162#[serde(deny_unknown_fields)]
163pub struct HiveStatusVaultMismatchV1 {
164 pub project_root: String,
166 pub resolved: String,
168 pub linked: Option<String>,
170 pub reason: String,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(deny_unknown_fields)]
177pub struct HiveStatusMetricsV2 {
178 pub sharing: super::metrics_v2::SharingStateV2,
181 pub last_sent_at: Option<String>,
182 pub buffered_buckets: u64,
183 pub dropped_buckets: u64,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub released: Option<bool>,
188}
189
190impl HiveCliStatusV1 {
191 pub fn not_connected(url: Option<&str>) -> Self {
193 Self {
194 available: true,
195 connected: false,
196 instance: None,
197 org: None,
198 last_seen: None,
199 url: url.unwrap_or(HIVE_DEFAULT_URL).to_string(),
200 drafts: HiveStatusDraftsV1::default(),
201 filter_packs: Vec::new(),
202 policy_refusal: None,
203 web_url: Some(url.unwrap_or(HIVE_DEFAULT_URL).to_string()),
204 bus_controls: Vec::new(),
205 bus_controls_inactive: None,
206 metrics_v2: None,
207 vault_mismatch: None,
208 managed: None,
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn not_connected_prints_nulls() {
219 assert_eq!(
220 serde_json::to_string(&HiveCliStatusV1::not_connected(None)).unwrap(),
221 r#"{"available":true,"connected":false,"instance":null,"org":null,"last_seen":null,"url":"https://hive.leanctx.com","drafts":{"queued":0,"refused":0},"filter_packs":[],"policy_refusal":null,"web_url":"https://hive.leanctx.com"}"#
222 );
223 }
224
225 #[test]
226 fn bus_controls_appear_only_when_present() {
227 let mut status = HiveCliStatusV1::not_connected(None);
228 let quiet = serde_json::to_string(&status).unwrap();
229 assert!(!quiet.contains("bus_controls"));
230 status.bus_controls_inactive = Some(HiveBusControlsInactiveV1::LeaseLapsed);
231 assert!(
232 serde_json::to_string(&status)
233 .unwrap()
234 .contains(r#""bus_controls_inactive":"lease_lapsed""#)
235 );
236 }
237
238 #[test]
239 fn a_1_0_status_still_reads() {
240 let old = r#"{"available":true,"connected":false,"instance":null,"org":null,"last_seen":null,"url":"https://hive.leanctx.com"}"#;
241 let status: HiveCliStatusV1 = serde_json::from_str(old).unwrap();
242 assert_eq!(
243 status,
244 HiveCliStatusV1 {
245 web_url: None,
246 ..HiveCliStatusV1::not_connected(None)
247 }
248 );
249 }
250}