protoc-gen-sphere-binding
protoc-gen-sphere-binding
is a protoc plugin that generates Go struct tags for Sphere binding from .proto
files. It is designed to inspect service definitions within your protobuf files and automatically generate corresponding Go struct tags based on sphere binding annotations.
Unlike other protoc plugins that generate new files, this plugin modifies the generated Go structs by injecting appropriate tags for request binding in HTTP handlers.
Installation
To install protoc-gen-sphere-binding
, use the following command:
go install github.com/go-sphere/protoc-gen-sphere-binding@latest
Prerequisites
You need to have the sphere binding proto definitions in your project. Add the following dependency to your buf.yaml
:
deps:
- buf.build/go-sphere/binding
Configuration Parameters
The behavior of protoc-gen-sphere-binding
can be customized with the following parameters:
version
: Print the current plugin version and exit. (Default:false
)out
: The output directory for the modified.proto
files. (Default:api
)
Usage with Buf
To use protoc-gen-sphere-binding
with buf
, you can configure it in your buf.gen.yaml
file. Note: protoc-gen-sphere-binding
cannot be used with the standard buf.gen.yaml
because it does not generate Go code, but rather modifies the .proto
files to include Sphere binding tags.
Here is an example configuration:
version: v2
managed:
enabled: true
override:
- file_option: go_package_prefix
value: github.com/go-sphere/sphere-layout/api
plugins:
- local: protoc-gen-go
out: api
opt:
- paths=source_relative
- local: protoc-gen-sphere-binding
out: api
opt:
- paths=source_relative
How It Works
protoc-gen-sphere-binding
processes protobuf files and adds Go struct tags based on sphere binding annotations. The plugin works in conjunction with protoc-gen-go
and should be run after the standard Go code generation to add the binding tags to the generated structs.
Binding Locations
The plugin supports the following binding locations through the sphere.binding.location
annotation:
BINDING_LOCATION_BODY
: Fields bound to JSON request body (default behavior)BINDING_LOCATION_QUERY
: Fields bound to query parameters (addsform
tag)BINDING_LOCATION_URI
: Fields bound to URI path parameters (addsuri
tag, removesjson
tag)BINDING_LOCATION_HEADER
: Fields bound to HTTP headers (addsheader
tag)BINDING_LOCATION_FORM
: Fields bound to form data (addsform
tag)
Proto Definition Example
Here’s a comprehensive example showing different binding locations:
syntax = "proto3";
package shared.v1;
import "buf/validate/validate.proto";
import "google/api/annotations.proto";
import "sphere/binding/binding.proto";
service TestService {
rpc RunTest(RunTestRequest) returns (RunTestResponse) {
option (google.api.http) = {
post: "/v1/test/{path_test1}"
body: "*"
};
}
}
message RunTestRequest {
// URI path parameter
string path_test1 = 1 [(sphere.binding.location) = BINDING_LOCATION_URI];
// Fields without binding annotation go to JSON body
string field_test1 = 2;
// Query parameter
string query_test1 = 3 [(sphere.binding.location) = BINDING_LOCATION_QUERY];
// Header value
string auth_token = 8 [(sphere.binding.location) = BINDING_LOCATION_HEADER];
// Custom tags
repeated int32 ids = 9 [
(sphere.binding.location) = BINDING_LOCATION_QUERY,
(sphere.binding.auto_tags) = "custom:\"ids\""
];
}
// Message with default auto tags
message BodyPathTestRequest {
option (sphere.binding.default_auto_tags) = "db";
message Request {
string name = 1; // Will get: db:"name" json:"name"
string email = 2; // Will get: db:"email" json:"email"
}
Request request = 1;
}
enum TestEnum {
TEST_ENUM_UNSPECIFIED = 0;
TEST_ENUM_VALUE1 = 1;
TEST_ENUM_VALUE2 = 2;
}
Generated Code
After running protoc-gen-go
followed by protoc-gen-sphere-binding
, the generated Go struct will have appropriate tags:
type RunTestRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PathTest1 string `protobuf:"bytes,1,opt,name=path_test1,json=pathTest1,proto3" json:"-" uri:"path_test1"`
FieldTest1 string `protobuf:"bytes,2,opt,name=field_test1,json=fieldTest1,proto3" json:"field_test1"`
QueryTest1 string `protobuf:"bytes,3,opt,name=query_test1,json=queryTest1,proto3" json:"-" form:"query_test1"`
AuthToken string `protobuf:"bytes,8,opt,name=auth_token,json=authToken,proto3" json:"-" header:"auth_token"`
Ids []int32 `protobuf:"varint,9,rep,packed,name=ids,proto3" json:"-" form:"ids" custom:"ids"`
}
type BodyPathTestRequest_Request struct {
state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name" db:"name"`
Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email" db:"email"`
}
Advanced Features
Message-Level Configuration
You can set default binding behavior for entire messages:
message SearchRequest {
option (sphere.binding.default_location) = BINDING_LOCATION_QUERY;
option (sphere.binding.default_auto_tags) = "form";
string name = 1; // Will be bound from query with form tag
int32 age = 2; // Will be bound from query with form tag
string email = 3; // Will be bound from query with form tag
}
Oneof Support
The plugin also supports oneof fields with default configurations:
message TestRequest {
oneof test_oneof {
option (sphere.binding.default_oneof_location) = BINDING_LOCATION_QUERY;
option (sphere.binding.default_oneof_auto_tags) = "form";
string option_a = 1;
string option_b = 2;
}
}
Custom Tags
Add custom Go struct tags using the auto_tags
annotation:
message DatabaseModel {
option (sphere.binding.default_auto_tags) = "db";
string name = 1; // Generated: `db:"name" json:"name"`
string email = 2 [(sphere.binding.auto_tags) = "validate:\"email\""];
// Generated: `db:"email" json:"email" validate:"email"`
}
Usage in HTTP Handlers
The generated tags work seamlessly with Gin’s binding functions:
func (s *TestService) RunTest(c *gin.Context) {
var req RunTestRequest
// Bind URI parameters
if err := c.ShouldBindUri(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Bind query parameters
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Bind JSON body
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Bind headers
if err := c.ShouldBindHeader(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Process request...
}
Integration with Other Generators
protoc-gen-sphere-binding
works best when combined with other Sphere generators:
- protoc-gen-go: Generates the base Go structs
- protoc-gen-sphere-binding: Adds binding tags to the structs
- protoc-gen-sphere: Generates HTTP handlers that use the tagged structs
Best Practices
- Use consistent binding locations: Establish patterns for where different types of data should be bound from
- Leverage message-level defaults: Use
default_location
anddefault_auto_tags
to reduce repetition - Be explicit when needed: Override defaults with field-level annotations when necessary
- Test your bindings: Verify that the generated tags work correctly with your HTTP framework
- Keep it simple: Avoid overly complex binding patterns that might confuse API consumers
Troubleshooting
Common Issues
- Tags not appearing: Make sure you’re running
protoc-gen-sphere-binding
afterprotoc-gen-go
- Binding not working: Verify that your HTTP framework supports the generated tag format
- Conflicts with existing tags: Check for conflicts between generated tags and existing protobuf tags
Debugging
You can verify the generated tags by examining the generated Go files or using reflection:
import (
"fmt"
"reflect"
)
func inspectTags() {
t := reflect.TypeOf(RunTestRequest{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field: %s, Tags: %s\n", field.Name, field.Tag)
}
}