# herolib-derive
[](https://crates.io/crates/herolib-derive)
[](https://docs.rs/herolib-derive)
Derive macros for herolib providing:
- `ToSchema` - Generate JSON Schema from Rust structs
- `ToHeroScript` - Serialize Rust structs to HeroScript format
- `FromHeroScript` - Deserialize HeroScript into Rust structs
- `OsisObject` - Implement OSIS database object trait for type-safe storage
- `Actor` - Define RPC-capable actors with OpenRPC specification generation
- `RpcMethod` - Mark methods as RPC endpoints
## Documentation
- [API Documentation (docs.rs)](https://docs.rs/herolib-derive)
- [Actor System Documentation](./docs/)
## Installation
```toml
[dependencies]
herolib-derive = "0.1"
```
## Building
```bash
./build.sh
```
## Actor System
The Actor system provides a standardized approach for building RPC services with automatic code generation.
### Overview
An **Actor** is a self-contained unit that:
- Encapsulates business logic
- Exposes methods via OpenRPC
- Communicates through Redis queues
- Supports synchronous and asynchronous operations
- Streams logs during execution
### Quick Example
```rust
use herolib_derive::{Actor, ToSchema};
use serde::{Deserialize, Serialize};
/// Input for greeting.
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct GreetInput {
pub name: String,
}
/// Output after greeting.
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct GreetOutput {
pub message: String,
}
/// Greeter handles greeting operations.
#[derive(Actor)]
#[actor(name = "greeter")]
pub struct Greeter;
impl Greeter {
/// Say hello to someone.
#[rpc_method]
#[rpc_example(
input = r#"{ "name": "World" }"#,
output = r#"{ "message": "Hello, World!" }"#
)]
pub async fn hello(
&self,
input: GreetInput,
logger: &RequestLogger,
) -> Result<GreetOutput, ActorError> {
logger.info(format!("Greeting {}", input.name)).await;
Ok(GreetOutput {
message: format!("Hello, {}!", input.name),
})
}
}
```
### Generated Artifacts
For each actor, the macros generate:
| `{actor}.openrpc.json` | OpenRPC 1.3 specification |
| `{actor}_client.rs` | Type-safe async client |
| `{actor}_handler.rs` | Redis queue handler/server |
| `{actor}_api.md` | API documentation |
| `{actor}_api_redis.md` | API documentation with Redis details |
### Redis Communication
Actors communicate via Redis queues:
```
actors:{actor_name}:{instance}:queue # Request queue (List)
actors:{actor_name}:{instance}:result:{id} # Results (String)
actors:{actor_name}:{instance}:logs:{id} # Log stream (List)
```
**Client sends request:**
```bash
LPUSH actors:greeter:main:queue '{"jsonrpc":"2.0","id":"req-1","method":"greeter.hello","params":{"name":"World"}}'
```
**Client reads result:**
```bash
GET actors:greeter:main:result:req-1
```
### Documentation
See the [docs/](./docs/) directory for detailed specifications:
1. [Actor System Architecture](./docs/01_actor_system_architecture.md)
2. [OpenRPC Generation](./docs/02_openrpc_generation.md)
3. [Redis Queue Protocol](./docs/03_redis_queue_protocol.md)
4. [Client Generation](./docs/04_client_generation.md)
5. [Handler Generation](./docs/05_handler_generation.md)
6. [Documentation Generation](./docs/06_documentation_generation.md)
7. [Example Actor](./docs/07_example_actor.md)
## OsisObject Macro
Implement the `OsisObject` trait for type-safe database storage with automatic SmartID generation:
```rust
use herolib_derive::OsisObject;
use herolib_osis::sid::SmartId;
use serde::{Serialize, Deserialize};
// Auto-generated type_name: "user"
#[derive(Default, Serialize, Deserialize, OsisObject)]
struct User {
sid: SmartId,
name: String,
}
// Auto-generated type_name: "user_profile"
#[derive(Default, Serialize, Deserialize, OsisObject)]
struct UserProfile {
sid: SmartId,
email: String,
}
// Custom type_name override
#[derive(Default, Serialize, Deserialize, OsisObject)]
#[osis(type_name = "members")]
struct Member {
sid: SmartId,
name: String,
}
```
Usage with `DBTyped`:
```rust
use herolib_osis::db::DBTyped;
let mut users: DBTyped<User> = DBTyped::new("/data", 0)?;
// Create and store (sid auto-generated)
let mut user = User::default();
user.name = "Alice".into();
users.set(&mut user)?; // user.sid is now set
// Retrieve by SID
let loaded = users.get(&user.sid)?;
```
## HeroScript Macros
### ToHeroScript / FromHeroScript
Serialize and deserialize Rust structs to/from HeroScript format:
```rust
use herolib_derive::{ToHeroScript, FromHeroScript};
#[derive(ToHeroScript, FromHeroScript, Default)]
struct Person {
name: String,
age: u32,
active: bool,
}
// Serialize to HeroScript
let person = Person { name: "John".into(), age: 30, active: true };
let hs = person.to_heroscript("person", "define");
// Output:
// !!person.define
// name:John
// age:30
// active:true
// Deserialize from HeroScript
let script = "!!person.define name:Jane age:25 active:false";
let jane = Person::from_heroscript(script).unwrap();
```
### ToSchema
Generate JSON Schema from Rust types:
```rust
use herolib_derive::ToSchema;
#[derive(ToSchema)]
struct Config {
/// Server hostname.
host: String,
/// Server port.
port: u16,
/// Enable debug mode.
#[serde(default)]
debug: bool,
}
// Get JSON Schema
let schema = Config::schema();
```
## License
Apache-2.0