mirror of
https://github.com/genuinetools/reg.git
synced 2024-09-19 00:31:02 -04:00
8e5eba8735
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
350 lines
8 KiB
Go
350 lines
8 KiB
Go
/*Package filters provides tools for encoding a mapping of keys to a set of
|
|
multiple values.
|
|
*/
|
|
package filters
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types/versions"
|
|
)
|
|
|
|
// Args stores a mapping of keys to a set of multiple values.
|
|
type Args struct {
|
|
fields map[string]map[string]bool
|
|
}
|
|
|
|
// KeyValuePair are used to initialize a new Args
|
|
type KeyValuePair struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
// Arg creates a new KeyValuePair for initializing Args
|
|
func Arg(key, value string) KeyValuePair {
|
|
return KeyValuePair{Key: key, Value: value}
|
|
}
|
|
|
|
// NewArgs returns a new Args populated with the initial args
|
|
func NewArgs(initialArgs ...KeyValuePair) Args {
|
|
args := Args{fields: map[string]map[string]bool{}}
|
|
for _, arg := range initialArgs {
|
|
args.Add(arg.Key, arg.Value)
|
|
}
|
|
return args
|
|
}
|
|
|
|
// ParseFlag parses a key=value string and adds it to an Args.
|
|
//
|
|
// Deprecated: Use Args.Add()
|
|
func ParseFlag(arg string, prev Args) (Args, error) {
|
|
filters := prev
|
|
if len(arg) == 0 {
|
|
return filters, nil
|
|
}
|
|
|
|
if !strings.Contains(arg, "=") {
|
|
return filters, ErrBadFormat
|
|
}
|
|
|
|
f := strings.SplitN(arg, "=", 2)
|
|
|
|
name := strings.ToLower(strings.TrimSpace(f[0]))
|
|
value := strings.TrimSpace(f[1])
|
|
|
|
filters.Add(name, value)
|
|
|
|
return filters, nil
|
|
}
|
|
|
|
// ErrBadFormat is an error returned when a filter is not in the form key=value
|
|
//
|
|
// Deprecated: this error will be removed in a future version
|
|
var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
|
|
|
|
// ToParam encodes the Args as args JSON encoded string
|
|
//
|
|
// Deprecated: use ToJSON
|
|
func ToParam(a Args) (string, error) {
|
|
return ToJSON(a)
|
|
}
|
|
|
|
// MarshalJSON returns a JSON byte representation of the Args
|
|
func (args Args) MarshalJSON() ([]byte, error) {
|
|
if len(args.fields) == 0 {
|
|
return []byte{}, nil
|
|
}
|
|
return json.Marshal(args.fields)
|
|
}
|
|
|
|
// ToJSON returns the Args as a JSON encoded string
|
|
func ToJSON(a Args) (string, error) {
|
|
if a.Len() == 0 {
|
|
return "", nil
|
|
}
|
|
buf, err := json.Marshal(a)
|
|
return string(buf), err
|
|
}
|
|
|
|
// ToParamWithVersion encodes Args as a JSON string. If version is less than 1.22
|
|
// then the encoded format will use an older legacy format where the values are a
|
|
// list of strings, instead of a set.
|
|
//
|
|
// Deprecated: Use ToJSON
|
|
func ToParamWithVersion(version string, a Args) (string, error) {
|
|
if a.Len() == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
if version != "" && versions.LessThan(version, "1.22") {
|
|
buf, err := json.Marshal(convertArgsToSlice(a.fields))
|
|
return string(buf), err
|
|
}
|
|
|
|
return ToJSON(a)
|
|
}
|
|
|
|
// FromParam decodes a JSON encoded string into Args
|
|
//
|
|
// Deprecated: use FromJSON
|
|
func FromParam(p string) (Args, error) {
|
|
return FromJSON(p)
|
|
}
|
|
|
|
// FromJSON decodes a JSON encoded string into Args
|
|
func FromJSON(p string) (Args, error) {
|
|
args := NewArgs()
|
|
|
|
if p == "" {
|
|
return args, nil
|
|
}
|
|
|
|
raw := []byte(p)
|
|
err := json.Unmarshal(raw, &args)
|
|
if err == nil {
|
|
return args, nil
|
|
}
|
|
|
|
// Fallback to parsing arguments in the legacy slice format
|
|
deprecated := map[string][]string{}
|
|
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
|
return args, err
|
|
}
|
|
|
|
args.fields = deprecatedArgs(deprecated)
|
|
return args, nil
|
|
}
|
|
|
|
// UnmarshalJSON populates the Args from JSON encode bytes
|
|
func (args Args) UnmarshalJSON(raw []byte) error {
|
|
if len(raw) == 0 {
|
|
return nil
|
|
}
|
|
return json.Unmarshal(raw, &args.fields)
|
|
}
|
|
|
|
// Get returns the list of values associated with the key
|
|
func (args Args) Get(key string) []string {
|
|
values := args.fields[key]
|
|
if values == nil {
|
|
return make([]string, 0)
|
|
}
|
|
slice := make([]string, 0, len(values))
|
|
for key := range values {
|
|
slice = append(slice, key)
|
|
}
|
|
return slice
|
|
}
|
|
|
|
// Add a new value to the set of values
|
|
func (args Args) Add(key, value string) {
|
|
if _, ok := args.fields[key]; ok {
|
|
args.fields[key][value] = true
|
|
} else {
|
|
args.fields[key] = map[string]bool{value: true}
|
|
}
|
|
}
|
|
|
|
// Del removes a value from the set
|
|
func (args Args) Del(key, value string) {
|
|
if _, ok := args.fields[key]; ok {
|
|
delete(args.fields[key], value)
|
|
if len(args.fields[key]) == 0 {
|
|
delete(args.fields, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Len returns the number of keys in the mapping
|
|
func (args Args) Len() int {
|
|
return len(args.fields)
|
|
}
|
|
|
|
// MatchKVList returns true if all the pairs in sources exist as key=value
|
|
// pairs in the mapping at key, or if there are no values at key.
|
|
func (args Args) MatchKVList(key string, sources map[string]string) bool {
|
|
fieldValues := args.fields[key]
|
|
|
|
//do not filter if there is no filter set or cannot determine filter
|
|
if len(fieldValues) == 0 {
|
|
return true
|
|
}
|
|
|
|
if len(sources) == 0 {
|
|
return false
|
|
}
|
|
|
|
for value := range fieldValues {
|
|
testKV := strings.SplitN(value, "=", 2)
|
|
|
|
v, ok := sources[testKV[0]]
|
|
if !ok {
|
|
return false
|
|
}
|
|
if len(testKV) == 2 && testKV[1] != v {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Match returns true if any of the values at key match the source string
|
|
func (args Args) Match(field, source string) bool {
|
|
if args.ExactMatch(field, source) {
|
|
return true
|
|
}
|
|
|
|
fieldValues := args.fields[field]
|
|
for name2match := range fieldValues {
|
|
match, err := regexp.MatchString(name2match, source)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if match {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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]
|
|
//do not filter if there is no filter set or cannot determine filter
|
|
if !ok || len(fieldValues) == 0 {
|
|
return true
|
|
}
|
|
|
|
// try to match full name value to avoid O(N) regular expression matching
|
|
return fieldValues[source]
|
|
}
|
|
|
|
// UniqueExactMatch returns true if there is only one value and the source
|
|
// matches exactly the value.
|
|
func (args Args) UniqueExactMatch(key, source string) bool {
|
|
fieldValues := args.fields[key]
|
|
//do not filter if there is no filter set or cannot determine filter
|
|
if len(fieldValues) == 0 {
|
|
return true
|
|
}
|
|
if len(args.fields[key]) != 1 {
|
|
return false
|
|
}
|
|
|
|
// try to match full name value to avoid O(N) regular expression matching
|
|
return fieldValues[source]
|
|
}
|
|
|
|
// FuzzyMatch returns true if the source matches exactly one value, or the
|
|
// source has one of the values as a prefix.
|
|
func (args Args) FuzzyMatch(key, source string) bool {
|
|
if args.ExactMatch(key, source) {
|
|
return true
|
|
}
|
|
|
|
fieldValues := args.fields[key]
|
|
for prefix := range fieldValues {
|
|
if strings.HasPrefix(source, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Include returns true if the key exists in the mapping
|
|
//
|
|
// Deprecated: use Contains
|
|
func (args Args) Include(field string) bool {
|
|
_, ok := args.fields[field]
|
|
return ok
|
|
}
|
|
|
|
// Contains returns true if the key exists in the mapping
|
|
func (args Args) Contains(field string) bool {
|
|
_, ok := args.fields[field]
|
|
return ok
|
|
}
|
|
|
|
type invalidFilter string
|
|
|
|
func (e invalidFilter) Error() string {
|
|
return "Invalid filter '" + string(e) + "'"
|
|
}
|
|
|
|
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(name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WalkValues iterates over the list of values for a key in the mapping and calls
|
|
// op() for each value. If op returns an error the iteration stops and the
|
|
// error is returned.
|
|
func (args Args) WalkValues(field string, op func(value string) error) error {
|
|
if _, ok := args.fields[field]; !ok {
|
|
return nil
|
|
}
|
|
for v := range args.fields[field] {
|
|
if err := op(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
|
|
m := map[string]map[string]bool{}
|
|
for k, v := range d {
|
|
values := map[string]bool{}
|
|
for _, vv := range v {
|
|
values[vv] = true
|
|
}
|
|
m[k] = values
|
|
}
|
|
return m
|
|
}
|
|
|
|
func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
|
|
m := map[string][]string{}
|
|
for k, v := range f {
|
|
values := []string{}
|
|
for kk := range v {
|
|
if v[kk] {
|
|
values = append(values, kk)
|
|
}
|
|
}
|
|
m[k] = values
|
|
}
|
|
return m
|
|
}
|