use std::{collections::HashMap, fmt::Debug, sync::Arc};
use async_trait::async_trait;
use http::{
header::{self, HOST},
HeaderMap, HeaderName, HeaderValue, Method,
};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use serde::Serialize;
use url::Url;
use crate::{
client::serde::RequestBody,
cookie::DeboaCookie,
errors::DeboaError,
form::{DeboaForm, Form},
response::DeboaResponse,
url::IntoUrl,
Deboa, Result,
};
pub trait IntoRequest {
fn into_request(self) -> Result<DeboaRequest>;
}
impl IntoRequest for DeboaRequest {
fn into_request(self) -> Result<DeboaRequest> {
Ok(self)
}
}
impl IntoRequest for &str {
fn into_request(self) -> Result<DeboaRequest> {
DeboaRequest::get(self)?.build()
}
}
impl IntoRequest for String {
fn into_request(self) -> Result<DeboaRequest> {
DeboaRequest::get(self)?.build()
}
}
impl IntoRequest for Url {
fn into_request(self) -> Result<DeboaRequest> {
DeboaRequest::get(self)?.build()
}
}
#[async_trait]
pub trait Fetch {
async fn fetch<T>(&self, client: T) -> Result<DeboaResponse>
where
T: AsMut<Deboa> + Send;
}
#[async_trait]
impl Fetch for &str {
async fn fetch<T>(&self, client: T) -> Result<DeboaResponse>
where
T: AsMut<Deboa> + Send,
{
DeboaRequest::get(*self)?.go(client).await
}
}
#[inline]
pub fn get<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
DeboaRequest::get(url)
}
#[inline]
pub fn post<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
DeboaRequest::post(url)
}
#[inline]
pub fn put<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
DeboaRequest::put(url)
}
#[inline]
pub fn delete<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
DeboaRequest::delete(url)
}
#[inline]
pub fn patch<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
DeboaRequest::patch(url)
}
pub struct DeboaRequestBuilder {
retries: u32,
url: Arc<Url>,
headers: HeaderMap,
cookies: Option<HashMap<String, DeboaCookie>>,
method: http::Method,
body: Arc<[u8]>,
form: Option<Form>,
}
impl DeboaRequestBuilder {
pub fn retries(mut self, retries: u32) -> Self {
self.retries = retries;
self
}
pub fn method(mut self, method: http::Method) -> Self {
self.method = method;
self
}
pub fn raw_body(mut self, body: &[u8]) -> Self {
self.body = body.into();
self
}
pub fn headers(mut self, headers: HeaderMap) -> Self {
self.headers = headers;
self
}
pub fn header(mut self, key: HeaderName, value: &str) -> Self {
self.headers
.insert(key, HeaderValue::from_str(value).unwrap());
self
}
pub fn cookies(mut self, cookies: HashMap<String, DeboaCookie>) -> Self {
self.cookies = Some(cookies);
self
}
pub fn cookie(mut self, cookie: DeboaCookie) -> Self {
if let Some(cookies) = &mut self.cookies {
cookies.insert(cookie.name().to_string(), cookie);
} else {
self.cookies = Some(HashMap::from([(cookie.name().to_string(), cookie)]));
}
self
}
pub fn form(mut self, form: Form) -> Self {
self.form = Some(form);
self
}
pub fn text(mut self, text: &str) -> Self {
self.body = text.as_bytes().into();
self
}
pub fn body_as<T: RequestBody, B: Serialize>(mut self, body_type: T, body: B) -> Result<Self> {
self.body = body_type.serialize(body)?.into();
Ok(self)
}
#[inline]
pub fn bearer_auth(self, token: &str) -> Self {
self.header(header::AUTHORIZATION, format!("Bearer {token}").as_str())
}
pub fn basic_auth(self, username: &str, password: &str) -> Self {
self.header(
header::AUTHORIZATION,
format!(
"Basic {}",
STANDARD.encode(format!("{username}:{password}"))
)
.as_str(),
)
}
pub fn build(self) -> Result<DeboaRequest> {
let mut request = DeboaRequest {
url: self.url,
headers: self.headers,
cookies: self.cookies,
retries: self.retries,
method: self.method,
body: self.body,
};
if let Some(form) = self.form {
request.set_form(form);
}
Ok(request)
}
pub async fn go<T: AsMut<Deboa>>(self, mut client: T) -> Result<DeboaResponse> {
client.as_mut().execute(self.build()?).await
}
}
pub struct DeboaRequest {
url: Arc<Url>,
headers: HeaderMap,
cookies: Option<HashMap<String, DeboaCookie>>,
retries: u32,
method: http::Method,
body: Arc<[u8]>,
}
impl Debug for DeboaRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DeboaRequest")
.field("url", &self.url)
.field("headers", &self.headers)
.field("cookies", &self.cookies)
.field("retries", &self.retries)
.field("method", &self.method)
.field("body", &self.body)
.finish()
}
}
impl AsRef<DeboaRequest> for DeboaRequest {
fn as_ref(&self) -> &DeboaRequest {
self
}
}
impl AsMut<DeboaRequest> for DeboaRequest {
fn as_mut(&mut self) -> &mut DeboaRequest {
self
}
}
impl DeboaRequest {
pub fn at<T: IntoUrl>(url: T, method: http::Method) -> Result<DeboaRequestBuilder> {
let parsed_url = url.into_url();
if let Err(e) = parsed_url {
return Err(DeboaError::UrlParse {
message: e.to_string(),
});
}
let url = parsed_url.unwrap();
let authority = url.authority();
let mut headers = HeaderMap::new();
headers.insert(header::HOST, HeaderValue::from_str(authority).unwrap());
Ok(DeboaRequestBuilder {
url: url.into(),
headers,
cookies: None,
retries: 0,
method,
body: Arc::new([]),
form: None,
})
}
#[inline]
pub fn from<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
DeboaRequest::at(url, Method::GET)
}
#[inline]
pub fn to<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
DeboaRequest::at(url, Method::POST)
}
#[inline]
pub fn get<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
Ok(DeboaRequest::from(url)?.method(Method::GET))
}
#[inline]
pub fn post<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
Ok(DeboaRequest::to(url)?.method(Method::POST))
}
#[inline]
pub fn put<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
Ok(DeboaRequest::to(url)?.method(Method::PUT))
}
#[inline]
pub fn patch<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
Ok(DeboaRequest::to(url)?.method(Method::PATCH))
}
#[inline]
pub fn delete<T: IntoUrl>(url: T) -> Result<DeboaRequestBuilder> {
Ok(DeboaRequest::from(url)?.method(Method::DELETE))
}
pub fn set_method(&mut self, method: http::Method) -> &mut Self {
self.method = method;
self
}
#[inline]
pub fn method(&self) -> &http::Method {
&self.method
}
pub fn set_url<T: IntoUrl>(&mut self, url: T) -> Result<&mut Self> {
let parsed_url = url.into_url();
if let Err(e) = parsed_url {
return Err(DeboaError::UrlParse {
message: e.to_string(),
});
}
let parsed_url = parsed_url.unwrap();
if self.has_header(&header::HOST) {
self.headers.remove(&header::HOST);
self.add_header(HOST, parsed_url.authority());
}
self.url = parsed_url.into();
Ok(self)
}
#[inline]
pub fn url(&self) -> Arc<Url> {
Arc::clone(&self.url)
}
#[inline]
pub fn retries(&self) -> u32 {
self.retries
}
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
pub fn add_header(&mut self, key: HeaderName, value: &str) -> &mut Self {
self.headers
.insert(key, HeaderValue::from_str(value).unwrap());
self
}
#[inline]
fn has_header(&self, key: &HeaderName) -> bool {
self.headers.contains_key(key)
}
pub fn add_bearer_auth(&mut self, token: &str) -> &mut Self {
let auth = format!("Bearer {token}");
self.add_header(header::AUTHORIZATION, &auth);
self
}
pub fn add_basic_auth(&mut self, username: &str, password: &str) -> &mut Self {
let auth = format!(
"Basic {}",
STANDARD.encode(format!("{username}:{password}"))
);
self.add_header(header::AUTHORIZATION, &auth);
self
}
pub fn add_cookie(&mut self, cookie: DeboaCookie) -> &mut Self {
if let Some(cookies) = &mut self.cookies {
cookies.insert(cookie.name().to_string(), cookie);
} else {
self.cookies = Some(HashMap::from([(cookie.name().to_string(), cookie)]));
}
self
}
pub fn remove_cookie(&mut self, name: &str) -> &mut Self {
if let Some(cookies) = &mut self.cookies {
cookies.remove(name);
}
self
}
pub fn has_cookie(&self, name: &str) -> bool {
if let Some(cookies) = &self.cookies {
cookies.contains_key(name)
} else {
false
}
}
pub fn set_cookies(&mut self, cookies: HashMap<String, DeboaCookie>) -> &mut Self {
self.cookies = Some(cookies);
self
}
pub fn cookies(&self) -> Option<&HashMap<String, DeboaCookie>> {
self.cookies.as_ref()
}
pub fn set_form(&mut self, form: Form) -> &mut Self {
let (content_type, body) = match form {
Form::EncodedForm(form) => (form.content_type(), form.build()),
Form::MultiPartForm(form) => (form.content_type(), form.build()),
};
self.add_header(header::CONTENT_TYPE, &content_type);
self.set_raw_body(body.as_bytes());
self
}
pub fn set_text(&mut self, text: String) -> &mut Self {
self.set_raw_body(text.as_bytes());
self
}
pub fn set_raw_body(&mut self, body: &[u8]) -> &mut Self {
self.add_header(header::CONTENT_LENGTH, &body.len().to_string());
self.body = body.into();
self
}
#[inline]
pub fn raw_body(&self) -> &[u8] {
&self.body
}
pub fn set_body_as<T: RequestBody, B: Serialize>(
&mut self,
body_type: T,
body: B,
) -> Result<&mut Self> {
body_type.register_content_type(self);
let body = body_type.serialize(body)?;
self.set_raw_body(&body);
Ok(self)
}
}