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