From 53dfc8f67974d5db711822b0378a74b4063e1e74 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 20 Jun 2021 00:47:04 +0200 Subject: [PATCH] feat: *api.Context is required as first handler argument --- builder_test.go | 16 +-- handler.go | 16 ++- handler_test.go | 22 ++-- internal/dynfunc/errors.go | 26 ++--- internal/dynfunc/handler.go | 90 +++++++------- internal/dynfunc/handler_test.go | 27 ++--- internal/dynfunc/signature.go | 85 +++++++------- internal/dynfunc/signature_test.go | 182 ++++++++++++++--------------- 8 files changed, 229 insertions(+), 235 deletions(-) diff --git a/builder_test.go b/builder_test.go index ab98dca..c123f3a 100644 --- a/builder_test.go +++ b/builder_test.go @@ -72,7 +72,7 @@ func TestBind(t *testing.T) { Config: "[]", HandlerMethod: "", HandlerPath: "", - HandlerFn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess }, + HandlerFn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess }, BindErr: errUnknownService, BuildErr: nil, }, @@ -108,7 +108,7 @@ func TestBind(t *testing.T) { ]`, HandlerMethod: http.MethodPost, HandlerPath: "/path", - HandlerFn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess }, + HandlerFn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess }, BindErr: errUnknownService, BuildErr: errMissingHandler, }, @@ -126,7 +126,7 @@ func TestBind(t *testing.T) { ]`, HandlerMethod: http.MethodGet, HandlerPath: "/paths", - HandlerFn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess }, + HandlerFn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess }, BindErr: errUnknownService, BuildErr: errMissingHandler, }, @@ -144,7 +144,7 @@ func TestBind(t *testing.T) { ]`, HandlerMethod: http.MethodGet, HandlerPath: "/path", - HandlerFn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess }, + HandlerFn: func(*api.Context) (*struct{}, api.Err) { return nil, api.ErrSuccess }, BindErr: nil, BuildErr: nil, }, @@ -164,7 +164,7 @@ func TestBind(t *testing.T) { ]`, HandlerMethod: http.MethodGet, 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, BuildErr: nil, }, @@ -184,7 +184,7 @@ func TestBind(t *testing.T) { ]`, HandlerMethod: http.MethodGet, 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, BuildErr: nil, }, @@ -204,7 +204,7 @@ func TestBind(t *testing.T) { ]`, HandlerMethod: http.MethodGet, 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, BuildErr: nil, }, @@ -224,7 +224,7 @@ func TestBind(t *testing.T) { ]`, HandlerMethod: http.MethodGet, 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, BuildErr: nil, }, diff --git a/handler.go b/handler.go index 7bda72e..d91f5e7 100644 --- a/handler.go +++ b/handler.go @@ -1,12 +1,14 @@ package aicra import ( + "context" "fmt" "net/http" "strings" "git.xdrm.io/go/aicra/api" "git.xdrm.io/go/aicra/internal/config" + "git.xdrm.io/go/aicra/internal/ctx" "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) { - // 5. pass execution to the handler - ctx := api.Context{Res: w, Req: r} - var outData, outErr = handler.dyn.Handle(ctx, input.Data) + // build context with builtin data + c := r.Context() + 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) for key, value := range outData { diff --git a/handler_test.go b/handler_test.go index 7c951b7..be2fdb7 100644 --- a/handler_test.go +++ b/handler_test.go @@ -82,9 +82,9 @@ func TestWith(t *testing.T) { 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 - value := ctx.Req.Context().Value(key) + value := ctx.Value(key) if value == nil { t.Fatalf("nothing found in context") } @@ -93,7 +93,7 @@ func TestWith(t *testing.T) { t.Fatalf("cannot cast context data to int") } // write to response - ctx.Res.Write([]byte(fmt.Sprintf("#%d#", cast))) + ctx.ResponseWriter().Write([]byte(fmt.Sprintf("#%d#", cast))) return nil, api.ErrSuccess } @@ -237,7 +237,7 @@ func TestWithAuth(t *testing.T) { 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 } @@ -290,7 +290,7 @@ func TestDynamicScope(t *testing.T) { } ]`, 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", body: ``, permissions: []string{"user[123]"}, @@ -311,7 +311,7 @@ func TestDynamicScope(t *testing.T) { } ]`, 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", body: ``, permissions: []string{"user[123]"}, @@ -332,7 +332,7 @@ func TestDynamicScope(t *testing.T) { } ]`, 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", body: ``, permissions: []string{"prefix.user[123].suffix"}, @@ -354,7 +354,7 @@ func TestDynamicScope(t *testing.T) { } ]`, path: "/prefix/{pid}/user/{uid}", - handler: func(struct { + handler: func(*api.Context, struct { Prefix uint User uint }) (*struct{}, api.Err) { @@ -381,7 +381,7 @@ func TestDynamicScope(t *testing.T) { } ]`, path: "/prefix/{pid}/user/{uid}", - handler: func(struct { + handler: func(*api.Context, struct { Prefix uint User uint }) (*struct{}, api.Err) { @@ -409,7 +409,7 @@ func TestDynamicScope(t *testing.T) { } ]`, path: "/prefix/{pid}/user/{uid}/suffix/{sid}", - handler: func(struct { + handler: func(*api.Context, struct { Prefix uint User uint Suffix uint @@ -438,7 +438,7 @@ func TestDynamicScope(t *testing.T) { } ]`, path: "/prefix/{pid}/user/{uid}/suffix/{sid}", - handler: func(struct { + handler: func(*api.Context, struct { Prefix uint User uint Suffix uint diff --git a/internal/dynfunc/errors.go b/internal/dynfunc/errors.go index ca87692..7d45b64 100644 --- a/internal/dynfunc/errors.go +++ b/internal/dynfunc/errors.go @@ -14,16 +14,19 @@ const errHandlerNotFunc = cerr("handler must be a func") const errNoServiceForHandler = cerr("no service found for this 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 const errUnexpectedInput = cerr("unexpected input struct") -// errMissingHandlerOutput - missing output for handler -const errMissingHandlerOutput = cerr("handler must have at least 1 output") +// errMissingHandlerOutputArgument - missing output for handler +const errMissingHandlerOutputArgument = cerr("missing handler first output argument: output struct") // 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 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 const errUnexportedName = cerr("unexported name") -// errMissingParamOutput - missing output argument for handler -const errMissingParamOutput = cerr("handler first output must be a *struct") +// errWrongOutputArgumentType - wrong type for output first argument +const errWrongOutputArgumentType = cerr("handler first output argument must be a *struct") -// errMissingParamFromConfig - missing a parameter in handler struct -const errMissingParamFromConfig = cerr("missing a parameter from configuration") - -// errMissingOutputFromConfig - missing a parameter in handler struct -const errMissingOutputFromConfig = cerr("missing a parameter from configuration") +// errMissingConfigArgument - missing an input/output argument in handler struct +const errMissingConfigArgument = cerr("missing an argument from the configuration") // errWrongParamTypeFromConfig - a configuration parameter type is invalid in the handler param struct const errWrongParamTypeFromConfig = cerr("invalid struct field type") -// errMissingHandlerErrorOutput - missing handler output error -const errMissingHandlerErrorOutput = cerr("last output must be of type api.Err") +// errMissingHandlerErrorArgument - missing handler output error +const errMissingHandlerErrorArgument = cerr("last output must be of type api.Err") diff --git a/internal/dynfunc/handler.go b/internal/dynfunc/handler.go index b4a371a..dd3bcdc 100644 --- a/internal/dynfunc/handler.go +++ b/internal/dynfunc/handler.go @@ -9,73 +9,69 @@ import ( "git.xdrm.io/go/aicra/internal/config" ) -// Handler represents a dynamic api handler +// Handler represents a dynamic aicra service handler type Handler struct { - spec *signature - fn interface{} - // whether fn uses api.Ctx as 1st argument - hasContext bool - // index in input arguments where the data struct must be - dataIndex int + // signature defined from the service configuration + signature *Signature + // fn provided function that will be the service's handler + fn interface{} } -// Build a handler from a service configuration and a dynamic function -// -// @fn must have as a signature : `func(inputStruct) (*outputStruct, api.Err)` -// - `inputStruct` is a struct{} containing a field for each service input (with valid reflect.Type) -// - `outputStruct` is a struct{} containing a field for each service output (with valid reflect.Type) +// Build a handler from a dynamic function and checks its signature against a +// service configuration +//e +// `fn` must have as a signature : `func(*api.Context, in) (*out, api.Err)` +// - `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: -// - 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 output, `outputStruct` must be omitted +// - it there is no input, `in` MUST be omitted +// - it there is no output, `out` MUST be omitted func Build(fn interface{}, service config.Service) (*Handler, error) { - h := &Handler{ - spec: signatureFromService(service), - fn: fn, - } + var ( + h = &Handler{ + signature: BuildSignature(service), + fn: fn, + } + fnType = reflect.TypeOf(fn) + ) - impl := reflect.TypeOf(fn) - - if impl.Kind() != reflect.Func { + if fnType.Kind() != reflect.Func { return nil, errHandlerNotFunc } - - 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 { + if err := h.signature.ValidateInput(fnType); err != nil { 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 h, nil } -// Handle binds input @data into the dynamic function and returns map output -func (h *Handler) Handle(ctx api.Context, data map[string]interface{}) (map[string]interface{}, api.Err) { - var ert = reflect.TypeOf(api.Err{}) - var fnv = reflect.ValueOf(h.fn) +// 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) { + var ( + ert = reflect.TypeOf(api.Err{}) + fnv = reflect.ValueOf(h.fn) + callArgs = make([]reflect.Value, 0) + ) - callArgs := []reflect.Value{} + // bind context + callArgs = append(callArgs, reflect.ValueOf(ctx)) - // bind context if used in handler - if h.hasContext { - callArgs = append(callArgs, reflect.ValueOf(ctx)) - } + inputStructRequired := fnv.Type().NumIn() > 1 - // bind input data - if fnv.Type().NumIn() > h.dataIndex { + // bind input arguments + if inputStructRequired { // create zero value struct - callStructPtr := reflect.New(fnv.Type().In(0)) - callStruct := callStructPtr.Elem() + var ( + callStructPtr = reflect.New(fnv.Type().In(1)) + callStruct = callStructPtr.Elem() + ) // set each field - for name := range h.spec.Input { + for name := range h.signature.Input { field := callStruct.FieldByName(name) if !field.CanSet() { continue @@ -115,12 +111,12 @@ func (h *Handler) Handle(ctx api.Context, data map[string]interface{}) (map[stri callArgs = append(callArgs, callStruct) } - // call the HandlerFn + // call the handler output := fnv.Call(callArgs) // no output OR pointer to output struct is nil 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) return outdata, api.Err{ 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 returnStruct := output[0].Elem() - for name := range h.spec.Output { + for name := range h.signature.Output { field := returnStruct.FieldByName(name) outdata[name] = field.Interface() } diff --git a/internal/dynfunc/handler_test.go b/internal/dynfunc/handler_test.go index d9167c4..851e2f2 100644 --- a/internal/dynfunc/handler_test.go +++ b/internal/dynfunc/handler_test.go @@ -8,7 +8,7 @@ import ( "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 func (s *testsignature) withArgs(dtypes ...reflect.Type) *testsignature { @@ -52,7 +52,7 @@ func TestInput(t *testing.T) { { Name: "none required none provided", 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, Input: []interface{}{}, ExpectedOutput: []interface{}{}, @@ -61,7 +61,7 @@ func TestInput(t *testing.T) { { Name: "int proxy (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 }, HasContext: false, @@ -72,7 +72,7 @@ func TestInput(t *testing.T) { { Name: "int proxy (11)", 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 }, HasContext: false, @@ -83,7 +83,7 @@ func TestInput(t *testing.T) { { Name: "*int proxy (nil)", 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 }, HasContext: false, @@ -94,7 +94,7 @@ func TestInput(t *testing.T) { { Name: "*int proxy (28)", 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 }, HasContext: false, @@ -105,7 +105,7 @@ func TestInput(t *testing.T) { { Name: "*int proxy (13)", 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 }, HasContext: false, @@ -119,16 +119,9 @@ func TestInput(t *testing.T) { 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, + signature: &Signature{Input: tcase.Spec.Input, Output: tcase.Spec.Output}, + fn: tcase.Fn, } // build input @@ -138,7 +131,7 @@ func TestInput(t *testing.T) { input[key] = val } - var output, err = handler.Handle(api.Context{}, input) + var output, err = handler.Handle(&api.Context{}, input) if err != tcase.ExpectedErr { t.Fatalf("expected api error <%v> got <%v>", tcase.ExpectedErr, err) } diff --git a/internal/dynfunc/signature.go b/internal/dynfunc/signature.go index 2ee32ae..6d33a33 100644 --- a/internal/dynfunc/signature.go +++ b/internal/dynfunc/signature.go @@ -9,15 +9,17 @@ import ( "git.xdrm.io/go/aicra/internal/config" ) -// signature represents input/output arguments for a dynamic function -type signature struct { - Input map[string]reflect.Type +// Signature represents input/output arguments for service from the aicra configuration +type Signature struct { + // Input arguments of the service + Input map[string]reflect.Type + // Output arguments of the service Output map[string]reflect.Type } -// builds a spec from the configuration service -func signatureFromService(service config.Service) *signature { - s := &signature{ +// BuildSignature builds a signature for a service configuration +func BuildSignature(service config.Service) *Signature { + s := &Signature{ Input: make(map[string]reflect.Type), Output: make(map[string]reflect.Type), } @@ -44,31 +46,32 @@ func signatureFromService(service config.Service) *signature { return s } -// checks for HandlerFn input arguments -func (s *signature) checkInput(impl reflect.Type, index int) error { - var requiredInput, structIndex = index, index - if len(s.Input) > 0 { // arguments struct - requiredInput++ +// ValidateInput validates a handler's input arguments against the service signature +func (s *Signature) ValidateInput(handlerType reflect.Type) error { + ctxType := reflect.TypeOf(api.Context{}) + + // missing or invalid first arg: api.Context + if handlerType.NumIn() < 1 || ctxType.AssignableTo(handlerType.In(0)) { + return errMissingHandlerContextArgument } - // missing arguments - if impl.NumIn() > requiredInput { - return errUnexpectedInput - } - - // none required + // no input required if len(s.Input) == 0 { + // input struct provided + if handlerType.NumIn() > 1 { + return errUnexpectedInput + } return nil } // too much arguments - if impl.NumIn() != requiredInput { - return errMissingHandlerArgumentParam + if handlerType.NumIn() > 2 { + return errMissingHandlerInputArgument } // arg must be a struct - structArg := impl.In(structIndex) - if structArg.Kind() != reflect.Struct { + inStruct := handlerType.In(1) + if inStruct.Kind() != reflect.Struct { return errMissingParamArgument } @@ -78,9 +81,9 @@ func (s *signature) checkInput(impl reflect.Type, index int) error { return fmt.Errorf("%s: %w", name, errUnexportedName) } - field, exists := structArg.FieldByName(name) + field, exists := inStruct.FieldByName(name) if !exists { - return fmt.Errorf("%s: %w", name, errMissingParamFromConfig) + return fmt.Errorf("%s: %w", name, errMissingConfigArgument) } if !ptype.AssignableTo(field.Type) { @@ -91,16 +94,18 @@ func (s *signature) checkInput(impl reflect.Type, index int) error { return nil } -// checks for HandlerFn output arguments -func (s signature) checkOutput(impl reflect.Type) error { - if impl.NumOut() < 1 { - return errMissingHandlerOutput +// ValidateOutput validates a handler's output arguments against the service signature +func (s Signature) ValidateOutput(handlerType reflect.Type) error { + errType := reflect.TypeOf(api.ErrUnknown) + + if handlerType.NumOut() < 1 { + return errMissingHandlerErrorArgument } // last output must be api.Err - errOutput := impl.Out(impl.NumOut() - 1) - if !errOutput.AssignableTo(reflect.TypeOf(api.ErrUnknown)) { - return errMissingHandlerErrorOutput + lastArgType := handlerType.Out(handlerType.NumOut() - 1) + if !lastArgType.AssignableTo(errType) { + return errMissingHandlerErrorArgument } // no output -> ok @@ -108,19 +113,19 @@ func (s signature) checkOutput(impl reflect.Type) error { return nil } - if impl.NumOut() != 2 { - return errMissingParamOutput + if handlerType.NumOut() < 2 { + return errMissingHandlerOutputArgument } // fail if first output is not a pointer to struct - structOutputPtr := impl.Out(0) - if structOutputPtr.Kind() != reflect.Ptr { - return errMissingParamOutput + outStructPtr := handlerType.Out(0) + if outStructPtr.Kind() != reflect.Ptr { + return errWrongOutputArgumentType } - structOutput := structOutputPtr.Elem() - if structOutput.Kind() != reflect.Struct { - return errMissingParamOutput + outStruct := outStructPtr.Elem() + if outStruct.Kind() != reflect.Struct { + return errWrongOutputArgumentType } // fail on invalid output @@ -129,9 +134,9 @@ func (s signature) checkOutput(impl reflect.Type) error { return fmt.Errorf("%s: %w", name, errUnexportedName) } - field, exists := structOutput.FieldByName(name) + field, exists := outStruct.FieldByName(name) if !exists { - return fmt.Errorf("%s: %w", name, errMissingOutputFromConfig) + return fmt.Errorf("%s: %w", name, errMissingConfigArgument) } // ignore types evalutating to nil diff --git a/internal/dynfunc/signature_test.go b/internal/dynfunc/signature_test.go index 205f58e..e9e97b2 100644 --- a/internal/dynfunc/signature_test.go +++ b/internal/dynfunc/signature_test.go @@ -20,22 +20,22 @@ func TestInputCheck(t *testing.T) { { Name: "no input 0 given", Input: map[string]reflect.Type{}, - Fn: func() {}, - FnCtx: func(api.Context) {}, + Fn: func(*api.Context) {}, + FnCtx: func(*api.Context) {}, Err: nil, }, { Name: "no input 1 given", Input: map[string]reflect.Type{}, - Fn: func(int) {}, - FnCtx: func(api.Context, int) {}, + Fn: func(*api.Context, int) {}, + FnCtx: func(*api.Context, int) {}, Err: errUnexpectedInput, }, { Name: "no input 2 given", Input: map[string]reflect.Type{}, - Fn: func(int, string) {}, - FnCtx: func(api.Context, int, string) {}, + Fn: func(*api.Context, int, string) {}, + FnCtx: func(*api.Context, int, string) {}, Err: errUnexpectedInput, }, { @@ -43,17 +43,17 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, - Fn: func() {}, - FnCtx: func(api.Context) {}, - Err: errMissingHandlerArgumentParam, + Fn: func(*api.Context) {}, + FnCtx: func(*api.Context) {}, + Err: errMissingHandlerInputArgument, }, { Name: "1 input non-struct given", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, - Fn: func(int) {}, - FnCtx: func(api.Context, int) {}, + Fn: func(*api.Context, int) {}, + FnCtx: func(*api.Context, int) {}, Err: errMissingParamArgument, }, { @@ -61,8 +61,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "test1": reflect.TypeOf(int(0)), }, - Fn: func(struct{}) {}, - FnCtx: func(api.Context, struct{}) {}, + Fn: func(*api.Context, struct{}) {}, + FnCtx: func(*api.Context, struct{}) {}, Err: errUnexportedName, }, { @@ -70,17 +70,17 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, - Fn: func(struct{}) {}, - FnCtx: func(api.Context, struct{}) {}, - Err: errMissingParamFromConfig, + Fn: func(*api.Context, struct{}) {}, + FnCtx: func(*api.Context, struct{}) {}, + Err: errMissingConfigArgument, }, { Name: "1 input invalid given", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, - Fn: func(struct{ Test1 string }) {}, - FnCtx: func(api.Context, struct{ Test1 string }) {}, + Fn: func(*api.Context, struct{ Test1 string }) {}, + FnCtx: func(*api.Context, struct{ Test1 string }) {}, Err: errWrongParamTypeFromConfig, }, { @@ -88,8 +88,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(int(0)), }, - Fn: func(struct{ Test1 int }) {}, - FnCtx: func(api.Context, struct{ Test1 int }) {}, + Fn: func(*api.Context, struct{ Test1 int }) {}, + FnCtx: func(*api.Context, struct{ Test1 int }) {}, Err: nil, }, { @@ -97,17 +97,17 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new(int)), }, - Fn: func(struct{}) {}, - FnCtx: func(api.Context, struct{}) {}, - Err: errMissingParamFromConfig, + Fn: func(*api.Context, struct{}) {}, + FnCtx: func(*api.Context, struct{}) {}, + Err: errMissingConfigArgument, }, { Name: "1 input ptr invalid given", Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new(int)), }, - Fn: func(struct{ Test1 string }) {}, - FnCtx: func(api.Context, struct{ Test1 string }) {}, + Fn: func(*api.Context, struct{ Test1 string }) {}, + FnCtx: func(*api.Context, struct{ Test1 string }) {}, Err: errWrongParamTypeFromConfig, }, { @@ -115,8 +115,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new(int)), }, - Fn: func(struct{ Test1 *string }) {}, - FnCtx: func(api.Context, struct{ Test1 *string }) {}, + Fn: func(*api.Context, struct{ Test1 *string }) {}, + FnCtx: func(*api.Context, struct{ Test1 *string }) {}, Err: errWrongParamTypeFromConfig, }, { @@ -124,8 +124,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new(int)), }, - Fn: func(struct{ Test1 *int }) {}, - FnCtx: func(api.Context, struct{ Test1 *int }) {}, + Fn: func(*api.Context, struct{ Test1 *int }) {}, + FnCtx: func(*api.Context, struct{ Test1 *int }) {}, Err: nil, }, { @@ -133,8 +133,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(string("")), }, - Fn: func(struct{ Test1 string }) {}, - FnCtx: func(api.Context, struct{ Test1 string }) {}, + Fn: func(*api.Context, struct{ Test1 string }) {}, + FnCtx: func(*api.Context, struct{ Test1 string }) {}, Err: nil, }, { @@ -142,8 +142,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(uint(0)), }, - Fn: func(struct{ Test1 uint }) {}, - FnCtx: func(api.Context, struct{ Test1 uint }) {}, + Fn: func(*api.Context, struct{ Test1 uint }) {}, + FnCtx: func(*api.Context, struct{ Test1 uint }) {}, Err: nil, }, { @@ -151,8 +151,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(float64(0)), }, - Fn: func(struct{ Test1 float64 }) {}, - FnCtx: func(api.Context, struct{ Test1 float64 }) {}, + Fn: func(*api.Context, struct{ Test1 float64 }) {}, + FnCtx: func(*api.Context, struct{ Test1 float64 }) {}, Err: nil, }, { @@ -160,8 +160,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf([]byte("")), }, - Fn: func(struct{ Test1 []byte }) {}, - FnCtx: func(api.Context, struct{ Test1 []byte }) {}, + Fn: func(*api.Context, struct{ Test1 []byte }) {}, + FnCtx: func(*api.Context, struct{ Test1 []byte }) {}, Err: nil, }, { @@ -169,8 +169,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf([]rune("")), }, - Fn: func(struct{ Test1 []rune }) {}, - FnCtx: func(api.Context, struct{ Test1 []rune }) {}, + Fn: func(*api.Context, struct{ Test1 []rune }) {}, + FnCtx: func(*api.Context, struct{ Test1 []rune }) {}, Err: nil, }, { @@ -178,8 +178,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new(string)), }, - Fn: func(struct{ Test1 *string }) {}, - FnCtx: func(api.Context, struct{ Test1 *string }) {}, + Fn: func(*api.Context, struct{ Test1 *string }) {}, + FnCtx: func(*api.Context, struct{ Test1 *string }) {}, Err: nil, }, { @@ -187,8 +187,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new(uint)), }, - Fn: func(struct{ Test1 *uint }) {}, - FnCtx: func(api.Context, struct{ Test1 *uint }) {}, + Fn: func(*api.Context, struct{ Test1 *uint }) {}, + FnCtx: func(*api.Context, struct{ Test1 *uint }) {}, Err: nil, }, { @@ -196,8 +196,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new(float64)), }, - Fn: func(struct{ Test1 *float64 }) {}, - FnCtx: func(api.Context, struct{ Test1 *float64 }) {}, + Fn: func(*api.Context, struct{ Test1 *float64 }) {}, + FnCtx: func(*api.Context, struct{ Test1 *float64 }) {}, Err: nil, }, { @@ -205,8 +205,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new([]byte)), }, - Fn: func(struct{ Test1 *[]byte }) {}, - FnCtx: func(api.Context, struct{ Test1 *[]byte }) {}, + Fn: func(*api.Context, struct{ Test1 *[]byte }) {}, + FnCtx: func(*api.Context, struct{ Test1 *[]byte }) {}, Err: nil, }, { @@ -214,8 +214,8 @@ func TestInputCheck(t *testing.T) { Input: map[string]reflect.Type{ "Test1": reflect.TypeOf(new([]rune)), }, - Fn: func(struct{ Test1 *[]rune }) {}, - FnCtx: func(api.Context, struct{ Test1 *[]rune }) {}, + Fn: func(*api.Context, struct{ Test1 *[]rune }) {}, + FnCtx: func(*api.Context, struct{ Test1 *[]rune }) {}, Err: nil, }, } @@ -225,47 +225,27 @@ func TestInputCheck(t *testing.T) { t.Parallel() // mock spec - s := signature{ + s := Signature{ Input: tcase.Input, Output: nil, } - t.Run("with-context", func(t *testing.T) { - err := s.checkInput(reflect.TypeOf(tcase.FnCtx), 1) - 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() - } + err := s.ValidateInput(reflect.TypeOf(tcase.FnCtx)) + 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() - } - } - }) - 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()) + 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() } - 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 { Output: map[string]reflect.Type{}, - Fn: func() {}, - Err: errMissingHandlerOutput, + Fn: func(*api.Context) {}, + Err: errMissingHandlerOutputArgument, }, // no input -> with last type not api.Err { Output: map[string]reflect.Type{}, - Fn: func() bool { return true }, - Err: errMissingHandlerErrorOutput, + Fn: func(*api.Context) bool { return true }, + Err: errMissingHandlerErrorArgument, }, // no input -> with api.Err { Output: map[string]reflect.Type{}, - Fn: func() api.Err { return api.ErrSuccess }, + Fn: func(*api.Context) api.Err { return api.ErrSuccess }, 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 { 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, }, // missing output struct in func @@ -306,7 +298,7 @@ func TestOutputCheck(t *testing.T) { "Test1": reflect.TypeOf(int(0)), }, Fn: func() api.Err { return api.ErrSuccess }, - Err: errMissingParamOutput, + Err: errWrongOutputArgumentType, }, // output not a pointer { @@ -314,7 +306,7 @@ func TestOutputCheck(t *testing.T) { "Test1": reflect.TypeOf(int(0)), }, Fn: func() (int, api.Err) { return 0, api.ErrSuccess }, - Err: errMissingParamOutput, + Err: errWrongOutputArgumentType, }, // output not a pointer to struct { @@ -322,7 +314,7 @@ func TestOutputCheck(t *testing.T) { "Test1": reflect.TypeOf(int(0)), }, Fn: func() (*int, api.Err) { return nil, api.ErrSuccess }, - Err: errMissingParamOutput, + Err: errWrongOutputArgumentType, }, // unexported param name { @@ -338,7 +330,7 @@ func TestOutputCheck(t *testing.T) { "Test1": reflect.TypeOf(int(0)), }, Fn: func() (*struct{}, api.Err) { return nil, api.ErrSuccess }, - Err: errMissingParamFromConfig, + Err: errMissingConfigArgument, }, // output field invalid type { @@ -371,12 +363,12 @@ func TestOutputCheck(t *testing.T) { t.Parallel() // mock spec - s := signature{ + s := Signature{ Input: nil, Output: tcase.Output, } - err := s.checkOutput(reflect.TypeOf(tcase.Fn)) + err := s.ValidateOutput(reflect.TypeOf(tcase.Fn)) if err == nil && tcase.Err != nil { t.Errorf("expected an error: '%s'", tcase.Err.Error()) t.FailNow()