github.com/integration-system/go-cmp@v0.0.0-20190131081942-ac5582987a2f/cmp/cmpopts/ignore.go (about) 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 5 package cmpopts 6 7 import ( 8 "fmt" 9 "reflect" 10 "unicode" 11 "unicode/utf8" 12 13 "github.com/integration-system/go-cmp/cmp" 14 ) 15 16 // IgnoreFields returns an Option that ignores exported fields of the 17 // given names on a single struct type. 18 // The struct type is specified by passing in a value of that type. 19 // 20 // The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a 21 // specific sub-field that is embedded or nested within the parent struct. 22 // 23 // This does not handle unexported fields; use IgnoreUnexported instead. 24 func IgnoreFields(typ interface{}, names ...string) cmp.Option { 25 sf := newStructFilter(typ, names...) 26 return cmp.FilterPath(sf.filter, cmp.Ignore()) 27 } 28 29 // IgnoreTypes returns an Option that ignores all values assignable to 30 // certain types, which are specified by passing in a value of each type. 31 func IgnoreTypes(typs ...interface{}) cmp.Option { 32 tf := newTypeFilter(typs...) 33 return cmp.FilterPath(tf.filter, cmp.Ignore()) 34 } 35 36 type typeFilter []reflect.Type 37 38 func newTypeFilter(typs ...interface{}) (tf typeFilter) { 39 for _, typ := range typs { 40 t := reflect.TypeOf(typ) 41 if t == nil { 42 // This occurs if someone tries to pass in sync.Locker(nil) 43 panic("cannot determine type; consider using IgnoreInterfaces") 44 } 45 tf = append(tf, t) 46 } 47 return tf 48 } 49 func (tf typeFilter) filter(p cmp.Path) bool { 50 if len(p) < 1 { 51 return false 52 } 53 t := p.Last().Type() 54 for _, ti := range tf { 55 if t.AssignableTo(ti) { 56 return true 57 } 58 } 59 return false 60 } 61 62 // IgnoreInterfaces returns an Option that ignores all values or references of 63 // values assignable to certain interface types. These interfaces are specified 64 // by passing in an anonymous struct with the interface types embedded in it. 65 // For example, to ignore sync.Locker, pass in struct{sync.Locker}{}. 66 func IgnoreInterfaces(ifaces interface{}) cmp.Option { 67 tf := newIfaceFilter(ifaces) 68 return cmp.FilterPath(tf.filter, cmp.Ignore()) 69 } 70 71 type ifaceFilter []reflect.Type 72 73 func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) { 74 t := reflect.TypeOf(ifaces) 75 if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct { 76 panic("input must be an anonymous struct") 77 } 78 for i := 0; i < t.NumField(); i++ { 79 fi := t.Field(i) 80 switch { 81 case !fi.Anonymous: 82 panic("struct cannot have named fields") 83 case fi.Type.Kind() != reflect.Interface: 84 panic("embedded field must be an interface type") 85 case fi.Type.NumMethod() == 0: 86 // This matches everything; why would you ever want this? 87 panic("cannot ignore empty interface") 88 default: 89 tf = append(tf, fi.Type) 90 } 91 } 92 return tf 93 } 94 func (tf ifaceFilter) filter(p cmp.Path) bool { 95 if len(p) < 1 { 96 return false 97 } 98 t := p.Last().Type() 99 for _, ti := range tf { 100 if t.AssignableTo(ti) { 101 return true 102 } 103 if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) { 104 return true 105 } 106 } 107 return false 108 } 109 110 // IgnoreUnexported returns an Option that only ignores the immediate unexported 111 // fields of a struct, including anonymous fields of unexported types. 112 // In particular, unexported fields within the struct's exported fields 113 // of struct types, including anonymous fields, will not be ignored unless the 114 // type of the field itself is also passed to IgnoreUnexported. 115 func IgnoreUnexported(typs ...interface{}) cmp.Option { 116 ux := newUnexportedFilter(typs...) 117 return cmp.FilterPath(ux.filter, cmp.Ignore()) 118 } 119 120 type unexportedFilter struct{ m map[reflect.Type]bool } 121 122 func newUnexportedFilter(typs ...interface{}) unexportedFilter { 123 ux := unexportedFilter{m: make(map[reflect.Type]bool)} 124 for _, typ := range typs { 125 t := reflect.TypeOf(typ) 126 if t == nil || t.Kind() != reflect.Struct { 127 panic(fmt.Sprintf("invalid struct type: %T", typ)) 128 } 129 ux.m[t] = true 130 } 131 return ux 132 } 133 func (xf unexportedFilter) filter(p cmp.Path) bool { 134 sf, ok := p.Index(-1).(cmp.StructField) 135 if !ok { 136 return false 137 } 138 return xf.m[p.Index(-2).Type()] && !isExported(sf.Name()) 139 } 140 141 // isExported reports whether the identifier is exported. 142 func isExported(id string) bool { 143 r, _ := utf8.DecodeRuneInString(id) 144 return unicode.IsUpper(r) 145 }