Skip to main content

alux_http_text/
route.rs

1//! Records the selectors a route composes, and renders the surface they describe.
2
3use crate::{TextEndpoint, TextHandlerImpl};
4use alux_http::{HttpMethod, HttpSelectorAlg, RouteAlg, RoutePath, SelectorAlg, describe_path};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7enum TextSelectorPart {
8    Method(HttpMethod),
9    Path(RoutePath),
10    Prefix(RoutePath),
11}
12
13/// Carries interpreted selector data.
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct TextSelector {
16    parts: Vec<TextSelectorPart>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20struct TextRouteEntry {
21    selector: TextSelector,
22    endpoint: TextEndpoint,
23}
24
25/// Carries an interpreted route composition.
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
27pub struct TextRoute {
28    entries: Vec<TextRouteEntry>,
29}
30
31impl TextRoute {
32    /// Returns each interpreted selector as `METHOD /path`, in declaration order.
33    pub fn labels(&self) -> Vec<String> {
34        self.entries.iter().map(|entry| entry.selector.label()).collect()
35    }
36
37    /// Returns each interpreted route path, in declaration order.
38    pub fn paths(&self) -> Vec<String> {
39        self.entries.iter().map(|entry| entry.selector.path()).collect()
40    }
41
42    /// Renders each route as a Markdown description.
43    pub fn lines(&self) -> Vec<String> {
44        self.entries
45            .iter()
46            .map(|entry| {
47                format!(
48                    concat!(
49                        "### {}\n",
50                        "- `handler`: `{}`\n",
51                        "- `inputs`: `{}`\n",
52                        "- `args`: `{}`\n",
53                        "- `result`: `{}`\n",
54                        "- `transform`: `{}`\n",
55                        "- `output`: `{}`",
56                    ),
57                    entry.selector.label(),
58                    entry.endpoint.handler,
59                    entry.endpoint.inputs,
60                    entry.endpoint.args,
61                    entry.endpoint.result,
62                    entry.endpoint.transform,
63                    entry.endpoint.output,
64                )
65            })
66            .collect()
67    }
68}
69
70impl TextSelector {
71    /// Returns the composed absolute path this selector matches.
72    pub fn path(&self) -> String {
73        describe_path(self.parts.iter().filter_map(|part| match part {
74            TextSelectorPart::Path(path) | TextSelectorPart::Prefix(path) => Some(path),
75            TextSelectorPart::Method(_) => None,
76        }))
77    }
78
79    /// Returns the selected method and path, using `*` when no method is selected.
80    pub fn label(&self) -> String {
81        let method = self
82            .parts
83            .iter()
84            .rev()
85            .find_map(|part| match part {
86                TextSelectorPart::Method(method) => Some(method.label()),
87                TextSelectorPart::Path(_) | TextSelectorPart::Prefix(_) => None,
88            })
89            .unwrap_or("*");
90
91        format!("{method} {}", self.path())
92    }
93}
94
95impl SelectorAlg for TextHandlerImpl {
96    type Selector = TextSelector;
97
98    fn identity(&self) -> TextSelector {
99        TextSelector::default()
100    }
101
102    fn compose(&self, mut first: TextSelector, second: TextSelector) -> TextSelector {
103        first.parts.extend(second.parts);
104        first
105    }
106}
107
108impl RouteAlg for TextHandlerImpl {
109    type Route = TextRoute;
110    type Selector = TextSelector;
111    type Endpoint = TextEndpoint;
112
113    fn initial(&self) -> TextRoute {
114        TextRoute::default()
115    }
116
117    fn coproduct(&self, mut left: TextRoute, right: TextRoute) -> TextRoute {
118        left.entries.extend(right.entries);
119        left
120    }
121
122    fn precompose(&self, selector: TextSelector, mut route: TextRoute) -> TextRoute {
123        for entry in &mut route.entries {
124            entry.selector = self.compose(selector.clone(), core::mem::take(&mut entry.selector));
125        }
126        route
127    }
128
129    fn lift(&self, endpoint: TextEndpoint) -> TextRoute {
130        TextRoute { entries: vec![TextRouteEntry { selector: self.identity(), endpoint }] }
131    }
132}
133
134impl HttpSelectorAlg for TextHandlerImpl {
135    type Selector = TextSelector;
136
137    fn http_method(&self, method: HttpMethod) -> TextSelector {
138        TextSelector { parts: vec![TextSelectorPart::Method(method)] }
139    }
140
141    fn http_path(&self, path: &RoutePath) -> TextSelector {
142        TextSelector { parts: vec![TextSelectorPart::Path(path.clone())] }
143    }
144
145    fn http_prefix(&self, prefix: &RoutePath) -> TextSelector {
146        TextSelector { parts: vec![TextSelectorPart::Prefix(prefix.clone())] }
147    }
148}