buf-check-strictrpc

This repository contains a custom buf
check plugin designed to enforce a stricter set of lint
rules for Protobuf RPCs, particularly when using frameworks like gRPC or
Connect.
While the buf
linter enforces some RPC_
rules, this plugin introduces additional constraints:
- Files containing a service definition must have a filename ending with
_service.proto
.
- Each file can only contain one
service
definition, which must appear at the top before any
message definitions.
- For every RPC method, there must be exactly two associated messages, named after the method and
suffixed with
Request
and Response
.
- Optionally, a third message may be present, which must be suffixed with
ErrorDetails
.
- Messages in the file must be listed in the same order as their corresponding RPC methods.
Plugin options
Useful options to configure the plugin:
Option |
Default |
Description |
disable_streaming |
false |
Disables streaming RPCs. (Some people just don't like em) |
allow_protobuf_empty |
false |
Allows usage of google.protobuf.Empty in requests or responses. |
Example
Given a dragon_service.proto
file:
service DragonService {
rpc GetDragon(GetDragonRequest) returns (GetDragonResponse) {}
rpc TrainDragon(TrainDragonRequest) returns (TrainDragonResponse) {}
}
message GetDragonRequest {}
message GetDragonResponse {}
message TrainDragonRequest {}
message TrainDragonResponse {}
// This message is optional.
message TrainDragonErrorDetails {}
Preparing a WASM binary
Compile the Go plugin to a WASM binary:
GOOS=wasip1 GOARCH=wasm go build -o buf-check-strictrpc.wasm main.go
Let's try it out!
wasmtime buf-check-strictrpc.wasm --help
Enforces an opinionated structure for RPC definitions, including strict file naming, single-service per file, and consistent request/response message naming patterns.
Commands:
check
info
list-categories
list-rules
Flags:
--format string The format to use for requests, responses, and specs. Must be one of ["binary", "json"]. (default "binary")
--protocol Print the protocol to stdout and exit.
--spec Print the spec to stdout in the specified format and exit.
-h, --help Show this help.
Neat, let's see what the protocol looks like:
wasmtime buf-check-strictrpc.wasm --protocol
1
Okay, now let's try something more interesting, what does the spec look like?
wasmtime buf-check-strictrpc.wasm --spec --format=json | jq
Output:
{
"procedures": [
{
"path": "/buf.plugin.check.v1.CheckService/Check",
"args": ["check"]
},
{
"path": "/buf.plugin.check.v1.CheckService/ListRules",
"args": ["list-rules"]
},
{
"path": "/buf.plugin.check.v1.CheckService/ListCategories",
"args": ["list-categories"]
},
{
"path": "/buf.plugin.info.v1.PluginInfoService/GetPluginInfo",
"args": ["info"]
}
]
}
Using WASM binary with buf
We could compile the plugin to normal Go binary, but what's the fun in that? So let's use wasmtime
to run the WASM binary:
GOOS=wasip1 GOARCH=wasm go build -o ./examples/buf-check-strictrpc.wasm main.go
buf lint ./examples/dragon.proto
examples/dragon.proto:1:1:service "dragon" must end with _service.proto. (wasmtime ./examples/buf-check-strictrpc.wasm)
Yikes, we forgot to add the _service.proto
suffix to our file!