feat: 实现了一个基本的文档过滤器
- 添加了 Document、Array 和 Object 接口的实现 - 实现了 DocumentOperator、ValueOperator 和 FieldOperator 函数 - 添加了 operatorGt 函数作为示例操作符 - 编写了相关测试用例以验证功能正确性
This commit is contained in:
parent
6aff4d7f48
commit
93fd7363a3
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
filter:
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "case",
|
||||||
|
"field":{
|
||||||
|
"$gt": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
And Document d={}
|
||||||
|
|
||||||
|
首先使用文档operator处理
|
||||||
|
|
||||||
|
documentOp(d, filter,nil,nil)
|
||||||
|
|
||||||
|
然后是值operator处理,匹配name
|
||||||
|
|
||||||
|
valueOp(d, nil, key,value)
|
||||||
|
|
||||||
|
字段op处理(d, {$gt:0}, key="field",nil)
|
|
@ -0,0 +1,148 @@
|
||||||
|
package coperator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isImplementObject(v interface{}) (Object, bool) {
|
||||||
|
switch a := v.(type) {
|
||||||
|
case Document:
|
||||||
|
return &a, true
|
||||||
|
case Array:
|
||||||
|
return &a, true
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Object interface {
|
||||||
|
Get(path string) interface{}
|
||||||
|
GetPathArray(path []string) interface{}
|
||||||
|
}
|
||||||
|
type Array []interface{}
|
||||||
|
|
||||||
|
func (a *Array) Get(path string) interface{} {
|
||||||
|
paths := strings.Split(path, ".")
|
||||||
|
return a.GetPathArray(paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Array) GetPathArray(path []string) interface{} {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return *a
|
||||||
|
}
|
||||||
|
index, err := strconv.Atoi(path[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(path) > 1 {
|
||||||
|
remaining := path[1:]
|
||||||
|
v := (*a)[index]
|
||||||
|
if pv, ok := isImplementObject(v); ok {
|
||||||
|
return pv.GetPathArray(remaining)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (*a)[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Document map[string]interface{}
|
||||||
|
|
||||||
|
func (d *Document) Get(path string) interface{} {
|
||||||
|
paths := strings.Split(path, ".")
|
||||||
|
return d.GetPathArray(paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Document) GetPathArray(path []string) interface{} {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return *d
|
||||||
|
}
|
||||||
|
if len(path) == 1 {
|
||||||
|
return (*d)[path[0]]
|
||||||
|
}
|
||||||
|
xv, xok := (*d)[path[0]]
|
||||||
|
if !xok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if v, ok := isImplementObject(xv); ok {
|
||||||
|
return v.GetPathArray(path[1:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Collection []Document
|
||||||
|
type Filter map[string]interface{}
|
||||||
|
|
||||||
|
type Operator func(doc *Document, filter Filter, key string, value interface{}) (bool, error)
|
||||||
|
|
||||||
|
var operatorMap = map[string]Operator{}
|
||||||
|
|
||||||
|
func DocumentOperator(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
|
||||||
|
|
||||||
|
for k, v := range filter {
|
||||||
|
|
||||||
|
if strings.HasPrefix(k, "$") {
|
||||||
|
// top level operator, commonly be $and,$or
|
||||||
|
opFn, ok := operatorMap[k]
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("operator not supported")
|
||||||
|
}
|
||||||
|
ret, err = opFn(doc, v.(Filter), "", nil)
|
||||||
|
if !ret {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if f, ok := v.(Filter); ok {
|
||||||
|
ret, err = FieldOperator(doc, f, k, nil)
|
||||||
|
if !ret {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret, err = ValueOperator(doc, filter, k, v)
|
||||||
|
if !ret {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValueOperator(doc *Document, filter Filter, key string, value interface{}) (bool, error) {
|
||||||
|
if doc.Get(key) != value {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FieldOperator(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
|
||||||
|
for k, v := range filter {
|
||||||
|
if !strings.HasPrefix(k, "$") {
|
||||||
|
return false, errors.New("uncorrect grammar")
|
||||||
|
}
|
||||||
|
opFn, ok := operatorMap[k]
|
||||||
|
if !ok {
|
||||||
|
return false, errors.New("unsupport operator")
|
||||||
|
}
|
||||||
|
if fv, ok := v.(Filter); ok {
|
||||||
|
ret, err = opFn(doc, fv, key, nil)
|
||||||
|
if !ret {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret, err = opFn(doc, nil, key, v)
|
||||||
|
if !ret {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func operatorGt(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
package coperator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArray_GetPathArray(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
array Array
|
||||||
|
path []string
|
||||||
|
want interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get first element",
|
||||||
|
array: Array{1, 2, 3},
|
||||||
|
path: []string{"0"},
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get nested array value",
|
||||||
|
array: Array{Array{4, 5}},
|
||||||
|
path: []string{"0", "1"},
|
||||||
|
want: 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := tt.array.GetPathArray(tt.path)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Array.GetPathArray() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocument_GetPathArray(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
doc Document
|
||||||
|
path []string
|
||||||
|
want interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get top level field",
|
||||||
|
doc: Document{
|
||||||
|
"name": "test",
|
||||||
|
},
|
||||||
|
path: []string{"name"},
|
||||||
|
want: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get nested field",
|
||||||
|
doc: Document{
|
||||||
|
"user": Document{
|
||||||
|
"id": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: []string{"user", "id"},
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := tt.doc.GetPathArray(tt.path)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Document.GetPathArray() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueOperator(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
doc Document
|
||||||
|
path []string
|
||||||
|
value interface{}
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get top level field",
|
||||||
|
doc: Document{
|
||||||
|
"name": "test",
|
||||||
|
},
|
||||||
|
path: []string{"name"},
|
||||||
|
value: "test",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get nested field",
|
||||||
|
doc: Document{
|
||||||
|
"user": Document{
|
||||||
|
"id": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
path: []string{"user", "id"},
|
||||||
|
value: 1,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, _ := ValueOperator(&tt.doc, Filter{}, strings.Join(tt.path, "."), tt.value)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Document.GetPathArray() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocumentOperator(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
doc Document
|
||||||
|
filter Filter
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
doc: Document{
|
||||||
|
"user": Document{
|
||||||
|
"id": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: Filter{
|
||||||
|
"user.id": 1,
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, _ := DocumentOperator(&tt.doc, tt.filter, "", nil)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Document.GetPathArray() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue