rust-integration-services 0.5.26

A modern, fast, and lightweight integration library written in Rust, designed for memory safety and stability.
Documentation
use std::marker::PhantomData;
use std::str::FromStr;

use anyhow::Error;
use bytes::Bytes;
use futures::StreamExt;
use http_body_util::{Empty, Full, StreamBody};
use http_body_util::{BodyExt, combinators::BoxBody};
use hyper::{HeaderMap, Uri};
use hyper::body::Frame;
use hyper::header::{HeaderName, HeaderValue};
use hyper::{Request, body::Incoming};

use crate::common::stream::ByteStream;

pub struct Final;
pub struct SetMethod;

#[derive(Debug)]
pub struct HttpRequest {
    body: BoxBody<Bytes, Error>,
    parts: hyper::http::request::Parts,
    params: Vec<(String, String)>,
}

impl HttpRequest {

    /// Create a new request using builder.
    pub fn builder() -> HttpRequestBuilder<SetMethod>  {
        HttpRequestBuilder {
            builder: Request::builder(),
            uri_string: None,
            _state: PhantomData
        }
    }

    /// Create a new request from hyper parts.
    pub fn from_parts(body: BoxBody<Bytes, Error>, parts: hyper::http::request::Parts) -> HttpRequest {
        HttpRequest {
            body,
            parts,
            params: Vec::new()
        }
    }

    /// Create a new request from hyper parts with params.
    pub fn from_parts_with_params(body: BoxBody<Bytes, Error>, parts: hyper::http::request::Parts, params: Vec<(String, String)>) -> HttpRequest {
        HttpRequest {
            body,
            parts,
            params
        }
    }

    /// Returns the boxed body.
    /// 
    /// Used for moving body between requests/responses.
    ///
    /// **This consumes the HttpRequest**
    pub fn body(self) -> ByteStream {
        let stream = self.body.into_data_stream();
        ByteStream::new(stream)
    }

    /// Returns the method.
    pub fn method(&self) -> &str {
        self.parts.method.as_str()
    }

    /// Returns the uri host.
    pub fn host(&self) -> Option<&str> {
        self.parts.uri.host()
    }

    /// Returns the uri path.
    pub fn path(&self) -> &str {
        self.parts.uri.path()
    }

    /// Returns the uri port.
    pub fn port(&self) -> Option<u16> {
        self.parts.uri.port_u16()
    }

    /// Returns the uri scheme.
    pub fn scheme(&self) -> Option<&str> {
        self.parts.uri.scheme_str()
    }

    /// Add a header.
    pub fn add_header(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> anyhow::Result<()> {
        let key = HeaderName::from_str(key.as_ref())?;
        let value = value.as_ref().parse()?;
        self.parts.headers.insert(key, value);
        Ok(())
    }

    /// Remove a header.
    pub fn remove_header(&mut self, key: impl AsRef<str>) {
        self.parts.headers.remove(key.as_ref());
    }

    /// Returns a single header by key.
    pub fn header(&self, key: impl AsRef<str>) -> Option<&HeaderValue> {
        self.parts.headers.get(key.as_ref())
    }

    /// Returns all headers.
    pub fn headers(&self) -> &HeaderMap {
        &self.parts.headers
    }

    // Returns a param by key.
    pub fn param(&self, key: impl AsRef<str>) -> Option<&str> {
        self.params.iter().find(|(k, _)| k == key.as_ref()).map(|(_, v)| v.as_str())
    }

    /// Returns an iterator with request params.
    pub fn params(&self) -> impl Iterator<Item = (&str, &str)>  {
        self.params.iter().map(|(k, v)| (k.as_str(), v.as_str()))
    }
}

pub struct HttpRequestBuilder<State> {
    builder: hyper::http::request::Builder,
    uri_string: Option<String>,
    _state: PhantomData<State>
}

impl HttpRequestBuilder<SetMethod> {
    /// Sets the HTTP method to `GET` and assigns the request URI.
    pub fn get(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("GET");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }

    /// Sets the HTTP method to `POST` and assigns the request URI.
    pub fn post(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("POST");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }

    /// Sets the HTTP method to `PUT` and assigns the request URI.
    pub fn put(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("PUT");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }

    /// Sets the HTTP method to `PATCH` and assigns the request URI.
    pub fn patch(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("PATCH");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }

    /// Sets the HTTP method to `DELETE` and assigns the request URI.
    pub fn delete(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("DELETE");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }

    /// Sets the HTTP method to `OPTIONS` and assigns the request URI.
    pub fn options(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("OPTIONS");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }

    /// Sets the HTTP method to `HEAD` and assigns the request URI.
    pub fn head(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("OPTIONS");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }

    /// Sets the HTTP method to `CONNECT` and assigns the request URI.
    pub fn connect(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("CONNECT");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }

    /// Sets the HTTP method to `TRACE` and assigns the request URI.
    pub fn trace(mut self, uri: impl Into<String>) -> HttpRequestBuilder<Final> {
        self.builder = self.builder.method("TRACE");
        HttpRequestBuilder {
            builder: self.builder,
            uri_string: Some(uri.into()),
            _state: PhantomData
        }
    }
}

impl HttpRequestBuilder<Final> {

    /// Finish the builder and the create the request with an empty body.
    pub fn body_empty(self) -> anyhow::Result<HttpRequest> {
        let uri: Uri = self.uri_string.unwrap().parse()?;
        let body = Empty::new().map_err(|e| match e {}).boxed();
        let request = self.builder.uri(&uri).header("Host", uri.host().unwrap()).body(body)?;
        Ok(HttpRequest::from(request))
    }

    /// Finish the builder and the create the request with a body of bytes in memory.
    pub fn body_bytes(self, body: impl Into<Bytes>) -> anyhow::Result<HttpRequest> {
        let uri: Uri = self.uri_string.unwrap().parse()?;
        let body = Full::from(body.into()).map_err(|e| match e {}).boxed();
        let request = self.builder.uri(&uri).header("Host", uri.host().unwrap()).body(body)?;
        Ok(HttpRequest::from(request))
    }

    /// Finish the builder and the create the request with a body of bytes as a stream.
    pub fn body_stream(self, stream: ByteStream) -> anyhow::Result<HttpRequest> {
        let uri: Uri = self.uri_string.unwrap().parse()?;
        let mapped_stream = stream.inner_stream().map(|res| { res.map(Frame::data) });
        let body = StreamBody::new(mapped_stream);
        let boxed_body: BoxBody<Bytes, anyhow::Error> = BodyExt::boxed(body);
        let request: Request<BoxBody<Bytes, Error>> = self.builder.uri(&uri).header("Host", uri.host().unwrap()).body(boxed_body)?;
        Ok(HttpRequest::from(request))
    }

    /// Add a header to the request.
    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.builder = self.builder.header(key.into(), value.into());
        self
    }

    /// Copy headers from another request or response.
    pub fn headers(mut self, headers: &HeaderMap) -> Self {
        for (key, value) in headers {
            self.builder = self.builder.header(key, value);
        }
        self
    }
}

impl From<HttpRequest> for Request<BoxBody<Bytes, Error>> {
    fn from(req: HttpRequest) -> Self {
        Request::from_parts(req.parts, req.body.boxed())
    }
}

impl From<Request<BoxBody<Bytes, Error>>> for HttpRequest {
    fn from(req: Request<BoxBody<Bytes, Error>>) -> Self {
        let (parts, body) = req.into_parts();
        HttpRequest::from_parts(body, parts)
    }
}

impl From<Request<Incoming>> for HttpRequest {
    fn from(req: Request<Incoming>) -> Self {
        let (parts, body) = req.into_parts();
        let body = body.map_err(anyhow::Error::from);
        HttpRequest::from_parts(body.boxed(), parts)
    }
}