use bytes::Bytes;
use http::{HeaderMap, HeaderName, HeaderValue, Method, Uri};
use crate::body::Body;
use crate::error::OxiHttpError;
use crate::OxiRequest;
#[derive(Debug)]
pub struct RequestBuilder {
method: Method,
uri: Uri,
headers: HeaderMap,
body: Body,
}
impl RequestBuilder {
pub fn new(method: Method, uri: Uri) -> Self {
Self {
method,
uri,
headers: HeaderMap::new(),
body: Body::empty(),
}
}
pub fn get(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
let uri = Uri::try_from(uri.as_ref())?;
Ok(Self::new(Method::GET, uri))
}
pub fn post(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
let uri = Uri::try_from(uri.as_ref())?;
Ok(Self::new(Method::POST, uri))
}
pub fn put(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
let uri = Uri::try_from(uri.as_ref())?;
Ok(Self::new(Method::PUT, uri))
}
pub fn delete(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
let uri = Uri::try_from(uri.as_ref())?;
Ok(Self::new(Method::DELETE, uri))
}
pub fn patch(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
let uri = Uri::try_from(uri.as_ref())?;
Ok(Self::new(Method::PATCH, uri))
}
pub fn head(uri: impl AsRef<str>) -> Result<Self, OxiHttpError> {
let uri = Uri::try_from(uri.as_ref())?;
Ok(Self::new(Method::HEAD, uri))
}
pub fn header(
mut self,
name: impl AsRef<str>,
value: impl AsRef<str>,
) -> Result<Self, OxiHttpError> {
let name = HeaderName::try_from(name.as_ref())
.map_err(|e| OxiHttpError::InvalidHeader(e.to_string()))?;
let value = HeaderValue::try_from(value.as_ref())
.map_err(|e| OxiHttpError::InvalidHeader(e.to_string()))?;
self.headers.insert(name, value);
Ok(self)
}
pub fn headers(mut self, headers: HeaderMap) -> Self {
for (k, v) in &headers {
self.headers.insert(k.clone(), v.clone());
}
self
}
pub fn body(mut self, body: impl Into<Body>) -> Self {
self.body = body.into();
self
}
pub fn json<T: serde::Serialize>(mut self, value: &T) -> Result<Self, OxiHttpError> {
let bytes = serde_json::to_vec(value).map_err(|e| OxiHttpError::Json(e.to_string()))?;
self.headers.insert(
http::header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
self.body = Body::full(Bytes::from(bytes));
Ok(self)
}
pub fn form(mut self, body: crate::FormBody) -> Self {
self.headers.insert(
http::header::CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
self.body = Body::full(body.build());
self
}
pub fn build(self) -> Result<OxiRequest<Body>, OxiHttpError> {
let mut builder = http::Request::builder().method(self.method).uri(self.uri);
for (k, v) in &self.headers {
builder = builder.header(k, v);
}
builder
.body(self.body)
.map_err(|e| OxiHttpError::Http(std::sync::Arc::new(e)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::FormBody;
#[test]
fn test_get_builder() {
let req = RequestBuilder::get("http://example.com/path")
.expect("valid URI")
.build()
.expect("build succeeds");
assert_eq!(req.method(), &Method::GET);
assert_eq!(req.uri().to_string(), "http://example.com/path");
}
#[test]
fn test_post_with_json() {
#[derive(serde::Serialize)]
struct Payload {
key: &'static str,
}
let req = RequestBuilder::post("http://example.com/api")
.expect("valid URI")
.json(&Payload { key: "value" })
.expect("serialises")
.build()
.expect("build succeeds");
assert_eq!(req.method(), &Method::POST);
assert_eq!(
req.headers()
.get(http::header::CONTENT_TYPE)
.map(|v| v.as_bytes()),
Some(b"application/json".as_ref()),
);
}
#[test]
fn test_headers_merged() {
let mut extra = HeaderMap::new();
extra.insert(
HeaderName::from_static("x-custom"),
HeaderValue::from_static("abc"),
);
let req = RequestBuilder::get("http://example.com")
.expect("valid URI")
.headers(extra)
.build()
.expect("build succeeds");
assert_eq!(
req.headers().get("x-custom").map(|v| v.as_bytes()),
Some(b"abc".as_ref()),
);
}
#[test]
fn test_form_sets_content_type() {
let form = FormBody::new().field("foo", "bar");
let req = RequestBuilder::post("http://example.com/form")
.expect("valid URI")
.form(form)
.build()
.expect("build succeeds");
assert_eq!(
req.headers()
.get(http::header::CONTENT_TYPE)
.map(|v| v.as_bytes()),
Some(b"application/x-www-form-urlencoded".as_ref()),
);
}
#[test]
fn test_invalid_uri_returns_error() {
let result = RequestBuilder::get("not a valid uri!!!!");
assert!(result.is_err());
}
#[test]
fn test_all_methods() {
let cases: &[(&str, Method)] = &[
("http://a.com", Method::POST),
("http://a.com", Method::PUT),
("http://a.com", Method::DELETE),
("http://a.com", Method::PATCH),
("http://a.com", Method::HEAD),
];
for (uri, method) in cases {
let req = match method.as_str() {
"POST" => RequestBuilder::post(uri),
"PUT" => RequestBuilder::put(uri),
"DELETE" => RequestBuilder::delete(uri),
"PATCH" => RequestBuilder::patch(uri),
"HEAD" => RequestBuilder::head(uri),
_ => unreachable!(),
}
.expect("valid uri")
.build()
.expect("build succeeds");
assert_eq!(req.method(), method);
}
}
}