1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4use std::str::FromStr;
5use std::{hash::Hash, time::Duration};
6
7#[cfg(feature = "third-party-active-checkers")]
8use crate::match_validation::http_validator_v2::HttpValidatorV2;
9
10#[cfg(feature = "third-party-active-checkers")]
11use super::aws_validator::AwsValidator;
12use super::config_v2::CustomHttpConfigV2;
13#[cfg(feature = "third-party-active-checkers")]
14use super::http_validator::HttpValidator;
15use super::match_validator::MatchValidator;
16
17pub const DEFAULT_HTTPS_TIMEOUT_SEC: u64 = 3;
18pub const DEFAULT_AWS_STS_ENDPOINT: &str = "https://sts.amazonaws.com";
19
20#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
21pub struct AwsConfig {
22 #[serde(default = "default_aws_sts_endpoint")]
24 pub aws_sts_endpoint: String,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub forced_datetime_utc: Option<DateTime<Utc>>,
28 #[serde(default = "default_timeout")]
29 pub timeout: Duration,
30}
31
32fn default_aws_sts_endpoint() -> String {
33 DEFAULT_AWS_STS_ENDPOINT.to_string()
34}
35
36fn default_timeout() -> Duration {
37 Duration::from_secs(DEFAULT_HTTPS_TIMEOUT_SEC)
38}
39
40impl Default for AwsConfig {
41 fn default() -> Self {
42 AwsConfig {
43 aws_sts_endpoint: default_aws_sts_endpoint(),
44 forced_datetime_utc: None,
45 timeout: default_timeout(),
46 }
47 }
48}
49
50#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
51#[serde(tag = "kind")]
52pub enum AwsType {
53 AwsId,
54 AwsSecret(AwsConfig),
55 AwsSession,
56}
57
58#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
59#[serde(rename_all = "UPPERCASE")]
60pub enum HttpMethod {
61 Get,
62 Post,
63 Put,
64 Delete,
65 Patch,
66}
67
68impl FromStr for HttpMethod {
69 type Err = String;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 match s.to_uppercase().as_str() {
73 "GET" => Ok(HttpMethod::Get),
74 "POST" => Ok(HttpMethod::Post),
75 "PUT" => Ok(HttpMethod::Put),
76 "DELETE" => Ok(HttpMethod::Delete),
77 "PATCH" => Ok(HttpMethod::Patch),
78 _ => Err(format!("Invalid HTTP method: {s}")),
79 }
80 }
81}
82
83#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
84pub struct RequestHeader {
85 pub key: String,
86 pub value: String,
88}
89
90impl RequestHeader {
91 pub fn get_value_with_match(&self, matche: &str) -> String {
92 let mut value = self.value.clone();
94 value = value.replace("$MATCH", matche);
95 value
96 }
97}
98
99#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
100pub struct HttpValidatorOption {
101 pub timeout: Duration,
102 }
106
107#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
108pub struct CustomHttpConfig {
109 pub endpoint: String,
110 #[serde(default)]
111 pub hosts: Vec<String>,
112 #[serde(default = "default_http_method")]
113 pub http_method: HttpMethod,
114 pub request_headers: BTreeMap<String, String>,
115 #[serde(default = "default_valid_http_status_code")]
116 pub valid_http_status_code: Vec<HttpStatusCodeRange>,
117 #[serde(default = "default_invalid_http_status_code")]
118 pub invalid_http_status_code: Vec<HttpStatusCodeRange>,
119 #[serde(default = "default_timeout_seconds")]
120 pub timeout_seconds: u32,
121}
122
123impl Default for CustomHttpConfig {
124 fn default() -> Self {
125 CustomHttpConfig {
126 endpoint: "".to_string(),
127 hosts: vec![],
128 http_method: HttpMethod::Get,
129 request_headers: BTreeMap::new(),
130 valid_http_status_code: vec![],
131 invalid_http_status_code: vec![],
132 timeout_seconds: DEFAULT_HTTPS_TIMEOUT_SEC as u32,
133 }
134 }
135}
136
137impl CustomHttpConfig {
138 pub fn get_endpoints(&self) -> Result<Vec<String>, String> {
139 if self.endpoint.contains("$HOST") && self.hosts.is_empty() {
143 return Err("Endpoint contains $HOST but no hosts are provided".to_string());
144 }
145 if !self.endpoint.contains("$HOST") && !self.hosts.is_empty() {
146 return Err("Endpoint does not contain $HOST but hosts are provided".to_string());
147 }
148
149 let mut endpoints = vec![];
151 for host in self.hosts.clone() {
152 endpoints.push(self.endpoint.replace("$HOST", &host));
153 }
154 if endpoints.is_empty() {
155 endpoints.push(self.endpoint.to_string());
157 }
158 Ok(endpoints)
159 }
160
161 pub fn with_endpoint(mut self, endpoint: String) -> Self {
164 self.endpoint = endpoint;
165 self
166 }
167
168 pub fn with_hosts(mut self, hosts: Vec<String>) -> Self {
169 self.hosts = hosts;
170 self
171 }
172
173 pub fn with_request_headers(mut self, request_headers: BTreeMap<String, String>) -> Self {
174 self.request_headers = request_headers;
175 self
176 }
177
178 pub fn with_valid_http_status_code(
179 mut self,
180 valid_http_status_code: Vec<HttpStatusCodeRange>,
181 ) -> Self {
182 self.valid_http_status_code = valid_http_status_code;
183 self
184 }
185
186 pub fn with_invalid_http_status_code(
187 mut self,
188 invalid_http_status_code: Vec<HttpStatusCodeRange>,
189 ) -> Self {
190 self.invalid_http_status_code = invalid_http_status_code;
191 self
192 }
193
194 pub fn set_endpoint(&mut self, endpoint: String) {
197 self.endpoint = endpoint;
198 }
199
200 pub fn set_hosts(&mut self, hosts: Vec<String>) {
201 self.hosts = hosts;
202 }
203
204 pub fn set_http_method(&mut self, http_method: HttpMethod) {
205 self.http_method = http_method;
206 }
207
208 pub fn set_request_headers(&mut self, request_headers: BTreeMap<String, String>) {
209 self.request_headers = request_headers;
210 }
211
212 pub fn set_valid_http_status_code(&mut self, valid_http_status_code: Vec<HttpStatusCodeRange>) {
213 self.valid_http_status_code = valid_http_status_code;
214 }
215
216 pub fn set_invalid_http_status_code(
217 &mut self,
218 invalid_http_status_code: Vec<HttpStatusCodeRange>,
219 ) {
220 self.invalid_http_status_code = invalid_http_status_code;
221 }
222
223 pub fn set_timeout_seconds(&mut self, timeout_seconds: u32) {
224 self.timeout_seconds = timeout_seconds;
225 }
226}
227
228fn default_timeout_seconds() -> u32 {
229 DEFAULT_HTTPS_TIMEOUT_SEC as u32
230}
231
232fn default_http_method() -> HttpMethod {
233 HttpMethod::Get
234}
235
236fn default_valid_http_status_code() -> Vec<HttpStatusCodeRange> {
237 vec![HttpStatusCodeRange {
238 start: 200,
239 end: 300,
240 }]
241}
242
243fn default_invalid_http_status_code() -> Vec<HttpStatusCodeRange> {
244 vec![HttpStatusCodeRange {
245 start: 400,
246 end: 500,
247 }]
248}
249
250#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
251pub struct HttpStatusCodeRange {
252 pub start: u16,
253 pub end: u16,
254}
255
256#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
257#[serde(tag = "type", content = "config")]
258pub enum MatchValidationType {
259 Aws(AwsType),
260 CustomHttp(CustomHttpConfig),
261 CustomHttpV2(CustomHttpConfigV2),
262}
263
264impl MatchValidationType {
265 pub fn can_create_match_validator(&self) -> bool {
267 match self {
268 MatchValidationType::Aws(aws_type) => matches!(aws_type, AwsType::AwsSecret(_)),
269 MatchValidationType::CustomHttp(_) => true,
270 MatchValidationType::CustomHttpV2(http_config_v2) => !http_config_v2.calls.is_empty(),
271 }
272 }
273 pub fn get_internal_match_validation_type(&self) -> InternalMatchValidationType {
274 match self {
275 MatchValidationType::Aws(_) => InternalMatchValidationType::Aws,
276 MatchValidationType::CustomHttp(http_config) => {
277 InternalMatchValidationType::CustomHttp(http_config.get_endpoints().unwrap())
278 }
279 MatchValidationType::CustomHttpV2(_) => InternalMatchValidationType::CustomHttpV2,
280 }
281 }
282 #[cfg(feature = "third-party-active-checkers")]
283 pub fn into_match_validator(&self) -> Result<Box<dyn MatchValidator>, String> {
284 match self {
285 MatchValidationType::Aws(aws_type) => match aws_type {
286 AwsType::AwsSecret(aws_config) => {
287 Ok(Box::new(AwsValidator::new(aws_config.clone())))
288 }
289 _ => Err("This aws type shall not be used to create a validator".to_string()),
290 },
291 MatchValidationType::CustomHttp(http_config) => Ok(Box::new(
292 HttpValidator::new_from_config(http_config.clone()),
293 )),
294 MatchValidationType::CustomHttpV2(_) => Ok(Box::new(HttpValidatorV2)),
295 }
296 }
297
298 #[cfg(not(feature = "third-party-active-checkers"))]
302 pub fn into_match_validator(&self) -> Result<Box<dyn MatchValidator>, String> {
303 Err("Third-party active checkers are not enabled in this build".to_string())
304 }
305}
306
307#[derive(PartialEq, Eq, Hash)]
311pub enum InternalMatchValidationType {
312 Aws,
313 CustomHttp(Vec<String>),
314 CustomHttpV2,
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[test]
322 fn test_serialization_of_aws_config() {
323 let aws_config = AwsConfig {
324 aws_sts_endpoint: "https://sts.amazonaws.com".to_string(),
325 forced_datetime_utc: None,
326 timeout: Duration::from_secs(3),
327 };
328 let serialized = serde_json::to_string(&aws_config).unwrap();
329 assert_eq!(
331 serialized,
332 "{\"aws_sts_endpoint\":\"https://sts.amazonaws.com\",\"timeout\":{\"secs\":3,\"nanos\":0}}"
333 );
334 }
335}