Compare commits

..

No commits in common. "af106acd3fe835740f65cc09b611fbcba8b9f9b2" and "1245861be72021a503378e9d08fc4522a6f3d739" have entirely different histories.

4 changed files with 15 additions and 189 deletions

View File

@ -11,7 +11,7 @@ import (
// Handler represents a dynamic api handler // Handler represents a dynamic api handler
type Handler struct { type Handler struct {
spec *signature spec *spec
fn interface{} fn interface{}
// whether fn uses api.Ctx as 1st argument // whether fn uses api.Ctx as 1st argument
hasContext bool hasContext bool
@ -26,12 +26,11 @@ type Handler struct {
// - `outputStruct` is a struct{} containing a field for each service output (with valid reflect.Type) // - `outputStruct` is a struct{} containing a field for each service output (with valid reflect.Type)
// //
// Special cases: // Special cases:
// - a first optional input parameter of type `api.Ctx` can be added
// - it there is no input, `inputStruct` must be omitted // - it there is no input, `inputStruct` must be omitted
// - it there is no output, `outputStruct` must be omitted // - it there is no output, `outputStruct` must be omitted
func Build(fn interface{}, service config.Service) (*Handler, error) { func Build(fn interface{}, service config.Service) (*Handler, error) {
h := &Handler{ h := &Handler{
spec: signatureFromService(service), spec: makeSpec(service),
fn: fn, fn: fn,
} }
@ -82,8 +81,8 @@ func (h *Handler) Handle(ctx api.Ctx, data map[string]interface{}) (map[string]i
} }
// get value from @data // get value from @data
value, provided := data[name] value, inData := data[name]
if !provided { if !inData {
continue continue
} }
@ -94,7 +93,7 @@ func (h *Handler) Handle(ctx api.Ctx, data map[string]interface{}) (map[string]i
var ptrType = field.Type().Elem() var ptrType = field.Type().Elem()
if !refvalue.Type().ConvertibleTo(ptrType) { if !refvalue.Type().ConvertibleTo(ptrType) {
log.Printf("Cannot convert %v into *%v", refvalue.Type(), ptrType) log.Printf("Cannot convert %v into %v", refvalue.Type(), ptrType)
return nil, api.ErrUncallableService return nil, api.ErrUncallableService
} }
@ -104,13 +103,12 @@ func (h *Handler) Handle(ctx api.Ctx, data map[string]interface{}) (map[string]i
field.Set(ptr) field.Set(ptr)
continue continue
} }
if !reflect.ValueOf(value).Type().ConvertibleTo(field.Type()) { if !reflect.ValueOf(value).Type().ConvertibleTo(field.Type()) {
log.Printf("Cannot convert %v into %v", reflect.ValueOf(value).Type(), field.Type()) log.Printf("Cannot convert %v into %v", reflect.ValueOf(value).Type(), field.Type())
return nil, api.ErrUncallableService return nil, api.ErrUncallableService
} }
field.Set(refvalue.Convert(field.Type())) field.Set(reflect.ValueOf(value).Convert(field.Type()))
} }
callArgs = append(callArgs, callStruct) callArgs = append(callArgs, callStruct)
} }

View File

@ -1,173 +0,0 @@
package dynfunc
import (
"fmt"
"reflect"
"testing"
"git.xdrm.io/go/aicra/api"
)
type testsignature signature
// builds a mock service with provided arguments as Input and matched as Output
func (s *testsignature) withArgs(dtypes ...reflect.Type) *testsignature {
if s.Input == nil {
s.Input = make(map[string]reflect.Type)
}
if s.Output == nil {
s.Output = make(map[string]reflect.Type)
}
for i, dtype := range dtypes {
name := fmt.Sprintf("P%d", i+1)
s.Input[name] = dtype
if dtype.Kind() == reflect.Ptr {
s.Output[name] = dtype.Elem()
} else {
s.Output[name] = dtype
}
}
return s
}
func TestInput(t *testing.T) {
type intstruct struct {
P1 int
}
type intptrstruct struct {
P1 *int
}
tcases := []struct {
Name string
Spec *testsignature
HasContext bool
Fn interface{}
Input []interface{}
ExpectedOutput []interface{}
ExpectedErr api.Err
}{
{
Name: "none required none provided",
Spec: (&testsignature{}).withArgs(),
Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
HasContext: false,
Input: []interface{}{},
ExpectedOutput: []interface{}{},
ExpectedErr: api.ErrSuccess,
},
{
Name: "int proxy (0)",
Spec: (&testsignature{}).withArgs(reflect.TypeOf(int(0))),
Fn: func(in intstruct) (*intstruct, api.Err) {
return &intstruct{P1: in.P1}, api.ErrSuccess
},
HasContext: false,
Input: []interface{}{int(0)},
ExpectedOutput: []interface{}{int(0)},
ExpectedErr: api.ErrSuccess,
},
{
Name: "int proxy (11)",
Spec: (&testsignature{}).withArgs(reflect.TypeOf(int(0))),
Fn: func(in intstruct) (*intstruct, api.Err) {
return &intstruct{P1: in.P1}, api.ErrSuccess
},
HasContext: false,
Input: []interface{}{int(11)},
ExpectedOutput: []interface{}{int(11)},
ExpectedErr: api.ErrSuccess,
},
{
Name: "*int proxy (nil)",
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
Fn: func(in intptrstruct) (*intptrstruct, api.Err) {
return &intptrstruct{P1: in.P1}, api.ErrSuccess
},
HasContext: false,
Input: []interface{}{},
ExpectedOutput: []interface{}{nil},
ExpectedErr: api.ErrSuccess,
},
{
Name: "*int proxy (28)",
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
Fn: func(in intptrstruct) (*intstruct, api.Err) {
return &intstruct{P1: *in.P1}, api.ErrSuccess
},
HasContext: false,
Input: []interface{}{28},
ExpectedOutput: []interface{}{28},
ExpectedErr: api.ErrSuccess,
},
{
Name: "*int proxy (13)",
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
Fn: func(in intptrstruct) (*intstruct, api.Err) {
return &intstruct{P1: *in.P1}, api.ErrSuccess
},
HasContext: false,
Input: []interface{}{13},
ExpectedOutput: []interface{}{13},
ExpectedErr: api.ErrSuccess,
},
}
for _, tcase := range tcases {
t.Run(tcase.Name, func(t *testing.T) {
t.Parallel()
var dataIndex = 0
if tcase.HasContext {
dataIndex = 1
}
var handler = &Handler{
spec: &signature{Input: tcase.Spec.Input, Output: tcase.Spec.Output},
fn: tcase.Fn,
dataIndex: dataIndex,
hasContext: tcase.HasContext,
}
// build input
input := make(map[string]interface{})
for i, val := range tcase.Input {
var key = fmt.Sprintf("P%d", i+1)
input[key] = val
}
var output, err = handler.Handle(api.Ctx{}, input)
if err != tcase.ExpectedErr {
t.Fatalf("expected api error <%v> got <%v>", tcase.ExpectedErr, err)
}
// check output
for i, expected := range tcase.ExpectedOutput {
var (
key = fmt.Sprintf("P%d", i+1)
val, exists = output[key]
)
if !exists {
t.Fatalf("missing output[%s]", key)
}
if expected != val {
var (
expectedt = reflect.ValueOf(expected)
valt = reflect.ValueOf(val)
expectedNil = !expectedt.IsValid() || expectedt.Kind() == reflect.Ptr && expectedt.IsNil()
valNil = !valt.IsValid() || valt.Kind() == reflect.Ptr && valt.IsNil()
)
// ignore both nil
if valNil && expectedNil {
continue
}
t.Fatalf("expected output[%s] to equal %T <%v> got %T <%v>", key, expected, expected, val, val)
}
}
})
}
}

View File

@ -9,15 +9,16 @@ import (
"git.xdrm.io/go/aicra/internal/config" "git.xdrm.io/go/aicra/internal/config"
) )
// signature represents input/output arguments for a dynamic function type spec struct {
type signature struct {
Input map[string]reflect.Type Input map[string]reflect.Type
Output map[string]reflect.Type Output map[string]reflect.Type
// HasContext defines whether the given handler has api.Ctx as first argument
HasContext bool
} }
// builds a spec from the configuration service // builds a spec from the configuration service
func signatureFromService(service config.Service) *signature { func makeSpec(service config.Service) *spec {
s := &signature{ s := &spec{
Input: make(map[string]reflect.Type), Input: make(map[string]reflect.Type),
Output: make(map[string]reflect.Type), Output: make(map[string]reflect.Type),
} }
@ -45,7 +46,7 @@ func signatureFromService(service config.Service) *signature {
} }
// checks for HandlerFn input arguments // checks for HandlerFn input arguments
func (s *signature) checkInput(impl reflect.Type, index int) error { func (s *spec) checkInput(impl reflect.Type, index int) error {
var requiredInput, structIndex = index, index var requiredInput, structIndex = index, index
if len(s.Input) > 0 { // arguments struct if len(s.Input) > 0 { // arguments struct
requiredInput++ requiredInput++
@ -92,7 +93,7 @@ func (s *signature) checkInput(impl reflect.Type, index int) error {
} }
// checks for HandlerFn output arguments // checks for HandlerFn output arguments
func (s signature) checkOutput(impl reflect.Type) error { func (s spec) checkOutput(impl reflect.Type) error {
if impl.NumOut() < 1 { if impl.NumOut() < 1 {
return errMissingHandlerOutput return errMissingHandlerOutput
} }

View File

@ -225,7 +225,7 @@ func TestInputCheck(t *testing.T) {
t.Parallel() t.Parallel()
// mock spec // mock spec
s := signature{ s := spec{
Input: tcase.Input, Input: tcase.Input,
Output: nil, Output: nil,
} }
@ -371,7 +371,7 @@ func TestOutputCheck(t *testing.T) {
t.Parallel() t.Parallel()
// mock spec // mock spec
s := signature{ s := spec{
Input: nil, Input: nil,
Output: tcase.Output, Output: tcase.Output,
} }