dd_sds/match_validation/
match_status.rs

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