Skip to main content

razor_stream_macros/
lib.rs

1//! # razor-stream-macros
2//!
3//! This crate provides procedural macros to simplify the implementation of RPC tasks for the [`razor-rpc`](https://docs.rs/razor-rpc) framework.
4//! These macros automatically generate boilerplate code for trait implementations, reducing manual effort and improving code clarity.
5//!
6//! # Provided Macros
7//!
8//! ### Client-Side
9//! - [`#[client_task]`](macro@client_task): For defining a client-side RPC task on a struct. It will not generate ClientTask trait (it's optional to you to define ClientTaskAction with it)
10//! - [`#[client_task_enum]`](macro@client_task_enum): For creating an enum that delegates to client task variants. It will generate ClientTask trait for the enum
11//!
12//! ### Server-Side
13//! - [`#[server_task_enum]`](macro@server_task_enum): For defining a server-side RPC task enum.
14
15mod client_task;
16mod client_task_enum;
17mod server_task_enum;
18
19/// # `#[client_task]`
20///
21/// The `#[client_task]` attribute macro is used on a struct to designate it as a client-side RPC task.
22/// It simplifies implementation by generating boilerplate code for several traits.
23///
24/// The macro always generates:
25/// - `Deref` and `DerefMut` to the field marked `#[field(common)]`.
26/// - `ClientTaskEncode` for the `#[field(req)]` and `#[field(req_blob)]` fields.
27/// - `ClientTaskDecode` for the `#[field(resp)]` and `#[field(resp_blob)]` fields.
28///
29/// The macro can also conditionally generate:
30/// - `ClientTaskAction`: Generated if a static action is provided (e.g., `#[client_task(1)]`) or if a field is marked `#[field(action)]`.
31/// - `ClientTaskDone`: Generated if both `#[field(res)]` and `#[field(noti)]` are present. If not generated, you must implement this trait manually.
32///
33/// ### Field Attributes:
34///
35/// * `#[field(common)]`: **(Mandatory)** Marks a field that holds common task information (e.g., `ClientTaskCommon`).
36///   Allows direct access to members like `seq` via `Deref`.
37///
38/// * `#[field(action)]`: Specifies a field that dynamically provides the RPC action. Mutually exclusive with a static action in `#[client_task(...)`.
39///
40/// * `#[field(req)]`: **(Mandatory)** Designates the field for the request payload.
41///
42/// * `#[field(resp)]`: **(Mandatory)** Designates the field for the response payload, which must be an `Option<T>`.
43///
44/// * `#[field(req_blob)]`: (Optional) Marks a field for an optional request blob. Must implement `AsRef<[u8]>`.
45///
46/// * `#[field(resp_blob)]`: (Optional) Marks a field for an optional response blob. Must be `Option<T>` where `T` implements `razor_stream::buffer::AllocateBuf`.
47///
48/// * `#[field(res)]`: (Optional) When used with `#[field(noti)]`, triggers automatic `ClientTaskDone` implementation.
49///   Must be of type `Option<Result<(), RpcError<E>>>` where `E` implements `razor_stream::error::RpcErrCodec`. Stores the final result of the task.
50///
51/// * `#[field(noti)]`: (Optional) When used with `#[field(res)]`, triggers automatic `ClientTaskDone` implementation.
52///   Must be an `Option` wrapping a crossfire list channel MTx sender (e.g., `Option<crossfire::MTx<crossfire::mpsc::List<ParentTask>>>`) to notify of task completion.
53///   where the task has impl `Into` as `ParentTask` extracted with `crossfire::flavor::Queue::Item`
54///
55/// ### Example of Automatic `ClientTaskDone`
56///
57/// ```
58/// use razor_stream::error::RpcError;
59/// use nix::errno::Errno;
60/// use razor_stream_macros::client_task;
61/// use serde_derive::{Deserialize, Serialize};
62/// use crossfire::{mpsc, MTx};
63/// use razor_stream::client::task::*;
64///
65/// #[derive(Debug, Default, Deserialize, Serialize)]
66/// pub struct FileReadReq {
67///     pub path: String,
68///     pub offset: u64,
69///     pub len: u64,
70/// }
71///
72/// #[derive(Debug, Default, Deserialize, Serialize)]
73/// pub struct FileReadResp {
74///     pub bytes_read: u64,
75/// }
76///
77/// // A task with automatic `ClientTaskDone` implementation.
78/// #[client_task(1, debug)]
79/// pub struct FileReadTask {
80///     #[field(common)]
81///     common: ClientTaskCommon,
82///     #[field(req)]
83///     req: FileReadReq,
84///     #[field(resp)]
85///     resp: Option<FileReadResp>,
86///     #[field(res)]
87///     res: Option<Result<(), RpcError<Errno>>>,
88///     #[field(noti)]
89///     noti: Option<MTx<mpsc::List<Self>>>,
90/// }
91///
92/// // Usage
93/// let (tx, rx) = mpsc::unbounded_blocking::<FileReadTask>();
94/// let mut task = FileReadTask {
95///     common: ClientTaskCommon { seq: 1, ..Default::default() },
96///     req: FileReadReq { path: "/path/to/file".to_string(), offset: 0, len: 1024 },
97///     resp: None,
98///     res: None,
99///     noti: Some(tx),
100/// };
101///
102/// task.set_ok();
103/// task.done();
104///
105/// let completed_task = rx.recv().unwrap();
106/// assert_eq!(completed_task.common.seq, 1);
107/// assert!(completed_task.res.is_some() && completed_task.res.as_ref().unwrap().is_ok());
108/// ```
109#[proc_macro_attribute]
110pub fn client_task(
111    attrs: proc_macro::TokenStream, input: proc_macro::TokenStream,
112) -> proc_macro::TokenStream {
113    client_task::client_task_impl(attrs, input)
114}
115
116/// # `#[client_task_enum]`
117///
118/// The `#[client_task_enum]` attribute is applied to an enum to delegate `ClientTask` related trait
119/// implementations to its variants. Each variant must wrap a struct that is a valid client task
120/// (often decorated with `#[client_task]`)
121///
122/// This macro generates `From` implementations for each variant, allowing for easy conversion
123/// from a specific task struct to the enum. It also delegates methods from `ClientTask`,
124/// `ClientTaskEncode`, and `ClientTaskDecode` to the inner task.
125///
126/// `client_task_enum` also require `error` flag, specified the custom error type for [RpcError<E: RpcErrCodec>](crate::error::RpcError)
127///
128/// ### `#[action]` on enum variants
129///
130/// As an alternative to defining the action inside the subtype, you can specify a static action
131/// directly on an enum variant using the `#[action(...)]` attribute. Only one action (numeric, or
132/// string literal, or numeric enum) is allowed per variant.
133///
134/// When `#[action(...)]` is used on a variant, the inner type does not need to define an action in this case,
135/// but if it does, the enum's action will take precedence.
136///
137/// ### Example:
138///
139/// ```rust
140/// use razor_stream::client::task::{ClientTask, ClientTaskCommon, ClientTaskAction, ClientTaskDone};
141/// use razor_stream::error::RpcError;
142/// use nix::errno::Errno;
143/// use razor_stream_macros::{client_task, client_task_enum};
144/// use serde_derive::{Deserialize, Serialize};
145/// use crossfire::{mpsc, MTx};
146///
147/// #[derive(PartialEq, Debug)]
148/// #[repr(u8)]
149/// enum FileAction {
150///     Open = 1,
151///     Close = 2,
152/// }
153///
154/// // Action can be specified in the FileTask enum
155/// #[client_task(debug)]
156/// pub struct FileOpenTask {
157///     #[field(common)]
158///     common: ClientTaskCommon,
159///     #[field(req)]
160///     req: String,
161///     #[field(resp)]
162///     resp: Option<()>,
163///     #[field(res)]
164///     res: Option<Result<(), RpcError<Errno>>>,
165///     #[field(noti)]
166///     noti: Option<MTx<mpsc::List<FileTask>>>,
167/// }
168///
169/// // Action can be either with client_task
170/// #[client_task(FileAction::Close, debug)]
171/// pub struct FileCloseTask {
172///     #[field(common)]
173///     common: ClientTaskCommon,
174///     #[field(req)]
175///     req: (),
176///     #[field(resp)]
177///     resp: Option<()>,
178///     #[field(res)]
179///     res: Option<Result<(), RpcError<Errno>>>,
180///     #[field(noti)]
181///     noti: Option<MTx<mpsc::List<FileTask>>>,
182/// }
183///
184/// #[client_task_enum(error=Errno)]
185/// #[derive(Debug)]
186/// pub enum FileTask {
187///     #[action(FileAction::Open)]
188///     Open(FileOpenTask),
189///     Close(FileCloseTask),
190/// }
191///
192/// // Usage
193/// let (tx, rx) = mpsc::unbounded_blocking();
194///
195/// // Test Open Task
196/// let open_task = FileOpenTask {
197///     common: ClientTaskCommon::default(),
198///     req: "/path/to/file".to_string(),
199///     resp: None,
200///     res: None,
201///     noti: Some(tx.clone()),
202/// };
203///
204/// let mut file_task: FileTask = open_task.into();
205/// assert_eq!(file_task.get_action(), razor_stream::proto::RpcAction::Num(1));
206/// file_task.set_ok();
207/// file_task.done();
208///
209/// let received = rx.recv().unwrap();
210/// assert!(matches!(received, FileTask::Open(_)));
211///
212/// // Test Close Task
213/// let close_task = FileCloseTask {
214///     common: ClientTaskCommon::default(),
215///     req: (),
216///     resp: None,
217///     res: None,
218///     noti: Some(tx),
219/// };
220///
221/// let mut file_task: FileTask = close_task.into();
222/// assert_eq!(file_task.get_action(), razor_stream::proto::RpcAction::Num(2));
223/// file_task.set_ok();
224/// file_task.done();
225///
226/// let received = rx.recv().unwrap();
227/// assert!(matches!(received, FileTask::Close(_)));
228/// ```
229#[proc_macro_attribute]
230pub fn client_task_enum(
231    attrs: proc_macro::TokenStream, input: proc_macro::TokenStream,
232) -> proc_macro::TokenStream {
233    client_task_enum::client_task_enum_impl(attrs, input)
234}
235
236/// # `#[server_task_enum]`
237///
238/// The `#[server_task_enum]` macro streamlines the creation of server-side task enums.
239/// When applied to an enum, it automatically implements necessary traits for processing RPC requests,
240/// reducing boilerplate and improving code maintainability.
241///
242/// ### Macro Arguments:
243///
244/// The `server_task_enum` can accept either or both of "req" and "resp" flags, with the following
245/// rules:
246/// - If `req` is specified, the enum is request task, the macro will impl [ServerTaskDecode](crate::server::task::ServerTaskDecode), [ServerTaskAction](crate::server::task::ServerTaskAction). each variant must have an `#[action(...)]` attribute.
247/// - If "resp" is specified, the enum is response task. The macro will impl [ServerTaskEncode](crate::server::task::ServerTaskEncode), [ServerTaskDone](crate::server::task::ServerTaskDone)
248/// - If both `req` and `resp` is specified, the response type for `ServerTaskDecode<R>` and `ServerTaskDone<R>` is implicitly `Self` (the enum itself). `resp_type` can be omitted.
249/// - If only "req" is specified (and "resp" is not), then `resp_type` must be provided. This `resp_type` specifies the type `<R>` for parameters of `ServerTaskDecode<R>` and `ServerTaskDone<R>`.
250///
251/// `server_task_enum` also requires `error` flag, specified the custom error type for [RpcError<E: RpcErrCodec>](crate::error::RpcError)
252///
253/// ### Variant Attributes:
254///
255/// * `#[action(...)]`: Associates one or more RPC action (which can be numeric, string, or enum value) with an enum variant.
256///   Multiple actions can be specified (e.g., `#[action(1, 2 )]`).
257///   If there's more than one actions. The subtype should store its action and implement ServerTaskAction.
258///
259/// ### Example:
260///
261/// ```rust
262/// use razor_stream::server::task::ServerTaskVariant;
263/// use razor_stream_macros::server_task_enum;
264/// use serde_derive::{Deserialize, Serialize};
265/// use nix::errno::Errno;
266///
267/// #[derive(PartialEq)]
268/// #[repr(u8)]
269/// enum FileAction {
270///     Open=1,
271///     Read=2,
272///     Write=3,
273///     Truncate=4,
274/// }
275///
276/// #[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
277/// struct FileOpenReq { pub path: String, }
278///
279/// #[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
280/// struct FileIOReq { pub path: String, pub offset: u64, pub len: u64, }
281///
282///
283/// #[server_task_enum(req, resp, error=Errno)]
284/// #[derive(Debug)]
285/// pub enum FileTask {
286///     #[action(FileAction::Open)]
287///     Open(ServerTaskVariant<FileTask, FileOpenReq, Errno>),
288///     #[action(FileAction::Read, FileAction::Write, FileAction::Truncate)]
289///     Read(ServerTaskVariant<FileTask, FileIOReq, Errno>),
290/// }
291/// ```
292#[proc_macro_attribute]
293pub fn server_task_enum(
294    attrs: proc_macro::TokenStream, input: proc_macro::TokenStream,
295) -> proc_macro::TokenStream {
296    server_task_enum::server_task_enum_impl(attrs, input)
297}