use std::convert::TryFrom;
use std::fmt;
use bytes::Bytes;
use http::{request::Parts, Method, Request as HttpRequest};
use serde::Serialize;
#[cfg(feature = "json")]
use serde_json;
use url::Url;
use web_sys::RequestCredentials;
use super::{Body, Client, Response};
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
pub struct Request {
method: Method,
url: Url,
headers: HeaderMap,
body: Option<Body>,
pub(super) cors: bool,
pub(super) credentials: Option<RequestCredentials>,
}
pub struct RequestBuilder {
client: Client,
request: crate::Result<Request>,
}
impl Request {
#[inline]
pub fn new(method: Method, url: Url) -> Self {
Request {
method,
url,
headers: HeaderMap::new(),
body: None,
cors: true,
credentials: None,
}
}
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
#[inline]
pub fn method_mut(&mut self) -> &mut Method {
&mut self.method
}
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
#[inline]
pub fn url_mut(&mut self) -> &mut Url {
&mut self.url
}
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
#[inline]
pub fn body(&self) -> Option<&Body> {
self.body.as_ref()
}
#[inline]
pub fn body_mut(&mut self) -> &mut Option<Body> {
&mut self.body
}
pub fn try_clone(&self) -> Option<Request> {
let body = match self.body.as_ref() {
Some(body) => Some(body.try_clone()?),
None => None,
};
Some(Self {
method: self.method.clone(),
url: self.url.clone(),
headers: self.headers.clone(),
body,
cors: self.cors,
credentials: self.credentials,
})
}
}
impl RequestBuilder {
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
RequestBuilder { client, request }
}
pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
let url = req.url_mut();
let mut pairs = url.query_pairs_mut();
let serializer = serde_urlencoded::Serializer::new(&mut pairs);
if let Err(err) = query.serialize(serializer) {
error = Some(crate::error::builder(err));
}
}
if let Ok(ref mut req) = self.request {
if let Some("") = req.url().query() {
req.url_mut().set_query(None);
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
match serde_urlencoded::to_string(form) {
Ok(body) => {
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
match serde_json::to_vec(json) {
Ok(body) => {
req.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
where
T: fmt::Display,
{
let header_value = format!("Bearer {}", token);
self.header(crate::header::AUTHORIZATION, header_value)
}
pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.body = Some(body.into());
}
self
}
#[cfg(feature = "multipart")]
#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
pub fn multipart(mut self, multipart: super::multipart::Form) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
*req.body_mut() = Some(Body::from_form(multipart))
}
self
}
pub fn header<K, V>(mut self, key: K, value: V) -> RequestBuilder
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
let mut error = None;
if let Ok(ref mut req) = self.request {
match <HeaderName as TryFrom<K>>::try_from(key) {
Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
Ok(value) => {
req.headers_mut().append(key, value);
}
Err(e) => error = Some(crate::error::builder(e.into())),
},
Err(e) => error = Some(crate::error::builder(e.into())),
};
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
crate::util::replace_headers(req.headers_mut(), headers);
}
self
}
pub fn fetch_mode_no_cors(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cors = false;
}
self
}
pub fn fetch_credentials_same_origin(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::SameOrigin);
}
self
}
pub fn fetch_credentials_include(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::Include);
}
self
}
pub fn fetch_credentials_omit(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::Omit);
}
self
}
pub fn build(self) -> crate::Result<Request> {
self.request
}
pub async fn send(self) -> crate::Result<Response> {
let req = self.request?;
self.client.execute_request(req).await
}
pub fn try_clone(&self) -> Option<RequestBuilder> {
self.request
.as_ref()
.ok()
.and_then(|req| req.try_clone())
.map(|req| RequestBuilder {
client: self.client.clone(),
request: Ok(req),
})
}
}
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
}
}
impl fmt::Debug for RequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("RequestBuilder");
match self.request {
Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
Err(ref err) => builder.field("error", err).finish(),
}
}
}
fn fmt_request_fields<'a, 'b>(
f: &'a mut fmt::DebugStruct<'a, 'b>,
req: &Request,
) -> &'a mut fmt::DebugStruct<'a, 'b> {
f.field("method", &req.method)
.field("url", &req.url)
.field("headers", &req.headers)
}
impl<T> TryFrom<HttpRequest<T>> for Request
where
T: Into<Body>,
{
type Error = crate::Error;
fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
let (parts, body) = req.into_parts();
let Parts {
method,
uri,
headers,
..
} = parts;
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
Ok(Request {
method,
url,
headers,
body: Some(body.into()),
cors: true,
credentials: None,
})
}
}
impl TryFrom<Request> for HttpRequest<Body> {
type Error = crate::Error;
fn try_from(req: Request) -> crate::Result<Self> {
let Request {
method,
url,
headers,
body,
..
} = req;
let mut req = HttpRequest::builder()
.method(method)
.uri(url.as_str())
.body(body.unwrap_or_else(|| Body::from(Bytes::default())))
.map_err(crate::error::builder)?;
*req.headers_mut() = headers;
Ok(req)
}
}