feat: 实现了一个基本的文档过滤器

- 添加了 Document、Array 和 Object 接口的实现
- 实现了 DocumentOperator、ValueOperator 和 FieldOperator 函数
- 添加了 operatorGt 函数作为示例操作符
- 编写了相关测试用例以验证功能正确性
This commit is contained in:
程广 2025-06-19 17:56:05 +08:00
parent 6aff4d7f48
commit 93fd7363a3
4 changed files with 317 additions and 0 deletions

21
case.md Normal file
View File

@ -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)

148
coperator.go Normal file
View File

@ -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
}

145
coperator_test.go Normal file
View File

@ -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)
}
})
}
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.pyer.club/kingecg/coperator
go 1.23.1