Skip to content

Commit

Permalink
feat: remove DirectiveRunOrder option due to performance
Browse files Browse the repository at this point in the history
  • Loading branch information
ggicci committed Apr 13, 2024
1 parent 3bcb8a9 commit fafe5db
Show file tree
Hide file tree
Showing 3 changed files with 10 additions and 104 deletions.
11 changes: 0 additions & 11 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,3 @@ 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)
}
32 changes: 10 additions & 22 deletions resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -56,7 +55,6 @@ 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 @@ -270,21 +268,23 @@ func scan(resolver *Resolver, ctx context.Context, rootValue reflect.Value) erro
return nil
}

// Resolve resolves the struct type by traversing the tree in depth-first order. Typically it is
// used to create a new struct instance by reading from some data source. This method always creates
// a new value of the type the resolver holds. And runs the directives on each field.
// Resolve resolves the struct type by traversing the tree in depth-first order.
// Typically it is used to create a new struct instance by reading from some
// data source. This method always creates a new value of the type the resolver
// holds. And runs the directives on each field.
//
// Use WithValue to create an Option that can add custom values to the context, the context can be
// used by the directive executors during the resolution. Example:
// Use WithValue to create an Option that can add custom values to the context,
// the context can be used by the directive executors during the resolution.
// Example:
//
// type Settings struct {
// DarkMode bool `owl:"env=MY_APP_DARK_MODE;cfg=appearance.dark_mode;default=false"`
// }
// resolver := owl.New(Settings{})
// settings, err := resolver.Resolve(WithValue("app_config", appConfig))
//
// NOTE: while iterating the tree, if resolving a field fails, the iteration will be
// stopped and the error will be returned.
// NOTE: while iterating the tree, if resolving a field fails, the iteration
// will be stopped and the error will be returned.
func (r *Resolver) Resolve(opts ...Option) (reflect.Value, error) {
ctx := context.Background()
for _, opt := range opts {
Expand Down Expand Up @@ -332,7 +332,7 @@ func (r *Resolver) runDirectives(ctx context.Context, rv reflect.Value) error {
ns = nsOverriden.(*Namespace)
}

for _, directive := range getSortedDirectives(ctx, r.Directives) {
for _, directive := range r.Directives {
dirRuntime := &DirectiveRuntime{
Directive: directive,
Resolver: r,
Expand Down Expand Up @@ -360,18 +360,6 @@ 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: 0 additions & 71 deletions resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"os"
"reflect"
"strings"
"testing"

"github.com/ggicci/owl"
Expand Down Expand Up @@ -849,76 +848,6 @@ 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 fafe5db

Please sign in to comment.