mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2024-12-29 12:17:48 +00:00
afb395b163
High-level API: func Transform() cmp.Option type Enum struct{ ... } type Message map[string]interface{} The Transform function transform messages into a Message type that cmp.Equal and cmp.Diff then knows how to traverse and compare. Change-Id: I445f3b5c69f054b6984f28c205cda69e44af3b89 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/164680 Reviewed-by: Damien Neil <dneil@google.com>
188 lines
4.7 KiB
Go
188 lines
4.7 KiB
Go
// Copyright 2019 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 protocmp
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"google.golang.org/protobuf/internal/detrand"
|
|
"google.golang.org/protobuf/internal/encoding/wire"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
)
|
|
|
|
// This implements a custom text marshaler similar to the prototext format.
|
|
// We don't use the prototext marshaler so that we can:
|
|
// • have finer grain control over the ordering of fields
|
|
// • marshal maps with a more aesthetically pleasant output
|
|
//
|
|
// TODO: If the prototext format gains a map-specific syntax, consider just
|
|
// using the prototext marshaler instead.
|
|
|
|
func appendValue(b []byte, v interface{}) []byte {
|
|
switch v := v.(type) {
|
|
case bool, int32, int64, uint32, uint64, float32, float64:
|
|
return append(b, fmt.Sprint(v)...)
|
|
case string:
|
|
return append(b, strconv.Quote(string(v))...)
|
|
case []byte:
|
|
return append(b, strconv.Quote(string(v))...)
|
|
case Enum:
|
|
return append(b, v.String()...)
|
|
case Message:
|
|
return appendMessage(b, v)
|
|
case protoreflect.RawFields:
|
|
return appendValue(b, transformRawFields(v))
|
|
default:
|
|
switch v := reflect.ValueOf(v); v.Kind() {
|
|
case reflect.Slice:
|
|
return appendList(b, v)
|
|
case reflect.Map:
|
|
return appendMap(b, v)
|
|
default:
|
|
panic(fmt.Sprintf("invalid type: %v", v.Type()))
|
|
}
|
|
}
|
|
}
|
|
|
|
func appendMessage(b []byte, m Message) []byte {
|
|
var knownKeys, extensionKeys, unknownKeys []string
|
|
for k := range m {
|
|
switch {
|
|
case protoreflect.Name(k).IsValid():
|
|
knownKeys = append(knownKeys, k)
|
|
case strings.HasPrefix(k, "[") && strings.HasSuffix(k, "]"):
|
|
extensionKeys = append(extensionKeys, k)
|
|
case len(strings.Trim(k, "0123456789")) == 0:
|
|
unknownKeys = append(unknownKeys, k)
|
|
}
|
|
}
|
|
sort.Slice(knownKeys, func(i, j int) bool {
|
|
fdi := m.Descriptor().Fields().ByName(protoreflect.Name(knownKeys[i]))
|
|
fdj := m.Descriptor().Fields().ByName(protoreflect.Name(knownKeys[j]))
|
|
return fdi.Index() < fdj.Index()
|
|
})
|
|
sort.Slice(extensionKeys, func(i, j int) bool {
|
|
return extensionKeys[i] < extensionKeys[j]
|
|
})
|
|
sort.Slice(unknownKeys, func(i, j int) bool {
|
|
ni, _ := strconv.Atoi(unknownKeys[i])
|
|
nj, _ := strconv.Atoi(unknownKeys[j])
|
|
return ni < nj
|
|
})
|
|
ks := append(append(append([]string(nil), knownKeys...), extensionKeys...), unknownKeys...)
|
|
|
|
b = append(b, '{')
|
|
for _, k := range ks {
|
|
b = append(b, k...)
|
|
b = append(b, ':')
|
|
b = appendValue(b, m[k])
|
|
b = append(b, delim()...)
|
|
}
|
|
b = bytes.TrimRight(b, delim())
|
|
b = append(b, '}')
|
|
return b
|
|
}
|
|
|
|
func appendList(b []byte, v reflect.Value) []byte {
|
|
b = append(b, '[')
|
|
for i := 0; i < v.Len(); i++ {
|
|
b = appendValue(b, v.Index(i).Interface())
|
|
b = append(b, delim()...)
|
|
}
|
|
b = bytes.TrimRight(b, delim())
|
|
b = append(b, ']')
|
|
return b
|
|
}
|
|
|
|
func appendMap(b []byte, v reflect.Value) []byte {
|
|
ks := v.MapKeys()
|
|
sort.Slice(ks, func(i, j int) bool {
|
|
ki, kj := ks[i], ks[j]
|
|
switch ki.Kind() {
|
|
case reflect.Bool:
|
|
return !ki.Bool() && kj.Bool()
|
|
case reflect.Int32, reflect.Int64:
|
|
return ki.Int() < kj.Int()
|
|
case reflect.Uint32, reflect.Uint64:
|
|
return ki.Uint() < kj.Uint()
|
|
case reflect.String:
|
|
return ki.String() < kj.String()
|
|
default:
|
|
panic(fmt.Sprintf("invalid kind: %v", ki.Kind()))
|
|
}
|
|
})
|
|
|
|
b = append(b, '{')
|
|
for _, k := range ks {
|
|
b = appendValue(b, k.Interface())
|
|
b = append(b, ':')
|
|
b = appendValue(b, v.MapIndex(k).Interface())
|
|
b = append(b, delim()...)
|
|
}
|
|
b = bytes.TrimRight(b, delim())
|
|
b = append(b, '}')
|
|
return b
|
|
}
|
|
|
|
func transformRawFields(b protoreflect.RawFields) interface{} {
|
|
var vs []interface{}
|
|
for len(b) > 0 {
|
|
num, typ, n := wire.ConsumeTag(b)
|
|
m := wire.ConsumeFieldValue(num, typ, b[n:])
|
|
vs = append(vs, transformRawField(typ, b[n:][:m]))
|
|
b = b[n+m:]
|
|
}
|
|
if len(vs) == 1 {
|
|
return vs[0]
|
|
}
|
|
return vs
|
|
}
|
|
|
|
func transformRawField(typ wire.Type, b protoreflect.RawFields) interface{} {
|
|
switch typ {
|
|
case wire.VarintType:
|
|
v, _ := wire.ConsumeVarint(b)
|
|
return v
|
|
case wire.Fixed32Type:
|
|
v, _ := wire.ConsumeFixed32(b)
|
|
return v
|
|
case wire.Fixed64Type:
|
|
v, _ := wire.ConsumeFixed64(b)
|
|
return v
|
|
case wire.BytesType:
|
|
v, _ := wire.ConsumeBytes(b)
|
|
return v
|
|
case wire.StartGroupType:
|
|
v := Message{}
|
|
for {
|
|
num2, typ2, n := wire.ConsumeTag(b)
|
|
if typ2 == wire.EndGroupType {
|
|
return v
|
|
}
|
|
m := wire.ConsumeFieldValue(num2, typ2, b[n:])
|
|
s := strconv.Itoa(int(num2))
|
|
b2, _ := v[s].(protoreflect.RawFields)
|
|
v[s] = append(b2, b[:n+m]...)
|
|
b = b[n+m:]
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("invalid type: %v", typ))
|
|
}
|
|
}
|
|
|
|
func delim() string {
|
|
// Deliberately introduce instability into the message string to
|
|
// discourage users from depending on it.
|
|
if detrand.Bool() {
|
|
return " "
|
|
}
|
|
return ", "
|
|
}
|