Skip to main content

alux_http/
algebra.rs

1use alux_ext::{ApplyAlg, ext};
2
3/// Describes the capability to build typed handler endpoints.
4pub trait HandlerAlg {
5    /// The interpreter's homogeneous endpoint representation.
6    type Endpoint;
7}
8
9/// Compiles a typed handler declaration supported by an interpreter.
10pub trait HandlerEndpointAlg<Context, Inputs, Args, Transform, Output> {
11    /// Interprets a typed first-order handler as a concrete endpoint.
12    ///
13    /// `Inputs`, `Args`, `Transform`, and `Output` remain type-level evidence;
14    /// only the handler value needs to be supplied at runtime.
15    fn finish_handler<Handler>(&self, handler: Handler) -> Self::Endpoint
16    where
17        Self: HandlerAlg,
18        Handler: ApplyAlg<Context, Args, Output = Output> + Send + Sync + 'static;
19}
20
21/// Interprets a named, defunctionalized HTTP program with `Compiler`.
22pub trait HttpProgramAlg<Compiler> {
23    /// The route representation produced by `Compiler`.
24    type Route;
25
26    /// Compiles the program through the supplied interpreter.
27    fn compile_http(self, compiler: &Compiler) -> Self::Route;
28}
29
30/// Describes categorical composition of route selectors.
31pub trait SelectorAlg {
32    /// The interpreter's selector representation.
33    type Selector;
34
35    /// Returns the selector identity.
36    fn identity(&self) -> Self::Selector;
37
38    /// Composes two selectors from left to right.
39    fn compose(&self, first: Self::Selector, second: Self::Selector) -> Self::Selector;
40}
41
42/// Describes categorical construction and composition of routes.
43pub trait RouteAlg {
44    /// The interpreter's composed route representation.
45    type Route;
46    /// The selector applied to a route or endpoint.
47    type Selector;
48    /// The homogeneous endpoint representation lifted into routes.
49    type Endpoint;
50
51    /// Returns the initial route.
52    fn initial(&self) -> Self::Route;
53
54    /// Forms the coproduct of two routes.
55    fn coproduct(&self, left: Self::Route, right: Self::Route) -> Self::Route;
56
57    /// Precomposes a route with a selector.
58    fn precompose(&self, selector: Self::Selector, route: Self::Route) -> Self::Route;
59
60    /// Lifts an endpoint into a route.
61    fn lift(&self, endpoint: Self::Endpoint) -> Self::Route;
62}
63
64/// Describes the HTTP input roles chosen by an interpreter.
65pub trait HttpInputAlg {
66    /// The interpreter's path extractor for `Input`.
67    type Path<Input>;
68    /// The interpreter's query extractor for `Input`.
69    type Query<Input>;
70    /// The interpreter's request-body extractor for `Input`.
71    type Body<Input>;
72    /// The interpreter's header extractor for `Input`.
73    type Header<Input>;
74    /// The interpreter's authentication extractor for `Input`.
75    type Auth<Input>;
76    /// The interpreter's endpoint-context extractor for `Input`.
77    type Context<Input>;
78}
79
80/// Describes HTTP selectors independently of route composition.
81pub trait HttpSelectorAlg {
82    /// The interpreter's HTTP selector representation.
83    type Selector;
84
85    /// Interprets the GET method selector.
86    fn http_get(&self) -> Self::Selector;
87
88    /// Interprets the POST method selector.
89    fn http_post(&self) -> Self::Selector;
90
91    /// Interprets an exact path selector.
92    fn http_path(&self, path: &str) -> Self::Selector;
93
94    /// Interprets a path-prefix selector.
95    fn http_prefix(&self, prefix: &str) -> Self::Selector;
96}
97
98/// Combines the capabilities required to interpret HTTP route composition.
99///
100/// The alias states that HTTP selectors, route composition, and selector composition are witnessed
101/// by one interpreter using a single selector representation.
102pub trait HttpRouteAlg:
103    SelectorAlg
104    + HttpSelectorAlg<Selector = <Self as SelectorAlg>::Selector>
105    + RouteAlg<Selector = <Self as SelectorAlg>::Selector>
106{
107}
108
109impl<This> HttpRouteAlg for This where
110    This: SelectorAlg
111        + HttpSelectorAlg<Selector = <This as SelectorAlg>::Selector>
112        + RouteAlg<Selector = <This as SelectorAlg>::Selector>
113{
114}
115
116/// Combines the capabilities required to interpret a typed HTTP API.
117///
118/// The alias states that endpoints built by the handler capability are the endpoints lifted into
119/// composed routes.
120pub trait HttpApiAlg:
121    HandlerAlg + HttpInputAlg + HttpRouteAlg + RouteAlg<Endpoint = <Self as HandlerAlg>::Endpoint>
122{
123}
124
125impl<This> HttpApiAlg for This where
126    This: HandlerAlg + HttpInputAlg + HttpRouteAlg + RouteAlg<Endpoint = <This as HandlerAlg>::Endpoint>
127{
128}
129
130/// Carries a fluent route composition over an interpreter.
131pub struct Routes<'a, Alg>
132where
133    Alg: RouteAlg,
134{
135    alg: &'a Alg,
136    route: Alg::Route,
137}
138
139impl<Alg> Routes<'_, Alg>
140where
141    Alg: RouteAlg,
142{
143    /// Returns the interpreted route value.
144    pub fn into_route(self) -> Alg::Route {
145        self.route
146    }
147
148    /// Forms a coproduct with another route composition.
149    #[must_use]
150    pub fn coproduct(self, other: Self) -> Self {
151        let route = self.alg.coproduct(self.route, other.route);
152        Self { alg: self.alg, route }
153    }
154
155    /// Merges another route composition into this one.
156    #[must_use]
157    pub fn merge(self, other: Self) -> Self {
158        self.coproduct(other)
159    }
160}
161
162impl<Alg> Routes<'_, Alg>
163where
164    Alg: HttpRouteAlg,
165{
166    fn append(self, route: Alg::Route) -> Self {
167        let route = self.alg.coproduct(self.route, route);
168        Self { alg: self.alg, route }
169    }
170
171    /// Adds an endpoint at an exact path.
172    #[must_use]
173    pub fn at(self, path: &str, endpoint: Alg::Endpoint) -> Self {
174        let route = self.alg.precompose(self.alg.http_path(path), self.alg.lift(endpoint));
175        self.append(route)
176    }
177
178    /// Nests another route composition under a path prefix.
179    #[must_use]
180    pub fn nest(self, prefix: &str, nested: Self) -> Self {
181        let route = self.alg.precompose(self.alg.http_prefix(prefix), nested.route);
182        self.append(route)
183    }
184}
185
186/// Provides fluent route composition on any `RouteAlg`.
187#[ext(name = RouteAlgExt, supertraits = RouteAlg + Sized)]
188pub impl<This> This
189where
190    This: RouteAlg,
191{
192    /// Starts with the initial route.
193    fn routes(&self) -> Routes<'_, Self> {
194        Routes { alg: self, route: self.initial() }
195    }
196
197    /// Wraps an already interpreted route for further fluent composition.
198    fn route(&self, route: This::Route) -> Routes<'_, Self> {
199        Routes { alg: self, route }
200    }
201}
202
203/// Compiles defunctionalized HTTP programs with an interpreter.
204#[ext(name = HttpProgramExt)]
205pub impl<This> This {
206    /// Compiles a named HTTP program with this interpreter.
207    fn compile_http<Program>(&self, program: Program) -> Program::Route
208    where
209        Program: HttpProgramAlg<This>,
210    {
211        program.compile_http(self)
212    }
213}