Skip to main content

alux_http_warp/
route.rs

1//! Composes selectors, and builds the warp filter they state.
2
3use crate::WarpRequest;
4use alux_http::{HttpMethod, HttpSelectorAlg, PathSegment, RouteAlg, RoutePath, SelectorAlg, describe_path};
5use bytes::Bytes;
6use core::future::Future;
7use core::pin::Pin;
8use std::sync::Arc;
9use warp::Filter;
10use warp::filters::BoxedFilter;
11use warp::http::Method;
12use warp::reply::Response;
13
14/// The answer one reached endpoint produces.
15pub type Answer = Pin<Box<dyn Future<Output = Response> + Send>>;
16
17/// Interprets a request method as the one warp matches on.
18fn warp_method(method: HttpMethod) -> Method {
19    match method {
20        HttpMethod::Get => Method::GET,
21        HttpMethod::Post => Method::POST,
22        HttpMethod::Put => Method::PUT,
23        HttpMethod::Patch => Method::PATCH,
24        HttpMethod::Delete => Method::DELETE,
25        HttpMethod::Head => Method::HEAD,
26        HttpMethod::Options => Method::OPTIONS,
27        HttpMethod::Trace => Method::TRACE,
28        HttpMethod::Connect => Method::CONNECT,
29    }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33enum WarpSelectorPart {
34    Method(HttpMethod),
35    Path(RoutePath),
36    Prefix(RoutePath),
37}
38
39/// Carries route-selection meaning before it becomes the filters warp matches with.
40#[derive(Debug, Clone, Default, PartialEq, Eq)]
41pub struct WarpSelector {
42    parts: Vec<WarpSelectorPart>,
43}
44
45impl WarpSelector {
46    /// Returns the composed absolute path this selector matches.
47    ///
48    /// warp matches by composing filters rather than by parsing a path, so a spelling is needed only
49    /// to describe the surface.
50    pub fn path(&self) -> String {
51        describe_path(self.paths())
52    }
53
54    /// Returns the selected method and path, using `*` when no method is selected.
55    pub fn label(&self) -> String {
56        let method = self.method().map_or("*", HttpMethod::label);
57        format!("{method} {}", self.path())
58    }
59
60    fn paths(&self) -> impl Iterator<Item = &RoutePath> {
61        self.parts.iter().filter_map(|part| match part {
62            WarpSelectorPart::Path(path) | WarpSelectorPart::Prefix(path) => Some(path),
63            WarpSelectorPart::Method(_) => None,
64        })
65    }
66
67    fn segments(&self) -> Vec<PathSegment> {
68        self.paths().flat_map(|path| path.segments().iter().cloned()).collect()
69    }
70
71    fn method(&self) -> Option<HttpMethod> {
72        self.parts.iter().rev().find_map(|part| match part {
73            WarpSelectorPart::Method(method) => Some(*method),
74            WarpSelectorPart::Path(_) | WarpSelectorPart::Prefix(_) => None,
75        })
76    }
77
78    /// Builds the filter matching this selector, gathering what its path binds.
79    fn filter(&self) -> BoxedFilter<(Vec<String>,)> {
80        let segments = self.segments();
81        let mut bound = warp::any().map(Vec::<String>::new).boxed();
82        let mut open = false;
83        for segment in &segments {
84            bound = match segment {
85                PathSegment::Literal(value) => bound.and(warp::path(value.clone())).boxed(),
86                PathSegment::Param(_) => bound
87                    .and(warp::path::param::<String>())
88                    .map(|mut bound: Vec<String>, value: String| {
89                        bound.push(value);
90                        bound
91                    })
92                    .boxed(),
93                PathSegment::Tail(_) => {
94                    open = true;
95                    bound
96                        .and(warp::path::tail())
97                        .map(|mut bound: Vec<String>, tail: warp::path::Tail| {
98                            bound.push(tail.as_str().to_owned());
99                            bound
100                        })
101                        .boxed()
102                }
103            };
104        }
105
106        // A tail already matches everything that is left, so only a closed path states an end.
107        let bound = if open { bound } else { bound.and(warp::path::end()).boxed() };
108
109        match self.method() {
110            Some(method) => {
111                let expected = warp_method(method);
112                bound
113                    .and(warp::method())
114                    .and_then(move |bound: Vec<String>, method: Method| {
115                        let expected = expected.clone();
116                        async move { if method == expected { Ok(bound) } else { Err(warp::reject()) } }
117                    })
118                    .boxed()
119            }
120            None => bound,
121        }
122    }
123}
124
125/// Erases what one endpoint does with a request warp's filters matched.
126#[derive(Clone)]
127pub struct WarpEndpoint(Arc<dyn Fn(WarpRequest) -> Answer + Send + Sync>);
128
129impl WarpEndpoint {
130    /// States an endpoint as what it answers, given what the filters gathered.
131    pub fn new<Reach>(reach: Reach) -> Self
132    where
133        Reach: Fn(WarpRequest) -> Answer + Send + Sync + 'static,
134    {
135        Self(Arc::new(reach))
136    }
137}
138
139#[derive(Clone)]
140struct WarpRouteEntry {
141    selector: WarpSelector,
142    endpoint: WarpEndpoint,
143}
144
145/// Carries a composable collection of warp endpoints.
146#[derive(Clone, Default)]
147pub struct WarpRoute {
148    entries: Vec<WarpRouteEntry>,
149}
150
151impl WarpRoute {
152    /// Returns each composed selector as `METHOD /path`, in declaration order.
153    pub fn labels(&self) -> Vec<String> {
154        self.entries.iter().map(|entry| entry.selector.label()).collect()
155    }
156
157    /// Returns each composed route path, in declaration order.
158    pub fn paths(&self) -> Vec<String> {
159        self.entries.iter().map(|entry| entry.selector.path()).collect()
160    }
161
162    /// Materializes the composed meaning as a native warp filter.
163    ///
164    /// A coproduct of routes is `or`, which is the whole of what composing a surface means here.
165    pub fn into_warp(self) -> BoxedFilter<(Response,)> {
166        let nothing = warp::any().and_then(|| async { Err::<Response, _>(warp::reject::not_found()) }).boxed();
167
168        self.entries.into_iter().fold(nothing, |answered, entry| {
169            let endpoint = entry.endpoint.clone();
170            let reached = entry
171                .selector
172                .filter()
173                .and(warp::query::raw().or(warp::any().map(String::new)).unify())
174                .and(warp::header::headers_cloned())
175                .and(warp::body::bytes())
176                .and_then(move |captures, query, headers, body: Bytes| {
177                    let endpoint = endpoint.clone();
178                    async move {
179                        let request = WarpRequest { captures, query, headers, body: body.to_vec() };
180
181                        Ok::<_, warp::Rejection>((endpoint.0)(request).await)
182                    }
183                })
184                .boxed();
185
186            answered.or(reached).unify().boxed()
187        })
188    }
189}
190
191/// Interprets categorical route composition as native warp filter composition.
192#[derive(Debug, Default)]
193pub struct WarpRouteImpl;
194
195impl SelectorAlg for WarpRouteImpl {
196    type Selector = WarpSelector;
197
198    fn identity(&self) -> WarpSelector {
199        WarpSelector::default()
200    }
201
202    fn compose(&self, mut first: WarpSelector, second: WarpSelector) -> WarpSelector {
203        first.parts.extend(second.parts);
204        first
205    }
206}
207
208impl RouteAlg for WarpRouteImpl {
209    type Route = WarpRoute;
210    type Selector = WarpSelector;
211    type Endpoint = WarpEndpoint;
212
213    fn initial(&self) -> WarpRoute {
214        WarpRoute::default()
215    }
216
217    fn coproduct(&self, mut left: WarpRoute, right: WarpRoute) -> WarpRoute {
218        left.entries.extend(right.entries);
219        left
220    }
221
222    fn precompose(&self, selector: WarpSelector, mut route: WarpRoute) -> WarpRoute {
223        for entry in &mut route.entries {
224            entry.selector = self.compose(selector.clone(), core::mem::take(&mut entry.selector));
225        }
226        route
227    }
228
229    fn lift(&self, endpoint: WarpEndpoint) -> WarpRoute {
230        WarpRoute { entries: vec![WarpRouteEntry { selector: self.identity(), endpoint }] }
231    }
232}
233
234impl HttpSelectorAlg for WarpRouteImpl {
235    type Selector = WarpSelector;
236
237    fn http_method(&self, method: HttpMethod) -> WarpSelector {
238        WarpSelector { parts: vec![WarpSelectorPart::Method(method)] }
239    }
240
241    fn http_path(&self, path: &RoutePath) -> WarpSelector {
242        WarpSelector { parts: vec![WarpSelectorPart::Path(path.clone())] }
243    }
244
245    fn http_prefix(&self, prefix: &RoutePath) -> WarpSelector {
246        WarpSelector { parts: vec![WarpSelectorPart::Prefix(prefix.clone())] }
247    }
248}