Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/util/subset/subset.go
5304 views
1
// Package subset implements functions to check if one value is a subset of
2
// another.
3
package subset
4
5
import (
6
"fmt"
7
"reflect"
8
9
"gopkg.in/yaml.v2"
10
)
11
12
// Assert checks whether target is a subset of source. source and target must
13
// be the same type. target is a subset of source when:
14
//
15
// - If target and source are slices or arrays, then target must have the same
16
// number of elements as source. Each element in target must be a subset of
17
// the corresponding element from source.
18
//
19
// - If target and source are maps, each key in source must exist in target.
20
// The value for each element in target must be a subset of the corresponding
21
// element from source.
22
//
23
// - Otherwise, target and source must be deeply equal.
24
//
25
// An instance of Error will be returned when target is not a subset of source.
26
//
27
// Subset checking is primarily useful when doing things like YAML assertions,
28
// where you only want to ensure that a subset of YAML is defined as expected.
29
func Assert(source, target interface{}) error {
30
return assert(reflect.ValueOf(source), reflect.ValueOf(target))
31
}
32
33
func assert(source, target reflect.Value) error {
34
// Deference interface/pointers for direct comparison
35
for canElem(source) {
36
source = source.Elem()
37
}
38
for canElem(target) {
39
target = target.Elem()
40
}
41
42
if source.Type() != target.Type() {
43
return &Error{Message: fmt.Sprintf("type mismatch: %T != %T", source.Interface(), target.Interface())}
44
}
45
46
switch source.Kind() {
47
case reflect.Slice, reflect.Array:
48
if source.Len() != target.Len() {
49
return &Error{Message: fmt.Sprintf("length mismatch: %d != %d", source.Len(), target.Len())}
50
}
51
for i := 0; i < source.Len(); i++ {
52
if err := assert(source.Index(i), target.Index(i)); err != nil {
53
return &Error{
54
Message: fmt.Sprintf("element %d", i),
55
Inner: err,
56
}
57
}
58
}
59
return nil
60
61
case reflect.Map:
62
iter := source.MapRange()
63
for iter.Next() {
64
var (
65
sourceElement = iter.Value()
66
targetElement = target.MapIndex(iter.Key())
67
)
68
if !targetElement.IsValid() {
69
return &Error{Message: fmt.Sprintf("missing key %v", iter.Key().Interface())}
70
}
71
if err := assert(sourceElement, targetElement); err != nil {
72
return &Error{
73
Message: fmt.Sprintf("%v", iter.Key().Interface()),
74
Inner: err,
75
}
76
}
77
}
78
return nil
79
80
default:
81
if !reflect.DeepEqual(source.Interface(), target.Interface()) {
82
return &Error{Message: fmt.Sprintf("%v != %v", source, target)}
83
}
84
return nil
85
}
86
}
87
88
func canElem(v reflect.Value) bool {
89
return v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr
90
}
91
92
// Error is a subset assertion error.
93
type Error struct {
94
Message string // Message of the error
95
Inner error // Optional inner error
96
}
97
98
// Error implements error.
99
func (e *Error) Error() string {
100
if e.Inner == nil {
101
return e.Message
102
}
103
return fmt.Sprintf("%s: %s", e.Message, e.Inner)
104
}
105
106
// Unwrap returns the inner error, if set.
107
func (e *Error) Unwrap() error { return e.Inner }
108
109
// YAMLAssert is like Assert but accepts YAML bytes as input.
110
func YAMLAssert(source, target []byte) error {
111
var sourceValue interface{}
112
if err := yaml.Unmarshal(source, &sourceValue); err != nil {
113
return err
114
}
115
var targetValue interface{}
116
if err := yaml.Unmarshal(target, &targetValue); err != nil {
117
return err
118
}
119
return Assert(sourceValue, targetValue)
120
}
121
122