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