use http::Method;
use reqwest::{header::HeaderMap, Response};
use std::{collections::HashMap, path::PathBuf};
use crate::parser::{self, context};
#[derive(Debug, Clone)]
pub enum FormDataType {
Text(String),
File(PathBuf),
}
#[derive(Debug, Clone)]
pub struct Request {
pub description: String,
pub method: Method,
pub url: String,
pub headers: HashMap<String, String>,
pub query_params: HashMap<String, String>,
pub form_data: HashMap<String, FormDataType>,
pub body: Option<String>,
pub body_content_type: Option<String>,
pub callback_src: Vec<String>,
pub working_dir: PathBuf,
}
impl Request {
pub async fn exec(&self) -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let mut request =
client.request(self.method.clone(), context::inject_from_prompt(&self.url));
let mut header_map = HeaderMap::new();
if let Some(content_type) = &self.body_content_type {
header_map.insert(
reqwest::header::CONTENT_TYPE,
context::inject_from_prompt(content_type)
.parse::<reqwest::header::HeaderValue>()
.unwrap(),
);
}
for (key, value) in &self.headers {
header_map.insert(
key.parse::<reqwest::header::HeaderName>()
.expect(format!("Invalid header name: {}", key).as_str()),
context::inject_from_prompt(value)
.parse::<reqwest::header::HeaderValue>()
.unwrap(),
);
}
let mut form = reqwest::multipart::Form::new();
for (key, value) in &self.form_data {
match value {
FormDataType::Text(text) => {
form = form.text(key.clone(), context::inject_from_prompt(text));
}
FormDataType::File(filepath) => {
let file = reqwest::multipart::Part::bytes(std::fs::read(filepath)?)
.file_name(filepath.file_name().unwrap().to_str().unwrap().to_string());
form = form.part(key.clone(), file);
}
}
}
let mut query_params: HashMap<String, String> = HashMap::new();
self.query_params.iter().for_each(|(key, value)| {
query_params.insert(key.clone(), context::inject_from_prompt(value));
});
request = match self.form_data.len() > 0 {
true => request
.headers(header_map)
.query(&query_params)
.multipart(form),
false => request.headers(header_map).query(&query_params),
};
if let Some(body) = &self.body {
request = request.body(context::inject_from_prompt(body));
}
log::debug!("REQUEST\n{:#?}", request);
let resp = request.send().await?;
log::debug!("RESPONSE\n{:#?}", resp);
let status_code = resp.status().as_str().to_string();
let resp_text = resp.text().await.unwrap();
let mut callback_resp: Vec<String> = vec![];
if self.callback_src.len() > 0 {
let mut env: HashMap<String, String> = HashMap::new();
let sanitized_response = resp_text.replace('\0', "");
env.insert("RESPONSE".to_string(), sanitized_response);
env.insert("STATUS".to_string(), status_code);
env.insert("DESCRIPTION".to_string(), self.description.clone());
for src in &self.callback_src {
callback_resp.push(parser::eval_shell_script(
src,
&self.working_dir,
Some(env.clone()),
));
}
log::debug!("CALLBACK RESPONSE\n{:#?}", callback_resp);
}
if callback_resp.len() > 0 {
Ok(callback_resp.join("\n"))
} else {
Ok(resp_text)
}
}
pub fn as_curl(&self) -> String {
let mut curl = String::new();
let headers = self
.headers
.iter()
.map(|(key, value)| format!("-H \'{}: {}\'", key, value))
.collect::<Vec<String>>()
.join(" ");
let headers = match &self.body_content_type {
Some(content_type) => format!("-H \'Content-Type: {}\' {}", content_type, headers),
None => headers,
};
let query_params = self
.query_params
.iter()
.map(|(key, value)| format!("{}={}", key, value))
.collect::<Vec<String>>()
.join("&");
let query_params = match query_params.len() {
0 => "".to_string(),
_ => format!("?{}", query_params),
};
let form_data = self
.form_data
.iter()
.map(|(key, value)| match value {
FormDataType::Text(text) => format!("-F \'{}={}\'", key, text),
FormDataType::File(filepath) => format!("-F \'{}=@{}\'", key, filepath.display()),
})
.collect::<Vec<String>>()
.join(" ");
let body = match &self.body {
Some(body) => format!("-d \' {}\'", body),
None => "".to_string(),
};
curl.push_str(&format!(
"curl -X {} \'{}{}\' {} {} {}",
self.method,
self.url,
query_params,
headers,
form_data,
body
));
return curl;
}
}