You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

183 lines
5.0 KiB

  1. // Copyright 2017, The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE.md file.
  4. package cmpopts
  5. import (
  6. "fmt"
  7. "reflect"
  8. "strings"
  9. "github.com/google/go-cmp/cmp"
  10. )
  11. // filterField returns a new Option where opt is only evaluated on paths that
  12. // include a specific exported field on a single struct type.
  13. // The struct type is specified by passing in a value of that type.
  14. //
  15. // The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
  16. // specific sub-field that is embedded or nested within the parent struct.
  17. func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
  18. // TODO: This is currently unexported over concerns of how helper filters
  19. // can be composed together easily.
  20. // TODO: Add tests for FilterField.
  21. sf := newStructFilter(typ, name)
  22. return cmp.FilterPath(sf.filter, opt)
  23. }
  24. type structFilter struct {
  25. t reflect.Type // The root struct type to match on
  26. ft fieldTree // Tree of fields to match on
  27. }
  28. func newStructFilter(typ interface{}, names ...string) structFilter {
  29. // TODO: Perhaps allow * as a special identifier to allow ignoring any
  30. // number of path steps until the next field match?
  31. // This could be useful when a concrete struct gets transformed into
  32. // an anonymous struct where it is not possible to specify that by type,
  33. // but the transformer happens to provide guarantees about the names of
  34. // the transformed fields.
  35. t := reflect.TypeOf(typ)
  36. if t == nil || t.Kind() != reflect.Struct {
  37. panic(fmt.Sprintf("%T must be a struct", typ))
  38. }
  39. var ft fieldTree
  40. for _, name := range names {
  41. cname, err := canonicalName(t, name)
  42. if err != nil {
  43. panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
  44. }
  45. ft.insert(cname)
  46. }
  47. return structFilter{t, ft}
  48. }
  49. func (sf structFilter) filter(p cmp.Path) bool {
  50. for i, ps := range p {
  51. if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
  52. return true
  53. }
  54. }
  55. return false
  56. }
  57. // fieldTree represents a set of dot-separated identifiers.
  58. //
  59. // For example, inserting the following selectors:
  60. // Foo
  61. // Foo.Bar.Baz
  62. // Foo.Buzz
  63. // Nuka.Cola.Quantum
  64. //
  65. // Results in a tree of the form:
  66. // {sub: {
  67. // "Foo": {ok: true, sub: {
  68. // "Bar": {sub: {
  69. // "Baz": {ok: true},
  70. // }},
  71. // "Buzz": {ok: true},
  72. // }},
  73. // "Nuka": {sub: {
  74. // "Cola": {sub: {
  75. // "Quantum": {ok: true},
  76. // }},
  77. // }},
  78. // }}
  79. type fieldTree struct {
  80. ok bool // Whether this is a specified node
  81. sub map[string]fieldTree // The sub-tree of fields under this node
  82. }
  83. // insert inserts a sequence of field accesses into the tree.
  84. func (ft *fieldTree) insert(cname []string) {
  85. if ft.sub == nil {
  86. ft.sub = make(map[string]fieldTree)
  87. }
  88. if len(cname) == 0 {
  89. ft.ok = true
  90. return
  91. }
  92. sub := ft.sub[cname[0]]
  93. sub.insert(cname[1:])
  94. ft.sub[cname[0]] = sub
  95. }
  96. // matchPrefix reports whether any selector in the fieldTree matches
  97. // the start of path p.
  98. func (ft fieldTree) matchPrefix(p cmp.Path) bool {
  99. for _, ps := range p {
  100. switch ps := ps.(type) {
  101. case cmp.StructField:
  102. ft = ft.sub[ps.Name()]
  103. if ft.ok {
  104. return true
  105. }
  106. if len(ft.sub) == 0 {
  107. return false
  108. }
  109. case cmp.Indirect:
  110. default:
  111. return false
  112. }
  113. }
  114. return false
  115. }
  116. // canonicalName returns a list of identifiers where any struct field access
  117. // through an embedded field is expanded to include the names of the embedded
  118. // types themselves.
  119. //
  120. // For example, suppose field "Foo" is not directly in the parent struct,
  121. // but actually from an embedded struct of type "Bar". Then, the canonical name
  122. // of "Foo" is actually "Bar.Foo".
  123. //
  124. // Suppose field "Foo" is not directly in the parent struct, but actually
  125. // a field in two different embedded structs of types "Bar" and "Baz".
  126. // Then the selector "Foo" causes a panic since it is ambiguous which one it
  127. // refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
  128. func canonicalName(t reflect.Type, sel string) ([]string, error) {
  129. var name string
  130. sel = strings.TrimPrefix(sel, ".")
  131. if sel == "" {
  132. return nil, fmt.Errorf("name must not be empty")
  133. }
  134. if i := strings.IndexByte(sel, '.'); i < 0 {
  135. name, sel = sel, ""
  136. } else {
  137. name, sel = sel[:i], sel[i:]
  138. }
  139. // Type must be a struct or pointer to struct.
  140. if t.Kind() == reflect.Ptr {
  141. t = t.Elem()
  142. }
  143. if t.Kind() != reflect.Struct {
  144. return nil, fmt.Errorf("%v must be a struct", t)
  145. }
  146. // Find the canonical name for this current field name.
  147. // If the field exists in an embedded struct, then it will be expanded.
  148. if !isExported(name) {
  149. // Disallow unexported fields:
  150. // * To discourage people from actually touching unexported fields
  151. // * FieldByName is buggy (https://golang.org/issue/4876)
  152. return []string{name}, fmt.Errorf("name must be exported")
  153. }
  154. sf, ok := t.FieldByName(name)
  155. if !ok {
  156. return []string{name}, fmt.Errorf("does not exist")
  157. }
  158. var ss []string
  159. for i := range sf.Index {
  160. ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
  161. }
  162. if sel == "" {
  163. return ss, nil
  164. }
  165. ssPost, err := canonicalName(sf.Type, sel)
  166. return append(ss, ssPost...), err
  167. }