Skip to content

Commit c6fc6b5

Browse files
committed
feat: reuse the fields created by struct initialization
Don't always create new fields for each call of resolve, instead pass the pointer of the fields to the subsequent calls.
1 parent 00c9ca2 commit c6fc6b5

File tree

4 files changed

+125
-20
lines changed

4 files changed

+125
-20
lines changed

helper_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ func exeEnvReader(rtm *owl.DirectiveRuntime) error {
1717
if len(rtm.Directive.Argv) == 0 {
1818
return nil
1919
}
20-
value := os.Getenv(rtm.Directive.Argv[0])
21-
rtm.Value.Elem().SetString(value)
20+
if value, ok := os.LookupEnv(rtm.Directive.Argv[0]); ok {
21+
rtm.Value.Elem().SetString(value)
22+
}
2223
return nil
2324
}
2425

owl_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ func TestOwlUseCaseEnvReader(t *testing.T) {
3131
// Resolve.
3232
gotValue, err := resolver.Resolve()
3333
assert.NoError(err)
34-
assert.NotNil(gotValue)
3534
gotConfig, ok := gotValue.Interface().(*EnvConfig)
3635
assert.True(ok)
3736
assert.Equal("/home/ggicci/.owl", gotConfig.Workspace)
@@ -84,7 +83,6 @@ func TestOwlUseCaseConfigLoader(t *testing.T) {
8483
// Resolve.
8584
gotValue, err := resolver.Resolve(owl.WithValue("ConfigFile", filename))
8685
assert.NoError(err)
87-
assert.NotNil(gotValue)
8886

8987
gotConfig, ok := gotValue.Interface().(*MyAppConfig)
9088
assert.True(ok)
@@ -103,7 +101,6 @@ func TestOwlUseCaseConfigLoader(t *testing.T) {
103101
// Resolve again.
104102
gotValue, err = resolver.Resolve(owl.WithValue("ConfigFile", filename))
105103
assert.NoError(err)
106-
assert.NotNil(gotValue)
107104

108105
gotConfig, ok = gotValue.Interface().(*MyAppConfig)
109106
assert.True(ok)

resolver.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ func (r *Resolver) Namespace() *Namespace {
124124

125125
// Find finds a field resolver by path. e.g. "Pagination.Page", "User.Name", etc.
126126
func (r *Resolver) Lookup(path string) *Resolver {
127-
return findResolver(r, strings.Split(path, "."))
127+
var paths []string
128+
if path != "" {
129+
paths = strings.Split(path, ".")
130+
}
131+
return findResolver(r, paths)
128132
}
129133

130134
func findResolver(root *Resolver, path []string) *Resolver {
@@ -173,15 +177,14 @@ func (r *Resolver) Resolve(opts ...Option) (reflect.Value, error) {
173177
for _, opt := range opts {
174178
ctx = opt.Apply(ctx)
175179
}
176-
return r.resolve(ctx)
180+
rootValue := reflect.New(r.Type)
181+
return rootValue, r.resolve(ctx, rootValue)
177182
}
178183

179-
func (root *Resolver) resolve(ctx context.Context) (reflect.Value, error) {
180-
rootValue := reflect.New(root.Type)
181-
184+
func (root *Resolver) resolve(ctx context.Context, rootValue reflect.Value) error {
182185
// Run the directives on current field.
183186
if err := root.runDirectives(ctx, rootValue); err != nil {
184-
return rootValue, err
187+
return err
185188
}
186189

187190
// Resolve the children fields.
@@ -195,18 +198,15 @@ func (root *Resolver) resolve(ctx context.Context) (reflect.Value, error) {
195198
}
196199

197200
for _, child := range root.Children {
198-
fieldValue, err := child.resolve(ctx)
199-
if err != nil {
200-
return rootValue, &ResolveError{
201+
if err := child.resolve(ctx, underlyingValue.Elem().Field(child.Index).Addr()); err != nil {
202+
return &ResolveError{
201203
Err: err,
202204
Resolver: child,
203205
}
204206
}
205-
underlyingValue.Elem().Field(child.Index).Set(fieldValue.Elem())
206207
}
207208
}
208-
209-
return rootValue, nil
209+
return nil
210210
}
211211

212212
func (r *Resolver) runDirectives(ctx context.Context, rv reflect.Value) error {
@@ -316,7 +316,11 @@ func buildResolver(typ reflect.Type, field reflect.StructField, parent *Resolver
316316
return nil, fmt.Errorf("build resolver for %q failed: %w", strings.Join(path, "."), err)
317317
}
318318
child.Index = i
319-
root.Children = append(root.Children, child)
319+
320+
// Skip the field if it has no children and no directives.
321+
if len(child.Children) > 0 || len(child.Directives) > 0 {
322+
root.Children = append(root.Children, child)
323+
}
320324
}
321325
}
322326
return root, nil

resolver_test.go

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"os"
8+
"reflect"
89
"testing"
910

1011
"github.com/ggicci/owl"
@@ -61,13 +62,14 @@ func (s *BuildResolverTreeTestSuite) Test_0_Lookup_IsLeaf() {
6162
assert := assert.New(s.T())
6263
for _, expected := range s.expected {
6364
resolver := s.tree.Lookup(expected.LookupPath)
64-
assert.Nil(s.tree.Lookup("SomeNonExistingPath"))
6565
assert.NotNil(resolver)
6666
assert.Equal(expected.Index, resolver.Index)
6767
assert.Equal(expected.NumFields, len(resolver.Children))
6868
assert.Equal(expected.Directives, resolver.Directives)
6969
assert.Equal(expected.Leaf, resolver.IsLeaf())
7070
}
71+
72+
assert.Nil(s.tree.Lookup("SomeNonExistingPath"))
7173
}
7274

7375
func (s *BuildResolverTreeTestSuite) Test_1_GetDirective() {
@@ -159,6 +161,70 @@ func TestNew_NormalCasesSuites(t *testing.T) {
159161
))
160162
}
161163

164+
func TestNew_SkipFieldsHavingNoDirectives(t *testing.T) {
165+
type AnotherForm struct {
166+
Username string `owl:"form=username"`
167+
Password string `owl:"form=password"`
168+
Hidden string // should be ignored
169+
Pagination *Pagination // should not be ignored
170+
}
171+
172+
suite.Run(t, NewBuildResolverTreeTestSuite(
173+
AnotherForm{},
174+
[]*expectedResolver{
175+
{
176+
Index: -1,
177+
LookupPath: "",
178+
NumFields: 3,
179+
Directives: nil,
180+
Leaf: false,
181+
},
182+
{
183+
Index: 0,
184+
LookupPath: "Username",
185+
Directives: []*owl.Directive{
186+
owl.NewDirective("form", "username"),
187+
},
188+
Leaf: true,
189+
},
190+
{
191+
Index: 1,
192+
LookupPath: "Password",
193+
NumFields: 0,
194+
Directives: []*owl.Directive{
195+
owl.NewDirective("form", "password"),
196+
},
197+
Leaf: true,
198+
},
199+
{
200+
Index: 3, // Pagination is the 4th field, Hidden is the 3rd field.
201+
LookupPath: "Pagination",
202+
NumFields: 2,
203+
Directives: nil,
204+
Leaf: false,
205+
},
206+
{
207+
Index: 0,
208+
LookupPath: "Pagination.Page",
209+
NumFields: 0,
210+
Directives: []*owl.Directive{
211+
owl.NewDirective("form", "page"),
212+
},
213+
Leaf: true,
214+
},
215+
{
216+
Index: 1,
217+
LookupPath: "Pagination.Size",
218+
NumFields: 0,
219+
Directives: []*owl.Directive{
220+
owl.NewDirective("form", "size"),
221+
},
222+
Leaf: true,
223+
},
224+
},
225+
))
226+
}
227+
162228
func TestNew_WithNilType(t *testing.T) {
163229
_, err := owl.New(nil)
164230
assert.ErrorIs(t, err, owl.ErrUnsupportedType)
@@ -368,7 +434,6 @@ func TestResolve_UnexportedField(t *testing.T) {
368434
// Resolve.
369435
gotValue, err := resolver.Resolve()
370436
assert.NoError(t, err)
371-
assert.NotNil(t, gotValue)
372437
gotUser, ok := gotValue.Interface().(*User)
373438
assert.True(t, ok)
374439
assert.Equal(t, "owl", gotUser.Name)
@@ -465,6 +530,44 @@ func TestResolve_DirectiveRuntimeContext(t *testing.T) {
465530
assert.ErrorContains(t, err, "field is required")
466531
}
467532

533+
func TestResolve_NestedExecution(t *testing.T) {
534+
type User struct {
535+
Name string `owl:"env=OWL_TEST_NAME"`
536+
Role string `owl:"env=OWL_TEST_ROLE"`
537+
}
538+
539+
type Request struct {
540+
// NOTE: Login will be created and updated by login directive,
541+
// and its fields will also be updated by env directive.
542+
Login User `owl:"login"`
543+
Action string `owl:"env=OWL_TEST_ACTION"`
544+
}
545+
546+
expected := &Request{
547+
Login: User{
548+
Name: "owl", // set by env
549+
Role: "admin", // set by login
550+
},
551+
Action: "addAccount",
552+
}
553+
554+
ns := owl.NewNamespace()
555+
ns.RegisterDirectiveExecutor("env", owl.DirectiveExecutorFunc(exeEnvReader))
556+
ns.RegisterDirectiveExecutor("login", owl.DirectiveExecutorFunc(func(dr *owl.DirectiveRuntime) error {
557+
u := User{Name: "hello", Role: "admin"}
558+
dr.Value.Elem().Set(reflect.ValueOf(u))
559+
return nil
560+
}))
561+
os.Setenv("OWL_TEST_NAME", "owl")
562+
os.Setenv("OWL_TEST_ACTION", "addAccount")
563+
564+
resolver, err := owl.New(Request{}, owl.WithNamespace(ns))
565+
assert.NoError(t, err)
566+
gotValue, err := resolver.Resolve()
567+
assert.NoError(t, err)
568+
assert.Equal(t, expected, gotValue.Interface().(*Request))
569+
}
570+
468571
func TestIterate(t *testing.T) {
469572
resolver, err := owl.New(UserSignUpForm{})
470573
assert.NoError(t, err)

0 commit comments

Comments
 (0)