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.
12 "github.com/google/go-cmp/cmp"
15 // filterField returns a new Option where opt is only evaluated on paths that
16 // include a specific exported field on a single struct type.
17 // The struct type is specified by passing in a value of that type.
19 // The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
20 // specific sub-field that is embedded or nested within the parent struct.
21 func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
22 // TODO: This is currently unexported over concerns of how helper filters
23 // can be composed together easily.
24 // TODO: Add tests for FilterField.
26 sf := newStructFilter(typ, name)
27 return cmp.FilterPath(sf.filter, opt)
30 type structFilter struct {
31 t reflect.Type // The root struct type to match on
32 ft fieldTree // Tree of fields to match on
35 func newStructFilter(typ interface{}, names ...string) structFilter {
36 // TODO: Perhaps allow * as a special identifier to allow ignoring any
37 // number of path steps until the next field match?
38 // This could be useful when a concrete struct gets transformed into
39 // an anonymous struct where it is not possible to specify that by type,
40 // but the transformer happens to provide guarantees about the names of
41 // the transformed fields.
43 t := reflect.TypeOf(typ)
44 if t == nil || t.Kind() != reflect.Struct {
45 panic(fmt.Sprintf("%T must be a non-pointer struct", typ))
48 for _, name := range names {
49 cname, err := canonicalName(t, name)
51 panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
55 return structFilter{t, ft}
58 func (sf structFilter) filter(p cmp.Path) bool {
59 for i, ps := range p {
60 if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
67 // fieldTree represents a set of dot-separated identifiers.
69 // For example, inserting the following selectors:
75 // Results in a tree of the form:
77 // "Foo": {ok: true, sub: {
81 // "Buzz": {ok: true},
85 // "Quantum": {ok: true},
89 type fieldTree struct {
90 ok bool // Whether this is a specified node
91 sub map[string]fieldTree // The sub-tree of fields under this node
94 // insert inserts a sequence of field accesses into the tree.
95 func (ft *fieldTree) insert(cname []string) {
97 ft.sub = make(map[string]fieldTree)
103 sub := ft.sub[cname[0]]
104 sub.insert(cname[1:])
105 ft.sub[cname[0]] = sub
108 // matchPrefix reports whether any selector in the fieldTree matches
109 // the start of path p.
110 func (ft fieldTree) matchPrefix(p cmp.Path) bool {
111 for _, ps := range p {
112 switch ps := ps.(type) {
113 case cmp.StructField:
114 ft = ft.sub[ps.Name()]
118 if len(ft.sub) == 0 {
129 // canonicalName returns a list of identifiers where any struct field access
130 // through an embedded field is expanded to include the names of the embedded
133 // For example, suppose field "Foo" is not directly in the parent struct,
134 // but actually from an embedded struct of type "Bar". Then, the canonical name
135 // of "Foo" is actually "Bar.Foo".
137 // Suppose field "Foo" is not directly in the parent struct, but actually
138 // a field in two different embedded structs of types "Bar" and "Baz".
139 // Then the selector "Foo" causes a panic since it is ambiguous which one it
140 // refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
141 func canonicalName(t reflect.Type, sel string) ([]string, error) {
143 sel = strings.TrimPrefix(sel, ".")
145 return nil, fmt.Errorf("name must not be empty")
147 if i := strings.IndexByte(sel, '.'); i < 0 {
150 name, sel = sel[:i], sel[i:]
153 // Type must be a struct or pointer to struct.
154 if t.Kind() == reflect.Ptr {
157 if t.Kind() != reflect.Struct {
158 return nil, fmt.Errorf("%v must be a struct", t)
161 // Find the canonical name for this current field name.
162 // If the field exists in an embedded struct, then it will be expanded.
163 sf, _ := t.FieldByName(name)
164 if !isExported(name) {
165 // Avoid using reflect.Type.FieldByName for unexported fields due to
166 // buggy behavior with regard to embeddeding and unexported fields.
167 // See https://golang.org/issue/4876 for details.
168 sf = reflect.StructField{}
169 for i := 0; i < t.NumField() && sf.Name == ""; i++ {
170 if t.Field(i).Name == name {
176 return []string{name}, fmt.Errorf("does not exist")
179 for i := range sf.Index {
180 ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
185 ssPost, err := canonicalName(sf.Type, sel)
186 return append(ss, ssPost...), err