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