Compare commits
No commits in common. "af106acd3fe835740f65cc09b611fbcba8b9f9b2" and "1245861be72021a503378e9d08fc4522a6f3d739" have entirely different histories.
af106acd3f
...
1245861be7
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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,
|
||||||
}
|
}
|
Loading…
Reference in New Issue