use bytes::Bytes;
use futures::Stream;
use serde::de::DeserializeOwned;
use std::{borrow::Cow, future::Future};
use url::Url;
use crate::error;
#[derive(Debug, Clone)]
pub struct HttpRequest<'a> {
url: Url,
body: Option<Cow<'a, str>>,
bearer_token: Option<Cow<'a, str>>,
}
impl<'a> HttpRequest<'a> {
pub fn new(url: Url) -> Self {
Self {
url,
body: None,
bearer_token: None,
}
}
pub fn with_body<T>(mut self, body: T) -> Self
where
T: Into<Cow<'a, str>>,
{
self.body = Some(body.into());
self
}
pub fn with_bearer_token<T>(mut self, token: T) -> Self
where
T: Into<Cow<'a, str>>,
{
self.bearer_token = Some(token.into());
self
}
pub fn url(&self) -> &Url {
&self.url
}
pub fn body(&self) -> Option<&str> {
self.body.as_deref()
}
pub fn bearer_token(&self) -> Option<&str> {
self.bearer_token.as_deref()
}
pub fn into_parts(self) -> (Url, Option<Cow<'a, str>>, Option<Cow<'a, str>>) {
(self.url, self.body, self.bearer_token)
}
pub fn into_owned(self) -> HttpRequest<'static> {
let (url, body, bearer_token) = self.into_parts();
HttpRequest {
url,
body: body.map(Cow::into_owned).map(Cow::Owned),
bearer_token: bearer_token.map(Cow::into_owned).map(Cow::Owned),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
}
pub trait BorrowHttpResponse: Sized {
fn status(&self) -> u16;
fn header(&self, name: &str) -> Result<Option<&str>, error::Error>;
fn text(self) -> impl Future<Output = Result<String, error::Error>>;
fn bytes(self) -> impl Future<Output = Result<Bytes, error::Error>>;
fn json<T>(self) -> impl Future<Output = Result<T, error::Error>>
where
T: DeserializeOwned;
}
pub trait BorrowChunkedHttpResponse: BorrowHttpResponse {
type Stream: Stream<Item = Result<Bytes, error::Error>>;
fn into_chunk_stream(
self,
) -> impl Future<Output = Result<Self::Stream, error::Error>> + use<Self>;
}
pub trait HttpResponse: Send + 'static {
fn status(&self) -> u16;
fn header(&self, name: &str) -> Result<Option<&str>, error::Error>;
fn text(
self,
) -> impl Future<Output = Result<String, error::Error>> + Send + 'static + use<Self>;
fn bytes(
self,
) -> impl Future<Output = Result<Bytes, error::Error>> + Send + 'static + use<Self>;
fn json<T>(
self,
) -> impl Future<Output = Result<T, error::Error>> + Send + 'static + use<Self, T>
where
T: DeserializeOwned + 'static;
}
pub trait ChunkedHttpResponse: HttpResponse {
type Stream: Stream<Item = Result<Bytes, error::Error>> + Send + 'static;
fn into_chunk_stream(
self,
) -> impl Future<Output = Result<Self::Stream, error::Error>> + Send + 'static + use<Self>;
}
pub trait HttpClient {
type Response: HttpResponse;
fn send(
&self,
method: HttpMethod,
request: HttpRequest<'static>,
) -> impl Future<Output = Result<Self::Response, error::Error>> + Send + 'static + use<Self>;
}
pub trait BorrowHttpClient {
type Response: BorrowHttpResponse;
fn send<'a>(
&'a self,
method: HttpMethod,
request: HttpRequest<'a>,
) -> impl Future<Output = Result<Self::Response, error::Error>> + use<'a, Self>;
}
#[cfg(test)]
mod tests {
use super::HttpRequest;
use std::borrow::Cow;
use url::Url;
#[test]
fn http_request_preserves_borrowed_body_and_token() {
let request = HttpRequest::new(Url::parse("http://localhost:8086").unwrap())
.with_body("cpu value=1i")
.with_bearer_token("jwt-token");
let (_url, body, token) = request.into_parts();
assert!(matches!(body, Some(Cow::Borrowed("cpu value=1i"))));
assert!(matches!(token, Some(Cow::Borrowed("jwt-token"))));
}
#[test]
fn http_request_into_owned_promotes_borrowed_parts() {
let request = HttpRequest::new(Url::parse("http://localhost:8086").unwrap())
.with_body("cpu value=1i")
.with_bearer_token("jwt-token")
.into_owned();
let (_url, body, token) = request.into_parts();
assert!(matches!(body, Some(Cow::Owned(body)) if body == "cpu value=1i"));
assert!(matches!(token, Some(Cow::Owned(token)) if token == "jwt-token"));
}
}