ash_rpc/macros.rs
1//! Convenience macros for JSON-RPC response creation.
2
3/// Create a success response with a result value and optional ID
4///
5/// # Examples:
6/// ```text
7/// // Success with ID
8/// rpc_success!(42, Some(1))
9///
10/// // Success with ID from variable
11/// rpc_success!(result, id)
12///
13/// // Success without ID
14/// rpc_success!({"status": "ok"})
15/// ```
16#[macro_export]
17macro_rules! rpc_success {
18 ($result:expr_2021, $id:expr_2021) => {
19 $crate::ResponseBuilder::new()
20 .success(serde_json::json!($result))
21 .id($id)
22 .build()
23 };
24 ($result:expr_2021) => {
25 $crate::ResponseBuilder::new()
26 .success(serde_json::json!($result))
27 .id(None)
28 .build()
29 };
30}
31
32/// Create an error response with code, message, and optional ID
33///
34/// # Usage:
35/// ```text
36/// // Error with explicit code, message and ID
37/// rpc_error!(error_codes::INVALID_PARAMS, "Invalid parameters", Some(1))
38///
39/// // Error with code and message, ID from variable
40/// rpc_error!(error_codes::INVALID_PARAMS, "Invalid parameters", id)
41///
42/// // Error without ID
43/// rpc_error!(error_codes::METHOD_NOT_FOUND, "Method not found")
44///
45/// // Error using predefined error codes
46/// rpc_error!(error_codes::INVALID_PARAMS, "Invalid parameters", id)
47/// ```
48#[macro_export]
49macro_rules! rpc_error {
50 ($code:expr_2021, $message:expr_2021, $id:expr_2021) => {
51 $crate::ResponseBuilder::new()
52 .error($crate::ErrorBuilder::new($code, $message).build())
53 .id($id)
54 .build()
55 };
56 ($code:expr_2021, $message:expr_2021) => {
57 $crate::ResponseBuilder::new()
58 .error($crate::ErrorBuilder::new($code, $message).build())
59 .id(None)
60 .build()
61 };
62}
63
64/// Create an error response with code, message, additional data and optional ID
65///
66/// # Usage:
67/// ```text
68/// // Error with data
69/// rpc_error_with_data!(error_codes::INVALID_PARAMS, "Invalid parameters", {"expected": "array"}, Some(1))
70///
71/// // Error with data but no ID
72/// rpc_error_with_data!(error_codes::INVALID_PARAMS, "Invalid parameters", {"expected": "array"})
73/// ```
74#[macro_export]
75macro_rules! rpc_error_with_data {
76 ($code:expr_2021, $message:expr_2021, $data:expr_2021, $id:expr_2021) => {
77 $crate::ResponseBuilder::new()
78 .error(
79 $crate::ErrorBuilder::new($code, $message)
80 .data(serde_json::json!($data))
81 .build(),
82 )
83 .id($id)
84 .build()
85 };
86 ($code:expr_2021, $message:expr_2021, $data:expr_2021) => {
87 $crate::ResponseBuilder::new()
88 .error(
89 $crate::ErrorBuilder::new($code, $message)
90 .data(serde_json::json!($data))
91 .build(),
92 )
93 .id(None)
94 .build()
95 };
96}
97
98/// Common error response shortcuts using predefined error codes
99///
100/// # Usage:
101/// ```text
102/// rpc_invalid_params!("Expected array of two numbers", id)
103/// rpc_method_not_found!(id)
104/// rpc_parse_error!("Invalid JSON", id)
105/// rpc_internal_error!("Database connection failed", id)
106/// ```
107#[macro_export]
108macro_rules! rpc_invalid_params {
109 ($message:expr_2021, $id:expr_2021) => {
110 rpc_error!($crate::error_codes::INVALID_PARAMS, $message, $id)
111 };
112 ($message:expr_2021) => {
113 rpc_error!($crate::error_codes::INVALID_PARAMS, $message)
114 };
115}
116
117#[macro_export]
118macro_rules! rpc_method_not_found {
119 ($id:expr_2021) => {
120 rpc_error!(
121 $crate::error_codes::METHOD_NOT_FOUND,
122 "Method not found",
123 $id
124 )
125 };
126 () => {
127 rpc_error!($crate::error_codes::METHOD_NOT_FOUND, "Method not found")
128 };
129}
130
131#[macro_export]
132macro_rules! rpc_parse_error {
133 ($message:expr_2021, $id:expr_2021) => {
134 rpc_error!($crate::error_codes::PARSE_ERROR, $message, $id)
135 };
136 ($message:expr_2021) => {
137 rpc_error!($crate::error_codes::PARSE_ERROR, $message)
138 };
139}
140
141#[macro_export]
142macro_rules! rpc_internal_error {
143 ($message:expr_2021, $id:expr_2021) => {
144 rpc_error!($crate::error_codes::INTERNAL_ERROR, $message, $id)
145 };
146 ($message:expr_2021) => {
147 rpc_error!($crate::error_codes::INTERNAL_ERROR, $message)
148 };
149}
150
151/// Create a JSON-RPC request
152///
153/// # Usage:
154/// ```text
155/// // Request with method, params and ID
156/// rpc_request!("add", [5, 3], 1)
157///
158/// // Request with method and ID (no params)
159/// rpc_request!("ping", 2)
160///
161/// // Request with method only (notification - no ID)
162/// rpc_request!("log")
163/// ```
164#[macro_export]
165macro_rules! rpc_request {
166 ($method:expr_2021, $params:expr_2021, $id:expr_2021) => {
167 $crate::RequestBuilder::new($method)
168 .params(serde_json::json!($params))
169 .id(serde_json::json!($id))
170 .build()
171 };
172 ($method:expr_2021, $id:expr_2021) => {
173 $crate::RequestBuilder::new($method)
174 .id(serde_json::json!($id))
175 .build()
176 };
177 ($method:expr_2021) => {
178 $crate::RequestBuilder::new($method).build()
179 };
180}
181
182/// Create a JSON-RPC notification
183///
184/// # Usage:
185/// ```text
186/// // Notification with method and params
187/// rpc_notification!("log", {"level": "info", "message": "Hello"})
188///
189/// // Notification with method only
190/// rpc_notification!("ping")
191/// ```
192#[macro_export]
193macro_rules! rpc_notification {
194 ($method:expr_2021, $params:expr_2021) => {
195 $crate::NotificationBuilder::new($method)
196 .params(serde_json::json!($params))
197 .build()
198 };
199 ($method:expr_2021) => {
200 $crate::NotificationBuilder::new($method).build()
201 };
202}
203
204/// Create a JSON-RPC error object (not a response)
205///
206/// # Usage:
207/// ```text
208/// // Error with code and message
209/// rpc_error_obj!(error_codes::INVALID_PARAMS, "Invalid parameters")
210///
211/// // Error with code, message and data
212/// rpc_error_obj!(error_codes::INVALID_PARAMS, "Invalid parameters", {"expected": "array"})
213/// ```
214#[macro_export]
215macro_rules! rpc_error_obj {
216 ($code:expr_2021, $message:expr_2021, $data:expr_2021) => {
217 $crate::ErrorBuilder::new($code, $message)
218 .data(serde_json::json!($data))
219 .build()
220 };
221 ($code:expr_2021, $message:expr_2021) => {
222 $crate::ErrorBuilder::new($code, $message).build()
223 };
224}
225
226//
227// Transport Macros - Easy server creation
228//
229
230/// Create and run a TCP JSON-RPC server
231///
232/// # Usage:
233/// ```text
234/// // Basic TCP server with registry
235/// rpc_tcp_server!("127.0.0.1:8080", registry);
236///
237/// // TCP server with error handling
238/// rpc_tcp_server!("127.0.0.1:8080", registry).expect("Failed to start server");
239/// ```
240#[cfg(feature = "tcp")]
241#[macro_export]
242macro_rules! rpc_tcp_server {
243 ($addr:expr_2021, $processor:expr_2021) => {{
244 let server = $crate::transports::tcp::TcpServer::builder($addr)
245 .processor($processor)
246 .build()?;
247 server.run()
248 }};
249}
250
251/// Create and run a TCP streaming JSON-RPC server
252///
253/// # Usage:
254/// ```text
255/// // Basic TCP streaming server
256/// rpc_tcp_stream_server!("127.0.0.1:8080", registry).await?;
257///
258/// // With error handling
259/// rpc_tcp_stream_server!("127.0.0.1:8080", registry).await.expect("Server failed");
260/// ```
261#[cfg(feature = "tcp-stream")]
262#[macro_export]
263macro_rules! rpc_tcp_stream_server {
264 ($addr:expr_2021, $processor:expr_2021) => {
265 async move {
266 let server = $crate::transports::tcp_stream::TcpStreamServer::builder($addr)
267 .processor($processor)
268 .build()?;
269 server.run().await
270 }
271 };
272}
273
274/// Create a TCP streaming JSON-RPC client
275///
276/// # Usage:
277/// ```text
278/// // Create and connect client
279/// let mut client = rpc_tcp_stream_client!("127.0.0.1:8080").await?;
280///
281/// // Send request and get response
282/// let response = client.call("method_name", Some(params)).await?;
283/// ```
284#[cfg(feature = "tcp-stream")]
285#[macro_export]
286macro_rules! rpc_tcp_stream_client {
287 ($addr:expr_2021) => {{
288 $crate::transports::tcp_stream::TcpStreamClient::builder($addr)
289 .build()
290 .connect()
291 }};
292}
293
294// Stateful Macros - Easy stateful processor creation
295
296/// Create a stateful JSON-RPC processor
297///
298/// # Usage:
299/// ```text
300/// // Create processor with context and handler
301/// let processor = rpc_stateful_processor!(service_context, handler);
302///
303/// // Create processor with context and method registry
304/// let processor = rpc_stateful_processor!(service_context, registry);
305/// ```
306#[cfg(feature = "stateful")]
307#[macro_export]
308macro_rules! rpc_stateful_processor {
309 ($context:expr_2021, $handler:expr_2021) => {
310 $crate::stateful::StatefulProcessor::new($context, $handler)
311 };
312}
313
314/// Create a stateful method registry
315///
316/// # Usage:
317/// ```text
318/// // Create empty registry
319/// let registry: StatefulMethodRegistry<MyContext> = rpc_stateful_registry!();
320///
321/// // Add methods with builder pattern
322/// let registry = rpc_stateful_registry!()
323/// .register_fn("method1", handler1)
324/// .register_fn("method2", handler2);
325/// ```
326#[cfg(feature = "stateful")]
327#[macro_export]
328macro_rules! rpc_stateful_registry {
329 () => {
330 $crate::stateful::StatefulMethodRegistry::new()
331 };
332}
333
334/// Create a stateful processor with builder pattern
335///
336/// # Usage:
337/// ```text
338/// // Create processor with builder
339/// let processor = rpc_stateful_builder!(context)
340/// .handler(handler)
341/// .build()?;
342///
343/// // Create processor with registry
344/// let processor = rpc_stateful_builder!(context)
345/// .registry(registry)
346/// .build()?;
347/// ```
348#[cfg(feature = "stateful")]
349#[macro_export]
350macro_rules! rpc_stateful_builder {
351 ($context:expr_2021) => {
352 $crate::stateful::StatefulProcessor::builder($context)
353 };
354}
355
356//
357// Method Definition Macros - Easy method creation
358//
359
360/// Define a simple JSON-RPC method with automatic trait implementation
361///
362/// # Usage:
363/// ```text
364/// rpc_method!(PingMethod, "ping", |_params, id| {
365/// rpc_success!("pong", id)
366/// });
367///
368/// rpc_method!(AddMethod, "add", |params, id| {
369/// let nums: Vec<i32> = serde_json::from_value(params.unwrap_or_default()).unwrap();
370/// rpc_success!(nums.iter().sum::<i32>(), id)
371/// });
372/// ```
373#[macro_export]
374macro_rules! rpc_method {
375 ($name:ident, $method_name:expr, $handler:expr) => {
376 pub struct $name;
377
378 #[async_trait::async_trait]
379 impl $crate::JsonRPCMethod for $name {
380 fn method_name(&self) -> &'static str {
381 $method_name
382 }
383
384 async fn call(
385 &self,
386 params: Option<serde_json::Value>,
387 id: Option<$crate::RequestId>,
388 ) -> $crate::Response {
389 ($handler)(params, id)
390 }
391 }
392 };
393}
394
395/// Validate and extract parameters with automatic error responses
396///
397/// # Usage:
398/// ```text
399/// rpc_method!(AddMethod, "add", |params, id| {
400/// let numbers = rpc_params!(params, id => Vec<i32>);
401/// rpc_success!(numbers.iter().sum::<i32>(), id)
402/// });
403/// ```
404#[macro_export]
405macro_rules! rpc_params {
406 ($params:expr, $id:expr => $type:ty) => {
407 match $params {
408 Some(p) => match serde_json::from_value::<$type>(p) {
409 Ok(params) => params,
410 Err(_) => return $crate::rpc_invalid_params!("Invalid parameter format", $id),
411 },
412 None => return $crate::rpc_invalid_params!("Missing required parameters", $id),
413 }
414 };
415 ($params:expr, $id:expr => Option<$type:ty>) => {
416 match $params {
417 Some(p) => match serde_json::from_value::<$type>(p) {
418 Ok(params) => Some(params),
419 Err(_) => return $crate::rpc_invalid_params!("Invalid parameter format", $id),
420 },
421 None => None,
422 }
423 };
424}
425
426/// Convert Result types to JSON-RPC responses with error logging
427///
428/// This macro logs detailed errors server-side and returns a generic error.
429/// For custom error messages, provide them explicitly.
430///
431/// # Usage:
432/// ```text
433/// rpc_method!(DivideMethod, "divide", |params, id| {
434/// let [a, b]: [f64; 2] = rpc_params!(params, id => [f64; 2]);
435/// let result = if b != 0.0 { Ok(a / b) } else { Err("Division by zero") };
436/// rpc_try!(result, id)
437/// });
438/// ```
439#[macro_export]
440macro_rules! rpc_try {
441 ($result:expr, $id:expr) => {
442 match $result {
443 Ok(value) => $crate::rpc_success!(value, $id),
444 Err(error) => {
445 tracing::error!(
446 error = %error,
447 request_id = ?$id,
448 "method execution failed"
449 );
450 $crate::rpc_error!(
451 $crate::error_codes::INTERNAL_ERROR,
452 "Internal server error",
453 $id
454 )
455 },
456 }
457 };
458 ($result:expr, $id:expr, $error_code:expr) => {
459 match $result {
460 Ok(value) => $crate::rpc_success!(value, $id),
461 Err(error) => {
462 tracing::error!(
463 error = %error,
464 request_id = ?$id,
465 error_code = $error_code,
466 "method execution failed"
467 );
468 $crate::rpc_error!(
469 $error_code,
470 "Server error",
471 $id
472 )
473 },
474 }
475 };
476 ($result:expr, $id:expr, $error_code:expr, $message:expr) => {
477 match $result {
478 Ok(value) => $crate::rpc_success!(value, $id),
479 Err(error) => {
480 tracing::error!(
481 error = %error,
482 request_id = ?$id,
483 error_code = $error_code,
484 "method execution failed"
485 );
486 $crate::rpc_error!($error_code, $message, $id)
487 },
488 }
489 };
490}
491
492/// Extract result from JSON-RPC response
493///
494/// # Usage:
495/// ```text
496/// let value = rpc_extract!(response);
497/// let typed_value: i32 = rpc_extract!(response => i32);
498/// ```
499#[macro_export]
500macro_rules! rpc_extract {
501 ($response:expr) => {
502 $response
503 .result()
504 .cloned()
505 .unwrap_or_else(|| serde_json::Value::Null)
506 };
507 ($response:expr => $type:ty) => {
508 serde_json::from_value::<$type>($crate::rpc_extract!($response)).unwrap_or_default()
509 };
510}
511
512/// Build a registry with multiple method instances
513///
514/// # Usage:
515/// ```text
516/// let registry = rpc_registry_with_methods![PingMethod, EchoMethod, AddMethod];
517/// ```
518#[macro_export]
519macro_rules! rpc_registry_with_methods {
520 ($($method:expr),* $(,)?) => {
521 $crate::MethodRegistry::new($crate::register_methods![$($method),*])
522 };
523}
524
525/// Create a simple JSON-RPC client call
526///
527/// # Usage:
528/// ```text
529/// let request = rpc_call_request!("method_name", [1, 2, 3], 42);
530/// let request = rpc_call_request!("ping", 1); // no params
531/// ```
532#[macro_export]
533macro_rules! rpc_call_request {
534 ($method:expr, $params:expr, $id:expr) => {
535 $crate::RequestBuilder::new($method)
536 .params(serde_json::json!($params))
537 .id(serde_json::json!($id))
538 .build()
539 };
540 ($method:expr, $id:expr) => {
541 $crate::RequestBuilder::new($method)
542 .id(serde_json::json!($id))
543 .build()
544 };
545}
546
547/// Handle common validation patterns
548///
549/// # Usage:
550/// ```text
551/// rpc_validate!(value > 0, "Value must be positive", id);
552/// rpc_validate!(!name.is_empty(), "Name cannot be empty", id);
553/// ```
554#[macro_export]
555macro_rules! rpc_validate {
556 ($condition:expr, $message:expr, $id:expr) => {
557 if !($condition) {
558 return $crate::rpc_invalid_params!($message, $id);
559 }
560 };
561}