/
matcher.go
138 lines (118 loc) · 3.7 KB
/
matcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package inmemory
import (
"bytes"
"errors"
"fmt"
"reflect"
"github.com/hellofresh/goengine"
"github.com/hellofresh/goengine/metadata"
)
var (
// ErrTypeMismatch occurs when a metadata value cannot be converted to the constraints value type
ErrTypeMismatch = errors.New("goengine: the values to compare are of a different type")
// ErrUnsupportedOperator occurs when a constraints operation is not supported for a type
ErrUnsupportedOperator = errors.New("goengine: the operator is not supported for this type")
)
type (
// IncompatibleMatcherError is an error that constraints multiple errors.
// This error is returned when a metadata.Matcher contains constraints that are
// not supported by the inmemory.MetadataMatcher
IncompatibleMatcherError []IncompatibleConstraintError
// IncompatibleConstraintError is an error indicating that a constraint is incompatible.
IncompatibleConstraintError struct {
Parent error
Constraint metadata.Constraint
}
// MetadataMatcher an in memory metadata matcher implementation
MetadataMatcher struct {
logger goengine.Logger
constraints []metadataConstraint
}
// metadataConstraint an in memory metadata constraint
metadataConstraint struct {
field string
operator metadata.Operator
value interface{}
valueType reflect.Type
}
)
// NewMetadataMatcher returns a new metadata matcher based of off the metadata.Matcher
func NewMetadataMatcher(matcher metadata.Matcher, logger goengine.Logger) (*MetadataMatcher, error) {
var constraints []metadataConstraint
var constraintErrors IncompatibleMatcherError
matcher.Iterate(func(c metadata.Constraint) {
cVal, err := asScalar(c.Value())
if err != nil {
constraintErrors = append(constraintErrors, IncompatibleConstraintError{err, c})
return
}
if !isSupportedOperator(cVal, c.Operator()) {
constraintErrors = append(constraintErrors, IncompatibleConstraintError{ErrUnsupportedOperator, c})
return
}
constraints = append(constraints, metadataConstraint{
c.Field(),
c.Operator(),
cVal,
reflect.TypeOf(cVal),
})
})
if len(constraintErrors) > 0 {
return nil, constraintErrors
}
return &MetadataMatcher{
constraints: constraints,
logger: logger,
}, nil
}
// Matches returns true if the constraints in the matcher are all satisfied by the metadata
func (m *MetadataMatcher) Matches(metadata metadata.Metadata) bool {
for _, c := range m.constraints {
valid, err := c.Matches(metadata.Value(c.field))
if err != nil {
if m.logger != nil {
m.logger.Warn("metadata constraint failed with error", func(e goengine.LoggerEntry) {
e.Error(err)
e.String("field", c.field)
})
}
return false
}
if !valid {
return false
}
}
return true
}
// Matches returns true if the value satisfies the constraint
func (c *metadataConstraint) Matches(val interface{}) (bool, error) {
// Ensure the value's are of the same type
if valType := reflect.TypeOf(val); valType != c.valueType {
// The types do not match let's see if they can be converted
if !valType.ConvertibleTo(c.valueType) {
return false, ErrTypeMismatch
}
val = reflect.ValueOf(val).Convert(c.valueType).Interface()
}
// Execute the comparison
return c.compareValue(val)
}
// Error an error message
func (e IncompatibleMatcherError) Error() string {
msg := bytes.NewBufferString("goengine: incompatible metadata.Matcher")
for _, err := range e {
_, _ = msg.WriteRune('\n')
_, _ = msg.WriteString(err.Error())
}
return msg.String()
}
// Error an error message
func (e *IncompatibleConstraintError) Error() string {
return fmt.Sprintf(
"constaint %s %s %v is incompatible %s",
e.Constraint.Field(),
e.Constraint.Operator(),
e.Constraint.Value(),
e.Parent.Error(),
)
}