Last active
July 12, 2022 19:23
-
-
Save jkelleyrtp/148d92e069ffbc6914e1311a68777f9f to your computer and use it in GitHub Desktop.
Rust endpoint polymorphism
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This code shows how you would make a Rust API polymorphic over various functions. | |
It uses a technique to differentiate impls of a trait using a ZST marker. | |
This particular example might be too complex for the intended crate, hence its extraction into a gist. | |
*/ | |
use std::marker::PhantomData; | |
use tokamak::{Request, Response}; | |
fn main() { | |
let mut app = App {}; | |
app.at("asd") | |
.get(|_| Response::ok()) | |
.extract(|req| Ok(10)) | |
.extract(|req| Ok("asd")) | |
.get(|req, num, name| { | |
// | |
Response::ok() | |
}) | |
.post(|req, num, name| { | |
// | |
Response::ok() | |
}); | |
take_extractor(req1); | |
take_extractor(req2); | |
take_extractor(req3); | |
take_extractor(req4); | |
} | |
struct App {} | |
impl App { | |
fn at<'a>(&'a mut self, f: &str) -> Route<'a, Extract0> { | |
todo!() | |
} | |
} | |
// We can be polymorphic over function arguments by suppling a ZST to differentiate each trait impl | |
struct Extract0; | |
struct Extract1; | |
struct Extract2; | |
struct Extract3; | |
// Our route encodes the pre-extracted values from the request | |
struct Route<'a, E, A = (), B = (), C = (), D = ()> { | |
app: &'a mut App, | |
_p: PhantomData<(A, B, C, D, E)>, | |
} | |
// For each unique extraction ZST we differentiate the extract method | |
impl<'a> Route<'a, Extract0> { | |
fn extract<A>(mut self, f: impl Fn(Request) -> tokamak::Result<A>) -> Route<'a, Extract1, A> { | |
todo!() | |
} | |
// impl Endpoint is configured for different extraction levels | |
fn get(mut self, f: impl Endpoint<Extract0>) -> Self { | |
todo!() | |
} | |
fn post(mut self, f: impl Fn(Request) -> Response) -> Self { | |
todo!() | |
} | |
} | |
impl<'a, A> Route<'a, Extract1, A> { | |
fn extract<B>( | |
mut self, | |
f: impl Fn(Request) -> tokamak::Result<B>, | |
) -> Route<'a, Extract2, A, B> { | |
todo!() | |
} | |
fn get(mut self, f: impl Endpoint<A>) -> Self { | |
todo!() | |
} | |
fn post(mut self, f: impl Endpoint<A>) -> Self { | |
todo!() | |
} | |
} | |
impl<'a, A, B> Route<'a, Extract2, A, B> { | |
fn extract<C>( | |
mut self, | |
f: impl Fn(Request) -> tokamak::Result<C>, | |
) -> Route<'a, Extract3, A, B, C> { | |
todo!() | |
} | |
fn get(mut self, f: impl Endpoint<Extract2, A, B>) -> Self { | |
todo!() | |
} | |
fn post(mut self, f: impl Endpoint<Extract2, A, B>) -> Self { | |
todo!() | |
} | |
} | |
fn req1(r: Request) -> Response { | |
todo!() | |
} | |
fn req2(r: Request, t: Admin) -> Response { | |
todo!() | |
} | |
fn req3(r: Request, t: User) -> Response { | |
todo!() | |
} | |
fn req4(r: Request, t: User, b: Admin) -> Response { | |
todo!() | |
} | |
fn req5(r: Request, t: User, b: Admin, len: MaxContentLength<100>) -> Response { | |
todo!() | |
} | |
// Structs can also implement FromRequest for auto extraction | |
struct Admin {} | |
impl FromRequest for Admin {} | |
struct User {} | |
impl FromRequest for User {} | |
struct MaxContentLength<const N: usize>(usize); | |
impl<const N: usize> FromRequest for MaxContentLength<N> {} | |
fn take_extractor<A, B, C, D, E, F, G>(f: impl Endpoint<Extract0, A, B, C, D, E, F, G>) {} | |
trait Endpoint<Ex, A = (), B = (), C = (), D = (), E = (), F = (), G = ()> {} | |
impl<F> Endpoint<Extract0> for F where F: Fn(Request) -> Response {} | |
trait FromRequest {} | |
impl<F, A: FromRequest> Endpoint<Extract0, A> for F where F: Fn(Request, A) -> Response {} | |
impl<F, A: FromRequest, B: FromRequest> Endpoint<Extract0, A, B> for F where | |
F: Fn(Request, A, B) -> Response | |
{ | |
} | |
impl<F, A: FromRequest, B: FromRequest, C: FromRequest> Endpoint<Extract0, A, B, C> for F where | |
F: Fn(Request, A, B, C) -> Response | |
{ | |
} | |
impl<F, A, B> Endpoint<Extract2, A, B> for F where F: Fn(Request, A, B) -> Response {} | |
// A complete implementation would cover cases o->n with no extractors | |
// And then cover o-n with each level pre-extracted. | |
// all the ones with zero extractors | |
// A, B, C | |
// then another set with increasing extractors | |
// _A, B, C | |
// _A, _B, C |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment