refactor: handler signature an middlewares for an idiomatic solution #24
|
@ -72,7 +72,7 @@ func TestBind(t *testing.T) {
|
||||||
Config: "[]",
|
Config: "[]",
|
||||||
HandlerMethod: "",
|
HandlerMethod: "",
|
||||||
HandlerPath: "",
|
HandlerPath: "",
|
||||||
HandlerFn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
HandlerFn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
BindErr: errUnknownService,
|
BindErr: errUnknownService,
|
||||||
BuildErr: nil,
|
BuildErr: nil,
|
||||||
},
|
},
|
||||||
|
@ -108,7 +108,7 @@ func TestBind(t *testing.T) {
|
||||||
]`,
|
]`,
|
||||||
HandlerMethod: http.MethodPost,
|
HandlerMethod: http.MethodPost,
|
||||||
HandlerPath: "/path",
|
HandlerPath: "/path",
|
||||||
HandlerFn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
HandlerFn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
BindErr: errUnknownService,
|
BindErr: errUnknownService,
|
||||||
BuildErr: errMissingHandler,
|
BuildErr: errMissingHandler,
|
||||||
},
|
},
|
||||||
|
@ -126,7 +126,7 @@ func TestBind(t *testing.T) {
|
||||||
]`,
|
]`,
|
||||||
HandlerMethod: http.MethodGet,
|
HandlerMethod: http.MethodGet,
|
||||||
HandlerPath: "/paths",
|
HandlerPath: "/paths",
|
||||||
HandlerFn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
HandlerFn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
BindErr: errUnknownService,
|
BindErr: errUnknownService,
|
||||||
BuildErr: errMissingHandler,
|
BuildErr: errMissingHandler,
|
||||||
},
|
},
|
||||||
|
@ -144,7 +144,7 @@ func TestBind(t *testing.T) {
|
||||||
]`,
|
]`,
|
||||||
HandlerMethod: http.MethodGet,
|
HandlerMethod: http.MethodGet,
|
||||||
HandlerPath: "/path",
|
HandlerPath: "/path",
|
||||||
HandlerFn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
HandlerFn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
BindErr: nil,
|
BindErr: nil,
|
||||||
BuildErr: nil,
|
BuildErr: nil,
|
||||||
},
|
},
|
||||||
|
@ -164,7 +164,7 @@ func TestBind(t *testing.T) {
|
||||||
]`,
|
]`,
|
||||||
HandlerMethod: http.MethodGet,
|
HandlerMethod: http.MethodGet,
|
||||||
HandlerPath: "/path",
|
HandlerPath: "/path",
|
||||||
HandlerFn: func(struct{ Name int }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
HandlerFn: func(*api.Context, struct{ Name int }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
BindErr: nil,
|
BindErr: nil,
|
||||||
BuildErr: nil,
|
BuildErr: nil,
|
||||||
},
|
},
|
||||||
|
@ -184,7 +184,7 @@ func TestBind(t *testing.T) {
|
||||||
]`,
|
]`,
|
||||||
HandlerMethod: http.MethodGet,
|
HandlerMethod: http.MethodGet,
|
||||||
HandlerPath: "/path",
|
HandlerPath: "/path",
|
||||||
HandlerFn: func(struct{ Name uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
HandlerFn: func(*api.Context, struct{ Name uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
BindErr: nil,
|
BindErr: nil,
|
||||||
BuildErr: nil,
|
BuildErr: nil,
|
||||||
},
|
},
|
||||||
|
@ -204,7 +204,7 @@ func TestBind(t *testing.T) {
|
||||||
]`,
|
]`,
|
||||||
HandlerMethod: http.MethodGet,
|
HandlerMethod: http.MethodGet,
|
||||||
HandlerPath: "/path",
|
HandlerPath: "/path",
|
||||||
HandlerFn: func(struct{ Name string }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
HandlerFn: func(*api.Context, struct{ Name string }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
BindErr: nil,
|
BindErr: nil,
|
||||||
BuildErr: nil,
|
BuildErr: nil,
|
||||||
},
|
},
|
||||||
|
@ -224,7 +224,7 @@ func TestBind(t *testing.T) {
|
||||||
]`,
|
]`,
|
||||||
HandlerMethod: http.MethodGet,
|
HandlerMethod: http.MethodGet,
|
||||||
HandlerPath: "/path",
|
HandlerPath: "/path",
|
||||||
HandlerFn: func(struct{ Name bool }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
HandlerFn: func(*api.Context, struct{ Name bool }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
BindErr: nil,
|
BindErr: nil,
|
||||||
BuildErr: nil,
|
BuildErr: nil,
|
||||||
},
|
},
|
||||||
|
|
16
handler.go
16
handler.go
|
@ -1,12 +1,14 @@
|
||||||
package aicra
|
package aicra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
|
"git.xdrm.io/go/aicra/internal/ctx"
|
||||||
"git.xdrm.io/go/aicra/internal/reqdata"
|
"git.xdrm.io/go/aicra/internal/reqdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -94,11 +96,17 @@ func (s Handler) resolve(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Handler) handle(input *reqdata.T, handler *apiHandler, service *config.Service, w http.ResponseWriter, r *http.Request) {
|
func (s *Handler) handle(input *reqdata.T, handler *apiHandler, service *config.Service, w http.ResponseWriter, r *http.Request) {
|
||||||
// 5. pass execution to the handler
|
// build context with builtin data
|
||||||
ctx := api.Context{Res: w, Req: r}
|
c := r.Context()
|
||||||
var outData, outErr = handler.dyn.Handle(ctx, input.Data)
|
c = context.WithValue(c, ctx.Request, r)
|
||||||
|
c = context.WithValue(c, ctx.Response, w)
|
||||||
|
c = context.WithValue(c, ctx.Auth, w)
|
||||||
|
apictx := &api.Context{Context: c}
|
||||||
|
|
||||||
// 6. build res from returned data
|
// pass execution to the handler
|
||||||
|
var outData, outErr = handler.dyn.Handle(apictx, input.Data)
|
||||||
|
|
||||||
|
// build response from returned arguments
|
||||||
var res = api.EmptyResponse().WithError(outErr)
|
var res = api.EmptyResponse().WithError(outErr)
|
||||||
for key, value := range outData {
|
for key, value := range outData {
|
||||||
|
|
||||||
|
|
|
@ -82,9 +82,9 @@ func TestWith(t *testing.T) {
|
||||||
t.Fatalf("setup: unexpected error <%v>", err)
|
t.Fatalf("setup: unexpected error <%v>", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pathHandler := func(ctx api.Context) (*struct{}, api.Err) {
|
pathHandler := func(ctx *api.Context) (*struct{}, api.Err) {
|
||||||
// write value from middlewares into response
|
// write value from middlewares into response
|
||||||
value := ctx.Req.Context().Value(key)
|
value := ctx.Value(key)
|
||||||
if value == nil {
|
if value == nil {
|
||||||
t.Fatalf("nothing found in context")
|
t.Fatalf("nothing found in context")
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ func TestWith(t *testing.T) {
|
||||||
t.Fatalf("cannot cast context data to int")
|
t.Fatalf("cannot cast context data to int")
|
||||||
}
|
}
|
||||||
// write to response
|
// write to response
|
||||||
ctx.Res.Write([]byte(fmt.Sprintf("#%d#", cast)))
|
ctx.ResponseWriter().Write([]byte(fmt.Sprintf("#%d#", cast)))
|
||||||
|
|
||||||
return nil, api.ErrSuccess
|
return nil, api.ErrSuccess
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,7 @@ func TestWithAuth(t *testing.T) {
|
||||||
t.Fatalf("setup: unexpected error <%v>", err)
|
t.Fatalf("setup: unexpected error <%v>", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pathHandler := func(ctx api.Context) (*struct{}, api.Err) {
|
pathHandler := func(ctx *api.Context) (*struct{}, api.Err) {
|
||||||
return nil, api.ErrNotImplemented
|
return nil, api.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ func TestDynamicScope(t *testing.T) {
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
path: "/path/{id}",
|
path: "/path/{id}",
|
||||||
handler: func(struct{ Input1 uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
handler: func(*api.Context, struct{ Input1 uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
url: "/path/123",
|
url: "/path/123",
|
||||||
body: ``,
|
body: ``,
|
||||||
permissions: []string{"user[123]"},
|
permissions: []string{"user[123]"},
|
||||||
|
@ -311,7 +311,7 @@ func TestDynamicScope(t *testing.T) {
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
path: "/path/{id}",
|
path: "/path/{id}",
|
||||||
handler: func(struct{ Input1 uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
handler: func(*api.Context, struct{ Input1 uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
url: "/path/666",
|
url: "/path/666",
|
||||||
body: ``,
|
body: ``,
|
||||||
permissions: []string{"user[123]"},
|
permissions: []string{"user[123]"},
|
||||||
|
@ -332,7 +332,7 @@ func TestDynamicScope(t *testing.T) {
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
path: "/path/{id}",
|
path: "/path/{id}",
|
||||||
handler: func(struct{ User uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
handler: func(*api.Context, struct{ User uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
url: "/path/123",
|
url: "/path/123",
|
||||||
body: ``,
|
body: ``,
|
||||||
permissions: []string{"prefix.user[123].suffix"},
|
permissions: []string{"prefix.user[123].suffix"},
|
||||||
|
@ -354,7 +354,7 @@ func TestDynamicScope(t *testing.T) {
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
path: "/prefix/{pid}/user/{uid}",
|
path: "/prefix/{pid}/user/{uid}",
|
||||||
handler: func(struct {
|
handler: func(*api.Context, struct {
|
||||||
Prefix uint
|
Prefix uint
|
||||||
User uint
|
User uint
|
||||||
}) (*struct{}, api.Err) {
|
}) (*struct{}, api.Err) {
|
||||||
|
@ -381,7 +381,7 @@ func TestDynamicScope(t *testing.T) {
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
path: "/prefix/{pid}/user/{uid}",
|
path: "/prefix/{pid}/user/{uid}",
|
||||||
handler: func(struct {
|
handler: func(*api.Context, struct {
|
||||||
Prefix uint
|
Prefix uint
|
||||||
User uint
|
User uint
|
||||||
}) (*struct{}, api.Err) {
|
}) (*struct{}, api.Err) {
|
||||||
|
@ -409,7 +409,7 @@ func TestDynamicScope(t *testing.T) {
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
path: "/prefix/{pid}/user/{uid}/suffix/{sid}",
|
path: "/prefix/{pid}/user/{uid}/suffix/{sid}",
|
||||||
handler: func(struct {
|
handler: func(*api.Context, struct {
|
||||||
Prefix uint
|
Prefix uint
|
||||||
User uint
|
User uint
|
||||||
Suffix uint
|
Suffix uint
|
||||||
|
@ -438,7 +438,7 @@ func TestDynamicScope(t *testing.T) {
|
||||||
}
|
}
|
||||||
]`,
|
]`,
|
||||||
path: "/prefix/{pid}/user/{uid}/suffix/{sid}",
|
path: "/prefix/{pid}/user/{uid}/suffix/{sid}",
|
||||||
handler: func(struct {
|
handler: func(*api.Context, struct {
|
||||||
Prefix uint
|
Prefix uint
|
||||||
User uint
|
User uint
|
||||||
Suffix uint
|
Suffix uint
|
||||||
|
|
|
@ -14,16 +14,19 @@ const errHandlerNotFunc = cerr("handler must be a func")
|
||||||
const errNoServiceForHandler = cerr("no service found for this handler")
|
const errNoServiceForHandler = cerr("no service found for this handler")
|
||||||
|
|
||||||
// errMissingHandlerArgumentParam - missing params arguments for handler
|
// errMissingHandlerArgumentParam - missing params arguments for handler
|
||||||
const errMissingHandlerArgumentParam = cerr("missing handler argument : parameter struct")
|
const errMissingHandlerContextArgument = cerr("missing handler first argument of type *api.Context")
|
||||||
|
|
||||||
|
// errMissingHandlerInputArgument - missing params arguments for handler
|
||||||
|
const errMissingHandlerInputArgument = cerr("missing handler argument: input struct")
|
||||||
|
|
||||||
// errUnexpectedInput - input argument is not expected
|
// errUnexpectedInput - input argument is not expected
|
||||||
const errUnexpectedInput = cerr("unexpected input struct")
|
const errUnexpectedInput = cerr("unexpected input struct")
|
||||||
|
|
||||||
// errMissingHandlerOutput - missing output for handler
|
// errMissingHandlerOutputArgument - missing output for handler
|
||||||
const errMissingHandlerOutput = cerr("handler must have at least 1 output")
|
const errMissingHandlerOutputArgument = cerr("missing handler first output argument: output struct")
|
||||||
|
|
||||||
// errMissingHandlerOutputError - missing error output for handler
|
// errMissingHandlerOutputError - missing error output for handler
|
||||||
const errMissingHandlerOutputError = cerr("handler must have its last output of type api.Err")
|
const errMissingHandlerOutputError = cerr("missing handler last output argument of type api.Err")
|
||||||
|
|
||||||
// errMissingRequestArgument - missing request argument for handler
|
// errMissingRequestArgument - missing request argument for handler
|
||||||
const errMissingRequestArgument = cerr("handler first argument must be of type api.Request")
|
const errMissingRequestArgument = cerr("handler first argument must be of type api.Request")
|
||||||
|
@ -34,17 +37,14 @@ const errMissingParamArgument = cerr("handler second argument must be a struct")
|
||||||
// errUnexportedName - argument is unexported in struct
|
// errUnexportedName - argument is unexported in struct
|
||||||
const errUnexportedName = cerr("unexported name")
|
const errUnexportedName = cerr("unexported name")
|
||||||
|
|
||||||
// errMissingParamOutput - missing output argument for handler
|
// errWrongOutputArgumentType - wrong type for output first argument
|
||||||
const errMissingParamOutput = cerr("handler first output must be a *struct")
|
const errWrongOutputArgumentType = cerr("handler first output argument must be a *struct")
|
||||||
|
|
||||||
// errMissingParamFromConfig - missing a parameter in handler struct
|
// errMissingConfigArgument - missing an input/output argument in handler struct
|
||||||
const errMissingParamFromConfig = cerr("missing a parameter from configuration")
|
const errMissingConfigArgument = cerr("missing an argument from the configuration")
|
||||||
|
|
||||||
// errMissingOutputFromConfig - missing a parameter in handler struct
|
|
||||||
const errMissingOutputFromConfig = cerr("missing a parameter from configuration")
|
|
||||||
|
|
||||||
// errWrongParamTypeFromConfig - a configuration parameter type is invalid in the handler param struct
|
// errWrongParamTypeFromConfig - a configuration parameter type is invalid in the handler param struct
|
||||||
const errWrongParamTypeFromConfig = cerr("invalid struct field type")
|
const errWrongParamTypeFromConfig = cerr("invalid struct field type")
|
||||||
|
|
||||||
// errMissingHandlerErrorOutput - missing handler output error
|
// errMissingHandlerErrorArgument - missing handler output error
|
||||||
const errMissingHandlerErrorOutput = cerr("last output must be of type api.Err")
|
const errMissingHandlerErrorArgument = cerr("last output must be of type api.Err")
|
||||||
|
|
|
@ -9,73 +9,69 @@ import (
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler represents a dynamic api handler
|
// Handler represents a dynamic aicra service handler
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
spec *signature
|
// signature defined from the service configuration
|
||||||
|
signature *Signature
|
||||||
|
// fn provided function that will be the service's handler
|
||||||
fn interface{}
|
fn interface{}
|
||||||
// whether fn uses api.Ctx as 1st argument
|
|
||||||
hasContext bool
|
|
||||||
// index in input arguments where the data struct must be
|
|
||||||
dataIndex int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a handler from a service configuration and a dynamic function
|
// Build a handler from a dynamic function and checks its signature against a
|
||||||
//
|
// service configuration
|
||||||
// @fn must have as a signature : `func(inputStruct) (*outputStruct, api.Err)`
|
//e
|
||||||
// - `inputStruct` is a struct{} containing a field for each service input (with valid reflect.Type)
|
// `fn` must have as a signature : `func(*api.Context, in) (*out, api.Err)`
|
||||||
// - `outputStruct` is a struct{} containing a field for each service output (with valid reflect.Type)
|
// - `in` is a struct{} containing a field for each service input (with valid reflect.Type)
|
||||||
|
// - `out` 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, `in` MUST be omitted
|
||||||
// - it there is no input, `inputStruct` must be omitted
|
// - it there is no output, `out` 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{
|
var (
|
||||||
spec: signatureFromService(service),
|
h = &Handler{
|
||||||
|
signature: BuildSignature(service),
|
||||||
fn: fn,
|
fn: fn,
|
||||||
}
|
}
|
||||||
|
fnType = reflect.TypeOf(fn)
|
||||||
|
)
|
||||||
|
|
||||||
impl := reflect.TypeOf(fn)
|
if fnType.Kind() != reflect.Func {
|
||||||
|
|
||||||
if impl.Kind() != reflect.Func {
|
|
||||||
return nil, errHandlerNotFunc
|
return nil, errHandlerNotFunc
|
||||||
}
|
}
|
||||||
|
if err := h.signature.ValidateInput(fnType); err != nil {
|
||||||
h.hasContext = impl.NumIn() >= 1 && reflect.TypeOf(api.Context{}).AssignableTo(impl.In(0))
|
|
||||||
if h.hasContext {
|
|
||||||
h.dataIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.spec.checkInput(impl, h.dataIndex); err != nil {
|
|
||||||
return nil, fmt.Errorf("input: %w", err)
|
return nil, fmt.Errorf("input: %w", err)
|
||||||
}
|
}
|
||||||
if err := h.spec.checkOutput(impl); err != nil {
|
if err := h.signature.ValidateOutput(fnType); err != nil {
|
||||||
return nil, fmt.Errorf("output: %w", err)
|
return nil, fmt.Errorf("output: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle binds input @data into the dynamic function and returns map output
|
// Handle binds input `data` into the dynamic function and returns an output map
|
||||||
func (h *Handler) Handle(ctx api.Context, data map[string]interface{}) (map[string]interface{}, api.Err) {
|
func (h *Handler) Handle(ctx *api.Context, data map[string]interface{}) (map[string]interface{}, api.Err) {
|
||||||
var ert = reflect.TypeOf(api.Err{})
|
var (
|
||||||
var fnv = reflect.ValueOf(h.fn)
|
ert = reflect.TypeOf(api.Err{})
|
||||||
|
fnv = reflect.ValueOf(h.fn)
|
||||||
|
callArgs = make([]reflect.Value, 0)
|
||||||
|
)
|
||||||
|
|
||||||
callArgs := []reflect.Value{}
|
// bind context
|
||||||
|
|
||||||
// bind context if used in handler
|
|
||||||
if h.hasContext {
|
|
||||||
callArgs = append(callArgs, reflect.ValueOf(ctx))
|
callArgs = append(callArgs, reflect.ValueOf(ctx))
|
||||||
}
|
|
||||||
|
|
||||||
// bind input data
|
inputStructRequired := fnv.Type().NumIn() > 1
|
||||||
if fnv.Type().NumIn() > h.dataIndex {
|
|
||||||
|
// bind input arguments
|
||||||
|
if inputStructRequired {
|
||||||
// create zero value struct
|
// create zero value struct
|
||||||
callStructPtr := reflect.New(fnv.Type().In(0))
|
var (
|
||||||
callStruct := callStructPtr.Elem()
|
callStructPtr = reflect.New(fnv.Type().In(1))
|
||||||
|
callStruct = callStructPtr.Elem()
|
||||||
|
)
|
||||||
|
|
||||||
// set each field
|
// set each field
|
||||||
for name := range h.spec.Input {
|
for name := range h.signature.Input {
|
||||||
field := callStruct.FieldByName(name)
|
field := callStruct.FieldByName(name)
|
||||||
if !field.CanSet() {
|
if !field.CanSet() {
|
||||||
continue
|
continue
|
||||||
|
@ -115,12 +111,12 @@ func (h *Handler) Handle(ctx api.Context, data map[string]interface{}) (map[stri
|
||||||
callArgs = append(callArgs, callStruct)
|
callArgs = append(callArgs, callStruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
// call the HandlerFn
|
// call the handler
|
||||||
output := fnv.Call(callArgs)
|
output := fnv.Call(callArgs)
|
||||||
|
|
||||||
// no output OR pointer to output struct is nil
|
// no output OR pointer to output struct is nil
|
||||||
outdata := make(map[string]interface{})
|
outdata := make(map[string]interface{})
|
||||||
if len(h.spec.Output) < 1 || output[0].IsNil() {
|
if len(h.signature.Output) < 1 || output[0].IsNil() {
|
||||||
var structerr = output[len(output)-1].Convert(ert)
|
var structerr = output[len(output)-1].Convert(ert)
|
||||||
return outdata, api.Err{
|
return outdata, api.Err{
|
||||||
Code: int(structerr.FieldByName("Code").Int()),
|
Code: int(structerr.FieldByName("Code").Int()),
|
||||||
|
@ -132,7 +128,7 @@ func (h *Handler) Handle(ctx api.Context, data map[string]interface{}) (map[stri
|
||||||
// extract struct from pointer
|
// extract struct from pointer
|
||||||
returnStruct := output[0].Elem()
|
returnStruct := output[0].Elem()
|
||||||
|
|
||||||
for name := range h.spec.Output {
|
for name := range h.signature.Output {
|
||||||
field := returnStruct.FieldByName(name)
|
field := returnStruct.FieldByName(name)
|
||||||
outdata[name] = field.Interface()
|
outdata[name] = field.Interface()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"git.xdrm.io/go/aicra/api"
|
"git.xdrm.io/go/aicra/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testsignature signature
|
type testsignature Signature
|
||||||
|
|
||||||
// builds a mock service with provided arguments as Input and matched as Output
|
// builds a mock service with provided arguments as Input and matched as Output
|
||||||
func (s *testsignature) withArgs(dtypes ...reflect.Type) *testsignature {
|
func (s *testsignature) withArgs(dtypes ...reflect.Type) *testsignature {
|
||||||
|
@ -52,7 +52,7 @@ func TestInput(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "none required none provided",
|
Name: "none required none provided",
|
||||||
Spec: (&testsignature{}).withArgs(),
|
Spec: (&testsignature{}).withArgs(),
|
||||||
Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
Fn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
HasContext: false,
|
HasContext: false,
|
||||||
Input: []interface{}{},
|
Input: []interface{}{},
|
||||||
ExpectedOutput: []interface{}{},
|
ExpectedOutput: []interface{}{},
|
||||||
|
@ -61,7 +61,7 @@ func TestInput(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "int proxy (0)",
|
Name: "int proxy (0)",
|
||||||
Spec: (&testsignature{}).withArgs(reflect.TypeOf(int(0))),
|
Spec: (&testsignature{}).withArgs(reflect.TypeOf(int(0))),
|
||||||
Fn: func(in intstruct) (*intstruct, api.Err) {
|
Fn: func(ctx *api.Context, in intstruct) (*intstruct, api.Err) {
|
||||||
return &intstruct{P1: in.P1}, api.ErrSuccess
|
return &intstruct{P1: in.P1}, api.ErrSuccess
|
||||||
},
|
},
|
||||||
HasContext: false,
|
HasContext: false,
|
||||||
|
@ -72,7 +72,7 @@ func TestInput(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "int proxy (11)",
|
Name: "int proxy (11)",
|
||||||
Spec: (&testsignature{}).withArgs(reflect.TypeOf(int(0))),
|
Spec: (&testsignature{}).withArgs(reflect.TypeOf(int(0))),
|
||||||
Fn: func(in intstruct) (*intstruct, api.Err) {
|
Fn: func(ctx *api.Context, in intstruct) (*intstruct, api.Err) {
|
||||||
return &intstruct{P1: in.P1}, api.ErrSuccess
|
return &intstruct{P1: in.P1}, api.ErrSuccess
|
||||||
},
|
},
|
||||||
HasContext: false,
|
HasContext: false,
|
||||||
|
@ -83,7 +83,7 @@ func TestInput(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "*int proxy (nil)",
|
Name: "*int proxy (nil)",
|
||||||
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
|
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
|
||||||
Fn: func(in intptrstruct) (*intptrstruct, api.Err) {
|
Fn: func(ctx *api.Context, in intptrstruct) (*intptrstruct, api.Err) {
|
||||||
return &intptrstruct{P1: in.P1}, api.ErrSuccess
|
return &intptrstruct{P1: in.P1}, api.ErrSuccess
|
||||||
},
|
},
|
||||||
HasContext: false,
|
HasContext: false,
|
||||||
|
@ -94,7 +94,7 @@ func TestInput(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "*int proxy (28)",
|
Name: "*int proxy (28)",
|
||||||
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
|
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
|
||||||
Fn: func(in intptrstruct) (*intstruct, api.Err) {
|
Fn: func(ctx *api.Context, in intptrstruct) (*intstruct, api.Err) {
|
||||||
return &intstruct{P1: *in.P1}, api.ErrSuccess
|
return &intstruct{P1: *in.P1}, api.ErrSuccess
|
||||||
},
|
},
|
||||||
HasContext: false,
|
HasContext: false,
|
||||||
|
@ -105,7 +105,7 @@ func TestInput(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "*int proxy (13)",
|
Name: "*int proxy (13)",
|
||||||
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
|
Spec: (&testsignature{}).withArgs(reflect.TypeOf(new(int))),
|
||||||
Fn: func(in intptrstruct) (*intstruct, api.Err) {
|
Fn: func(ctx *api.Context, in intptrstruct) (*intstruct, api.Err) {
|
||||||
return &intstruct{P1: *in.P1}, api.ErrSuccess
|
return &intstruct{P1: *in.P1}, api.ErrSuccess
|
||||||
},
|
},
|
||||||
HasContext: false,
|
HasContext: false,
|
||||||
|
@ -119,16 +119,9 @@ func TestInput(t *testing.T) {
|
||||||
t.Run(tcase.Name, func(t *testing.T) {
|
t.Run(tcase.Name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var dataIndex = 0
|
|
||||||
if tcase.HasContext {
|
|
||||||
dataIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var handler = &Handler{
|
var handler = &Handler{
|
||||||
spec: &signature{Input: tcase.Spec.Input, Output: tcase.Spec.Output},
|
signature: &Signature{Input: tcase.Spec.Input, Output: tcase.Spec.Output},
|
||||||
fn: tcase.Fn,
|
fn: tcase.Fn,
|
||||||
dataIndex: dataIndex,
|
|
||||||
hasContext: tcase.HasContext,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// build input
|
// build input
|
||||||
|
@ -138,7 +131,7 @@ func TestInput(t *testing.T) {
|
||||||
input[key] = val
|
input[key] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
var output, err = handler.Handle(api.Context{}, input)
|
var output, err = handler.Handle(&api.Context{}, input)
|
||||||
if err != tcase.ExpectedErr {
|
if err != tcase.ExpectedErr {
|
||||||
t.Fatalf("expected api error <%v> got <%v>", tcase.ExpectedErr, err)
|
t.Fatalf("expected api error <%v> got <%v>", tcase.ExpectedErr, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,17 @@ import (
|
||||||
"git.xdrm.io/go/aicra/internal/config"
|
"git.xdrm.io/go/aicra/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// signature represents input/output arguments for a dynamic function
|
// Signature represents input/output arguments for service from the aicra configuration
|
||||||
type signature struct {
|
type Signature struct {
|
||||||
|
// Input arguments of the service
|
||||||
Input map[string]reflect.Type
|
Input map[string]reflect.Type
|
||||||
|
// Output arguments of the service
|
||||||
Output map[string]reflect.Type
|
Output map[string]reflect.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// builds a spec from the configuration service
|
// BuildSignature builds a signature for a service configuration
|
||||||
func signatureFromService(service config.Service) *signature {
|
func BuildSignature(service config.Service) *Signature {
|
||||||
s := &signature{
|
s := &Signature{
|
||||||
Input: make(map[string]reflect.Type),
|
Input: make(map[string]reflect.Type),
|
||||||
Output: make(map[string]reflect.Type),
|
Output: make(map[string]reflect.Type),
|
||||||
}
|
}
|
||||||
|
@ -44,31 +46,32 @@ func signatureFromService(service config.Service) *signature {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks for HandlerFn input arguments
|
// ValidateInput validates a handler's input arguments against the service signature
|
||||||
func (s *signature) checkInput(impl reflect.Type, index int) error {
|
func (s *Signature) ValidateInput(handlerType reflect.Type) error {
|
||||||
var requiredInput, structIndex = index, index
|
ctxType := reflect.TypeOf(api.Context{})
|
||||||
if len(s.Input) > 0 { // arguments struct
|
|
||||||
requiredInput++
|
// missing or invalid first arg: api.Context
|
||||||
|
if handlerType.NumIn() < 1 || ctxType.AssignableTo(handlerType.In(0)) {
|
||||||
|
return errMissingHandlerContextArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
// missing arguments
|
// no input required
|
||||||
if impl.NumIn() > requiredInput {
|
if len(s.Input) == 0 {
|
||||||
|
// input struct provided
|
||||||
|
if handlerType.NumIn() > 1 {
|
||||||
return errUnexpectedInput
|
return errUnexpectedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
// none required
|
|
||||||
if len(s.Input) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// too much arguments
|
// too much arguments
|
||||||
if impl.NumIn() != requiredInput {
|
if handlerType.NumIn() > 2 {
|
||||||
return errMissingHandlerArgumentParam
|
return errMissingHandlerInputArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
// arg must be a struct
|
// arg must be a struct
|
||||||
structArg := impl.In(structIndex)
|
inStruct := handlerType.In(1)
|
||||||
if structArg.Kind() != reflect.Struct {
|
if inStruct.Kind() != reflect.Struct {
|
||||||
return errMissingParamArgument
|
return errMissingParamArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +81,9 @@ func (s *signature) checkInput(impl reflect.Type, index int) error {
|
||||||
return fmt.Errorf("%s: %w", name, errUnexportedName)
|
return fmt.Errorf("%s: %w", name, errUnexportedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
field, exists := structArg.FieldByName(name)
|
field, exists := inStruct.FieldByName(name)
|
||||||
if !exists {
|
if !exists {
|
||||||
return fmt.Errorf("%s: %w", name, errMissingParamFromConfig)
|
return fmt.Errorf("%s: %w", name, errMissingConfigArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ptype.AssignableTo(field.Type) {
|
if !ptype.AssignableTo(field.Type) {
|
||||||
|
@ -91,16 +94,18 @@ func (s *signature) checkInput(impl reflect.Type, index int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks for HandlerFn output arguments
|
// ValidateOutput validates a handler's output arguments against the service signature
|
||||||
func (s signature) checkOutput(impl reflect.Type) error {
|
func (s Signature) ValidateOutput(handlerType reflect.Type) error {
|
||||||
if impl.NumOut() < 1 {
|
errType := reflect.TypeOf(api.ErrUnknown)
|
||||||
return errMissingHandlerOutput
|
|
||||||
|
if handlerType.NumOut() < 1 {
|
||||||
|
return errMissingHandlerErrorArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
// last output must be api.Err
|
// last output must be api.Err
|
||||||
errOutput := impl.Out(impl.NumOut() - 1)
|
lastArgType := handlerType.Out(handlerType.NumOut() - 1)
|
||||||
if !errOutput.AssignableTo(reflect.TypeOf(api.ErrUnknown)) {
|
if !lastArgType.AssignableTo(errType) {
|
||||||
return errMissingHandlerErrorOutput
|
return errMissingHandlerErrorArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
// no output -> ok
|
// no output -> ok
|
||||||
|
@ -108,19 +113,19 @@ func (s signature) checkOutput(impl reflect.Type) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if impl.NumOut() != 2 {
|
if handlerType.NumOut() < 2 {
|
||||||
return errMissingParamOutput
|
return errMissingHandlerOutputArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
// fail if first output is not a pointer to struct
|
// fail if first output is not a pointer to struct
|
||||||
structOutputPtr := impl.Out(0)
|
outStructPtr := handlerType.Out(0)
|
||||||
if structOutputPtr.Kind() != reflect.Ptr {
|
if outStructPtr.Kind() != reflect.Ptr {
|
||||||
return errMissingParamOutput
|
return errWrongOutputArgumentType
|
||||||
}
|
}
|
||||||
|
|
||||||
structOutput := structOutputPtr.Elem()
|
outStruct := outStructPtr.Elem()
|
||||||
if structOutput.Kind() != reflect.Struct {
|
if outStruct.Kind() != reflect.Struct {
|
||||||
return errMissingParamOutput
|
return errWrongOutputArgumentType
|
||||||
}
|
}
|
||||||
|
|
||||||
// fail on invalid output
|
// fail on invalid output
|
||||||
|
@ -129,9 +134,9 @@ func (s signature) checkOutput(impl reflect.Type) error {
|
||||||
return fmt.Errorf("%s: %w", name, errUnexportedName)
|
return fmt.Errorf("%s: %w", name, errUnexportedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
field, exists := structOutput.FieldByName(name)
|
field, exists := outStruct.FieldByName(name)
|
||||||
if !exists {
|
if !exists {
|
||||||
return fmt.Errorf("%s: %w", name, errMissingOutputFromConfig)
|
return fmt.Errorf("%s: %w", name, errMissingConfigArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore types evalutating to nil
|
// ignore types evalutating to nil
|
||||||
|
|
|
@ -20,22 +20,22 @@ func TestInputCheck(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "no input 0 given",
|
Name: "no input 0 given",
|
||||||
Input: map[string]reflect.Type{},
|
Input: map[string]reflect.Type{},
|
||||||
Fn: func() {},
|
Fn: func(*api.Context) {},
|
||||||
FnCtx: func(api.Context) {},
|
FnCtx: func(*api.Context) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "no input 1 given",
|
Name: "no input 1 given",
|
||||||
Input: map[string]reflect.Type{},
|
Input: map[string]reflect.Type{},
|
||||||
Fn: func(int) {},
|
Fn: func(*api.Context, int) {},
|
||||||
FnCtx: func(api.Context, int) {},
|
FnCtx: func(*api.Context, int) {},
|
||||||
Err: errUnexpectedInput,
|
Err: errUnexpectedInput,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "no input 2 given",
|
Name: "no input 2 given",
|
||||||
Input: map[string]reflect.Type{},
|
Input: map[string]reflect.Type{},
|
||||||
Fn: func(int, string) {},
|
Fn: func(*api.Context, int, string) {},
|
||||||
FnCtx: func(api.Context, int, string) {},
|
FnCtx: func(*api.Context, int, string) {},
|
||||||
Err: errUnexpectedInput,
|
Err: errUnexpectedInput,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -43,17 +43,17 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() {},
|
Fn: func(*api.Context) {},
|
||||||
FnCtx: func(api.Context) {},
|
FnCtx: func(*api.Context) {},
|
||||||
Err: errMissingHandlerArgumentParam,
|
Err: errMissingHandlerInputArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input non-struct given",
|
Name: "1 input non-struct given",
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(int) {},
|
Fn: func(*api.Context, int) {},
|
||||||
FnCtx: func(api.Context, int) {},
|
FnCtx: func(*api.Context, int) {},
|
||||||
Err: errMissingParamArgument,
|
Err: errMissingParamArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -61,8 +61,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"test1": reflect.TypeOf(int(0)),
|
"test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{}) {},
|
Fn: func(*api.Context, struct{}) {},
|
||||||
FnCtx: func(api.Context, struct{}) {},
|
FnCtx: func(*api.Context, struct{}) {},
|
||||||
Err: errUnexportedName,
|
Err: errUnexportedName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -70,17 +70,17 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{}) {},
|
Fn: func(*api.Context, struct{}) {},
|
||||||
FnCtx: func(api.Context, struct{}) {},
|
FnCtx: func(*api.Context, struct{}) {},
|
||||||
Err: errMissingParamFromConfig,
|
Err: errMissingConfigArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input invalid given",
|
Name: "1 input invalid given",
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 string }) {},
|
Fn: func(*api.Context, struct{ Test1 string }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 string }) {},
|
FnCtx: func(*api.Context, struct{ Test1 string }) {},
|
||||||
Err: errWrongParamTypeFromConfig,
|
Err: errWrongParamTypeFromConfig,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -88,8 +88,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 int }) {},
|
Fn: func(*api.Context, struct{ Test1 int }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 int }) {},
|
FnCtx: func(*api.Context, struct{ Test1 int }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -97,17 +97,17 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(int)),
|
"Test1": reflect.TypeOf(new(int)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{}) {},
|
Fn: func(*api.Context, struct{}) {},
|
||||||
FnCtx: func(api.Context, struct{}) {},
|
FnCtx: func(*api.Context, struct{}) {},
|
||||||
Err: errMissingParamFromConfig,
|
Err: errMissingConfigArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "1 input ptr invalid given",
|
Name: "1 input ptr invalid given",
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(int)),
|
"Test1": reflect.TypeOf(new(int)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 string }) {},
|
Fn: func(*api.Context, struct{ Test1 string }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 string }) {},
|
FnCtx: func(*api.Context, struct{ Test1 string }) {},
|
||||||
Err: errWrongParamTypeFromConfig,
|
Err: errWrongParamTypeFromConfig,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -115,8 +115,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(int)),
|
"Test1": reflect.TypeOf(new(int)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 *string }) {},
|
Fn: func(*api.Context, struct{ Test1 *string }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 *string }) {},
|
FnCtx: func(*api.Context, struct{ Test1 *string }) {},
|
||||||
Err: errWrongParamTypeFromConfig,
|
Err: errWrongParamTypeFromConfig,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -124,8 +124,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(int)),
|
"Test1": reflect.TypeOf(new(int)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 *int }) {},
|
Fn: func(*api.Context, struct{ Test1 *int }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 *int }) {},
|
FnCtx: func(*api.Context, struct{ Test1 *int }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -133,8 +133,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(string("")),
|
"Test1": reflect.TypeOf(string("")),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 string }) {},
|
Fn: func(*api.Context, struct{ Test1 string }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 string }) {},
|
FnCtx: func(*api.Context, struct{ Test1 string }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -142,8 +142,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(uint(0)),
|
"Test1": reflect.TypeOf(uint(0)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 uint }) {},
|
Fn: func(*api.Context, struct{ Test1 uint }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 uint }) {},
|
FnCtx: func(*api.Context, struct{ Test1 uint }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -151,8 +151,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(float64(0)),
|
"Test1": reflect.TypeOf(float64(0)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 float64 }) {},
|
Fn: func(*api.Context, struct{ Test1 float64 }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 float64 }) {},
|
FnCtx: func(*api.Context, struct{ Test1 float64 }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -160,8 +160,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf([]byte("")),
|
"Test1": reflect.TypeOf([]byte("")),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 []byte }) {},
|
Fn: func(*api.Context, struct{ Test1 []byte }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 []byte }) {},
|
FnCtx: func(*api.Context, struct{ Test1 []byte }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -169,8 +169,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf([]rune("")),
|
"Test1": reflect.TypeOf([]rune("")),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 []rune }) {},
|
Fn: func(*api.Context, struct{ Test1 []rune }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 []rune }) {},
|
FnCtx: func(*api.Context, struct{ Test1 []rune }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -178,8 +178,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(string)),
|
"Test1": reflect.TypeOf(new(string)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 *string }) {},
|
Fn: func(*api.Context, struct{ Test1 *string }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 *string }) {},
|
FnCtx: func(*api.Context, struct{ Test1 *string }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -187,8 +187,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(uint)),
|
"Test1": reflect.TypeOf(new(uint)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 *uint }) {},
|
Fn: func(*api.Context, struct{ Test1 *uint }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 *uint }) {},
|
FnCtx: func(*api.Context, struct{ Test1 *uint }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -196,8 +196,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new(float64)),
|
"Test1": reflect.TypeOf(new(float64)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 *float64 }) {},
|
Fn: func(*api.Context, struct{ Test1 *float64 }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 *float64 }) {},
|
FnCtx: func(*api.Context, struct{ Test1 *float64 }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -205,8 +205,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new([]byte)),
|
"Test1": reflect.TypeOf(new([]byte)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 *[]byte }) {},
|
Fn: func(*api.Context, struct{ Test1 *[]byte }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 *[]byte }) {},
|
FnCtx: func(*api.Context, struct{ Test1 *[]byte }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -214,8 +214,8 @@ func TestInputCheck(t *testing.T) {
|
||||||
Input: map[string]reflect.Type{
|
Input: map[string]reflect.Type{
|
||||||
"Test1": reflect.TypeOf(new([]rune)),
|
"Test1": reflect.TypeOf(new([]rune)),
|
||||||
},
|
},
|
||||||
Fn: func(struct{ Test1 *[]rune }) {},
|
Fn: func(*api.Context, struct{ Test1 *[]rune }) {},
|
||||||
FnCtx: func(api.Context, struct{ Test1 *[]rune }) {},
|
FnCtx: func(*api.Context, struct{ Test1 *[]rune }) {},
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -225,13 +225,12 @@ func TestInputCheck(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// mock spec
|
// mock spec
|
||||||
s := signature{
|
s := Signature{
|
||||||
Input: tcase.Input,
|
Input: tcase.Input,
|
||||||
Output: nil,
|
Output: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("with-context", func(t *testing.T) {
|
err := s.ValidateInput(reflect.TypeOf(tcase.FnCtx))
|
||||||
err := s.checkInput(reflect.TypeOf(tcase.FnCtx), 1)
|
|
||||||
if err == nil && tcase.Err != nil {
|
if err == nil && tcase.Err != nil {
|
||||||
t.Errorf("expected an error: '%s'", tcase.Err.Error())
|
t.Errorf("expected an error: '%s'", tcase.Err.Error())
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -248,25 +247,6 @@ func TestInputCheck(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("without-context", func(t *testing.T) {
|
|
||||||
err := s.checkInput(reflect.TypeOf(tcase.Fn), 0)
|
|
||||||
if err == nil && tcase.Err != nil {
|
|
||||||
t.Errorf("expected an error: '%s'", tcase.Err.Error())
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if err != nil && tcase.Err == nil {
|
|
||||||
t.Errorf("unexpected error: '%s'", err.Error())
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && tcase.Err != nil {
|
|
||||||
if !errors.Is(err, tcase.Err) {
|
|
||||||
t.Errorf("expected the error <%s> got <%s>", tcase.Err, err)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,25 +259,37 @@ func TestOutputCheck(t *testing.T) {
|
||||||
// no input -> missing api.Err
|
// no input -> missing api.Err
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{},
|
Output: map[string]reflect.Type{},
|
||||||
Fn: func() {},
|
Fn: func(*api.Context) {},
|
||||||
Err: errMissingHandlerOutput,
|
Err: errMissingHandlerOutputArgument,
|
||||||
},
|
},
|
||||||
// no input -> with last type not api.Err
|
// no input -> with last type not api.Err
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{},
|
Output: map[string]reflect.Type{},
|
||||||
Fn: func() bool { return true },
|
Fn: func(*api.Context) bool { return true },
|
||||||
Err: errMissingHandlerErrorOutput,
|
Err: errMissingHandlerErrorArgument,
|
||||||
},
|
},
|
||||||
// no input -> with api.Err
|
// no input -> with api.Err
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{},
|
Output: map[string]reflect.Type{},
|
||||||
Fn: func() api.Err { return api.ErrSuccess },
|
Fn: func(*api.Context) api.Err { return api.ErrSuccess },
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
|
// no input -> missing *api.Context
|
||||||
|
{
|
||||||
|
Output: map[string]reflect.Type{},
|
||||||
|
Fn: func(*api.Context) api.Err { return api.ErrSuccess },
|
||||||
|
Err: errMissingHandlerContextArgument,
|
||||||
|
},
|
||||||
|
// no input -> invlaid *api.Context type
|
||||||
|
{
|
||||||
|
Output: map[string]reflect.Type{},
|
||||||
|
Fn: func(*api.Context, int) api.Err { return api.ErrSuccess },
|
||||||
|
Err: errMissingHandlerContextArgument,
|
||||||
|
},
|
||||||
// func can have output if not specified
|
// func can have output if not specified
|
||||||
{
|
{
|
||||||
Output: map[string]reflect.Type{},
|
Output: map[string]reflect.Type{},
|
||||||
Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
Fn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
// missing output struct in func
|
// missing output struct in func
|
||||||
|
@ -306,7 +298,7 @@ func TestOutputCheck(t *testing.T) {
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() api.Err { return api.ErrSuccess },
|
Fn: func() api.Err { return api.ErrSuccess },
|
||||||
Err: errMissingParamOutput,
|
Err: errWrongOutputArgumentType,
|
||||||
},
|
},
|
||||||
// output not a pointer
|
// output not a pointer
|
||||||
{
|
{
|
||||||
|
@ -314,7 +306,7 @@ func TestOutputCheck(t *testing.T) {
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (int, api.Err) { return 0, api.ErrSuccess },
|
Fn: func() (int, api.Err) { return 0, api.ErrSuccess },
|
||||||
Err: errMissingParamOutput,
|
Err: errWrongOutputArgumentType,
|
||||||
},
|
},
|
||||||
// output not a pointer to struct
|
// output not a pointer to struct
|
||||||
{
|
{
|
||||||
|
@ -322,7 +314,7 @@ func TestOutputCheck(t *testing.T) {
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (*int, api.Err) { return nil, api.ErrSuccess },
|
Fn: func() (*int, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: errMissingParamOutput,
|
Err: errWrongOutputArgumentType,
|
||||||
},
|
},
|
||||||
// unexported param name
|
// unexported param name
|
||||||
{
|
{
|
||||||
|
@ -338,7 +330,7 @@ func TestOutputCheck(t *testing.T) {
|
||||||
"Test1": reflect.TypeOf(int(0)),
|
"Test1": reflect.TypeOf(int(0)),
|
||||||
},
|
},
|
||||||
Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess },
|
||||||
Err: errMissingParamFromConfig,
|
Err: errMissingConfigArgument,
|
||||||
},
|
},
|
||||||
// output field invalid type
|
// output field invalid type
|
||||||
{
|
{
|
||||||
|
@ -371,12 +363,12 @@ func TestOutputCheck(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// mock spec
|
// mock spec
|
||||||
s := signature{
|
s := Signature{
|
||||||
Input: nil,
|
Input: nil,
|
||||||
Output: tcase.Output,
|
Output: tcase.Output,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.checkOutput(reflect.TypeOf(tcase.Fn))
|
err := s.ValidateOutput(reflect.TypeOf(tcase.Fn))
|
||||||
if err == nil && tcase.Err != nil {
|
if err == nil && tcase.Err != nil {
|
||||||
t.Errorf("expected an error: '%s'", tcase.Err.Error())
|
t.Errorf("expected an error: '%s'", tcase.Err.Error())
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
|
Loading…
Reference in New Issue