Skip to main content

prosa_hyper/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/COPYRIGHT"))]
2//!
3//! [![github]](https://github.com/worldline/ProSA-Hyper) [![crates-io]](https://crates.io/crates/prosa-hyper) [![docs-rs]](crate)
4//!
5//! [github]: https://img.shields.io/badge/github-46beaa?style=for-the-badge&labelColor=555555&logo=github
6//! [crates-io]: https://img.shields.io/badge/crates.io-ffeb78?style=for-the-badge&labelColor=555555&logo=rust
7//! [docs-rs]: https://img.shields.io/badge/docs.rs-41b4d2?style=for-the-badge&labelColor=555555&logo=docs.rs
8//!
9//! ProSA Hyper processor to handle HTTP client and server
10
11#![warn(missing_docs)]
12
13use std::{convert::Infallible, io};
14
15use bytes::Bytes;
16use http_body_util::combinators::BoxBody;
17use hyper::{Response, Version};
18use prosa::core::{
19    error::{BusError, ProcError},
20    msg::{ErrorMsg, InternalMsg},
21};
22use thiserror::Error;
23
24const H2: &[u8] = b"h2";
25
26/// Product version header value used for `Server` or `User-Agent` header in HTTP requests and responses
27#[cfg(target_family = "unix")]
28pub const PRODUCT_VERSION_HEADER: &str = concat!(
29    env!("CARGO_PKG_NAME"),
30    "/",
31    env!("CARGO_PKG_VERSION"),
32    " (Unix)"
33);
34#[cfg(target_family = "windows")]
35pub const PRODUCT_VERSION_HEADER: &str = concat!(
36    env!("CARGO_PKG_NAME"),
37    "/",
38    env!("CARGO_PKG_VERSION"),
39    " (Windows)"
40);
41#[cfg(all(not(target_family = "unix"), not(target_family = "windows")))]
42pub const PRODUCT_VERSION_HEADER: &str =
43    concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
44
45/// Global Hyper processor error
46#[derive(Debug, Error)]
47pub enum HyperProcError {
48    /// IO Error
49    #[error("Hyper IO error: {0}")]
50    Io(#[from] io::Error),
51    /// Hyper Error
52    #[error("Hyper error[{1}]: `{0}`")]
53    Hyper(hyper::Error, String),
54    /// Internal bus error
55    #[error("Internal bus error: {0}")]
56    InternalBus(#[from] BusError),
57    /// Other Error
58    #[error("Other error: {0}")]
59    Other(String),
60}
61
62impl ProcError for HyperProcError {
63    fn recoverable(&self) -> bool {
64        match self {
65            HyperProcError::Io(error) => error.recoverable(),
66            HyperProcError::Hyper(..) => true,
67            HyperProcError::InternalBus(b) => b.recoverable(),
68            HyperProcError::Other(_) => true,
69        }
70    }
71}
72
73impl<M> From<tokio::sync::mpsc::error::SendError<InternalMsg<M>>> for HyperProcError
74where
75    M: 'static
76        + std::marker::Send
77        + std::marker::Sync
78        + std::marker::Sized
79        + std::clone::Clone
80        + std::fmt::Debug
81        + prosa::core::msg::Tvf
82        + std::default::Default,
83{
84    fn from(err: tokio::sync::mpsc::error::SendError<InternalMsg<M>>) -> Self {
85        HyperProcError::InternalBus(BusError::InternalQueue(format!(
86            "Failed to send message: {}",
87            err
88        )))
89    }
90}
91
92/// Error related to an HTTP message or Hyper error
93#[derive(Debug, Error)]
94pub enum HttpError {
95    /// Hyper Error
96    #[error("Hyper error: `{0}`")]
97    Hyper(#[from] hyper::Error),
98    /// HTTP Error
99    #[error("HTTP error: `{0}`")]
100    Http(#[from] http::Error),
101}
102
103/// Method to get a string version of the Hyper Version object
104fn hyper_version_str(version: Version) -> &'static str {
105    match version {
106        hyper::Version::HTTP_11 => "HTTP/1.1",
107        hyper::Version::HTTP_2 => "HTTP/2",
108        hyper::Version::HTTP_3 => "HTTP/3",
109        _ => "Unknown",
110    }
111}
112
113/// Type alias for the closure that processes an internal service response or error.
114///
115/// Receives `Ok(M)` on success or `Err(ErrorMsg<M>)` on service error.
116/// Returns an HTTP response or an HTTP error.
117pub type SrvRespHandler<A, M> = Box<
118    dyn FnOnce(
119            &A,
120            Result<M, ErrorMsg<M>>,
121        ) -> Result<Response<BoxBody<Bytes, Infallible>>, HttpError>
122        + Send,
123>;
124
125/// Enum to define all type of response to request it can be made
126pub enum HyperResp<A, M>
127where
128    A: server::adaptor::HyperServerAdaptor<M>,
129    M: 'static
130        + std::marker::Send
131        + std::marker::Sync
132        + std::marker::Sized
133        + std::clone::Clone
134        + std::fmt::Debug
135        + prosa::core::msg::Tvf
136        + std::default::Default,
137{
138    /// Make an internal service request and process the response with the provided handler
139    SrvReq(String, M, SrvRespHandler<A, M>),
140    /// Make a direct HTTP response
141    HttpResp(Response<BoxBody<Bytes, Infallible>>),
142    /// Response with an HTTP error
143    HttpErr(HttpError),
144}
145
146impl<A, M> std::fmt::Debug for HyperResp<A, M>
147where
148    A: server::adaptor::HyperServerAdaptor<M>,
149    M: 'static
150        + std::marker::Send
151        + std::marker::Sync
152        + std::marker::Sized
153        + std::clone::Clone
154        + std::fmt::Debug
155        + prosa::core::msg::Tvf
156        + std::default::Default,
157{
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        match self {
160            HyperResp::SrvReq(name, msg, _) => f
161                .debug_tuple("SrvReq")
162                .field(name)
163                .field(msg)
164                .field(&"<handler>")
165                .finish(),
166            HyperResp::HttpResp(resp) => f.debug_tuple("HttpResp").field(resp).finish(),
167            HyperResp::HttpErr(err) => f.debug_tuple("HttpErr").field(err).finish(),
168        }
169    }
170}
171
172impl<A, M> From<HttpError> for HyperResp<A, M>
173where
174    A: server::adaptor::HyperServerAdaptor<M>,
175    M: 'static
176        + std::marker::Send
177        + std::marker::Sync
178        + std::marker::Sized
179        + std::clone::Clone
180        + std::fmt::Debug
181        + prosa::core::msg::Tvf
182        + std::default::Default,
183{
184    fn from(err: HttpError) -> Self {
185        Self::HttpErr(err)
186    }
187}
188
189impl<A, M> From<hyper::Error> for HyperResp<A, M>
190where
191    A: server::adaptor::HyperServerAdaptor<M>,
192    M: 'static
193        + std::marker::Send
194        + std::marker::Sync
195        + std::marker::Sized
196        + std::clone::Clone
197        + std::fmt::Debug
198        + prosa::core::msg::Tvf
199        + std::default::Default,
200{
201    fn from(err: hyper::Error) -> Self {
202        Self::HttpErr(HttpError::Hyper(err))
203    }
204}
205
206impl<A, M> From<http::Error> for HyperResp<A, M>
207where
208    A: server::adaptor::HyperServerAdaptor<M>,
209    M: 'static
210        + std::marker::Send
211        + std::marker::Sync
212        + std::marker::Sized
213        + std::clone::Clone
214        + std::fmt::Debug
215        + prosa::core::msg::Tvf
216        + std::default::Default,
217{
218    fn from(err: http::Error) -> Self {
219        Self::HttpErr(HttpError::Http(err))
220    }
221}
222
223impl<A, M> From<Result<Response<BoxBody<Bytes, Infallible>>, http::Error>> for HyperResp<A, M>
224where
225    A: server::adaptor::HyperServerAdaptor<M>,
226    M: 'static
227        + std::marker::Send
228        + std::marker::Sync
229        + std::marker::Sized
230        + std::clone::Clone
231        + std::fmt::Debug
232        + prosa::core::msg::Tvf
233        + std::default::Default,
234{
235    fn from(res: Result<Response<BoxBody<Bytes, Infallible>>, http::Error>) -> Self {
236        match res {
237            Ok(response) => Self::HttpResp(response),
238            Err(err) => Self::HttpErr(HttpError::Http(err)),
239        }
240    }
241}
242
243#[cfg(feature = "server")]
244pub mod server;
245
246#[cfg(feature = "client")]
247pub mod client;
248
249#[cfg(any(feature = "server", feature = "client"))]
250#[cfg(test)]
251pub mod tests;