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  }