Skip to main content

alux_http/
program.rs

1use crate::{
2    BytesOut, Connect, Delete, EmptyOut, FileOut, Get, HandlerEndpointAlg, Head, HeaderOut, HtmlOut, HttpApiAlg,
3    HttpInputAlg, HttpMethodAlg, HttpProgramAlg, HttpRouteAlg, JsonOut, NamedValuesAlg, Options, Patch, Post, Put,
4    RedirectOut, ResultOut, RouteAlg, RoutePath, StatusOut, StreamOut, TextOut, Trace, WithAlg,
5};
6use alux_ext::{ApplyAlg, HandlerContextAlg, OperationAlg};
7use core::marker::PhantomData;
8
9/// Compiles a first-order route program with a concrete interpreter.
10pub trait CompileRouteProgram<Compiler> {
11    /// The route representation produced by `Compiler`.
12    type Route;
13
14    /// Folds the complete first-order program through `compiler`.
15    ///
16    /// Endpoint construction happens here, after composition has preserved all
17    /// handler, input, argument, and output-transform types.
18    fn compile_route(self, compiler: &Compiler) -> Self::Route;
19}
20
21/// Represents the empty route program.
22#[derive(Debug, Default)]
23pub struct Empty;
24
25/// Represents the categorical coproduct of two route programs.
26#[derive(Debug)]
27pub struct Merge<Left, Right> {
28    left: Left,
29    right: Right,
30}
31
32/// Represents a route program nested below an HTTP path prefix.
33#[derive(Debug)]
34pub struct Nest<Program> {
35    prefix: RoutePath,
36    program: Program,
37}
38
39/// Includes a separately named HTTP program in a route program.
40#[derive(Debug)]
41pub struct Named<Program>(Program);
42
43/// Represents an endpoint without choosing an HTTP interpreter.
44#[derive(Debug)]
45pub struct Endpoint<Method, Handler, Inputs, Args, Transform> {
46    path: RoutePath,
47    handler: Handler,
48    marker: PhantomData<fn(Method, Inputs, Args, Transform)>,
49}
50
51/// Carries a typed operation declaration as first-order data.
52#[derive(Debug)]
53pub struct Operation<Handler, Inputs = (), Args = (), Transform = ()> {
54    handler: Handler,
55    marker: PhantomData<fn(Inputs, Args, Transform)>,
56}
57
58/// Carries a typed route program during fluent composition.
59#[derive(Debug)]
60pub struct RouteProgram<Program>(Program);
61
62/// Constructs neutral HTTP route programs.
63#[derive(Debug, Default)]
64pub struct HttpProgramBuilder;
65
66/// Marks an input supplied directly by an interpreter.
67pub struct Direct<Input>(PhantomData<Input>);
68
69/// Marks an HTTP path input.
70pub struct Path<Input>(PhantomData<Input>);
71
72/// Marks an HTTP query input.
73pub struct Query<Input>(PhantomData<Input>);
74
75/// Marks an HTTP request-body input.
76pub struct Body<Input>(PhantomData<Input>);
77
78/// Marks a form-encoded HTTP request-body input.
79pub struct Form<Input>(PhantomData<Input>);
80
81/// Marks an HTTP request body taken as it arrived.
82pub struct RawBody<Input>(PhantomData<Input>);
83
84/// Marks an HTTP header input.
85pub struct Header<Input>(PhantomData<Input>);
86
87/// Marks an input read from the cookies a caller sent.
88pub struct Cookie<Input>(PhantomData<Input>);
89
90/// Marks an input read from a request body arriving as parts.
91pub struct Multipart<Input>(PhantomData<Input>);
92
93/// Marks an HTTP authentication input.
94pub struct Auth<Input>(PhantomData<Input>);
95
96/// Marks an endpoint-context input.
97pub struct Context<Input>(PhantomData<Input>);
98
99/// Maps neutral input roles to the input types selected by an interpreter.
100pub trait InterpretInputsAlg<Compiler> {
101    /// The extractor product understood by `Compiler`.
102    type Inputs;
103}
104
105impl<Compiler> InterpretInputsAlg<Compiler> for () {
106    type Inputs = ();
107}
108
109impl<Compiler, Input> InterpretInputsAlg<Compiler> for Direct<Input> {
110    type Inputs = Input;
111}
112
113impl<Compiler, Input> InterpretInputsAlg<Compiler> for Path<Input>
114where
115    Compiler: HttpInputAlg,
116{
117    type Inputs = Compiler::Path<Input>;
118}
119
120impl<Compiler, Input> InterpretInputsAlg<Compiler> for Query<Input>
121where
122    Compiler: HttpInputAlg,
123{
124    type Inputs = Compiler::Query<Input>;
125}
126
127impl<Compiler, Input> InterpretInputsAlg<Compiler> for Body<Input>
128where
129    Compiler: HttpInputAlg,
130{
131    type Inputs = Compiler::Body<Input>;
132}
133
134impl<Compiler, Input> InterpretInputsAlg<Compiler> for Form<Input>
135where
136    Compiler: HttpInputAlg,
137{
138    type Inputs = Compiler::Form<Input>;
139}
140
141impl<Compiler, Input> InterpretInputsAlg<Compiler> for RawBody<Input>
142where
143    Compiler: HttpInputAlg,
144{
145    type Inputs = Compiler::RawBody<Input>;
146}
147
148impl<Compiler, Input> InterpretInputsAlg<Compiler> for Header<Input>
149where
150    Compiler: HttpInputAlg,
151{
152    type Inputs = Compiler::Header<Input>;
153}
154
155impl<Compiler, Input> InterpretInputsAlg<Compiler> for Cookie<Input>
156where
157    Compiler: HttpInputAlg,
158{
159    type Inputs = Compiler::Cookie<Input>;
160}
161
162impl<Compiler, Input> InterpretInputsAlg<Compiler> for Multipart<Input>
163where
164    Compiler: HttpInputAlg,
165{
166    type Inputs = Compiler::Multipart<Input>;
167}
168
169impl<Compiler, Input> InterpretInputsAlg<Compiler> for Auth<Input>
170where
171    Compiler: HttpInputAlg,
172{
173    type Inputs = Compiler::Auth<Input>;
174}
175
176impl<Compiler, Input> InterpretInputsAlg<Compiler> for Context<Input>
177where
178    Compiler: HttpInputAlg,
179{
180    type Inputs = Compiler::Context<Input>;
181}
182
183macro_rules! interpret_inputs {
184    ($($input:ident),+ $(,)?) => {
185        impl<Compiler, $($input),+> InterpretInputsAlg<Compiler> for ($($input,)+)
186        where
187            $($input: InterpretInputsAlg<Compiler>,)+
188        {
189            type Inputs = ($($input::Inputs,)+);
190        }
191    };
192}
193
194interpret_inputs!(I1);
195interpret_inputs!(I1, I2);
196interpret_inputs!(I1, I2, I3);
197interpret_inputs!(I1, I2, I3, I4);
198interpret_inputs!(I1, I2, I3, I4, I5);
199interpret_inputs!(I1, I2, I3, I4, I5, I6);
200interpret_inputs!(I1, I2, I3, I4, I5, I6, I7);
201interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8);
202interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9);
203interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10);
204interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11);
205interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12);
206interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13);
207interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14);
208interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15);
209interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15, I16);
210
211impl HttpProgramBuilder {
212    /// Starts an empty, uninterpreted route program.
213    ///
214    /// Subsequent calls record route syntax without requiring any concrete
215    /// framework capabilities.
216    pub fn routes(&self) -> RouteProgram<Empty> {
217        RouteProgram(Empty)
218    }
219
220    /// Wraps a first-order handler operation in a neutral declaration.
221    ///
222    /// Input roles and an output kind can then be attached while the handler's
223    /// result type remains inferred through `ApplyAlg`.
224    pub fn op<Handler>(&self, handler: Handler) -> Operation<Handler> {
225        Operation { handler, marker: PhantomData }
226    }
227
228    /// Includes a named HTTP program as an uninterpreted composition node.
229    ///
230    /// The named program is compiled by the same interpreter as its enclosing
231    /// route program when the complete tree is folded.
232    pub fn program<Program>(&self, program: Program) -> RouteProgram<Named<Program>> {
233        RouteProgram(Named(program))
234    }
235}
236
237/// Carries an operation declaration with one additional typed input.
238pub type WithInput<Handler, Inputs, Args, Transform, Extractor, Arg> =
239    Operation<Handler, <Inputs as WithAlg>::With<Extractor>, <Args as WithAlg>::With<Arg>, Transform>;
240
241/// Carries a route program with one additional typed endpoint.
242pub type WithEndpoint<Program, Method, Handler, Inputs, Args, Transform> =
243    RouteProgram<Merge<Program, Endpoint<Method, Handler, Inputs, Args, Transform>>>;
244
245macro_rules! output_methods {
246    ($($method:ident => $kind:ident, $alg:ident, $selected:ident, $meaning:literal),+ $(,)?) => {
247        $(
248            #[doc = concat!("Marks the inferred handler result for ", $meaning, " interpretation.")]
249            pub fn $method(self) -> Operation<Handler, Inputs, Args, $kind> {
250                self.out()
251            }
252        )+
253    };
254}
255
256/// States the nine method declarations on both a program and a standalone operation.
257///
258/// An author writes the program form; a declaration read by the `http` macro becomes the operation
259/// form. Emitting both from one list is what keeps the two spellings of a method in step.
260macro_rules! route_methods {
261    ($($method:ident => $marker:ident, $label:literal),+ $(,)?) => {
262        impl<Program> RouteProgram<Program> {
263            $(
264                #[doc = concat!("Records a `", $label, "` selector and typed operation at an exact path.")]
265                pub fn $method<Handler, Inputs, Args, Transform>(
266                    self,
267                    path: &str,
268                    operation: Operation<Handler, Inputs, Args, Transform>,
269                ) -> WithEndpoint<Program, $marker, Handler, Inputs, Args, Transform> {
270                    self.method(path, operation)
271                }
272            )+
273        }
274
275        impl<Handler, Inputs, Args, Transform> Operation<Handler, Inputs, Args, Transform> {
276            $(
277                #[doc = concat!("Declares this operation at an exact path, answered under `", $label, "`.")]
278                ///
279                /// This is the same thing the declaration of that name on `RouteProgram` states,
280                /// for one endpoint standing on its own rather than one inside a composition.
281                pub fn $method(self, path: &str) -> Endpoint<$marker, Handler, Inputs, Args, Transform> {
282                    self.declare::<$marker>(path)
283                }
284            )+
285        }
286    };
287}
288
289impl<Handler, Inputs, Args, Transform> Operation<Handler, Inputs, Args, Transform> {
290    /// Declares this operation at one path under one method selector, with no program around it.
291    ///
292    /// A program that states many endpoints composes their routes rather than their types, so it
293    /// needs each endpoint on its own. The named declarations below select one method each; this
294    /// states the same thing for a method held as a type parameter.
295    pub fn declare<Method>(self, path: &str) -> Endpoint<Method, Handler, Inputs, Args, Transform> {
296        Endpoint { path: RoutePath::parse(path), handler: self.handler, marker: PhantomData }
297    }
298}
299
300impl<Handler, Inputs, Args, Transform> Operation<Handler, Inputs, Args, Transform>
301where
302    Inputs: WithAlg,
303    Args: WithAlg,
304{
305    // Changes only the declaration's phantom input and argument products while
306    // preserving the first-order handler value.
307    fn with_as<Input, Arg>(self) -> WithInput<Handler, Inputs, Args, Transform, Input, Arg> {
308        Operation { handler: self.handler, marker: PhantomData }
309    }
310
311    /// Records an argument supplied directly in the interpreter's input product.
312    pub fn with<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Direct<Input>, Input> {
313        self.with_as::<Direct<Input>, Input>()
314    }
315
316    /// Records a path extractor whose value becomes the next handler argument.
317    pub fn path<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Path<Input>, Input> {
318        self.with_as::<Path<Input>, Input>()
319    }
320
321    /// Records a query extractor whose value becomes the next handler argument.
322    pub fn query<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Query<Input>, Input>
323    where
324        Input: NamedValuesAlg,
325    {
326        self.with_as::<Query<Input>, Input>()
327    }
328
329    /// Records a request-body extractor whose value becomes the next handler argument.
330    pub fn body<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Body<Input>, Input> {
331        self.with_as::<Body<Input>, Input>()
332    }
333
334    /// Records a form-encoded request-body extractor whose value becomes the next handler argument.
335    pub fn form<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Form<Input>, Input> {
336        self.with_as::<Form<Input>, Input>()
337    }
338
339    /// Records the request body as it arrived, becoming the next handler argument.
340    pub fn raw_body<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, RawBody<Input>, Input> {
341        self.with_as::<RawBody<Input>, Input>()
342    }
343
344    /// Records an incoming header extractor whose value becomes the next handler argument.
345    pub fn in_header<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Header<Input>, Input>
346    where
347        Input: NamedValuesAlg,
348    {
349        self.with_as::<Header<Input>, Input>()
350    }
351
352    /// Records a cookie extractor whose value becomes the next handler argument.
353    pub fn cookie<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Cookie<Input>, Input>
354    where
355        Input: NamedValuesAlg,
356    {
357        self.with_as::<Cookie<Input>, Input>()
358    }
359
360    /// Records a body arriving as parts, read into the next handler argument.
361    pub fn multipart<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Multipart<Input>, Input> {
362        self.with_as::<Multipart<Input>, Input>()
363    }
364
365    /// Records an authentication extractor whose value becomes the next handler argument.
366    pub fn auth<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Auth<Input>, Input>
367    where
368        Input: NamedValuesAlg,
369    {
370        self.with_as::<Auth<Input>, Input>()
371    }
372
373    /// Records an endpoint-context extractor whose value becomes the next handler argument.
374    pub fn context<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Context<Input>, Input> {
375        self.with_as::<Context<Input>, Input>()
376    }
377
378    /// Replaces the declaration's output-kind marker without converting a value.
379    ///
380    /// The selected kind is interpreted only after the handler result type is
381    /// known at the compilation boundary.
382    pub fn out<NewTransform>(self) -> Operation<Handler, Inputs, Args, NewTransform> {
383        Operation { handler: self.handler, marker: PhantomData }
384    }
385
386    with_output_kinds!(output_methods);
387
388    /// Answers with `CODE` and the body already stated.
389    pub fn status<const CODE: u16>(self) -> Operation<Handler, Inputs, Args, StatusOut<Transform, CODE>> {
390        self.out()
391    }
392
393    /// Answers with an outgoing response header the handler states, beside the body already stated.
394    ///
395    /// The handler answers with the header's value and the body, so a value an endpoint cannot know
396    /// is one the handler still states.
397    pub fn out_header<Name>(self) -> Operation<Handler, Inputs, Args, HeaderOut<Transform, Name>> {
398        self.out()
399    }
400
401    /// Answers with what the handler's failure means when it fails.
402    ///
403    /// The kind already stated answers the successful result, so `.json().result()` states JSON on
404    /// success and the meaning of the failure otherwise.
405    pub fn result(self) -> Operation<Handler, Inputs, Args, ResultOut<Transform>> {
406        self.out()
407    }
408}
409
410impl<Program> RouteProgram<Program> {
411    /// Reads this program below an HTTP path prefix, with no program around it.
412    ///
413    /// A program that states many nestings composes their routes rather than their types, so it
414    /// needs each nesting on its own. `RouteProgram::nest` states the same thing inside a
415    /// composition, and is what an author writes.
416    pub fn under(self, prefix: &str) -> Nest<Program> {
417        Nest { prefix: RoutePath::parse(prefix), program: self.0 }
418    }
419
420    /// Records the categorical coproduct of two typed route programs.
421    ///
422    /// Neither side is interpreted, so both complete program types remain
423    /// available to later folds.
424    pub fn merge<Other>(self, other: RouteProgram<Other>) -> RouteProgram<Merge<Program, Other>> {
425        RouteProgram(Merge { left: self.0, right: other.0 })
426    }
427
428    /// Records `other` under `prefix` and merges it into this program.
429    ///
430    /// The prefix is selector precomposition rather than a framework-specific
431    /// router operation.
432    pub fn nest<Other>(self, prefix: &str, other: RouteProgram<Other>) -> RouteProgram<Merge<Program, Nest<Other>>> {
433        self.merge(RouteProgram(Nest { prefix: RoutePath::parse(prefix), program: other.0 }))
434    }
435
436    /// Records a method selector and typed operation at an exact path.
437    ///
438    /// The named declarations below select one method each and are what an author writes; this
439    /// states the same thing for a method held as a type parameter.
440    pub fn method<Method, Handler, Inputs, Args, Transform>(
441        self,
442        path: &str,
443        operation: Operation<Handler, Inputs, Args, Transform>,
444    ) -> WithEndpoint<Program, Method, Handler, Inputs, Args, Transform> {
445        self.merge(RouteProgram(Endpoint {
446            path: RoutePath::parse(path),
447            handler: operation.handler,
448            marker: PhantomData,
449        }))
450    }
451
452    /// Removes the fluent wrapper and returns the first-order syntax tree.
453    pub fn into_program(self) -> Program {
454        self.0
455    }
456}
457
458with_http_methods!(route_methods);
459
460impl<Compiler> CompileRouteProgram<Compiler> for Empty
461where
462    Compiler: RouteAlg,
463{
464    type Route = Compiler::Route;
465
466    fn compile_route(self, compiler: &Compiler) -> Self::Route {
467        compiler.initial()
468    }
469}
470
471impl<Compiler, Left, Right, Route> CompileRouteProgram<Compiler> for Merge<Left, Right>
472where
473    Compiler: RouteAlg<Route = Route>,
474    Left: CompileRouteProgram<Compiler, Route = Route>,
475    Right: CompileRouteProgram<Compiler, Route = Route>,
476{
477    type Route = Route;
478
479    fn compile_route(self, compiler: &Compiler) -> Route {
480        compiler.coproduct(self.left.compile_route(compiler), self.right.compile_route(compiler))
481    }
482}
483
484impl<Compiler, Program, Route> CompileRouteProgram<Compiler> for Nest<Program>
485where
486    Compiler: HttpRouteAlg<Route = Route>,
487    Program: CompileRouteProgram<Compiler, Route = Route>,
488{
489    type Route = Route;
490
491    fn compile_route(self, compiler: &Compiler) -> Route {
492        compiler.precompose(compiler.http_prefix(&self.prefix), self.program.compile_route(compiler))
493    }
494}
495
496impl<Compiler, Program> CompileRouteProgram<Compiler> for Named<Program>
497where
498    Program: HttpProgramAlg<Compiler>,
499{
500    type Route = Program::Route;
501
502    fn compile_route(self, compiler: &Compiler) -> Self::Route {
503        self.0.compile_http(compiler)
504    }
505}
506
507impl<Compiler, Method, Handler, Inputs, Args, Transform, Handle> CompileRouteProgram<Compiler>
508    for Endpoint<Method, Handler, Inputs, Args, Transform>
509where
510    Compiler: HttpApiAlg
511        + HandlerContextAlg<Handler::Context, Handle = Handle>
512        + HandlerEndpointAlg<Handle, Inputs::Inputs, Args, Transform, Handler::Output>,
513    Method: HttpMethodAlg,
514    Handler: OperationAlg + ApplyAlg<Handle, Args> + Send + Sync + 'static,
515    Inputs: InterpretInputsAlg<Compiler>,
516{
517    type Route = Compiler::Route;
518
519    fn compile_route(self, compiler: &Compiler) -> Self::Route {
520        let selector = compiler.compose(compiler.http_method(Method::METHOD), compiler.http_path(&self.path));
521        let endpoint = compiler.finish_handler(self.handler);
522
523        compiler.precompose(selector, compiler.lift(endpoint))
524    }
525}