trillium-http 1.1.0

the http implementation for the trillium toolkit
Documentation
//! Conversion impls between the `http` crate (versions 0.x and 1.x) and `trillium-http`
//! types.
//!
//! The two feature-gated submodules share a single macro-defined body parameterized on
//! the `http` crate version (`http0` or `http1`); each invocation expands into the same
//! set of `mod {version, method, status, header_name, headers, header_values, tests}`
//! sub-modules with `use $http as http;` aliasing the appropriate version.

#[macro_export]
#[doc(hidden)]
macro_rules! __generate_http_compat {
    ($http:ident) => {
        pub use headers::HeaderConversionError; // for semver

        mod version {
            use $http as http;
            impl TryFrom<http::Version> for crate::Version {
                type Error = String;

                fn try_from(version: http::Version) -> Result<Self, Self::Error> {
                    match version {
                        http::Version::HTTP_09 => Ok(crate::Version::Http0_9),
                        http::Version::HTTP_10 => Ok(crate::Version::Http1_0),
                        http::Version::HTTP_11 => Ok(crate::Version::Http1_1),
                        http::Version::HTTP_2 => Ok(crate::Version::Http2),
                        http::Version::HTTP_3 => Ok(crate::Version::Http3),
                        other => Err(format!("unsupported version {other:?}")),
                    }
                }
            }

            impl From<crate::Version> for http::Version {
                fn from(version: crate::Version) -> Self {
                    match version {
                        crate::Version::Http0_9 => http::Version::HTTP_09,
                        crate::Version::Http1_0 => http::Version::HTTP_10,
                        crate::Version::Http1_1 => http::Version::HTTP_11,
                        crate::Version::Http2 => http::Version::HTTP_2,
                        crate::Version::Http3 => http::Version::HTTP_3,
                    }
                }
            }

            impl PartialEq<crate::Version> for http::Version {
                fn eq(&self, other: &crate::Version) -> bool {
                    match TryInto::<crate::Version>::try_into(*self) {
                        Ok(v) => v.eq(other),
                        Err(_) => false,
                    }
                }
            }

            impl PartialEq<http::Version> for crate::Version {
                fn eq(&self, other: &http::Version) -> bool {
                    Into::<http::Version>::into(*self).eq(other)
                }
            }
        }

        mod method {
            use std::str::FromStr;
            use $http as http;

            impl TryFrom<http::Method> for crate::Method {
                type Error = <crate::Method as FromStr>::Err;

                fn try_from(http_method: http::Method) -> Result<Self, Self::Error> {
                    http_method.as_str().parse()
                }
            }

            impl TryFrom<&http::Method> for crate::Method {
                type Error = <crate::Method as FromStr>::Err;

                fn try_from(http_method: &http::Method) -> Result<Self, Self::Error> {
                    http_method.as_str().parse()
                }
            }

            impl TryFrom<crate::Method> for http::Method {
                type Error = <http::Method as FromStr>::Err;

                fn try_from(trillium_method: crate::Method) -> Result<Self, Self::Error> {
                    trillium_method.as_ref().parse()
                }
            }

            impl PartialEq<crate::Method> for http::Method {
                fn eq(&self, other: &crate::Method) -> bool {
                    TryInto::<crate::Method>::try_into(self).is_ok_and(|m| m.eq(other))
                }
            }

            impl PartialEq<http::Method> for crate::Method {
                fn eq(&self, other: &http::Method) -> bool {
                    TryInto::<http::Method>::try_into(*self).is_ok_and(|m| m.eq(other))
                }
            }
        }

        mod status {
            use $http as http;

            impl TryFrom<http::StatusCode> for crate::Status {
                type Error = <crate::Status as TryFrom<u16>>::Error;

                fn try_from(http_status_code: http::StatusCode) -> Result<Self, Self::Error> {
                    http_status_code.as_u16().try_into()
                }
            }

            impl TryFrom<crate::Status> for http::StatusCode {
                type Error = http::status::InvalidStatusCode;

                fn try_from(trillium_status: crate::Status) -> Result<Self, Self::Error> {
                    http::StatusCode::from_u16(trillium_status as u16)
                }
            }

            impl PartialEq<crate::Status> for http::StatusCode {
                fn eq(&self, other: &crate::Status) -> bool {
                    self.as_u16() == (*other as u16)
                }
            }

            impl PartialEq<http::StatusCode> for crate::Status {
                fn eq(&self, other: &http::StatusCode) -> bool {
                    (*self as u16) == other.as_u16()
                }
            }
        }

        mod header_name {
            use $http as http;

            impl From<http::HeaderName> for crate::HeaderName<'static> {
                fn from(http_header_name: http::header::HeaderName) -> Self {
                    http_header_name.as_str().to_owned().into()
                }
            }

            impl TryFrom<crate::HeaderName<'_>> for http::HeaderName {
                type Error = http::header::InvalidHeaderName;

                fn try_from(trillium_header_name: crate::HeaderName) -> Result<Self, Self::Error> {
                    http::header::HeaderName::from_bytes(trillium_header_name.as_ref().as_bytes())
                }
            }

            impl PartialEq<http::HeaderName> for crate::HeaderName<'_> {
                fn eq(&self, other: &http::HeaderName) -> bool {
                    AsRef::<str>::as_ref(self) == AsRef::<str>::as_ref(other)
                }
            }

            impl PartialEq<crate::HeaderName<'_>> for http::HeaderName {
                fn eq(&self, other: &crate::HeaderName<'_>) -> bool {
                    AsRef::<str>::as_ref(other) == AsRef::<str>::as_ref(self)
                }
            }
        }

        mod headers {
            use thiserror::Error;
            use $http as http;

            impl From<http::HeaderMap> for crate::Headers {
                fn from(http_header_map: http::HeaderMap) -> Self {
                    let mut trillium_headers = crate::Headers::default();
                    let mut current_header_name = None;
                    for (http_header_name, http_header_value) in http_header_map {
                        current_header_name = http_header_name.or(current_header_name);

                        if let Some(http_header_name) = current_header_name.as_ref() {
                            trillium_headers.append(
                                http_header_name.clone(),
                                crate::HeaderValue::from(http_header_value),
                            );
                        }
                    }

                    trillium_headers
                }
            }

            /// An error enum that represents failures to convert
            /// [`Headers`][crate::Headers] into a [`http::HeaderMap`]
            #[derive(Debug, Error)]
            pub enum HeaderConversionError {
                /// A header that was valid in trillium was not valid as a
                /// [`http::header::HeaderName`].
                ///
                /// Please consider filing an issue with trillium, as there are
                /// not currently known examples of this.
                #[error(transparent)]
                InvalidHeaderName(#[from] http::header::InvalidHeaderName),

                /// A header that was valid in trillium was not valid as a
                /// [`http::header::HeaderValue`].
                ///
                /// Please consider filing an issue with trillium, as there are
                /// not currently known examples of this.
                #[error(transparent)]
                InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
            }

            impl TryFrom<crate::Headers> for http::HeaderMap {
                type Error = HeaderConversionError;

                fn try_from(trillium_headers: crate::Headers) -> Result<Self, Self::Error> {
                    let mut http_header_map = http::HeaderMap::default();
                    for (trillium_header_name, trillium_header_values) in trillium_headers {
                        let http_header_name =
                            http::header::HeaderName::try_from(trillium_header_name)?;
                        for trillium_header_value in trillium_header_values {
                            let http_header_value =
                                http::header::HeaderValue::try_from(trillium_header_value)?;
                            http_header_map.append(http_header_name.clone(), http_header_value);
                        }
                    }
                    Ok(http_header_map)
                }
            }
        }

        mod header_values {
            use $http as http;

            impl From<http::HeaderValue> for crate::HeaderValue {
                fn from(http_header_value: http::HeaderValue) -> Self {
                    http_header_value.as_bytes().to_owned().into()
                }
            }

            impl TryFrom<crate::HeaderValue> for http::HeaderValue {
                type Error = http::header::InvalidHeaderValue;

                fn try_from(
                    trillium_header_value: crate::HeaderValue,
                ) -> Result<Self, Self::Error> {
                    http::HeaderValue::from_bytes(trillium_header_value.as_ref())
                }
            }

            impl PartialEq<crate::HeaderValue> for http::HeaderValue {
                fn eq(&self, other: &crate::HeaderValue) -> bool {
                    other.as_ref() == self.as_ref()
                }
            }
        }

        #[cfg(test)]
        mod tests {
            use $http as http;

            #[test]
            fn versions() {
                assert_eq!(
                    http::Version::from(crate::Version::Http2),
                    http::Version::HTTP_2
                );
                assert_eq!(
                    crate::Version::try_from(http::Version::HTTP_09).unwrap(),
                    crate::Version::Http0_9
                );

                assert_eq!(crate::Version::Http1_1, http::Version::HTTP_11);
                assert_eq!(http::Version::HTTP_3, crate::Version::Http3);
            }

            #[test]
            fn method_round_trip() {
                assert_eq!(
                    http::Method::try_from(crate::Method::Delete).unwrap(),
                    http::Method::DELETE
                );
                assert_eq!(
                    crate::Method::try_from(http::Method::DELETE).unwrap(),
                    crate::Method::Delete
                );
                assert_eq!(
                    http::Method::try_from(crate::Method::BaselineControl).unwrap(),
                    "BASELINE-CONTROL"
                );

                assert_eq!(crate::Method::Post, http::Method::POST);
                assert_eq!(http::Method::PATCH, crate::Method::Patch);
            }

            #[test]
            fn status_round_trip() {
                assert_eq!(
                    http::StatusCode::try_from(crate::Status::Ok).unwrap(),
                    http::StatusCode::OK
                );
                assert_eq!(
                    crate::Status::try_from(http::StatusCode::OK).unwrap(),
                    crate::Status::Ok
                );

                assert_eq!(
                    http::StatusCode::try_from(crate::Status::ImATeapot).unwrap(),
                    418
                );

                assert_eq!(crate::Status::Ok, http::StatusCode::OK);
                assert_eq!(http::StatusCode::CONFLICT, crate::Status::Conflict);
            }

            #[test]
            fn headers_round_trip() {
                let trillium_headers: crate::Headers = [
                    (crate::KnownHeaderName::Host, "foo.bar".to_string()),
                    (crate::KnownHeaderName::Cookie, "cookie 1".to_string()),
                    (crate::KnownHeaderName::Cookie, "cookie 2".to_string()),
                ]
                .into_iter()
                .collect();

                let http_header_map: http::HeaderMap = trillium_headers.clone().try_into().unwrap();
                assert_eq!(http_header_map.get(http::header::HOST).unwrap(), "foo.bar");
                assert_eq!(
                    http_header_map
                        .get_all(http::header::COOKIE)
                        .into_iter()
                        .collect::<Vec<_>>(),
                    vec!["cookie 1", "cookie 2"]
                );

                let new_trillium_headers = crate::Headers::from(http_header_map);
                assert_eq!(&trillium_headers, &new_trillium_headers);
                assert_eq!(
                    new_trillium_headers
                        .get(crate::KnownHeaderName::Host)
                        .unwrap(),
                    "foo.bar"
                );
                assert_eq!(
                    new_trillium_headers
                        .get_values(crate::KnownHeaderName::Cookie)
                        .unwrap(),
                    &crate::HeaderValues::from(vec!["cookie 1", "cookie 2"])
                );
            }
        }
    };
}

#[cfg(feature = "http-compat-0")]
pub mod http_compat0 {
    //! Conversion between [`http0`] and `trillium-http` types.
    crate::__generate_http_compat!(http0);
}

#[cfg(feature = "http-compat-1")]
pub mod http_compat1 {
    //! Conversion between [`http1`] and `trillium-http` types.
    crate::__generate_http_compat!(http1);
}