Skip to content

Commit

Permalink
✨ feat: method can return an optional error now
Browse files Browse the repository at this point in the history
  • Loading branch information
0xE8551CCB committed Oct 9, 2019
1 parent 219dcd6 commit b9a384a
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 7 deletions.
2 changes: 1 addition & 1 deletion examples/todo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func main() {
}

printFullFields(&task)
printWithOnlyFields(&task, "Title", "SimpleUser")
printWithOnlyFields(&task, "Description")
printWithOnlyFields(&task, "ID", "User[id,Notifications[ID],AnotherNotifications[Title]]", "simple_user[id]")
printMany()
printWithExcludeFields(&task, "Description", "ID", "User[Name,Notifications[ID,Content],AnotherNotifications], SimpleUser")
Expand Down
4 changes: 2 additions & 2 deletions examples/todo/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ type TaskModel struct {
}

func (t *TaskModel) User() *UserModel {
tag := "user"
return &UserModel{ID: t.UserID, Tag: &tag}
//tag := "user"
return &UserModel{ID: t.UserID}
}
4 changes: 2 additions & 2 deletions examples/todo/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type TaskSchema struct {
SimpleUser *UserSchema `json:"simple_user,omitempty" portal:"nested;only:Name;attr:User"`
}

func (ts *TaskSchema) GetDescription(model *model.TaskModel) string {
func (ts *TaskSchema) GetDescription(model *model.TaskModel) (string, error) {
time.Sleep(1 * time.Second)
return "Custom description"
return "Custom description", nil
}
47 changes: 45 additions & 2 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package portal

import (
"context"
"errors"
"fmt"
"reflect"

"github.com/pkg/errors"
)

func nestedValue(ctx context.Context, any interface{}, chainingAttrs []string) (interface{}, error) {
Expand Down Expand Up @@ -35,6 +36,18 @@ func nestedValue(ctx context.Context, any interface{}, chainingAttrs []string) (
}

// invokeStructMethod calls the specified method of given struct `any` and return results.
// Note:
// - Context param is optional
// - Method must returns at least one value.
// - Max number of return values is two, and the last one must be of `error` type.
//
// Supported method definitions:
// - `func (f *Foo) Bar(v interface{}) error`
// - `func (f *Foo) Bar(v interface{}) string`
// - `func (f *Foo) Bar(ctx context.Context, v interface{}) error`
// - `func (f *Foo) Bar(ctx context.Context, v interface{}) string`
// - `func (f *Foo) Bar(ctx context.Context, v interface{}) (string, error)`
// - `func (f *Foo) Bar(ctx context.Context, v interface{}) (string, error)`
func invokeStructMethod(ctx context.Context, any interface{}, name string, args ...interface{}) (interface{}, error) {
structValue := reflect.ValueOf(any)
method, err := findStructMethod(structValue, name)
Expand All @@ -53,6 +66,23 @@ func invokeStructMethod(ctx context.Context, any interface{}, name string, args
if numIn != len(args) && !methodType.IsVariadic() {
return reflect.ValueOf(nil), fmt.Errorf("method '%s' must has %d params: %d", name, numIn, len(args))
}

numOut := methodType.NumOut()
switch numOut {
case 1:
// Cases like:
// func (f *Foo) Bar() error
// func (f *Foo) Bar() string
case 2:
// Cases like:
// func (f *Foo) Bar() (string, error)
if !methodType.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
return reflect.ValueOf(nil), fmt.Errorf("the last return value of method '%s' must be of `error` type", name)
}
default:
return reflect.ValueOf(nil), fmt.Errorf("method '%s' must returns one result with an optional error", name)
}

in := make([]reflect.Value, len(args))
for i := 0; i < len(args); i++ {
var inType reflect.Type
Expand All @@ -69,7 +99,20 @@ func invokeStructMethod(ctx context.Context, any interface{}, name string, args
return reflect.ValueOf(nil), fmt.Errorf("method '%s', param[%d] must be %s, not %s", name, i, inType, argType)
}
}
return (method.Call(in)[0]).Interface(), nil

outs := method.Call(in)
switch len(outs) {
case 1:
return outs[0].Interface(), nil
case 2:
err := outs[1].Interface()
if err != nil {
return nil, errors.WithStack(err.(error))
}
return outs[0].Interface(), nil
default:
panic(fmt.Errorf("unexpected results returned by method '%s'", name))
}
}

func findStructMethod(any reflect.Value, name string) (reflect.Value, error) {
Expand Down
40 changes: 40 additions & 0 deletions utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ type Book struct {
name string
}

func (b Book) NotReturn() {

}

func (b Book) ReturnTooMany() (string, string, error) {
return "", "", nil
}

func (b Book) ShortName() string {
return b.name
}
Expand All @@ -109,6 +117,18 @@ func (b *Book) Plus(ctx context.Context, v int) int {
return v + 100
}

func (b *Book) NoError() (string, error) {
return "no error", nil
}

func (b *Book) ReturnError() (string, error) {
return "", errors.New("error")
}

func (b *Book) LastReturnValueNotErrorType() (string, string) {
return "", ""
}

//nolint
func TestInvokeMethod(t *testing.T) {
book := Book{name: "Test"}
Expand Down Expand Up @@ -143,6 +163,10 @@ func TestInvokeMethod(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, 200, ret)

ret, err = invokeStructMethod(ctx, book, "NoError")
assert.Nil(t, err)
assert.Equal(t, "no error", ret)

ret, err = invokeStructMethod(ctx, &book, "MethodNotFound")
assert.Errorf(t, err, "method 'MethodNotFound' not found in 'Book'")

Expand All @@ -153,6 +177,22 @@ func TestInvokeMethod(t *testing.T) {
_, err = invokeStructMethod(ctx, &book, "Plus", 1, 2)
assert.NotNil(t, err)
assert.Equal(t, "method 'Plus' must has 2 params: 3", err.Error())

_, err = invokeStructMethod(ctx, &book, "NotReturn")
assert.NotNil(t, err)
assert.Equal(t, "method 'NotReturn' must returns one result with an optional error", err.Error())

_, err = invokeStructMethod(ctx, &book, "ReturnTooMany")
assert.NotNil(t, err)
assert.Equal(t, "method 'ReturnTooMany' must returns one result with an optional error", err.Error())

_, err = invokeStructMethod(ctx, &book, "ReturnError")
assert.NotNil(t, err)
assert.Equal(t, "error", err.Error())

_, err = invokeStructMethod(ctx, &book, "LastReturnValueNotErrorType")
assert.NotNil(t, err)
assert.Equal(t, "the last return value of method 'LastReturnValueNotErrorType' must be of `error` type", err.Error())
}

//1000000 1371 ns/op
Expand Down

0 comments on commit b9a384a

Please sign in to comment.