Skip to main content

alux_http_direct/
route.rs

1//! Routes a request against the paths a program states, with no router but the program's own.
2
3use crate::{DirectError, DirectRequest, DirectResponse};
4use alux_http::{HttpMethod, HttpSelectorAlg, PathSegment, RouteAlg, RoutePath, SelectorAlg, describe_path};
5use core::future::Future;
6use core::pin::Pin;
7use std::sync::Arc;
8
9/// The answer one reached endpoint produces.
10pub type Answer = Pin<Box<dyn Future<Output = DirectResponse> + Send>>;
11
12type Reached = Arc<dyn Fn(DirectRequest, Vec<String>) -> Answer + Send + Sync>;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15enum DirectSelectorPart {
16    Method(HttpMethod),
17    Path(RoutePath),
18    Prefix(RoutePath),
19}
20
21/// Carries route-selection meaning, which for this interpretation is all the routing there is.
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct DirectSelector {
24    parts: Vec<DirectSelectorPart>,
25}
26
27impl DirectSelector {
28    /// Returns the composed absolute path this selector matches.
29    ///
30    /// There is no router to spell a path for, so the description is the only rendering needed.
31    pub fn path(&self) -> String {
32        describe_path(self.paths())
33    }
34
35    /// Returns the selected method and path, using `*` when no method is selected.
36    pub fn label(&self) -> String {
37        let method = self.method().map_or("*", HttpMethod::label);
38        format!("{method} {}", self.path())
39    }
40
41    fn paths(&self) -> impl Iterator<Item = &RoutePath> {
42        self.parts.iter().filter_map(|part| match part {
43            DirectSelectorPart::Path(path) | DirectSelectorPart::Prefix(path) => Some(path),
44            DirectSelectorPart::Method(_) => None,
45        })
46    }
47
48    fn segments(&self) -> Vec<&PathSegment> {
49        self.paths().flat_map(RoutePath::segments).collect()
50    }
51
52    fn method(&self) -> Option<HttpMethod> {
53        self.parts.iter().rev().find_map(|part| match part {
54            DirectSelectorPart::Method(method) => Some(*method),
55            DirectSelectorPart::Path(_) | DirectSelectorPart::Prefix(_) => None,
56        })
57    }
58
59    /// Returns what the path bound, or nothing where this selector does not match it.
60    ///
61    /// A literal matches itself, a parameter binds one segment, and a tail binds every segment that
62    /// is left. Anything the request states beyond what the selector reads is not a match.
63    fn captures(&self, path: &str) -> Option<Vec<String>> {
64        let mut asked = path.split('/').filter(|segment| !segment.is_empty()).peekable();
65        let mut captured = Vec::new();
66        for segment in self.segments() {
67            match segment {
68                PathSegment::Literal(value) => {
69                    if asked.next()? != value {
70                        return None;
71                    }
72                }
73                PathSegment::Param(_) => captured.push(asked.next()?.to_owned()),
74                PathSegment::Tail(_) => {
75                    captured.push(asked.by_ref().collect::<Vec<_>>().join("/"));
76
77                    return Some(captured);
78                }
79            }
80        }
81
82        asked.next().is_none().then_some(captured)
83    }
84}
85
86/// Erases what one endpoint does with a request it was reached by.
87#[derive(Clone)]
88pub struct DirectEndpoint(Reached);
89
90impl DirectEndpoint {
91    /// States an endpoint as what it answers, given a request and what its path bound.
92    pub fn new<Reach>(reach: Reach) -> Self
93    where
94        Reach: Fn(DirectRequest, Vec<String>) -> Answer + Send + Sync + 'static,
95    {
96        Self(Arc::new(reach))
97    }
98}
99
100#[derive(Clone)]
101struct DirectRouteEntry {
102    selector: DirectSelector,
103    endpoint: DirectEndpoint,
104}
105
106/// Carries a composable collection of endpoints and the selectors that reach them.
107#[derive(Clone, Default)]
108pub struct DirectRoute {
109    entries: Vec<DirectRouteEntry>,
110}
111
112impl DirectRoute {
113    /// Returns each composed selector as `METHOD /path`, in declaration order.
114    pub fn labels(&self) -> Vec<String> {
115        self.entries.iter().map(|entry| entry.selector.label()).collect()
116    }
117
118    /// Returns each composed route path, in declaration order.
119    pub fn paths(&self) -> Vec<String> {
120        self.entries.iter().map(|entry| entry.selector.path()).collect()
121    }
122
123    /// Answers a request by reaching the endpoint its method and path select.
124    ///
125    /// Declaration order is the order candidates are read in, so a surface answers with the first
126    /// endpoint that states it. A path that is declared under other methods is answered as such,
127    /// rather than as nothing being there.
128    pub async fn answer(&self, request: DirectRequest) -> DirectResponse {
129        let mut allowed = false;
130        for entry in &self.entries {
131            let Some(captures) = entry.selector.captures(request.path()) else { continue };
132            allowed = true;
133            if entry.selector.method().is_none_or(|method| Some(method) == request.method()) {
134                return (entry.endpoint.0)(request, captures).await;
135            }
136        }
137
138        let error = if allowed {
139            DirectError::method_not_allowed(request.path())
140        } else {
141            DirectError::not_found(request.path())
142        };
143
144        error.into()
145    }
146}
147
148/// Interprets categorical route composition as the only routing this interpretation needs.
149#[derive(Debug, Default)]
150pub struct DirectRouteImpl;
151
152impl SelectorAlg for DirectRouteImpl {
153    type Selector = DirectSelector;
154
155    fn identity(&self) -> DirectSelector {
156        DirectSelector::default()
157    }
158
159    fn compose(&self, mut first: DirectSelector, second: DirectSelector) -> DirectSelector {
160        first.parts.extend(second.parts);
161        first
162    }
163}
164
165impl RouteAlg for DirectRouteImpl {
166    type Route = DirectRoute;
167    type Selector = DirectSelector;
168    type Endpoint = DirectEndpoint;
169
170    fn initial(&self) -> DirectRoute {
171        DirectRoute::default()
172    }
173
174    fn coproduct(&self, mut left: DirectRoute, right: DirectRoute) -> DirectRoute {
175        left.entries.extend(right.entries);
176        left
177    }
178
179    fn precompose(&self, selector: DirectSelector, mut route: DirectRoute) -> DirectRoute {
180        for entry in &mut route.entries {
181            entry.selector = self.compose(selector.clone(), core::mem::take(&mut entry.selector));
182        }
183        route
184    }
185
186    fn lift(&self, endpoint: DirectEndpoint) -> DirectRoute {
187        DirectRoute { entries: vec![DirectRouteEntry { selector: self.identity(), endpoint }] }
188    }
189}
190
191impl HttpSelectorAlg for DirectRouteImpl {
192    type Selector = DirectSelector;
193
194    fn http_method(&self, method: HttpMethod) -> DirectSelector {
195        DirectSelector { parts: vec![DirectSelectorPart::Method(method)] }
196    }
197
198    fn http_path(&self, path: &RoutePath) -> DirectSelector {
199        DirectSelector { parts: vec![DirectSelectorPart::Path(path.clone())] }
200    }
201
202    fn http_prefix(&self, prefix: &RoutePath) -> DirectSelector {
203        DirectSelector { parts: vec![DirectSelectorPart::Prefix(prefix.clone())] }
204    }
205}