proto: allow text proto map keys to be omitted or order swapped
diff --git a/proto/equal.go b/proto/equal.go
index cafb99f..8b16f95 100644
--- a/proto/equal.go
+++ b/proto/equal.go
@@ -191,6 +191,13 @@
}
return true
case reflect.Ptr:
+ // Maps may have nil values in them, so check for nil.
+ if v1.IsNil() && v2.IsNil() {
+ return true
+ }
+ if v1.IsNil() != v2.IsNil() {
+ return false
+ }
return equalAny(v1.Elem(), v2.Elem(), prop)
case reflect.Slice:
if v1.Type().Elem().Kind() == reflect.Uint8 {
diff --git a/proto/text_parser.go b/proto/text_parser.go
index 8fa9f3a..0b8c59f 100644
--- a/proto/text_parser.go
+++ b/proto/text_parser.go
@@ -602,8 +602,9 @@
// The map entry should be this sequence of tokens:
// < key : KEY value : VALUE >
- // Technically the "key" and "value" could come in any order,
- // but in practice they won't.
+ // However, implementations may omit key or value, and technically
+ // we should support them in any order. See b/28924776 for a time
+ // this went wrong.
tok := p.next()
var terminator string
@@ -615,32 +616,39 @@
default:
return p.errorf("expected '{' or '<', found %q", tok.value)
}
- if err := p.consumeToken("key"); err != nil {
- return err
- }
- if err := p.consumeToken(":"); err != nil {
- return err
- }
- if err := p.readAny(key, props.mkeyprop); err != nil {
- return err
- }
- if err := p.consumeOptionalSeparator(); err != nil {
- return err
- }
- if err := p.consumeToken("value"); err != nil {
- return err
- }
- if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil {
- return err
- }
- if err := p.readAny(val, props.mvalprop); err != nil {
- return err
- }
- if err := p.consumeOptionalSeparator(); err != nil {
- return err
- }
- if err := p.consumeToken(terminator); err != nil {
- return err
+ for {
+ tok := p.next()
+ if tok.err != nil {
+ return tok.err
+ }
+ if tok.value == terminator {
+ break
+ }
+ switch tok.value {
+ case "key":
+ if err := p.consumeToken(":"); err != nil {
+ return err
+ }
+ if err := p.readAny(key, props.mkeyprop); err != nil {
+ return err
+ }
+ if err := p.consumeOptionalSeparator(); err != nil {
+ return err
+ }
+ case "value":
+ if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil {
+ return err
+ }
+ if err := p.readAny(val, props.mvalprop); err != nil {
+ return err
+ }
+ if err := p.consumeOptionalSeparator(); err != nil {
+ return err
+ }
+ default:
+ p.back()
+ return p.errorf(`expected "key", "value", or %q, found %q`, terminator, tok.value)
+ }
}
dst.SetMapIndex(key, val)
diff --git a/proto/text_parser_test.go b/proto/text_parser_test.go
index 32c4117..f25d5b0 100644
--- a/proto/text_parser_test.go
+++ b/proto/text_parser_test.go
@@ -508,7 +508,10 @@
const in = `name_mapping:<key:1234 value:"Feist"> name_mapping:<key:1 value:"Beatles">` +
`msg_mapping:<key:-4, value:<f: 2.0>,>` + // separating commas are okay
`msg_mapping<key:-2 value<f: 4.0>>` + // no colon after "value"
- `byte_mapping:<key:true value:"so be it">`
+ `msg_mapping:<value:<f: 5.0>>` + // omitted key
+ `msg_mapping:<key:1>` + // omitted value
+ `byte_mapping:<key:true value:"so be it">` +
+ `byte_mapping:<>` // omitted key and value
want := &MessageWithMap{
NameMapping: map[int32]string{
1: "Beatles",
@@ -517,9 +520,12 @@
MsgMapping: map[int64]*FloatingPoint{
-4: {F: Float64(2.0)},
-2: {F: Float64(4.0)},
+ 0: {F: Float64(5.0)},
+ 1: nil,
},
ByteMapping: map[bool][]byte{
- true: []byte("so be it"),
+ false: nil,
+ true: []byte("so be it"),
},
}
if err := UnmarshalText(in, m); err != nil {