# Guided Error Responses

## Overview

The substrate implements **guided error responses** that help clients bootstrap their RPC connection. When a client calls an unknown activation, the error response includes a `try` field containing an exact JSON-RPC request to try next.

## The Problem

JSON-RPC servers typically return generic errors:

```json
{"code": -32601, "message": "Method not found"}
```

This tells the client nothing useful. They must:
1. Read documentation
2. Guess at available methods
3. Trial-and-error their way to a working request

## The Solution

Guided errors include actionable context:

```json
{
  "code": -32601,
  "message": "Activation 'foo' not found",
  "data": {
    "try": {"jsonrpc": "2.0", "id": 1, "method": "plexus_schema"},
    "activation": "foo",
    "available_activations": ["arbor", "bash", "cone", "health"]
  }
}
```

The `try` field is:
- **Copy-pasteable**: Users can immediately try the suggested request
- **Machine-readable**: Clients can auto-retry with the suggested request
- **Contextual**: Points to discovery (`plexus_schema`) not a random method

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                     JSON-RPC Request                             │
│                    {"method": "foo_bar"}                         │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              GuidedErrorMiddleware (RpcServiceT)                 │
│                                                                  │
│  1. Parse method name: "foo_bar" → namespace="foo"               │
│  2. Check: Is "foo" in ActivationRegistry?                       │
│  3. NO → Return GuidedError::activation_not_found()              │
│     YES → Pass to inner service                                  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    jsonrpsee RpcService                          │
│                   (method routing, etc.)                         │
└─────────────────────────────────────────────────────────────────┘
```

## Implementation

### Key Types

**`src/plexus/errors.rs`**:

```rust
/// A suggested JSON-RPC request to try next
pub struct TryRequest {
    pub jsonrpc: String,
    pub id: u64,
    pub method: String,
    pub params: Vec<Value>,
}

/// Error data with `try` field for guided discovery
pub struct GuidedErrorData {
    pub try_request: TryRequest,
    pub context: Value,  // Flattened into the data object
}

/// Builder for creating guided JSON-RPC errors
pub struct GuidedError;

impl GuidedError {
    pub fn activation_not_found(activation: &str, available: Vec<String>) -> ErrorObjectOwned;
    pub fn method_not_found(...) -> ErrorObjectOwned;
    pub fn invalid_params(...) -> ErrorObjectOwned;
    pub fn parse_error(message: &str) -> ErrorObjectOwned;
}
```

**`src/plexus/middleware.rs`**:

```rust
/// Tracks known activation namespaces
pub struct ActivationRegistry {
    pub activations: Vec<String>,
}

/// Middleware that intercepts requests BEFORE they hit jsonrpsee routing
pub struct GuidedErrorMiddleware<S> {
    inner: S,
    registry: Arc<ActivationRegistry>,
}

impl<'a, S> RpcServiceT<'a> for GuidedErrorMiddleware<S> {
    fn call(&self, req: Request<'a>) -> Self::Future {
        // Check activation exists BEFORE calling inner service
        if let Some(guided_error) = check_activation_exists(&method_name, &req_id, &registry) {
            return guided_error;
        }
        // Otherwise delegate to inner service
        inner.call(req).await
    }
}
```

### Wiring in `main.rs`

```rust
// Create activation registry from plexus
let activation_namespaces: Vec<String> = activations.iter()
    .map(|a| a.namespace.clone())
    .collect();
let registry = Arc::new(ActivationRegistry::new(activation_namespaces));

// Build RPC middleware
let rpc_middleware = RpcServiceBuilder::new()
    .layer_fn(move |service| {
        GuidedErrorMiddleware::new(service, registry.clone())
    });

// Start server with middleware
let server = Server::builder()
    .set_rpc_middleware(rpc_middleware)
    .build(addr)
    .await?;
```

## Challenges and Ugliness

### 1. Subscriptions Don't Return Errors Through `MethodResponse`

**Problem**: We initially tried to intercept errors *after* the inner service returned:

```rust
let response = inner.call(req).await;
if response.is_error() {
    // Enrich the error
}
```

This doesn't work for subscriptions. When jsonrpsee handles a subscription to an unknown method, the `MethodResponse` has:
- `is_error() == false`
- `is_success() == false`

The actual "Method not found" error is sent through a different channel (the subscription error notification).

**Solution**: We intercept *before* calling the inner service:

```rust
// Check BEFORE calling inner service
if let Some(guided_error) = check_activation_exists(&method_name, ...) {
    return guided_error;  // Short-circuit
}
let response = inner.call(req).await;
```

This is uglier because we duplicate the "does this method exist?" logic that jsonrpsee already has internally.

### 2. Method Name Parsing Heuristics

**Problem**: We parse `foo_bar` as activation=`foo`, method=`bar`. But what about:
- `plexus_schema` - Should NOT be treated as activation "plexus"
- `some_activation_with_underscores_method` - Which underscore is the delimiter?

**Current Solution**:
- Skip namespace "plexus" explicitly
- Use `splitn(2, '_')` to only split on first underscore

**Ugliness**: This assumes all activations use single-word namespaces. If an activation is named `my_activation`, it would never match.

### 3. Duplicated Activation Registry

**Problem**: The middleware needs to know which activations exist. But the `Plexus` owns the activations, and we can't borrow from it after converting to RPC module.

**Solution**: Extract activation names into a separate `ActivationRegistry` before `plexus.into_rpc_module()` consumes the plexus:

```rust
let activation_namespaces: Vec<String> = activations.iter()
    .map(|a| a.namespace.clone())
    .collect();
let registry = Arc::new(ActivationRegistry::new(activation_namespaces));

let module = plexus.into_rpc_module()?;  // Consumes plexus
```

**Ugliness**: The registry is a snapshot at startup time. If activations could be added/removed at runtime, the registry would be stale.

### 4. jsonrpsee's `RpcServiceT` Lifetime Complexity

**Problem**: The `RpcServiceT<'a>` trait requires careful lifetime handling:

```rust
impl<'a, S> RpcServiceT<'a> for GuidedErrorMiddleware<S>
where
    S: RpcServiceT<'a> + Send + Sync + Clone + 'static,
{
    type Future = Pin<Box<dyn Future<Output = MethodResponse> + Send + 'a>>;

    fn call(&self, req: Request<'a>) -> Self::Future {
        let req_id = req.id.clone();  // Must clone, can't borrow
        // ...
    }
}
```

We must clone `req.id` because the async block outlives the borrow. The `Request` has borrowed data that can't be moved into the async block.

### 5. No Post-Hoc Error Enrichment for Valid Activations

**Problem**: If someone calls `bash_nonexistent`, the activation "bash" exists, so our middleware lets it through. jsonrpsee then returns a generic "Method not found" without our guided error data.

**Why**: We can't easily enrich errors *after* they're generated because:
1. Subscription errors don't come through `MethodResponse::is_error()`
2. We'd need to know the activation's available methods to suggest one

**Future Work**: The middleware could be extended to:
1. Maintain a registry of `{activation -> [methods]}`
2. Check both activation AND method existence
3. Generate `GuidedError::method_not_found()` with method suggestions

### 6. Error Code Semantics

**Problem**: We use `-32601` (Method not found) for "Activation not found". Is that correct?

JSON-RPC 2.0 spec defines:
- `-32601`: Method not found - The method does not exist / is not available

**Rationale**: An unknown activation means the method doesn't exist. Using a custom error code would require clients to understand our error taxonomy.

**Alternative Considered**: Using a custom code like `-32001` with a different meaning. Rejected because standard clients expect `-32601` for "I don't know that method."

## Testing

```rust
#[tokio::test]
async fn test_guided_error_includes_try_field() {
    let client = create_client().await.unwrap();

    // Try foo_bar - 'foo' is not a valid activation
    let result = client
        .subscribe::<Value, _>("foo_bar", rpc_params![], "unsubscribe_foo")
        .await;

    let err = result.expect_err("Should have failed");
    let err_str = format!("{:?}", err);

    assert!(err_str.contains("Activation 'foo' not found"));
    assert!(err_str.contains("plexus_schema"));  // try field
    assert!(err_str.contains("available_activations"));
}
```

## Configuration

The server port is configurable via environment variable:

```bash
SUBSTRATE_PORT=4445 cargo run
```

Tests also respect this:

```bash
SUBSTRATE_PORT=4445 cargo test --test rpc_integration
```

## Related Documents

- [Self-Documenting RPC](./16680998353176467711_self-documenting-rpc.md) - Original design vision
- [Help System Architecture](./16680975572170886655_help-system.md) - Progressive disclosure design
- [Substrate Architecture](./16680975879064433663_substrate-architecture.md) - Overall system structure
