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, Copy, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum HiveOrgPolicyStateV1 {
45 Required,
46 Unpinned,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(deny_unknown_fields)]
52pub struct HiveStatusPolicyV1 {
53 pub version: u64,
54 pub state: HiveOrgPolicyStateV1,
55 pub verified_at: UtcTimestamp,
56 pub not_after: Option<UtcTimestamp>,
58 pub enforced: bool,
60 #[serde(default, skip_serializing_if = "Vec::is_empty")]
64 pub required_filter_packs: Vec<HiveStatusRequiredPackV1>,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(deny_unknown_fields)]
70pub struct HiveStatusRequiredPackV1 {
71 pub pack_id: String,
72 pub version: u64,
73 pub installed: bool,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(deny_unknown_fields)]
82pub struct HiveStatusFilterPackV1 {
83 pub pack_id: String,
84 pub version: u64,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum HiveBusControlActionV1 {
91 Hold,
92 Stop,
93 Veto,
94 #[serde(other)]
95 Unknown,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(deny_unknown_fields)]
101pub struct HiveStatusBusControlV1 {
102 pub target_agent: Option<String>,
104 pub action: HiveBusControlActionV1,
105 pub target_ref: Option<String>,
107 pub reason: String,
108 pub issued_by: String,
109 pub since: UtcTimestamp,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum HiveBusControlsInactiveV1 {
116 NotEnrolled,
117 LeaseLapsed,
118 #[serde(other)]
119 Unknown,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124#[serde(deny_unknown_fields)]
125pub struct HiveCliStatusV1 {
126 pub available: bool,
128 pub connected: bool,
130 pub instance: Option<HiveStatusInstanceV1>,
131 pub org: Option<HiveStatusOrgV1>,
132 pub last_seen: Option<UtcTimestamp>,
134 pub url: String,
136 #[serde(default)]
138 pub drafts: HiveStatusDraftsV1,
139 #[serde(default)]
141 pub policy: Option<HiveStatusPolicyV1>,
142 #[serde(default)]
144 pub filter_packs: Vec<HiveStatusFilterPackV1>,
145 #[serde(default)]
147 pub policy_refusal: Option<String>,
148 #[serde(default)]
151 pub web_url: Option<String>,
152 #[serde(default, skip_serializing_if = "Vec::is_empty")]
155 pub bus_controls: Vec<HiveStatusBusControlV1>,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub bus_controls_inactive: Option<HiveBusControlsInactiveV1>,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub metrics_v2: Option<HiveStatusMetricsV2>,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(deny_unknown_fields)]
168pub struct HiveStatusMetricsV2 {
169 pub sharing: super::metrics_v2::SharingStateV2,
172 pub last_sent_at: Option<String>,
173 pub buffered_buckets: u64,
174 pub dropped_buckets: u64,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub released: Option<bool>,
179}
180
181impl HiveCliStatusV1 {
182 pub fn not_connected(url: Option<&str>) -> Self {
184 Self {
185 available: true,
186 connected: false,
187 instance: None,
188 org: None,
189 last_seen: None,
190 url: url.unwrap_or(HIVE_DEFAULT_URL).to_string(),
191 drafts: HiveStatusDraftsV1::default(),
192 policy: None,
193 filter_packs: Vec::new(),
194 policy_refusal: None,
195 web_url: Some(url.unwrap_or(HIVE_DEFAULT_URL).to_string()),
196 bus_controls: Vec::new(),
197 bus_controls_inactive: None,
198 metrics_v2: None,
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn not_connected_prints_nulls() {
209 assert_eq!(
210 serde_json::to_string(&HiveCliStatusV1::not_connected(None)).unwrap(),
211 r#"{"available":true,"connected":false,"instance":null,"org":null,"last_seen":null,"url":"https://hive.leanctx.com","drafts":{"queued":0,"refused":0},"policy":null,"filter_packs":[],"policy_refusal":null,"web_url":"https://hive.leanctx.com"}"#
212 );
213 }
214
215 #[test]
216 fn bus_controls_appear_only_when_present() {
217 let mut status = HiveCliStatusV1::not_connected(None);
218 let quiet = serde_json::to_string(&status).unwrap();
219 assert!(!quiet.contains("bus_controls"));
220 status.bus_controls_inactive = Some(HiveBusControlsInactiveV1::LeaseLapsed);
221 assert!(
222 serde_json::to_string(&status)
223 .unwrap()
224 .contains(r#""bus_controls_inactive":"lease_lapsed""#)
225 );
226 }
227
228 #[test]
229 fn a_1_0_status_still_reads() {
230 let old = r#"{"available":true,"connected":false,"instance":null,"org":null,"last_seen":null,"url":"https://hive.leanctx.com"}"#;
231 let status: HiveCliStatusV1 = serde_json::from_str(old).unwrap();
232 assert_eq!(
233 status,
234 HiveCliStatusV1 {
235 web_url: None,
236 ..HiveCliStatusV1::not_connected(None)
237 }
238 );
239 }
240}