mirror of https://github.com/grafana/grafana
Add support for sending health check to datasource plugins. (#22771)
closes #21519 ref grafana/grafana-plugin-sdk-go#93pull/22779/head
parent
2693f44a03
commit
ebc9549cbc
@ -0,0 +1,89 @@ |
||||
// Copyright 2017, 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.md file.
|
||||
|
||||
// Package cmpopts provides common options for the cmp package.
|
||||
package cmpopts |
||||
|
||||
import ( |
||||
"math" |
||||
"reflect" |
||||
|
||||
"github.com/google/go-cmp/cmp" |
||||
) |
||||
|
||||
func equateAlways(_, _ interface{}) bool { return true } |
||||
|
||||
// EquateEmpty returns a Comparer option that determines all maps and slices
|
||||
// with a length of zero to be equal, regardless of whether they are nil.
|
||||
//
|
||||
// EquateEmpty can be used in conjunction with SortSlices and SortMaps.
|
||||
func EquateEmpty() cmp.Option { |
||||
return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways)) |
||||
} |
||||
|
||||
func isEmpty(x, y interface{}) bool { |
||||
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) |
||||
return (x != nil && y != nil && vx.Type() == vy.Type()) && |
||||
(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) && |
||||
(vx.Len() == 0 && vy.Len() == 0) |
||||
} |
||||
|
||||
// EquateApprox returns a Comparer option that determines float32 or float64
|
||||
// values to be equal if they are within a relative fraction or absolute margin.
|
||||
// This option is not used when either x or y is NaN or infinite.
|
||||
//
|
||||
// The fraction determines that the difference of two values must be within the
|
||||
// smaller fraction of the two values, while the margin determines that the two
|
||||
// values must be within some absolute margin.
|
||||
// To express only a fraction or only a margin, use 0 for the other parameter.
|
||||
// The fraction and margin must be non-negative.
|
||||
//
|
||||
// The mathematical expression used is equivalent to:
|
||||
// |x-y| ≤ max(fraction*min(|x|, |y|), margin)
|
||||
//
|
||||
// EquateApprox can be used in conjunction with EquateNaNs.
|
||||
func EquateApprox(fraction, margin float64) cmp.Option { |
||||
if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) { |
||||
panic("margin or fraction must be a non-negative number") |
||||
} |
||||
a := approximator{fraction, margin} |
||||
return cmp.Options{ |
||||
cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)), |
||||
cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)), |
||||
} |
||||
} |
||||
|
||||
type approximator struct{ frac, marg float64 } |
||||
|
||||
func areRealF64s(x, y float64) bool { |
||||
return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0) |
||||
} |
||||
func areRealF32s(x, y float32) bool { |
||||
return areRealF64s(float64(x), float64(y)) |
||||
} |
||||
func (a approximator) compareF64(x, y float64) bool { |
||||
relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y)) |
||||
return math.Abs(x-y) <= math.Max(a.marg, relMarg) |
||||
} |
||||
func (a approximator) compareF32(x, y float32) bool { |
||||
return a.compareF64(float64(x), float64(y)) |
||||
} |
||||
|
||||
// EquateNaNs returns a Comparer option that determines float32 and float64
|
||||
// NaN values to be equal.
|
||||
//
|
||||
// EquateNaNs can be used in conjunction with EquateApprox.
|
||||
func EquateNaNs() cmp.Option { |
||||
return cmp.Options{ |
||||
cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)), |
||||
cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)), |
||||
} |
||||
} |
||||
|
||||
func areNaNsF64s(x, y float64) bool { |
||||
return math.IsNaN(x) && math.IsNaN(y) |
||||
} |
||||
func areNaNsF32s(x, y float32) bool { |
||||
return areNaNsF64s(float64(x), float64(y)) |
||||
} |
@ -0,0 +1,207 @@ |
||||
// Copyright 2017, 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.md file.
|
||||
|
||||
package cmpopts |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
"unicode" |
||||
"unicode/utf8" |
||||
|
||||
"github.com/google/go-cmp/cmp" |
||||
"github.com/google/go-cmp/cmp/internal/function" |
||||
) |
||||
|
||||
// IgnoreFields returns an Option that ignores exported fields of the
|
||||
// given names on a single struct type.
|
||||
// The struct type is specified by passing in a value of that type.
|
||||
//
|
||||
// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
|
||||
// specific sub-field that is embedded or nested within the parent struct.
|
||||
//
|
||||
// This does not handle unexported fields; use IgnoreUnexported instead.
|
||||
func IgnoreFields(typ interface{}, names ...string) cmp.Option { |
||||
sf := newStructFilter(typ, names...) |
||||
return cmp.FilterPath(sf.filter, cmp.Ignore()) |
||||
} |
||||
|
||||
// IgnoreTypes returns an Option that ignores all values assignable to
|
||||
// certain types, which are specified by passing in a value of each type.
|
||||
func IgnoreTypes(typs ...interface{}) cmp.Option { |
||||
tf := newTypeFilter(typs...) |
||||
return cmp.FilterPath(tf.filter, cmp.Ignore()) |
||||
} |
||||
|
||||
type typeFilter []reflect.Type |
||||
|
||||
func newTypeFilter(typs ...interface{}) (tf typeFilter) { |
||||
for _, typ := range typs { |
||||
t := reflect.TypeOf(typ) |
||||
if t == nil { |
||||
// This occurs if someone tries to pass in sync.Locker(nil)
|
||||
panic("cannot determine type; consider using IgnoreInterfaces") |
||||
} |
||||
tf = append(tf, t) |
||||
} |
||||
return tf |
||||
} |
||||
func (tf typeFilter) filter(p cmp.Path) bool { |
||||
if len(p) < 1 { |
||||
return false |
||||
} |
||||
t := p.Last().Type() |
||||
for _, ti := range tf { |
||||
if t.AssignableTo(ti) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// IgnoreInterfaces returns an Option that ignores all values or references of
|
||||
// values assignable to certain interface types. These interfaces are specified
|
||||
// by passing in an anonymous struct with the interface types embedded in it.
|
||||
// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
|
||||
func IgnoreInterfaces(ifaces interface{}) cmp.Option { |
||||
tf := newIfaceFilter(ifaces) |
||||
return cmp.FilterPath(tf.filter, cmp.Ignore()) |
||||
} |
||||
|
||||
type ifaceFilter []reflect.Type |
||||
|
||||
func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) { |
||||
t := reflect.TypeOf(ifaces) |
||||
if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct { |
||||
panic("input must be an anonymous struct") |
||||
} |
||||
for i := 0; i < t.NumField(); i++ { |
||||
fi := t.Field(i) |
||||
switch { |
||||
case !fi.Anonymous: |
||||
panic("struct cannot have named fields") |
||||
case fi.Type.Kind() != reflect.Interface: |
||||
panic("embedded field must be an interface type") |
||||
case fi.Type.NumMethod() == 0: |
||||
// This matches everything; why would you ever want this?
|
||||
panic("cannot ignore empty interface") |
||||
default: |
||||
tf = append(tf, fi.Type) |
||||
} |
||||
} |
||||
return tf |
||||
} |
||||
func (tf ifaceFilter) filter(p cmp.Path) bool { |
||||
if len(p) < 1 { |
||||
return false |
||||
} |
||||
t := p.Last().Type() |
||||
for _, ti := range tf { |
||||
if t.AssignableTo(ti) { |
||||
return true |
||||
} |
||||
if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// IgnoreUnexported returns an Option that only ignores the immediate unexported
|
||||
// fields of a struct, including anonymous fields of unexported types.
|
||||
// In particular, unexported fields within the struct's exported fields
|
||||
// of struct types, including anonymous fields, will not be ignored unless the
|
||||
// type of the field itself is also passed to IgnoreUnexported.
|
||||
//
|
||||
// Avoid ignoring unexported fields of a type which you do not control (i.e. a
|
||||
// type from another repository), as changes to the implementation of such types
|
||||
// may change how the comparison behaves. Prefer a custom Comparer instead.
|
||||
func IgnoreUnexported(typs ...interface{}) cmp.Option { |
||||
ux := newUnexportedFilter(typs...) |
||||
return cmp.FilterPath(ux.filter, cmp.Ignore()) |
||||
} |
||||
|
||||
type unexportedFilter struct{ m map[reflect.Type]bool } |
||||
|
||||
func newUnexportedFilter(typs ...interface{}) unexportedFilter { |
||||
ux := unexportedFilter{m: make(map[reflect.Type]bool)} |
||||
for _, typ := range typs { |
||||
t := reflect.TypeOf(typ) |
||||
if t == nil || t.Kind() != reflect.Struct { |
||||
panic(fmt.Sprintf("invalid struct type: %T", typ)) |
||||
} |
||||
ux.m[t] = true |
||||
} |
||||
return ux |
||||
} |
||||
func (xf unexportedFilter) filter(p cmp.Path) bool { |
||||
sf, ok := p.Index(-1).(cmp.StructField) |
||||
if !ok { |
||||
return false |
||||
} |
||||
return xf.m[p.Index(-2).Type()] && !isExported(sf.Name()) |
||||
} |
||||
|
||||
// isExported reports whether the identifier is exported.
|
||||
func isExported(id string) bool { |
||||
r, _ := utf8.DecodeRuneInString(id) |
||||
return unicode.IsUpper(r) |
||||
} |
||||
|
||||
// IgnoreSliceElements returns an Option that ignores elements of []V.
|
||||
// The discard function must be of the form "func(T) bool" which is used to
|
||||
// ignore slice elements of type V, where V is assignable to T.
|
||||
// Elements are ignored if the function reports true.
|
||||
func IgnoreSliceElements(discardFunc interface{}) cmp.Option { |
||||
vf := reflect.ValueOf(discardFunc) |
||||
if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() { |
||||
panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) |
||||
} |
||||
return cmp.FilterPath(func(p cmp.Path) bool { |
||||
si, ok := p.Index(-1).(cmp.SliceIndex) |
||||
if !ok { |
||||
return false |
||||
} |
||||
if !si.Type().AssignableTo(vf.Type().In(0)) { |
||||
return false |
||||
} |
||||
vx, vy := si.Values() |
||||
if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() { |
||||
return true |
||||
} |
||||
if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() { |
||||
return true |
||||
} |
||||
return false |
||||
}, cmp.Ignore()) |
||||
} |
||||
|
||||
// IgnoreMapEntries returns an Option that ignores entries of map[K]V.
|
||||
// The discard function must be of the form "func(T, R) bool" which is used to
|
||||
// ignore map entries of type K and V, where K and V are assignable to T and R.
|
||||
// Entries are ignored if the function reports true.
|
||||
func IgnoreMapEntries(discardFunc interface{}) cmp.Option { |
||||
vf := reflect.ValueOf(discardFunc) |
||||
if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() { |
||||
panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) |
||||
} |
||||
return cmp.FilterPath(func(p cmp.Path) bool { |
||||
mi, ok := p.Index(-1).(cmp.MapIndex) |
||||
if !ok { |
||||
return false |
||||
} |
||||
if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) { |
||||
return false |
||||
} |
||||
k := mi.Key() |
||||
vx, vy := mi.Values() |
||||
if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() { |
||||
return true |
||||
} |
||||
if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() { |
||||
return true |
||||
} |
||||
return false |
||||
}, cmp.Ignore()) |
||||
} |
@ -0,0 +1,147 @@ |
||||
// Copyright 2017, 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.md file.
|
||||
|
||||
package cmpopts |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
"sort" |
||||
|
||||
"github.com/google/go-cmp/cmp" |
||||
"github.com/google/go-cmp/cmp/internal/function" |
||||
) |
||||
|
||||
// SortSlices returns a Transformer option that sorts all []V.
|
||||
// The less function must be of the form "func(T, T) bool" which is used to
|
||||
// sort any slice with element type V that is assignable to T.
|
||||
//
|
||||
// The less function must be:
|
||||
// • Deterministic: less(x, y) == less(x, y)
|
||||
// • Irreflexive: !less(x, x)
|
||||
// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
|
||||
//
|
||||
// The less function does not have to be "total". That is, if !less(x, y) and
|
||||
// !less(y, x) for two elements x and y, their relative order is maintained.
|
||||
//
|
||||
// SortSlices can be used in conjunction with EquateEmpty.
|
||||
func SortSlices(lessFunc interface{}) cmp.Option { |
||||
vf := reflect.ValueOf(lessFunc) |
||||
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { |
||||
panic(fmt.Sprintf("invalid less function: %T", lessFunc)) |
||||
} |
||||
ss := sliceSorter{vf.Type().In(0), vf} |
||||
return cmp.FilterValues(ss.filter, cmp.Transformer("cmpopts.SortSlices", ss.sort)) |
||||
} |
||||
|
||||
type sliceSorter struct { |
||||
in reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
} |
||||
|
||||
func (ss sliceSorter) filter(x, y interface{}) bool { |
||||
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) |
||||
if !(x != nil && y != nil && vx.Type() == vy.Type()) || |
||||
!(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) || |
||||
(vx.Len() <= 1 && vy.Len() <= 1) { |
||||
return false |
||||
} |
||||
// Check whether the slices are already sorted to avoid an infinite
|
||||
// recursion cycle applying the same transform to itself.
|
||||
ok1 := sort.SliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) }) |
||||
ok2 := sort.SliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) }) |
||||
return !ok1 || !ok2 |
||||
} |
||||
func (ss sliceSorter) sort(x interface{}) interface{} { |
||||
src := reflect.ValueOf(x) |
||||
dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len()) |
||||
for i := 0; i < src.Len(); i++ { |
||||
dst.Index(i).Set(src.Index(i)) |
||||
} |
||||
sort.SliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) }) |
||||
ss.checkSort(dst) |
||||
return dst.Interface() |
||||
} |
||||
func (ss sliceSorter) checkSort(v reflect.Value) { |
||||
start := -1 // Start of a sequence of equal elements.
|
||||
for i := 1; i < v.Len(); i++ { |
||||
if ss.less(v, i-1, i) { |
||||
// Check that first and last elements in v[start:i] are equal.
|
||||
if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) { |
||||
panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i))) |
||||
} |
||||
start = -1 |
||||
} else if start == -1 { |
||||
start = i |
||||
} |
||||
} |
||||
} |
||||
func (ss sliceSorter) less(v reflect.Value, i, j int) bool { |
||||
vx, vy := v.Index(i), v.Index(j) |
||||
return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool() |
||||
} |
||||
|
||||
// SortMaps returns a Transformer option that flattens map[K]V types to be a
|
||||
// sorted []struct{K, V}. The less function must be of the form
|
||||
// "func(T, T) bool" which is used to sort any map with key K that is
|
||||
// assignable to T.
|
||||
//
|
||||
// Flattening the map into a slice has the property that cmp.Equal is able to
|
||||
// use Comparers on K or the K.Equal method if it exists.
|
||||
//
|
||||
// The less function must be:
|
||||
// • Deterministic: less(x, y) == less(x, y)
|
||||
// • Irreflexive: !less(x, x)
|
||||
// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
|
||||
// • Total: if x != y, then either less(x, y) or less(y, x)
|
||||
//
|
||||
// SortMaps can be used in conjunction with EquateEmpty.
|
||||
func SortMaps(lessFunc interface{}) cmp.Option { |
||||
vf := reflect.ValueOf(lessFunc) |
||||
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { |
||||
panic(fmt.Sprintf("invalid less function: %T", lessFunc)) |
||||
} |
||||
ms := mapSorter{vf.Type().In(0), vf} |
||||
return cmp.FilterValues(ms.filter, cmp.Transformer("cmpopts.SortMaps", ms.sort)) |
||||
} |
||||
|
||||
type mapSorter struct { |
||||
in reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
} |
||||
|
||||
func (ms mapSorter) filter(x, y interface{}) bool { |
||||
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) |
||||
return (x != nil && y != nil && vx.Type() == vy.Type()) && |
||||
(vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) && |
||||
(vx.Len() != 0 || vy.Len() != 0) |
||||
} |
||||
func (ms mapSorter) sort(x interface{}) interface{} { |
||||
src := reflect.ValueOf(x) |
||||
outType := reflect.StructOf([]reflect.StructField{ |
||||
{Name: "K", Type: src.Type().Key()}, |
||||
{Name: "V", Type: src.Type().Elem()}, |
||||
}) |
||||
dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len()) |
||||
for i, k := range src.MapKeys() { |
||||
v := reflect.New(outType).Elem() |
||||
v.Field(0).Set(k) |
||||
v.Field(1).Set(src.MapIndex(k)) |
||||
dst.Index(i).Set(v) |
||||
} |
||||
sort.Slice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) }) |
||||
ms.checkSort(dst) |
||||
return dst.Interface() |
||||
} |
||||
func (ms mapSorter) checkSort(v reflect.Value) { |
||||
for i := 1; i < v.Len(); i++ { |
||||
if !ms.less(v, i-1, i) { |
||||
panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i))) |
||||
} |
||||
} |
||||
} |
||||
func (ms mapSorter) less(v reflect.Value, i, j int) bool { |
||||
vx, vy := v.Index(i).Field(0), v.Index(j).Field(0) |
||||
return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool() |
||||
} |
@ -0,0 +1,182 @@ |
||||
// Copyright 2017, 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.md file.
|
||||
|
||||
package cmpopts |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
"strings" |
||||
|
||||
"github.com/google/go-cmp/cmp" |
||||
) |
||||
|
||||
// filterField returns a new Option where opt is only evaluated on paths that
|
||||
// include a specific exported field on a single struct type.
|
||||
// The struct type is specified by passing in a value of that type.
|
||||
//
|
||||
// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
|
||||
// specific sub-field that is embedded or nested within the parent struct.
|
||||
func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option { |
||||
// TODO: This is currently unexported over concerns of how helper filters
|
||||
// can be composed together easily.
|
||||
// TODO: Add tests for FilterField.
|
||||
|
||||
sf := newStructFilter(typ, name) |
||||
return cmp.FilterPath(sf.filter, opt) |
||||
} |
||||
|
||||
type structFilter struct { |
||||
t reflect.Type // The root struct type to match on
|
||||
ft fieldTree // Tree of fields to match on
|
||||
} |
||||
|
||||
func newStructFilter(typ interface{}, names ...string) structFilter { |
||||
// TODO: Perhaps allow * as a special identifier to allow ignoring any
|
||||
// number of path steps until the next field match?
|
||||
// This could be useful when a concrete struct gets transformed into
|
||||
// an anonymous struct where it is not possible to specify that by type,
|
||||
// but the transformer happens to provide guarantees about the names of
|
||||
// the transformed fields.
|
||||
|
||||
t := reflect.TypeOf(typ) |
||||
if t == nil || t.Kind() != reflect.Struct { |
||||
panic(fmt.Sprintf("%T must be a struct", typ)) |
||||
} |
||||
var ft fieldTree |
||||
for _, name := range names { |
||||
cname, err := canonicalName(t, name) |
||||
if err != nil { |
||||
panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err)) |
||||
} |
||||
ft.insert(cname) |
||||
} |
||||
return structFilter{t, ft} |
||||
} |
||||
|
||||
func (sf structFilter) filter(p cmp.Path) bool { |
||||
for i, ps := range p { |
||||
if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// fieldTree represents a set of dot-separated identifiers.
|
||||
//
|
||||
// For example, inserting the following selectors:
|
||||
// Foo
|
||||
// Foo.Bar.Baz
|
||||
// Foo.Buzz
|
||||
// Nuka.Cola.Quantum
|
||||
//
|
||||
// Results in a tree of the form:
|
||||
// {sub: {
|
||||
// "Foo": {ok: true, sub: {
|
||||
// "Bar": {sub: {
|
||||
// "Baz": {ok: true},
|
||||
// }},
|
||||
// "Buzz": {ok: true},
|
||||
// }},
|
||||
// "Nuka": {sub: {
|
||||
// "Cola": {sub: {
|
||||
// "Quantum": {ok: true},
|
||||
// }},
|
||||
// }},
|
||||
// }}
|
||||
type fieldTree struct { |
||||
ok bool // Whether this is a specified node
|
||||
sub map[string]fieldTree // The sub-tree of fields under this node
|
||||
} |
||||
|
||||
// insert inserts a sequence of field accesses into the tree.
|
||||
func (ft *fieldTree) insert(cname []string) { |
||||
if ft.sub == nil { |
||||
ft.sub = make(map[string]fieldTree) |
||||
} |
||||
if len(cname) == 0 { |
||||
ft.ok = true |
||||
return |
||||
} |
||||
sub := ft.sub[cname[0]] |
||||
sub.insert(cname[1:]) |
||||
ft.sub[cname[0]] = sub |
||||
} |
||||
|
||||
// matchPrefix reports whether any selector in the fieldTree matches
|
||||
// the start of path p.
|
||||
func (ft fieldTree) matchPrefix(p cmp.Path) bool { |
||||
for _, ps := range p { |
||||
switch ps := ps.(type) { |
||||
case cmp.StructField: |
||||
ft = ft.sub[ps.Name()] |
||||
if ft.ok { |
||||
return true |
||||
} |
||||
if len(ft.sub) == 0 { |
||||
return false |
||||
} |
||||
case cmp.Indirect: |
||||
default: |
||||
return false |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// canonicalName returns a list of identifiers where any struct field access
|
||||
// through an embedded field is expanded to include the names of the embedded
|
||||
// types themselves.
|
||||
//
|
||||
// For example, suppose field "Foo" is not directly in the parent struct,
|
||||
// but actually from an embedded struct of type "Bar". Then, the canonical name
|
||||
// of "Foo" is actually "Bar.Foo".
|
||||
//
|
||||
// Suppose field "Foo" is not directly in the parent struct, but actually
|
||||
// a field in two different embedded structs of types "Bar" and "Baz".
|
||||
// Then the selector "Foo" causes a panic since it is ambiguous which one it
|
||||
// refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
|
||||
func canonicalName(t reflect.Type, sel string) ([]string, error) { |
||||
var name string |
||||
sel = strings.TrimPrefix(sel, ".") |
||||
if sel == "" { |
||||
return nil, fmt.Errorf("name must not be empty") |
||||
} |
||||
if i := strings.IndexByte(sel, '.'); i < 0 { |
||||
name, sel = sel, "" |
||||
} else { |
||||
name, sel = sel[:i], sel[i:] |
||||
} |
||||
|
||||
// Type must be a struct or pointer to struct.
|
||||
if t.Kind() == reflect.Ptr { |
||||
t = t.Elem() |
||||
} |
||||
if t.Kind() != reflect.Struct { |
||||
return nil, fmt.Errorf("%v must be a struct", t) |
||||
} |
||||
|
||||
// Find the canonical name for this current field name.
|
||||
// If the field exists in an embedded struct, then it will be expanded.
|
||||
if !isExported(name) { |
||||
// Disallow unexported fields:
|
||||
// * To discourage people from actually touching unexported fields
|
||||
// * FieldByName is buggy (https://golang.org/issue/4876)
|
||||
return []string{name}, fmt.Errorf("name must be exported") |
||||
} |
||||
sf, ok := t.FieldByName(name) |
||||
if !ok { |
||||
return []string{name}, fmt.Errorf("does not exist") |
||||
} |
||||
var ss []string |
||||
for i := range sf.Index { |
||||
ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name) |
||||
} |
||||
if sel == "" { |
||||
return ss, nil |
||||
} |
||||
ssPost, err := canonicalName(sf.Type, sel) |
||||
return append(ss, ssPost...), err |
||||
} |
@ -0,0 +1,35 @@ |
||||
// Copyright 2018, 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.md file.
|
||||
|
||||
package cmpopts |
||||
|
||||
import ( |
||||
"github.com/google/go-cmp/cmp" |
||||
) |
||||
|
||||
type xformFilter struct{ xform cmp.Option } |
||||
|
||||
func (xf xformFilter) filter(p cmp.Path) bool { |
||||
for _, ps := range p { |
||||
if t, ok := ps.(cmp.Transform); ok && t.Option() == xf.xform { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// AcyclicTransformer returns a Transformer with a filter applied that ensures
|
||||
// that the transformer cannot be recursively applied upon its own output.
|
||||
//
|
||||
// An example use case is a transformer that splits a string by lines:
|
||||
// AcyclicTransformer("SplitLines", func(s string) []string{
|
||||
// return strings.Split(s, "\n")
|
||||
// })
|
||||
//
|
||||
// Had this been an unfiltered Transformer instead, this would result in an
|
||||
// infinite cycle converting a string to []string to [][]string and so on.
|
||||
func AcyclicTransformer(name string, xformFunc interface{}) cmp.Option { |
||||
xf := xformFilter{cmp.Transformer(name, xformFunc)} |
||||
return cmp.FilterPath(xf.filter, xf.xform) |
||||
} |
Loading…
Reference in new issue