Skip to content

Instantly share code, notes, and snippets.

@michaljemala
Created October 9, 2018 09:27
Show Gist options
  • Save michaljemala/ee40726ef3c0045557dad7b2e2c58985 to your computer and use it in GitHub Desktop.
Save michaljemala/ee40726ef3c0045557dad7b2e2c58985 to your computer and use it in GitHub Desktop.
Mongo-like expressions
package graphql
import (
"fmt"
"strings"
"github.com/aintu/api-go-poc/orm"
)
const (
and = "$and"
or = "$or"
equal = "$eq"
notEqual = "$neq"
greaterThan = "$gt"
greaterOrEqual = "$gte"
lowerThan = "$lt"
lowerOrEqual = "$lte"
in = "$in"
notIn = "$nin"
isNull = "$null"
isNotNull = "$nnull"
textInfix = "$textInfix"
textPrefix = "$textPrefix"
textSuffix = "$textSuffix"
textIInfix = "$textIInfix"
textIPrefix = "$textIPrefix"
textISuffix = "$textISuffix"
)
func ConvertExpression(exp map[string]interface{}) (orm.Condition, error) {
return convert(exp)
}
func convert(exp map[string]interface{}) (orm.Condition, error) {
var conds []orm.Condition
for key, val := range exp {
switch key {
case and:
c, err := convertAnd(val)
if err != nil {
return nil, err
}
conds = append(conds, c)
case or:
c, err := convertOr(val)
if err != nil {
return nil, err
}
conds = append(conds, c)
default:
if strings.HasPrefix(key, "$") {
return nil, fmt.Errorf("field %q cannot start with \"$\" sign", key)
}
c, err := convertField(key, val)
if err != nil {
return nil, err
}
conds = append(conds, c)
}
}
if len(conds) == 1 {
return conds[0], nil
}
return orm.And(conds...), nil
}
func convertAnd(i interface{}) (orm.Condition, error) {
exps, ok := i.([]interface{})
if !ok {
return nil, fmt.Errorf("operator %q expects an array of expressions", and)
}
if len(exps) < 2 {
return nil, fmt.Errorf("operator %q must have at least two expressions", and)
}
var conds []orm.Condition
for _, e := range exps {
subexp, ok := e.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid expresion: %#v", e)
}
condition, err := convert(subexp)
if err != nil {
return nil, err
}
conds = append(conds, condition)
}
return orm.And(conds...), nil
}
func convertOr(i interface{}) (orm.Condition, error) {
exps, ok := i.([]interface{})
if !ok {
return nil, fmt.Errorf("operator %q expects an array of expressions", or)
}
if len(exps) < 2 {
return nil, fmt.Errorf("operator %q must have at least two expressions", or)
}
var conds []orm.Condition
for _, e := range exps {
subexp, ok := e.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("invalid expresion: %#v", e)
}
condition, err := convert(subexp)
if err != nil {
return nil, err
}
conds = append(conds, condition)
}
return orm.Or(conds...), nil
}
func convertField(key string, i interface{}) (orm.Condition, error) {
exp, ok := i.(map[string]interface{})
if !ok {
return orm.NewPredicate(key, orm.Equal, i), nil
}
if len(exp) != 1 {
return nil, fmt.Errorf("invalid expresion: %#v", exp)
}
for op, val := range exp {
switch op {
case equal:
return orm.NewPredicate(key, orm.Equal, val), nil
case notEqual:
return orm.NewPredicate(key, orm.NotEqual, val), nil
case greaterThan:
return orm.NewPredicate(key, orm.GreaterThan, val), nil
case greaterOrEqual:
return orm.NewPredicate(key, orm.GreaterOrEqual, val), nil
case lowerThan:
return orm.NewPredicate(key, orm.LessThan, val), nil
case lowerOrEqual:
return orm.NewPredicate(key, orm.LessOrEqual, val), nil
case in:
return convertIn(key, val)
case notIn:
c, err := convertIn(key, val)
if err != nil {
return nil, err
}
return orm.Not(c), nil
case isNull:
return orm.NewPredicate(key, orm.IsNull), nil
case isNotNull:
return orm.Not(orm.NewPredicate(key, orm.IsNull)), nil
case textInfix:
return orm.NewPredicate(key, orm.Infix, val), nil
case textSuffix:
return orm.NewPredicate(key, orm.Suffix, val), nil
case textPrefix:
return orm.NewPredicate(key, orm.Prefix, val), nil
case textIInfix:
return orm.NewPredicate(key, orm.IInfix, val), nil
case textISuffix:
return orm.NewPredicate(key, orm.ISuffix, val), nil
case textIPrefix:
return orm.NewPredicate(key, orm.IPrefix, val), nil
}
}
return nil, fmt.Errorf("invalid expression: %#v", exp)
}
func convertIn(key string, i interface{}) (orm.Condition, error) {
vals, ok := i.([]interface{})
if !ok {
return nil, fmt.Errorf("operator %q expects an array of expressions", in)
}
if len(vals) == 0 {
return nil, fmt.Errorf("operato %q must have at least one value", in)
}
return orm.NewPredicate(key, orm.In, vals...), nil
}
package graphql
import (
"fmt"
"reflect"
"testing"
"github.com/aintu/api-go-poc/orm"
)
func TestConvertExpression(t *testing.T) {
for i, data := range []struct {
s string
in map[string]interface{}
want orm.Condition
hasErr bool
}{
{
in: map[string]interface{}{
"key1": 10,
"key2": 20,
},
want: orm.And(
orm.NewPredicate("key1", orm.Equal, 10),
orm.NewPredicate("key2", orm.Equal, 20),
),
hasErr: false,
},
{
in: map[string]interface{}{
"key1": 10,
"key2": map[string]interface{}{
"$eq": 20,
},
},
want: orm.And(
orm.NewPredicate("key1", orm.Equal, 10),
orm.NewPredicate("key2", orm.Equal, 20),
),
hasErr: false,
},
{
in: map[string]interface{}{
"$and": []interface{}{
map[string]interface{}{"key1": 10},
map[string]interface{}{"key2": 20},
},
},
want: orm.And(
orm.NewPredicate("key1", orm.Equal, 10),
orm.NewPredicate("key2", orm.Equal, 20),
),
hasErr: false,
},
{
in: map[string]interface{}{
"$or": []interface{}{
map[string]interface{}{
"key1": 10,
"key2": map[string]interface{}{
"$in": []interface{}{"alpha", "beta", "gamma"},
},
},
map[string]interface{}{
"key3": map[string]interface{}{
"$gt": 0,
},
},
},
},
want: orm.Or(
orm.And(
orm.NewPredicate("key1", orm.Equal, 10),
orm.NewPredicate("key2", orm.In, "alpha", "beta", "gamma"),
),
orm.NewPredicate("key3", orm.GreaterThan, 0),
),
hasErr: false,
},
{
in: map[string]interface{}{
"$eq": 10,
},
hasErr: true,
},
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
has, err := ConvertExpression(data.in)
if err != nil && !data.hasErr {
t.Error(err)
return
}
if !reflect.DeepEqual(data.want, has) {
t.Errorf("want %#v, has %#v", data.want, has)
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment