dd_sds/match_validation/
match_status.rs

1use reqwest::blocking::Response;
2use serde::{Deserialize, Serialize};
3
4const BODY_PREFIX_LENGTH: usize = 30;
5
6#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone, Serialize, Deserialize)]
7pub enum MatchStatus {
8    // The ordering here is important, values further down the list have a higher priority when merging.
9    NotChecked,
10    NotAvailable,
11    /// Missing matches that are required for the match to be checked
12    MissingDependentMatch,
13    Invalid,
14    ValidationError(Vec<ValidationError>),
15    Valid,
16}
17
18#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone, Serialize, Deserialize)]
19pub enum ValidationError {
20    UnknownResponseType(UnknownResponseTypeInfo),
21    HttpError(HttpErrorInfo),
22}
23
24#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone, Serialize, Deserialize)]
25pub struct HttpErrorInfo {
26    pub status_code: u16,
27    pub message: String,
28}
29
30#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone, Serialize, Deserialize)]
31pub struct UnknownResponseTypeInfo {
32    pub status_code: u16,
33    pub body_length: usize,
34    // Prefix of the response body
35    pub body_prefix: Option<String>,
36}
37
38impl UnknownResponseTypeInfo {
39    pub fn from_status_and_body(status_code: u16, body: &str) -> Self {
40        let prefix = match body.len() {
41            0 => None,
42            _ => Some(body.chars().take(BODY_PREFIX_LENGTH).collect::<String>()),
43        };
44        Self {
45            status_code,
46            body_length: body.len(),
47            body_prefix: prefix,
48        }
49    }
50}
51
52impl From<Response> for UnknownResponseTypeInfo {
53    fn from(response: Response) -> Self {
54        let status_code = response.status().as_u16();
55        let body = response.text().unwrap_or_default();
56        UnknownResponseTypeInfo::from_status_and_body(status_code, &body)
57    }
58}
59
60impl std::fmt::Display for MatchStatus {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            MatchStatus::NotChecked => write!(f, "NotChecked"),
64            MatchStatus::NotAvailable => write!(f, "NotAvailable"),
65            MatchStatus::Invalid => write!(f, "Invalid"),
66            MatchStatus::MissingDependentMatch => write!(f, "MissingDependentMatch",),
67            MatchStatus::ValidationError(validation_errors) => {
68                write!(
69                    f,
70                    "Error({})",
71                    validation_errors
72                        .iter()
73                        .map(|e| e.to_string())
74                        .collect::<Vec<String>>()
75                        .join(", ")
76                )
77            }
78            MatchStatus::Valid => write!(f, "Valid"),
79        }
80    }
81}
82
83impl std::fmt::Display for HttpErrorInfo {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        write!(
86            f,
87            "Http error: status_code: {}, message: {}",
88            self.status_code, self.message
89        )
90    }
91}
92
93impl std::fmt::Display for UnknownResponseTypeInfo {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(
96            f,
97            "No condition matched response with status_code: {} and body_length: {}",
98            self.status_code, self.body_length
99        )
100    }
101}
102
103impl std::fmt::Display for ValidationError {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        match self {
106            ValidationError::UnknownResponseType(inner) => inner.fmt(f),
107            ValidationError::HttpError(inner) => inner.fmt(f),
108        }
109    }
110}
111
112impl MatchStatus {
113    // Order matters as we want to update the match_status only if the new match_status has higher priority.
114    // (in case of split key where we try different combinations of id and secret (aws use-case))
115    pub fn merge(&mut self, new_status: MatchStatus) {
116        match (self, new_status) {
117            (
118                MatchStatus::ValidationError(existing_errors),
119                MatchStatus::ValidationError(mut new_errors),
120            ) => existing_errors.append(&mut new_errors),
121            (existing_status, new_status) if new_status > *existing_status => {
122                *existing_status = new_status;
123            }
124            _ => {}
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_merge() {
135        let mut status = MatchStatus::NotChecked;
136        status.merge(MatchStatus::NotAvailable);
137        assert_eq!(status, MatchStatus::NotAvailable);
138
139        status.merge(MatchStatus::Invalid);
140        assert_eq!(status, MatchStatus::Invalid);
141
142        status.merge(MatchStatus::ValidationError(vec![
143            ValidationError::HttpError(HttpErrorInfo {
144                status_code: 500,
145                message: "error".to_string(),
146            }),
147        ]));
148        assert_eq!(
149            status,
150            MatchStatus::ValidationError(vec![ValidationError::HttpError(HttpErrorInfo {
151                status_code: 500,
152                message: "error".to_string(),
153            })])
154        );
155
156        status.merge(MatchStatus::Valid);
157        assert_eq!(status, MatchStatus::Valid);
158    }
159    #[test]
160    fn test_merge_lower_prio() {
161        let mut status = MatchStatus::Valid;
162        status.merge(MatchStatus::NotChecked);
163        assert_eq!(status, MatchStatus::Valid);
164
165        status.merge(MatchStatus::NotAvailable);
166        assert_eq!(status, MatchStatus::Valid);
167
168        status.merge(MatchStatus::Invalid);
169        assert_eq!(status, MatchStatus::Valid);
170
171        status.merge(MatchStatus::ValidationError(vec![
172            ValidationError::HttpError(HttpErrorInfo {
173                status_code: 500,
174                message: "error".to_string(),
175            }),
176        ]));
177        assert_eq!(status, MatchStatus::Valid);
178
179        status = MatchStatus::ValidationError(vec![ValidationError::HttpError(HttpErrorInfo {
180            status_code: 500,
181            message: "error".to_string(),
182        })]);
183        status.merge(MatchStatus::NotChecked);
184
185        assert_eq!(
186            status,
187            MatchStatus::ValidationError(vec![ValidationError::HttpError(HttpErrorInfo {
188                status_code: 500,
189                message: "error".to_string(),
190            })])
191        );
192
193        status.merge(MatchStatus::NotAvailable);
194        assert_eq!(
195            status,
196            MatchStatus::ValidationError(vec![ValidationError::HttpError(HttpErrorInfo {
197                status_code: 500,
198                message: "error".to_string(),
199            })])
200        );
201
202        status.merge(MatchStatus::Invalid);
203        assert_eq!(
204            status,
205            MatchStatus::ValidationError(vec![ValidationError::HttpError(HttpErrorInfo {
206                status_code: 500,
207                message: "error".to_string(),
208            })])
209        );
210
211        status = MatchStatus::Invalid;
212        status.merge(MatchStatus::NotChecked);
213        assert_eq!(status, MatchStatus::Invalid);
214
215        status.merge(MatchStatus::NotAvailable);
216        assert_eq!(status, MatchStatus::Invalid);
217
218        status = MatchStatus::NotAvailable;
219        status.merge(MatchStatus::NotChecked);
220        assert_eq!(status, MatchStatus::NotAvailable);
221    }
222}