use crate::client::{BufferedService, map_buffer_error, try_acquire_buffer_slot};
use crate::config::TransportSecurity;
use crate::error::{HttpError, InvalidUriKind};
use crate::response::{HttpResponse, ResponseBody};
use bytes::Bytes;
use http::{Request, Response};
use http_body_util::Full;
use serde::Serialize;
use tower::Service;
#[derive(Clone, Debug)]
enum BodyKind {
Empty,
Bytes(Bytes),
Json(Bytes),
Form(Bytes),
}
#[must_use = "RequestBuilder does nothing until .send() is called"]
pub struct RequestBuilder {
service: BufferedService,
max_body_size: usize,
method: http::Method,
url: String,
headers: Vec<(http::header::HeaderName, http::header::HeaderValue)>,
body: BodyKind,
error: Option<HttpError>,
transport_security: TransportSecurity,
}
impl RequestBuilder {
pub(crate) fn new(
service: BufferedService,
max_body_size: usize,
method: http::Method,
url: String,
transport_security: TransportSecurity,
) -> Self {
Self {
service,
max_body_size,
method,
url,
headers: Vec::new(),
body: BodyKind::Empty,
error: None,
transport_security,
}
}
pub fn header(mut self, name: &str, value: &str) -> Self {
if self.error.is_some() {
return self;
}
match (
http::header::HeaderName::try_from(name),
http::header::HeaderValue::try_from(value),
) {
(Ok(name), Ok(value)) => {
self.headers.push((name, value));
}
(Err(e), _) => {
self.error = Some(HttpError::InvalidHeaderName(e));
}
(_, Err(e)) => {
self.error = Some(HttpError::InvalidHeaderValue(e));
}
}
self
}
pub fn headers(mut self, headers: Vec<(String, String)>) -> Self {
if self.error.is_some() {
return self;
}
for (name, value) in headers {
match (
http::header::HeaderName::try_from(name),
http::header::HeaderValue::try_from(value),
) {
(Ok(name), Ok(value)) => {
self.headers.push((name, value));
}
(Err(e), _) => {
self.error = Some(HttpError::InvalidHeaderName(e));
return self;
}
(_, Err(e)) => {
self.error = Some(HttpError::InvalidHeaderValue(e));
return self;
}
}
}
self
}
pub fn json<T: Serialize>(mut self, body: &T) -> Result<Self, HttpError> {
if let Some(e) = self.error.take() {
return Err(e);
}
let json_bytes = serde_json::to_vec(body)?;
self.body = BodyKind::Json(Bytes::from(json_bytes));
Ok(self)
}
pub fn form(mut self, fields: &[(&str, &str)]) -> Result<Self, HttpError> {
if let Some(e) = self.error.take() {
return Err(e);
}
let form_string = serde_urlencoded::to_string(fields)?;
self.body = BodyKind::Form(Bytes::from(form_string));
Ok(self)
}
pub fn body_bytes(mut self, body: Bytes) -> Self {
self.body = BodyKind::Bytes(body);
self
}
pub fn body_string(mut self, body: String) -> Self {
self.body = BodyKind::Bytes(Bytes::from(body));
self
}
fn validate_url(&self) -> Result<http::Uri, HttpError> {
let uri: http::Uri =
self.url
.parse()
.map_err(|e: http::uri::InvalidUri| HttpError::InvalidUri {
url: self.url.clone(),
kind: InvalidUriKind::ParseError,
reason: e.to_string(),
})?;
if uri.authority().is_none() {
return Err(HttpError::InvalidUri {
url: self.url.clone(),
kind: InvalidUriKind::MissingAuthority,
reason: "missing host/authority".to_owned(),
});
}
match uri.scheme_str() {
Some("https") => Ok(uri),
Some("http") => match self.transport_security {
TransportSecurity::AllowInsecureHttp => Ok(uri),
TransportSecurity::TlsOnly => Err(HttpError::InvalidScheme {
scheme: "http".to_owned(),
reason: "HTTPS required (transport security is TlsOnly)".to_owned(),
}),
},
Some(scheme) => Err(HttpError::InvalidScheme {
scheme: scheme.to_owned(),
reason: "only http:// and https:// schemes are supported".to_owned(),
}),
None => Err(HttpError::InvalidUri {
url: self.url.clone(),
kind: InvalidUriKind::MissingScheme,
reason: "missing scheme".to_owned(),
}),
}
}
pub async fn send(mut self) -> Result<HttpResponse, HttpError> {
if let Some(e) = self.error.take() {
return Err(e);
}
let uri = self.validate_url()?;
let mut builder = Request::builder().method(self.method).uri(uri);
let has_content_type = self
.headers
.iter()
.any(|(name, _)| name == http::header::CONTENT_TYPE);
if !has_content_type {
match &self.body {
BodyKind::Json(_) => {
builder = builder.header("content-type", "application/json");
}
BodyKind::Form(_) => {
builder = builder.header("content-type", "application/x-www-form-urlencoded");
}
BodyKind::Empty | BodyKind::Bytes(_) => {}
}
}
for (name, value) in self.headers {
builder = builder.header(name, value);
}
let body_bytes = match self.body {
BodyKind::Empty => Bytes::new(),
BodyKind::Bytes(b) | BodyKind::Json(b) | BodyKind::Form(b) => b,
};
let request = builder.body(Full::new(body_bytes))?;
try_acquire_buffer_slot(&mut self.service).await?;
let inner: Response<ResponseBody> =
self.service.call(request).await.map_err(map_buffer_error)?;
Ok(HttpResponse {
inner,
max_body_size: self.max_body_size,
})
}
}