Skip to main content

rama_http_hyperium/
lib.rs

1//! Conversions between the native `rama-http-types` types and the hyperium
2//! [`http`] crate equivalents, for interop with the wider `http`/`tower`/`hyper`
3//! ecosystem.
4//!
5//! `rama-http-types` is `http`-free; this crate is the opt-in bridge, exposed as
6//! two **sealed**, **fallible** extension traits: [`TryIntoHyperiumHttp`]
7//! (rama → `http`) and [`TryIntoRamaHttp`] (`http` → rama). They are local
8//! traits, so they implement the foreign `http` types directly (no orphan rule,
9//! no conversion marker) and are callable as plain methods.
10//!
11//! The conversions are fallible by design: an invalid method/status, an
12//! unrepresentable URI, or an invalid header name/value is surfaced as an error
13//! (the direction's natural catch-all — [`http::Error`] one way,
14//! [`rama_http_types::Error`] the other) rather than silently coerced.
15//!
16//! Bodies are bridged (not copied) by [`HyperiumBody`] / [`RamaBody`], which
17//! wrap rama's [`Body`](rama_http_types::body::http_body::Body) ↔ the external
18//! [`http_body::Body`], converting only trailer frames.
19//!
20//! ```
21//! use rama_http_hyperium::{TryIntoHyperiumHttp, TryIntoRamaHttp};
22//!
23//! let hyper_req = http::Request::builder().uri("http://example.com/").body(()).unwrap();
24//! let rama_req: rama_http_types::Request<()> = hyper_req.try_into_rama_http().unwrap();
25//! assert_eq!(rama_req.uri().to_string(), "http://example.com/");
26//!
27//! let back: http::Request<()> = rama_req.try_into_hyperium_http().unwrap();
28//! assert_eq!(back.uri(), "http://example.com/");
29//! ```
30//!
31//! [`http`]: https://docs.rs/http
32
33#![doc(
34    html_favicon_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/rama_logo.svg"
35)]
36#![doc(
37    html_logo_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/rama_logo.svg"
38)]
39#![cfg_attr(docsrs, feature(doc_cfg))]
40
41use rama_core::extensions::Extension;
42use rama_http_types::{
43    HeaderMap, Method, Request, Response, StatusCode, Version, request, response,
44};
45use rama_net::uri::Uri;
46
47mod into_hyperium;
48mod into_rama;
49
50#[doc(inline)]
51pub use into_hyperium::{HyperiumBody, HyperiumBodyError, TryIntoHyperiumHttp};
52#[doc(inline)]
53pub use into_rama::{RamaBody, RamaBodyError, TryIntoRamaHttp};
54
55mod sealed {
56    //! Seals the conversion traits so the implemented set stays a closed set
57    //! defined by this crate, never extended downstream.
58    pub trait Sealed {}
59}
60
61macro_rules! impl_sealed {
62    ($($ty:ty),* $(,)?) => {
63        $( impl crate::sealed::Sealed for $ty {} )*
64    };
65}
66
67impl_sealed!(
68    Method,
69    StatusCode,
70    Version,
71    Uri,
72    HeaderMap,
73    request::Parts,
74    response::Parts,
75    http::Method,
76    http::StatusCode,
77    http::Version,
78    http::Uri,
79    http::HeaderMap,
80    http::request::Parts,
81    http::response::Parts,
82);
83impl<T> sealed::Sealed for Request<T> {}
84impl<T> sealed::Sealed for Response<T> {}
85impl<T> sealed::Sealed for http::Request<T> {}
86impl<T> sealed::Sealed for http::Response<T> {}
87impl sealed::Sealed for &http::HeaderMap {}
88
89/// Stash for the hyperium `http::Extensions` carried across a rama round-trip,
90/// so `rama → http → rama` preserves http-only extensions.
91#[derive(Clone, Debug, Extension)]
92pub(crate) struct HyperExtensions(pub(crate) http::Extensions);
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn request_round_trip() {
100        let hyper = http::Request::builder()
101            .method("POST")
102            .uri("https://example.com:8443/path?q=1")
103            .header("x-test", "v")
104            .body(())
105            .unwrap();
106        let rama: Request<()> = hyper.try_into_rama_http().unwrap();
107        assert_eq!(rama.method().as_str(), "POST");
108        assert_eq!(rama.uri().to_string(), "https://example.com:8443/path?q=1");
109        assert_eq!(rama.headers().get("x-test").unwrap().as_bytes(), b"v");
110
111        let back: http::Request<()> = rama.try_into_hyperium_http().unwrap();
112        assert_eq!(back.method(), "POST");
113        assert_eq!(back.uri(), "https://example.com:8443/path?q=1");
114        assert_eq!(back.headers().get("x-test").unwrap(), "v");
115    }
116
117    #[test]
118    fn request_round_trip_preserves_rama_extension() {
119        use rama_core::extensions::ExtensionsRef as _;
120
121        #[derive(Debug, Clone, PartialEq, Eq, Extension)]
122        struct Label(u32);
123
124        let rama = Request::builder().uri("http://x/").body(()).unwrap();
125        rama.extensions().insert(Label(7));
126
127        // rama → http stashes the rama extensions; http → rama restores them.
128        let hyper: http::Request<()> = rama.try_into_hyperium_http().unwrap();
129        let back: Request<()> = hyper.try_into_rama_http().unwrap();
130        assert_eq!(*back.extensions().get_ref::<Label>().unwrap(), Label(7));
131    }
132
133    #[test]
134    fn response_round_trip() {
135        let hyper = http::Response::builder()
136            .status(404)
137            .header("x-y", "z")
138            .body(())
139            .unwrap();
140        let rama: Response<()> = hyper.try_into_rama_http().unwrap();
141        assert_eq!(rama.status().as_u16(), 404);
142
143        let back: http::Response<()> = rama.try_into_hyperium_http().unwrap();
144        assert_eq!(back.status(), 404);
145        assert_eq!(back.headers().get("x-y").unwrap(), "z");
146    }
147
148    #[test]
149    fn http_uri_authority_form_preserves_host() {
150        // A CONNECT-style authority-form http::Uri must keep its host when
151        // converted to rama (it must not be misread as `scheme:path`).
152        let mut parts = http::uri::Parts::default();
153        parts.authority = Some("example.com:443".parse().unwrap());
154        let hyper_uri = http::Uri::from_parts(parts).unwrap();
155        assert!(hyper_uri.scheme().is_none() && hyper_uri.authority().is_some());
156
157        let rama_uri: Uri = hyper_uri.try_into_rama_http().unwrap();
158        assert_eq!(
159            rama_uri.host(),
160            Some(rama_net::address::Host::EXAMPLE_NAME.view())
161        );
162        assert_eq!(rama_uri.port_u16(), Some(443));
163    }
164}