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 channel sender (e.g., `Option<crossfire::mpsc::MTx<Self>>`) to notify of task completion.
53///
54/// ### Example of Automatic `ClientTaskDone`
55///
56/// ```
57/// use razor_stream::error::RpcError;
58/// use nix::errno::Errno;
59/// use razor_stream_macros::client_task;
60/// use serde_derive::{Deserialize, Serialize};
61/// use crossfire::{mpsc, MTx};
62/// use razor_stream::client::task::*;
63///
64/// #[derive(Debug, Default, Deserialize, Serialize)]
65/// pub struct FileReadReq {
66/// pub path: String,
67/// pub offset: u64,
68/// pub len: u64,
69/// }
70///
71/// #[derive(Debug, Default, Deserialize, Serialize)]
72/// pub struct FileReadResp {
73/// pub bytes_read: u64,
74/// }
75///
76/// // A task with automatic `ClientTaskDone` implementation.
77/// #[client_task(1, debug)]
78/// pub struct FileReadTask {
79/// #[field(common)]
80/// common: ClientTaskCommon,
81/// #[field(req)]
82/// req: FileReadReq,
83/// #[field(resp)]
84/// resp: Option<FileReadResp>,
85/// #[field(res)]
86/// res: Option<Result<(), RpcError<Errno>>>,
87/// #[field(noti)]
88/// noti: Option<MTx<Self>>,
89/// }
90///
91/// // Usage
92/// let (tx, rx) = mpsc::unbounded_blocking::<FileReadTask>();
93/// let mut task = FileReadTask {
94/// common: ClientTaskCommon { seq: 1, ..Default::default() },
95/// req: FileReadReq { path: "/path/to/file".to_string(), offset: 0, len: 1024 },
96/// resp: None,
97/// res: None,
98/// noti: Some(tx),
99/// };
100///
101/// task.set_ok();
102/// task.done();
103///
104/// let completed_task = rx.recv().unwrap();
105/// assert_eq!(completed_task.common.seq, 1);
106/// assert!(completed_task.res.is_some() && completed_task.res.as_ref().unwrap().is_ok());
107/// ```
108#[proc_macro_attribute]
109pub fn client_task(
110 attrs: proc_macro::TokenStream, input: proc_macro::TokenStream,
111) -> proc_macro::TokenStream {
112 client_task::client_task_impl(attrs, input)
113}
114
115/// # `#[client_task_enum]`
116///
117/// The `#[client_task_enum]` attribute is applied to an enum to delegate `ClientTask` related trait
118/// implementations to its variants. Each variant must wrap a struct that is a valid client task
119/// (often decorated with `#[client_task]`)
120///
121/// This macro generates `From` implementations for each variant, allowing for easy conversion
122/// from a specific task struct to the enum. It also delegates methods from `ClientTask`,
123/// `ClientTaskEncode`, and `ClientTaskDecode` to the inner task.
124///
125/// `client_task_enum` also require `error` flag, specified the custom error type for [RpcError<E: RpcErrCodec>](crate::error::RpcError)
126///
127/// ### `#[action]` on enum variants
128///
129/// As an alternative to defining the action inside the subtype, you can specify a static action
130/// directly on an enum variant using the `#[action(...)]` attribute. Only one action (numeric, or
131/// string literal, or numeric enum) is allowed per variant.
132///
133/// When `#[action(...)]` is used on a variant, the inner type does not need to define an action in this case,
134/// but if it does, the enum's action will take precedence.
135///
136/// ### Example:
137///
138/// ```rust
139/// use razor_stream::client::task::{ClientTask, ClientTaskCommon, ClientTaskAction, ClientTaskDone};
140/// use razor_stream::error::RpcError;
141/// use nix::errno::Errno;
142/// use razor_stream_macros::{client_task, client_task_enum};
143/// use serde_derive::{Deserialize, Serialize};
144/// use crossfire::{mpsc, MTx};
145///
146/// #[derive(PartialEq, Debug)]
147/// #[repr(u8)]
148/// enum FileAction {
149/// Open = 1,
150/// Close = 2,
151/// }
152///
153/// // Action can be specified in the FileTask enum
154/// #[client_task(debug)]
155/// pub struct FileOpenTask {
156/// #[field(common)]
157/// common: ClientTaskCommon,
158/// #[field(req)]
159/// req: String,
160/// #[field(resp)]
161/// resp: Option<()>,
162/// #[field(res)]
163/// res: Option<Result<(), RpcError<Errno>>>,
164/// #[field(noti)]
165/// noti: Option<MTx<FileTask>>,
166/// }
167///
168/// // Action can be either with client_task
169/// #[client_task(FileAction::Close, debug)]
170/// pub struct FileCloseTask {
171/// #[field(common)]
172/// common: ClientTaskCommon,
173/// #[field(req)]
174/// req: (),
175/// #[field(resp)]
176/// resp: Option<()>,
177/// #[field(res)]
178/// res: Option<Result<(), RpcError<Errno>>>,
179/// #[field(noti)]
180/// noti: Option<MTx<FileTask>>,
181/// }
182///
183/// #[client_task_enum(error=Errno)]
184/// #[derive(Debug)]
185/// pub enum FileTask {
186/// #[action(FileAction::Open)]
187/// Open(FileOpenTask),
188/// Close(FileCloseTask),
189/// }
190///
191/// // Usage
192/// let (tx, rx) = mpsc::unbounded_blocking();
193///
194/// // Test Open Task
195/// let open_task = FileOpenTask {
196/// common: ClientTaskCommon::default(),
197/// req: "/path/to/file".to_string(),
198/// resp: None,
199/// res: None,
200/// noti: Some(tx.clone()),
201/// };
202///
203/// let mut file_task: FileTask = open_task.into();
204/// assert_eq!(file_task.get_action(), razor_stream::proto::RpcAction::Num(1));
205/// file_task.set_ok();
206/// file_task.done();
207///
208/// let received = rx.recv().unwrap();
209/// assert!(matches!(received, FileTask::Open(_)));
210///
211/// // Test Close Task
212/// let close_task = FileCloseTask {
213/// common: ClientTaskCommon::default(),
214/// req: (),
215/// resp: None,
216/// res: None,
217/// noti: Some(tx),
218/// };
219///
220/// let mut file_task: FileTask = close_task.into();
221/// assert_eq!(file_task.get_action(), razor_stream::proto::RpcAction::Num(2));
222/// file_task.set_ok();
223/// file_task.done();
224///
225/// let received = rx.recv().unwrap();
226/// assert!(matches!(received, FileTask::Close(_)));
227/// ```
228#[proc_macro_attribute]
229pub fn client_task_enum(
230 attrs: proc_macro::TokenStream, input: proc_macro::TokenStream,
231) -> proc_macro::TokenStream {
232 client_task_enum::client_task_enum_impl(attrs, input)
233}
234
235/// # `#[server_task_enum]`
236///
237/// The `#[server_task_enum]` macro streamlines the creation of server-side task enums.
238/// When applied to an enum, it automatically implements necessary traits for processing RPC requests,
239/// reducing boilerplate and improving code maintainability.
240///
241/// ### Macro Arguments:
242///
243/// The `server_task_enum` can accept either or both of "req" and "resp" flags, with the following
244/// rules:
245/// - 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.
246/// - If "resp" is specified, the enum is response task. The macro will impl [ServerTaskEncode](crate::server::task::ServerTaskEncode), [ServerTaskDone](crate::server::task::ServerTaskDone)
247/// - 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.
248/// - 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>`.
249///
250/// `server_task_enum` also requires `error` flag, specified the custom error type for [RpcError<E: RpcErrCodec>](crate::error::RpcError)
251///
252/// ### Variant Attributes:
253///
254/// * `#[action(...)]`: Associates one or more RPC action (which can be numeric, string, or enum value) with an enum variant.
255/// Multiple actions can be specified (e.g., `#[action(1, 2 )]`).
256/// If there's more than one actions. The subtype should store its action and implement ServerTaskAction.
257///
258/// ### Example:
259///
260/// ```rust
261/// use razor_stream::server::task::ServerTaskVariant;
262/// use razor_stream_macros::server_task_enum;
263/// use serde_derive::{Deserialize, Serialize};
264/// use nix::errno::Errno;
265///
266/// #[derive(PartialEq)]
267/// #[repr(u8)]
268/// enum FileAction {
269/// Open=1,
270/// Read=2,
271/// Write=3,
272/// Truncate=4,
273/// }
274///
275/// #[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
276/// struct FileOpenReq { pub path: String, }
277///
278/// #[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
279/// struct FileIOReq { pub path: String, pub offset: u64, pub len: u64, }
280///
281///
282/// #[server_task_enum(req, resp, error=Errno)]
283/// #[derive(Debug)]
284/// pub enum FileTask {
285/// #[action(FileAction::Open)]
286/// Open(ServerTaskVariant<FileTask, FileOpenReq, Errno>),
287/// #[action(FileAction::Read, FileAction::Write, FileAction::Truncate)]
288/// Read(ServerTaskVariant<FileTask, FileIOReq, Errno>),
289/// }
290/// ```
291#[proc_macro_attribute]
292pub fn server_task_enum(
293 attrs: proc_macro::TokenStream, input: proc_macro::TokenStream,
294) -> proc_macro::TokenStream {
295 server_task_enum::server_task_enum_impl(attrs, input)
296}