Skip to content

Commit 2219029

Browse files
authored
feat: add namespace_separator option for RPC methods (#1544)
* feat: add namespace_separator option for RPC methods * test: add tests for namespace_separator formatting with dot and slash
1 parent 042d8fd commit 2219029

File tree

3 files changed

+80
-7
lines changed

3 files changed

+80
-7
lines changed

proc-macros/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub(crate) mod visitor;
8080
/// that generated for it by the macro.
8181
///
8282
/// ```ignore
83-
/// #[rpc(client, server, namespace = "foo")]
83+
/// #[rpc(client, server, namespace = "foo", namespace_separator = ".")]
8484
/// pub trait Rpc {
8585
/// #[method(name = "foo")]
8686
/// async fn async_method(&self, param_a: u8, param_b: String) -> u16;
@@ -147,6 +147,8 @@ pub(crate) mod visitor;
147147
/// implementation's methods conveniently.
148148
/// - `namespace`: add a prefix to all the methods and subscriptions in this RPC. For example, with namespace `foo` and
149149
/// method `spam`, the resulting method name will be `foo_spam`.
150+
/// - `namespace_separator`: customize the separator used between namespace and method name. Defaults to `_`.
151+
/// For example, `namespace = "foo", namespace_separator = "."` results in method names like `foo.bar` instead of `foo_bar`.
150152
/// - `server_bounds`: replace *all* auto-generated trait bounds with the user-defined ones for the server
151153
/// implementation.
152154
/// - `client_bounds`: replace *all* auto-generated trait bounds with the user-defined ones for the client

proc-macros/src/rpc_macro.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ pub struct RpcDescription {
262262
pub(crate) needs_client: bool,
263263
/// Optional prefix for RPC namespace.
264264
pub(crate) namespace: Option<String>,
265+
/// Optional separator between namespace and method name. Defaults to `_`.
266+
pub(crate) namespace_separator: Option<String>,
265267
/// Trait definition in which all the attributes were stripped.
266268
pub(crate) trait_def: syn::ItemTrait,
267269
/// List of RPC methods defined in the trait.
@@ -276,12 +278,20 @@ pub struct RpcDescription {
276278

277279
impl RpcDescription {
278280
pub fn from_item(attr: Attribute, mut item: syn::ItemTrait) -> syn::Result<Self> {
279-
let [client, server, namespace, client_bounds, server_bounds] =
280-
AttributeMeta::parse(attr)?.retain(["client", "server", "namespace", "client_bounds", "server_bounds"])?;
281+
let [client, server, namespace, namespace_separator, client_bounds, server_bounds] =
282+
AttributeMeta::parse(attr)?.retain([
283+
"client",
284+
"server",
285+
"namespace",
286+
"namespace_separator",
287+
"client_bounds",
288+
"server_bounds",
289+
])?;
281290

282291
let needs_server = optional(server, Argument::flag)?.is_some();
283292
let needs_client = optional(client, Argument::flag)?.is_some();
284293
let namespace = optional(namespace, Argument::string)?;
294+
let namespace_separator = optional(namespace_separator, Argument::string)?;
285295
let client_bounds = optional(client_bounds, Argument::group)?;
286296
let server_bounds = optional(server_bounds, Argument::group)?;
287297
if !needs_server && !needs_client {
@@ -368,6 +378,7 @@ impl RpcDescription {
368378
needs_server,
369379
needs_client,
370380
namespace,
381+
namespace_separator,
371382
trait_def: item,
372383
methods,
373384
subscriptions,
@@ -400,12 +411,18 @@ impl RpcDescription {
400411
quote! { #jsonrpsee::#item }
401412
}
402413

403-
/// Based on the namespace, renders the full name of the RPC method/subscription.
414+
/// Based on the namespace and separator, renders the full name of the RPC method/subscription.
404415
/// Examples:
405-
/// For namespace `foo` and method `makeSpam`, result will be `foo_makeSpam`.
406-
/// For no namespace and method `makeSpam` it will be just `makeSpam`.
416+
/// For namespace `foo`, method `makeSpam`, and separator `_`, result will be `foo_makeSpam`.
417+
/// For separator `.`, result will be `foo.makeSpam`.
418+
/// For no namespace, returns just `makeSpam`.
407419
pub(crate) fn rpc_identifier<'a>(&self, method: &'a str) -> Cow<'a, str> {
408-
if let Some(ns) = &self.namespace { format!("{ns}_{method}").into() } else { Cow::Borrowed(method) }
420+
if let Some(ns) = &self.namespace {
421+
let sep = self.namespace_separator.as_deref().unwrap_or("_");
422+
format!("{ns}{sep}{method}").into()
423+
} else {
424+
Cow::Borrowed(method)
425+
}
409426
}
410427
}
411428

tests/tests/proc_macros.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,60 @@ async fn macro_zero_copy_cow() {
296296
assert_eq!(resp, r#"{"jsonrpc":"2.0","id":0,"result":"Zero copy params: false"}"#);
297297
}
298298

299+
#[tokio::test]
300+
async fn namespace_separator_dot_formatting_works() {
301+
use jsonrpsee::core::async_trait;
302+
use jsonrpsee::proc_macros::rpc;
303+
use serde_json::json;
304+
305+
#[rpc(server, namespace = "foo", namespace_separator = ".")]
306+
pub trait DotSeparatorRpc {
307+
#[method(name = "dot")]
308+
fn dot(&self, a: u32, b: &str) -> Result<String, jsonrpsee::types::ErrorObjectOwned>;
309+
}
310+
311+
struct DotImpl;
312+
313+
#[async_trait]
314+
impl DotSeparatorRpcServer for DotImpl {
315+
fn dot(&self, a: u32, b: &str) -> Result<String, jsonrpsee::types::ErrorObjectOwned> {
316+
Ok(format!("Called with: {}, {}", a, b))
317+
}
318+
}
319+
320+
let module = DotImpl.into_rpc();
321+
let res: String = module.call("foo.dot", [json!(1_u64), json!("test")]).await.unwrap();
322+
323+
assert_eq!(&res, "Called with: 1, test");
324+
}
325+
326+
#[tokio::test]
327+
async fn namespace_separator_slash_formatting_works() {
328+
use jsonrpsee::core::async_trait;
329+
use jsonrpsee::proc_macros::rpc;
330+
use serde_json::json;
331+
332+
#[rpc(server, namespace = "math", namespace_separator = "/")]
333+
pub trait SlashSeparatorRpc {
334+
#[method(name = "add")]
335+
fn add(&self, x: i32, y: i32) -> Result<i32, jsonrpsee::types::ErrorObjectOwned>;
336+
}
337+
338+
struct SlashImpl;
339+
340+
#[async_trait]
341+
impl SlashSeparatorRpcServer for SlashImpl {
342+
fn add(&self, x: i32, y: i32) -> Result<i32, jsonrpsee::types::ErrorObjectOwned> {
343+
Ok(x + y)
344+
}
345+
}
346+
347+
let module = SlashImpl.into_rpc();
348+
let result: i32 = module.call("math/add", [json!(20), json!(22)]).await.unwrap();
349+
350+
assert_eq!(result, 42);
351+
}
352+
299353
// Disabled on MacOS as GH CI timings on Mac vary wildly (~100ms) making this test fail.
300354
#[cfg(not(target_os = "macos"))]
301355
#[ignore]

0 commit comments

Comments
 (0)