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>
204where
205    Inputs: WithAlg,
206    Args: WithAlg,
207{
208    // Changes only the declaration's phantom input and argument products while
209    // preserving the first-order handler value.
210    fn with_as<Input, Arg>(self) -> WithInput<Handler, Inputs, Args, Transform, Input, Arg> {
211        Operation { handler: self.handler, marker: PhantomData }
212    }
213
214    /// Records an argument supplied directly in the interpreter's input product.
215    pub fn with<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Direct<Input>, Input> {
216        self.with_as::<Direct<Input>, Input>()
217    }
218
219    /// Records a path extractor whose value becomes the next handler argument.
220    pub fn path<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Path<Input>, Input> {
221        self.with_as::<Path<Input>, Input>()
222    }
223
224    /// Records a query extractor whose value becomes the next handler argument.
225    pub fn query<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Query<Input>, Input> {
226        self.with_as::<Query<Input>, Input>()
227    }
228
229    /// Records a request-body extractor whose value becomes the next handler argument.
230    pub fn body<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Body<Input>, Input> {
231        self.with_as::<Body<Input>, Input>()
232    }
233
234    /// Records a header extractor whose value becomes the next handler argument.
235    pub fn header<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Header<Input>, Input> {
236        self.with_as::<Header<Input>, Input>()
237    }
238
239    /// Records an authentication extractor whose value becomes the next handler argument.
240    pub fn auth<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Auth<Input>, Input> {
241        self.with_as::<Auth<Input>, Input>()
242    }
243
244    /// Records an endpoint-context extractor whose value becomes the next handler argument.
245    pub fn context<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Context<Input>, Input> {
246        self.with_as::<Context<Input>, Input>()
247    }
248
249    /// Replaces the declaration's output-kind marker without converting a value.
250    ///
251    /// The selected kind is interpreted only after the handler result type is
252    /// known at the compilation boundary.
253    pub fn out<NewTransform>(self) -> Operation<Handler, Inputs, Args, NewTransform> {
254        Operation { handler: self.handler, marker: PhantomData }
255    }
256
257    /// Marks the inferred handler result for JSON interpretation.
258    pub fn json(self) -> Operation<Handler, Inputs, Args, JsonOut> {
259        self.out()
260    }
261
262    /// Marks the inferred handler result for streamed-file interpretation.
263    pub fn file(self) -> Operation<Handler, Inputs, Args, FileOut> {
264        self.out()
265    }
266}
267
268impl<Program> RouteProgram<Program> {
269    /// Records the categorical coproduct of two typed route programs.
270    ///
271    /// Neither side is interpreted, so both complete program types remain
272    /// available to later folds.
273    pub fn merge<Other>(self, other: RouteProgram<Other>) -> RouteProgram<Merge<Program, Other>> {
274        RouteProgram(Merge { left: self.0, right: other.0 })
275    }
276
277    /// Records `other` under `prefix` and merges it into this program.
278    ///
279    /// The prefix is selector precomposition rather than a framework-specific
280    /// router operation.
281    pub fn nest<Other>(self, prefix: &str, other: RouteProgram<Other>) -> RouteProgram<Merge<Program, Nest<Other>>> {
282        self.merge(RouteProgram(Nest { prefix: prefix.into(), program: other.0 }))
283    }
284
285    /// Records a GET selector and typed operation at an exact path.
286    pub fn get<Handler, Inputs, Args, Transform>(
287        self,
288        path: &str,
289        operation: Operation<Handler, Inputs, Args, Transform>,
290    ) -> WithEndpoint<Program, Get, Handler, Inputs, Args, Transform> {
291        self.merge(RouteProgram(Endpoint { path: path.into(), handler: operation.handler, marker: PhantomData }))
292    }
293
294    /// Records a POST selector and typed operation at an exact path.
295    pub fn post<Handler, Inputs, Args, Transform>(
296        self,
297        path: &str,
298        operation: Operation<Handler, Inputs, Args, Transform>,
299    ) -> WithEndpoint<Program, Post, Handler, Inputs, Args, Transform> {
300        self.merge(RouteProgram(Endpoint { path: path.into(), handler: operation.handler, marker: PhantomData }))
301    }
302
303    /// Removes the fluent wrapper and returns the first-order syntax tree.
304    pub fn into_program(self) -> Program {
305        self.0
306    }
307}
308
309impl<Compiler> CompileRouteProgram<Compiler> for Empty
310where
311    Compiler: RouteAlg,
312{
313    type Route = Compiler::Route;
314
315    fn compile_route(self, compiler: &Compiler) -> Self::Route {
316        compiler.initial()
317    }
318}
319
320impl<Compiler, Left, Right, Route> CompileRouteProgram<Compiler> for Merge<Left, Right>
321where
322    Compiler: RouteAlg<Route = Route>,
323    Left: CompileRouteProgram<Compiler, Route = Route>,
324    Right: CompileRouteProgram<Compiler, Route = Route>,
325{
326    type Route = Route;
327
328    fn compile_route(self, compiler: &Compiler) -> Route {
329        compiler.coproduct(self.left.compile_route(compiler), self.right.compile_route(compiler))
330    }
331}
332
333impl<Compiler, Program, Route> CompileRouteProgram<Compiler> for Nest<Program>
334where
335    Compiler: HttpRouteAlg<Route = Route>,
336    Program: CompileRouteProgram<Compiler, Route = Route>,
337{
338    type Route = Route;
339
340    fn compile_route(self, compiler: &Compiler) -> Route {
341        compiler.precompose(compiler.http_prefix(&self.prefix), self.program.compile_route(compiler))
342    }
343}
344
345impl<Compiler, Program> CompileRouteProgram<Compiler> for Named<Program>
346where
347    Program: HttpProgramAlg<Compiler>,
348{
349    type Route = Program::Route;
350
351    fn compile_route(self, compiler: &Compiler) -> Self::Route {
352        self.0.compile_http(compiler)
353    }
354}
355
356trait HttpMethodAlg<Compiler> {
357    fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
358    where
359        Compiler: HttpApiAlg;
360}
361
362impl<Compiler> HttpMethodAlg<Compiler> for Get {
363    fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
364    where
365        Compiler: HttpApiAlg,
366    {
367        compiler.http_get()
368    }
369}
370
371impl<Compiler> HttpMethodAlg<Compiler> for Post {
372    fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
373    where
374        Compiler: HttpApiAlg,
375    {
376        compiler.http_post()
377    }
378}
379
380impl<Compiler, Method, Handler, Inputs, Args, Transform, Handle> CompileRouteProgram<Compiler>
381    for Endpoint<Method, Handler, Inputs, Args, Transform>
382where
383    Compiler: HttpApiAlg
384        + HandlerContextAlg<Handler::Context, Handle = Handle>
385        + HandlerEndpointAlg<Handle, Inputs::Inputs, Args, Transform, Handler::Output>,
386    Method: HttpMethodAlg<Compiler>,
387    Handler: OperationAlg + ApplyAlg<Handle, Args> + Send + Sync + 'static,
388    Inputs: InterpretInputsAlg<Compiler>,
389{
390    type Route = Compiler::Route;
391
392    fn compile_route(self, compiler: &Compiler) -> Self::Route {
393        let selector = compiler.compose(Method::selector(compiler), compiler.http_path(&self.path));
394        let endpoint = compiler.finish_handler(self.handler);
395
396        compiler.precompose(selector, compiler.lift(endpoint))
397    }
398}