Skip to content

Commit

Permalink
api/types/filters: Add GetBoolOrDefault
Browse files Browse the repository at this point in the history
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
  • Loading branch information
vvoland committed Jan 26, 2023
1 parent 2b9de2e commit 0d68591
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 11 deletions.
37 changes: 37 additions & 0 deletions api/types/filters/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package filters

import "fmt"

// invalidFilter indicates that the provided filter or its value is invalid
type invalidFilter struct {
Filter string
Value []string
}

func (e invalidFilter) Error() string {
msg := "invalid filter"
if e.Filter != "" {
msg += " '" + e.Filter
if e.Value != nil {
msg = fmt.Sprintf("%s=%s", msg, e.Value)
}
msg += "'"
}
return msg
}

// InvalidParameter marks this error as ErrInvalidParameter
func (e invalidFilter) InvalidParameter() {}

// unreachableCode is an error indicating that the code path was not expected to be reached.
type unreachableCode struct {
Filter string
Value []string
}

// System marks this error as ErrSystem
func (e unreachableCode) System() {}

func (e unreachableCode) Error() string {
return fmt.Sprintf("unreachable code reached for filter: %q with values: %s", e.Filter, e.Value)
}
45 changes: 34 additions & 11 deletions api/types/filters/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"strings"

"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
)

// Args stores a mapping of keys to a set of multiple values.
Expand Down Expand Up @@ -99,7 +98,7 @@ func FromJSON(p string) (Args, error) {
// Fallback to parsing arguments in the legacy slice format
deprecated := map[string][]string{}
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
return args, invalidFilter{errors.Wrap(err, "invalid filter")}
return args, invalidFilter{}
}

args.fields = deprecatedArgs(deprecated)
Expand Down Expand Up @@ -196,6 +195,38 @@ func (args Args) Match(field, source string) bool {
return false
}

// GetBoolOrDefault returns a boolean value of the key if the key is present
// and is intepretable as a boolean value. Otherwise the default value is returned.
// Error is not nil only if the filter values are not valid boolean or are conflicting.
func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) {
fieldValues, ok := args.fields[key]

if !ok {
return defaultValue, nil
}

if len(fieldValues) == 0 {
return defaultValue, invalidFilter{key, nil}
}

isFalse := fieldValues["0"] || fieldValues["false"]
isTrue := fieldValues["1"] || fieldValues["true"]

conflicting := isFalse && isTrue
invalid := !isFalse && !isTrue

if conflicting || invalid {
return defaultValue, invalidFilter{key, args.Get(key)}
} else if isFalse {
return false, nil
} else if isTrue {
return true, nil
}

// This code shouldn't be reached.
return defaultValue, unreachableCode{Filter: key, Value: args.Get(key)}
}

// ExactMatch returns true if the source matches exactly one of the values.
func (args Args) ExactMatch(key, source string) bool {
fieldValues, ok := args.fields[key]
Expand Down Expand Up @@ -246,20 +277,12 @@ func (args Args) Contains(field string) bool {
return ok
}

type invalidFilter struct{ error }

func (e invalidFilter) Error() string {
return e.error.Error()
}

func (invalidFilter) InvalidParameter() {}

// Validate compared the set of accepted keys against the keys in the mapping.
// An error is returned if any mapping keys are not in the accepted set.
func (args Args) Validate(accepted map[string]bool) error {
for name := range args.fields {
if !accepted[name] {
return invalidFilter{errors.New("invalid filter '" + name + "'")}
return invalidFilter{name, nil}
}
}
return nil
Expand Down
116 changes: 116 additions & 0 deletions api/types/filters/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package filters // import "github.com/docker/docker/api/types/filters"
import (
"encoding/json"
"errors"
"sort"
"testing"

"gotest.tools/v3/assert"
Expand Down Expand Up @@ -418,3 +419,118 @@ func TestClone(t *testing.T) {
f2.Add("baz", "qux")
assert.Check(t, is.Len(f.Get("baz"), 0))
}

func TestGetBoolOrDefault(t *testing.T) {
for _, tC := range []struct {
name string
args map[string][]string
defValue bool
expectedErr error
expectedValue bool
}{
{
name: "single true",
args: map[string][]string{
"dangling": {"true"},
},
defValue: false,
expectedErr: nil,
expectedValue: true,
},
{
name: "single false",
args: map[string][]string{
"dangling": {"false"},
},
defValue: true,
expectedErr: nil,
expectedValue: false,
},
{
name: "single bad value",
args: map[string][]string{
"dangling": {"potato"},
},
defValue: true,
expectedErr: invalidFilter{Filter: "dangling", Value: []string{"potato"}},
expectedValue: true,
},
{
name: "two bad values",
args: map[string][]string{
"dangling": {"banana", "potato"},
},
defValue: true,
expectedErr: invalidFilter{Filter: "dangling", Value: []string{"banana", "potato"}},
expectedValue: true,
},
{
name: "two conflicting values",
args: map[string][]string{
"dangling": {"false", "true"},
},
defValue: false,
expectedErr: invalidFilter{Filter: "dangling", Value: []string{"false", "true"}},
expectedValue: false,
},
{
name: "multiple conflicting values",
args: map[string][]string{
"dangling": {"false", "true", "1"},
},
defValue: true,
expectedErr: invalidFilter{Filter: "dangling", Value: []string{"false", "true", "1"}},
expectedValue: true,
},
{
name: "1 means true",
args: map[string][]string{
"dangling": {"1"},
},
defValue: false,
expectedErr: nil,
expectedValue: true,
},
{
name: "0 means false",
args: map[string][]string{
"dangling": {"0"},
},
defValue: true,
expectedErr: nil,
expectedValue: false,
},
} {
tC := tC
t.Run(tC.name, func(t *testing.T) {
a := NewArgs()

for key, values := range tC.args {
for _, value := range values {
a.Add(key, value)
}
}

value, err := a.GetBoolOrDefault("dangling", tC.defValue)

if tC.expectedErr == nil {
assert.Check(t, is.Nil(err))
} else {
assert.Check(t, is.ErrorType(err, tC.expectedErr))

// Check if error is the same.
expected := tC.expectedErr.(invalidFilter)
actual := err.(invalidFilter)

assert.Check(t, is.Equal(expected.Filter, actual.Filter))

sort.Strings(expected.Value)
sort.Strings(actual.Value)
assert.Check(t, is.DeepEqual(expected.Value, actual.Value))
}

assert.Check(t, is.Equal(tC.expectedValue, value))
})
}

}

0 comments on commit 0d68591

Please sign in to comment.