Go:安全地将整数转换为协议缓冲区枚举值的最佳实践

Go: best practice for safely converting integers to protocol buffer enum values

我在原型文件中有一个枚举,它在 pb.go 文件中生成整数常量。我现在有一些来自外部数据源的整数,并希望将它们安全地映射到可能的常量。

这是我目前拥有的:https://play.golang.org/p/-5VZqPbukd

package main

import (
    "errors"
    "fmt"
)

//enum in the proto file
//
// enum X {
//    A = 0;
//    B = 1;
//    C = 2;
//    D = 3;
// }

//enum type generated by protoc
type X int32

//enum constants generated by protoc
const (
    X_A X = 0
    X_B X = 1
    X_C X = 2
    X_D X = 3
)

func intToX(v int) (X, error) {
    x := X(v)
    switch x {
    case X_A, X_B, X_C, X_D:
        return x, nil
    }
    return 0, errors.New("could not convert int to X")
}

func main() {
    for i := -1; i < 10; i++ {
        if x, err := intToX(i); err != nil {
            fmt.Println("unhandled error:", err, "for input value", i)
        } else {
            fmt.Printf("%d => X(%d)\n", i, x)
        }
    }
}

问题:是否有更好、更惯用的方法将传入的整数值映射到协议生成的常量?

特别是,我想避免在 case A, B, C, D 语句中明确列出所有常量。

我不知道你用的是哪个原型生成包,但是github.com/golang/protobuf/proto你也可以得到枚举的反向映射。

示例 xyz.pb.go 生成的文件:

type TimeInterval int32

const (
    TimeInterval_TI_UNKNOWN TimeInterval = 0
    TimeInterval_TI_HOUR    TimeInterval = 1
    TimeInterval_TI_DAY     TimeInterval = 2
    TimeInterval_TI_WEEK    TimeInterval = 3
    TimeInterval_TI_MONTH   TimeInterval = 4
    TimeInterval_TI_QUARTER TimeInterval = 5
    TimeInterval_TI_YEAR    TimeInterval = 6
)

var TimeInterval_name = map[int32]string{
    0: "TI_UNKNOWN",
    1: "TI_HOUR",
    2: "TI_DAY",
    3: "TI_WEEK",
    4: "TI_MONTH",
    5: "TI_QUARTER",
    6: "TI_YEAR",
}
var TimeInterval_value = map[string]int32{
    "TI_UNKNOWN": 0,
    "TI_HOUR":    1,
    "TI_DAY":     2,
    "TI_WEEK":    3,
    "TI_MONTH":   4,
    "TI_QUARTER": 5,
    "TI_YEAR":    6,
}

func (x TimeInterval) String() string {
    return proto.EnumName(TimeInterval_name, int32(x))
}
func (TimeInterval) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

因此,您可以通过以下方式测试是否存在:

if _, found := TimeInterval_name[testinputint]; found{
    //all ok
} else {
   //not a value for this enum
}

是的,@RickyA 提到使用范围很好,因为它可以验证所有可能的基础常量值。

此外,您还可以检查枚举的长度,尽管这只有在基础枚举值没有任何 'gaps' 并且具有相应的数字范围时才有可能。

代码详细解释:

typelength := int32(len(TimeInterval_name))
if testinputint < 0 || int32(testinputint) >= typelength {
    // not a value for this enum, return err
}

不那么冗长,只是使用 int 而不是 int32

if testinputint < 0 || int(testinputint) >= len(TimeInterval_name) {
    // not a value for this enum, return err 
}

但如前所述,这仅对遵守适当 iota 的枚举有效。当您将枚举更改为读取如下内容时,情况可能并非如此:

var TimeInterval_name = map[int32]string{
    0: "TI_UNKNOWN",
    1: "TI_HOUR",
    2: "TI_DAY",
    3: "TI_WEEK",
    // we do not use month anymore 4: "TI_MONTH",
    5: "TI_QUARTER",
    6: "TI_YEAR",
}

因为生成的地图的长度会明显小于六:)

也就是说使用上面@Ricky_A找到的方法以防万一