Skip to content

Commit

Permalink
feat(filter): add In and NotIn operators (#44)
Browse files Browse the repository at this point in the history
* feat(filter): add In and NotIn operators

* test: add additional tests

* chore: updating some comments

* chore(golangci-lint): fix violations
  • Loading branch information
ekristen authored Mar 28, 2024
1 parent 714d468 commit 804aa1b
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 10 deletions.
56 changes: 50 additions & 6 deletions pkg/filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package filter
import (
"fmt"
"regexp"
"slices"
"strconv"
"strings"
"time"
Expand All @@ -22,6 +23,8 @@ const (
DateOlderThan Type = "dateOlderThan"
Suffix Type = "suffix"
Prefix Type = "prefix"
NotIn Type = "NotIn"
In Type = "In"

Global = "__global__"
)
Expand All @@ -48,6 +51,7 @@ func (f Filters) Get(resourceType string) []Filter {
return filters
}

// Validate checks if the filters are valid or not and returns an error if they are not
func (f Filters) Validate() error {
for resourceType, filters := range f {
for _, filter := range filters {
Expand All @@ -60,7 +64,8 @@ func (f Filters) Validate() error {
return nil
}

// Append appends the filters from f2 to f
// Append appends the filters from f2 to f. This is primarily used to append filters from a preset
// to a set of filters that were defined on a resource type.
func (f Filters) Append(f2 Filters) {
for resourceType, filter := range f2 {
f[resourceType] = append(f[resourceType], filter...)
Expand All @@ -84,6 +89,9 @@ type Filter struct {
// Value is the value to filter on
Value string

// Values allows for multiple values to be specified for a filter
Values []string

// Invert is a flag to invert the filter
Invert string
}
Expand Down Expand Up @@ -138,6 +146,12 @@ func (f *Filter) Match(o string) (bool, error) {
case Suffix:
return strings.HasSuffix(o, f.Value), nil

case In:
return slices.Contains(f.Values, o), nil

case NotIn:
return !slices.Contains(f.Values, o), nil

default:
return false, fmt.Errorf("unknown type %s", f.Type)
}
Expand All @@ -152,20 +166,50 @@ func (f *Filter) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}

m := map[string]string{}
m := map[string]interface{}{}
err := unmarshal(m)
if err != nil {
fmt.Println("%%%%%%%%")
return err
}

f.Type = Type(m["type"])
f.Value = m["value"]
f.Property = m["property"]
f.Invert = m["invert"]
f.Type = Type(m["type"].(string))

if m["value"] == nil {
f.Value = ""
} else {
f.Value = m["value"].(string)
}

if m["values"] == nil {
f.Values = []string{}
} else {
interfaceSlice := m["values"].([]interface{})
stringSlice := make([]string, len(interfaceSlice))
for i, v := range interfaceSlice {
str, _ := v.(string)
stringSlice[i] = str
}

f.Values = stringSlice
}

if m["property"] == nil {
f.Property = ""
} else {
f.Property = m["property"].(string)
}

if m["invert"] == nil {
f.Invert = ""
} else {
f.Invert = m["invert"].(string)
}

return nil
}

// NewExactFilter creates a new filter that matches the exact value
func NewExactFilter(value string) Filter {
return Filter{
Type: Exact,
Expand Down
77 changes: 73 additions & 4 deletions pkg/filter/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ func TestFilter_GlobalYAML(t *testing.T) {

expected := filter.Filters{
"Resource1": []filter.Filter{
{Property: "prop3", Type: filter.Exact, Value: "value3"},
{Property: "prop1", Type: filter.Exact, Value: "value1"},
{Property: "prop3", Type: filter.Exact, Value: "value3", Values: []string{}},
{Property: "prop1", Type: filter.Exact, Value: "value1", Values: []string{}},
},
"Resource2": []filter.Filter{
{Property: "prop3", Type: filter.Exact, Value: "value3"},
{Property: "prop2", Type: filter.Exact, Value: "value2"},
{Property: "prop3", Type: filter.Exact, Value: "value3", Values: []string{}},
{Property: "prop2", Type: filter.Exact, Value: "value2", Values: []string{}},
},
}

Expand Down Expand Up @@ -217,6 +217,24 @@ func TestFilter_UnmarshalFilter(t *testing.T) {
match: []string{"12345-somesuffix"},
error: true,
},
{
name: "invert",
yaml: `{"type":"exact","value":"foo","invert":"true"}`,
match: []string{"foo"},
mismatch: []string{"bar", "baz"},
},
{
name: "not-in",
yaml: `{"type":"NotIn","values":["foo","bar"]}`,
match: []string{"baz", "qux"},
mismatch: []string{"foo", "bar"},
},
{
name: "in",
yaml: `{"type":"In","values":["foo","bar"]}`,
match: []string{"foo", "bar"},
mismatch: []string{"baz", "qux"},
},
}

for _, tc := range cases {
Expand Down Expand Up @@ -274,6 +292,45 @@ func TestFilter_Merge(t *testing.T) {
},
}

// Merge the two Filters objects
f1.Merge(f2)

// Create the expected result
expected := filter.Filters{
"resource1": []filter.Filter{
{Property: "prop1", Type: filter.Exact, Value: "value1"},
{Property: "prop2", Type: filter.Glob, Value: "value2"},
},
"resource2": []filter.Filter{
{Property: "prop3", Type: filter.Regex, Value: "value3"},
},
}

validateErr := expected.Validate()
assert.NoError(t, validateErr)

// Check if the result is as expected
if !reflect.DeepEqual(f1, expected) {
t.Errorf("Merge() = %v, want %v", f1, expected)
}
}

func TestFilter_Append(t *testing.T) {
// Create two Filters objects
f1 := filter.Filters{
"resource1": []filter.Filter{
{Property: "prop1", Type: filter.Exact, Value: "value1"},
},
}
f2 := filter.Filters{
"resource1": []filter.Filter{
{Property: "prop2", Type: filter.Glob, Value: "value2"},
},
"resource2": []filter.Filter{
{Property: "prop3", Type: filter.Regex, Value: "value3"},
},
}

// Append the two Filters objects
f1.Append(f2)

Expand All @@ -297,6 +354,18 @@ func TestFilter_Merge(t *testing.T) {
}
}

func TestFilter_EmptyType(t *testing.T) {
f := filter.Filter{
Property: "Name",
Type: "",
Value: "anything",
}

match, err := f.Match("anything")
assert.NoError(t, err)
assert.True(t, match)
}

func TestFilter_ValidateError(t *testing.T) {
filters := filter.Filters{
"resource1": []filter.Filter{
Expand Down

0 comments on commit 804aa1b

Please sign in to comment.