mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-01 03:14:16 +00:00
c7aa53a3e0
Use protobuild to generate test messages, both to simplify generating proto2/proto3/extension variants of each test where appropriate and to make it easier to test alternative message generators in the future. Add various additional merge tests. Change-Id: I4ba3ce232304e1d8325543680e2b6aae61de7364 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/219146 Reviewed-by: Joe Tsai <joetsai@google.com>
152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
// Copyright 2020 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 protobuild constructs messages.
|
|
//
|
|
// This package is used to construct multiple types of message with a similar shape
|
|
// from a common template.
|
|
package protobuild
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
|
|
pref "google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/reflect/protoregistry"
|
|
)
|
|
|
|
// A Value is a value assignable to a field.
|
|
// A Value may be a value accepted by protoreflect.ValueOf. In addition:
|
|
//
|
|
// • An int may be assigned to any numeric field.
|
|
//
|
|
// • A float64 may be assigned to a double field.
|
|
//
|
|
// • Either a string or []byte may be assigned to a string or bytes field.
|
|
//
|
|
// • A string containing the value name may be assigned to an enum field.
|
|
//
|
|
// • A slice may be assigned to a list, and a map may be assigned to a map.
|
|
type Value interface{}
|
|
|
|
// A Message is a template to apply to a message. Keys are field names, including
|
|
// extension names.
|
|
type Message map[pref.Name]Value
|
|
|
|
// Unknown is a key associated with the unknown fields of a message.
|
|
// The value should be a []byte.
|
|
const Unknown = "@unknown"
|
|
|
|
// Build applies the template to a message.
|
|
func (template Message) Build(m pref.Message) {
|
|
md := m.Descriptor()
|
|
fields := md.Fields()
|
|
exts := make(map[pref.Name]pref.FieldDescriptor)
|
|
protoregistry.GlobalTypes.RangeExtensionsByMessage(md.FullName(), func(xt pref.ExtensionType) bool {
|
|
xd := xt.TypeDescriptor()
|
|
exts[xd.Name()] = xd
|
|
return true
|
|
})
|
|
for k, v := range template {
|
|
if k == Unknown {
|
|
m.SetUnknown(pref.RawFields(v.([]byte)))
|
|
continue
|
|
}
|
|
fd := fields.ByName(k)
|
|
if fd == nil {
|
|
fd = exts[k]
|
|
}
|
|
if fd == nil {
|
|
panic(fmt.Sprintf("%v.%v: not found", md.FullName(), k))
|
|
}
|
|
switch {
|
|
case fd.IsList():
|
|
list := m.Mutable(fd).List()
|
|
s := reflect.ValueOf(v)
|
|
for i := 0; i < s.Len(); i++ {
|
|
if fd.Message() == nil {
|
|
list.Append(fieldValue(fd, s.Index(i).Interface()))
|
|
} else {
|
|
e := list.NewElement()
|
|
s.Index(i).Interface().(Message).Build(e.Message())
|
|
list.Append(e)
|
|
}
|
|
}
|
|
case fd.IsMap():
|
|
mapv := m.Mutable(fd).Map()
|
|
rm := reflect.ValueOf(v)
|
|
for _, k := range rm.MapKeys() {
|
|
mk := fieldValue(fd.MapKey(), k.Interface()).MapKey()
|
|
if fd.MapValue().Message() == nil {
|
|
mv := fieldValue(fd.MapValue(), rm.MapIndex(k).Interface())
|
|
mapv.Set(mk, mv)
|
|
} else if mapv.Has(mk) {
|
|
mv := mapv.Get(mk).Message()
|
|
rm.MapIndex(k).Interface().(Message).Build(mv)
|
|
} else {
|
|
mv := mapv.NewValue()
|
|
rm.MapIndex(k).Interface().(Message).Build(mv.Message())
|
|
mapv.Set(mk, mv)
|
|
}
|
|
}
|
|
default:
|
|
if fd.Message() == nil {
|
|
m.Set(fd, fieldValue(fd, v))
|
|
} else {
|
|
v.(Message).Build(m.Mutable(fd).Message())
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func fieldValue(fd pref.FieldDescriptor, v interface{}) pref.Value {
|
|
switch o := v.(type) {
|
|
case int:
|
|
switch fd.Kind() {
|
|
case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind:
|
|
if min, max := math.MinInt32, math.MaxInt32; o < min || o > max {
|
|
panic(fmt.Sprintf("%v: value %v out of range [%v, %v]", fd.FullName(), o, min, max))
|
|
}
|
|
v = int32(o)
|
|
case pref.Uint32Kind, pref.Fixed32Kind:
|
|
if min, max := 0, math.MaxUint32; o < min || o > max {
|
|
panic(fmt.Sprintf("%v: value %v out of range [%v, %v]", fd.FullName(), o, min, max))
|
|
}
|
|
v = uint32(o)
|
|
case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind:
|
|
v = int64(o)
|
|
case pref.Uint64Kind, pref.Fixed64Kind:
|
|
if o < 0 {
|
|
panic(fmt.Sprintf("%v: value %v out of range [%v, %v]", fd.FullName(), o, 0, uint64(math.MaxUint64)))
|
|
}
|
|
v = uint64(o)
|
|
case pref.FloatKind:
|
|
v = float32(o)
|
|
case pref.DoubleKind:
|
|
v = float64(o)
|
|
case pref.EnumKind:
|
|
v = pref.EnumNumber(o)
|
|
default:
|
|
panic(fmt.Sprintf("%v: invalid value type int", fd.FullName()))
|
|
}
|
|
case float64:
|
|
switch fd.Kind() {
|
|
case pref.FloatKind:
|
|
v = float32(o)
|
|
}
|
|
case string:
|
|
switch fd.Kind() {
|
|
case pref.BytesKind:
|
|
v = []byte(o)
|
|
case pref.EnumKind:
|
|
v = fd.Enum().Values().ByName(pref.Name(o)).Number()
|
|
}
|
|
case []byte:
|
|
return pref.ValueOf(append([]byte{}, o...))
|
|
}
|
|
return pref.ValueOf(v)
|
|
}
|