mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-17 10:11:58 +00:00
6f9095c675
Rather than having the Converter carry a NewMessage method, have the struct simply expose the MessageType or EnumType since they carry more information and are retrieved anyways as part of the functionality of NewConverter. While changing Converter, export the fields and remove all the methods. Also, add an IsLegacy boolean, which is useful for the later implementation of the extension fields. Add a wrapLegacyEnum function which is used to wrap v1 enums as v2 enums. We use this functionality in NewLegacyConverter to detrive the EnumType. Additionally, modify wrapLegacyMessage to return a protoreflect.ProtoMessage to be consistent with wrapLegacyEnum which must return a protoreflect.ProtoEnum. Change-Id: Idc8989d07e4895d30de4ebc22c9ffa7357815cad Reviewed-on: https://go-review.googlesource.com/c/148827 Reviewed-by: Herbie Ong <herbie@google.com>
151 lines
4.6 KiB
Go
151 lines
4.6 KiB
Go
// Copyright 2018 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 impl
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"sync"
|
|
|
|
descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
|
pvalue "github.com/golang/protobuf/v2/internal/value"
|
|
pref "github.com/golang/protobuf/v2/reflect/protoreflect"
|
|
ptype "github.com/golang/protobuf/v2/reflect/prototype"
|
|
)
|
|
|
|
var enumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType
|
|
|
|
// wrapLegacyEnum wraps v as a protoreflect.ProtoEnum,
|
|
// where v must be an int32 kind and not implement the v2 API already.
|
|
func wrapLegacyEnum(v reflect.Value) pref.ProtoEnum {
|
|
// Fast-path: check if a EnumType is cached for this concrete type.
|
|
if et, ok := enumTypeCache.Load(v.Type()); ok {
|
|
return et.(pref.EnumType).New(pref.EnumNumber(v.Int()))
|
|
}
|
|
|
|
// Slow-path: derive enum descriptor and initialize EnumType.
|
|
var m sync.Map // map[protoreflect.EnumNumber]proto.Enum
|
|
ed := loadEnumDesc(v.Type())
|
|
et := ptype.GoEnum(ed, func(et pref.EnumType, n pref.EnumNumber) pref.ProtoEnum {
|
|
if e, ok := m.Load(n); ok {
|
|
return e.(pref.ProtoEnum)
|
|
}
|
|
e := &legacyEnumWrapper{num: n, pbTyp: et, goTyp: v.Type()}
|
|
m.Store(n, e)
|
|
return e
|
|
})
|
|
enumTypeCache.Store(v.Type(), et)
|
|
return et.(pref.EnumType).New(pref.EnumNumber(v.Int()))
|
|
}
|
|
|
|
type legacyEnumWrapper struct {
|
|
num pref.EnumNumber
|
|
pbTyp pref.EnumType
|
|
goTyp reflect.Type
|
|
}
|
|
|
|
func (e *legacyEnumWrapper) Number() pref.EnumNumber {
|
|
return e.num
|
|
}
|
|
func (e *legacyEnumWrapper) Type() pref.EnumType {
|
|
return e.pbTyp
|
|
}
|
|
func (e *legacyEnumWrapper) ProtoReflect() pref.Enum {
|
|
return e
|
|
}
|
|
func (e *legacyEnumWrapper) Unwrap() interface{} {
|
|
v := reflect.New(e.goTyp).Elem()
|
|
v.SetInt(int64(e.num))
|
|
return v.Interface()
|
|
}
|
|
|
|
var (
|
|
_ pref.Enum = (*legacyEnumWrapper)(nil)
|
|
_ pref.ProtoEnum = (*legacyEnumWrapper)(nil)
|
|
_ pvalue.Unwrapper = (*legacyEnumWrapper)(nil)
|
|
)
|
|
|
|
var enumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
|
|
|
|
// loadEnumDesc returns an EnumDescriptor derived from the Go type,
|
|
// which must be an int32 kind and not implement the v2 API already.
|
|
func loadEnumDesc(t reflect.Type) pref.EnumDescriptor {
|
|
// Fast-path: check if an EnumDescriptor is cached for this concrete type.
|
|
if v, ok := enumDescCache.Load(t); ok {
|
|
return v.(pref.EnumDescriptor)
|
|
}
|
|
|
|
// Slow-path: initialize EnumDescriptor from the proto descriptor.
|
|
if t.Kind() != reflect.Int32 || t.PkgPath() == "" {
|
|
panic(fmt.Sprintf("got %v, want named int32 kind", t))
|
|
}
|
|
|
|
// Derive the enum descriptor from the raw descriptor proto.
|
|
e := new(ptype.StandaloneEnum)
|
|
ev := reflect.Zero(t).Interface()
|
|
if _, ok := ev.(pref.ProtoEnum); ok {
|
|
panic(fmt.Sprintf("%v already implements proto.Enum", t))
|
|
}
|
|
if ed, ok := ev.(legacyEnum); ok {
|
|
b, idxs := ed.EnumDescriptor()
|
|
fd := loadFileDesc(b)
|
|
|
|
// Derive syntax.
|
|
switch fd.GetSyntax() {
|
|
case "proto2", "":
|
|
e.Syntax = pref.Proto2
|
|
case "proto3":
|
|
e.Syntax = pref.Proto3
|
|
}
|
|
|
|
// Derive the full name and correct enum descriptor.
|
|
var ed *descriptorV1.EnumDescriptorProto
|
|
e.FullName = pref.FullName(fd.GetPackage())
|
|
if len(idxs) == 1 {
|
|
ed = fd.EnumType[idxs[0]]
|
|
e.FullName = e.FullName.Append(pref.Name(ed.GetName()))
|
|
} else {
|
|
md := fd.MessageType[idxs[0]]
|
|
e.FullName = e.FullName.Append(pref.Name(md.GetName()))
|
|
for _, i := range idxs[1 : len(idxs)-1] {
|
|
md = md.NestedType[i]
|
|
e.FullName = e.FullName.Append(pref.Name(md.GetName()))
|
|
}
|
|
ed = md.EnumType[idxs[len(idxs)-1]]
|
|
e.FullName = e.FullName.Append(pref.Name(ed.GetName()))
|
|
}
|
|
|
|
// Derive the enum values.
|
|
for _, vd := range ed.GetValue() {
|
|
e.Values = append(e.Values, ptype.EnumValue{
|
|
Name: pref.Name(vd.GetName()),
|
|
Number: pref.EnumNumber(vd.GetNumber()),
|
|
})
|
|
}
|
|
} else {
|
|
// If the type does not implement legacyEnum, then there is no reliable
|
|
// way to derive the original protobuf type information.
|
|
// We are unable to use the global enum registry since it is
|
|
// unfortunately keyed by the full name, which we do not know.
|
|
// Furthermore, some generated enums register with a fork of
|
|
// golang/protobuf so the enum may not even be found in the registry.
|
|
//
|
|
// Instead, create a bogus enum descriptor to ensure that
|
|
// most operations continue to work. For example, textpb and jsonpb
|
|
// will be unable to parse a message with an enum value by name.
|
|
e.Syntax = pref.Proto2
|
|
e.FullName = deriveFullName(t)
|
|
e.Values = []ptype.EnumValue{{Name: "INVALID", Number: math.MinInt32}}
|
|
}
|
|
|
|
ed, err := ptype.NewEnum(e)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
enumDescCache.Store(t, ed)
|
|
return ed
|
|
}
|