Rust 宏生成端点路径

问题描述 投票:0回答:1

我正在尝试创建一个 Rust 宏来生成像

"/api/v2/stats/<token>"
这样的端点。

我认为像扭曲路径那样做会很酷:

warp::path!("sum" / u32 / u32)
。 但在 warp 中,它们不需要支持带点的标记树,即表达式,并检索它们的值......

到目前为止我得到的是:

macro_rules! path {
    () => {};
    ($next:tt $($tail:tt)*) => {{
        println!(stringify!($next));
        path!($($tail)*);
    }};
}

fn main() {
    struct Data {
        event: String,
        token: String,
    }
    let data = Data {
        event: String::from("stats"),
        token: String::from("a1b2c3d4"),
    };
    path!("/api/v2" / data.event / data.token)
}

游乐场:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=babf87265cc6060fc1695019e30e38bf

这显示了宏所看到的内容:

"/api/v2"
/
data
.
event
/
data
.
token

我知道标记树稍后可以被重新解释为表达式,因此应该有一种方法可以将 tt 保留在尾部,将斜杠与“其他任何内容”分开,并将它们作为表达式来检索它们的值,但我不知道如何实现。 ..
我怎样才能让它返回字符串

"/api/v2/stats/a1b2c3d4"

编辑:根据要求,这里有更多输入和预期输出的示例:

struct Conf<'a> {env: &'a str};
let conf = Conf { env: "dev" };
let subsystem = "stats";

path!("/"); // root: "/"
path!("/api/v1" / data.event / "results"); // "/api/v1/stats/results"
path!("/api/v2/errors" / conf.env / subsystem); // "/api/v2/errors/dev/stats"

编辑:我有点用表达式来做到这一点,这不是那么具有表现力,更像是一种解决方法,但它有效:

macro_rules! path {
    ($($path:expr),+) => {{
        let mut s = [$($path),+].into_iter()
            .flat_map(|p| [p, "/"])
            .collect::<String>();
        s.pop();
        s
    }}
}

限制:它只接受逗号,路径部分必须是 &str 所以我必须手动引用它们,最重要的是,这可以更好地表示为一个函数,但它是一个开始的东西:

let result_url = path!("/api/v2", &data.event, &data.token);

谢谢!

rust rust-macros
1个回答
2
投票

您可以使用 tt muncher 来实现此目的:

macro_rules! path {
    (@munch / ) => {
        String::from("/")
    };
    (@munch / $part:literal $(/)* ) => {
        format!("/{}", $part)
    };
    (@munch / $part:literal / $($tail:tt)* ) => {
        format!("/{}{}", $part, path!(@munch / $($tail)*))
    };
    (@munch / $($parts:ident).+ $(/)* ) => {
        format!("/{}", & $($parts).+)
    };
    (@munch / $($parts:ident).+ / $($tail:tt)* ) => {
        format!("/{}{}", & $($parts).+, path!(@munch / $($tail)*))
    };
    (/ $($input:tt)*) => {
        path!(@munch / $($input)*)
    };
}

游乐场

目前这会产生嵌套的

format!
调用。为了避免这种情况,您可能还需要使用累加器。我对这种东西很感兴趣,所以我正在开发一个带有累加器的版本。

编辑:这是累加器版本

macro_rules! path {
    (/) => {
        String::from("/")
    };
    (/ $($input:tt)*) => {
        path!(@munch { / $($input)* } => ())
    };

    (@munch { / $part:literal $(/)* } => ($($accum:expr),*)) => {
        path!(@done ($( $accum, )* $part))
    };
    (@munch { / $part:literal / $($tail:tt)* } => ($($accum:expr),*)) => {
        path!(@munch { / $($tail)* } => ($( $accum, )* $part ))
    };
    
    (@munch { / $($parts:ident).+ $(/)* } => ($($accum:expr),*)) => {
        path!(@done ($( $accum, )* & $($parts).+ ))
    };
    (@munch { / $($parts:ident).+ / $($tail:tt)* } => ($($accum:expr),*)) => {
        path!(@munch { / $($tail)* } => ($( $accum, )* & $($parts).+ ))
    };

    (@replace_expr $_t:tt => $sub:expr) => { $sub };
    (@done ($($accum:expr),*)) => {
        format!(
            concat!($( path!(@replace_expr ($accum) => "/{}"), )*),
            $( $accum, )*
        )
    };
}

游乐场

Edit2:根据您的要求,另一个版本使用两个累加器来支持前导文字

macro_rules! path {
    (/) => {
        String::from("/")
    };
    (/ $($input:tt)*) => {
        path!(@munch { / $($input)* } -> () : ())
    };
    ($part:literal $(/)*) => {
        String::from($part)
    };
    ($part:literal $($input:tt)*) => {
        path!(@munch { $($input)* } -> ("{}") : ($part))
    };

    (@munch { / $part:literal $(/)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        path!(@done ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* $part))
    };
    (@munch { / $part:literal / $($tail:tt)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        path!(@munch { / $($tail)* } -> ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* $part ))
    };
    
    (@munch { / $($parts:ident).+ $(/)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        path!(@done ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* & $($parts).+ ))
    };
    (@munch { / $($parts:ident).+ / $($tail:tt)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        path!(@munch { / $($tail)* } -> ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* & $($parts).+ ))
    };

    (@done ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        format!(
            concat!($( $fmt_accum, )*),
            $( $args_accum, )*
        )
    };
}

游乐场

© www.soinside.com 2019 - 2024. All rights reserved.