use std::{fmt, future::Future, marker::PhantomData};
use crux_core::{Command, command};
use http_types::{
Body, Method, Mime, Url,
convert::DeserializeOwned,
headers::{HeaderName, ToHeaderValues},
};
use serde::Serialize;
use crate::{
HttpError, Request, Response,
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,
{
#[allow(clippy::missing_panics_doc)]
pub fn header(mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) -> Self {
self.req.as_mut().unwrap().insert_header(key, 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) -> crate::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) -> crate::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>, HttpError>>,
> {
let req = self.req.expect("RequestBuilder::build called twice");
command::RequestBuilder::new(|ctx| async move {
let operation = req
.into_protocol_request()
.await
.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::<Vec<u8>>::new(response.into())
.await
.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)
}
}