1use std::sync::Arc;
2use std::time::Duration;
3use std::time::Instant;
4
5use crate::command::{Body, HttpMethod};
6use crate::config::secret::SensitiveString;
7use crate::response_template::field::TrackedField;
8
9pub enum BodyFormat {
12 Json,
13}
14
15pub struct RequestConfig {
18 pub client: reqwest::Client,
19 pub host: Arc<String>,
20 pub method: HttpMethod,
21 pub body: Arc<Option<Body>>,
22 pub tracked_fields: Option<Arc<Vec<TrackedField>>>,
23 pub headers: Arc<Vec<(String, SensitiveString)>>,
27}
28
29impl RequestConfig {
30 pub fn resolve_body(&self, template_body: Option<String>) -> Option<(String, &'static str)> {
34 if let Some(content) = template_body {
35 return Some((content, "application/json"));
36 }
37 self.body.as_ref().as_ref().map(|b| match b {
38 Body::Formatted { content, format } => (
39 content.clone(),
40 match format {
41 BodyFormat::Json => "application/json",
42 },
43 ),
44 })
45 }
46}
47
48pub struct RequestResult {
51 pub duration: Duration,
52 pub completed_at: Instant,
56 pub success: bool,
57 pub status_code: Option<u16>,
58 pub response_body: Option<String>,
59}
60
61pub struct RequestRecord {
68 pub duration: std::time::Duration,
69 pub completed_at: Instant,
72 pub success: bool,
73 pub status_code: Option<u16>,
74 pub extraction: Option<crate::response_template::extractor::ExtractionResult>,
76}
77
78impl RequestResult {
79 pub fn new(
84 duration: Duration,
85 success: bool,
86 status_code: Option<u16>,
87 response_body: Option<String>,
88 ) -> Self {
89 Self {
90 duration,
91 completed_at: Instant::now(),
92 success,
93 status_code,
94 response_body,
95 }
96 }
97}
98
99pub struct Request {
102 client: reqwest::Client,
103 url: Arc<String>,
104 method: HttpMethod,
105 body: Option<(String, &'static str)>,
106 headers: Option<Arc<Vec<(String, String)>>>,
107 capture_response: bool,
108}
109
110impl Request {
111 pub fn new(client: reqwest::Client, url: Arc<String>, method: HttpMethod) -> Self {
112 Self {
113 client,
114 url,
115 method,
116 body: None,
117 headers: None,
118 capture_response: false,
119 }
120 }
121
122 pub fn body(mut self, content: String, content_type: &'static str) -> Self {
123 self.body = Some((content, content_type));
124 self
125 }
126
127 pub fn headers(mut self, headers: Arc<Vec<(String, String)>>) -> Self {
131 self.headers = Some(headers);
132 self
133 }
134
135 pub fn read_response(mut self) -> Self {
136 self.capture_response = true;
137 self
138 }
139
140 pub async fn execute(self) -> RequestResult {
141 let start = Instant::now();
142 let mut req = match self.method {
143 HttpMethod::Get => self.client.get(self.url.as_str()),
144 HttpMethod::Post => self.client.post(self.url.as_str()),
145 HttpMethod::Put => self.client.put(self.url.as_str()),
146 HttpMethod::Patch => self.client.patch(self.url.as_str()),
147 HttpMethod::Delete => self.client.delete(self.url.as_str()),
148 };
149 if let Some((content, content_type)) = self.body {
150 req = req.header("Content-Type", content_type).body(content);
151 }
152 if let Some(headers) = self.headers {
154 for (name, value) in headers.iter() {
155 req = req.header(name, value);
156 }
157 }
158
159 match req.send().await {
160 Ok(resp) => {
161 let status = resp.status();
162 let response_body = if self.capture_response {
163 resp.text().await.ok()
164 } else {
165 None
166 };
167 let duration = start.elapsed();
168 let completed_at = Instant::now();
169 RequestResult {
170 duration,
171 completed_at,
172 success: status.is_success(),
173 status_code: Some(status.as_u16()),
174 response_body,
175 }
176 }
177 Err(_) => {
178 let duration = start.elapsed();
179 let completed_at = Instant::now();
180 RequestResult {
181 duration,
182 completed_at,
183 success: false,
184 status_code: None,
185 response_body: None,
186 }
187 }
188 }
189 }
190}