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 NotChecked,
10 NotAvailable,
11 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 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 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}