// Copyright (C) 2017 Marius Schellenberger // Package filter implements simple struct field filtering package filter import ( "errors" "reflect" "strconv" "strings" ) const any = '*' // Filter is the filter interface to be implemented for faster non generic filtering // Example implementation: // type f []struct // func (v f) Len() int { return len(v) } // func (v f) Index(i int) interface{} { return v[i] } // func (v *f) Remove(i, j int) { *v = append((*v)[:i], (*v)[j:]...) } type Filter interface { Len() int Index(int) interface{} Remove(int, int) } type match struct { fields []string value string } // Logic is the filter logic type Logic int const ( // AND all of the queries have to match AND Logic = iota // OR only one query has to match OR // NOT none of the queries have to match NOT ) // Query is the filter query type Query struct { m []match l Logic } // NewQuery parses the filter query and match logic // Examples: // Explicit match: : // Wildcard match: // :* // :* // :** // Nested structs: // .: // Multiple matches: // : :* // Supported data types are: string, int, bool // Bool does not support the wildcard operator func NewQuery(s string, l Logic) (q *Query, err error) { if l > 2 || l < 0 { l = AND } q = &Query{l: l} qm := make(map[string]struct{}) fields := strings.Split(s, " ") for _, v := range fields { if v == "" { continue } m := strings.SplitN(v, ":", 2) if len(m) != 2 { err = errors.New("invalid match: " + v) return } if l == AND { if _, ok := qm[m[0]]; ok { err = errors.New("field for AND matching used more than once: " + m[0]) return } qm[m[0]] = struct{}{} } q.m = append(q.m, match{strings.Split(m[0], "."), m[1]}) } return } func (q *Query) check(oks []bool) (b bool) { b = true switch q.l { case AND: for _, ok := range oks { if !ok { b = false } } case NOT: for _, ok := range oks { if ok { b = false } } } return } func (q *Query) match(v reflect.Value) bool { if q.l == OR { return q.orMatch(v) } return q.allMatch(v) } func (q *Query) allMatch(v reflect.Value) (ok bool) { if v.Kind() != reflect.Struct { return } ql := len(q.m) if ql == 0 { return } oks := make([]bool, ql) for i, qv := range q.m { s := "" fl := len(qv.fields) vl := len(qv.value) if fl == 0 { continue } val := v.FieldByName(qv.fields[0]) for j := 1; j < fl; j++ { val = val.FieldByName(qv.fields[j]) } switch val.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: s = strconv.Itoa(int(val.Int())) case reflect.String: s = val.String() case reflect.Bool: if vl == 0 { continue } b := val.Bool() if b && qv.value[0] == 't' { oks[i] = true continue } if !b && qv.value[0] == 'f' { oks[i] = true } continue default: continue } switch { case vl >= 3: if qv.value[0] == any && qv.value[vl-1] == any { if strings.Contains(s, qv.value[1:vl-1]) { oks[i] = true continue } } fallthrough case vl >= 2: switch { case qv.value[0] == any: if strings.HasSuffix(s, qv.value[1:]) { oks[i] = true continue } case qv.value[vl-1] == any: if strings.HasPrefix(s, qv.value[:vl-1]) { oks[i] = true continue } } } if s == qv.value { oks[i] = true } } return q.check(oks) } func (q *Query) orMatch(v reflect.Value) bool { if v.Kind() != reflect.Struct { return false } ql := len(q.m) if ql == 0 { return false } for _, qv := range q.m { s := "" fl := len(qv.fields) vl := len(qv.value) if fl == 0 { continue } val := v.FieldByName(qv.fields[0]) for j := 1; j < fl; j++ { val = val.FieldByName(qv.fields[j]) } switch val.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: s = strconv.Itoa(int(val.Int())) case reflect.String: s = val.String() case reflect.Bool: if vl == 0 { continue } b := val.Bool() if b && qv.value[0] == 't' { return true } if !b && qv.value[0] == 'f' { return true } continue default: continue } switch { case vl >= 3: if qv.value[0] == any && qv.value[vl-1] == any { if strings.Contains(s, qv.value[1:vl-1]) { return true } } fallthrough case vl >= 2: switch { case qv.value[0] == any: if strings.HasSuffix(s, qv.value[1:]) { return true } case qv.value[vl-1] == any: if strings.HasPrefix(s, qv.value[:vl-1]) { return true } } } if s == qv.value { return true } } return false } // GenericFilter takes any slice of structs value and returns the same slice type containing only the filter matches. // Returns nil if interface{} is not a slice. func (q *Query) GenericFilter(i interface{}) interface{} { v := reflect.ValueOf(i) if v.Kind() != reflect.Slice { return nil } ret := reflect.MakeSlice(v.Type(), 0, 0) for i := 0; i < v.Len(); i++ { val := v.Index(i) if q.match(val) { ret = reflect.Append(ret, val) } } return ret.Interface() } // Filter takes a pointer to the Filter interface as an argument. // The original slice is modified is the filtering process. func (q Query) Filter(f Filter) { count := 0 i := 0 for ; i < f.Len(); i++ { if q.match(reflect.ValueOf(f.Index(i))) { if count > 0 { f.Remove(i-count, i) i -= count } count = 0 } else { count++ } } if count > 0 { f.Remove(i-count, i) } }