Skip to content

Commit

Permalink
feat: allow sorting directives WithDirectiveRunOrder
Browse files Browse the repository at this point in the history
  • Loading branch information
ggicci committed Nov 13, 2023
1 parent 21dcd8f commit 8fffb3d
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 1 deletion.
1 change: 1 addition & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ type contextKey int
const (
ckNamespace contextKey = iota
ckResolveNestedDirectives
ckDirectiveRunOrder
)
11 changes: 11 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,14 @@ func WithValue(key, value interface{}) Option {
func WithNestedDirectivesEnabled(resolve bool) Option {
return WithValue(ckResolveNestedDirectives, resolve)
}

type DirectiveRunOrder func(*Directive, *Directive) bool

// WithDirectiveRunOrder sets the order of execution for directives.
//
// When used in New, the directives will be sorted at the tree building stage.
//
// When Used in Resolve or Scan, a copy of the directives will be sorted and used.
func WithDirectiveRunOrder(runOrder DirectiveRunOrder) Option {
return WithValue(ckDirectiveRunOrder, runOrder)
}
16 changes: 15 additions & 1 deletion resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -55,6 +56,7 @@ func New(structValue interface{}, opts ...Option) (*Resolver, error) {
// Apply the context to each resolver.
tree.Iterate(func(r *Resolver) error {
r.Context = ctx
r.Directives = getSortedDirectives(ctx, r.Directives)
return nil
})

Expand Down Expand Up @@ -317,7 +319,7 @@ func (r *Resolver) runDirectives(ctx context.Context, rv reflect.Value) error {
ns = nsOverriden.(*Namespace)
}

for _, directive := range r.Directives {
for _, directive := range getSortedDirectives(ctx, r.Directives) {
dirRuntime := &DirectiveRuntime{
Directive: directive,
Resolver: r,
Expand Down Expand Up @@ -345,6 +347,18 @@ func (r *Resolver) runDirectives(ctx context.Context, rv reflect.Value) error {
return nil
}

func getSortedDirectives(ctx context.Context, directives []*Directive) []*Directive {
if directiveRunOrder := ctx.Value(ckDirectiveRunOrder); directiveRunOrder != nil {
var directivesCopy []*Directive
directivesCopy = append(directivesCopy, directives...)
sort.SliceStable(directivesCopy, func(i, j int) bool {
return directiveRunOrder.(DirectiveRunOrder)(directivesCopy[i], directivesCopy[j])
})
return directivesCopy
}
return directives // the original one
}

func (r *Resolver) DebugLayoutText(depth int) string {
var sb strings.Builder
sb.WriteString(r.String())
Expand Down
71 changes: 71 additions & 0 deletions resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"os"
"reflect"
"strings"
"testing"

"github.com/ggicci/owl"
Expand Down Expand Up @@ -840,6 +841,76 @@ func TestWithNestedDirectivesEnabled_definitionOfNestedDirectives(t *testing.T)
}, tracker.Executed.ExecutedDirectives(), "tell the difference between nested and non-nested directives")
}

func TestWithDirectiveRunOrder_buildtime(t *testing.T) {
type Record struct {
R1 string `owl:"DOTA=2;csgo=1"`
R2 string `owl:"apple=green;pear;Grape=purple"`
}

tree, err := owl.New(Record{}, owl.WithDirectiveRunOrder(func(d1, d2 *owl.Directive) bool {
return strings.ToLower(d1.Name) < strings.ToLower(d2.Name) // sort directives by name (alphabetical order)
}))

assert.NoError(t, err)
assert.NotNil(t, tree)
suite.Run(t, NewBuildResolverTreeTestSuite(
tree,
[]*expectedResolver{
{
Index: []int{0},
LookupPath: "R1",
NumFields: 0,
Directives: []*owl.Directive{
owl.NewDirective("csgo", "1"),
owl.NewDirective("DOTA", "2"),
},
Leaf: true,
},
{
Index: []int{1},
LookupPath: "R2",
NumFields: 0,
Directives: []*owl.Directive{
owl.NewDirective("apple", "green"),
owl.NewDirective("Grape", "purple"),
owl.NewDirective("pear"),
},
Leaf: true,
},
},
))
}

func TestWithDirectiveRunOrder_runtime(t *testing.T) {
ns, tracker := createNsForTracking()
resolver, err := owl.New(User{}, owl.WithNamespace(ns))
assert.NoError(t, err)

form := &User{
Name: "Ggicci",
Gender: "male",
Birthday: "1991-11-10",
}

err = resolver.Scan(form, owl.WithDirectiveRunOrder(func(d1, d2 *owl.Directive) bool {
return d1.Name == "default" // makes default directive run first
}))
assert.NoError(t, err)

expected := ExecutedDataList{
{owl.NewDirective("form", "name"), "Ggicci"},

// The order of directives below is different from the order in the struct.
// Because we set the directive run order with WithDirectiveRunOrder when calling Scan.
// Now the default directive will run first, then the form directive.
{owl.NewDirective("default", "unknown"), "male"},
{owl.NewDirective("form", "gender"), "male"},

{owl.NewDirective("form", "birthday"), "1991-11-10"},
}
assert.Equal(t, expected, tracker.Executed)
}

func TestTreeDebugLayout(t *testing.T) {
var (
tree *owl.Resolver
Expand Down

0 comments on commit 8fffb3d

Please sign in to comment.