~lthms/typed-urls

88801d6a51eba5734c80d4ade65b321a01552b81 — Thomas Letan 3 months ago 462c217
Remove the Routing trait in favor of the more logical TypedUrl
4 files changed, 61 insertions(+), 69 deletions(-)

M Cargo.toml
M src/lib.rs
M typed-urls-derive/src/lib.rs
M typed-urls-tera/src/lib.rs
M Cargo.toml => Cargo.toml +1 -0
@@ 6,6 6,7 @@ name = "typed-urls"
version = "0.1.0"
authors = ["Thomas Letan <lthms@soap.coffee>"]
edition = "2018"
license = "AGPL-3.0-or-later"

[dependencies]
serde_json = "*"

M src/lib.rs => src/lib.rs +9 -15
@@ 1,38 1,32 @@
use std::collections::HashMap;
use std::fmt::Display;
use std::slice::Iter;

use serde_json::Value;
pub use typed_urls_derive::TypedUrl;

pub trait Routing {
    type Url: Display;
pub trait TypedUrl: Sized {
    type Route: Display;

    fn enumerate() -> Iter<'static, Self>
    where
        Self : std::marker::Sized;

    fn to_string(&self) -> String;

    fn to_url(
        &self,
    fn routes() -> &'static [Self::Route];
    fn from_route(
        route : &Self::Route,
        args : &HashMap<String, Value>,
    ) -> Option<Self::Url>;
    ) -> Option<Self>;
}

pub trait Arg {
pub trait UrlPathParam {
    fn from_value(value : &Value) -> Option<Self>
    where
        Self : std::marker::Sized;
}

impl Arg for String {
impl UrlPathParam for String {
    fn from_value(value : &Value) -> Option<Self> {
        value.as_str().map(|x| x.to_owned())
    }
}

impl Arg for i32 {
impl UrlPathParam for i32 {
    fn from_value(value : &Value) -> Option<Self> {
        value.as_i64().map(|x| x as i32)
    }

M typed-urls-derive/src/lib.rs => typed-urls-derive/src/lib.rs +33 -43
@@ 101,7 101,7 @@ fn typed_fields_table(ast : &DataEnum) -> HashMap<Ident, Vec<(Type, Ident)>> {
    res
}

fn derive_ast_to_string(
fn derive_enum_display_impl(
    name : &Ident,
    ast : &DataEnum,
    fmts : &HashMap<Ident, LitStr>,


@@ 120,11 120,8 @@ fn derive_ast_to_string(
    }

    quote! {
        use std::fmt;
        use std::fmt::Display;

        impl Display for #name {
            fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
        impl std::fmt::Display for #name {
            fn fmt(&self, f : &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match self {
                    #(#match_bodies),*
                }


@@ 141,7 138,7 @@ fn ident_to_litstr(
    LitStr::new(&format!("{}{}{}", prefix, id, suffix), Span::call_site())
}

fn derive_route_to_string(
fn derive_route_display_impl(
    ast : &DataEnum,
    fmts : &HashMap<Ident, LitStr>,
    fields : &HashMap<Ident, Vec<Ident>>,


@@ 159,14 156,16 @@ fn derive_route_to_string(
            .collect::<Vec<LitStr>>();

        match_bodies.push(quote! {
            Route::#ident => format!(#fmt, #(#flds),*)
            Route::#ident => write!(f, #fmt, #(#flds),*)
        });
    }

    quote! {
        fn to_string(&self) -> String {
            match self {
                #(#match_bodies),*
        impl std::fmt::Display for Route {
            fn fmt(&self, f : &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match self {
                    #(#match_bodies),*
                }
            }
        }
    }


@@ 181,27 180,22 @@ fn derive_typed_url_boilerplate(ast : &DeriveInput) -> Quote {
    let fmts = format_table(&enum_ast);

    let route_enum = derive_route_enum(enum_ast);

    let route_routes_impl = derive_route_routes_impl(
        enum_name,
        enum_ast,
        &fmts,
        &typed_field,
        &fields,
    );

    let route_display = derive_route_display_impl(enum_ast, &fmts, &fields);
    let routes = derive_routes_array(enum_ast);

    let enum_to_string =
        derive_ast_to_string(enum_name, &enum_ast, &fmts, &fields);
    let enum_typed_url_impl =
        derive_enum_typed_url_impl(enum_name, enum_ast, &typed_field, &fields);

    quote! {
        use typed_urls::Routing;
    let enum_display =
        derive_enum_display_impl(enum_name, &enum_ast, &fmts, &fields);

        #enum_to_string
    quote! {
        #route_enum
        #route_display
        #routes
        #route_routes_impl

        #enum_display
        #enum_typed_url_impl
    }
}



@@ 230,7 224,7 @@ fn derive_route_enum(enum_ast : &DataEnum) -> Quote {
    }
}

fn derive_route_to_url(
fn derive_enum_from_route(
    name : &Ident,
    enum_ast : &DataEnum,
    tf : &HashMap<Ident, Vec<(Type, Ident)>>,


@@ 249,7 243,7 @@ fn derive_route_to_url(
            let fname = ident_to_litstr("", field, "");

            arg_fetch.push(quote! {
                let #field = args.get(#fname).and_then(<#ty as Arg>::from_value)?;
                let #field = args.get(#fname).and_then(<#ty as UrlPathParam>::from_value)?;
            })
        }



@@ 262,38 256,34 @@ fn derive_route_to_url(
    }

    quote! {
        fn to_url(
            &self,
        fn from_route(
            route : &Self::Route,
            args : &std::collections::HashMap<String, serde_json::Value>,
        ) -> Option<#name> {
            use typed_urls::Arg;
            match self {
            use typed_urls::UrlPathParam;
            match route {
                #(#match_bodies),*
            }
        }
    }
}

fn derive_route_routes_impl(
fn derive_enum_typed_url_impl(
    name : &Ident,
    enum_ast : &DataEnum,
    fmts : &HashMap<Ident, LitStr>,
    tf : &HashMap<Ident, Vec<(Type, Ident)>>,
    uf : &HashMap<Ident, Vec<Ident>>,
) -> Quote {
    let to_string_impl = derive_route_to_string(enum_ast, fmts, uf);
    let to_url_impl = derive_route_to_url(name, enum_ast, tf, uf);
    let from_route_impl = derive_enum_from_route(name, enum_ast, tf, uf);

    quote! {
        impl Routing for Route {
            type Url = #name;

            #to_string_impl
        impl typed_urls::TypedUrl for #name {
            type Route = Route;

            #to_url_impl
            #from_route_impl

            fn enumerate() -> std::slice::Iter<'static, Self> {
                ROUTES.iter()
            fn routes() -> &'static [Self::Route] {
                ROUTES
            }
        }
    }

M typed-urls-tera/src/lib.rs => typed-urls-tera/src/lib.rs +18 -11
@@ 1,32 1,39 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::{Debug, Display};

use serde_json::Value;
use tera::{Tera, Error, Function};
use typed_urls::Routing;
use tera::{Error, Function, Tera};
use typed_urls::TypedUrl;

struct TeraFunction<R>(R);
struct TeraFunction<U>(U::Route)
where
    U : TypedUrl;

impl<R> Function for TeraFunction<R>
impl<U> Function for TeraFunction<U>
where
    R : Routing + Sync + Send,
    U : TypedUrl + Display,
    U::Route : Sync + Send,
{
    fn call(
        &self,
        args : &HashMap<String, Value>,
    ) -> Result<Value, Error> {
        let res = self.0.to_url(args).ok_or(Error::msg("missing keys"))?;
        let res =
            U::from_route(&self.0, args).ok_or(Error::msg("missing keys"))?;

        Ok(Value::String(res.to_string()))
    }
}

pub fn register_typed_url<R>(tera : &mut Tera) -> ()
where R : Routing + Sync + Send + Debug + Clone + 'static {
    for route in R::enumerate() {
pub fn register_typed_url<U>(tera : &mut Tera) -> ()
where
    U : TypedUrl + Display + 'static,
    U::Route : Sync + Send + Debug + Clone + Display + 'static,
{
    for route in U::routes() {
        tera.register_function(
            &format!("{:?}Url", route),
            TeraFunction(route.clone()),
            TeraFunction::<U>(route.clone()),
        )
    }
}