Skip to main content

alux_http/
algebra.rs

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