# herolib-derive

[![Crates.io](https://img.shields.io/crates/v/herolib-derive.svg)](https://crates.io/crates/herolib-derive)
[![Documentation](https://docs.rs/herolib-derive/badge.svg)](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:

| Artifact | Description |
|----------|-------------|
| `{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
