mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-02-04 03:39:48 +00:00
reflect/prototype: implement Format methods
Implement Format methods to provide the ability to pretty-print the descriptor in a humanly readable fashion. While this functionality is not strictly necessary for the operation of Go protobuf, it is a useful aid for humans. Change-Id: I88807b38b5be713867f2f2aab5a0843fc147dc35 Reviewed-on: https://go-review.googlesource.com/131255 Reviewed-by: Herbie Ong <herbie@google.com>
This commit is contained in:
parent
3cf6e62f69
commit
17764983d4
@ -6,21 +6,253 @@ package prototype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/proto/internal/pragma"
|
||||
pref "google.golang.org/proto/reflect/protoreflect"
|
||||
)
|
||||
|
||||
// TODO: This is useful for print descriptor types in a human readable way.
|
||||
// This is not strictly necessary.
|
||||
|
||||
// list is an interface that matches any of the list interfaces defined in the
|
||||
// protoreflect package.
|
||||
type list interface {
|
||||
Len() int
|
||||
pragma.DoNotImplement
|
||||
}
|
||||
|
||||
func formatList(s fmt.State, r rune, vs list) {}
|
||||
func formatList(s fmt.State, r rune, vs list) {
|
||||
io.WriteString(s, formatListOpt(vs, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
|
||||
}
|
||||
func formatListOpt(vs list, isRoot, allowMulti bool) string {
|
||||
start, end := "[", "]"
|
||||
if isRoot {
|
||||
var name string
|
||||
switch vs.(type) {
|
||||
case pref.FieldNumbers:
|
||||
name = "FieldNumbers"
|
||||
case pref.FieldRanges:
|
||||
name = "FieldRanges"
|
||||
case pref.FileImports:
|
||||
name = "FileImports"
|
||||
case pref.Descriptor:
|
||||
name = reflect.ValueOf(vs).MethodByName("Get").Type().Out(0).Name() + "s"
|
||||
}
|
||||
start, end = name+randomSpace()+"{", "}"
|
||||
}
|
||||
|
||||
func formatDesc(s fmt.State, r rune, t pref.Descriptor) {}
|
||||
var ss []string
|
||||
switch vs := vs.(type) {
|
||||
case pref.FieldNumbers:
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
ss = append(ss, fmt.Sprint(vs.Get(i)))
|
||||
}
|
||||
return start + joinStrings(ss, false) + end
|
||||
case pref.FieldRanges:
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
r := vs.Get(i)
|
||||
if r[0]+1 == r[1] {
|
||||
ss = append(ss, fmt.Sprintf("%d", r[0]))
|
||||
} else {
|
||||
ss = append(ss, fmt.Sprintf("%d:%d", r[0], r[1]))
|
||||
}
|
||||
}
|
||||
return start + joinStrings(ss, false) + end
|
||||
case pref.FileImports:
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
var rs records
|
||||
rs.Append(reflect.ValueOf(vs.Get(i)), "Path", "Package", "IsPublic", "IsWeak")
|
||||
ss = append(ss, "{"+rs.Join()+"}")
|
||||
}
|
||||
return start + joinStrings(ss, allowMulti) + end
|
||||
default:
|
||||
_, isEnumValue := vs.(pref.EnumValueDescriptors)
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
m := reflect.ValueOf(vs).MethodByName("Get")
|
||||
v := m.Call([]reflect.Value{reflect.ValueOf(i)})[0].Interface()
|
||||
ss = append(ss, formatDescOpt(v.(pref.Descriptor), false, allowMulti && !isEnumValue))
|
||||
}
|
||||
return start + joinStrings(ss, allowMulti && isEnumValue) + end
|
||||
}
|
||||
}
|
||||
|
||||
// descriptorAccessors is a list of accessors to print for each descriptor.
|
||||
//
|
||||
// Do not print all accessors since some contain redundant information,
|
||||
// while others are pointers that we do not want to follow since the descriptor
|
||||
// is actually a cyclic graph.
|
||||
//
|
||||
// Using a list allows us to print the accessors in a sensible order.
|
||||
var descriptorAccessors = map[reflect.Type][]string{
|
||||
reflect.TypeOf((*pref.FileDescriptor)(nil)).Elem(): {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"},
|
||||
reflect.TypeOf((*pref.MessageDescriptor)(nil)).Elem(): {"IsMapEntry", "Fields", "Oneofs", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"},
|
||||
reflect.TypeOf((*pref.FieldDescriptor)(nil)).Elem(): {"Number", "Cardinality", "Kind", "JSONName", "IsPacked", "IsMap", "IsWeak", "Default", "OneofType", "ExtendedType", "MessageType", "EnumType"},
|
||||
reflect.TypeOf((*pref.OneofDescriptor)(nil)).Elem(): {"Fields"}, // not directly used; must keep in sync with formatDescOpt
|
||||
reflect.TypeOf((*pref.EnumDescriptor)(nil)).Elem(): {"Values"},
|
||||
reflect.TypeOf((*pref.EnumValueDescriptor)(nil)).Elem(): {"Number"},
|
||||
reflect.TypeOf((*pref.ServiceDescriptor)(nil)).Elem(): {"Methods"},
|
||||
reflect.TypeOf((*pref.MethodDescriptor)(nil)).Elem(): {"InputType", "OutputType", "IsStreamingClient", "IsStreamingServer"},
|
||||
}
|
||||
|
||||
func formatDesc(s fmt.State, r rune, t pref.Descriptor) {
|
||||
io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
|
||||
}
|
||||
func formatDescOpt(t pref.Descriptor, isRoot, allowMulti bool) string {
|
||||
rv := reflect.ValueOf(t)
|
||||
rt := rv.MethodByName("ProtoType").Type().In(0)
|
||||
|
||||
start, end := "{", "}"
|
||||
if isRoot {
|
||||
start = rt.Name() + randomSpace() + "{"
|
||||
}
|
||||
|
||||
_, isFile := t.(pref.FileDescriptor)
|
||||
rs := records{allowMulti: allowMulti}
|
||||
if t.IsPlaceholder() {
|
||||
if isFile {
|
||||
rs.Append(rv, "Path", "Package", "IsPlaceholder")
|
||||
} else {
|
||||
rs.Append(rv, "FullName", "IsPlaceholder")
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case isFile:
|
||||
rs.Append(rv, "Syntax")
|
||||
case isRoot:
|
||||
rs.Append(rv, "Syntax", "FullName")
|
||||
default:
|
||||
rs.Append(rv, "Name")
|
||||
}
|
||||
if t, ok := t.(pref.OneofDescriptor); ok {
|
||||
var ss []string
|
||||
fs := t.Fields()
|
||||
for i := 0; i < fs.Len(); i++ {
|
||||
ss = append(ss, string(fs.Get(i).Name()))
|
||||
}
|
||||
if len(ss) > 0 {
|
||||
rs.recs = append(rs.recs, [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
|
||||
}
|
||||
} else {
|
||||
rs.Append(rv, descriptorAccessors[rt]...)
|
||||
}
|
||||
// TODO: Print GoType
|
||||
}
|
||||
return start + rs.Join() + end
|
||||
}
|
||||
|
||||
type records struct {
|
||||
recs [][2]string
|
||||
allowMulti bool
|
||||
}
|
||||
|
||||
func (rs *records) Append(v reflect.Value, accessors ...string) {
|
||||
for _, a := range accessors {
|
||||
var rv reflect.Value
|
||||
if m := v.MethodByName(a); m.IsValid() {
|
||||
rv = m.Call(nil)[0]
|
||||
}
|
||||
if v.Kind() == reflect.Struct && !rv.IsValid() {
|
||||
rv = v.FieldByName(a)
|
||||
}
|
||||
if !rv.IsValid() {
|
||||
panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a))
|
||||
}
|
||||
if _, ok := rv.Interface().(pref.Value); ok {
|
||||
rv = rv.MethodByName("Interface").Call(nil)[0]
|
||||
if !rv.IsNil() {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore zero values.
|
||||
var isZero bool
|
||||
switch rv.Kind() {
|
||||
case reflect.Interface, reflect.Slice:
|
||||
isZero = rv.IsNil()
|
||||
case reflect.Bool:
|
||||
isZero = rv.Bool() == false
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
isZero = rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
isZero = rv.Uint() == 0
|
||||
case reflect.String:
|
||||
isZero = rv.String() == ""
|
||||
}
|
||||
if n, ok := rv.Interface().(list); ok {
|
||||
isZero = n.Len() == 0
|
||||
}
|
||||
if isZero {
|
||||
continue
|
||||
}
|
||||
|
||||
// Format the value.
|
||||
var s string
|
||||
v := rv.Interface()
|
||||
switch v := v.(type) {
|
||||
case list:
|
||||
s = formatListOpt(v, false, rs.allowMulti)
|
||||
case pref.FieldDescriptor, pref.OneofDescriptor, pref.EnumValueDescriptor, pref.MethodDescriptor:
|
||||
s = string(v.(pref.Descriptor).Name())
|
||||
case pref.Descriptor:
|
||||
s = string(v.FullName())
|
||||
case string:
|
||||
s = strconv.Quote(v)
|
||||
case []byte:
|
||||
s = fmt.Sprintf("%q", v)
|
||||
default:
|
||||
s = fmt.Sprint(v)
|
||||
}
|
||||
rs.recs = append(rs.recs, [2]string{a, s})
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *records) Join() string {
|
||||
var ss []string
|
||||
|
||||
// In single line mode, simply join all records with commas.
|
||||
if !rs.allowMulti {
|
||||
for _, r := range rs.recs {
|
||||
ss = append(ss, r[0]+": "+r[1])
|
||||
}
|
||||
return joinStrings(ss, false)
|
||||
}
|
||||
|
||||
// In allowMulti line mode, align single line records for more readable output.
|
||||
var maxLen int
|
||||
flush := func(i int) {
|
||||
for _, r := range rs.recs[len(ss):i] {
|
||||
padding := strings.Repeat(" ", maxLen-len(r[0]))
|
||||
ss = append(ss, r[0]+": "+padding+r[1])
|
||||
}
|
||||
maxLen = 0
|
||||
}
|
||||
for i, r := range rs.recs {
|
||||
if isMulti := strings.Contains(r[1], "\n"); isMulti {
|
||||
flush(i)
|
||||
ss = append(ss, r[0]+": "+strings.Join(strings.Split(r[1], "\n"), "\n\t"))
|
||||
} else if maxLen < len(r[0]) {
|
||||
maxLen = len(r[0])
|
||||
}
|
||||
}
|
||||
flush(len(rs.recs))
|
||||
return joinStrings(ss, true)
|
||||
}
|
||||
|
||||
func joinStrings(ss []string, isMulti bool) string {
|
||||
if len(ss) == 0 {
|
||||
return ""
|
||||
}
|
||||
if isMulti {
|
||||
return "\n\t" + strings.Join(ss, "\n\t") + "\n"
|
||||
}
|
||||
return strings.Join(ss, ", ")
|
||||
}
|
||||
|
||||
// randomSpace randomly returns a string that is either empty or a single space.
|
||||
// This is done deliberately to ensure that the output is slightly non-stable.
|
||||
//
|
||||
// These makes it harder for people to depend on the debug string as stable
|
||||
// and provides us the flexibility to make changes.
|
||||
func randomSpace() string {
|
||||
return " "[:rand.Intn(2)]
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package prototype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -63,6 +64,43 @@ func TestDescriptors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDescriptorAccessors tests that descriptorAccessors is up-to-date.
|
||||
func TestDescriptorAccessors(t *testing.T) {
|
||||
ignore := map[string]bool{"ProtoType": true, "DescriptorByName": true}
|
||||
rt := reflect.TypeOf((*pref.Descriptor)(nil)).Elem()
|
||||
for i := 0; i < rt.NumMethod(); i++ {
|
||||
ignore[rt.Method(i).Name] = true
|
||||
}
|
||||
|
||||
for rt, m := range descriptorAccessors {
|
||||
got := map[string]bool{}
|
||||
for _, s := range m {
|
||||
got[s] = true
|
||||
}
|
||||
want := map[string]bool{}
|
||||
for i := 0; i < rt.NumMethod(); i++ {
|
||||
want[rt.Method(i).Name] = true
|
||||
}
|
||||
|
||||
// Check if descriptorAccessors contains a non-existent accessor.
|
||||
// If this test fails, remove the accessor from descriptorAccessors.
|
||||
for s := range got {
|
||||
if !want[s] && !ignore[s] {
|
||||
t.Errorf("%v.%v does not exist", rt, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are new protoreflect interface methods that are not
|
||||
// handled by the formatter. If this fails, either add the method to
|
||||
// ignore or add them to descriptorAccessors.
|
||||
for s := range want {
|
||||
if !got[s] && !ignore[s] {
|
||||
t.Errorf("%v.%v is not called by formatter", rt, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Test NewFileFromDescriptorProto with imported files.
|
||||
|
||||
func TestFile(t *testing.T) {
|
||||
@ -127,18 +165,18 @@ func TestFile(t *testing.T) {
|
||||
Name: "field_six", // "test.B.field_six"
|
||||
Number: 6,
|
||||
Cardinality: pref.Required,
|
||||
Kind: pref.StringKind,
|
||||
Kind: pref.BytesKind,
|
||||
}},
|
||||
Oneofs: []Oneof{
|
||||
{Name: "O1"}, // "test.B.O1"
|
||||
{Name: "O2"}, // "test.B.O2"
|
||||
},
|
||||
ExtensionRanges: [][2]pref.FieldNumber{{1000, 2000}},
|
||||
ExtensionRanges: [][2]pref.FieldNumber{{1000, 2000}, {3000, 3001}},
|
||||
}, {
|
||||
Name: "C", // "test.C"
|
||||
Messages: []Message{{
|
||||
Name: "A", // "test.C.A"
|
||||
Fields: []Field{{Name: "F", Number: 1, Cardinality: pref.Required, Kind: pref.BytesKind}},
|
||||
Fields: []Field{{Name: "F", Number: 1, Cardinality: pref.Required, Kind: pref.BytesKind, Default: pref.ValueOf([]byte("dead\xbe\xef"))}},
|
||||
}},
|
||||
Enums: []Enum{{
|
||||
Name: "E1", // "test.C.E1"
|
||||
@ -244,7 +282,7 @@ func TestFile(t *testing.T) {
|
||||
Name: protoV1.String("field_six"),
|
||||
Number: protoV1.Int32(6),
|
||||
Label: descriptorV1.FieldDescriptorProto_Label(pref.Required).Enum(),
|
||||
Type: descriptorV1.FieldDescriptorProto_Type(pref.StringKind).Enum(),
|
||||
Type: descriptorV1.FieldDescriptorProto_Type(pref.BytesKind).Enum(),
|
||||
}},
|
||||
OneofDecl: []*descriptorV1.OneofDescriptorProto{
|
||||
{Name: protoV1.String("O1")},
|
||||
@ -252,16 +290,18 @@ func TestFile(t *testing.T) {
|
||||
},
|
||||
ExtensionRange: []*descriptorV1.DescriptorProto_ExtensionRange{
|
||||
{Start: protoV1.Int32(1000), End: protoV1.Int32(2000)},
|
||||
{Start: protoV1.Int32(3000), End: protoV1.Int32(3001)},
|
||||
},
|
||||
}, {
|
||||
Name: protoV1.String("C"),
|
||||
NestedType: []*descriptorV1.DescriptorProto{{
|
||||
Name: protoV1.String("A"),
|
||||
Field: []*descriptorV1.FieldDescriptorProto{{
|
||||
Name: protoV1.String("F"),
|
||||
Number: protoV1.Int32(1),
|
||||
Label: descriptorV1.FieldDescriptorProto_Label(pref.Required).Enum(),
|
||||
Type: descriptorV1.FieldDescriptorProto_Type(pref.BytesKind).Enum(),
|
||||
Name: protoV1.String("F"),
|
||||
Number: protoV1.Int32(1),
|
||||
Label: descriptorV1.FieldDescriptorProto_Label(pref.Required).Enum(),
|
||||
Type: descriptorV1.FieldDescriptorProto_Type(pref.BytesKind).Enum(),
|
||||
DefaultValue: protoV1.String(`dead\276\357`),
|
||||
}},
|
||||
}},
|
||||
EnumType: []*descriptorV1.EnumDescriptorProto{{
|
||||
@ -320,12 +360,14 @@ func TestFile(t *testing.T) {
|
||||
{"NewFileFromDescriptorProto", fd2},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Run("Accessors", func(t *testing.T) {
|
||||
// Run sub-tests in parallel to induce potential races.
|
||||
t.Run("", func(t *testing.T) { t.Parallel(); testFileAccessors(t, tt.desc) })
|
||||
t.Run("", func(t *testing.T) { t.Parallel(); testFileAccessors(t, tt.desc) })
|
||||
})
|
||||
// Run sub-tests in parallel to induce potential races.
|
||||
for i := 0; i < 2; i++ {
|
||||
t.Run("Accessors", func(t *testing.T) { t.Parallel(); testFileAccessors(t, tt.desc) })
|
||||
t.Run("FormatCompact", func(t *testing.T) { t.Parallel(); testFileFormatCompact(t, tt.desc) })
|
||||
t.Run("FormatMulti", func(t *testing.T) { t.Parallel(); testFileFormatMulti(t, tt.desc) })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -442,7 +484,7 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) {
|
||||
},
|
||||
"ByNumber:6": M{
|
||||
"Cardinality": pref.Required,
|
||||
"Default": "",
|
||||
"Default": []byte(nil),
|
||||
"OneofType": nil,
|
||||
},
|
||||
},
|
||||
@ -474,13 +516,14 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) {
|
||||
"Has:6": true,
|
||||
},
|
||||
"ExtensionRanges": M{
|
||||
"Len": 1,
|
||||
"Len": 2,
|
||||
"Get:0": [2]pref.FieldNumber{1000, 2000},
|
||||
"Has:999": false,
|
||||
"Has:1000": true,
|
||||
"Has:1500": true,
|
||||
"Has:1999": true,
|
||||
"Has:2000": false,
|
||||
"Has:3000": true,
|
||||
},
|
||||
},
|
||||
"Get:2": M{
|
||||
@ -576,7 +619,6 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) {
|
||||
}
|
||||
checkAccessors(t, "", reflect.ValueOf(fd), want)
|
||||
}
|
||||
|
||||
func checkAccessors(t *testing.T, p string, rv reflect.Value, want map[string]interface{}) {
|
||||
if rv.Interface() == nil {
|
||||
t.Errorf("%v is nil, want non-nil", p)
|
||||
@ -629,6 +671,159 @@ func checkAccessors(t *testing.T, p string, rv reflect.Value, want map[string]in
|
||||
}
|
||||
}
|
||||
|
||||
func testFileFormatCompact(t *testing.T, fd pref.FileDescriptor) {
|
||||
const want = `FileDescriptor{Syntax: proto2, Path: "path/to/file.proto", Package: test, Messages: [{Name: A, IsMapEntry: true, Fields: [{Name: key, Number: 1, Cardinality: optional, Kind: string, JSONName: "key"}, {Name: value, Number: 2, Cardinality: optional, Kind: message, JSONName: "value", MessageType: test.B}]}, {Name: B, Fields: [{Name: field_one, Number: 1, Cardinality: optional, Kind: string, JSONName: "fieldOne", Default: "hello", OneofType: O1}, {Name: field_two, Number: 2, Cardinality: optional, Kind: enum, JSONName: "Field2", Default: 1, OneofType: O2, EnumType: test.E1}, {Name: field_three, Number: 3, Cardinality: optional, Kind: message, JSONName: "fieldThree", OneofType: O2, MessageType: test.C}, {Name: field_four, Number: 4, Cardinality: repeated, Kind: message, JSONName: "Field4", IsMap: true, MessageType: test.A}, {Name: field_five, Number: 5, Cardinality: repeated, Kind: int32, JSONName: "fieldFive", IsPacked: true}, {Name: field_six, Number: 6, Cardinality: required, Kind: bytes, JSONName: "fieldSix"}], Oneofs: [{Name: O1, Fields: [field_one]}, {Name: O2, Fields: [field_two, field_three]}], RequiredNumbers: [6], ExtensionRanges: [1000:2000, 3000]}, {Name: C, Messages: [{Name: A, Fields: [{Name: F, Number: 1, Cardinality: required, Kind: bytes, JSONName: "F", Default: "dead\xbe\xef"}], RequiredNumbers: [1]}], Enums: [{Name: E1, Values: [{Name: FOO}, {Name: BAR, Number: 1}]}], Extensions: [{Name: X, Number: 1000, Cardinality: repeated, Kind: message, ExtendedType: test.B, MessageType: test.C}]}], Enums: [{Name: E1, Values: [{Name: FOO}, {Name: BAR, Number: 1}]}], Extensions: [{Name: X, Number: 1000, Cardinality: repeated, Kind: message, IsPacked: true, ExtendedType: test.B, MessageType: test.C}], Services: [{Name: S, Methods: [{Name: M, InputType: test.A, OutputType: test.C.A, IsStreamingClient: true, IsStreamingServer: true}]}]}`
|
||||
got := fmt.Sprintf("%v", fd)
|
||||
got = strings.Replace(got, "FileDescriptor ", "FileDescriptor", 1) // cleanup randomizer
|
||||
if got != want {
|
||||
t.Errorf("fmt.Sprintf(%q, fd):\ngot: %s\nwant: %s", "%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testFileFormatMulti(t *testing.T, fd pref.FileDescriptor) {
|
||||
const want = `FileDescriptor{
|
||||
Syntax: proto2
|
||||
Path: "path/to/file.proto"
|
||||
Package: test
|
||||
Messages: [{
|
||||
Name: A
|
||||
IsMapEntry: true
|
||||
Fields: [{
|
||||
Name: key
|
||||
Number: 1
|
||||
Cardinality: optional
|
||||
Kind: string
|
||||
JSONName: "key"
|
||||
}, {
|
||||
Name: value
|
||||
Number: 2
|
||||
Cardinality: optional
|
||||
Kind: message
|
||||
JSONName: "value"
|
||||
MessageType: test.B
|
||||
}]
|
||||
}, {
|
||||
Name: B
|
||||
Fields: [{
|
||||
Name: field_one
|
||||
Number: 1
|
||||
Cardinality: optional
|
||||
Kind: string
|
||||
JSONName: "fieldOne"
|
||||
Default: "hello"
|
||||
OneofType: O1
|
||||
}, {
|
||||
Name: field_two
|
||||
Number: 2
|
||||
Cardinality: optional
|
||||
Kind: enum
|
||||
JSONName: "Field2"
|
||||
Default: 1
|
||||
OneofType: O2
|
||||
EnumType: test.E1
|
||||
}, {
|
||||
Name: field_three
|
||||
Number: 3
|
||||
Cardinality: optional
|
||||
Kind: message
|
||||
JSONName: "fieldThree"
|
||||
OneofType: O2
|
||||
MessageType: test.C
|
||||
}, {
|
||||
Name: field_four
|
||||
Number: 4
|
||||
Cardinality: repeated
|
||||
Kind: message
|
||||
JSONName: "Field4"
|
||||
IsMap: true
|
||||
MessageType: test.A
|
||||
}, {
|
||||
Name: field_five
|
||||
Number: 5
|
||||
Cardinality: repeated
|
||||
Kind: int32
|
||||
JSONName: "fieldFive"
|
||||
IsPacked: true
|
||||
}, {
|
||||
Name: field_six
|
||||
Number: 6
|
||||
Cardinality: required
|
||||
Kind: bytes
|
||||
JSONName: "fieldSix"
|
||||
}]
|
||||
Oneofs: [{
|
||||
Name: O1
|
||||
Fields: [field_one]
|
||||
}, {
|
||||
Name: O2
|
||||
Fields: [field_two, field_three]
|
||||
}]
|
||||
RequiredNumbers: [6]
|
||||
ExtensionRanges: [1000:2000, 3000]
|
||||
}, {
|
||||
Name: C
|
||||
Messages: [{
|
||||
Name: A
|
||||
Fields: [{
|
||||
Name: F
|
||||
Number: 1
|
||||
Cardinality: required
|
||||
Kind: bytes
|
||||
JSONName: "F"
|
||||
Default: "dead\xbe\xef"
|
||||
}]
|
||||
RequiredNumbers: [1]
|
||||
}]
|
||||
Enums: [{
|
||||
Name: E1
|
||||
Values: [
|
||||
{Name: FOO}
|
||||
{Name: BAR, Number: 1}
|
||||
]
|
||||
}]
|
||||
Extensions: [{
|
||||
Name: X
|
||||
Number: 1000
|
||||
Cardinality: repeated
|
||||
Kind: message
|
||||
ExtendedType: test.B
|
||||
MessageType: test.C
|
||||
}]
|
||||
}]
|
||||
Enums: [{
|
||||
Name: E1
|
||||
Values: [
|
||||
{Name: FOO}
|
||||
{Name: BAR, Number: 1}
|
||||
]
|
||||
}]
|
||||
Extensions: [{
|
||||
Name: X
|
||||
Number: 1000
|
||||
Cardinality: repeated
|
||||
Kind: message
|
||||
IsPacked: true
|
||||
ExtendedType: test.B
|
||||
MessageType: test.C
|
||||
}]
|
||||
Services: [{
|
||||
Name: S
|
||||
Methods: [{
|
||||
Name: M
|
||||
InputType: test.A
|
||||
OutputType: test.C.A
|
||||
IsStreamingClient: true
|
||||
IsStreamingServer: true
|
||||
}]
|
||||
}]
|
||||
}`
|
||||
got := fmt.Sprintf("%+v", fd)
|
||||
got = strings.Replace(got, "FileDescriptor ", "FileDescriptor", 1) // cleanup randomizer
|
||||
if got != want {
|
||||
t.Errorf("fmt.Sprintf(%q, fd):\ngot: %s\nwant: %s", "%+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
f := &File{
|
||||
Syntax: pref.Proto2,
|
||||
|
Loading…
x
Reference in New Issue
Block a user