「这是我参与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
})
}
更完整的实现可能在每个消息值字段中处理。 我们希望这个简单的示例能够让您了解协议缓冲区反射及其用途。