804 words   |  3 min

Tip

The feature for generating code based on custom templates and Protobuf is only supported in sponge versions v1.11.0 and above.

Combining custom templates with Protobuf allows for generating code for various custom scenarios. You only need to provide proto files and template directory paths as the main parameters. Multiple proto files can be specified for batch code generation.

Applicable scenarios:

  • gRPC service code.
  • Test cases for gRPC server and client.
  • HTTP service code, such as API, router, service, etc.

Preparation

Before starting, please ensure the following:

  • Install sponge.
  • Prepare the Protobuf files. If your proto file depends on other proto files, store the dependencies in a separate directory.

Open the terminal and start the sponge UI service:

sponge run

Access the sponge code generation UI at http://localhost:24631 in your browser.

Below is an example of a user.proto Protobuf file with the following content:

syntax = "proto3";

package api.user.v1;

import "google/api/annotations.proto";
import "validate/validate.proto";

option go_package = "user/api/user/v1;v1";

service User {
  rpc Login(LoginRequest) returns (LoginReply) {
    option (google.api.http) = {
      post: "/api/v1/auth/login"
      body: "*"
    };
  }
}

message LoginRequest {
  string email = 1 [(validate.rules).string.email = true];
  string password = 2 [(validate.rules).string.min_len = 6];
}

message LoginReply {
  uint64 id = 1;
  string token = 2;
}

Fixed Fields and Custom Fields

The code generation feature supports two types of fields:

  • Fixed Fields
  • Custom Fields

Both fixed and custom fields correspond to placeholders in the template code.

Fixed Fields: These fields are automatically parsed from Protobuf files and cannot be modified. For example, fixed fields parsed from a user.proto file might look like this:

{
    "Proto": {
        "FileDir": "api/user/v1",
        "FileName": "user.proto",
        "FileNamePrefix": "user",
        "FileNamePrefixCamel": "User",
        "GoPackage": "\"user/api/user/v1\"",
        "GoPkgName": "userV1",
        "ImportPkgMap": {
            "userV1": "userV1 \"user/api/user/v1\""
        },
        "FieldImportPkgMap": {
            "userV1": "userV1 \"user/api/user/v1\""
        },
        "Package": "api.user.v1",
        "Services": [
            {
                "GoPkgName": "userV1",
                "Methods": [
                    {
                        "Comment": "// Login",
                        "HTTPRequestBody": "",
                        "HTTPRequestMethod": "POST",
                        "HTTPRouter": "/api/v1/auth/login",
                        "InvokeType": "unary_call",
                        "IsIgnoreGinBind": false,
                        "IsPassGinContext": false,
                        "MethodName": "Login",
                        "ReplyFields": [
                            {
                                "Comment": "",
                                "FieldType": "uint64",
                                "GoType": "uint64",
                                "GoTypeCrossPkg": "uint64",
                                "ImportPkgName": "",
                                "ImportPkgPath": "",
                                "Name": "Id"
                            }
                        ],
                        "ReplyImportPkgName": "userV1",
                        "ReplyName": "LoginReply",
                        "RequestFields": [
                            {
                                "Comment": "",
                                "FieldType": "string",
                                "GoType": "string",
                                "GoTypeCrossPkg": "string",
                                "ImportPkgName": "",
                                "ImportPkgPath": "",
                                "Name": "Email"
                            }
                        ],
                        "RequestImportPkgName": "userV1",
                        "RequestName": "LoginRequest"
                    }
                ],
                "ServiceName": "User",
                "ServiceNameCamel": "User",
                "ServiceNameCamelFCL": "user",
                "ServiceNamePluralCamel": "Users",
                "ServiceNamePluralCamelFCL": "users"
            }
        ]
    }
}

Custom Fields: These fields are optional and should be defined in a JSON file only if required by the template. For example, create a file named fields.json with the following content:

{
    "ModuleName": "user",
    "PackageName": "service",
    "ServerName": "grpc",
    "Port": 8282
}

Note

Avoid using Proto as a field name in custom fields, as it conflicts with the fixed field name.


Create Custom Template Code

Template code is the core of code generation, implemented based on Go's text/template library. It is recommended to familiarize yourself with its basic syntax rules first, it is simple and can be familiarized in a few minutes. Click here to learn more: Go text/template Basic Syntax Rules.

The template filename should use a variable format (e.g., {{.Proto.FileNamePrefix}}.go.tmpl) to avoid naming conflicts when processing multiple proto files. Below is an example of a gRPC service template:

package service

import (
    "context"
    "google.golang.org/grpc"
    {{- range $pkgName, $pkgPath := .Proto.ImportPkgMap }}
    {{$pkgPath}}
    {{- end }}
)

// 注册 gRPC 服务
func Register{{.Proto.FileNamePrefixCamel}}Server(server *grpc.Server) {
    {{- range .Proto.Services }}
    {{.GoPkgName}}.Register{{.ServiceNameCamel}}Server(server, New{{.ServiceNameCamel}}Server())
    {{- end }}
}

{{- range .Proto.Services }}
type {{.ServiceNameCamelFCL}} struct {
    {{.GoPkgName}}.Unimplemented{{.ServiceNameCamel}}Server
}

// 创建服务实例
func New{{.ServiceNameCamel}}Server() {{.GoPkgName}}.{{.ServiceNameCamel}}Server {
    return &{{.ServiceNameCamelFCL}}{}
}

{{- range .Methods }}
// {{.Comment}}
func (s *{{.ServiceNameCamelFCL}}) {{.MethodName}}(ctx context.Context, req *{{.RequestImportPkgName}}.{{.RequestName}}) (*{{.ReplyImportPkgName}}.{{.ReplyName}}, error) {
    return &{{.ReplyImportPkgName}}.{{.ReplyName}}{}, nil
}
{{- end }}
{{- end }}

Store the template files in a directory (e.g., template), which can contain multiple template files and subdirectories.


Generate Code

Follow these steps in the sponge UI to generate code:

  1. Access the Interface: Navigate to Generate custom code -> Protobuf in the left menu.
  2. Fill in Parameters (hover over the ? icon next to each parameter for more details):
    • Template Directory Path: e.g., /template/grpc/service
    • Proto File Path: e.g., user.proto
    • Dependent Proto File Directory (optional): Specify directories for additional Protobuf imports.
    • Custom Fields JSON File (optional): e.g., fields.json.
  3. Click the Download Code button to generate the code, as shown below.

Interface Example

Below is an example of the UI operation:

Code Generation Interface

Tip

Equivalent command: sponge template protobuf --tpl-dir=/template/grpc/service --proto-file=user.proto --fields=fields.json

Tip

Clicking the Show Info button allows you to view field information. These fields correspond to the placeholders in the template, making it easier to write template code.