Skip to main content

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}