blob: 8790d5f17bad6b0cb069afafd650ac006b5b8788 [file] [log] [blame]
// 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)
}