wadm_types/
api.rs

1use serde::{Deserialize, Serialize};
2
3use crate::Manifest;
4
5/// The default topic prefix for the wadm API;
6pub const DEFAULT_WADM_TOPIC_PREFIX: &str = "wadm.api";
7pub const WADM_STATUS_API_PREFIX: &str = "wadm.status";
8
9/// The request body for getting a manifest
10#[derive(Debug, Serialize, Deserialize)]
11pub struct GetModelRequest {
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub version: Option<String>,
14}
15
16/// The response from a get request
17#[derive(Debug, Serialize, Deserialize)]
18pub struct GetModelResponse {
19    pub result: GetResult,
20    #[serde(default)]
21    pub message: String,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub manifest: Option<Manifest>,
24}
25
26#[derive(Debug, Serialize, Deserialize)]
27pub struct ListModelsResponse {
28    pub result: GetResult,
29    #[serde(default)]
30    pub message: String,
31    pub models: Vec<ModelSummary>,
32}
33
34/// Possible outcomes of a get request
35#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
36#[serde(rename_all = "lowercase")]
37pub enum GetResult {
38    Error,
39    Success,
40    NotFound,
41}
42
43/// The type returned when putting a model
44#[derive(Debug, Serialize, Deserialize)]
45pub struct PutModelResponse {
46    pub result: PutResult,
47    #[serde(default)]
48    pub total_versions: usize,
49    #[serde(default)]
50    pub current_version: String,
51    #[serde(default)]
52    pub message: String,
53    #[serde(default)]
54    pub name: String,
55}
56
57/// Possible outcomes of a put request
58#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
59#[serde(rename_all = "lowercase")]
60pub enum PutResult {
61    Error,
62    Created,
63    NewVersion,
64}
65
66/// Summary of a given model returned when listing
67#[derive(Debug, Serialize, Deserialize, Clone)]
68pub struct ModelSummary {
69    pub name: String,
70    pub version: String,
71    pub description: Option<String>,
72    pub deployed_version: Option<String>,
73    #[serde(default)]
74    pub detailed_status: Status,
75    #[deprecated(since = "0.14.0", note = "Use detailed_status instead")]
76    pub status: StatusType,
77    #[deprecated(since = "0.14.0", note = "Use detailed_status instead")]
78    pub status_message: Option<String>,
79}
80
81/// The response to a versions request
82#[derive(Debug, Serialize, Deserialize)]
83pub struct VersionResponse {
84    pub result: GetResult,
85    #[serde(default)]
86    pub message: String,
87    pub versions: Vec<VersionInfo>,
88}
89
90/// Information about a given version of a model, returned as part of a list of all versions
91#[derive(Debug, Serialize, Deserialize, Clone)]
92pub struct VersionInfo {
93    pub version: String,
94    pub deployed: bool,
95}
96
97/// A request for deleting a model
98#[derive(Debug, Serialize, Deserialize)]
99pub struct DeleteModelRequest {
100    #[serde(default)]
101    pub version: Option<String>,
102}
103
104/// A response from a delete request
105#[derive(Debug, Serialize, Deserialize)]
106pub struct DeleteModelResponse {
107    pub result: DeleteResult,
108    #[serde(default)]
109    pub message: String,
110    #[serde(default)]
111    pub undeploy: bool,
112}
113
114/// All possible outcomes of a delete operation
115#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
116#[serde(rename_all = "lowercase")]
117pub enum DeleteResult {
118    Deleted,
119    Error,
120    Noop,
121}
122
123/// A request for deploying a model.
124///
125/// If the given version is empty (or the body is empty), it will deploy the latest version. If the
126/// version is set to "latest", it will also deploy the latest version
127#[derive(Debug, Serialize, Deserialize)]
128pub struct DeployModelRequest {
129    pub version: Option<String>,
130}
131
132/// A response from a deploy or undeploy request
133#[derive(Debug, Serialize, Deserialize)]
134pub struct DeployModelResponse {
135    pub result: DeployResult,
136    #[serde(default)]
137    pub message: String,
138    #[serde(default)]
139    pub name: String,
140    #[serde(default)]
141    pub version: Option<String>,
142}
143
144/// All possible outcomes of a deploy operation
145#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
146#[serde(rename_all = "lowercase")]
147pub enum DeployResult {
148    Error,
149    Acknowledged,
150    NotFound,
151}
152
153/// A request to undeploy a model
154///
155/// Right now this is just an empty struct, but it is reserved for future use
156#[derive(Debug, Serialize, Deserialize)]
157pub struct UndeployModelRequest {}
158
159/// A response to a status request
160#[derive(Debug, Serialize, Deserialize)]
161pub struct StatusResponse {
162    pub result: StatusResult,
163    #[serde(default)]
164    pub message: String,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub status: Option<Status>,
167}
168
169/// All possible outcomes of a status operation
170#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
171#[serde(rename_all = "lowercase")]
172pub enum StatusResult {
173    Error,
174    Ok,
175    NotFound,
176}
177
178/// The current status of a model
179#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
180pub struct Status {
181    #[serde(rename = "status")]
182    pub info: StatusInfo,
183    #[serde(skip_serializing_if = "Vec::is_empty", default)]
184    pub scalers: Vec<ScalerStatus>,
185    #[serde(default)]
186    #[deprecated(since = "0.14.0")]
187    pub version: String,
188    #[serde(default)]
189    #[deprecated(since = "0.14.0")]
190    pub components: Vec<ComponentStatus>,
191}
192
193impl Status {
194    pub fn new(info: StatusInfo, scalers: Vec<ScalerStatus>) -> Self {
195        #[allow(deprecated)]
196        Status {
197            info,
198            scalers,
199            version: String::with_capacity(0),
200            components: Vec::with_capacity(0),
201        }
202    }
203}
204
205/// The current status of a component
206#[derive(Debug, Serialize, Deserialize, Default, Clone, Eq, PartialEq)]
207pub struct ComponentStatus {
208    pub name: String,
209    #[serde(rename = "type")]
210    pub component_type: String,
211    #[serde(rename = "status")]
212    pub info: StatusInfo,
213    #[serde(skip_serializing_if = "Vec::is_empty", default)]
214    pub traits: Vec<TraitStatus>,
215}
216
217/// The current status of a trait
218#[derive(Debug, Serialize, Deserialize, Default, Clone, Eq, PartialEq)]
219pub struct TraitStatus {
220    #[serde(rename = "type")]
221    pub trait_type: String,
222    #[serde(rename = "status")]
223    pub info: StatusInfo,
224}
225
226/// The current status of a scaler
227#[derive(Debug, Serialize, Deserialize, Default, Clone, Eq, PartialEq)]
228pub struct ScalerStatus {
229    /// The id of the scaler
230    #[serde(default)]
231    pub id: String,
232    /// The kind of scaler
233    #[serde(default)]
234    pub kind: String,
235    /// The human-readable name of the scaler
236    #[serde(default)]
237    pub name: String,
238    #[serde(rename = "status")]
239    pub info: StatusInfo,
240}
241
242/// Common high-level status information
243#[derive(Debug, Serialize, Deserialize, Default, Clone, Eq, PartialEq)]
244pub struct StatusInfo {
245    #[serde(rename = "type")]
246    pub status_type: StatusType,
247    #[serde(skip_serializing_if = "String::is_empty", default)]
248    pub message: String,
249}
250
251impl StatusInfo {
252    pub fn undeployed(message: &str) -> Self {
253        StatusInfo {
254            status_type: StatusType::Undeployed,
255            message: message.to_owned(),
256        }
257    }
258
259    pub fn deployed(message: &str) -> Self {
260        StatusInfo {
261            status_type: StatusType::Deployed,
262            message: message.to_owned(),
263        }
264    }
265
266    pub fn failed(message: &str) -> Self {
267        StatusInfo {
268            status_type: StatusType::Failed,
269            message: message.to_owned(),
270        }
271    }
272
273    pub fn reconciling(message: &str) -> Self {
274        StatusInfo {
275            status_type: StatusType::Reconciling,
276            message: message.to_owned(),
277        }
278    }
279
280    pub fn waiting(message: &str) -> Self {
281        StatusInfo {
282            status_type: StatusType::Waiting,
283            message: message.to_owned(),
284        }
285    }
286
287    pub fn unhealthy(message: &str) -> Self {
288        StatusInfo {
289            status_type: StatusType::Unhealthy,
290            message: message.to_owned(),
291        }
292    }
293}
294
295/// All possible status types
296#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Copy, Default)]
297#[serde(rename_all = "lowercase")]
298pub enum StatusType {
299    Waiting,
300    #[default]
301    Undeployed,
302    #[serde(alias = "compensating")]
303    Reconciling,
304    #[serde(alias = "ready")]
305    Deployed,
306    Failed,
307    Unhealthy,
308}
309
310// Implementing add makes it easy for use to get an aggregate status by summing all of them together
311impl std::ops::Add for StatusType {
312    type Output = Self;
313
314    fn add(self, rhs: Self) -> Self::Output {
315        // If any match, return the same status
316        if self == rhs {
317            return self;
318        }
319
320        // Because we match on exact matches above, we don't have to handle them in the match below.
321        // For all of the statuses _except_ ready, they will override the other status. Order of the
322        // matching matters below
323        match (self, rhs) {
324            // Anything that is failed means the whole thing is failed
325            (Self::Failed, _) => Self::Failed,
326            (_, Self::Failed) => Self::Failed,
327            // If anything is undeployed, the whole thing is
328            (Self::Undeployed, _) => Self::Undeployed,
329            (_, Self::Undeployed) => Self::Undeployed,
330            // If anything is waiting, the whole thing is
331            (Self::Waiting, _) => Self::Waiting,
332            (_, Self::Waiting) => Self::Waiting,
333            (Self::Reconciling, _) => Self::Reconciling,
334            (_, Self::Reconciling) => Self::Reconciling,
335            (Self::Unhealthy, _) => Self::Unhealthy,
336            (_, Self::Unhealthy) => Self::Unhealthy,
337            // This is technically covered in the first comparison, but we'll be explicit
338            (Self::Deployed, Self::Deployed) => Self::Deployed,
339        }
340    }
341}
342
343impl std::iter::Sum for StatusType {
344    fn sum<I: Iterator<Item = Self>>(mut iter: I) -> Self {
345        // Grab the first status to use as our seed, defaulting to undeployed
346        let first = iter.next().unwrap_or_default();
347        iter.fold(first, |a, b| a + b)
348    }
349}
350
351#[cfg(test)]
352mod test {
353    use super::*;
354
355    #[test]
356    fn test_status_aggregate() {
357        assert!(matches!(
358            [StatusType::Deployed, StatusType::Deployed]
359                .into_iter()
360                .sum(),
361            StatusType::Deployed
362        ));
363
364        assert!(matches!(
365            [StatusType::Undeployed, StatusType::Undeployed]
366                .into_iter()
367                .sum(),
368            StatusType::Undeployed
369        ));
370
371        assert!(matches!(
372            [StatusType::Undeployed, StatusType::Failed]
373                .into_iter()
374                .sum(),
375            StatusType::Failed
376        ));
377
378        assert!(matches!(
379            [StatusType::Reconciling, StatusType::Undeployed]
380                .into_iter()
381                .sum(),
382            StatusType::Undeployed
383        ));
384
385        assert!(matches!(
386            [StatusType::Deployed, StatusType::Undeployed]
387                .into_iter()
388                .sum(),
389            StatusType::Undeployed
390        ));
391
392        assert!(matches!(
393            [
394                StatusType::Deployed,
395                StatusType::Reconciling,
396                StatusType::Undeployed,
397                StatusType::Failed
398            ]
399            .into_iter()
400            .sum(),
401            StatusType::Failed
402        ));
403
404        assert!(matches!(
405            [StatusType::Deployed, StatusType::Unhealthy]
406                .into_iter()
407                .sum(),
408            StatusType::Unhealthy
409        ));
410
411        assert!(matches!(
412            [StatusType::Reconciling, StatusType::Unhealthy]
413                .into_iter()
414                .sum(),
415            StatusType::Reconciling
416        ));
417
418        let empty: Vec<StatusType> = Vec::new();
419        assert!(matches!(empty.into_iter().sum(), StatusType::Undeployed));
420    }
421}