plexus-macros 0.5.2

Procedural macros for Plexus RPC activations
Documentation
# Session Types as Runtime-Typed Streams

## The Hub as a Self-Describing Type System

The hub isn't just an RPC server—it's a **typed system that exposes its own types at runtime**. Every method's protocol is introspectable: clients can discover not just "what methods exist" but "what is the exact shape of the conversation I'll have with this method."

Session types are the compile-time contract. Streams are the runtime execution. The schema is the bridge that lets external systems understand both.

## The Key Insight: Session Type IS the Stream Definition

A session type like:
```rust
Loop<Choose<(Send<Chunk, Continue>, Send<Done, Done>)>>
```

Doesn't need to be "converted" to a stream. It **defines** the stream's item type:
```rust
Stream<Item = enum { Chunk(Chunk), Done(Done) }>
```

The macro doesn't convert—it **extracts** the implicit enum from the session type.

## Pipeline: Session Type → Schema

The transformation happens in stages, each building on the previous:

### Stage 1: Proc Macro AST Parsing

The `#[hub_method]` macro receives the return type as `syn::Type`. We recursively walk this AST:

```
Input: Loop<Choose<(Send<Chunk, Continue<0>>, Send<Exit, Done>)>>

AST Walk:
  TypePath "Loop"
    → generic arg: TypePath "Choose"
      → generic arg: TypeTuple
        → TypePath "Send" [Chunk, Continue<0>]
        → TypePath "Send" [Exit, Done]
```

Output: An intermediate representation (IR) capturing protocol structure and payload type paths.

### Stage 2: IR → Generated Code

From the IR, generate:

```rust
// 1. Protocol schema function (uses ToProtocolSchema trait)
fn method_schema() -> MethodSchema {
    MethodSchema {
        name: "method",
        protocol: <ReturnType as ToProtocolSchema>::protocol_schema(),
        server_protocol: <Dual<ReturnType> as ToProtocolSchema>::protocol_schema(),
    }
}

// 2. Stream item enum (extracted from Choose branches)
enum MethodStreamItem {
    Chunk(Chunk),  // from Send<Chunk, Continue>
    Exit(Exit),    // from Send<Exit, Done>
}

// 3. SessionItem impl
impl SessionItem for MethodStreamItem {
    fn is_terminal(&self) -> bool {
        matches!(self, Self::Exit(_))
    }
}
```

### Stage 3: Runtime Schema Generation

At runtime, `ToProtocolSchema::protocol_schema()` is called. This uses the trait impls we built:

```rust
impl<T: JsonSchema, S: ToProtocolSchema> ToProtocolSchema for Send<T, S> {
    fn protocol_schema() -> ProtocolSchema {
        ProtocolSchema::Send {
            payload_type: type_name::<T>(),
            payload_schema: schemars::schema_for!(T),
            then: Box::new(S::protocol_schema()),
        }
    }
}
```

The session type's structure is preserved; payload types get their JSON Schema via schemars.

### Stage 4: Schema Serialization

The `ProtocolSchema` enum serializes to JSON, giving clients the full protocol description:

```json
{
  "type": "loop",
  "body": {
    "type": "choose",
    "branches": [
      { "type": "send", "payload_schema": {...}, "then": { "type": "continue" } },
      { "type": "send", "payload_schema": {...}, "then": { "type": "done" } }
    ]
  }
}
```

## The Union Type for Methods

Each method's return type contributes to a plugin-level union:

```rust
// Generated by #[hub_methods]
enum MyPluginEvent {
    Execute(ExecuteStreamItem),  // from execute() -> Loop<Choose<...>>
    Status(StatusResponse),      // from status() -> Send<StatusResponse, Done>
}
```

The schema exposes this:
```json
{
  "methods": {
    "execute": {
      "protocol": { "type": "loop", "body": { "type": "choose", ... } },
      "stream_item_schema": { "oneOf": [...] }
    },
    "status": {
      "protocol": { "type": "send", "then": "done" },
      "response_schema": { ... }
    }
  }
}
```

## What the Macro Generates

For `#[hub_methods]` on an impl block:

1. **Per-method schema functions**: `fn execute_schema() -> MethodSchema`
2. **Stream item enums**: Generated from session type's `Choose` branches
3. **Method enum**: `enum MyPluginMethod { Execute(Input), Status }` for dispatch
4. **Event union**: `enum MyPluginEvent { ... }` for all possible outputs
5. **RPC trait + impl**: jsonrpsee integration with subscriptions for streams
6. **Activation trait impl**: Plugin system integration

## Summary

| Stage | Input | Output |
|-------|-------|--------|
| 1. AST Parse | `syn::Type` | Protocol IR |
| 2. Codegen | Protocol IR | Rust code (enums, impls, fns) |
| 3. Runtime | `ToProtocolSchema` call | `ProtocolSchema` value |
| 4. Serialize | `ProtocolSchema` | JSON Schema |

The hub exposes its types because the types ARE the protocol. Session types make the implicit explicit—then we serialize that explicitness as JSON Schema.