Implement oneof support.
This includes the code generation changes,
and the infrastructure to wire it up to the encode/decode machinery.
The overall API changes are these:
- oneofs in a message are replaced by a single interface field
- each field in a oneof gets a distinguished type that satisfies
the corresponding interface
- a type switch may be used to distinguish between oneof fields
Fixes #29.
diff --git a/proto/text_parser.go b/proto/text_parser.go
index 7d0c757..2e2a67f 100644
--- a/proto/text_parser.go
+++ b/proto/text_parser.go
@@ -385,8 +385,7 @@
}
// Returns the index in the struct for the named field, as well as the parsed tag properties.
-func structFieldByName(st reflect.Type, name string) (int, *Properties, bool) {
- sprops := GetProperties(st)
+func structFieldByName(sprops *StructProperties, name string) (int, *Properties, bool) {
i, ok := sprops.decoderOrigNames[name]
if ok {
return i, sprops.Prop[i], true
@@ -438,7 +437,8 @@
func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
st := sv.Type()
- reqCount := GetProperties(st).reqCount
+ sprops := GetProperties(st)
+ reqCount := sprops.reqCount
var reqFieldErr error
fieldSet := make(map[string]bool)
// A struct is a sequence of "name: value", terminated by one of
@@ -520,95 +520,129 @@
sl = reflect.Append(sl, ext)
SetExtension(ep, desc, sl.Interface())
}
+ if err := p.consumeOptionalSeparator(); err != nil {
+ return err
+ }
+ continue
+ }
+
+ // This is a normal, non-extension field.
+ name := tok.value
+ var dst reflect.Value
+ fi, props, ok := structFieldByName(sprops, name)
+ if ok {
+ dst = sv.Field(fi)
} else {
- // This is a normal, non-extension field.
- name := tok.value
- fi, props, ok := structFieldByName(st, name)
- if !ok {
- return p.errorf("unknown field name %q in %v", name, st)
+ // Maybe it is a oneof.
+ // TODO: If this shows in profiles, cache the mapping.
+ for _, oot := range sprops.oneofTypes {
+ sp := reflect.ValueOf(oot).Type() // *T
+ var p Properties
+ p.Parse(sp.Elem().Field(0).Tag.Get("protobuf"))
+ if p.OrigName != name {
+ continue
+ }
+ nv := reflect.New(sp.Elem())
+ dst = nv.Elem().Field(0)
+ props = &p
+ // There will be exactly one interface field that
+ // this new value is assignable to.
+ for i := 0; i < st.NumField(); i++ {
+ f := st.Field(i)
+ if f.Type.Kind() != reflect.Interface {
+ continue
+ }
+ if !nv.Type().AssignableTo(f.Type) {
+ continue
+ }
+ sv.Field(i).Set(nv)
+ break
+ }
+ break
}
+ }
+ if !dst.IsValid() {
+ return p.errorf("unknown field name %q in %v", name, st)
+ }
- dst := sv.Field(fi)
-
- if dst.Kind() == reflect.Map {
- // Consume any colon.
- if err := p.checkForColon(props, dst.Type()); err != nil {
- return err
- }
-
- // Construct the map if it doesn't already exist.
- if dst.IsNil() {
- dst.Set(reflect.MakeMap(dst.Type()))
- }
- key := reflect.New(dst.Type().Key()).Elem()
- val := reflect.New(dst.Type().Elem()).Elem()
-
- // 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.
-
- tok := p.next()
- var terminator string
- switch tok.value {
- case "<":
- terminator = ">"
- case "{":
- terminator = "}"
- 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
- }
-
- dst.SetMapIndex(key, val)
- continue
- }
-
- // Check that it's not already set if it's not a repeated field.
- if !props.Repeated && fieldSet[name] {
- return p.errorf("non-repeated field %q was repeated", name)
- }
-
- if err := p.checkForColon(props, st.Field(fi).Type); err != nil {
+ if dst.Kind() == reflect.Map {
+ // Consume any colon.
+ if err := p.checkForColon(props, dst.Type()); err != nil {
return err
}
- // Parse into the field.
- fieldSet[name] = true
- if err := p.readAny(dst, props); err != nil {
- if _, ok := err.(*RequiredNotSetError); !ok {
- return err
- }
- reqFieldErr = err
- } else if props.Required {
- reqCount--
+ // Construct the map if it doesn't already exist.
+ if dst.IsNil() {
+ dst.Set(reflect.MakeMap(dst.Type()))
}
+ key := reflect.New(dst.Type().Key()).Elem()
+ val := reflect.New(dst.Type().Elem()).Elem()
+
+ // 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.
+
+ tok := p.next()
+ var terminator string
+ switch tok.value {
+ case "<":
+ terminator = ">"
+ case "{":
+ terminator = "}"
+ 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
+ }
+
+ dst.SetMapIndex(key, val)
+ continue
+ }
+
+ // Check that it's not already set if it's not a repeated field.
+ if !props.Repeated && fieldSet[name] {
+ return p.errorf("non-repeated field %q was repeated", name)
+ }
+
+ if err := p.checkForColon(props, dst.Type()); err != nil {
+ return err
+ }
+
+ // Parse into the field.
+ fieldSet[name] = true
+ if err := p.readAny(dst, props); err != nil {
+ if _, ok := err.(*RequiredNotSetError); !ok {
+ return err
+ }
+ reqFieldErr = err
+ } else if props.Required {
+ reqCount--
}
if err := p.consumeOptionalSeparator(); err != nil {