Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
10 KiB
323 lines
10 KiB
// Copyright 2019 The Go Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package proto |
|
|
|
import ( |
|
"bytes" |
|
"compress/gzip" |
|
"fmt" |
|
"io/ioutil" |
|
"reflect" |
|
"strings" |
|
"sync" |
|
|
|
"google.golang.org/protobuf/reflect/protoreflect" |
|
"google.golang.org/protobuf/reflect/protoregistry" |
|
"google.golang.org/protobuf/runtime/protoimpl" |
|
) |
|
|
|
// filePath is the path to the proto source file. |
|
type filePath = string // e.g., "google/protobuf/descriptor.proto" |
|
|
|
// fileDescGZIP is the compressed contents of the encoded FileDescriptorProto. |
|
type fileDescGZIP = []byte |
|
|
|
var fileCache sync.Map // map[filePath]fileDescGZIP |
|
|
|
// RegisterFile is called from generated code to register the compressed |
|
// FileDescriptorProto with the file path for a proto source file. |
|
// |
|
// Deprecated: Use protoregistry.GlobalFiles.RegisterFile instead. |
|
func RegisterFile(s filePath, d fileDescGZIP) { |
|
// Decompress the descriptor. |
|
zr, err := gzip.NewReader(bytes.NewReader(d)) |
|
if err != nil { |
|
panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err)) |
|
} |
|
b, err := ioutil.ReadAll(zr) |
|
if err != nil { |
|
panic(fmt.Sprintf("proto: invalid compressed file descriptor: %v", err)) |
|
} |
|
|
|
// Construct a protoreflect.FileDescriptor from the raw descriptor. |
|
// Note that DescBuilder.Build automatically registers the constructed |
|
// file descriptor with the v2 registry. |
|
protoimpl.DescBuilder{RawDescriptor: b}.Build() |
|
|
|
// Locally cache the raw descriptor form for the file. |
|
fileCache.Store(s, d) |
|
} |
|
|
|
// FileDescriptor returns the compressed FileDescriptorProto given the file path |
|
// for a proto source file. It returns nil if not found. |
|
// |
|
// Deprecated: Use protoregistry.GlobalFiles.FindFileByPath instead. |
|
func FileDescriptor(s filePath) fileDescGZIP { |
|
if v, ok := fileCache.Load(s); ok { |
|
return v.(fileDescGZIP) |
|
} |
|
|
|
// Find the descriptor in the v2 registry. |
|
var b []byte |
|
if fd, _ := protoregistry.GlobalFiles.FindFileByPath(s); fd != nil { |
|
if fd, ok := fd.(interface{ ProtoLegacyRawDesc() []byte }); ok { |
|
b = fd.ProtoLegacyRawDesc() |
|
} else { |
|
// TODO: Use protodesc.ToFileDescriptorProto to construct |
|
// a descriptorpb.FileDescriptorProto and marshal it. |
|
// However, doing so causes the proto package to have a dependency |
|
// on descriptorpb, leading to cyclic dependency issues. |
|
} |
|
} |
|
|
|
// Locally cache the raw descriptor form for the file. |
|
if len(b) > 0 { |
|
v, _ := fileCache.LoadOrStore(s, protoimpl.X.CompressGZIP(b)) |
|
return v.(fileDescGZIP) |
|
} |
|
return nil |
|
} |
|
|
|
// enumName is the name of an enum. For historical reasons, the enum name is |
|
// neither the full Go name nor the full protobuf name of the enum. |
|
// The name is the dot-separated combination of just the proto package that the |
|
// enum is declared within followed by the Go type name of the generated enum. |
|
type enumName = string // e.g., "my.proto.package.GoMessage_GoEnum" |
|
|
|
// enumsByName maps enum values by name to their numeric counterpart. |
|
type enumsByName = map[string]int32 |
|
|
|
// enumsByNumber maps enum values by number to their name counterpart. |
|
type enumsByNumber = map[int32]string |
|
|
|
var enumCache sync.Map // map[enumName]enumsByName |
|
var numFilesCache sync.Map // map[protoreflect.FullName]int |
|
|
|
// RegisterEnum is called from the generated code to register the mapping of |
|
// enum value names to enum numbers for the enum identified by s. |
|
// |
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterEnum instead. |
|
func RegisterEnum(s enumName, _ enumsByNumber, m enumsByName) { |
|
if _, ok := enumCache.Load(s); ok { |
|
panic("proto: duplicate enum registered: " + s) |
|
} |
|
enumCache.Store(s, m) |
|
|
|
// This does not forward registration to the v2 registry since this API |
|
// lacks sufficient information to construct a complete v2 enum descriptor. |
|
} |
|
|
|
// EnumValueMap returns the mapping from enum value names to enum numbers for |
|
// the enum of the given name. It returns nil if not found. |
|
// |
|
// Deprecated: Use protoregistry.GlobalTypes.FindEnumByName instead. |
|
func EnumValueMap(s enumName) enumsByName { |
|
if v, ok := enumCache.Load(s); ok { |
|
return v.(enumsByName) |
|
} |
|
|
|
// Check whether the cache is stale. If the number of files in the current |
|
// package differs, then it means that some enums may have been recently |
|
// registered upstream that we do not know about. |
|
var protoPkg protoreflect.FullName |
|
if i := strings.LastIndexByte(s, '.'); i >= 0 { |
|
protoPkg = protoreflect.FullName(s[:i]) |
|
} |
|
v, _ := numFilesCache.Load(protoPkg) |
|
numFiles, _ := v.(int) |
|
if protoregistry.GlobalFiles.NumFilesByPackage(protoPkg) == numFiles { |
|
return nil // cache is up-to-date; was not found earlier |
|
} |
|
|
|
// Update the enum cache for all enums declared in the given proto package. |
|
numFiles = 0 |
|
protoregistry.GlobalFiles.RangeFilesByPackage(protoPkg, func(fd protoreflect.FileDescriptor) bool { |
|
walkEnums(fd, func(ed protoreflect.EnumDescriptor) { |
|
name := protoimpl.X.LegacyEnumName(ed) |
|
if _, ok := enumCache.Load(name); !ok { |
|
m := make(enumsByName) |
|
evs := ed.Values() |
|
for i := evs.Len() - 1; i >= 0; i-- { |
|
ev := evs.Get(i) |
|
m[string(ev.Name())] = int32(ev.Number()) |
|
} |
|
enumCache.LoadOrStore(name, m) |
|
} |
|
}) |
|
numFiles++ |
|
return true |
|
}) |
|
numFilesCache.Store(protoPkg, numFiles) |
|
|
|
// Check cache again for enum map. |
|
if v, ok := enumCache.Load(s); ok { |
|
return v.(enumsByName) |
|
} |
|
return nil |
|
} |
|
|
|
// walkEnums recursively walks all enums declared in d. |
|
func walkEnums(d interface { |
|
Enums() protoreflect.EnumDescriptors |
|
Messages() protoreflect.MessageDescriptors |
|
}, f func(protoreflect.EnumDescriptor)) { |
|
eds := d.Enums() |
|
for i := eds.Len() - 1; i >= 0; i-- { |
|
f(eds.Get(i)) |
|
} |
|
mds := d.Messages() |
|
for i := mds.Len() - 1; i >= 0; i-- { |
|
walkEnums(mds.Get(i), f) |
|
} |
|
} |
|
|
|
// messageName is the full name of protobuf message. |
|
type messageName = string |
|
|
|
var messageTypeCache sync.Map // map[messageName]reflect.Type |
|
|
|
// RegisterType is called from generated code to register the message Go type |
|
// for a message of the given name. |
|
// |
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterMessage instead. |
|
func RegisterType(m Message, s messageName) { |
|
mt := protoimpl.X.LegacyMessageTypeOf(m, protoreflect.FullName(s)) |
|
if err := protoregistry.GlobalTypes.RegisterMessage(mt); err != nil { |
|
panic(err) |
|
} |
|
messageTypeCache.Store(s, reflect.TypeOf(m)) |
|
} |
|
|
|
// RegisterMapType is called from generated code to register the Go map type |
|
// for a protobuf message representing a map entry. |
|
// |
|
// Deprecated: Do not use. |
|
func RegisterMapType(m interface{}, s messageName) { |
|
t := reflect.TypeOf(m) |
|
if t.Kind() != reflect.Map { |
|
panic(fmt.Sprintf("invalid map kind: %v", t)) |
|
} |
|
if _, ok := messageTypeCache.Load(s); ok { |
|
panic(fmt.Errorf("proto: duplicate proto message registered: %s", s)) |
|
} |
|
messageTypeCache.Store(s, t) |
|
} |
|
|
|
// MessageType returns the message type for a named message. |
|
// It returns nil if not found. |
|
// |
|
// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead. |
|
func MessageType(s messageName) reflect.Type { |
|
if v, ok := messageTypeCache.Load(s); ok { |
|
return v.(reflect.Type) |
|
} |
|
|
|
// Derive the message type from the v2 registry. |
|
var t reflect.Type |
|
if mt, _ := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(s)); mt != nil { |
|
t = messageGoType(mt) |
|
} |
|
|
|
// If we could not get a concrete type, it is possible that it is a |
|
// pseudo-message for a map entry. |
|
if t == nil { |
|
d, _ := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(s)) |
|
if md, _ := d.(protoreflect.MessageDescriptor); md != nil && md.IsMapEntry() { |
|
kt := goTypeForField(md.Fields().ByNumber(1)) |
|
vt := goTypeForField(md.Fields().ByNumber(2)) |
|
t = reflect.MapOf(kt, vt) |
|
} |
|
} |
|
|
|
// Locally cache the message type for the given name. |
|
if t != nil { |
|
v, _ := messageTypeCache.LoadOrStore(s, t) |
|
return v.(reflect.Type) |
|
} |
|
return nil |
|
} |
|
|
|
func goTypeForField(fd protoreflect.FieldDescriptor) reflect.Type { |
|
switch k := fd.Kind(); k { |
|
case protoreflect.EnumKind: |
|
if et, _ := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName()); et != nil { |
|
return enumGoType(et) |
|
} |
|
return reflect.TypeOf(protoreflect.EnumNumber(0)) |
|
case protoreflect.MessageKind, protoreflect.GroupKind: |
|
if mt, _ := protoregistry.GlobalTypes.FindMessageByName(fd.Message().FullName()); mt != nil { |
|
return messageGoType(mt) |
|
} |
|
return reflect.TypeOf((*protoreflect.Message)(nil)).Elem() |
|
default: |
|
return reflect.TypeOf(fd.Default().Interface()) |
|
} |
|
} |
|
|
|
func enumGoType(et protoreflect.EnumType) reflect.Type { |
|
return reflect.TypeOf(et.New(0)) |
|
} |
|
|
|
func messageGoType(mt protoreflect.MessageType) reflect.Type { |
|
return reflect.TypeOf(MessageV1(mt.Zero().Interface())) |
|
} |
|
|
|
// MessageName returns the full protobuf name for the given message type. |
|
// |
|
// Deprecated: Use protoreflect.MessageDescriptor.FullName instead. |
|
func MessageName(m Message) messageName { |
|
if m == nil { |
|
return "" |
|
} |
|
if m, ok := m.(interface{ XXX_MessageName() messageName }); ok { |
|
return m.XXX_MessageName() |
|
} |
|
return messageName(protoimpl.X.MessageDescriptorOf(m).FullName()) |
|
} |
|
|
|
// RegisterExtension is called from the generated code to register |
|
// the extension descriptor. |
|
// |
|
// Deprecated: Use protoregistry.GlobalTypes.RegisterExtension instead. |
|
func RegisterExtension(d *ExtensionDesc) { |
|
if err := protoregistry.GlobalTypes.RegisterExtension(d); err != nil { |
|
panic(err) |
|
} |
|
} |
|
|
|
type extensionsByNumber = map[int32]*ExtensionDesc |
|
|
|
var extensionCache sync.Map // map[messageName]extensionsByNumber |
|
|
|
// RegisteredExtensions returns a map of the registered extensions for the |
|
// provided protobuf message, indexed by the extension field number. |
|
// |
|
// Deprecated: Use protoregistry.GlobalTypes.RangeExtensionsByMessage instead. |
|
func RegisteredExtensions(m Message) extensionsByNumber { |
|
// Check whether the cache is stale. If the number of extensions for |
|
// the given message differs, then it means that some extensions were |
|
// recently registered upstream that we do not know about. |
|
s := MessageName(m) |
|
v, _ := extensionCache.Load(s) |
|
xs, _ := v.(extensionsByNumber) |
|
if protoregistry.GlobalTypes.NumExtensionsByMessage(protoreflect.FullName(s)) == len(xs) { |
|
return xs // cache is up-to-date |
|
} |
|
|
|
// Cache is stale, re-compute the extensions map. |
|
xs = make(extensionsByNumber) |
|
protoregistry.GlobalTypes.RangeExtensionsByMessage(protoreflect.FullName(s), func(xt protoreflect.ExtensionType) bool { |
|
if xd, ok := xt.(*ExtensionDesc); ok { |
|
xs[int32(xt.TypeDescriptor().Number())] = xd |
|
} else { |
|
// TODO: This implies that the protoreflect.ExtensionType is a |
|
// custom type not generated by protoc-gen-go. We could try and |
|
// convert the type to an ExtensionDesc. |
|
} |
|
return true |
|
}) |
|
extensionCache.Store(s, xs) |
|
return xs |
|
}
|
|
|