Skip to main content

alux_http_actix/
handler.rs

1//! Reaches one endpoint: it reads the arguments its roles state, applies the operation, and answers.
2
3use crate::input::{
4    ActixBodyInput, ActixCookieInput, ActixFormInput, ActixHeaderInput, ActixInputsAlg, ActixMultipartInput,
5    ActixPathInput, ActixQueryInput, ActixRequestInput,
6};
7use crate::route::{ActixEndpoint, ActixRoute, ActixRouteImpl, ActixSelector};
8use actix_web::web::Payload;
9use actix_web::{HttpRequest, HttpResponse, Route};
10use alux_ext::{ApplyAlg, HandlerContextAlg, OperationAlg};
11use alux_http::{
12    HandlerAlg, HandlerEndpointAlg, HttpInputAlg, HttpMethod, HttpSelectorAlg, OutputAlg, OutputKindAlg, RouteAlg,
13    RoutePath, SelectorAlg,
14};
15use std::sync::Arc;
16
17/// Interprets typed HTTP programs as executable actix-web routes.
18///
19/// Shared state reaches a handler as the semantic context the program already threads, not as
20/// `actix_web::Data`, because a framework composing domain code is the inversion this design exists
21/// to avoid.
22pub struct ActixHandlerImpl<Context> {
23    context: Arc<Context>,
24}
25
26impl<Context> ActixHandlerImpl<Context> {
27    /// Creates an interpreter owning a newly shared context.
28    pub fn new(context: Context) -> Self {
29        Self { context: Arc::new(context) }
30    }
31
32    /// Creates an interpreter from an existing shared context.
33    pub fn from_shared(context: Arc<Context>) -> Self {
34        Self { context }
35    }
36}
37
38impl<Context> HandlerContextAlg<Context> for ActixHandlerImpl<Context>
39where
40    Context: Send + Sync + 'static,
41{
42    type Handle = Arc<Context>;
43}
44
45impl<Context> HandlerAlg for ActixHandlerImpl<Context> {
46    type Endpoint = ActixEndpoint;
47}
48
49impl<Context> HttpInputAlg for ActixHandlerImpl<Context> {
50    type Path<I> = ActixPathInput<I>;
51    type Query<I> = ActixQueryInput<I>;
52    type Body<I> = ActixBodyInput<I>;
53    type Form<I> = ActixFormInput<I>;
54    type Multipart<I> = ActixMultipartInput<I>;
55    // actix-web reads an unread body, a header, an authentication value, and an endpoint context
56    // through its own request extractor.
57    type RawBody<I> = ActixRequestInput<I>;
58    type Header<I> = ActixHeaderInput<I>;
59    type Cookie<I> = ActixCookieInput<I>;
60    type Auth<I> = ActixHeaderInput<I>;
61    type Context<I> = ActixRequestInput<I>;
62}
63
64impl<Context, Inputs, Args, Transform, Answering, Output>
65    HandlerEndpointAlg<Arc<Context>, Inputs, Args, Transform, Output> for ActixHandlerImpl<Context>
66where
67    Context: Send + Sync + 'static,
68    Inputs: ActixInputsAlg<Args> + 'static,
69    Args: 'static,
70    Output: 'static,
71    Transform: OutputKindAlg<Self, Output, Transform = Answering> + 'static,
72    Answering: OutputAlg<Output, Output = HttpResponse>,
73{
74    fn finish_handler<Handler>(&self, handler: Handler) -> <Self as HandlerAlg>::Endpoint
75    where
76        Handler: OperationAlg + ApplyAlg<Arc<Context>, Args, Output = Output> + Send + Sync + 'static,
77    {
78        let context = Arc::clone(&self.context);
79        let handler = Arc::new(handler);
80
81        // A route is made again for every worker, so what is held is the making of one.
82        ActixEndpoint::new(move || {
83            let context = Arc::clone(&context);
84            let handler = Arc::clone(&handler);
85
86            Route::new().to(move |request: HttpRequest, payload: Payload| {
87                let context = Arc::clone(&context);
88                let handler = Arc::clone(&handler);
89                async move {
90                    // What a handler is given is the whole payload; what reads it is the roles.
91                    let mut payload = payload.into_inner();
92                    let inputs = match Inputs::extract(&request, &mut payload).await {
93                        Ok(inputs) => inputs,
94                        Err(rejection) => return rejection.error_response(),
95                    };
96                    let output = handler.apply(context, inputs).await;
97
98                    Answering::output(output)
99                }
100            })
101        })
102    }
103}
104
105impl<Context> SelectorAlg for ActixHandlerImpl<Context> {
106    type Selector = ActixSelector;
107
108    fn identity(&self) -> Self::Selector {
109        SelectorAlg::identity(&ActixRouteImpl)
110    }
111
112    fn compose(&self, first: Self::Selector, second: Self::Selector) -> Self::Selector {
113        SelectorAlg::compose(&ActixRouteImpl, first, second)
114    }
115}
116
117impl<Context> RouteAlg for ActixHandlerImpl<Context> {
118    type Route = ActixRoute;
119    type Selector = ActixSelector;
120    type Endpoint = ActixEndpoint;
121
122    fn initial(&self) -> Self::Route {
123        RouteAlg::initial(&ActixRouteImpl)
124    }
125
126    fn coproduct(&self, left: Self::Route, right: Self::Route) -> Self::Route {
127        RouteAlg::coproduct(&ActixRouteImpl, left, right)
128    }
129
130    fn precompose(&self, selector: Self::Selector, route: Self::Route) -> Self::Route {
131        RouteAlg::precompose(&ActixRouteImpl, selector, route)
132    }
133
134    fn lift(&self, endpoint: Self::Endpoint) -> Self::Route {
135        RouteAlg::lift(&ActixRouteImpl, endpoint)
136    }
137}
138
139impl<Context> HttpSelectorAlg for ActixHandlerImpl<Context> {
140    type Selector = ActixSelector;
141
142    fn http_method(&self, method: HttpMethod) -> Self::Selector {
143        HttpSelectorAlg::http_method(&ActixRouteImpl, method)
144    }
145
146    fn http_path(&self, path: &RoutePath) -> Self::Selector {
147        HttpSelectorAlg::http_path(&ActixRouteImpl, path)
148    }
149
150    fn http_prefix(&self, prefix: &RoutePath) -> Self::Selector {
151        HttpSelectorAlg::http_prefix(&ActixRouteImpl, prefix)
152    }
153}