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@latestPrerequisites
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/optionsConfiguration 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_modelis 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/telegram-bot/telegram;Update
- response_model=github.com/go-sphere/telegram-bot/telegram;Message
- extra_data_model=github.com/go-sphere/telegram-bot/telegram;MethodExtraData
- extra_data_constructor=github.com/go-sphere/telegram-bot/telegram;NewMethodExtraDataProto 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: "bot"
extra: {
key: "command"
value: "start"
}
extra: {
key: "callback_query"
value: "start"
}
};
}
rpc ProcessMenu(ProcessMenuRequest) returns (ProcessMenuResponse) {
option (sphere.options.options) = {
key: "bot"
extra: {
key: "callback_query"
value: "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: "bot"
extra: {
key: "command"
value: "/start"
}
extra: {
key: "callback_query"
value: "start_.*"
}
};
}
}options_key in buf.gen.yaml selects the key field (bot in the official templates). Command names and callback patterns belong in extra.
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/telegram-bot/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, render)
// 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=customMultiple 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}"
};
option (sphere.options.options) = {
key: "bot"
extra: {
key: "command"
value: "user_info"
}
};
option (sphere.options.options) = {
key: "event"
text: "user.get"
};
}
}text is valid when the option value is a single string. Use extra when one key carries several named fields, as the bot template does.
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.optionsannotations - 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-spherefor HTTP routing - Combine with
protoc-gen-sphere-errorsfor consistent error handling - Works with any transport layer that can use the generated interfaces