我正在尝试创建一个 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)
}
这显示了宏所看到的内容:
"/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);
谢谢!
您可以使用 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, )*
)
};
}