Skip to content

Commit

Permalink
feat(filter): query filter ex
Browse files Browse the repository at this point in the history
  • Loading branch information
morlay committed Jul 13, 2024
1 parent 47f5b1e commit 3c148b4
Show file tree
Hide file tree
Showing 22 changed files with 1,613 additions and 12 deletions.
3 changes: 3 additions & 0 deletions example/apis/org/org__list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org

import (
"context"
"github.com/octohelm/courier/pkg/filter"
"net/http"

"github.com/octohelm/courier/pkg/courierhttp"
Expand All @@ -10,6 +11,8 @@ import (
// 拉取组织列表
type ListOrg struct {
courierhttp.MethodGet `path:"/orgs"`

Name *filter.Filter[string] `name:"org~name,omitempty" in:"query"`
}

func (r *ListOrg) Output(ctx context.Context) (any, error) {
Expand Down
2 changes: 2 additions & 0 deletions example/apis/org/zz_generated.runtimedoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ func (v Info) RuntimeDoc(names ...string) ([]string, bool) {
func (v ListOrg) RuntimeDoc(names ...string) ([]string, bool) {
if len(names) > 0 {
switch names[0] {
case "Name":
return []string{}, true

}

Expand Down
7 changes: 6 additions & 1 deletion pkg/courierhttp/transport/incoming_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,13 @@ func (t *incomingTransport) decodeFromRequestInfo(ctx context.Context, info cour
}

if len(values) > 0 {
paramValue := param.FieldValue(rv)
if paramValue.Kind() != reflect.Ptr {
paramValue = paramValue.Addr()
}

err := core.Wrap(param.Transformer, &param.TransformerOption.CommonOption).
DecodeFrom(ctx, core.NewStringReaders(values), param.FieldValue(rv).Addr())
DecodeFrom(ctx, core.NewStringReaders(values), paramValue)

if err != nil {
errSet.AddErr(err, validator.Location(param.In), param.Name)
Expand Down
268 changes: 268 additions & 0 deletions pkg/filter/composed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
package filter

import (
"bytes"
"encoding/json"
"go/ast"
"reflect"
"strings"

"github.com/octohelm/courier/pkg/filter/internal/directive"
slicesx "github.com/octohelm/x/slices"
)

func Compose(filters ...any) *Composed {
c := &Composed{}

for _, filter := range filters {
rv := reflect.ValueOf(filter)

if rv.Kind() == reflect.Struct {
t := rv.Type()
if t.Kind() == reflect.Ptr {
t = t.Elem()
}

for i := 0; i < rv.NumField(); i++ {
f := t.Field(i)

if !ast.IsExported(f.Name) {
continue
}

fv := rv.Field(i)

if ruleExpr, ok := fv.Interface().(RuleExpr); ok {
name := f.Name

if tagName, ok := f.Tag.Lookup("name"); ok {
n := strings.SplitN(tagName, ",", 2)[0]
if n != "" {
name = n
}
}

c.register(name, &fieldRuler{
name: name,
tpe: t,
ruleExprIdx: i,
})

if fv.Kind() == reflect.Ptr && fv.IsNil() {
continue
}

if !ruleExpr.IsZero() {
c.rules = append(c.rules, ruleExpr.WhereOf(name))
}

break
}
}
}
}

return c
}

type fieldRuler struct {
tpe reflect.Type
name string
ruleExprIdx int
}

func (t *fieldRuler) Name() string {
return t.name
}

func (t *fieldRuler) New() *ruleWrapper {
rv := reflect.New(t.tpe)

f := rv.Elem().Field(t.ruleExprIdx)
if f.Kind() == reflect.Ptr {
f.Set(reflect.New(f.Type().Elem()))
}

return &ruleWrapper{
obj: rv.Interface(),
rule: f.Interface().(Rule),
}
}

type ruleWrapper struct {
obj any
rule Rule
}

func (r *ruleWrapper) Obj() any {
return r.obj
}

func (r *ruleWrapper) Rule() Rule {
return r.rule
}

func (r *ruleWrapper) UnmarshalDirective(dec *directive.Decoder) error {
return r.rule.UnmarshalDirective(dec)
}

type Composed struct {
Filters []any

fieldRulers map[string]*fieldRuler
rules []Arg
}

func (c *Composed) register(fieldName string, fr *fieldRuler) {
if c.fieldRulers == nil {
c.fieldRulers = map[string]*fieldRuler{}
}
c.fieldRulers[fieldName] = fr
}

func (c Composed) IsZero() bool {
return len(c.rules) == 0
}

func (c *Composed) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}

d := directive.NewDecoder(bytes.NewBuffer(data))

d.RegisterDirectiveNewer("or", func() directive.Unmarshaler {
return directive.UnmarshalerFunc(func(dec *directive.Decoder) error {
_, err := dec.DirectiveName()
if err != nil {
return err
}

for {
k, text := dec.Next()
if k == directive.EOF || k == directive.KindFuncEnd {
break
}

switch k {
case directive.KindFuncStart:
sub, err := dec.Unmarshaler(string(text))
if err != nil {
return err
}
if err := sub.UnmarshalDirective(dec); err != nil {
return err
}
if arg, ok := sub.(Arg); ok {
c.rules = append(c.rules, arg)
}
default:

}
}

return nil
})
})

d.RegisterDirectiveNewer("where", func() directive.Unmarshaler {
return directive.UnmarshalerFunc(func(dec *directive.Decoder) error {
_, err := dec.DirectiveName()
if err != nil {
return err
}

argIdx := 0
var fr *fieldRuler

for {
k, text := dec.Next()
if k == directive.EOF || k == directive.KindFuncEnd {
break
}

switch k {
case directive.KindValue:
if argIdx == 0 {
name := ""
if err := json.Unmarshal(text, &name); err != nil {
return err
}

n, ok := c.fieldRulers[name]
if ok {
fr = n
continue
}

return &ErrUnsupportedQLField{
FieldName: name,
}
}
argIdx++
case directive.KindFuncStart:
if fr == nil {
return &directive.ErrInvalidDirective{}
}

wrapper := fr.New()

if err := wrapper.UnmarshalDirective(dec); err != nil {
return err
}

if ruleExpr, ok := wrapper.rule.(RuleExpr); ok {
c.rules = append(c.rules, ruleExpr.WhereOf(fr.Name()))
}

c.Filters = append(c.Filters, wrapper.obj)

argIdx++
default:

}
}

return nil
})
})

_, err := d.DirectiveName()
if err != nil {
return err
}

for {
k, text := d.Next()
if k == directive.EOF || k == directive.KindFuncEnd {
break
}

switch k {
case directive.KindFuncStart:
sub, err := d.Unmarshaler(string(text))
if err != nil {
return err
}
if err := sub.UnmarshalDirective(d); err != nil {
return err
}
default:

}
}

return nil
}

func (c Composed) MarshalText() ([]byte, error) {
switch len(c.rules) {
case 0:
return nil, nil
case 1:
return c.rules[0].MarshalText()
}
return directive.MarshalDirective("or", slicesx.Map(c.rules, func(e Arg) any {
return e
})...)
}
37 changes: 37 additions & 0 deletions pkg/filter/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package filter

import (
"fmt"

"github.com/octohelm/courier/pkg/statuserror"
)

type ErrInvalidFilterOp struct {
statuserror.BadRequest

Op string
}

func (e *ErrInvalidFilterOp) Error() string {
return fmt.Sprintf("invalid filter op `%s`", e.Op)
}

type ErrInvalidFilter struct {
statuserror.BadRequest

Filter string
}

func (e *ErrInvalidFilter) Error() string {
return fmt.Sprintf("invalid filter `%s`", e.Filter)
}

type ErrUnsupportedQLField struct {
statuserror.BadRequest

FieldName string
}

func (e *ErrUnsupportedQLField) Error() string {
return fmt.Sprintf("unsupported ql field `%s`", e.FieldName)
}
Loading

0 comments on commit 3c148b4

Please sign in to comment.