use crate::{
FileOut, HandlerEndpointAlg, HttpApiAlg, HttpInputAlg, HttpProgramAlg, HttpRouteAlg, JsonOut, RouteAlg, 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: String,
program: Program,
}
#[derive(Debug)]
pub struct Named<Program>(Program);
#[derive(Debug)]
pub struct Get;
#[derive(Debug)]
pub struct Post;
#[derive(Debug)]
pub struct Endpoint<Method, Handler, Inputs, Args, Transform> {
path: String,
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 Header<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 Header<Input>
where
Compiler: HttpInputAlg,
{
type Inputs = Compiler::Header<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);
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>>>;
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> {
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 header<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Header<Input>, Input> {
self.with_as::<Header<Input>, Input>()
}
pub fn auth<Input>(self) -> WithInput<Handler, Inputs, Args, Transform, Auth<Input>, Input> {
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 }
}
pub fn json(self) -> Operation<Handler, Inputs, Args, JsonOut> {
self.out()
}
pub fn file(self) -> Operation<Handler, Inputs, Args, FileOut> {
self.out()
}
}
impl<Program> RouteProgram<Program> {
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: prefix.into(), program: other.0 }))
}
pub fn get<Handler, Inputs, Args, Transform>(
self,
path: &str,
operation: Operation<Handler, Inputs, Args, Transform>,
) -> WithEndpoint<Program, Get, Handler, Inputs, Args, Transform> {
self.merge(RouteProgram(Endpoint { path: path.into(), handler: operation.handler, marker: PhantomData }))
}
pub fn post<Handler, Inputs, Args, Transform>(
self,
path: &str,
operation: Operation<Handler, Inputs, Args, Transform>,
) -> WithEndpoint<Program, Post, Handler, Inputs, Args, Transform> {
self.merge(RouteProgram(Endpoint { path: path.into(), handler: operation.handler, marker: PhantomData }))
}
pub fn into_program(self) -> Program {
self.0
}
}
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)
}
}
trait HttpMethodAlg<Compiler> {
fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
where
Compiler: HttpApiAlg;
}
impl<Compiler> HttpMethodAlg<Compiler> for Get {
fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
where
Compiler: HttpApiAlg,
{
compiler.http_get()
}
}
impl<Compiler> HttpMethodAlg<Compiler> for Post {
fn selector(compiler: &Compiler) -> <Compiler as RouteAlg>::Selector
where
Compiler: HttpApiAlg,
{
compiler.http_post()
}
}
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<Compiler>,
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(Method::selector(compiler), compiler.http_path(&self.path));
let endpoint = compiler.finish_handler(self.handler);
compiler.precompose(selector, compiler.lift(endpoint))
}
}