folk-plugin-grpc 0.1.3

gRPC plugin for Folk — unary call passthrough to PHP workers via tonic
Documentation
# folk-plugin-grpc

gRPC plugin for Folk — unary call passthrough to PHP workers via tonic. No Rust protobuf codegen required.

> **Status:** in active development. See [folk-spec]https://github.com/Folk-Project/folk-spec for the roadmap.

## Requirements

- Rust 1.85+
- [folk-api]https://github.com/Folk-Project/folk-api
- PHP with `google/protobuf` or the `protobuf` PECL extension

## Installation

```toml
# Cargo.toml
folk-plugin-grpc = "0.1"
```

## Quick start

Add to `folk.build.toml`:

```toml
[[plugin]]
crate_name = "folk-plugin-grpc"
version = "0.1"
config_key = "grpc"
```

Configure in `folk.toml`:

```toml
[grpc]
listen = "0.0.0.0:50051"
reflection = false
```

Write a PHP gRPC service class:

```php
<?php
use Folk\Sdk\Grpc\GrpcModeHandler;

class GreeterService implements GrpcModeHandler
{
    public function call(string $service, string $method, string $payload): string
    {
        // $service = "helloworld.Greeter"
        // $method  = "SayHello"
        // $payload = raw protobuf bytes

        $request = new \Helloworld\HelloRequest();
        $request->mergeFromString($payload);

        $reply = new \Helloworld\HelloReply();
        $reply->setMessage('Hello, ' . $request->getName());

        return $reply->serializeToString();
    }
}
```

Register in the worker entry point:

```php
$loop = new \Folk\Sdk\Worker\WorkerLoop();
$loop->register('grpc.call', function (mixed $params): string {
    $handler = new GreeterService();
    return $handler->call($params['service'], $params['method'], $params['payload']);
});
$loop->run();
```

Test with grpcurl:

```bash
grpcurl -plaintext localhost:50051 list
# helloworld.Greeter

grpcurl -plaintext -d '{"name": "Folk"}' localhost:50051 helloworld.Greeter/SayHello
# { "message": "Hello, Folk" }
```

## Configuration

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `listen` | `SocketAddr` | `"0.0.0.0:50051"` | Address and port for the gRPC server. |
| `reflection` | `bool` | `false` | Enable gRPC server reflection (for grpcurl/Postman discovery). |

## How it works

### Passthrough model

The key design decision: **Rust does not decode or encode protobuf messages.** It treats protobuf payloads as opaque bytes. PHP handles all protobuf serialization using its own generated classes.

This means:
- No `.proto` files needed at build time in Rust.
- No protobuf codegen step in the Rust build.
- PHP owns the service contract entirely.
- Adding or changing protobuf services requires no Rust recompilation.

### Request flow

1. A gRPC client sends an HTTP/2 request to `/{ServiceName}/{MethodName}`.
2. The plugin extracts the service and method names from the URI path.
3. The 5-byte gRPC framing (compression flag + length prefix) is stripped.
4. The raw protobuf bytes are wrapped in a `GrpcEnvelope`:
   ```
   { service: "helloworld.Greeter", method: "SayHello", payload: <bytes> }
   ```
5. The envelope is serialized to MessagePack and sent to a PHP worker via `executor.execute()`.
6. The PHP worker decodes protobuf, executes logic, and returns serialized protobuf bytes.
7. The plugin re-applies gRPC framing and returns the response.

### Error handling

- Bad path format → gRPC status `UNIMPLEMENTED` (12)
- Body read failure → gRPC status `INTERNAL` (13)
- Worker execution error → gRPC status `INTERNAL` (13)

### RPC method

**`grpc.services`** — Lists registered gRPC service names via the admin socket.

## License

MIT