use std::collections::HashMap;
use std::time::Duration;
use crate::zerror::Result;
use crate::zhttp::types::{BodyFormat, Method, Response};
use crate::zhttp::Client;
use base64::engine::Engine;
#[derive(Debug, Clone)]
pub struct RequestBuilder {
pub(crate) method: Method,
pub(crate) url: String,
pub(crate) headers: HashMap<String, String>,
pub(crate) body: Option<Vec<u8>>,
pub(crate) body_format: Option<BodyFormat>,
pub(crate) client: Client,
pub(crate) timeout: Option<Duration>,
pub(crate) retries: Option<u32>,
pub(crate) follow_redirects: Option<bool>,
pub(crate) verify_ssl: Option<bool>,
}
impl RequestBuilder {
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(key.into(), value.into());
self
}
pub fn headers(
mut self,
headers: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
for (key, value) in headers {
self.headers.insert(key.into(), value.into());
}
self
}
pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
self.body = Some(body.into());
self
}
#[cfg(feature = "json")]
pub fn json<T: serde::Serialize>(mut self, value: &T) -> Result<Self> {
let body = serde_json::to_vec(value)?;
self.body = Some(body);
self.body_format = Some(BodyFormat::Json);
Ok(self)
}
pub fn form(
mut self,
data: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
let mut form_data = Vec::new();
for (key, value) in data {
if !form_data.is_empty() {
form_data.push(b'&');
}
form_data.extend(urlencoding::encode(&key.into()).as_bytes());
form_data.push(b'=');
form_data.extend(urlencoding::encode(&value.into()).as_bytes());
}
self.body = Some(form_data);
self.body_format = Some(BodyFormat::FormUrlEncoded);
self
}
pub fn body_format(mut self, format: BodyFormat) -> Self {
self.body_format = Some(format);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn retries(mut self, retries: u32) -> Self {
self.retries = Some(retries);
self
}
pub fn follow_redirects(mut self, follow: bool) -> Self {
self.follow_redirects = Some(follow);
self
}
pub fn verify_ssl(mut self, verify: bool) -> Self {
self.verify_ssl = Some(verify);
self
}
pub fn basic_auth(self, username: impl Into<String>, password: impl Into<String>) -> Self {
let auth = format!(
"Basic {}",
base64::engine::general_purpose::STANDARD.encode(format!(
"{}:{}",
username.into(),
password.into()
))
);
self.header("Authorization", auth)
}
pub fn bearer_auth(self, token: impl Into<String>) -> Self {
self.header("Authorization", format!("Bearer {}", token.into()))
}
pub fn query(
mut self,
params: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
let mut url = self.url;
let mut first = !url.contains('?');
for (key, value) in params {
url.push_str(if first { "?" } else { "&" });
url.push_str(&urlencoding::encode(&key.into()));
url.push('=');
url.push_str(&urlencoding::encode(&value.into()));
first = false;
}
self.url = url;
self
}
pub fn multipart(
mut self,
parts: impl IntoIterator<Item = (String, Vec<u8>, Option<String>)>,
) -> Self {
let boundary = format!("------------------------{}", uuid::Uuid::new_v4());
let mut body = Vec::new();
for (name, content, filename) in parts {
body.extend_from_slice(b"--");
body.extend_from_slice(boundary.as_bytes());
body.extend_from_slice(b"\r\n");
body.extend_from_slice(b"Content-Disposition: form-data; name=\"");
body.extend_from_slice(name.as_bytes());
body.extend_from_slice(b"\"");
if let Some(filename) = filename {
body.extend_from_slice(b"; filename=\"");
body.extend_from_slice(filename.as_bytes());
body.extend_from_slice(b"\"");
}
body.extend_from_slice(b"\r\n\r\n");
body.extend_from_slice(&content);
body.extend_from_slice(b"\r\n");
}
body.extend_from_slice(b"--");
body.extend_from_slice(boundary.as_bytes());
body.extend_from_slice(b"--\r\n");
self.body = Some(body);
self.body_format = Some(BodyFormat::FormData(boundary));
self
}
pub fn json_str(self, json: impl Into<String>) -> Self {
self.body(json.into().into_bytes())
.body_format(BodyFormat::Json)
}
pub fn compression(self, compression: bool) -> Self {
if compression {
self.header("Accept-Encoding", "gzip, deflate")
} else {
self
}
}
pub fn keep_alive(self, keep_alive: bool) -> Self {
self.header(
"Connection",
if keep_alive { "keep-alive" } else { "close" },
)
}
pub fn send(self) -> Result<Response> {
let client = self.client.clone();
client.send_request(self).map_err(|e| e.into())
}
}