ts-webapi 0.4.11

Library for my web API projects
Documentation
//! Helpers for testing web APIs.

#![allow(clippy::missing_panics_doc)]

use core::str::FromStr;

use bytes::Bytes;
use http::{HeaderName, HeaderValue, Request, Response, StatusCode, header::CONTENT_TYPE};
use http_body_util::{Empty, Full};
use mime::APPLICATION_JSON;
use serde::de::DeserializeOwned;

/// Extension trait for a response for testing.
pub trait ResponseTestExt {
    /// Deserialize the JSON body as the given type.
    fn body<T: DeserializeOwned>(self) -> impl Future<Output = T> + Send;

    /// Expect the response to have the given header.
    #[track_caller]
    fn expect_has_header<H: AsRef<str>>(self, header: H) -> Self;

    /// Expect the response to have the given header with the given value.
    #[track_caller]
    fn expect_header<H: AsRef<str>>(self, header: H, value: &str) -> Self;

    /// Expect the response to have the given status.
    #[track_caller]
    fn expect_status(self, status: StatusCode) -> Self;

    /// Get the given header.
    #[track_caller]
    fn get_header<H: AsRef<str>>(&self, header: H) -> Option<String>;
}

impl<B: http_body_util::BodyExt + Send> ResponseTestExt for Response<B>
where
    <B as http_body::Body>::Data: Send,
{
    async fn body<T: DeserializeOwned>(self) -> T {
        let body = self
            .into_body()
            .collect()
            .await
            .unwrap_or_else(|_| panic!("failed to collect body"))
            .to_bytes();

        serde_json::from_slice(&body).expect("should be able to deserialize body")
    }

    #[track_caller]
    fn expect_has_header<H: AsRef<str>>(self, header: H) -> Self {
        self.headers()
            .get(HeaderName::from_str(header.as_ref()).expect("headers hould be ok"))
            .unwrap_or_else(|| {
                panic!("expected response to have the `{}` header", header.as_ref())
            });
        self
    }

    #[track_caller]
    fn expect_header<H: AsRef<str>>(self, header: H, value: &str) -> Self {
        let real_value = self.get_header(&header).unwrap_or_else(|| {
            panic!("expected response to have the `{}` header", header.as_ref())
        });
        assert_eq!(
            real_value,
            value,
            "header `{}` did not have the expected value",
            header.as_ref()
        );
        self
    }

    #[track_caller]
    fn expect_status(self, status: StatusCode) -> Self {
        assert_eq!(self.status(), status);
        self
    }

    #[track_caller]
    fn get_header<H: AsRef<str>>(&self, header: H) -> Option<String> {
        let value = self
            .headers()
            .get(HeaderName::from_str(header.as_ref()).expect("headers hould be ok"))?;

        let value = value
            .to_str()
            .unwrap_or_else(|_| panic!("expected header `{}` to be a string", header.as_ref()))
            .to_string();

        Some(value)
    }
}

/// Create a POST request.
pub fn post(
    uri: &str,
    body: &serde_json::Value,
    additional_headers: Vec<(&str, &str)>,
) -> Request<Full<Bytes>> {
    let body = Full::new(Bytes::from_owner(
        serde_json::to_vec(body).expect("body should be ok"),
    ));

    let mut request = Request::builder()
        .uri(uri)
        .method("POST")
        .header(CONTENT_TYPE, APPLICATION_JSON.as_ref());

    let headers = request.headers_mut().expect("request should be ok");
    for (name, value) in additional_headers {
        headers.insert(
            HeaderName::from_str(name).expect("header name should be ok"),
            HeaderValue::from_str(value).expect("header value should be ok"),
        );
    }

    request.body(body).expect("request should be ok")
}

/// Create a PUT request.
pub fn put(
    uri: &str,
    body: &serde_json::Value,
    additional_headers: Vec<(&str, &str)>,
) -> Request<Full<Bytes>> {
    let body = Full::new(Bytes::from_owner(
        serde_json::to_vec(body).expect("body should be ok"),
    ));

    let mut request = Request::builder()
        .uri(uri)
        .method("PUT")
        .header(CONTENT_TYPE, APPLICATION_JSON.as_ref());

    let headers = request.headers_mut().expect("request should be ok");
    for (name, value) in additional_headers {
        headers.insert(
            HeaderName::from_str(name).expect("header name should be ok"),
            HeaderValue::from_str(value).expect("header value should be ok"),
        );
    }

    request.body(body).expect("request should be ok")
}

/// Create a GET request.
pub fn get(uri: &str, additional_headers: Vec<(&str, &str)>) -> Request<Empty<Bytes>> {
    let mut request = Request::builder().uri(uri).method("GET");

    let headers = request.headers_mut().expect("request should be ok");
    for (name, value) in additional_headers {
        headers.insert(
            HeaderName::from_str(name).expect("header name should be ok"),
            HeaderValue::from_str(value).expect("header value should be ok"),
        );
    }

    request.body(Empty::new()).expect("request should be ok")
}

/// Create a DELETE request.
pub fn delete(uri: &str, additional_headers: Vec<(&str, &str)>) -> Request<Empty<Bytes>> {
    let mut request = Request::builder().uri(uri).method("DELETE");

    let headers = request.headers_mut().expect("request should be ok");
    for (name, value) in additional_headers {
        headers.insert(
            HeaderName::from_str(name).expect("header name should be ok"),
            HeaderValue::from_str(value).expect("header value should be ok"),
        );
    }

    request.body(Empty::new()).expect("request should be ok")
}