use std::{fmt, future::Future, marker::PhantomData};
use crux_core::{Command, command};
use http::{HeaderValue, Method};
use mime::Mime;
use serde::Serialize;
use serde::de::DeserializeOwned;
use url::Url;
use crate::{
HttpError, Request, Response, Result,
body::Body,
expect::{ExpectBytes, ExpectJson, ExpectString, ResponseExpectation},
middleware::Middleware,
protocol::{HttpRequest, HttpResult, ProtocolRequestBuilder},
};
#[deprecated(since = "0.16.0", note = "Import directly from crate root")]
pub use crate::Http;
#[must_use]
pub struct RequestBuilder<Effect, Event, ExpectBody = Vec<u8>> {
req: Option<Request>,
effect: PhantomData<Effect>,
event: PhantomData<fn() -> Event>,
expectation: Box<dyn ResponseExpectation<Body = ExpectBody> + Send>,
}
impl<Effect, Event> RequestBuilder<Effect, Event, Vec<u8>>
where
Effect: Send + From<crux_core::Request<HttpRequest>> + 'static,
Event: 'static,
{
pub(crate) fn new(method: Method, url: Url) -> Self {
Self {
req: Some(Request::new(method, url)),
effect: PhantomData,
event: PhantomData,
expectation: Box::new(ExpectBytes),
}
}
}
impl<Effect, Event, ExpectBody> RequestBuilder<Effect, Event, ExpectBody>
where
Effect: Send + From<crux_core::Request<HttpRequest>> + 'static,
Event: Send + 'static,
ExpectBody: 'static,
{
pub fn header(
mut self,
name: impl http::header::IntoHeaderName,
value: impl AsRef<str>,
) -> Self {
let value = HeaderValue::from_str(value.as_ref()).expect("invalid header value");
self.req.as_mut().unwrap().insert_header(name, value);
self
}
#[allow(clippy::missing_panics_doc)]
pub fn content_type(mut self, content_type: impl Into<Mime>) -> Self {
self.req
.as_mut()
.unwrap()
.set_content_type(&content_type.into());
self
}
#[allow(clippy::missing_panics_doc)]
pub fn body(mut self, body: impl Into<Body>) -> Self {
self.req.as_mut().unwrap().set_body(body);
self
}
pub fn body_json(self, json: &impl Serialize) -> Result<Self> {
Ok(self.body(Body::from_json(json)?))
}
pub fn body_string(self, string: String) -> Self {
self.body(Body::from_string(string))
}
pub fn body_bytes(self, bytes: impl AsRef<[u8]>) -> Self {
self.body(Body::from(bytes.as_ref()))
}
pub fn body_form(self, form: &impl Serialize) -> Result<Self> {
Ok(self.body(Body::from_form(form)?))
}
#[allow(clippy::missing_panics_doc)]
pub fn query(mut self, query: &impl Serialize) -> std::result::Result<Self, HttpError> {
self.req.as_mut().unwrap().set_query(query)?;
Ok(self)
}
#[allow(clippy::missing_panics_doc)]
pub fn middleware(mut self, middleware: impl Middleware) -> Self {
self.req.as_mut().unwrap().middleware(middleware);
self
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn build(
self,
) -> command::RequestBuilder<Effect, Event, impl Future<Output = Result<Response<ExpectBody>>>>
{
let req = self.req.expect("RequestBuilder::build called twice");
command::RequestBuilder::new(|ctx| async move {
let operation = req
.into_protocol_request()
.expect("should be able to convert request to protocol request");
let result = Command::request_from_shell(operation)
.into_future(ctx)
.await;
match result {
HttpResult::Ok(response) => response
.try_into()
.and_then(Response::<Vec<u8>>::new)
.and_then(|r| self.expectation.decode(r)),
HttpResult::Err(error) => Err(error),
}
})
}
pub fn expect_string(self) -> RequestBuilder<Effect, Event, String> {
let expectation = Box::<ExpectString>::default();
RequestBuilder {
req: self.req,
effect: PhantomData,
event: PhantomData,
expectation,
}
}
pub fn expect_json<T>(self) -> RequestBuilder<Effect, Event, T>
where
T: DeserializeOwned + 'static,
{
let expectation = Box::<ExpectJson<T>>::default();
RequestBuilder {
req: self.req,
effect: PhantomData,
event: PhantomData,
expectation,
}
}
}
impl<Effect, Event> fmt::Debug for RequestBuilder<Effect, Event> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.req, f)
}
}