hsnet-rpc-macro

Crates.iohsnet-rpc-macro
lib.rshsnet-rpc-macro
version0.1.0
created_at2025-10-14 08:47:20.573648+00
updated_at2025-10-14 08:47:20.573648+00
descriptionProcedural macros for hsnet-rpc framework
homepage
repository
max_upload_size
id1881889
size20,685
lee (loyalpartner)

documentation

README

hsnet-rpc-macro

Proc macro 实现,为 hsnet-rpc 提供 #[rpc(server)] 宏。

目的

消除重复的 handler 注册代码。

手动注册的问题

// 需要写两遍:trait 定义 + 手动注册
trait MyService {
    async fn start(&self, config: Config) -> Result<String, RpcError>;
    async fn stop(&self, force: bool) -> Result<String, RpcError>;
}

// 手动注册(容易出错)
let server = RpcServer::new()
    .handle("start", {
        let service = service.clone();
        move |config: Config| {
            let service = service.clone();
            async move { service.start(config).await }
        }
    })
    .handle("stop", {
        let service = service.clone();
        move |force: bool| {
            let service = service.clone();
            async move { service.stop(force).await }
        }
    });

问题:

  • 注册代码重复且容易出错
  • 方法名字符串("start")容易拼写错误
  • 参数类型重复声明
  • 闭包嵌套深,难以阅读

使用宏后

#[rpc(server)]
trait MyService {
    #[method(name = "start")]
    async fn start(&self, config: Config) -> Result<String, RpcError>;

    #[method(name = "stop")]
    async fn stop(&self, force: bool) -> Result<String, RpcError>;
}

// 自动生成注册代码
let server = service.into_rpc();

使用方法

1. 标记 trait

use hsnet_rpc::{rpc, RpcError};
use async_trait::async_trait;

#[async_trait]
#[rpc(server)]
pub trait ControlRpc {
    #[method(name = "start")]
    async fn start(&self, config: Config) -> Result<String, RpcError>;

    #[method(name = "stop")]
    async fn stop(&self, force: bool) -> Result<String, RpcError>;

    // 无参数方法
    #[method(name = "status")]
    async fn status(&self) -> Result<String, RpcError>;
}

2. 实现 trait

pub struct ControlService { /* ... */ }

#[async_trait]
impl ControlRpc for ControlService {
    async fn start(&self, config: Config) -> Result<String, RpcError> {
        // 业务逻辑
        Ok("started".to_string())
    }

    async fn stop(&self, force: bool) -> Result<String, RpcError> {
        Ok("stopped".to_string())
    }

    async fn status(&self) -> Result<String, RpcError> {
        Ok("running".to_string())
    }
}

3. 使用生成的 into_rpc() 方法

use hsnet_rpc::IntoRpcServer; // 宏生成的 trait

let service = Arc::new(ControlService::new());
let server = service.into_rpc();  // 自动注册所有方法

server.run_unix("/tmp/control.sock").await?;

宏展开后的代码

输入:

#[rpc(server)]
pub trait ControlRpc {
    #[method(name = "start")]
    async fn start(&self, config: Config) -> Result<String, RpcError>;

    #[method(name = "stop")]
    async fn stop(&self, force: bool) -> Result<String, RpcError>;
}

展开为:

// 1. 保留原始 trait 定义(去掉 #[rpc] 和 #[method] 属性)
pub trait ControlRpc {
    async fn start(&self, config: Config) -> Result<String, RpcError>;
    async fn stop(&self, force: bool) -> Result<String, RpcError>;
}

// 2. 生成扩展 trait
pub trait IntoRpcServer {
    fn into_rpc(self: std::sync::Arc<Self>) -> hsnet_rpc::RpcServer;
}

// 3. 为所有实现了 ControlRpc 的类型实现 IntoRpcServer
impl<T: ControlRpc + Send + Sync + 'static> IntoRpcServer for T {
    fn into_rpc(self: std::sync::Arc<Self>) -> hsnet_rpc::RpcServer {
        let service = self;
        let mut server = hsnet_rpc::RpcServer::new();

        // 注册 "start" 方法(单参数)
        server = server.handle("start", {
            let service = service.clone();
            move |config: Config| {
                let service = service.clone();
                async move { service.start(config).await }
            }
        });

        // 注册 "stop" 方法(单参数)
        server = server.handle("stop", {
            let service = service.clone();
            move |force: bool| {
                let service = service.clone();
                async move { service.stop(force).await }
            }
        });

        server
    }
}

支持的特性

1. 条件编译(#[cfg]

#[rpc(server)]
pub trait ControlRpc {
    // 所有平台都有
    #[method(name = "start")]
    async fn start(&self, config: Config) -> Result<String, RpcError>;

    // 仅 Windows 平台
    #[cfg(windows)]
    #[method(name = "restart")]
    async fn restart(&self) -> Result<String, RpcError>;

    // 仅 Unix 平台
    #[cfg(unix)]
    #[method(name = "reload_config")]
    async fn reload_config(&self) -> Result<String, RpcError>;
}

宏会自动处理 #[cfg] 属性,生成条件编译的注册代码:

impl<T: ControlRpc + Send + Sync + 'static> IntoRpcServer for T {
    fn into_rpc(self: Arc<Self>) -> RpcServer {
        let mut server = RpcServer::new();

        server = server.handle("start", /* ... */);

        #[cfg(windows)]
        {
            server = server.handle("restart", /* ... */);
        }

        #[cfg(unix)]
        {
            server = server.handle("reload_config", /* ... */);
        }

        server
    }
}

2. 无参数方法

#[method(name = "status")]
async fn status(&self) -> Result<String, RpcError>;

生成:

server = server.handle("status", {
    let service = service.clone();
    move |_: ()| {
        let service = service.clone();
        async move { service.status().await }
    }
});

客户端调用:

let status: String = client.call("status", ()).await?;

3. 单参数方法

#[method(name = "start")]
async fn start(&self, config: Config) -> Result<String, RpcError>;

生成:

server = server.handle("start", {
    let service = service.clone();
    move |config: Config| {
        let service = service.clone();
        async move { service.start(config).await }
    }
});

限制

1. 仅支持单参数方法

// ✅ 支持
#[method(name = "method1")]
async fn method1(&self) -> Result<String, RpcError>;

#[method(name = "method2")]
async fn method2(&self, param: Config) -> Result<String, RpcError>;

// ❌ 不支持(会生成编译错误)
#[method(name = "method3")]
async fn method3(&self, p1: String, p2: u32) -> Result<String, RpcError>;

原因: 保持简单性。多参数可以用结构体包装:

#[derive(Serialize, Deserialize)]
struct Method3Params {
    p1: String,
    p2: u32,
}

#[method(name = "method3")]
async fn method3(&self, params: Method3Params) -> Result<String, RpcError>;

2. 参数必须是值类型

// ✅ 正确
#[method(name = "start")]
async fn start(&self, config: Config) -> Result<String, RpcError>;

// ❌ 错误(编译失败)
#[method(name = "start")]
async fn start(&self, config: &Config) -> Result<String, RpcError>;

原因: 生成的闭包跨任务边界,需要 'static 生命周期。

解决方案:

  • 小对象直接传值(如 bool, u32
  • 大对象使用 StringVecArc 等拥有所有权的类型
  • trait 方法内部可以转换为引用使用

3. 返回值必须是 Result<T, RpcError>

// ✅ 正确
#[method(name = "start")]
async fn start(&self, config: Config) -> Result<String, RpcError>;

// ❌ 错误(类型不匹配)
#[method(name = "start")]
async fn start(&self, config: Config) -> String;

原因: RPC 调用需要统一的错误处理。

4. 必须和 #[async_trait] 配合使用

use async_trait::async_trait;

#[async_trait]  // 必须
#[rpc(server)]
pub trait ControlRpc {
    // async fn 在 trait 中需要 async_trait
    #[method(name = "start")]
    async fn start(&self, config: Config) -> Result<String, RpcError>;
}

原因: Rust trait 中的 async fn 需要 async_trait 宏支持。

实现细节

宏处理流程

  1. 解析 trait 定义

    • 使用 syn::parse_macro_input 解析 TokenStream
    • 提取 trait 名称、可见性、泛型等
  2. 提取 RPC 方法

    • 遍历 trait items,查找带 #[method] 属性的方法
    • 解析 #[method(name = "xxx")] 提取方法名
    • 提取参数类型(跳过 &self
    • 提取 #[cfg] 属性用于条件编译
  3. 生成 handler 注册代码

    • 为每个方法生成闭包:
      • 无参数:move |_: ()| { ... }
      • 单参数:move |param: T| { ... }
      • 多参数:生成编译错误
  4. 生成最终代码

    • 保留原始 trait(去掉宏属性)
    • 生成 IntoRpcServer trait
    • 生成 impl<T: OriginalTrait> IntoRpcServer for T

关键代码片段

// 解析方法名
fn parse_method_name(attr: &Attribute) -> String {
    if let Meta::List(meta_list) = &attr.meta {
        let tokens_str = meta_list.tokens.to_string();
        // 简单解析 name = "xxx"
        if let Some(start) = tokens_str.find('"') {
            if let Some(end) = tokens_str[start + 1..].find('"') {
                return tokens_str[start + 1..start + 1 + end].to_string();
            }
        }
    }
    String::new()
}

// 生成 handler 注册代码
if method.params.is_empty() {
    // 无参数
    quote! {
        server = server.handle(#rpc_name, {
            let service = service.clone();
            move |_: ()| {
                let service = service.clone();
                async move { service.#method_name().await }
            }
        });
    }
} else if method.params.len() == 1 {
    // 单参数
    let param_name = &method.params[0].0;
    let param_type = &method.params[0].1;
    quote! {
        server = server.handle(#rpc_name, {
            let service = service.clone();
            move |#param_name: #param_type| {
                let service = service.clone();
                async move { service.#method_name(#param_name).await }
            }
        });
    }
}

调试宏展开

使用 cargo expand 查看宏展开后的代码:

# 安装 cargo-expand
cargo install cargo-expand

# 展开特定文件的宏
cargo expand --package client --lib ctrl_ipc::service

# 或者展开整个 crate
cargo expand --package client

实际使用示例

参考 client/src/ctrl_ipc/service.rs,这是生产环境中的完整示例:

#[async_trait]
#[rpc(server)]
pub trait ControlRpc {
    // 通用方法
    #[method(name = "wg_start")]
    async fn wg_start(&self, config: WgConfig) -> Result<String, RpcError>;

    #[method(name = "wg_stop")]
    async fn wg_stop(&self, force: bool) -> Result<String, RpcError>;

    // Windows 专用方法
    #[cfg(windows)]
    #[method(name = "restart")]
    async fn restart(&self) -> Result<String, RpcError>;

    #[cfg(windows)]
    #[method(name = "cmd_svc")]
    async fn cmd_svc(&self, msg: IpcCommand) -> Result<String, RpcError>;

    // ... 共 18 个方法
}

// 实现
pub struct ControlService { /* ... */ }

#[async_trait]
impl ControlRpc for ControlService {
    async fn wg_start(&self, config: WgConfig) -> Result<String, RpcError> {
        // 业务逻辑
    }
    // ...
}

// 使用
let service = ControlService::new(...);
let server = service.into_rpc();
server.run_pipe(r"\\.\pipe\hsnetv2_ctrl_ipc").await?;

技术栈

  • syn: 解析 Rust 语法树
  • quote: 生成 Rust 代码
  • proc-macro2: 过程宏基础设施

相关文档

License

MIT

Commit count: 0

cargo fmt