Skip to main content

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}