Skip to main content

prosa_hyper/server/
adaptor.rs

1//! Hyper server adaptor definition
2use std::convert::Infallible;
3
4use bytes::Bytes;
5use http::response;
6use http_body_util::{Empty, Full, combinators::BoxBody};
7use hyper::{Request, Response, StatusCode};
8use prosa::core::{adaptor::Adaptor, error::ProcError, msg::ErrorMsg, proc::ProcBusParam as _};
9
10use crate::{HttpError, HyperResp, PRODUCT_VERSION_HEADER};
11
12use super::proc::HyperServerProc;
13
14#[cfg_attr(doc, aquamarine::aquamarine)]
15/// Trait to define the Hyper server adaptor structure
16///
17/// ```mermaid
18/// graph LR
19///     IN[Input HTTP server]
20///     ProSA[ProSA Hyper Procesor]
21///
22///     IN-- HTTP request (process_server_request) -->ProSA
23///     ProSA-- HTTP response (process_server_response) -->IN
24/// ```
25pub trait HyperServerAdaptor<M>
26where
27    M: 'static
28        + std::marker::Send
29        + std::marker::Sync
30        + std::marker::Sized
31        + std::clone::Clone
32        + std::fmt::Debug
33        + prosa::core::msg::Tvf
34        + std::default::Default,
35{
36    /// Create a new adaptor
37    fn new(proc: &HyperServerProc<M>) -> Result<Self, Box<dyn ProcError + Send + Sync>>
38    where
39        Self: Sized;
40
41    /// Initiate a response builder with specific status code and headers from adaptor.
42    /// The response with its header generated will be used by the processor in case of error
43    fn response_builder<T>(&self, status_code: T) -> response::Builder
44    where
45        T: TryInto<StatusCode>,
46        <T as TryInto<StatusCode>>::Error: Into<http::Error>,
47    {
48        Response::builder()
49            .status(status_code)
50            .header(hyper::header::SERVER, PRODUCT_VERSION_HEADER)
51    }
52
53    /// Method to process input HTTP requests. Received by the ProSA through Hyper
54    fn process_http_request(
55        &self,
56        req: Request<hyper::body::Incoming>,
57    ) -> impl std::future::Future<Output = HyperResp<Self, M>> + Send
58    where
59        Self: Sized + Send + Sync + 'static,
60        M: 'static
61            + Send
62            + Sync
63            + Sized
64            + Clone
65            + std::fmt::Debug
66            + prosa::core::msg::Tvf
67            + Default;
68}
69
70/// Convert a service error message into a default HTTP response.
71///
72/// This provides a default mapping from [`prosa::core::service::ServiceError`] variants to HTTP status codes:
73/// - `NoError` -> `202 Accepted`
74/// - `UnableToReachService` -> `503 Service Unavailable`
75/// - `Timeout` -> `504 Gateway Timeout`
76/// - `ProtocolError` -> `502 Bad Gateway`
77///
78/// The `response_builder` parameter allows customizing the response headers (e.g., adding a `Server` header).
79pub fn default_srv_error_response<M, F>(
80    err: &ErrorMsg<M>,
81    response_builder: F,
82) -> Result<Response<BoxBody<Bytes, Infallible>>, HttpError>
83where
84    M: 'static
85        + std::marker::Send
86        + std::marker::Sync
87        + std::marker::Sized
88        + std::clone::Clone
89        + std::fmt::Debug
90        + prosa::core::msg::Tvf
91        + std::default::Default,
92    F: Fn(StatusCode) -> response::Builder,
93{
94    match err.get_err() {
95        prosa::core::service::ServiceError::NoError(_) => response_builder(StatusCode::ACCEPTED)
96            .body(BoxBody::new(Empty::<Bytes>::new()))
97            .map_err(|e| e.into()),
98        prosa::core::service::ServiceError::UnableToReachService(_) => {
99            response_builder(StatusCode::SERVICE_UNAVAILABLE)
100                .body(BoxBody::new(Full::new(Bytes::from("Can't reach service"))))
101                .map_err(|e| e.into())
102        }
103        prosa::core::service::ServiceError::Timeout(_, _) => {
104            response_builder(StatusCode::GATEWAY_TIMEOUT)
105                .body(BoxBody::new(Empty::<Bytes>::new()))
106                .map_err(|e| e.into())
107        }
108        prosa::core::service::ServiceError::ProtocolError(_) => {
109            response_builder(StatusCode::BAD_GATEWAY)
110                .body(BoxBody::new(Empty::<Bytes>::new()))
111                .map_err(|e| e.into())
112        }
113    }
114}
115
116/// Hello adaptor for the Hyper server processor. Use to respond to a request with a simple hello message
117#[derive(Debug, Adaptor, Clone)]
118pub struct HelloHyperServerAdaptor {
119    hello_msg: String,
120}
121
122impl<M> HyperServerAdaptor<M> for HelloHyperServerAdaptor
123where
124    M: 'static
125        + std::marker::Send
126        + std::marker::Sync
127        + std::marker::Sized
128        + std::clone::Clone
129        + std::fmt::Debug
130        + prosa::core::msg::Tvf
131        + std::default::Default,
132{
133    fn new(proc: &HyperServerProc<M>) -> Result<Self, Box<dyn ProcError + Send + Sync>> {
134        Ok(HelloHyperServerAdaptor {
135            hello_msg: format!("Hello from {}", proc.name()),
136        })
137    }
138
139    async fn process_http_request(
140        &self,
141        _req: Request<hyper::body::Incoming>,
142    ) -> HyperResp<Self, M> {
143        Response::builder()
144            .status(200)
145            .header("Server", PRODUCT_VERSION_HEADER)
146            .body(BoxBody::new(Full::new(Bytes::from(self.hello_msg.clone()))))
147            .into()
148    }
149}