You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现类Warp的Rust路径宏以生成API端点路径

实现支持表达式的Rust路径生成宏

要实现类似warp::path!、支持/分隔且能正确处理带点表达式的路径宏,核心是利用Rust宏的表达式捕获器(expr)替代单个token捕获,避免拆分带点的字段访问或方法调用。以下是完整实现:

宏定义

#[macro_export]
macro_rules! path {
    ($($segments:tt)+) => {
        $crate::path_internal!($($segments)+)
    };
}

#[macro_export]
macro_rules! path_internal {
    // 递归终止:无剩余段,返回空字符串
    () => {
        String::new()
    };

    // 处理「静态段 + / + 剩余路径」
    ($static:literal / $($rest:tt)*) => {
        let mut path = String::from($static);
        path.push('/');
        path += &$crate::path_internal!($($rest)*);
        path
    };

    // 处理「动态表达式段 + / + 剩余路径」
    (<$expr:expr> / $($rest:tt)*) => {
        let mut path = format!("{}", $expr);
        path.push('/');
        path += &$crate::path_internal!($($rest)*);
        path
    };

    // 最后一段是静态段
    ($static:literal) => {
        String::from($static)
    };

    // 最后一段是动态表达式段
    (<$expr:expr>) => {
        format!("{}", $expr)
    };
}

关键设计点

  1. 递归解析路径段:通过path_internal!辅助宏递归处理每个/分隔的路径片段,逐个拼接结果。
  2. 表达式捕获器:动态段使用<$expr:expr>捕获完整的Rust表达式,能正确识别带点的字段访问(如data.event)、方法调用(如user.get_id())等,不会像tt捕获器那样拆分单个token。
  3. 通用字符串转换:用format!("{}", $expr)替代to_string(),支持所有实现Display trait的类型(包括整数、浮点数、自定义结构体等)。

使用示例

use std::fmt;

fn main() {
    // 带点字段访问的场景
    let data = ApiData { event: "user_login".to_string() };
    let stats_path = path!("/api/v2/stats/" <data.event>);
    println!("{}", stats_path); // 输出:/api/v2/stats/user_login

    // 混合静态段与多动态段
    let user_id = 1001;
    let post_slug = "rust-macro-guide";
    let post_path = path!("/users/" <user_id> "/posts/" <post_slug>);
    println!("{}", post_path); // 输出:/users/1001/posts/rust-macro-guide

    // 自定义结构体(需实现Display)
    let config = AppConfig { version: "v3".to_string() };
    let health_path = path!("/health/" <config>);
    println!("{}", health_path); // 输出:/health/AppConfig(version: v3)
}

#[derive(Debug)]
struct ApiData {
    event: String,
}

#[derive(Debug)]
struct AppConfig {
    version: String,
}

impl fmt::Display for AppConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "AppConfig(version: {})", self.version)
    }
}

边界情况处理

  • 路径开头的/:直接作为静态段处理,如path!("/api")生成"/api"。
  • 路径结尾的/:如path!("/api/")会生成"/api/",符合直观预期。
  • 纯动态路径:path!(<user.id>)直接返回user.id的字符串表示。

内容的提问来源于stack exchange,提问作者rsalmei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 03:05:22