grpc_graphql_gateway 0.1.1

A Rust implementation of gRPC-GraphQL gateway - generates GraphQL execution code from gRPC services
# grpc_graphql_gateway

Bridge your gRPC services to GraphQL. This crate builds an `async-graphql` schema directly from protobuf descriptors (including custom `(graphql.*)` options) and routes requests to your gRPC backends via `tonic`.

## Highlights
- GraphQL **queries**, **mutations**, and **subscriptions** from gRPC methods (unary + server streaming)
- Dynamic schema generation from descriptor sets; optional pluck/rename/omit field directives
- Lazy or eager TLS/plain gRPC clients via a small builder API
- Axum HTTP + WebSocket (graphql-ws) integration out of the box
- Middleware and error-hook support for auth/logging/observability
- `protoc-gen-graphql-template` helper that emits a starter gateway file and prints example operations

## Install
```toml
[dependencies]
grpc-graphql-gateway = "0.1"
tokio = { version = "1", features = ["full"] }
tonic = "0.12"
```

## Generate descriptors
Use `tonic-build` (already wired in `build.rs`) to emit `graphql_descriptor.bin`:
```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("cargo:rerun-if-changed=proto/graphql.proto");
    let out_dir = std::env::var("OUT_DIR")?;
    let proto_include = std::env::var("PROTOC_INCLUDE").unwrap_or_else(|_| "/usr/local/include".to_string());

    tonic_build::configure()
        .build_server(false)
        .build_client(false)
        .file_descriptor_set_path(std::path::PathBuf::from(&out_dir).join("graphql_descriptor.bin"))
        .compile_protos(&["proto/graphql.proto"], &["proto", &proto_include])?;
    Ok(())
}
```

## Quick start
```rust
use grpc_graphql_gateway::{Gateway, GrpcClient, Result};

const DESCRIPTORS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/graphql_descriptor.bin"));

#[tokio::main]
async fn main() -> Result<()> {
    let builder = Gateway::builder()
        .with_descriptor_set_bytes(DESCRIPTORS)
        .add_grpc_client(
            "greeter.Greeter",
            GrpcClient::builder("http://127.0.0.1:50051").lazy(true).connect_lazy()?,
        );

    builder.serve("0.0.0.0:8888").await
}
```
- HTTP GraphQL endpoint: `POST /graphql`
- WebSocket subscriptions: `GET /graphql/ws` (graphql-ws)

### Built-in greeter example
The repository ships a runnable greeter service defined in `proto/greeter.proto` that exercises queries, mutations, subscriptions, and a resolver. Run both the gRPC backend and the GraphQL gateway (this is the default `cargo run` target):
```bash
cargo run            # or: cargo run --bin greeter
```
gRPC listens on `127.0.0.1:50051`, and GraphQL (HTTP + websocket) is at `http://127.0.0.1:8888/graphql` (`ws://127.0.0.1:8888/graphql/ws` for subscriptions).

Sample operations you can paste into GraphiQL or curl:
```graphql
query { hello(name: "GraphQL") { message meta { correlationId from { id displayName trusted } } } }
mutation { updateGreeting(input: { name: "GraphQL", salutation: "Howdy" }) { message } }
subscription { streamHello(name: "GraphQL") { message meta { correlationId } } }
query { user(id: "demo") { id displayName trusted } }
```

Upload mutation (uses the GraphQL `Upload` scalar; send as multipart):
```graphql
mutation ($file: Upload!) {
  uploadAvatar(input: { userId: "demo", avatar: $file }) { userId size }
}
```
```
curl http://127.0.0.1:8888/graphql \
  --form 'operations={ "query": "mutation ($file: Upload!) { uploadAvatar(input:{ userId:\"demo\", avatar:$file }) { userId size } }", "variables": { "file": null } }' \
  --form 'map={ "0": ["variables.file"] }' \
  --form '0=@./proto/greeter.proto;type=application/octet-stream'
```

Multi-upload mutation (list of `Upload`):
```graphql
mutation ($files: [Upload!]!) {
  uploadAvatars(input: { userId: "demo", avatars: $files }) { userId sizes }
}
```
```
curl http://127.0.0.1:8888/graphql \
  --form 'operations={ "query": "mutation ($files: [Upload!]!) { uploadAvatars(input:{ userId:\"demo\", avatars:$files }) { userId sizes } }", "variables": { "files": [null, null] } }' \
  --form 'map={ "0": ["variables.files.0"], "1": ["variables.files.1"] }' \
  --form '0=@./proto/greeter.proto;type=application/octet-stream' \
  --form '1=@./README.md;type=text/plain'
```

## How it fits together
A quick view of how protobuf descriptors, the generated schema, and gRPC clients are wired to serve GraphQL over HTTP and WebSocket:
```mermaid
flowchart LR
  subgraph Client["GraphQL clients"]
    http["HTTP POST /graphql"]
    ws["WebSocket /graphql/ws"]
  end

  subgraph Gateway["grpc-graphql-gateway"]
    desc["Descriptor set\n(graphql_descriptor.bin)"]
    schema["SchemaBuilder\nasync-graphql schema"]
    mux["ServeMux\nAxum routes + middlewares\n(optional error hooks)"]
    pool["GrpcClient pool\n(lazy/eager, TLS/plain)"]
  end

  subgraph Services["Your gRPC backends"]
    svc1["Service 1"]
    svc2["Service N"]
  end

  http --> mux
  ws --> mux
  desc --> schema
  pool --> schema
  schema --> mux
  mux --> pool
  pool --> svc1
  pool --> svc2
```

## Proto annotations (from `proto/graphql.proto`)
- Service defaults:
  ```proto
  service Greeter {
    option (graphql.service) = { host: "127.0.0.1:50051", insecure: true };
    rpc SayHello(HelloRequest) returns (HelloReply) {
      option (graphql.schema) = { type: QUERY, name: "hello" };
    }
  }
  ```
- Method options (`graphql.schema`):
  - `type`: QUERY | MUTATION | SUBSCRIPTION | RESOLVER
  - `name`: override GraphQL field name
  - `request.name`: wrap input as a single argument
  - `response.pluck`: expose a nested field instead of the whole message
  - `response.required`: mark return type non-null
- Field options (`graphql.field`):
  - `required`: non-null in GraphQL
  - `name`: rename field
  - `omit`: skip field entirely

## Using the template generator
`protoc-gen-graphql-template` emits a ready-to-run `graphql_gateway.rs` that wires clients, logs discovered operations, and prints example queries/mutations/subscriptions.
```bash
protoc \
  --plugin=protoc-gen-graphql-template=target/debug/protoc-gen-graphql-template \
  --graphql-template_out=. \
  --proto_path=proto \
  your_service.proto
```
Open the generated file, point endpoints at your services, and run it.

### Example: Greeter
```bash
cargo build --bin protoc-gen-graphql-template

protoc \
  --plugin=protoc-gen-graphql-template=target/debug/protoc-gen-graphql-template \
  --graphql-template_out=./generated \
  --proto_path=proto \
  proto/greeter.proto

rustc ./generated/graphql_gateway.rs -L target/debug/deps
./graphql_gateway
```
The generated gateway will log which queries/mutations/subscriptions it found and print example GraphQL operations such as:
```
Example queries:
  query { hello }
Example mutations:
  mutation { createUser }
Example subscriptions:
  subscription { streamHello }
```

## Middleware and error hooks
Attach middlewares and inspect errors:
```rust
use grpc_graphql_gateway::{Gateway, GrpcClient};
use grpc_graphql_gateway::middleware::LoggingMiddleware;

let builder = Gateway::builder()
    .with_descriptor_set_bytes(DESCRIPTORS)
    .add_grpc_client("service", GrpcClient::builder("http://localhost:50051").connect_lazy()?)
    .add_middleware(LoggingMiddleware);
```
`GatewayBuilder::with_error_handler` lets you capture `GraphQLError`s before responses are returned.

## Type mapping (protobuf -> GraphQL)
- `string` -> `String`
- `bool` -> `Boolean`
- `int32`/`uint32` -> `Int`
- `int64`/`uint64` -> `String` (to avoid precision loss)
- `float`/`double` -> `Float`
- `bytes` -> `Upload` (inputs via multipart) / `String` (base64 responses)
- `repeated` -> `[T]`
- `message` -> `Object` / `InputObject`
- `enum` -> `Enum`

`Upload` inputs follow the GraphQL multipart request spec and are valid on mutations.

## GraphQL Federation

This gateway supports [Apollo Federation v2](https://www.apollographql.com/docs/federation/), enabling you to compose multiple GraphQL services into a unified supergraph.

### Enabling Federation

Enable federation support when building your gateway:

```rust
let gateway = Gateway::builder()
    .with_descriptor_set_bytes(DESCRIPTORS)
    .enable_federation()  // Enable federation support
    .add_grpc_client("service", client)
    .build()?;
```

### Defining Entities

Mark protobuf messages as federated entities using the `graphql.entity` option:

```protobuf
message User {
  option (graphql.entity) = {
    keys: "id"              // Primary key field
    resolvable: true        // This service can resolve this entity
  };
  
  string id = 1 [(graphql.field) = { required: true }];
  string email = 2;
  string name = 3;
}
```

**Multiple Keys**: Support composite keys and multiple key definitions:
```protobuf
message Product {
  option (graphql.entity) = {
    keys: "upc"           // Single field key
    keys: "sku type"      // Composite key (multiple fields)
  };
  
  string upc = 1;
  string sku = 2;
  string type = 3;
}
```

### Extending Entities

Extend entities from other services using `extend: true`:

```protobuf
message UserExtension {
  option (graphql.entity) = {
    extend: true          // This extends User from another service
    keys: "id"
  };
  
  string id = 1 [(graphql.field) = { 
    external: true        // This field is defined in another service
    required: true 
  }];
  
  repeated Review reviews = 2 [(graphql.field) = {
    requires: "id"        // This field requires `id` from the base entity
  }];
}
```

### Field-Level Federation Directives

#### `@external`
Mark fields that are defined in another service:
```protobuf
string user_id = 1 [(graphql.field) = { external: true }];
```

#### `@requires`
Specify fields needed from other services to resolve this field:
```protobuf
int32 total_reviews = 2 [(graphql.field) = { requires: "id email" }];
```

#### `@provides`
Indicate which fields this field provides to the supergraph:
```protobuf
User author = 3 [(graphql.field) = { provides: "id name" }];
```

#### `@shareable`
Mark fields that can be resolved from multiple subgraphs (Federation v2 requirement):
```protobuf
string email = 2 [(graphql.field) = { shareable: true }];
string name = 3 [(graphql.field) = { shareable: true }];
```
**Note**: In Apollo Federation v2, fields that appear in multiple subgraphs must be marked as `@shareable`, otherwise the supergraph composition will fail.

### Entity Resolution

When federation is enabled and entities are defined, the gateway automatically exposes the `_entities` query for entity resolution. The current implementation returns entity representations as-is; for production use, implement a custom `EntityResolver`:

```rust
use grpc_graphql_gateway::{EntityResolver, FederationConfig};

struct MyEntityResolver {
    // Your gRPC clients or other resolution logic
}

#[async_trait::async_trait]
impl EntityResolver for MyEntityResolver {
    async fn resolve_entity(
        &self,
        entity_config: &EntityConfig,
        representation: &IndexMap<Name, Value>,
    ) -> Result<Value> {
        // 1. Extract key fields from representation
        // 2. Call appropriate gRPC service
        // 3. Return resolved entity
        todo!("Implement entity resolution")
    }
}
```

### Federation Schema Features

When federation is enabled, the gateway automatically:
- ✅ Adds `@key` directives to entity types
- ✅ Adds `@extends` directive to entity extensions
- ✅ Adds `@external`, `@requires`, `@provides`, `@shareable` directives to fields
- ✅ Exposes `_entities(representations: [_Any!]!)` query
- ✅ Exposes `_service { sdl }` query (via async-graphql)
- ✅ Registers entity types in the `_Entity` union

### Example: Federated Microservices

See `proto/federation_example.proto` for a complete example showing:
- User service (defines the User entity)
- Product service (defines Product entity, references User)
- Review service (extends both User and Product entities)

Each service can be deployed independently while participating in a unified federated graph.

### Running the federation example with Apollo Router

Use Apollo Router in front of the federated subgraph exposed by `cargo run --bin federation`:

1. Start the gRPC services + three GraphQL subgraphs:
   ```bash
   cargo run --bin federation
   ```
2. Compose a supergraph schema (requires the `rover` CLI):
   ```bash
   ./examples/federation/compose_supergraph.sh
   # writes: examples/federation/supergraph.graphql
   ```
3. Run Apollo Router with the composed file (default port `4000`):
   ```bash
   router --supergraph examples/federation/supergraph.graphql --dev
   ```
4. Send requests to the router at `http://127.0.0.1:4000/` (or hit the subgraphs directly if you skip the router). The router hides the `_entities` helper; if you want to call `_entities` directly, target the owning subgraph (e.g., `http://127.0.0.1:8891/graphql` for `federation_example_User`).

### Federation Best Practices

1. **Define clear entity boundaries**: Each service should own its entities
2. **Use composite keys when needed**: For entities with multiple identifying fields
3. **Mark external fields correctly**: Avoid duplicating field resolution logic
4. **Use `@provides` sparingly**: Only when you're returning partial entities
5. **Keep `@requires` minimal**: Only specify fields you actually need
6. **Mark shared fields as `@shareable`**: When the same field is resolved in multiple subgraphs, explicitly mark it with `shareable: true`


## Development
- Format: `cargo fmt`
- Lint/tests: `cargo test`
- A runnable example lives at `examples/greeter`, wired up to `proto/greeter.proto` (query/mutation/subscription/resolver).

## License
MIT. See [LICENSE](./LICENSE).