Skip to content

Commit a1f698f

Browse files
Refactor: actors derive commands macro (#398)
* add actor_command macro and use for FileTransferService * extend for generic service types and BlockchainService * update docs * update rustdocs * cargo fmt * update gitignore with cursor * address CR --------- Co-authored-by: Facundo Farall <[email protected]>
1 parent 9540c44 commit a1f698f

23 files changed

+1274
-1086
lines changed

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ config.toml
3737

3838

3939
# IDE
40-
4140
.DS_Store
4241
.vscode/
4342
.zed/
4443
.idea/
4544
.cursorignore
46-
.worktrees/
45+
.worktrees/
46+
.cursorindexingignore
47+
.specstory/

client/actors-derive/README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,129 @@ pub struct NewChallengeSeed {
195195
#[ActorEventBus("blockchain_service")]
196196
pub struct BlockchainServiceEventBusProvider;
197197
```
198+
199+
# StorageHub Actors Command Macros
200+
201+
This crate provides procedural macros to simplify actor command boilerplate code in the StorageHub actors framework.
202+
203+
## Features
204+
205+
- `actor_command` attribute macro: Automatically enhances command enums with callbacks and generates the Interface trait.
206+
- `command` attribute macro: Specifies behavior for individual command variants.
207+
208+
## Usage
209+
210+
### Basic Command Definition
211+
212+
```rust
213+
#[actor_command(
214+
service = FileTransferService,
215+
default_mode = "SyncAwait",
216+
default_error_type = RequestError
217+
)]
218+
pub enum FileTransferServiceCommand {
219+
#[command(mode = "AsyncAwait", success_type = (Vec<u8>, ProtocolName), error_type = RequestFailure)]
220+
UploadRequest {
221+
peer_id: PeerId,
222+
file_key: FileKey,
223+
file_key_proof: FileKeyProof,
224+
bucket_id: Option<BucketId>,
225+
},
226+
227+
UploadResponse {
228+
request_id: UploadRequestId,
229+
file_complete: bool,
230+
},
231+
232+
// Other commands...
233+
}
234+
```
235+
236+
### Command Modes
237+
238+
The macro supports three command modes:
239+
240+
1. **FireAndForget**: No response is expected
241+
2. **SyncAwait**: Wait for a direct response from the actor
242+
3. **AsyncAwait**: Wait for an asynchronous response (e.g., from a network operation)
243+
244+
### Extension Traits
245+
246+
You can define extension traits in addition to the automatically generated Interface trait:
247+
248+
```rust
249+
#[async_trait::async_trait]
250+
pub trait FileTransferServiceInterfaceExt {
251+
fn parse_remote_upload_data_response(
252+
&self,
253+
data: Vec<u8>,
254+
) -> Result<schema::v1::provider::RemoteUploadDataResponse, RequestError>;
255+
256+
async fn extract_peer_ids_and_register_known_addresses(
257+
&self,
258+
multiaddresses: Vec<Multiaddr>,
259+
) -> Vec<PeerId>;
260+
}
261+
262+
#[async_trait::async_trait]
263+
impl FileTransferServiceInterfaceExt for ActorHandle<FileTransferService> {
264+
// Implementations...
265+
}
266+
```
267+
268+
## Attribute Parameters
269+
270+
### `actor_command` Parameters
271+
272+
- `service`: (Required) The service type that processes these commands
273+
- `default_mode`: (Optional) Default command mode, one of: "FireAndForget", "SyncAwait", "AsyncAwait"
274+
- `default_error_type`: (Optional) Default error type for command responses
275+
- `default_inner_channel_type`: (Optional) Default channel type for AsyncAwait mode
276+
277+
### `command` Parameters
278+
279+
- `mode`: (Optional) Override the default command mode
280+
- `success_type`: (Optional) The success type returned in the Result
281+
- `error_type`: (Optional) Override the default error type
282+
- `inner_channel_type`: (Optional) Override the default channel type for AsyncAwait mode
283+
284+
## Generated Code
285+
286+
The macro automatically:
287+
288+
1. Adds a `callback` field to each command variant based on the mode
289+
2. Generates a trait with a method for each command
290+
3. Implements the trait for ActorHandle<ServiceType>
291+
292+
This eliminates boilerplate code and ensures consistent error handling.
293+
294+
## Real World Example
295+
296+
Here's an example from the StorageHub codebase:
297+
298+
```rust
299+
#[actor_command(
300+
service = BlockchainService<FSH: ForestStorageHandler + Clone + Send + Sync + 'static>,
301+
default_mode = "SyncAwait",
302+
default_inner_channel_type = tokio::sync::oneshot::Receiver,
303+
)]
304+
pub enum BlockchainServiceCommand {
305+
#[command(success_type = SubmittedTransaction)]
306+
SendExtrinsic {
307+
call: storage_hub_runtime::RuntimeCall,
308+
options: SendExtrinsicOptions,
309+
},
310+
#[command(success_type = Extrinsic)]
311+
GetExtrinsicFromBlock {
312+
block_hash: H256,
313+
extrinsic_hash: H256,
314+
},
315+
#[command(mode = "AsyncAwait", inner_channel_type = tokio::sync::oneshot::Receiver)]
316+
WaitForBlock {
317+
block_number: BlockNumber,
318+
},
319+
// ... more commands
320+
}
321+
```
322+
323+
This generates a trait `BlockchainServiceCommandInterface` with methods like `send_extrinsic`, `get_extrinsic_from_block`, etc., which can be called on an `ActorHandle<BlockchainService>`.

0 commit comments

Comments
 (0)