Skip to content

Commit 49220a9

Browse files
committed
Move protoc-gen-grafbase-subgraph over from grafbase/grafbase repo
It's the protoc plugin companion to the gRPC extension. It belongs here.
1 parent 0bc8a34 commit 49220a9

34 files changed

+9037
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: protoc-gen-grafbase-subgraph-release
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
tags:
7+
- 'protoc-gen-grafbase-subgraph-*'
8+
9+
permissions:
10+
contents: write
11+
12+
env:
13+
CARGO_PROFILE_DEV_DEBUG: 0
14+
CARGO_PROFILE_TEST_DEBUG: 0
15+
CARGO_TERM_COLOR: 'always'
16+
17+
jobs:
18+
build-and-upload:
19+
name: Build and upload artifact
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
platform:
24+
[
25+
{ 'target': 'x86_64-unknown-linux-musl', 'runner': 'depot-ubuntu-24.04-8' },
26+
{ 'target': 'aarch64-unknown-linux-musl', 'runner': 'depot-ubuntu-24.04-arm-8' },
27+
{ 'target': 'aarch64-apple-darwin', 'runner': 'depot-macos-latest' },
28+
{ 'target': 'x86_64-pc-windows-msvc', 'runner': 'depot-windows-2022-8' },
29+
]
30+
runs-on: ${{ matrix.platform.runner }}
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v4
34+
35+
- name: Install Rust
36+
uses: ./.github/actions/install-rust
37+
with:
38+
target: ${{ matrix.platform.target }}
39+
40+
- name: Install musl-tools
41+
if: ${{ contains(matrix.platform.target, 'linux') }}
42+
shell: bash
43+
run: |
44+
sudo apt-get install musl musl-tools
45+
# This seems like a horrible hack that might come back to bite, but lets see!
46+
sudo ln -s /bin/g++ /bin/musl-g++
47+
sudo ln -s /bin/g++ /bin/aarch64-linux-musl-g++
48+
49+
- name: Build binaries
50+
shell: bash
51+
run: |
52+
cargo build -p protoc-gen-grafbase-subgraph --release
53+
54+
- name: Prepare binary (Unix)
55+
if: matrix.platform.target != 'x86_64-pc-windows-msvc'
56+
run: |
57+
cd target/release
58+
tar -czf protoc-gen-grafbase-subgraph-${{ matrix.platform.target }}.tar.gz protoc-gen-grafbase-subgraph
59+
60+
- name: Prepare binary (Windows)
61+
if: matrix.platform.target == 'x86_64-pc-windows-msvc'
62+
run: |
63+
cd target/release
64+
7z a protoc-gen-grafbase-subgraph-${{ matrix.platform.target }}.zip protoc-gen-grafbase-subgraph.exe
65+
66+
- name: Upload assets (Unix)
67+
if: matrix.platform.target != 'x86_64-pc-windows-msvc'
68+
uses: actions/upload-artifact@v4
69+
with:
70+
name: protoc-gen-grafbase-subgraph-${{ matrix.platform.target }}
71+
path: target/release/protoc-gen-grafbase-subgraph-${{ matrix.platform.target }}.tar.gz
72+
73+
- name: Upload assets (Windows)
74+
if: matrix.platform.target == 'x86_64-pc-windows-msvc'
75+
uses: actions/upload-artifact@v4
76+
with:
77+
name: protoc-gen-grafbase-subgraph-${{ matrix.platform.target }}
78+
path: target/release/protoc-gen-grafbase-subgraph-${{ matrix.platform.target }}.zip
79+
80+
create-release:
81+
name: Create GitHub release
82+
needs: build-and-upload
83+
runs-on: ubuntu-latest
84+
steps:
85+
- name: Checkout code
86+
uses: actions/checkout@v4
87+
88+
- name: Get version from tag
89+
id: get_version
90+
run: echo "VERSION=${GITHUB_REF#refs/tags/protoc-gen-grafbase-subgraph-}" >> $GITHUB_OUTPUT
91+
92+
- name: Download all artifacts
93+
uses: actions/download-artifact@v4
94+
with:
95+
path: artifacts
96+
97+
- name: Create GitHub Release
98+
id: create_release
99+
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2
100+
with:
101+
draft: false
102+
prerelease: false
103+
files: |
104+
artifacts/protoc-gen-grafbase-subgraph-x86_64-unknown-linux-musl/protoc-gen-grafbase-subgraph-x86_64-unknown-linux-musl.tar.gz
105+
artifacts/protoc-gen-grafbase-subgraph-aarch64-unknown-linux-musl/protoc-gen-grafbase-subgraph-aarch64-unknown-linux-musl.tar.gz
106+
artifacts/protoc-gen-grafbase-subgraph-aarch64-apple-darwin/protoc-gen-grafbase-subgraph-aarch64-apple-darwin.tar.gz
107+
artifacts/protoc-gen-grafbase-subgraph-x86_64-pc-windows-msvc/protoc-gen-grafbase-subgraph-x86_64-pc-windows-msvc.zip
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/out/
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## 0.2.0 - 2025-07-24
2+
3+
### Added
4+
5+
- **GraphQL Directive Support**: Added support for all directive options defined in `options.proto`:
6+
- `object_directives` and `input_object_directives` for object-level directives
7+
- `input_field_directives` and `output_field_directives` for field-level directives
8+
- `enum_directives` for enum-level directives
9+
- `enum_value_directives` for enum value-level directives
10+
- **Query Field Mapping**: Added support for mapping gRPC service methods to GraphQL Query fields instead of Mutations:
11+
- `is_graphql_query_field` and `is_graphql_mutation_field` options on individual methods
12+
- `graphql_default_to_query_fields` and `graphql_default_to_mutation_fields` options on services to make all methods default to Query (or Mutation) fields
13+
- **Query Type Generation**: The generator now creates a `type Query` in addition to `type Mutation` and `type Subscription` based on method configurations
14+
15+
## 0.1.0 - 2025-04-15
16+
17+
- Initial release. The output matches the directives expected by version 0.1.0 of the grpc extension.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "protoc-gen-grafbase-subgraph"
3+
version = "0.1.0"
4+
edition.workspace = true
5+
license.workspace = true
6+
homepage.workspace = true
7+
keywords.workspace = true
8+
repository.workspace = true
9+
10+
[dependencies]
11+
grafbase-workspace-hack.workspace = true
12+
indexmap.workspace = true
13+
paste.workspace = true
14+
protobuf.workspace = true
15+
protobuf-support.workspace = true
16+
17+
[dev-dependencies]
18+
insta.workspace = true
19+
tempfile.workspace = true
20+
walkdir.workspace = true
21+
22+
[lints]
23+
workspace = true
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# protoc-gen-grafbase-subgraph
2+
3+
This binary crate is a protoc plugin that generates a GraphQL subgraph to be used in concert with the Grafbase gRPC extension.
4+
5+
## Installation
6+
7+
Download the relevant binary from your platform from the [GitHub releases](https://github.com/grafbase/extensions/releases?q=protoc-gen-grafbase-subgraph&expanded=true).
8+
9+
## Usage with buf
10+
11+
Make sure the binary is in your PATH, then configure it in your `buf.gen.yaml` file:
12+
13+
```yaml
14+
version: v2
15+
managed:
16+
enabled: true
17+
plugins:
18+
- local: protoc-gen-grafbase-subgraph
19+
out: .
20+
inputs:
21+
- directory: proto
22+
```
23+
24+
## Usage with protoc
25+
26+
Make sure the binary is in your PATH, then run protoc with the `--grafbase-subgraph_out` flag. For example:
27+
28+
```
29+
protoc --grafbase-subgraph_out=. proto/*.proto
30+
```
31+
32+
## Custom options
33+
34+
To make use of custom options, you will need to import the option definitions. They are defined in the `grafbase.options` package, available in the "options.proto" file in this project. Also note that since this module imports ["google/protobuf/descriptor.proto"](https://github.com/protocolbuffers/protobuf/blob/8228ee42b512cc330971e61bc9b86935a59f3477/src/google/protobuf/descriptor.proto), that one has to be present in your project as well.
35+
36+
With `buf`, you can make use of the [inputs.git_repo](https://buf.build/docs/configuration/v2/buf-gen-yaml/#git_repo) option.
37+
38+
### Mapping RPC methods to Query fields
39+
40+
By default, RPC methods are mapped to fields on Mutation. But you can also map them to fields on Query:
41+
42+
```protobuf
43+
import "grafbase/options.proto";
44+
45+
service SearchService {
46+
rpc Search(SearchRequest) returns (SearchResponse) {
47+
option (grafbase.graphql.is_query_field) = true;
48+
}
49+
}
50+
```
51+
52+
### Default all service methods to Query fields
53+
54+
```protobuf
55+
import "grafbase/options.proto";
56+
57+
service SearchService {
58+
option (grafbase.graphql.default_to_query_fields) = true;
59+
60+
rpc Search(SearchRequest) returns (SearchResponse) {
61+
option (grafbase.graphql.method_field_directives) = "@lookup";
62+
}
63+
}
64+
```
65+
66+
Analogous to the "is_query_field" option above, there is also a "is_mutation_field" option to map RPC methods to fields on Mutation when your service defaults to Query:
67+
68+
```protobuf
69+
import "grafbase/options.proto";
70+
71+
service SearchService {
72+
option (grafbase.graphql.default_to_query_fields) = true;
73+
74+
rpc Search(SearchRequest) returns (SearchResponse) {
75+
option (grafbase.graphql.is_mutation_field) = true;
76+
}
77+
}
78+
```
79+
80+
### Adding GraphQL directives on types, fields and enum values
81+
82+
```protobuf
83+
import "grafbase/options.proto";
84+
85+
message MyMessage {
86+
option (grafbase.graphql.output_object_directives) = "@key(fields: \"id\")";
87+
88+
string id = 1 [(grafbase.graphql.output_field_directives) = "@deprecated"];
89+
}
90+
91+
enum Color {
92+
option (grafbase.graphql.enum_directives) = "@deprecated";
93+
94+
RED = 0 [(grafbase.graphql.enum_value_directives) = "@deprecated @tag(name: \"private\")"];
95+
GREEN = 1 [(grafbase.graphql.enum_value_directives) = "@deprecated"];
96+
BLUE = 2 [(grafbase.graphql.enum_value_directives) = "@deprecated"];
97+
}
98+
```
99+
100+
## Limitations
101+
102+
- Methods with client streaming are supported, but only one message can be sent from the client side.
103+
104+
## Contributing
105+
106+
### Releasing
107+
108+
To release a new version of the binary:
109+
110+
1. Update the version number in `Cargo.toml`
111+
2. Create a tag with the format `protoc-gen-grafbase-subgraph-X.Y.Z` (e.g., `protoc-gen-grafbase-subgraph-0.2.0`)
112+
3. Push the tag to GitHub:
113+
```
114+
git tag protoc-gen-grafbase-subgraph-X.Y.Z
115+
git push origin protoc-gen-grafbase-subgraph-X.Y.Z
116+
```
117+
4. The GitHub Actions workflow will automatically build the binary for multiple platforms and create a release with the artifacts
118+
119+
## Prior art
120+
121+
- https://github.com/ysugimoto/grpc-graphql-gateway generates a graphql-go based GraphQL server that proxies to a gRPC server.
122+
- https://github.com/danielvladco/go-proto-gql another project that does the same. Unmaintained.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
syntax = "proto2";
2+
3+
import "google/protobuf/descriptor.proto";
4+
5+
package grafbase.graphql;
6+
7+
extend google.protobuf.MessageOptions {
8+
optional string object_directives = 58301;
9+
optional string input_object_directives = 58302;
10+
}
11+
12+
extend google.protobuf.FieldOptions {
13+
optional string output_field_directives = 58301;
14+
optional string input_field_directives = 58302;
15+
}
16+
17+
extend google.protobuf.EnumOptions {
18+
optional string enum_directives = 58301;
19+
}
20+
21+
extend google.protobuf.EnumValueOptions {
22+
optional string enum_value_directives = 58301;
23+
}
24+
25+
extend google.protobuf.ServiceOptions {
26+
optional bool default_to_query_fields = 58301;
27+
}
28+
29+
extend google.protobuf.MethodOptions {
30+
optional string directives = 58301;
31+
optional bool is_query = 58302;
32+
optional bool is_mutation = 58303;
33+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::fmt;
2+
3+
pub(crate) fn display_fn(f: impl Fn(&mut fmt::Formatter<'_>) -> fmt::Result) -> impl fmt::Display {
4+
struct DisplayFn<F>(F);
5+
6+
impl<F> fmt::Display for DisplayFn<F>
7+
where
8+
F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
9+
{
10+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11+
(self.0)(f)
12+
}
13+
}
14+
15+
DisplayFn(f)
16+
}
17+
18+
pub(crate) fn grpc_path_to_graphql_name(path: &str) -> impl fmt::Display {
19+
display_fn(|f| {
20+
let mut segments = path
21+
.split_terminator('.')
22+
.filter(|segment| !segment.is_empty())
23+
.peekable();
24+
25+
while let Some(segment) = segments.next() {
26+
f.write_str(segment)?;
27+
28+
if segments.peek().is_some() {
29+
f.write_str("_")?;
30+
}
31+
}
32+
33+
Ok(())
34+
})
35+
}

0 commit comments

Comments
 (0)