# Struct-to-Plugin Macro: Feasibility Analysis

## Goal

Automatically derive a Plexus plugin from a Rust struct, exposing all its methods as JSON-RPC endpoints with generated schemas.

```rust
#[derive(Plugin)]
struct Registry {
    models: Vec<ModelInfo>,
    families: HashSet<String>,
}

#[plugin_methods]
impl Registry {
    async fn list_models(&self) -> Vec<ModelInfo> { ... }
    async fn get_model(&self, id: String) -> Option<ModelInfo> { ... }
    async fn list_families(&self) -> Vec<String> { ... }
}

// Generates:
// - registry.list_models endpoint
// - registry.get_model endpoint
// - registry.list_families endpoint
// - JSON Schema for params and returns
// - Routing infrastructure
```

## What Needs to Be Generated

### 1. Method Dispatch

```rust
// From this:
async fn get_model(&self, id: String) -> Option<ModelInfo>

// Generate:
match method_name {
    "get_model" => {
        let params: GetModelParams = serde_json::from_value(params)?;
        let result = self.get_model(params.id).await;
        serde_json::to_value(result)?
    }
}
```

### 2. Parameter Structs

```rust
// Auto-generate from method signature:
#[derive(Deserialize, JsonSchema)]
struct GetModelParams {
    id: String,
}
```

### 3. JSON Schema

```rust
// From return type Option<ModelInfo>, generate:
{
    "anyOf": [
        {"$ref": "#/$defs/ModelInfo"},
        {"type": "null"}
    ]
}
```

### 4. Plugin Schema

```rust
PluginSchema {
    namespace: "registry",
    methods: vec![
        MethodSchema {
            name: "list_models",
            params: None,
            returns: Some(schema_for::<Vec<ModelInfo>>()),
        },
        MethodSchema {
            name: "get_model",
            params: Some(schema_for::<GetModelParams>()),
            returns: Some(schema_for::<Option<ModelInfo>>()),
        },
    ],
}
```

## Rust Macro Capabilities

### What Proc Macros CAN Do

1. **Parse method signatures** - extract name, params, return type
2. **Generate new structs** - param structs from method args
3. **Generate impl blocks** - dispatch logic, schema methods
4. **Derive trait implementations** - Serialize, Deserialize, JsonSchema
5. **Access type names** - but not resolve them fully

### What Proc Macros CANNOT Do

1. **Resolve type aliases** - `type Id = String` appears as `Id`, not `String`
2. **See trait implementations** - can't know if `T: JsonSchema`
3. **Cross-crate type information** - limited visibility
4. **Runtime reflection** - Rust has none

## Implementation Approaches

### Approach 1: Attribute Macro on Impl Block

```rust
#[plexus_plugin(namespace = "registry")]
impl Registry {
    /// List all available models
    #[plexus_method]
    async fn list_models(&self) -> Vec<ModelInfo> {
        self.models.clone()
    }

    /// Get a specific model by ID
    #[plexus_method]
    async fn get_model(&self, id: String) -> Option<ModelInfo> {
        self.models.iter().find(|m| m.id == id).cloned()
    }
}
```

**Generates:**

```rust
// Parameter structs
#[derive(Deserialize, JsonSchema)]
struct GetModelParams { id: String }

// Plugin trait impl
impl PlexusPlugin for Registry {
    fn namespace(&self) -> &str { "registry" }

    fn schema(&self) -> PluginSchema {
        PluginSchema {
            namespace: "registry".into(),
            methods: vec![
                MethodSchema {
                    name: "list_models".into(),
                    description: Some("List all available models".into()),
                    params: None,
                    returns: Some(schema_for::<Vec<ModelInfo>>()),
                },
                MethodSchema {
                    name: "get_model".into(),
                    description: Some("Get a specific model by ID".into()),
                    params: Some(schema_for::<GetModelParams>()),
                    returns: Some(schema_for::<Option<ModelInfo>>()),
                },
            ],
            ..Default::default()
        }
    }

    async fn call(&self, method: &str, params: Value) -> Result<Value> {
        match method {
            "list_models" => {
                let result = self.list_models().await;
                Ok(serde_json::to_value(result)?)
            }
            "get_model" => {
                let p: GetModelParams = serde_json::from_value(params)?;
                let result = self.get_model(p.id).await;
                Ok(serde_json::to_value(result)?)
            }
            _ => Err(MethodNotFound(method.into()))
        }
    }
}
```

### Approach 2: Derive Macro with Declarative Methods

```rust
#[derive(Plugin)]
#[plugin(namespace = "registry")]
struct Registry {
    models: Vec<ModelInfo>,
}

// Methods defined separately, linked by naming convention
impl Registry {
    #[plugin_method]
    async fn list_models(&self) -> Vec<ModelInfo> { ... }
}
```

### Approach 3: Builder Pattern (No Macros)

```rust
let registry = PluginBuilder::new("registry")
    .method("list_models", |r: &Registry, _: ()| async {
        r.models.clone()
    })
    .method("get_model", |r: &Registry, p: GetModelParams| async {
        r.models.iter().find(|m| m.id == p.id).cloned()
    })
    .build(registry_instance);
```

**Pros:** No macro magic, explicit
**Cons:** Verbose, schema generation still needs JsonSchema derives

## Key Challenges

### 1. Async Method Handling

```rust
// Input
async fn foo(&self, x: i32) -> String

// Must handle:
// - Boxing the future
// - Lifetime of &self
// - Error propagation
```

**Solution:** Use `async_trait` pattern or `Pin<Box<dyn Future>>`.

### 2. Schema Generation for Complex Types

```rust
// This needs JsonSchema derive on ModelInfo
async fn get_model(&self, id: String) -> Option<ModelInfo>
```

**Solution:** Require `JsonSchema` bound, add compile error if missing:

```rust
const _: () = {
    fn assert_json_schema<T: schemars::JsonSchema>() {}
    fn check() {
        assert_json_schema::<ModelInfo>();
        assert_json_schema::<Option<ModelInfo>>();
    }
};
```

### 3. Streaming Responses

```rust
// How to handle streaming?
async fn chat(&self, prompt: String) -> impl Stream<Item = ChatChunk>
```

**Options:**
1. Detect `Stream` return type, generate streaming handler
2. Separate attribute: `#[plexus_method(streaming)]`
3. Wrapper type: `-> StreamingResponse<ChatChunk>`

### 4. Error Handling

```rust
// Method can fail
async fn get_model(&self, id: String) -> Result<ModelInfo, RegistryError>
```

**Solution:** Unwrap `Result`, map `Err` to JSON-RPC error:

```rust
match self.get_model(p.id).await {
    Ok(v) => Ok(serde_json::to_value(v)?),
    Err(e) => Err(PlexusError::from(e)),
}
```

### 5. State and Mutability

```rust
// Mutable method
async fn add_model(&mut self, model: ModelInfo)
```

**Solution:** Require `Arc<RwLock<Self>>` or `Arc<Mutex<Self>>` wrapper:

```rust
#[plexus_plugin(state = "arc_rwlock")]
impl Registry { ... }
```

## Recommended Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                     #[plexus_plugin]                            │
│                                                                 │
│  1. Parse impl block                                            │
│  2. For each #[plexus_method]:                                  │
│     - Extract name, params, return type                         │
│     - Generate param struct if needed                           │
│     - Add to method registry                                    │
│  3. Generate PlexusPlugin impl:                                 │
│     - namespace() -> &str                                       │
│     - schema() -> PluginSchema                                  │
│     - call(method, params) -> Result<Value>                     │
│  4. Generate schema_for::<T>() calls                            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Runtime Registration                          │
│                                                                 │
│  hub.register(Registry::new(...));                              │
│  // Plugin auto-registers via PlexusPlugin trait                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

## Feasibility Assessment

| Aspect | Feasibility | Notes |
|--------|-------------|-------|
| Method extraction | ✅ High | Proc macro can parse impl blocks |
| Param struct generation | ✅ High | Straightforward code gen |
| Schema generation | ✅ High | Leverage schemars JsonSchema |
| Dispatch generation | ✅ High | Match statement codegen |
| Async handling | ⚠️ Medium | Needs careful lifetime handling |
| Streaming | ⚠️ Medium | Requires convention or annotation |
| Error propagation | ✅ High | Map to PlexusError |
| Documentation | ✅ High | Extract from doc comments |

## Minimal Viable Implementation

### Step 1: Define the trait

```rust
#[async_trait]
pub trait PlexusPlugin: Send + Sync {
    fn namespace(&self) -> &str;
    fn schema(&self) -> PluginSchema;
    async fn call(&self, method: &str, params: Value) -> Result<Value, PlexusError>;
}
```

### Step 2: Create the proc macro crate

```rust
// plexus-macros/src/lib.rs
#[proc_macro_attribute]
pub fn plexus_plugin(attr: TokenStream, item: TokenStream) -> TokenStream {
    // Parse namespace from attr
    // Parse impl block from item
    // Generate PlexusPlugin impl
    // Return original impl + generated code
}

#[proc_macro_attribute]
pub fn plexus_method(_attr: TokenStream, item: TokenStream) -> TokenStream {
    // Just a marker, actual work done by plexus_plugin
    item
}
```

### Step 3: Use syn and quote

```rust
use syn::{parse_macro_input, ItemImpl, ImplItem, FnArg, ReturnType};
use quote::quote;

fn generate_plugin_impl(impl_block: &ItemImpl, namespace: &str) -> TokenStream {
    let self_ty = &impl_block.self_ty;
    let methods = extract_methods(impl_block);

    let dispatch_arms = methods.iter().map(|m| {
        let name = &m.name;
        let name_str = name.to_string();
        // ... generate match arm
    });

    quote! {
        #[async_trait::async_trait]
        impl PlexusPlugin for #self_ty {
            fn namespace(&self) -> &str { #namespace }

            async fn call(&self, method: &str, params: Value) -> Result<Value, PlexusError> {
                match method {
                    #(#dispatch_arms)*
                    _ => Err(PlexusError::MethodNotFound(method.into()))
                }
            }

            // ... schema generation
        }
    }.into()
}
```

## Conclusion

**Fully feasible** with proc macros. Recommended approach:

1. **Start simple**: Attribute macro on impl blocks
2. **Require JsonSchema**: All param/return types must derive it
3. **Handle streaming explicitly**: Use wrapper type or attribute
4. **Leverage existing crates**: `async_trait`, `schemars`, `serde`

The main complexity is in handling async lifetimes and streaming responses correctly. The schema generation is straightforward since `schemars` does the heavy lifting.

## Next Steps

1. Create `plexus-macros` proc-macro crate
2. Define `PlexusPlugin` trait in hub
3. Implement basic `#[plexus_plugin]` for sync methods
4. Add async support with `async_trait`
5. Add streaming support
6. Test with Registry as first target
