use crate::{
BytesOut, Connect, Delete, EmptyOut, FileOut, Get, HandlerEndpointAlg, Head, HeaderOut, HtmlOut, HttpApiAlg,
HttpInputAlg, HttpMethodAlg, HttpProgramAlg, HttpRouteAlg, JsonOut, NamedValuesAlg, Options, Patch, Post, Put,
RedirectOut, ResultOut, RouteAlg, RoutePath, StatusOut, StreamOut, TextOut, Trace, WithAlg,
};
use alux_ext::{ApplyAlg, HandlerContextAlg, OperationAlg};
use core::marker::PhantomData;
pub trait CompileRouteProgram<Compiler> {
type Route;
fn compile_route(self, compiler: &Compiler) -> Self::Route;
}
#[derive(Debug, Default)]
pub struct Empty;
#[derive(Debug)]
pub struct Merge<Left, Right> {
left: Left,
right: Right,
}
#[derive(Debug)]
pub struct Nest<Program> {
prefix: RoutePath,
program: Program,
}
#[derive(Debug)]
pub struct Named<Program>(Program);
#[derive(Debug)]
pub struct Endpoint<Method, Handler, Inputs, Args, Transform> {
path: RoutePath,
handler: Handler,
marker: PhantomData<fn(Method, Inputs, Args, Transform)>,
}
#[derive(Debug)]
pub struct Operation<Handler, Inputs = (), Args = (), Transform = ()> {
handler: Handler,
marker: PhantomData<fn(Inputs, Args, Transform)>,
}
#[derive(Debug)]
pub struct RouteProgram<Program>(Program);
#[derive(Debug, Default)]
pub struct HttpProgramBuilder;
pub struct Direct<Input>(PhantomData<Input>);
pub struct Path<Input>(PhantomData<Input>);
pub struct Query<Input>(PhantomData<Input>);
pub struct Body<Input>(PhantomData<Input>);
pub struct Form<Input>(PhantomData<Input>);
pub struct RawBody<Input>(PhantomData<Input>);
pub struct Header<Input>(PhantomData<Input>);
pub struct Cookie<Input>(PhantomData<Input>);
pub struct Multipart<Input>(PhantomData<Input>);
pub struct Auth<Input>(PhantomData<Input>);
pub struct Context<Input>(PhantomData<Input>);
pub trait InterpretInputsAlg<Compiler> {
type Inputs;
}
impl<Compiler> InterpretInputsAlg<Compiler> for () {
type Inputs = ();
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Direct<Input> {
type Inputs = Input;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Path<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Path<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Query<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Query<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Body<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Body<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Form<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Form<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for RawBody<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::RawBody<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Header<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Header<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Cookie<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Cookie<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Multipart<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Multipart<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Auth<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Auth<Input>;
}
impl<Compiler, Input> InterpretInputsAlg<Compiler> for Context<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Context<Input>;
}
macro_rules! interpret_inputs {
($($input:ident),+ $(,)?) => {
impl<Compiler, $($input),+> InterpretInputsAlg<Compiler> for ($($input,)+)
where
$($input: InterpretInputsAlg<Compiler>,)+
{
type Inputs = ($($input::Inputs,)+);
}
};
}
interpret_inputs!(I1);
interpret_inputs!(I1, I2);
interpret_inputs!(I1, I2, I3);
interpret_inputs!(I1, I2, I3, I4);
interpret_inputs!(I1, I2, I3, I4, I5);
interpret_inputs!(I1, I2, I3, I4, I5, I6);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15);
interpret_inputs!(I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15, I16);
impl HttpProgramBuilder {
pub fn routes(&self) -> RouteProgram<Empty> {
RouteProgram(Empty)
}
pub fn op<Handler>(&self, handler: Handler) -> Operation<Handler> {
Operation { handler, marker: PhantomData }
}
pub fn program<Program>(&self, program: Program) -> RouteProgram<Named<Program>> {
RouteProgram(Named(program))
}
}
pub type WithInput<Handler, Inputs, Args, Transform, Extractor, Arg> =
Operation<Handler, <Inputs as WithAlg>::With<Extractor>, <Args as WithAlg>::With<Arg>, Transform>;
pub type WithEndpoint<Program, Method, Handler, Inputs, Args, Transform> =
RouteProgram<Merge<Program, Endpoint<Method, Handler, Inputs, Args, Transform>>>;
macro_rules! output_methods {
($($method:ident => $kind:ident, $alg:ident, $selected:ident, $meaning:literal),+ $(,)?) => {
$(
#[doc = concat!("Marks the inferred handler result for ", $meaning, " interpretation.")]
pub fn $method(self) -> Operation<Handler, Inputs, Args, $kind> {
self.out()
}
)+
};
}
macro_rules! route_methods {
($($method:ident => $marker:ident, $label:literal),+ $(,)?) => {
impl<Program> RouteProgram<Program> {
$(
#[doc = concat!("Records a `", $label, "` selector and typed operation at an exact path.")]
pub fn $method<Handler, Inputs, Args, Transform>(
self,
path: &str,
operation: Operation<Handler, Inputs, Args, Transform>,
) -> WithEndpoint<Program, $marker, Handler, Inputs, Args, Transform> {
self.method(path, operation)
}
)+
}
impl<Handler, Inputs, Args, Transform> Operation<Handler, Inputs, Args, Transform> {
$(
#[doc = concat!("Declares this operation at an exact path, answered under `", $label, "`.")]
pub fn $method(self, path: &str) -> Endpoint<$marker, Handler, Inputs, Args, Transform> {
self.declare::<$marker>(path)
}
)+
}
};
}
impl<Handler, Inputs, Args, Transform> Operation<Handler, Inputs, Args, Transform> {
pub fn declare<Method>(self, path: &str) -> Endpoint<Method, Handler, Inputs, Args, Transform> {
Endpoint { path: RoutePath::parse(path), handler: self.handler, marker: PhantomData }
}
}
impl<Handler, Inputs, Args, Transform> Operation<Handler, Inputs, Args, Transform>
where
Inputs: WithAlg,
Args: WithAlg,
{
fn with_as<Input, Arg>(self) -> WithInput<Handler, Inputs, Args, Transform, Input, Arg> {
Operation { handler: self.handler, marker: PhantomData }
}
pub fn with<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Direct<Input>, Input> {
self.with_as::<Direct<Input>, Input>()
}
pub fn path<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Path<Input>, Input> {
self.with_as::<Path<Input>, Input>()
}
pub fn query<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Query<Input>, Input>
where
Input: NamedValuesAlg,
{
self.with_as::<Query<Input>, Input>()
}
pub fn body<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Body<Input>, Input> {
self.with_as::<Body<Input>, Input>()
}
pub fn form<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Form<Input>, Input> {
self.with_as::<Form<Input>, Input>()
}
pub fn raw_body<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, RawBody<Input>, Input> {
self.with_as::<RawBody<Input>, Input>()
}
pub fn in_header<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Header<Input>, Input>
where
Input: NamedValuesAlg,
{
self.with_as::<Header<Input>, Input>()
}
pub fn cookie<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Cookie<Input>, Input>
where
Input: NamedValuesAlg,
{
self.with_as::<Cookie<Input>, Input>()
}
pub fn multipart<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Multipart<Input>, Input> {
self.with_as::<Multipart<Input>, Input>()
}
pub fn auth<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Auth<Input>, Input>
where
Input: NamedValuesAlg,
{
self.with_as::<Auth<Input>, Input>()
}
pub fn context<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Context<Input>, Input> {
self.with_as::<Context<Input>, Input>()
}
pub fn out<NewTransform>(self) -> Operation<Handler, Inputs, Args, NewTransform> {
Operation { handler: self.handler, marker: PhantomData }
}
with_output_kinds!(output_methods);
pub fn status<const CODE: u16>(self) -> Operation<Handler, Inputs, Args, StatusOut<Transform, CODE>> {
self.out()
}
pub fn out_header<Name>(self) -> Operation<Handler, Inputs, Args, HeaderOut<Transform, Name>> {
self.out()
}
pub fn result(self) -> Operation<Handler, Inputs, Args, ResultOut<Transform>> {
self.out()
}
}
impl<Program> RouteProgram<Program> {
pub fn under(self, prefix: &str) -> Nest<Program> {
Nest { prefix: RoutePath::parse(prefix), program: self.0 }
}
pub fn merge<Other>(self, other: RouteProgram<Other>) -> RouteProgram<Merge<Program, Other>> {
RouteProgram(Merge { left: self.0, right: other.0 })
}
pub fn nest<Other>(self, prefix: &str, other: RouteProgram<Other>) -> RouteProgram<Merge<Program, Nest<Other>>> {
self.merge(RouteProgram(Nest { prefix: RoutePath::parse(prefix), program: other.0 }))
}
pub fn method<Method, Handler, Inputs, Args, Transform>(
self,
path: &str,
operation: Operation<Handler, Inputs, Args, Transform>,
) -> WithEndpoint<Program, Method, Handler, Inputs, Args, Transform> {
self.merge(RouteProgram(Endpoint {
path: RoutePath::parse(path),
handler: operation.handler,
marker: PhantomData,
}))
}
pub fn into_program(self) -> Program {
self.0
}
}
with_http_methods!(route_methods);
impl<Compiler> CompileRouteProgram<Compiler> for Empty
where
Compiler: RouteAlg,
{
type Route = Compiler::Route;
fn compile_route(self, compiler: &Compiler) -> Self::Route {
compiler.initial()
}
}
impl<Compiler, Left, Right, Route> CompileRouteProgram<Compiler> for Merge<Left, Right>
where
Compiler: RouteAlg<Route = Route>,
Left: CompileRouteProgram<Compiler, Route = Route>,
Right: CompileRouteProgram<Compiler, Route = Route>,
{
type Route = Route;
fn compile_route(self, compiler: &Compiler) -> Route {
compiler.coproduct(self.left.compile_route(compiler), self.right.compile_route(compiler))
}
}
impl<Compiler, Program, Route> CompileRouteProgram<Compiler> for Nest<Program>
where
Compiler: HttpRouteAlg<Route = Route>,
Program: CompileRouteProgram<Compiler, Route = Route>,
{
type Route = Route;
fn compile_route(self, compiler: &Compiler) -> Route {
compiler.precompose(compiler.http_prefix(&self.prefix), self.program.compile_route(compiler))
}
}
impl<Compiler, Program> CompileRouteProgram<Compiler> for Named<Program>
where
Program: HttpProgramAlg<Compiler>,
{
type Route = Program::Route;
fn compile_route(self, compiler: &Compiler) -> Self::Route {
self.0.compile_http(compiler)
}
}
impl<Compiler, Method, Handler, Inputs, Args, Transform, Handle> CompileRouteProgram<Compiler>
for Endpoint<Method, Handler, Inputs, Args, Transform>
where
Compiler: HttpApiAlg
+ HandlerContextAlg<Handler::Context, Handle = Handle>
+ HandlerEndpointAlg<Handle, Inputs::Inputs, Args, Transform, Handler::Output>,
Method: HttpMethodAlg,
Handler: OperationAlg + ApplyAlg<Handle, Args> + Send + Sync + 'static,
Inputs: InterpretInputsAlg<Compiler>,
{
type Route = Compiler::Route;
fn compile_route(self, compiler: &Compiler) -> Self::Route {
let selector = compiler.compose(compiler.http_method(Method::METHOD), compiler.http_path(&self.path));
let endpoint = compiler.finish_handler(self.handler);
compiler.precompose(selector, compiler.lift(endpoint))
}
}