use std::collections::BTreeMap;
use base64::Engine;
#[cfg(feature = "serde")]
use serde::Serialize;
use super::{HttpMethod, RequestBody, TestClient, TestResponse};
pub struct TestRequestBuilder<'a, T: TestClient + ?Sized> {
client: &'a T,
method: HttpMethod,
path: String,
headers: BTreeMap<String, String>,
body: Option<RequestBody>,
}
impl<'a, T: TestClient + ?Sized> TestRequestBuilder<'a, T> {
#[must_use]
pub const fn new(client: &'a T, method: HttpMethod, path: String) -> Self {
Self {
client,
method,
path,
headers: BTreeMap::new(),
body: None,
}
}
#[must_use]
pub fn header<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
self.headers.insert(key.into(), value.into());
self
}
#[must_use]
pub fn headers<K: Into<String>, V: Into<String>>(
mut self,
headers: impl IntoIterator<Item = (K, V)>,
) -> Self {
for (key, value) in headers {
self.headers.insert(key.into(), value.into());
}
self
}
#[must_use]
pub fn content_type(self, content_type: &str) -> Self {
self.header("content-type", content_type)
}
#[must_use]
pub fn authorization(self, auth: &str) -> Self {
self.header("authorization", auth)
}
#[must_use]
pub fn bearer_token(self, token: &str) -> Self {
self.header("authorization", format!("Bearer {token}"))
}
#[must_use]
pub fn user_agent(self, user_agent: &str) -> Self {
self.header("user-agent", user_agent)
}
#[must_use]
pub fn body_bytes(mut self, body: Vec<u8>) -> Self {
self.body = Some(RequestBody::Bytes(body));
self
}
#[cfg(feature = "serde")]
#[must_use]
pub fn json<S: Serialize>(mut self, value: &S) -> Self {
self.body = Some(RequestBody::json(value).expect("Failed to serialize JSON"));
self
}
#[must_use]
pub fn form<K: Into<String>, V: Into<String>>(
mut self,
data: impl IntoIterator<Item = (K, V)>,
) -> Self {
self.body = Some(RequestBody::form(data));
self
}
#[must_use]
pub fn text(mut self, text: String) -> Self {
self.body = Some(RequestBody::Text(text));
self
}
pub fn send(mut self) -> Result<TestResponse, T::Error> {
if let Some(ref body) = self.body
&& !self.headers.contains_key("content-type")
{
let (_, content_type) = body
.to_bytes_and_content_type()
.expect("Failed to serialize request body");
self.headers
.insert("content-type".to_string(), content_type);
}
let body_bytes = self.body.as_ref().map(|body| {
let (bytes, _) = body
.to_bytes_and_content_type()
.expect("Failed to serialize request body");
bytes
});
self.client.execute_request(
self.method.as_str(),
&self.path,
&self.headers,
body_bytes.as_deref(),
)
}
}
impl<T: TestClient + ?Sized> TestRequestBuilder<'_, T> {
#[must_use]
pub fn query<K: Into<String>, V: Into<String>>(
mut self,
params: impl IntoIterator<Item = (K, V)>,
) -> Self {
let query_string = params
.into_iter()
.map(|(k, v)| {
format!(
"{}={}",
urlencoding::encode(&k.into()),
urlencoding::encode(&v.into())
)
})
.collect::<Vec<_>>()
.join("&");
if !query_string.is_empty() {
if self.path.contains('?') {
self.path.push('&');
} else {
self.path.push('?');
}
self.path.push_str(&query_string);
}
self
}
#[cfg(feature = "serde")]
#[must_use]
pub fn json_post<S: Serialize>(self, value: &S) -> Self {
self.content_type("application/json").json(value)
}
#[must_use]
pub fn form_post<K: Into<String>, V: Into<String>>(
self,
data: impl IntoIterator<Item = (K, V)>,
) -> Self {
self.content_type("application/x-www-form-urlencoded")
.form(data)
}
#[must_use]
pub fn basic_auth(self, username: &str, password: Option<&str>) -> Self {
let credentials =
password.map_or_else(|| username.to_string(), |pwd| format!("{username}:{pwd}"));
let encoded = base64::engine::general_purpose::STANDARD.encode(credentials);
self.authorization(&format!("Basic {encoded}"))
}
}