Skip to main content

alux_http/
program.rs

1use crate::{
2    FileOut, HandlerEndpointAlg, HttpApiAlg, HttpInputAlg, HttpProgramAlg, HttpRouteAlg, JsonOut, RouteAlg, WithAlg,
3};
4use alux_ext::{ApplyAlg, HandlerContextAlg, OperationAlg};
5use core::marker::PhantomData;
6
7/// Compiles a first-order route program with a concrete interpreter.
8pub trait CompileRouteProgram<Compiler> {
9    /// The route representation produced by `Compiler`.
10    type Route;
11
12    /// Folds the complete first-order program through `compiler`.
13    ///
14    /// Endpoint construction happens here, after composition has preserved all
15    /// handler, input, argument, and output-transform types.
16    fn compile_route(self, compiler: &Compiler) -> Self::Route;
17}
18
19/// Represents the empty route program.
20#[derive(Debug, Default)]
21pub struct Empty;
22
23/// Represents the categorical coproduct of two route programs.
24#[derive(Debug)]
25pub struct Merge<Left, Right> {
26    left: Left,
27    right: Right,
28}
29
30/// Represents a route program nested below an HTTP path prefix.
31#[derive(Debug)]
32pub struct Nest<Program> {
33    prefix: String,
34    program: Program,
35}
36
37/// Includes a separately named HTTP program in a route program.
38#[derive(Debug)]
39pub struct Named<Program>(Program);
40
41/// Identifies a GET endpoint declaration.
42#[derive(Debug)]
43pub struct Get;
44
45/// Identifies a POST endpoint declaration.
46#[derive(Debug)]
47pub struct Post;
48
49/// Represents an endpoint without choosing an HTTP interpreter.
50#[derive(Debug)]
51pub struct Endpoint<Method, Handler, Inputs, Args, Transform> {
52    path: String,
53    handler: Handler,
54    marker: PhantomData<fn(Method, Inputs, Args, Transform)>,
55}
56
57/// Carries a typed operation declaration as first-order data.
58#[derive(Debug)]
59pub struct Operation<Handler, Inputs = (), Args = (), Transform = ()> {
60    handler: Handler,
61    marker: PhantomData<fn(Inputs, Args, Transform)>,
62}
63
64/// Carries a typed route program during fluent composition.
65#[derive(Debug)]
66pub struct RouteProgram<Program>(Program);
67
68/// Constructs neutral HTTP route programs.
69#[derive(Debug, Default)]
70pub struct HttpProgramBuilder;
71
72/// Marks an input supplied directly by an interpreter.
73pub struct Direct<Input>(PhantomData<Input>);
74
75/// Marks an HTTP path input.
76pub struct Path<Input>(PhantomData<Input>);
77
78/// Marks an HTTP query input.
79pub struct Query<Input>(PhantomData<Input>);
80
81/// Marks an HTTP request-body input.
82pub struct Body<Input>(PhantomData<Input>);
83
84/// Marks an HTTP header input.
85pub struct Header<Input>(PhantomData<Input>);
86
87/// Marks an HTTP authentication input.
88pub struct Auth<Input>(PhantomData<Input>);
89
90/// Marks an endpoint-context input.
91pub struct Context<Input>(PhantomData<Input>);
92
93/// Maps neutral input roles to the input types selected by an interpreter.
94pub trait InterpretInputsAlg<Compiler> {
95    /// The extractor product understood by `Compiler`.
96    type Inputs;
97}
98
99impl<Compiler> InterpretInputsAlg<Compiler> for () {
100    type Inputs = ();
101}
102
103impl<Compiler, Input> InterpretInputsAlg<Compiler> for Direct<Input> {
104    type Inputs = Input;
105}
106
107impl<Compiler, Input> InterpretInputsAlg<Compiler> for Path<Input>
108where
109    Compiler: HttpInputAlg,
110{
111    type Inputs = Compiler::Path<Input>;
112}
113
114impl<Compiler, Input> InterpretInputsAlg<Compiler> for Query<Input>
115where
116    Compiler: HttpInputAlg,
117{
118    type Inputs = Compiler::Query<Input>;
119}
120
121impl<Compiler, Input> InterpretInputsAlg<Compiler> for Body<Input>
122where
123    Compiler: HttpInputAlg,
124{
125    type Inputs = Compiler::Body<Input>;
126}
127
128impl<Compiler, Input> InterpretInputsAlg<Compiler> for Header<Input>
129where
130    Compiler: HttpInputAlg,
131{
132    type Inputs = Compiler::Header<Input>;
133}
134
135impl<Compiler, Input> InterpretInputsAlg<Compiler> for Auth<Input>
136where
137    Compiler: HttpInputAlg,
138{
139    type Inputs = Compiler::Auth<Input>;
140}
141
142impl<Compiler, Input> InterpretInputsAlg<Compiler> for Context<Input>
143where
144    Compiler: HttpInputAlg,
145{
146    type Inputs = Compiler::Context<Input>;
147}
148
149macro_rules! interpret_inputs {
150    ($($input:ident),+ $(,)?) => {
151        impl<Compiler, $($input),+> InterpretInputsAlg<Compiler> for ($($input,)+)
152        where
153            $($input: InterpretInputsAlg<Compiler>,)+
154        {
155            type Inputs = ($($input::Inputs,)+);
156        }
157    };
158}
159
160interpret_inputs!(I1);
161interpret_inputs!(I1, I2);
162interpret_inputs!(I1, I2, I3);
163interpret_inputs!(I1, I2, I3, I4);
164interpret_inputs!(I1, I2, I3, I4, I5);
165interpret_inputs!(I1, I2, I3, I4, I5, I6);
166interpret_inputs!(I1, I2, I3, I4, I5, I6, I7);
167interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8);
168
169impl HttpProgramBuilder {
170    /// Starts an empty, uninterpreted route program.
171    ///
172    /// Subsequent calls record route syntax without requiring any concrete
173    /// framework capabilities.
174    pub fn routes(&self) -> RouteProgram<Empty> {
175        RouteProgram(Empty)
176    }
177
178    /// Wraps a first-order handler operation in a neutral declaration.
179    ///
180    /// Input roles and an output kind can then be attached while the handler's
181    /// result type remains inferred through `ApplyAlg`.
182    pub fn op<Handler>(&self, handler: Handler) -> Operation<Handler> {
183        Operation { handler, marker: PhantomData }
184    }
185
186    /// Includes a named HTTP program as an uninterpreted composition node.
187    ///
188    /// The named program is compiled by the same interpreter as its enclosing
189    /// route program when the complete tree is folded.
190    pub fn program<Program>(&self, program: Program) -> RouteProgram<Named<Program>> {
191        RouteProgram(Named(program))
192    }
193}
194
195/// Carries an operation declaration with one additional typed input.
196pub type WithInput<Handler, Inputs, Args, Transform, Extractor, Arg> =
197    Operation<Handler, <Inputs as WithAlg>::With<Extractor>, <Args as WithAlg>::With<Arg>, Transform>;
198
199/// Carries a route program with one additional typed endpoint.
200pub type WithEndpoint<Program, Method, Handler, Inputs, Args, Transform> =
201    RouteProgram<Merge<Program, Endpoint<Method, Handler, Inputs, Args, Transform>>>;
202
203impl<Handler, Inputs, Args, Transform> Operation<Handler, Inputs, Args, Transform> {
204    /// Declares this operation at one path under one method selector, with no program around it.
205    ///
206    /// A program that states many endpoints composes their routes rather than their types, so it
207    /// needs each endpoint on its own. `RouteProgram::get` and `RouteProgram::post` state the same
208    /// thing inside a composition, and are what an author writes.
209    pub fn declare<Method>(self, path: &str) -> Endpoint<Method, Handler, Inputs, Args, Transform> {
210        Endpoint { path: path.into(), handler: self.handler, marker: PhantomData }
211    }
212}
213
214impl<Handler, Inputs, Args, Transform> Operation<Handler, Inputs, Args, Transform>
215where
216    Inputs: WithAlg,
217    Args: WithAlg,
218{
219    // Changes only the declaration's phantom input and argument products while
220    // preserving the first-order handler value.
221    fn with_as<Input, Arg>(self) -> WithInput<Handler, Inputs, Args, Transform, Input, Arg> {
222        Operation { handler: self.handler, marker: PhantomData }
223    }
224
225    /// Records an argument supplied directly in the interpreter's input product.
226    pub fn with<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Direct<Input>, Input> {
227        self.with_as::<Direct<Input>, Input>()
228    }
229
230    /// Records a path extractor whose value becomes the next handler argument.
231    pub fn path<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Path<Input>, Input> {
232        self.with_as::<Path<Input>, Input>()
233    }
234
235    /// Records a query extractor whose value becomes the next handler argument.
236    pub fn query<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Query<Input>, Input> {
237        self.with_as::<Query<Input>, Input>()
238    }
239
240    /// Records a request-body extractor whose value becomes the next handler argument.
241    pub fn body<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Body<Input>, Input> {
242        self.with_as::<Body<Input>, Input>()
243    }
244
245    /// Records a header extractor whose value becomes the next handler argument.
246    pub fn header<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Header<Input>, Input> {
247        self.with_as::<Header<Input>, Input>()
248    }
249
250    /// Records an authentication extractor whose value becomes the next handler argument.
251    pub fn auth<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Auth<Input>, Input> {
252        self.with_as::<Auth<Input>, Input>()
253    }
254
255    /// Records an endpoint-context extractor whose value becomes the next handler argument.
256    pub fn context<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Context<Input>, Input> {
257        self.with_as::<Context<Input>, Input>()
258    }
259
260    /// Replaces the declaration's output-kind marker without converting a value.
261    ///
262    /// The selected kind is interpreted only after the handler result type is
263    /// known at the compilation boundary.
264    pub fn out<NewTransform>(self) -> Operation<Handler, Inputs, Args, NewTransform> {
265        Operation { handler: self.handler, marker: PhantomData }
266    }
267
268    /// Marks the inferred handler result for JSON interpretation.
269    pub fn json(self) -> Operation<Handler, Inputs, Args, JsonOut> {
270        self.out()
271    }
272
273    /// Marks the inferred handler result for streamed-file interpretation.
274    pub fn file(self) -> Operation<Handler, Inputs, Args, FileOut> {
275        self.out()
276    }
277}
278
279impl<Program> RouteProgram<Program> {
280    /// Reads this program below an HTTP path prefix, with no program around it.
281    ///
282    /// A program that states many nestings composes their routes rather than their types, so it
283    /// needs each nesting on its own. `RouteProgram::nest` states the same thing inside a
284    /// composition, and is what an author writes.
285    pub fn under(self, prefix: &str) -> Nest<Program> {
286        Nest { prefix: prefix.into(), program: self.0 }
287    }
288
289    /// Records the categorical coproduct of two typed route programs.
290    ///
291    /// Neither side is interpreted, so both complete program types remain
292    /// available to later folds.
293    pub fn merge<Other>(self, other: RouteProgram<Other>) -> RouteProgram<Merge<Program, Other>> {
294        RouteProgram(Merge { left: self.0, right: other.0 })
295    }
296
297    /// Records `other` under `prefix` and merges it into this program.
298    ///
299    /// The prefix is selector precomposition rather than a framework-specific
300    /// router operation.
301    pub fn nest<Other>(self, prefix: &str, other: RouteProgram<Other>) -> RouteProgram<Merge<Program, Nest<Other>>> {
302        self.merge(RouteProgram(Nest { prefix: prefix.into(), program: other.0 }))
303    }
304
305    /// Records a GET selector and typed operation at an exact path.
306    pub fn get<Handler, Inputs, Args, Transform>(
307        self,
308        path: &str,
309        operation: Operation<Handler, Inputs, Args, Transform>,
310    ) -> WithEndpoint<Program, Get, Handler, Inputs, Args, Transform> {
311        self.merge(RouteProgram(Endpoint { path: path.into(), handler: operation.handler, marker: PhantomData }))
312    }
313
314    /// Records a POST selector and typed operation at an exact path.
315    pub fn post<Handler, Inputs, Args, Transform>(
316        self,
317        path: &str,
318        operation: Operation<Handler, Inputs, Args, Transform>,
319    ) -> WithEndpoint<Program, Post, Handler, Inputs, Args, Transform> {
320        self.merge(RouteProgram(Endpoint { path: path.into(), handler: operation.handler, marker: PhantomData }))
321    }
322
323    /// Removes the fluent wrapper and returns the first-order syntax tree.
324    pub fn into_program(self) -> Program {
325        self.0
326    }
327}
328
329impl<Compiler> CompileRouteProgram<Compiler> for Empty
330where
331    Compiler: RouteAlg,
332{
333    type Route = Compiler::Route;
334
335    fn compile_route(self, compiler: &Compiler) -> Self::Route {
336        compiler.initial()
337    }
338}
339
340impl<Compiler, Left, Right, Route> CompileRouteProgram<Compiler> for Merge<Left, Right>
341where
342    Compiler: RouteAlg<Route = Route>,
343    Left: CompileRouteProgram<Compiler, Route = Route>,
344    Right: CompileRouteProgram<Compiler, Route = Route>,
345{
346    type Route = Route;
347
348    fn compile_route(self, compiler: &Compiler) -> Route {
349        compiler.coproduct(self.left.compile_route(compiler), self.right.compile_route(compiler))
350    }
351}
352
353impl<Compiler, Program, Route> CompileRouteProgram<Compiler> for Nest<Program>
354where
355    Compiler: HttpRouteAlg<Route = Route>,
356    Program: CompileRouteProgram<Compiler, Route = Route>,
357{
358    type Route = Route;
359
360    fn compile_route(self, compiler: &Compiler) -> Route {
361        compiler.precompose(compiler.http_prefix(&self.prefix), self.program.compile_route(compiler))
362    }
363}
364
365impl<Compiler, Program> CompileRouteProgram<Compiler> for Named<Program>
366where
367    Program: HttpProgramAlg<Compiler>,
368{
369    type Route = Program::Route;
370
371    fn compile_route(self, compiler: &Compiler) -> Self::Route {
372        self.0.compile_http(compiler)
373    }
374}
375
376trait HttpMethodAlg<Compiler> {
377    fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
378    where
379        Compiler: HttpApiAlg;
380}
381
382impl<Compiler> HttpMethodAlg<Compiler> for Get {
383    fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
384    where
385        Compiler: HttpApiAlg,
386    {
387        compiler.http_get()
388    }
389}
390
391impl<Compiler> HttpMethodAlg<Compiler> for Post {
392    fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
393    where
394        Compiler: HttpApiAlg,
395    {
396        compiler.http_post()
397    }
398}
399
400impl<Compiler, Method, Handler, Inputs, Args, Transform, Handle> CompileRouteProgram<Compiler>
401    for Endpoint<Method, Handler, Inputs, Args, Transform>
402where
403    Compiler: HttpApiAlg
404        + HandlerContextAlg<Handler::Context, Handle = Handle>
405        + HandlerEndpointAlg<Handle, Inputs::Inputs, Args, Transform, Handler::Output>,
406    Method: HttpMethodAlg<Compiler>,
407    Handler: OperationAlg + ApplyAlg<Handle, Args> + Send + Sync + 'static,
408    Inputs: InterpretInputsAlg<Compiler>,
409{
410    type Route = Compiler::Route;
411
412    fn compile_route(self, compiler: &Compiler) -> Self::Route {
413        let selector = compiler.compose(Method::selector(compiler), compiler.http_path(&self.path));
414        let endpoint = compiler.finish_handler(self.handler);
415
416        compiler.precompose(selector, compiler.lift(endpoint))
417    }
418}