Go的一个新协议缓冲区API(二)

316 阅读2分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。

这个函数接受一个 proto.Message,一个由所有生成的消息类型实现的接口类型。 此类型是 protoreflect 包中定义的类型的别名:

type ProtoMessage interface{
    ProtoReflect() Message
}

为了避免填满生成消息的命名空间,该接口仅包含一个返回 protoreflect.Message 的方法,该方法提供对消息内容的访问。

(为什么是别名?因为 protoreflect.Message 有一个对应的方法返回原始的 proto.Message,我们需要避免两个包之间的导入循环。)

protoreflect.Message.Range 方法为消息中的每个填充字段调用一个函数。

m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
    // ...
    return true
})

range 函数参数是 protoreflect.FieldDescriptor (字段的协议缓冲区的类型)和 protoreflect.Value (字段值)。

protoreflect.FieldDescriptor.Options 方法返回的是 google.protobuf.FieldOptions类型。

opts := fd.Options().(*descriptorpb.FieldOptions)

(为什么是类型断言?由于生成的descriptorpb包依赖于protoreflect,所以protoreflect包不能返回具体的选项类型,避免循环导入。)

这样我们能够检查我们的拓展返回值是否true or false

if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
    return true // don't redact non-sensitive fields
}

请注意,我们在这里查看的是字段描述符,而不是字段值。 我们感兴趣的信息在于协议缓冲区类型系统,而不是 Go 系统。

这也是我们简化 proto 包 API 的一个示例。 原始的 proto.GetExtension 返回一个值和一个错误。 新的 proto.GetExtension 只返回一个值,如果该字段不存在,则返回该字段的默认值。 在 Unmarshal 时报告扩展解码错误。

一旦我们确定了需要编辑的字段,清除它就很简单:

m.Clear(fd)

综上所述,我们完整的代码是:

// Redact clears every sensitive field in pb.
func Redact(pb proto.Message) {
    m := pb.ProtoReflect()
    m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
        opts := fd.Options().(*descriptorpb.FieldOptions)
        if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
            return true
        }
        m.Clear(fd)
        return true
    })
}

更完整的实现可能在每个消息值字段中处理。 我们希望这个简单的示例能够让您了解协议缓冲区反射及其用途。