protoc-gen-route
protoc-gen-route
is a protoc plugin that generates routing code from .proto
files. It is designed to inspect service definitions within your protobuf files and automatically generate corresponding route handlers based on a specified template. This plugin creates Go code that provides structured routing with operation constants, extra data handling, server interfaces, and codec interfaces for seamless integration with various transport protocols.
Features
- Generates operation constants for each service method
- Creates extra data mappings from proto options
- Provides server and codec interfaces for type-safe implementations
- Supports custom request and response models
- Generates handler functions with automatic request/response conversion
- Integrates with the sphere options framework
- Supports flexible template customization
Installation
To install protoc-gen-route
, use the following command:
go install github.com/go-sphere/protoc-gen-route@latest
Prerequisites
You need to have the sphere options proto definitions in your project. Add the following dependency to your buf.yaml
:
deps:
- buf.build/go-sphere/options
Configuration Parameters
The behavior of protoc-gen-route
can be customized with the following parameters:
version
: Print the current plugin version and exit. (Default:false
)options_key
: The key for the option extension in your proto file that contains routing information. (Default:route
)file_suffix
: The suffix for the generated files. (Default:_route.pb.go
)template_file
: Path to a custom Go template file. If not provided, the default internal template is used.request_model
: (Required) The fully qualified Go type for the request model (e.g.,github.com/gin-gonic/gin.Context
).response_model
: (Required) The fully qualified Go type for the response model.extra_data_model
: The fully qualified Go type for an additional data model to be used in the template.extra_data_constructor
: A function that constructs and returns a pointer to theextra_data_model
. (Required ifextra_data_model
is set).
Usage with Buf
To use protoc-gen-route
with buf
, you can configure it in your buf.gen.yaml
file. Here is an example configuration:
version: v2
managed:
enabled: true
disable:
- file_option: go_package_prefix
module: buf.build/go-sphere/options
plugins:
- local: protoc-gen-go
out: api
opt:
- paths=source_relative
- local: protoc-gen-route
out: api
opt:
- paths=source_relative
- options_key=bot
- request_model=github.com/go-sphere/sphere/social/telegram;Update
- response_model=github.com/go-sphere/sphere/social/telegram;Message
- extra_data_model=github.com/go-sphere/sphere/social/telegram;MethodExtraData
- extra_data_constructor=github.com/go-sphere/sphere/social/telegram;NewMethodExtraData
Proto Definition Example
Here’s how to define services with routing options in your .proto
files:
syntax = "proto3";
package bot.v1;
import "sphere/options/options.proto";
service MenuService {
// UpdateCount handles count update operations
rpc UpdateCount(UpdateCountRequest) returns (UpdateCountResponse) {
option (sphere.options.options) = [
{
key: "callback_query"
text: "start"
},
{
key: "command"
text: "start"
}
];
}
rpc ProcessMenu(ProcessMenuRequest) returns (ProcessMenuResponse) {
option (sphere.options.options) = [
{
key: "callback_query"
text: "menu_.*"
}
];
}
}
message UpdateCountRequest {
int64 value = 1;
int64 offset = 2;
}
message UpdateCountResponse {
int64 value = 1;
}
message ProcessMenuRequest {
string menu_id = 1;
string action = 2;
}
message ProcessMenuResponse {
string result = 1;
}
Generated Code
The plugin generates Go code with the following components for each service:
Operation Constants
const OperationBotMenuServiceUpdateCount = "/bot.v1.MenuService/UpdateCount"
const OperationBotMenuServiceProcessMenu = "/bot.v1.MenuService/ProcessMenu"
Extra Data Variables
var ExtraBotDataMenuServiceUpdateCount = telegram.NewMethodExtraData(map[string]string{
"callback_query": "start",
"command": "start",
})
var ExtraBotDataMenuServiceProcessMenu = telegram.NewMethodExtraData(map[string]string{
"callback_query": "menu_.*",
})
Helper Functions
func GetExtraBotDataByMenuServiceOperation(operation string) *telegram.MethodExtraData {
switch operation {
case OperationBotMenuServiceUpdateCount:
return ExtraBotDataMenuServiceUpdateCount
case OperationBotMenuServiceProcessMenu:
return ExtraBotDataMenuServiceProcessMenu
default:
return nil
}
}
Server Interface
type MenuServiceBotServer interface {
UpdateCount(context.Context, *UpdateCountRequest) (*UpdateCountResponse, error)
ProcessMenu(context.Context, *ProcessMenuRequest) (*ProcessMenuResponse, error)
}
Codec Interface
type MenuServiceBotCodec interface {
DecodeUpdateCountRequest(*telegram.Update) (*UpdateCountRequest, error)
EncodeUpdateCountResponse(*UpdateCountResponse) (*telegram.Message, error)
DecodeProcessMenuRequest(*telegram.Update) (*ProcessMenuRequest, error)
EncodeProcessMenuResponse(*ProcessMenuResponse) (*telegram.Message, error)
}
Registration Function
func RegisterMenuServiceBotServer(server MenuServiceBotServer, codec MenuServiceBotCodec) map[string]telegram.Handler {
handlers := make(map[string]telegram.Handler)
handlers[OperationBotMenuServiceUpdateCount] = func(ctx context.Context, update *telegram.Update) (*telegram.Message, error) {
req, err := codec.DecodeUpdateCountRequest(update)
if err != nil {
return nil, err
}
resp, err := server.UpdateCount(ctx, req)
if err != nil {
return nil, err
}
return codec.EncodeUpdateCountResponse(resp)
}
handlers[OperationBotMenuServiceProcessMenu] = func(ctx context.Context, update *telegram.Update) (*telegram.Message, error) {
req, err := codec.DecodeProcessMenuRequest(update)
if err != nil {
return nil, err
}
resp, err := server.ProcessMenu(ctx, req)
if err != nil {
return nil, err
}
return codec.EncodeProcessMenuResponse(resp)
}
return handlers
}
Common Use Cases
Bot Command Routing
The plugin is commonly used for routing bot commands in messaging platforms:
service BotService {
rpc Start(StartRequest) returns (StartResponse) {
option (sphere.options.options) = [
{
key: "command"
text: "/start"
},
{
key: "callback_query"
text: "start_.*"
}
];
}
}
Event Routing
You can also use it for general event routing:
service EventService {
rpc HandleUserCreated(UserCreatedRequest) returns (UserCreatedResponse) {
option (sphere.options.options) = [
{
key: "event_type"
text: "user.created"
}
];
}
}
Integration Example
Here’s how you might integrate the generated code in a bot application:
package main
import (
"context"
"log"
botv1 "myproject/api/bot/v1"
"github.com/go-sphere/sphere/social/telegram"
)
// Implement the server interface
type MenuService struct{}
func (s *MenuService) UpdateCount(ctx context.Context, req *botv1.UpdateCountRequest) (*botv1.UpdateCountResponse, error) {
// Your business logic here
newValue := req.Value + req.Offset
return &botv1.UpdateCountResponse{
Value: newValue,
}, nil
}
func (s *MenuService) ProcessMenu(ctx context.Context, req *botv1.ProcessMenuRequest) (*botv1.ProcessMenuResponse, error) {
// Your business logic here
return &botv1.ProcessMenuResponse{
Result: fmt.Sprintf("Processed menu %s with action %s", req.MenuId, req.Action),
}, nil
}
// Implement the codec interface
type MenuServiceCodec struct{}
func (c *MenuServiceCodec) DecodeUpdateCountRequest(update *telegram.Update) (*botv1.UpdateCountRequest, error) {
// Parse the telegram update into your request struct
// This would typically extract data from callback data or command parameters
}
func (c *MenuServiceCodec) EncodeUpdateCountResponse(resp *botv1.UpdateCountResponse) (*telegram.Message, error) {
// Convert your response into a telegram message
return &telegram.Message{
Text: fmt.Sprintf("Count updated to: %d", resp.Value),
}, nil
}
// Similar implementations for other methods...
func main() {
server := &MenuService{}
codec := &MenuServiceCodec{}
// Register handlers
handlers := botv1.RegisterMenuServiceBotServer(server, codec)
// Use with your bot framework
for operation, handler := range handlers {
extraData := botv1.GetExtraBotDataByMenuServiceOperation(operation)
// Register with your bot router using the operation and extra data
log.Printf("Registered handler for %s with extra data: %v", operation, extraData)
}
}
Advanced Configuration
Custom Templates
You can provide your own template file for custom code generation:
plugins:
- local: protoc-gen-route
out: api
opt:
- paths=source_relative
- template_file=./templates/custom_route.tmpl
- options_key=custom
Multiple Option Keys
You can generate routing code for different transport protocols by using different option keys:
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (sphere.options.options) = [
{
key: "http"
text: "GET /users/{id}"
},
{
key: "bot"
text: "user_info"
},
{
key: "event"
text: "user.get"
}
];
}
}
Best Practices
- Use meaningful operation keys: Choose keys that clearly indicate the transport or routing mechanism
- Group related operations: Keep related service methods in the same service for better organization
- Implement proper error handling: Ensure your codec implementations handle errors gracefully
- Use consistent naming: Follow consistent naming patterns for your services and methods
- Document your options: Include comments explaining the routing options for each method
Troubleshooting
Common Issues
- Missing option annotations: Ensure all methods that should generate routes have the appropriate
sphere.options.options
annotations - Template errors: Check your custom template syntax if using a custom template file
- Missing dependencies: Verify that all required model types are available and properly imported
Integration with Other Generators
protoc-gen-route
works well alongside other Sphere generators:
- Use with
protoc-gen-sphere
for HTTP routing - Combine with
protoc-gen-sphere-errors
for consistent error handling - Works with any transport layer that can use the generated interfaces