1use crate::{Dialect, Section};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::ops::{Deref, DerefMut};
5
6pub const CONDITION_READY: &str = "Ready";
7
8#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
9#[serde(rename_all = "camelCase")]
10pub struct Condition {
11 pub last_transition_time: DateTime<Utc>,
12 #[serde(default, skip_serializing_if = "is_empty")]
13 pub message: Option<String>,
14 #[serde(default, skip_serializing_if = "is_empty")]
15 pub reason: Option<String>,
16 #[serde(default = "default_condition_status")]
17 pub status: String,
18 pub r#type: String,
19}
20
21fn is_empty(value: &Option<String>) -> bool {
22 match value {
23 None => true,
24 Some(str) if str.is_empty() => true,
25 _ => false,
26 }
27}
28
29#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
30pub struct Conditions(pub Vec<Condition>);
31
32fn default_condition_status() -> String {
33 "Unknown".into()
34}
35
36impl Dialect for Conditions {
37 fn key() -> &'static str {
38 "conditions"
39 }
40
41 fn section() -> Section {
42 Section::Status
43 }
44}
45
46#[derive(Clone, Debug, Default)]
47pub struct ConditionStatus {
48 pub status: Option<bool>,
49 pub reason: Option<String>,
50 pub message: Option<String>,
51}
52
53impl From<Option<bool>> for ConditionStatus {
54 fn from(value: Option<bool>) -> Self {
55 Self {
56 status: value,
57 ..Default::default()
58 }
59 }
60}
61
62impl From<bool> for ConditionStatus {
63 fn from(value: bool) -> Self {
64 Self {
65 status: Some(value),
66 ..Default::default()
67 }
68 }
69}
70
71impl Conditions {
72 fn make_status(status: Option<bool>) -> String {
73 match status {
74 Some(true) => "True",
75 Some(false) => "False",
76 None => "Unknown",
77 }
78 .into()
79 }
80
81 pub fn update<T, S>(&mut self, r#type: T, status: S)
82 where
83 T: AsRef<str>,
84 S: Into<ConditionStatus>,
85 {
86 let status = status.into();
87 let str_status = Self::make_status(status.status);
88
89 for mut condition in &mut self.0 {
90 if condition.r#type == r#type.as_ref() {
91 if condition.status != str_status {
92 condition.last_transition_time = Utc::now();
93 condition.status = str_status;
94 }
95 condition.reason = status.reason;
96 condition.message = status.message;
97
98 return;
99 }
100 }
101
102 self.0.push(Condition {
105 last_transition_time: Utc::now(),
106 message: status.message,
107 reason: status.reason,
108 status: str_status,
109 r#type: r#type.as_ref().into(),
110 });
111 }
112
113 pub fn aggregate_ready(mut self) -> Self {
115 let mut ready = true;
116 for condition in &self.0 {
117 if condition.r#type == CONDITION_READY {
118 continue;
119 }
120
121 if condition.status != "True" {
122 ready = false;
123 break;
124 }
125 }
126
127 self.update(
128 CONDITION_READY,
129 match ready {
130 true => ConditionStatus {
131 status: Some(true),
132 reason: None,
133 message: None,
134 },
135 false => ConditionStatus {
136 status: Some(false),
137 reason: Some("NonReadyConditions".into()),
138 message: None,
139 },
140 },
141 );
142 self
143 }
144
145 pub fn clear_ready<T>(mut self, r#type: T) -> Self
147 where
148 T: AsRef<str>,
149 {
150 let r#type = r#type.as_ref();
151 self.0.retain(|c| c.r#type != r#type);
152 self.aggregate_ready()
153 }
154}
155
156impl Deref for Conditions {
157 type Target = Vec<Condition>;
158
159 fn deref(&self) -> &Self::Target {
160 &self.0
161 }
162}
163
164impl DerefMut for Conditions {
165 fn deref_mut(&mut self) -> &mut Self::Target {
166 &mut self.0
167 }
168}
169
170#[cfg(test)]
171mod test {
172
173 use super::*;
174
175 #[test]
176 fn insert_cond_1() {
177 let mut conditions = Conditions::default();
178
179 conditions.update(
180 "KafkaReady",
181 ConditionStatus {
182 status: Some(true),
183 ..Default::default()
184 },
185 );
186
187 let now = Utc::now();
188 conditions.0[0].last_transition_time = now;
189
190 assert_eq!(
191 conditions.0,
192 vec![Condition {
193 last_transition_time: now,
194 message: None,
195 reason: None,
196 status: "True".to_string(),
197 r#type: "KafkaReady".to_string()
198 }]
199 );
200 }
201
202 #[test]
203 fn update_cond_1() {
204 let mut conditions = Conditions::default();
205
206 conditions.update(
209 "KafkaReady",
210 ConditionStatus {
211 status: Some(true),
212 ..Default::default()
213 },
214 );
215 conditions.update(
216 "FooBarReady",
217 ConditionStatus {
218 status: None,
219 ..Default::default()
220 },
221 );
222
223 let now = Utc::now();
225 conditions.0[0].last_transition_time = now;
226 conditions.0[1].last_transition_time = now;
227
228 assert_eq!(
229 conditions.0,
230 vec![
231 Condition {
232 last_transition_time: now,
233 message: None,
234 reason: None,
235 status: "True".to_string(),
236 r#type: "KafkaReady".to_string()
237 },
238 Condition {
239 last_transition_time: now,
240 message: None,
241 reason: None,
242 status: "Unknown".to_string(),
243 r#type: "FooBarReady".to_string()
244 }
245 ]
246 );
247
248 conditions.update(
249 "FooBarReady",
250 ConditionStatus {
251 status: Some(true),
252 message: Some("All systems are ready to go".into()),
253 reason: Some("AllSystemsGo".into()),
254 ..Default::default()
255 },
256 );
257
258 assert_eq!(conditions.0[0].last_transition_time, now);
260 assert_ne!(conditions.0[1].last_transition_time, now);
261
262 let now = Utc::now();
264 conditions.0[0].last_transition_time = now;
265 conditions.0[1].last_transition_time = now;
266
267 assert_eq!(
268 conditions.0,
269 vec![
270 Condition {
271 last_transition_time: now,
272 message: None,
273 reason: None,
274 status: "True".to_string(),
275 r#type: "KafkaReady".to_string()
276 },
277 Condition {
278 last_transition_time: now,
279 message: Some("All systems are ready to go".into()),
280 reason: Some("AllSystemsGo".into()),
281 status: "True".to_string(),
282 r#type: "FooBarReady".to_string()
283 }
284 ]
285 );
286 }
287
288 #[test]
289 fn serialize() {
290 let json = serde_json::to_string(&Conditions(vec![Condition {
291 last_transition_time: DateTime::parse_from_rfc3339("2001-02-03T12:34:56Z")
292 .expect("Valid timestmap")
293 .with_timezone(&Utc),
294 message: None,
295 reason: None,
296 status: "True".to_string(),
297 r#type: "Ready".to_string(),
298 }]))
299 .expect("Serialize to JSON");
300 assert_eq!(
301 json,
302 r#"[{"lastTransitionTime":"2001-02-03T12:34:56Z","status":"True","type":"Ready"}]"#
303 );
304 }
305
306 #[test]
307 fn conversions() {
308 let mut conditions = Conditions::default();
309 conditions.update(
310 "Foo",
311 ConditionStatus {
312 status: None,
313 reason: None,
314 message: None,
315 },
316 );
317 conditions.update("Foo", true);
318 conditions.update("Bar", Some(true));
319 }
320
321 #[test]
322 fn clear_ready() {
323 let mut conditions = Conditions::default();
324 conditions.update(
325 "SomeReady",
326 ConditionStatus {
327 status: None,
328 reason: None,
329 message: None,
330 },
331 );
332 conditions = conditions.aggregate_ready();
333 assert_eq!(conditions.len(), 2);
334 conditions = conditions.clear_ready("SomeReady");
335 assert_eq!(conditions.len(), 1);
336 let c = conditions.get(0).unwrap();
337 assert_eq!(c.r#type, "Ready");
338 assert_eq!(c.status, "True");
339 }
340}