Initial commit
This commit is contained in:
307
filter.go
Normal file
307
filter.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package mgoapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.basehosts.de/gopackage/mgocrud"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
func structFieldFromJSON(t reflect.Type, fieldname string) (sfield string, err error) {
|
||||
if t.Kind() == reflect.Slice {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return "", fmt.Errorf("cannot find field from json name %s, %+v is no struct", fieldname, t)
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Anonymous {
|
||||
// embedded struct
|
||||
embedSField, _ := structFieldFromJSON(f.Type, fieldname)
|
||||
if embedSField != "" {
|
||||
return embedSField, nil
|
||||
}
|
||||
}
|
||||
jsonTag := strings.Split(f.Tag.Get("json"), ",")
|
||||
if jsonTag[0] == "-" {
|
||||
continue
|
||||
}
|
||||
if jsonTag[0] == fieldname {
|
||||
return f.Name, nil
|
||||
}
|
||||
if jsonTag[0] == "" && f.Name == fieldname {
|
||||
return f.Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("json field %s not found in %+v", fieldname, t)
|
||||
}
|
||||
|
||||
func bsonFieldFromStruct(t reflect.Type, fieldname string) (bfield string, err error) {
|
||||
if t.Kind() == reflect.Slice {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return "", fmt.Errorf("cannot find struct field from bson name %s, %+v is no struct", fieldname, t)
|
||||
}
|
||||
|
||||
if f, found := t.FieldByName(fieldname); found {
|
||||
bsonTag := strings.Split(f.Tag.Get("bson"), ",")
|
||||
if bsonTag[0] == "-" {
|
||||
return "", fmt.Errorf("struct field %s from %+v is no bson field", fieldname, t)
|
||||
}
|
||||
if bsonTag[0] != "" {
|
||||
return bsonTag[0], nil
|
||||
}
|
||||
return strings.ToLower(fieldname), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("struct field %s not found in %+v", fieldname, t)
|
||||
}
|
||||
|
||||
func typeOfStructFieldByName(t reflect.Type, fieldname string) (reflect.Type, error) {
|
||||
if t.Kind() == reflect.Slice {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("cannot get field type of %s, %+v is no struct", fieldname, t)
|
||||
}
|
||||
|
||||
if f, found := t.FieldByName(fieldname); found {
|
||||
fT := f.Type
|
||||
if fT.Kind() == reflect.Ptr {
|
||||
return fT.Elem(), nil
|
||||
}
|
||||
return fT, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("struct field %s not found in %+v", fieldname, t)
|
||||
}
|
||||
|
||||
func _checkValue(fieldT reflect.Type, val interface{}) (newVal interface{}, err error) {
|
||||
fieldTE := fieldT
|
||||
if fieldTE.Kind() == reflect.Ptr || fieldTE.Kind() == reflect.Slice {
|
||||
fieldTE = fieldTE.Elem()
|
||||
}
|
||||
if fieldTE.Kind() == reflect.Ptr {
|
||||
fieldTE = fieldTE.Elem()
|
||||
}
|
||||
switch value := val.(type) {
|
||||
|
||||
case string:
|
||||
switch fieldTE {
|
||||
case reflect.TypeOf(bson.ObjectId("")): // struct field is objectid
|
||||
newVal = bson.ObjectIdHex(value)
|
||||
case reflect.TypeOf(time.Now()): // struct field is time
|
||||
newVal, err = time.Parse(time.RFC3339, value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("date string from filter is not compatible with type %s (date must be RFC3339)", fieldT)
|
||||
}
|
||||
}
|
||||
case float64:
|
||||
switch fieldTE {
|
||||
case reflect.TypeOf(int(0)): // json is always float64 for numbers
|
||||
newVal = int(value)
|
||||
}
|
||||
case bson.RegEx:
|
||||
switch fieldTE {
|
||||
case reflect.TypeOf(""):
|
||||
// leave as is
|
||||
newVal = value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if newVal == nil {
|
||||
valT := reflect.TypeOf(val)
|
||||
if fieldTE == valT {
|
||||
newVal = val
|
||||
} else if valT.Kind() == reflect.Map {
|
||||
// go into sub object and validate
|
||||
newVal, err = _validateFilter(fieldTE, val.(map[string]interface{}), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("type %s from filter is not compatible with type %s", valT, fieldT)
|
||||
}
|
||||
}
|
||||
|
||||
return newVal, err
|
||||
}
|
||||
|
||||
func _validateFilter(structT reflect.Type, f bson.M, checkValues bool) (bson.M, error) {
|
||||
newF := make(bson.M)
|
||||
for key, val := range f {
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("empty key in filter for model %+v", structT)
|
||||
}
|
||||
newKey := ""
|
||||
var newVal interface{}
|
||||
|
||||
if key == "$text" {
|
||||
// dont modify or check search
|
||||
newKey = key
|
||||
newVal = val
|
||||
} else if strings.HasPrefix(key, "$") {
|
||||
// is filter function and no fieldname
|
||||
newKey = key
|
||||
valT := reflect.TypeOf(val)
|
||||
if valT.Kind() == reflect.Slice {
|
||||
l := len(val.([]interface{}))
|
||||
newVal = make([]interface{}, l, l)
|
||||
for i := 0; i < l; i++ {
|
||||
var err error
|
||||
switch valF := val.([]interface{})[i].(type) {
|
||||
case map[string]interface{}:
|
||||
// array element is object
|
||||
newVal.([]interface{})[i], err = _validateFilter(structT, valF, checkValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
// array element is value
|
||||
newVal.([]interface{})[i], err = _checkValue(structT, valF)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s from %+v for field %s", err.Error(), structT, newKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
switch valF := val.(type) {
|
||||
case map[string]interface{}:
|
||||
newVal, err = _validateFilter(structT, valF, checkValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
newVal, err = _checkValue(structT, val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s from %+v for field %s", err.Error(), structT, newKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
keys := strings.Split(key, ".")
|
||||
|
||||
var _getBSONField func(k []string, t reflect.Type) error
|
||||
_getBSONField = func(k []string, t reflect.Type) error {
|
||||
structField, err := structFieldFromJSON(t, k[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bsonField, err := bsonFieldFromStruct(t, structField)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newKey += bsonField
|
||||
|
||||
fieldT, err := typeOfStructFieldByName(t, structField)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(k) > 1 {
|
||||
newKey += "."
|
||||
return _getBSONField(k[1:], fieldT)
|
||||
}
|
||||
|
||||
// last element -> check value
|
||||
if checkValues {
|
||||
newVal, err = _checkValue(fieldT, val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s from %+v for field %s", err.Error(), t, newKey)
|
||||
}
|
||||
} else {
|
||||
newVal = val
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := _getBSONField(keys, structT)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
newF[newKey] = newVal
|
||||
|
||||
// spew.Dump(key)
|
||||
// spew.Dump(val)
|
||||
// spew.Dump(newKey)
|
||||
}
|
||||
|
||||
return newF, nil
|
||||
}
|
||||
|
||||
func validateFilter(c *Context, m mgocrud.ModelInterface, filter bson.M) (newFilter bson.M, err error) {
|
||||
// spew.Dump(filter)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("filter validation error: %s", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if filter != nil {
|
||||
newFilter, err = _validateFilter(reflect.TypeOf(m), filter, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i, ok := m.(interface {
|
||||
ValidateFilter(*Context, bson.M) (bson.M, error)
|
||||
}); ok {
|
||||
// custom filter manipulation
|
||||
newFilter, err = i.ValidateFilter(c, newFilter)
|
||||
}
|
||||
|
||||
// spew.Dump(newFilter)
|
||||
|
||||
return newFilter, err
|
||||
}
|
||||
|
||||
func validateSelect(c *Context, m mgocrud.ModelInterface, selector bson.M) (newSelector bson.M, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("select validation error: %s", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if selector != nil {
|
||||
newSelector, err = _validateFilter(reflect.TypeOf(m), selector, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i, ok := m.(interface {
|
||||
ValidateSelect(*Context, bson.M) bson.M
|
||||
}); ok {
|
||||
// custom select manipulation
|
||||
newSelector = i.ValidateSelect(c, newSelector)
|
||||
}
|
||||
|
||||
return newSelector, nil
|
||||
}
|
||||
Reference in New Issue
Block a user