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
61impl RequestResult {
62 pub fn new(
67 duration: Duration,
68 success: bool,
69 status_code: Option<u16>,
70 response_body: Option<String>,
71 ) -> Self {
72 Self {
73 duration,
74 completed_at: Instant::now(),
75 success,
76 status_code,
77 response_body,
78 }
79 }
80}
81
82pub struct Request {
85 client: reqwest::Client,
86 url: String,
87 method: HttpMethod,
88 body: Option<(String, &'static str)>,
89 headers: Vec<(String, String)>,
90 capture_response: bool,
91}
92
93impl Request {
94 pub fn new(client: reqwest::Client, url: String, method: HttpMethod) -> Self {
95 Self {
96 client,
97 url,
98 method,
99 body: None,
100 headers: Vec::new(),
101 capture_response: false,
102 }
103 }
104
105 pub fn body(mut self, content: String, content_type: &'static str) -> Self {
106 self.body = Some((content, content_type));
107 self
108 }
109
110 pub fn headers(mut self, headers: Vec<(String, String)>) -> Self {
114 self.headers = headers;
115 self
116 }
117
118 pub fn read_response(mut self) -> Self {
119 self.capture_response = true;
120 self
121 }
122
123 pub async fn execute(self) -> RequestResult {
124 let start = Instant::now();
125 let mut req = match self.method {
126 HttpMethod::Get => self.client.get(&self.url),
127 HttpMethod::Post => self.client.post(&self.url),
128 HttpMethod::Put => self.client.put(&self.url),
129 HttpMethod::Patch => self.client.patch(&self.url),
130 HttpMethod::Delete => self.client.delete(&self.url),
131 };
132 if let Some((content, content_type)) = self.body {
133 req = req.header("Content-Type", content_type).body(content);
134 }
135 for (name, value) in self.headers {
137 req = req.header(name, value);
138 }
139 match req.send().await {
140 Ok(resp) => {
141 let status = resp.status();
142 let response_body = if self.capture_response {
143 resp.text().await.ok()
144 } else {
145 None
146 };
147 let duration = start.elapsed();
148 let completed_at = Instant::now();
149 RequestResult {
150 duration,
151 completed_at,
152 success: status.is_success(),
153 status_code: Some(status.as_u16()),
154 response_body,
155 }
156 }
157 Err(_) => {
158 let duration = start.elapsed();
159 let completed_at = Instant::now();
160 RequestResult {
161 duration,
162 completed_at,
163 success: false,
164 status_code: None,
165 response_body: None,
166 }
167 }
168 }
169 }
170}