goprotobuf: Repeated extensions.
Also picks up a tweak to the JSON tags of the XXX_ fields.
R=r
CC=golang-dev
http://codereview.appspot.com/6175045
diff --git a/proto/decode.go b/proto/decode.go
index f259b7d..de885f6 100644
--- a/proto/decode.go
+++ b/proto/decode.go
@@ -357,7 +357,9 @@
iv := reflect.NewAt(st, unsafe.Pointer(base)).Interface()
if e, ok := iv.(extendableProto); ok && isExtensionField(e, int32(tag)) {
if err = o.skip(st, tag, wire); err == nil {
- e.ExtensionMap()[int32(tag)] = Extension{enc: append([]byte(nil), o.buf[oi:o.index]...)}
+ ext := e.ExtensionMap()[int32(tag)] // may be missing
+ ext.enc = append(ext.enc, o.buf[oi:o.index]...)
+ e.ExtensionMap()[int32(tag)] = ext
}
continue
}
diff --git a/proto/extensions.go b/proto/extensions.go
index 70297c2..b9ba8b0 100644
--- a/proto/extensions.go
+++ b/proto/extensions.go
@@ -66,6 +66,11 @@
Tag string // protobuf tag style
}
+func (ed *ExtensionDesc) repeated() bool {
+ t := reflect.TypeOf(ed.ExtensionType)
+ return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
+}
+
/*
Extension represents an extension in a message.
@@ -192,21 +197,33 @@
// decodeExtension decodes an extension encoded in b.
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
- // Discard wire type and field number varint. It isn't needed.
- _, n := DecodeVarint(b)
- o := NewBuffer(b[n:])
+ o := NewBuffer(b)
t := reflect.TypeOf(extension.ExtensionType)
+ rep := extension.repeated()
+
props := &Properties{}
props.Init(t, "irrelevant_name", extension.Tag, 0)
- // t is a pointer, likely to a struct.
- // Allocate a "field" to store the pointer itself; the
- // struct pointer will be stored here. We pass
+ // t is a pointer to a struct, pointer to basic type or a slice.
+ // Allocate a "field" to store the pointer/slice itself; the
+ // pointer/slice will be stored here. We pass
// the address of this field to props.dec.
value := reflect.New(t).Elem()
- if err := props.dec(o, props, value.UnsafeAddr()); err != nil {
- return nil, err
+
+ for {
+ // Discard wire type and field number varint. It isn't needed.
+ if _, err := o.DecodeVarint(); err != nil {
+ return nil, err
+ }
+
+ if err := props.dec(o, props, value.UnsafeAddr()); err != nil {
+ return nil, err
+ }
+
+ if !rep || o.index >= len(o.buf) {
+ break
+ }
}
return value.Interface(), nil
}
@@ -229,10 +246,6 @@
return
}
-// TODO: (needed for repeated extensions)
-// - ExtensionSize
-// - AddExtension
-
// SetExtension sets the specified extension of pb to the specified value.
func SetExtension(pb extendableProto, extension *ExtensionDesc, value interface{}) error {
if err := checkExtensionTypes(pb, extension); err != nil {
diff --git a/proto/testdata/test.pb.go b/proto/testdata/test.pb.go
index d2a2c16..fdf5500 100644
--- a/proto/testdata/test.pb.go
+++ b/proto/testdata/test.pb.go
@@ -165,7 +165,7 @@
type GoEnum struct {
Foo *FOO `protobuf:"varint,1,req,name=foo,enum=testdata.FOO" json:"foo,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *GoEnum) Reset() { *this = GoEnum{} }
@@ -174,7 +174,7 @@
type GoTestField struct {
Label *string `protobuf:"bytes,1,req" json:"Label,omitempty"`
Type *string `protobuf:"bytes,2,req" json:"Type,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *GoTestField) Reset() { *this = GoTestField{} }
@@ -253,7 +253,7 @@
Requiredgroup *GoTest_RequiredGroup `protobuf:"group,70,req,name=RequiredGroup" json:"requiredgroup,omitempty"`
Repeatedgroup []*GoTest_RepeatedGroup `protobuf:"group,80,rep,name=RepeatedGroup" json:"repeatedgroup,omitempty"`
Optionalgroup *GoTest_OptionalGroup `protobuf:"group,90,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *GoTest) Reset() { *this = GoTest{} }
@@ -277,7 +277,7 @@
type GoTest_RequiredGroup struct {
RequiredField *string `protobuf:"bytes,71,req" json:"RequiredField,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *GoTest_RequiredGroup) Reset() { *this = GoTest_RequiredGroup{} }
@@ -285,7 +285,7 @@
type GoTest_RepeatedGroup struct {
RequiredField *string `protobuf:"bytes,81,req" json:"RequiredField,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *GoTest_RepeatedGroup) Reset() { *this = GoTest_RepeatedGroup{} }
@@ -293,7 +293,7 @@
type GoTest_OptionalGroup struct {
RequiredField *string `protobuf:"bytes,91,req" json:"RequiredField,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *GoTest_OptionalGroup) Reset() { *this = GoTest_OptionalGroup{} }
@@ -305,7 +305,7 @@
SkipFixed64 *uint64 `protobuf:"fixed64,13,req,name=skip_fixed64" json:"skip_fixed64,omitempty"`
SkipString *string `protobuf:"bytes,14,req,name=skip_string" json:"skip_string,omitempty"`
Skipgroup *GoSkipTest_SkipGroup `protobuf:"group,15,req,name=SkipGroup" json:"skipgroup,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *GoSkipTest) Reset() { *this = GoSkipTest{} }
@@ -314,7 +314,7 @@
type GoSkipTest_SkipGroup struct {
GroupInt32 *int32 `protobuf:"varint,16,req,name=group_int32" json:"group_int32,omitempty"`
GroupString *string `protobuf:"bytes,17,req,name=group_string" json:"group_string,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *GoSkipTest_SkipGroup) Reset() { *this = GoSkipTest_SkipGroup{} }
@@ -322,7 +322,7 @@
type NonPackedTest struct {
A []int32 `protobuf:"varint,1,rep,name=a" json:"a,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *NonPackedTest) Reset() { *this = NonPackedTest{} }
@@ -330,7 +330,7 @@
type PackedTest struct {
B []int32 `protobuf:"varint,1,rep,packed,name=b" json:"b,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *PackedTest) Reset() { *this = PackedTest{} }
@@ -338,7 +338,7 @@
type MaxTag struct {
LastField *string `protobuf:"bytes,536870911,opt,name=last_field" json:"last_field,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *MaxTag) Reset() { *this = MaxTag{} }
@@ -348,7 +348,7 @@
Host *string `protobuf:"bytes,1,req,name=host" json:"host,omitempty"`
Port *int32 `protobuf:"varint,2,opt,name=port,def=4000" json:"port,omitempty"`
Connected *bool `protobuf:"varint,3,opt,name=connected" json:"connected,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *InnerMessage) Reset() { *this = InnerMessage{} }
@@ -361,7 +361,7 @@
Value []byte `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
Weight *float32 `protobuf:"fixed32,3,opt,name=weight" json:"weight,omitempty"`
Inner *InnerMessage `protobuf:"bytes,4,opt,name=inner" json:"inner,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *OtherMessage) Reset() { *this = OtherMessage{} }
@@ -377,8 +377,8 @@
Bikeshed *MyMessage_Color `protobuf:"varint,7,opt,name=bikeshed,enum=testdata.MyMessage_Color" json:"bikeshed,omitempty"`
Somegroup *MyMessage_SomeGroup `protobuf:"group,8,opt,name=SomeGroup" json:"somegroup,omitempty"`
RepBytes [][]byte `protobuf:"bytes,10,rep,name=rep_bytes" json:"rep_bytes,omitempty"`
- XXX_extensions map[int32]proto.Extension `json:",omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_extensions map[int32]proto.Extension `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *MyMessage) Reset() { *this = MyMessage{} }
@@ -400,7 +400,7 @@
type MyMessage_SomeGroup struct {
GroupField *int32 `protobuf:"varint,9,opt,name=group_field" json:"group_field,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *MyMessage_SomeGroup) Reset() { *this = MyMessage_SomeGroup{} }
@@ -408,7 +408,7 @@
type Ext struct {
Data *string `protobuf:"bytes,1,opt,name=data" json:"data,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *Ext) Reset() { *this = Ext{} }
@@ -440,7 +440,7 @@
type MessageList struct {
Message []*MessageList_Message `protobuf:"group,1,rep" json:"message,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *MessageList) Reset() { *this = MessageList{} }
@@ -449,7 +449,7 @@
type MessageList_Message struct {
Name *string `protobuf:"bytes,2,req,name=name" json:"name,omitempty"`
Count *int32 `protobuf:"varint,3,req,name=count" json:"count,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *MessageList_Message) Reset() { *this = MessageList_Message{} }
@@ -458,7 +458,7 @@
type Strings struct {
StringField *string `protobuf:"bytes,1,opt,name=string_field" json:"string_field,omitempty"`
BytesField []byte `protobuf:"bytes,2,opt,name=bytes_field" json:"bytes_field,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *Strings) Reset() { *this = Strings{} }
@@ -483,7 +483,7 @@
F_Ninf *float32 `protobuf:"fixed32,16,opt,def=-inf" json:"F_Ninf,omitempty"`
F_Nan *float32 `protobuf:"fixed32,17,opt,def=nan" json:"F_Nan,omitempty"`
Sub *SubDefaults `protobuf:"bytes,18,opt,name=sub" json:"sub,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *Defaults) Reset() { *this = Defaults{} }
@@ -512,7 +512,7 @@
type SubDefaults struct {
N *int64 `protobuf:"varint,1,opt,name=n,def=7" json:"n,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *SubDefaults) Reset() { *this = SubDefaults{} }
@@ -522,12 +522,20 @@
type RepeatedEnum struct {
Color []RepeatedEnum_Color `protobuf:"varint,1,rep,name=color,enum=testdata.RepeatedEnum_Color" json:"color,omitempty"`
- XXX_unrecognized []byte `json:",omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (this *RepeatedEnum) Reset() { *this = RepeatedEnum{} }
func (this *RepeatedEnum) String() string { return proto.CompactTextString(this) }
+var E_Greeting = &proto.ExtensionDesc{
+ ExtendedType: (*MyMessage)(nil),
+ ExtensionType: ([]string)(nil),
+ Field: 106,
+ Name: "testdata.greeting",
+ Tag: "bytes,106,rep,name=greeting",
+}
+
func init() {
proto.RegisterEnum("testdata.FOO", FOO_name, FOO_value)
proto.RegisterEnum("testdata.GoTest_KIND", GoTest_KIND_name, GoTest_KIND_value)
@@ -537,4 +545,5 @@
proto.RegisterExtension(E_Ext_More)
proto.RegisterExtension(E_Ext_Text)
proto.RegisterExtension(E_Ext_Number)
+ proto.RegisterExtension(E_Greeting)
}
diff --git a/proto/testdata/test.proto b/proto/testdata/test.proto
index 45d9df4..97a28dd 100644
--- a/proto/testdata/test.proto
+++ b/proto/testdata/test.proto
@@ -248,6 +248,10 @@
optional string data = 1;
}
+extend MyMessage {
+ repeated string greeting = 106;
+}
+
message MessageList {
repeated group Message = 1 {
required string name = 2;
diff --git a/proto/text.go b/proto/text.go
index 9cd7b69..a8c5429 100644
--- a/proto/text.go
+++ b/proto/text.go
@@ -410,15 +410,27 @@
continue
}
- fmt.Fprintf(w, "[%s]:", desc.Name)
- if !w.compact {
- w.WriteByte(' ')
+ // Repeated extensions will appear as a slice.
+ if !desc.repeated() {
+ writeExtension(w, desc.Name, pb)
+ } else {
+ v := reflect.ValueOf(pb)
+ for i := 0; i < v.Len(); i++ {
+ writeExtension(w, desc.Name, v.Index(i).Interface())
+ }
}
- writeAny(w, reflect.ValueOf(pb), nil)
- w.WriteByte('\n')
}
}
+func writeExtension(w *textWriter, name string, pb interface{}) {
+ fmt.Fprintf(w, "[%s]:", name)
+ if !w.compact {
+ w.WriteByte(' ')
+ }
+ writeAny(w, reflect.ValueOf(pb), nil)
+ w.WriteByte('\n')
+}
+
func marshalText(w io.Writer, pb interface{}, compact bool) {
if pb == nil {
w.Write([]byte("<nil>"))
diff --git a/proto/text_parser.go b/proto/text_parser.go
index 2124308..3800188 100644
--- a/proto/text_parser.go
+++ b/proto/text_parser.go
@@ -350,14 +350,33 @@
return err
}
+ rep := desc.repeated()
+
// Read the extension structure, and set it in
// the value we're constructing.
- ext := reflect.New(typ).Elem()
+ var ext reflect.Value
+ if !rep {
+ ext = reflect.New(typ).Elem()
+ } else {
+ ext = reflect.New(typ.Elem()).Elem()
+ }
if err := p.readAny(ext, props); err != nil {
return err
}
- SetExtension(sv.Addr().Interface().(extendableProto),
- desc, ext.Interface())
+ ep := sv.Addr().Interface().(extendableProto)
+ if !rep {
+ SetExtension(ep, desc, ext.Interface())
+ } else {
+ old, err := GetExtension(ep, desc)
+ var sl reflect.Value
+ if err == nil {
+ sl = reflect.ValueOf(old) // existing slice
+ } else {
+ sl = reflect.MakeSlice(typ, 0, 1)
+ }
+ sl = reflect.Append(sl, ext)
+ SetExtension(ep, desc, sl.Interface())
+ }
} else {
// This is a normal, non-extension field.
fi, props, ok := structFieldByName(st, tok.value)
diff --git a/proto/text_parser_test.go b/proto/text_parser_test.go
index 6212e9e..dbd6b2c 100644
--- a/proto/text_parser_test.go
+++ b/proto/text_parser_test.go
@@ -64,6 +64,16 @@
return UnmarshalTextTest{in: text, out: msg}
}
+func buildExtRepStringTest(text string) UnmarshalTextTest {
+ msg := &MyMessage{
+ Count: Int32(42),
+ }
+ if err := SetExtension(msg, E_Greeting, []string{"bula", "hola"}); err != nil {
+ panic(err)
+ }
+ return UnmarshalTextTest{in: text, out: msg}
+}
+
var unMarshalTextTests = []UnmarshalTextTest{
// Basic
{
@@ -225,6 +235,7 @@
buildExtStructTest(`count: 42 [testdata.Ext.more]:<data:"Hello, world!" >`),
buildExtStructTest(`count: 42 [testdata.Ext.more] {data:"Hello, world!"}`),
buildExtDataTest(`count: 42 [testdata.Ext.text]:"Hello, world!" [testdata.Ext.number]:1729`),
+ buildExtRepStringTest(`count: 42 [testdata.greeting]:"bula" [testdata.greeting]:"hola"`),
// Big all-in-one
{
diff --git a/proto/text_test.go b/proto/text_test.go
index 7d8adfb..f09b1ab 100644
--- a/proto/text_test.go
+++ b/proto/text_test.go
@@ -79,6 +79,10 @@
if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
panic(err)
}
+ greetings := []string{"adg", "easy", "cow"}
+ if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
+ panic(err)
+ }
// Add an unknown extension. We marshal a pb.Ext, and fake the ID.
b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")})
@@ -126,6 +130,9 @@
[testdata.Ext.more]: <
data: "Big gobs for big rats"
>
+[testdata.greeting]: "adg"
+[testdata.greeting]: "easy"
+[testdata.greeting]: "cow"
/* 13 unknown bytes */
tag201: "\t3G skiing"
/* 3 unknown bytes */