Skip to content

Commit

Permalink
fix protoc-gen-go-errors bug with code OK && improve protoc-gen-go-er…
Browse files Browse the repository at this point in the history
…rors docs
  • Loading branch information
sevennt committed Jan 24, 2024
1 parent 0410bfa commit 0229a6b
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 59 deletions.
4 changes: 3 additions & 1 deletion cmd/protoc-gen-go-errors/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ func main() {
continue
}

generateFile(gen, f)
if _, err := generateFile(gen, f); err != nil {
return err
}
}
return nil
})
Expand Down
69 changes: 58 additions & 11 deletions cmd/protoc-gen-go-errors/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package main
import (
"fmt"
"regexp"
"slices"

Check failure on line 27 in cmd/protoc-gen-go-errors/render.go

View workflow job for this annotation

GitHub Actions / build

package slices is not in GOROOT (/opt/hostedtoolcache/go/1.19.13/x64/src/slices)
"strings"

"github.com/iancoleman/strcase"
Expand All @@ -37,9 +38,9 @@ const (
)

// generateFile generates a _errors.pb.go file containing ego errors definitions.
func generateFile(gen *protogen.Plugin, file *protogen.File) *protogen.GeneratedFile {
func generateFile(gen *protogen.Plugin, file *protogen.File) (*protogen.GeneratedFile, error) {
if len(file.Enums) == 0 {
return nil
return nil, nil
}
filename := file.GeneratedFilenamePrefix + "_errors.pb.go"
g := gen.NewGeneratedFile(filename, file.GoImportPath)
Expand All @@ -48,14 +49,14 @@ func generateFile(gen *protogen.Plugin, file *protogen.File) *protogen.Generated
g.P("package ", file.GoPackageName)
g.P()
g.QualifiedGoIdent(codesPackage.Ident(""))
generateFileContent(gen, file, g)
return g
err := generateFileContent(gen, file, g)
return g, err
}

// generateFileContent generates the ego errors definitions, excluding the package statement.
func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile) {
func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile) error {
if len(file.Enums) == 0 {
return
return nil
}

g.P("// This is a compile-time assertion to ensure that this generated file")
Expand All @@ -64,14 +65,19 @@ func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.
g.P()
index := 0
for _, enum := range file.Enums {
if !generationErrorsSection(gen, file, g, enum) {
hasNoErr, err := generationErrorsSection(gen, file, g, enum)
if err != nil {
return fmt.Errorf("generationErrorsSection fail, %w", err)
}
if !hasNoErr {
index++
}
}
// If all enums do not contain 'errors.code', the current file is skipped
if index == 0 {
g.Skip()
}
return nil
}

const (
Expand All @@ -80,7 +86,8 @@ const (
fieldLevelI18nAnnotation = "i18n"
)

func generationErrorsSection(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, enum *protogen.Enum) bool {
// generationErrorsSection generate errors stub code
func generationErrorsSection(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, enum *protogen.Enum) (bool, error) {
var ew errorWrapper
for _, v := range enum.Values {
var i18n = map[string]string{}
Expand All @@ -100,13 +107,17 @@ func generationErrorsSection(gen *protogen.Plugin, file *protogen.File, g *proto

upperCamelValue := strcase.ToCamel(strings.ToLower(desc))
comment = buildComment(upperCamelValue, comment)
camelCode, ok := strToCode[eCode.val]
if !ok {
return false, fmt.Errorf(`invalid grpc code:"%s", valid code is:%v`, eCode.val, validCode)
}

err := &errorInfo{
Name: string(enum.Desc.Name()),
Value: desc,
UpperCamelValue: strcase.ToCamel(strings.ToLower(desc)),
LowerCamelValue: strcase.ToLowerCamel(strings.ToLower(desc)),
Code: strcase.ToCamel(strings.ToLower(eCode.val)),
Code: camelCode,
Key: string(v.Desc.FullName()),
Comment: comment,
HasComment: len(comment) > 0,
Expand All @@ -115,10 +126,46 @@ func generationErrorsSection(gen *protogen.Plugin, file *protogen.File, g *proto
ew.Errors = append(ew.Errors, err)
}
if len(ew.Errors) == 0 {
return true
return true, nil
}
g.P(ew.execute())
return false
return false, nil
}

var validCode = make([]string, 0, len(strToCode))

func init() {
validCode = genValidCode()
slices.Sort(validCode)
}

func genValidCode() []string {
codes := make([]string, 0, len(strToCode))
for k := range strToCode {
codes = append(codes, k)
}
return codes
}

// strToCode defines valid grpc code.
var strToCode = map[string]string{
`OK`: "OK",
`CANCELLED`: "Canceled",
`UNKNOWN`: "Unknown",
`INVALID_ARGUMENT`: "InvalidArgument",
`DEADLINE_EXCEEDED`: "DeadlineExceeded",
`NOT_FOUND`: "NotFound",
`ALREADY_EXISTS`: "AlreadyExists",
`PERMISSION_DENIED`: "PermissionDenied",
`RESOURCE_EXHAUSTED`: "ResourceExhausted",
`FAILED_PRECONDITION`: "FailedPrecondition",
`ABORTED`: "Aborted",
`OUT_OF_RANGE`: "OutOfRange",
`UNIMPLEMENTED`: "Unimplemented",
`INTERNAL`: "Internal",
`UNAVAILABLE`: "Unavailable",
`DATA_LOSS`: "DataLoss",
`UNAUTHENTICATED`: "Unauthenticated",
}

// buildComment returns comment content with prefix //
Expand Down
2 changes: 2 additions & 0 deletions cmd/protoc-gen-go-errors/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ var i18n = map[string]map[string]string{
{{- end }}
}
// ReasonI18n provides error messages in a specified language.
// For instance, to get an error message in Chinese for "@i18n.cn", you can use ReasonI18n(e, "cn").
func ReasonI18n(e eerrors.Error, lan string) string {
return i18n[eerrors.FromError(e).Reason][lan]
}
Expand Down
56 changes: 56 additions & 0 deletions examples/grpc/errors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# protoc-gen-go-errors 使用说明

## 使用准备
- 需要本地提前安装好 protoc google/protobuf 等
- 需要下载最新的 protoc-gen-go-errors 插件,并添加到本地环境变量

## 定义错误 proto 文件
```protobuf
syntax = "proto3";
package biz.v1;
// 下行注解是必要的,有这个注解 protoc-gen-go-errors 才尝试解析当前 protobuf 文件中的 enum,并基于 enum 生成错误桩代码
// @plugins=protoc-gen-go-errors
// language-specified package name
option go_package = "biz/v1;bizv1";
option java_multiple_files = true;
option java_outer_classname = "BizProto";
option java_package = "com.ego.biz.v1";
// enum Err 定义了错误的不同枚举值,protoc-gen-go-errors 会基于 enum Err 枚举值生成错误桩代码
// @code 为错误关联的gRPC Code (遵循 https://grpc.github.io/grpc/core/md_doc_statuscodes.html 定义,需要全大写),
// 包含 OK、UNKNOWN、INVALID_ARGUMENT、PERMISSION_DENIED等
// @i18n.cn 国际化中文文案
// @i18n.en 国际化英文文案
enum Err {
// 请求正常,实际上不算是一个错误
// @code=OK
// @i18n.cn="请求成功"
// @i18n.en="OK"
ERR_OK = 0;
// 未知错误,比如业务panic了
// @code=UNKNOWN # 定义了这个错误关联的gRPC Code为:UNKNOWN
// @i18n.cn="服务内部未知错误" # 定义了一个中文错误文案
// @i18n.en="unknown error" # 定义了一个英文错误文案
ERR_UNKNOWN = 1;
// 找不到指定用户
// @code=NOT_FOUND
// @i18n.cn="找不到指定用户"
// @i18n.en="user not found"
ERR_USER_NOT_FOUND = 2;
// 用户ID不合法
// @code=INVALID_ARGUMENT
// @i18n.cn="用户ID不合法"
// @i18n.en="invalid user id"
ERR_USER_ID_NOT_VALID = 3;
}
```

## 使用
```bash
protoc --proto_path=. --go_out=paths=source_relative:. --go-errors_out=paths=source_relative:. ./errors.proto
```

详细用例可以参考 [./error.sh](./error.sh) 脚本
3 changes: 2 additions & 1 deletion examples/grpc/errors/error.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ SERVER=${ROOT}/examples/grpc/direct/server

rm -rf ./protoc-gen-go-errors
go build -o ${ROOT}/bin/protoc-gen-go-errors ${ROOT}/cmd/protoc-gen-go-errors
export PATH=$PATH:${ROOT}/bin/
export PATH=${ROOT}/bin/:$PATH

protoc --proto_path=. --go_out=paths=source_relative:. --go-errors_out=paths=source_relative:. ./errors.proto
70 changes: 45 additions & 25 deletions examples/grpc/errors/errors.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 26 additions & 10 deletions examples/grpc/errors/errors.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,39 @@ syntax = "proto3";

package biz.v1;

// 下行注解是必要的,有这个注解 protoc-gen-go-errors 才尝试解析当前 protobuf 文件中的 enum,并基于 enum 生成错误桩代码
// @plugins=protoc-gen-go-errors

// language-specified package name
option go_package = "biz/v1;bizv1";
option java_multiple_files = true;
option java_outer_classname = "BizProto";
option java_package = "com.ego.biz.v1";

// @plugins=protoc-gen-go-errors

// 错误定义
// enum Err 定义了错误的不同枚举值,protoc-gen-go-errors 会基于 enum Err 枚举值生成错误桩代码
// @code 为错误关联的gRPC Code (遵循 https://grpc.github.io/grpc/core/md_doc_statuscodes.html 定义,需要全大写),
// 包含 OK、UNKNOWN、INVALID_ARGUMENT、PERMISSION_DENIED等
// @i18n.cn 国际化中文文案
// @i18n.en 国际化英文文案
enum Err {
// 未知类型
// @code=UNKNOWN
ERR_INVALID = 0;
// 找不到资源
// 请求正常,实际上不算是一个错误
// @code=OK
// @i18n.cn="请求成功"
// @i18n.en="OK"
ERR_OK = 0;
// 未知错误,比如业务panic了
// @code=UNKNOWN # 定义了这个错误关联的gRPC Code为:UNKNOWN
// @i18n.cn="服务内部未知错误" # 定义了一个中文错误文案
// @i18n.en="unknown error" # 定义了一个英文错误文案
ERR_UNKNOWN = 1;
// 找不到指定用户
// @code=NOT_FOUND
ERR_USER_NOT_FOUND = 1;
// 客户端参数错误
// @i18n.cn="找不到指定用户"
// @i18n.en="user not found"
ERR_USER_NOT_FOUND = 2;
// 用户ID不合法
// @code=INVALID_ARGUMENT
ERR_CONTENT_MISSING = 2;
// @i18n.cn="用户ID不合法"
// @i18n.en="invalid user id"
ERR_USER_ID_NOT_VALID = 3;
}
Loading

0 comments on commit 0229a6b

Please sign in to comment.