| // 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" |
| "math" |
| "reflect" |
| "strings" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| |
| "google.golang.org/protobuf/proto" |
| "google.golang.org/protobuf/reflect/protoreflect" |
| ) |
| |
| var ( |
| enumReflectType = reflect.TypeOf(Enum{}) |
| messageReflectType = reflect.TypeOf(Message{}) |
| ) |
| |
| // FilterEnum filters opt to only be applicable on standalone Enums, |
| // singular fields of enums, list fields of enums, or map fields of enum values, |
| // where the enum is the same type as the specified enum. |
| // |
| // The Go type of the last path step may be an: |
| // • Enum for singular fields, elements of a repeated field, |
| // values of a map field, or standalone Enums |
| // • []Enum for list fields |
| // • map[K]Enum for map fields |
| // • interface{} for a Message map entry value |
| // |
| // This must be used in conjunction with Transform. |
| func FilterEnum(enum protoreflect.Enum, opt cmp.Option) cmp.Option { |
| return FilterDescriptor(enum.Descriptor(), opt) |
| } |
| |
| // FilterMessage filters opt to only be applicable on standalone Messages, |
| // singular fields of messages, list fields of messages, or map fields of |
| // message values, where the message is the same type as the specified message. |
| // |
| // The Go type of the last path step may be an: |
| // • Message for singular fields, elements of a repeated field, |
| // values of a map field, or standalone Messages |
| // • []Message for list fields |
| // • map[K]Message for map fields |
| // • interface{} for a Message map entry value |
| // |
| // This must be used in conjunction with Transform. |
| func FilterMessage(message proto.Message, opt cmp.Option) cmp.Option { |
| return FilterDescriptor(message.ProtoReflect().Descriptor(), opt) |
| } |
| |
| // FilterField filters opt to only be applicable on the specified field |
| // in the message. It panics if a field of the given name does not exist. |
| // |
| // The Go type of the last path step may be an: |
| // • T for singular fields |
| // • []T for list fields |
| // • map[K]T for map fields |
| // • interface{} for a Message map entry value |
| // |
| // This must be used in conjunction with Transform. |
| func FilterField(message proto.Message, name protoreflect.Name, opt cmp.Option) cmp.Option { |
| md := message.ProtoReflect().Descriptor() |
| return FilterDescriptor(mustFindFieldDescriptor(md, name), opt) |
| } |
| |
| // FilterOneof filters opt to only be applicable on all fields within the |
| // specified oneof in the message. It panics if a oneof of the given name |
| // does not exist. |
| // |
| // The Go type of the last path step may be an: |
| // • T for singular fields |
| // • []T for list fields |
| // • map[K]T for map fields |
| // • interface{} for a Message map entry value |
| // |
| // This must be used in conjunction with Transform. |
| func FilterOneof(message proto.Message, name protoreflect.Name, opt cmp.Option) cmp.Option { |
| md := message.ProtoReflect().Descriptor() |
| return FilterDescriptor(mustFindOneofDescriptor(md, name), opt) |
| } |
| |
| // FilterDescriptor ignores the specified descriptor. |
| // |
| // The following descriptor types may be specified: |
| // • protoreflect.EnumDescriptor |
| // • protoreflect.MessageDescriptor |
| // • protoreflect.FieldDescriptor |
| // • protoreflect.OneofDescriptor |
| // |
| // For the behavior of each, see the corresponding filter function. |
| // Since this filter accepts a protoreflect.FieldDescriptor, it can be used |
| // to also filter for extension fields as a protoreflect.ExtensionDescriptor |
| // is just an alias to protoreflect.FieldDescriptor. |
| // |
| // This must be used in conjunction with Transform. |
| func FilterDescriptor(desc protoreflect.Descriptor, opt cmp.Option) cmp.Option { |
| f := newNameFilters(desc) |
| return cmp.FilterPath(f.Filter, opt) |
| } |
| |
| // IgnoreEnums ignores all enums of the specified types. |
| // It is equivalent to FilterEnum(enum, cmp.Ignore()) for each enum. |
| // |
| // This must be used in conjunction with Transform. |
| func IgnoreEnums(enums ...protoreflect.Enum) cmp.Option { |
| var ds []protoreflect.Descriptor |
| for _, e := range enums { |
| ds = append(ds, e.Descriptor()) |
| } |
| return IgnoreDescriptors(ds...) |
| } |
| |
| // IgnoreMessages ignores all messages of the specified types. |
| // It is equivalent to FilterMessage(message, cmp.Ignore()) for each message. |
| // |
| // This must be used in conjunction with Transform. |
| func IgnoreMessages(messages ...proto.Message) cmp.Option { |
| var ds []protoreflect.Descriptor |
| for _, m := range messages { |
| ds = append(ds, m.ProtoReflect().Descriptor()) |
| } |
| return IgnoreDescriptors(ds...) |
| } |
| |
| // IgnoreFields ignores the specified fields in the specified message. |
| // It is equivalent to FilterField(message, name, cmp.Ignore()) for each field |
| // in the message. |
| // |
| // This must be used in conjunction with Transform. |
| func IgnoreFields(message proto.Message, names ...protoreflect.Name) cmp.Option { |
| var ds []protoreflect.Descriptor |
| md := message.ProtoReflect().Descriptor() |
| for _, s := range names { |
| ds = append(ds, mustFindFieldDescriptor(md, s)) |
| } |
| return IgnoreDescriptors(ds...) |
| } |
| |
| // IgnoreOneofs ignores fields of the specified oneofs in the specified message. |
| // It is equivalent to FilterOneof(message, name, cmp.Ignore()) for each oneof |
| // in the message. |
| // |
| // This must be used in conjunction with Transform. |
| func IgnoreOneofs(message proto.Message, names ...protoreflect.Name) cmp.Option { |
| var ds []protoreflect.Descriptor |
| md := message.ProtoReflect().Descriptor() |
| for _, s := range names { |
| ds = append(ds, mustFindOneofDescriptor(md, s)) |
| } |
| return IgnoreDescriptors(ds...) |
| } |
| |
| // IgnoreDescriptors ignores the specified set of descriptors. |
| // It is equivalent to FilterDescriptor(desc, cmp.Ignore()) for each descriptor. |
| // |
| // This must be used in conjunction with Transform. |
| func IgnoreDescriptors(descs ...protoreflect.Descriptor) cmp.Option { |
| return cmp.FilterPath(newNameFilters(descs...).Filter, cmp.Ignore()) |
| } |
| |
| func mustFindFieldDescriptor(md protoreflect.MessageDescriptor, s protoreflect.Name) protoreflect.FieldDescriptor { |
| d := findDescriptor(md, s) |
| if fd, ok := d.(protoreflect.FieldDescriptor); ok && fd.TextName() == string(s) { |
| return fd |
| } |
| |
| var suggestion string |
| switch d := d.(type) { |
| case protoreflect.FieldDescriptor: |
| suggestion = fmt.Sprintf("; consider specifying field %q instead", d.TextName()) |
| case protoreflect.OneofDescriptor: |
| suggestion = fmt.Sprintf("; consider specifying oneof %q with IgnoreOneofs instead", d.Name()) |
| } |
| panic(fmt.Sprintf("message %q has no field %q%s", md.FullName(), s, suggestion)) |
| } |
| |
| func mustFindOneofDescriptor(md protoreflect.MessageDescriptor, s protoreflect.Name) protoreflect.OneofDescriptor { |
| d := findDescriptor(md, s) |
| if od, ok := d.(protoreflect.OneofDescriptor); ok && d.Name() == s { |
| return od |
| } |
| |
| var suggestion string |
| switch d := d.(type) { |
| case protoreflect.OneofDescriptor: |
| suggestion = fmt.Sprintf("; consider specifying oneof %q instead", d.Name()) |
| case protoreflect.FieldDescriptor: |
| suggestion = fmt.Sprintf("; consider specifying field %q with IgnoreFields instead", d.TextName()) |
| } |
| panic(fmt.Sprintf("message %q has no oneof %q%s", md.FullName(), s, suggestion)) |
| } |
| |
| func findDescriptor(md protoreflect.MessageDescriptor, s protoreflect.Name) protoreflect.Descriptor { |
| // Exact match. |
| if fd := md.Fields().ByTextName(string(s)); fd != nil { |
| return fd |
| } |
| if od := md.Oneofs().ByName(s); od != nil && !od.IsSynthetic() { |
| return od |
| } |
| |
| // Best-effort match. |
| // |
| // It's a common user mistake to use the CamelCased field name as it appears |
| // in the generated Go struct. Instead of complaining that it doesn't exist, |
| // suggest the real protobuf name that the user may have desired. |
| normalize := func(s protoreflect.Name) string { |
| return strings.Replace(strings.ToLower(string(s)), "_", "", -1) |
| } |
| for i := 0; i < md.Fields().Len(); i++ { |
| if fd := md.Fields().Get(i); normalize(fd.Name()) == normalize(s) { |
| return fd |
| } |
| } |
| for i := 0; i < md.Oneofs().Len(); i++ { |
| if od := md.Oneofs().Get(i); normalize(od.Name()) == normalize(s) { |
| return od |
| } |
| } |
| return nil |
| } |
| |
| type nameFilters struct { |
| names map[protoreflect.FullName]bool |
| } |
| |
| func newNameFilters(descs ...protoreflect.Descriptor) *nameFilters { |
| f := &nameFilters{names: make(map[protoreflect.FullName]bool)} |
| for _, d := range descs { |
| switch d := d.(type) { |
| case protoreflect.EnumDescriptor: |
| f.names[d.FullName()] = true |
| case protoreflect.MessageDescriptor: |
| f.names[d.FullName()] = true |
| case protoreflect.FieldDescriptor: |
| f.names[d.FullName()] = true |
| case protoreflect.OneofDescriptor: |
| for i := 0; i < d.Fields().Len(); i++ { |
| f.names[d.Fields().Get(i).FullName()] = true |
| } |
| default: |
| panic("invalid descriptor type") |
| } |
| } |
| return f |
| } |
| |
| func (f *nameFilters) Filter(p cmp.Path) bool { |
| vx, vy := p.Last().Values() |
| return (f.filterValue(vx) && f.filterValue(vy)) || f.filterFields(p) |
| } |
| |
| func (f *nameFilters) filterFields(p cmp.Path) bool { |
| // Trim off trailing type-assertions so that the filter can match on the |
| // concrete value held within an interface value. |
| if _, ok := p.Last().(cmp.TypeAssertion); ok { |
| p = p[:len(p)-1] |
| } |
| |
| // Filter for Message maps. |
| mi, ok := p.Index(-1).(cmp.MapIndex) |
| if !ok { |
| return false |
| } |
| ps := p.Index(-2) |
| if ps.Type() != messageReflectType { |
| return false |
| } |
| |
| // Check field name. |
| vx, vy := ps.Values() |
| mx := vx.Interface().(Message) |
| my := vy.Interface().(Message) |
| k := mi.Key().String() |
| if f.filterFieldName(mx, k) && f.filterFieldName(my, k) { |
| return true |
| } |
| |
| // Check field value. |
| vx, vy = mi.Values() |
| if f.filterFieldValue(vx) && f.filterFieldValue(vy) { |
| return true |
| } |
| |
| return false |
| } |
| |
| func (f *nameFilters) filterFieldName(m Message, k string) bool { |
| if _, ok := m[k]; !ok { |
| return true // treat missing fields as already filtered |
| } |
| var fd protoreflect.FieldDescriptor |
| switch mm := m[messageTypeKey].(messageMeta); { |
| case protoreflect.Name(k).IsValid(): |
| fd = mm.md.Fields().ByTextName(k) |
| default: |
| fd = mm.xds[k] |
| } |
| if fd != nil { |
| return f.names[fd.FullName()] |
| } |
| return false |
| } |
| |
| func (f *nameFilters) filterFieldValue(v reflect.Value) bool { |
| if !v.IsValid() { |
| return true // implies missing slice element or map entry |
| } |
| v = v.Elem() // map entries are always populated values |
| switch t := v.Type(); { |
| case t == enumReflectType || t == messageReflectType: |
| // Check for singular message or enum field. |
| return f.filterValue(v) |
| case t.Kind() == reflect.Slice && (t.Elem() == enumReflectType || t.Elem() == messageReflectType): |
| // Check for list field of enum or message type. |
| return f.filterValue(v.Index(0)) |
| case t.Kind() == reflect.Map && (t.Elem() == enumReflectType || t.Elem() == messageReflectType): |
| // Check for map field of enum or message type. |
| return f.filterValue(v.MapIndex(v.MapKeys()[0])) |
| } |
| return false |
| } |
| |
| func (f *nameFilters) filterValue(v reflect.Value) bool { |
| if !v.IsValid() { |
| return true // implies missing slice element or map entry |
| } |
| if !v.CanInterface() { |
| return false // implies unexported struct field |
| } |
| switch v := v.Interface().(type) { |
| case Enum: |
| return v.Descriptor() != nil && f.names[v.Descriptor().FullName()] |
| case Message: |
| return v.Descriptor() != nil && f.names[v.Descriptor().FullName()] |
| } |
| return false |
| } |
| |
| // IgnoreDefaultScalars ignores singular scalars that are unpopulated or |
| // explicitly set to the default value. |
| // This option does not effect elements in a list or entries in a map. |
| // |
| // This must be used in conjunction with Transform. |
| func IgnoreDefaultScalars() cmp.Option { |
| return cmp.FilterPath(func(p cmp.Path) bool { |
| // Filter for Message maps. |
| mi, ok := p.Index(-1).(cmp.MapIndex) |
| if !ok { |
| return false |
| } |
| ps := p.Index(-2) |
| if ps.Type() != messageReflectType { |
| return false |
| } |
| |
| // Check whether both fields are default or unpopulated scalars. |
| vx, vy := ps.Values() |
| mx := vx.Interface().(Message) |
| my := vy.Interface().(Message) |
| k := mi.Key().String() |
| return isDefaultScalar(mx, k) && isDefaultScalar(my, k) |
| }, cmp.Ignore()) |
| } |
| |
| func isDefaultScalar(m Message, k string) bool { |
| if _, ok := m[k]; !ok { |
| return true |
| } |
| |
| var fd protoreflect.FieldDescriptor |
| switch mm := m[messageTypeKey].(messageMeta); { |
| case protoreflect.Name(k).IsValid(): |
| fd = mm.md.Fields().ByTextName(k) |
| default: |
| fd = mm.xds[k] |
| } |
| if fd == nil || !fd.Default().IsValid() { |
| return false |
| } |
| switch fd.Kind() { |
| case protoreflect.BytesKind: |
| v, ok := m[k].([]byte) |
| return ok && bytes.Equal(fd.Default().Bytes(), v) |
| case protoreflect.FloatKind: |
| v, ok := m[k].(float32) |
| return ok && equalFloat64(fd.Default().Float(), float64(v)) |
| case protoreflect.DoubleKind: |
| v, ok := m[k].(float64) |
| return ok && equalFloat64(fd.Default().Float(), float64(v)) |
| case protoreflect.EnumKind: |
| v, ok := m[k].(Enum) |
| return ok && fd.Default().Enum() == v.Number() |
| default: |
| return reflect.DeepEqual(fd.Default().Interface(), m[k]) |
| } |
| } |
| |
| func equalFloat64(x, y float64) bool { |
| return x == y || (math.IsNaN(x) && math.IsNaN(y)) |
| } |
| |
| // IgnoreEmptyMessages ignores messages that are empty or unpopulated. |
| // It applies to standalone Messages, singular message fields, |
| // list fields of messages, and map fields of message values. |
| // |
| // This must be used in conjunction with Transform. |
| func IgnoreEmptyMessages() cmp.Option { |
| return cmp.FilterPath(func(p cmp.Path) bool { |
| vx, vy := p.Last().Values() |
| return (isEmptyMessage(vx) && isEmptyMessage(vy)) || isEmptyMessageFields(p) |
| }, cmp.Ignore()) |
| } |
| |
| func isEmptyMessageFields(p cmp.Path) bool { |
| // Filter for Message maps. |
| mi, ok := p.Index(-1).(cmp.MapIndex) |
| if !ok { |
| return false |
| } |
| ps := p.Index(-2) |
| if ps.Type() != messageReflectType { |
| return false |
| } |
| |
| // Check field value. |
| vx, vy := mi.Values() |
| if isEmptyMessageFieldValue(vx) && isEmptyMessageFieldValue(vy) { |
| return true |
| } |
| |
| return false |
| } |
| |
| func isEmptyMessageFieldValue(v reflect.Value) bool { |
| if !v.IsValid() { |
| return true // implies missing slice element or map entry |
| } |
| v = v.Elem() // map entries are always populated values |
| switch t := v.Type(); { |
| case t == messageReflectType: |
| // Check singular field for empty message. |
| if !isEmptyMessage(v) { |
| return false |
| } |
| case t.Kind() == reflect.Slice && t.Elem() == messageReflectType: |
| // Check list field for all empty message elements. |
| for i := 0; i < v.Len(); i++ { |
| if !isEmptyMessage(v.Index(i)) { |
| return false |
| } |
| } |
| case t.Kind() == reflect.Map && t.Elem() == messageReflectType: |
| // Check map field for all empty message values. |
| for _, k := range v.MapKeys() { |
| if !isEmptyMessage(v.MapIndex(k)) { |
| return false |
| } |
| } |
| default: |
| return false |
| } |
| return true |
| } |
| |
| func isEmptyMessage(v reflect.Value) bool { |
| if !v.IsValid() { |
| return true // implies missing slice element or map entry |
| } |
| if !v.CanInterface() { |
| return false // implies unexported struct field |
| } |
| if m, ok := v.Interface().(Message); ok { |
| for k := range m { |
| if k != messageTypeKey && k != messageInvalidKey { |
| return false |
| } |
| } |
| return true |
| } |
| return false |
| } |
| |
| // IgnoreUnknown ignores unknown fields in all messages. |
| // |
| // This must be used in conjunction with Transform. |
| func IgnoreUnknown() cmp.Option { |
| return cmp.FilterPath(func(p cmp.Path) bool { |
| // Filter for Message maps. |
| mi, ok := p.Index(-1).(cmp.MapIndex) |
| if !ok { |
| return false |
| } |
| ps := p.Index(-2) |
| if ps.Type() != messageReflectType { |
| return false |
| } |
| |
| // Filter for unknown fields (which always have a numeric map key). |
| return strings.Trim(mi.Key().String(), "0123456789") == "" |
| }, cmp.Ignore()) |
| } |
| |
| // SortRepeated sorts repeated fields of the specified element type. |
| // The less function must be of the form "func(T, T) bool" where T is the |
| // Go element type for the repeated field kind. |
| // |
| // The element type T can be one of the following: |
| // • Go type for a protobuf scalar kind except for an enum |
| // (i.e., bool, int32, int64, uint32, uint64, float32, float64, string, and []byte) |
| // • E where E is a concrete enum type that implements protoreflect.Enum |
| // • M where M is a concrete message type that implement proto.Message |
| // |
| // This option only applies to repeated fields within a protobuf message. |
| // It does not operate on higher-order Go types that seem like a repeated field. |
| // For example, a []T outside the context of a protobuf message will not be |
| // handled by this option. To sort Go slices that are not repeated fields, |
| // consider using "github.com/google/go-cmp/cmp/cmpopts".SortSlices instead. |
| // |
| // This must be used in conjunction with Transform. |
| func SortRepeated(lessFunc interface{}) cmp.Option { |
| t, ok := checkTTBFunc(lessFunc) |
| if !ok { |
| panic(fmt.Sprintf("invalid less function: %T", lessFunc)) |
| } |
| |
| var opt cmp.Option |
| var sliceType reflect.Type |
| switch vf := reflect.ValueOf(lessFunc); { |
| case t.Implements(enumV2Type): |
| et := reflect.Zero(t).Interface().(protoreflect.Enum).Type() |
| lessFunc = func(x, y Enum) bool { |
| vx := reflect.ValueOf(et.New(x.Number())) |
| vy := reflect.ValueOf(et.New(y.Number())) |
| return vf.Call([]reflect.Value{vx, vy})[0].Bool() |
| } |
| opt = FilterDescriptor(et.Descriptor(), cmpopts.SortSlices(lessFunc)) |
| sliceType = reflect.SliceOf(enumReflectType) |
| case t.Implements(messageV2Type): |
| mt := reflect.Zero(t).Interface().(protoreflect.ProtoMessage).ProtoReflect().Type() |
| lessFunc = func(x, y Message) bool { |
| mx := mt.New().Interface() |
| my := mt.New().Interface() |
| proto.Merge(mx, x) |
| proto.Merge(my, y) |
| vx := reflect.ValueOf(mx) |
| vy := reflect.ValueOf(my) |
| return vf.Call([]reflect.Value{vx, vy})[0].Bool() |
| } |
| opt = FilterDescriptor(mt.Descriptor(), cmpopts.SortSlices(lessFunc)) |
| sliceType = reflect.SliceOf(messageReflectType) |
| default: |
| switch t { |
| case reflect.TypeOf(bool(false)): |
| case reflect.TypeOf(int32(0)): |
| case reflect.TypeOf(int64(0)): |
| case reflect.TypeOf(uint32(0)): |
| case reflect.TypeOf(uint64(0)): |
| case reflect.TypeOf(float32(0)): |
| case reflect.TypeOf(float64(0)): |
| case reflect.TypeOf(string("")): |
| case reflect.TypeOf([]byte(nil)): |
| default: |
| panic(fmt.Sprintf("invalid element type: %v", t)) |
| } |
| opt = cmpopts.SortSlices(lessFunc) |
| sliceType = reflect.SliceOf(t) |
| } |
| |
| return cmp.FilterPath(func(p cmp.Path) bool { |
| // Filter to only apply to repeated fields within a message. |
| if t := p.Index(-1).Type(); t == nil || t != sliceType { |
| return false |
| } |
| if t := p.Index(-2).Type(); t == nil || t.Kind() != reflect.Interface { |
| return false |
| } |
| if t := p.Index(-3).Type(); t == nil || t != messageReflectType { |
| return false |
| } |
| return true |
| }, opt) |
| } |
| |
| func checkTTBFunc(lessFunc interface{}) (reflect.Type, bool) { |
| switch t := reflect.TypeOf(lessFunc); { |
| case t == nil: |
| return nil, false |
| case t.NumIn() != 2 || t.In(0) != t.In(1) || t.IsVariadic(): |
| return nil, false |
| case t.NumOut() != 1 || t.Out(0) != reflect.TypeOf(false): |
| return nil, false |
| default: |
| return t.In(0), true |
| } |
| } |
| |
| // SortRepeatedFields sorts the specified repeated fields. |
| // Sorting a repeated field is useful for treating the list as a multiset |
| // (i.e., a set where each value can appear multiple times). |
| // It panics if the field does not exist or is not a repeated field. |
| // |
| // The sort ordering is as follows: |
| // • Booleans are sorted where false is sorted before true. |
| // • Integers are sorted in ascending order. |
| // • Floating-point numbers are sorted in ascending order according to |
| // the total ordering defined by IEEE-754 (section 5.10). |
| // • Strings and bytes are sorted lexicographically in ascending order. |
| // • Enums are sorted in ascending order based on its numeric value. |
| // • Messages are sorted according to some arbitrary ordering |
| // which is undefined and may change in future implementations. |
| // |
| // The ordering chosen for repeated messages is unlikely to be aesthetically |
| // preferred by humans. Consider using a custom sort function: |
| // |
| // FilterField(m, "foo_field", SortRepeated(func(x, y *foopb.MyMessage) bool { |
| // ... // user-provided definition for less |
| // })) |
| // |
| // This must be used in conjunction with Transform. |
| func SortRepeatedFields(message proto.Message, names ...protoreflect.Name) cmp.Option { |
| var opts cmp.Options |
| md := message.ProtoReflect().Descriptor() |
| for _, name := range names { |
| fd := mustFindFieldDescriptor(md, name) |
| if !fd.IsList() { |
| panic(fmt.Sprintf("message field %q is not repeated", fd.FullName())) |
| } |
| |
| var lessFunc interface{} |
| switch fd.Kind() { |
| case protoreflect.BoolKind: |
| lessFunc = func(x, y bool) bool { return !x && y } |
| case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: |
| lessFunc = func(x, y int32) bool { return x < y } |
| case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: |
| lessFunc = func(x, y int64) bool { return x < y } |
| case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: |
| lessFunc = func(x, y uint32) bool { return x < y } |
| case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: |
| lessFunc = func(x, y uint64) bool { return x < y } |
| case protoreflect.FloatKind: |
| lessFunc = lessF32 |
| case protoreflect.DoubleKind: |
| lessFunc = lessF64 |
| case protoreflect.StringKind: |
| lessFunc = func(x, y string) bool { return x < y } |
| case protoreflect.BytesKind: |
| lessFunc = func(x, y []byte) bool { return bytes.Compare(x, y) < 0 } |
| case protoreflect.EnumKind: |
| lessFunc = func(x, y Enum) bool { return x.Number() < y.Number() } |
| case protoreflect.MessageKind, protoreflect.GroupKind: |
| lessFunc = func(x, y Message) bool { return x.String() < y.String() } |
| default: |
| panic(fmt.Sprintf("invalid kind: %v", fd.Kind())) |
| } |
| opts = append(opts, FilterDescriptor(fd, cmpopts.SortSlices(lessFunc))) |
| } |
| return opts |
| } |
| |
| func lessF32(x, y float32) bool { |
| // Bit-wise implementation of IEEE-754, section 5.10. |
| xi := int32(math.Float32bits(x)) |
| yi := int32(math.Float32bits(y)) |
| xi ^= int32(uint32(xi>>31) >> 1) |
| yi ^= int32(uint32(yi>>31) >> 1) |
| return xi < yi |
| } |
| func lessF64(x, y float64) bool { |
| // Bit-wise implementation of IEEE-754, section 5.10. |
| xi := int64(math.Float64bits(x)) |
| yi := int64(math.Float64bits(y)) |
| xi ^= int64(uint64(xi>>63) >> 1) |
| yi ^= int64(uint64(yi>>63) >> 1) |
| return xi < yi |
| } |