added match logic and query error checking

This commit is contained in:
ston1th 2017-07-09 15:09:39 +02:00
commit 5a8e1e6cf6
4 changed files with 315 additions and 70 deletions

184
filter.go
View file

@ -4,6 +4,7 @@
package filter
import (
"errors"
"reflect"
"strconv"
"strings"
@ -22,10 +23,21 @@ type match struct {
value string
}
// Query implements the filter query
type Query []match
type Logic int
// NewQuery parses the filter query.
const (
AND Logic = iota
OR
NOT
)
// Query implements the filter query
type Query struct {
m []match
l Logic
}
// NewQuery parses the filter query and match logic
// Examples:
// Explicit match: <struct field>:<struct value>
// Wildcard match:
@ -38,34 +50,79 @@ type Query []match
// <struct field>:<struct value> <struct field>:<struct value>*
// Supported data types are: string, int, bool
// Bool does not support the wildcard operator
func NewQuery(s string) (q Query) {
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 {
m := strings.Split(v, ":")
if len(m) == 2 {
q = append(q, match{strings.Split(m[0], "."), m[1]})
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) (ok bool) {
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
}
if len(q) == 0 {
ql := len(q.m)
if ql == 0 {
return
}
ok = true
for _, qv := range q {
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 i := 1; i < fl; i++ {
val = val.FieldByName(qv.fields[i])
for j := 1; j < fl; j++ {
val = val.FieldByName(qv.fields[j])
}
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16,
@ -74,50 +131,121 @@ func (q Query) match(v reflect.Value) (ok bool) {
case reflect.String:
s = val.String()
case reflect.Bool:
if val.Bool() {
s = "true"
} else {
s = "false"
if vl == 0 {
continue
}
if s != qv.value {
ok = false
b := val.Bool()
if b && qv.value[0] == 't' {
oks[i] = true
continue
}
if !b && qv.value[0] == 'f' {
oks[i] = true
}
continue
default:
ok = false
continue
}
vl := len(qv.value)
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:
if qv.value[0] == any {
switch {
case qv.value[0] == any:
if strings.HasSuffix(s, qv.value[1:]) {
oks[i] = true
continue
}
}
if qv.value[vl-1] == any {
case qv.value[vl-1] == any:
if strings.HasPrefix(s, qv.value[:vl-1]) {
oks[i] = true
continue
}
}
}
if s != qv.value {
ok = false
if s == qv.value {
oks[i] = true
}
}
return
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{} {
func (q *Query) GenericFilter(i interface{}) interface{} {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Slice {
return nil