use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use crate::command::{Body, HttpMethod};
use crate::config::secret::SensitiveString;
use crate::response_template::field::TrackedField;
const MAX_RESPONSE_BODY_SIZE: usize = 10 * 1024 * 1024;
pub enum BodyFormat {
Json,
}
pub struct RequestConfig {
pub client: reqwest::Client,
pub host: Arc<String>,
pub method: HttpMethod,
pub body: Arc<Option<Body>>,
pub tracked_fields: Option<Arc<Vec<TrackedField>>>,
pub headers: Arc<Vec<(String, SensitiveString)>>,
}
impl RequestConfig {
pub fn resolve_body(&self, template_body: Option<String>) -> Option<(String, &'static str)> {
if let Some(content) = template_body {
return Some((content, "application/json"));
}
self.body.as_ref().as_ref().map(|b| match b {
Body::Formatted { content, format } => (
content.clone(),
match format {
BodyFormat::Json => "application/json",
},
),
})
}
}
pub struct RequestResult {
pub duration: Duration,
pub completed_at: Instant,
pub success: bool,
pub status_code: Option<u16>,
pub response_body: Option<String>,
}
pub struct RequestRecord {
pub duration: std::time::Duration,
pub completed_at: Instant,
pub success: bool,
pub status_code: Option<u16>,
pub extraction: Option<crate::response_template::extractor::ExtractionResult>,
pub scenario: Option<Arc<str>>,
pub step: Option<Arc<str>>,
pub skipped: bool,
}
impl RequestResult {
pub fn new(
duration: Duration,
success: bool,
status_code: Option<u16>,
response_body: Option<String>,
) -> Self {
Self {
duration,
completed_at: Instant::now(),
success,
status_code,
response_body,
}
}
}
pub struct Request {
client: reqwest::Client,
url: Arc<String>,
method: HttpMethod,
body: Option<(String, &'static str)>,
headers: Option<Arc<Vec<(String, String)>>>,
capture_response: bool,
}
impl Request {
pub fn new(client: reqwest::Client, url: Arc<String>, method: HttpMethod) -> Self {
Self {
client,
url,
method,
body: None,
headers: None,
capture_response: false,
}
}
pub fn body(mut self, content: String, content_type: &'static str) -> Self {
self.body = Some((content, content_type));
self
}
pub fn headers(mut self, headers: Arc<Vec<(String, String)>>) -> Self {
self.headers = Some(headers);
self
}
pub fn read_response(mut self) -> Self {
self.capture_response = true;
self
}
pub async fn execute(self) -> RequestResult {
let start = Instant::now();
let mut req = match self.method {
HttpMethod::Get => self.client.get(self.url.as_str()),
HttpMethod::Post => self.client.post(self.url.as_str()),
HttpMethod::Put => self.client.put(self.url.as_str()),
HttpMethod::Patch => self.client.patch(self.url.as_str()),
HttpMethod::Delete => self.client.delete(self.url.as_str()),
};
if let Some((content, content_type)) = self.body {
req = req.header("Content-Type", content_type).body(content);
}
if let Some(headers) = self.headers {
for (name, value) in headers.iter() {
req = req.header(name, value);
}
}
match req.send().await {
Ok(resp) => {
let status = resp.status();
let response_body = if self.capture_response {
let too_large = resp
.content_length()
.is_some_and(|len| len > MAX_RESPONSE_BODY_SIZE as u64);
if too_large {
None
} else {
match resp.bytes().await {
Ok(bytes) if bytes.len() <= MAX_RESPONSE_BODY_SIZE => {
String::from_utf8(bytes.to_vec()).ok()
}
_ => None,
}
}
} else {
None
};
let duration = start.elapsed();
let completed_at = Instant::now();
RequestResult {
duration,
completed_at,
success: status.is_success(),
status_code: Some(status.as_u16()),
response_body,
}
}
Err(_) => {
let duration = start.elapsed();
let completed_at = Instant::now();
RequestResult {
duration,
completed_at,
success: false,
status_code: None,
response_body: None,
}
}
}
}
}