Skip to content

Instantly share code, notes, and snippets.

@jkelleyrtp
Last active July 12, 2022 19:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jkelleyrtp/148d92e069ffbc6914e1311a68777f9f to your computer and use it in GitHub Desktop.
Save jkelleyrtp/148d92e069ffbc6914e1311a68777f9f to your computer and use it in GitHub Desktop.
Rust endpoint polymorphism
/*
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