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}