refactor: handler signature an middlewares for an idiomatic solution #24

Merged
xdrm-brackets merged 6 commits from refactor/api-context into 0.4.0 2021-06-20 07:51:42 +00:00
7 changed files with 77 additions and 39 deletions
Showing only changes of commit fa1ecfd97f - Show all commits

View File

@ -1,17 +1,42 @@
package api package api
import ( import (
"context"
"net/http" "net/http"
) )
// Ctx contains additional information for handlers // custom context key type
// type ctxKey int
// usually input/output arguments built by aicra are sufficient
// but the Ctx lets you manage your request from scratch if required const (
// ctxRequest ctxKey = iota
// If required, set api.Ctx as the first argument of your handler; if you ctxAuth
// don't need it, only use standard input arguments and it will be ignored )
type Ctx struct {
Res http.ResponseWriter // Context is a simple wrapper around context.Context that adds helper methods
Req *http.Request // to access additional information
type Context struct{ context.Context }
// Request current request
func (c Context) Request() *http.Request {
var (
raw = c.Value(ctxRequest)
cast, ok = raw.(*http.Request)
)
if !ok {
return nil
}
return cast
}
// Auth associated with this request
func (c Context) Auth() *Auth {
var (
raw = c.Value(ctxAuth)
cast, ok = raw.(*Auth)
)
if !ok {
return nil
}
return cast
} }

View File

@ -95,7 +95,7 @@ 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 // 5. pass execution to the handler
ctx := api.Ctx{Res: w, Req: r} ctx := api.Context{Res: w, Req: r}
var outData, outErr = handler.dyn.Handle(ctx, input.Data) var outData, outErr = handler.dyn.Handle(ctx, input.Data)
// 6. build res from returned data // 6. build res from returned data

View File

@ -82,7 +82,7 @@ func TestWith(t *testing.T) {
t.Fatalf("setup: unexpected error <%v>", err) t.Fatalf("setup: unexpected error <%v>", err)
} }
pathHandler := func(ctx api.Ctx) (*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.Req.Context().Value(key)
if value == nil { if value == nil {
@ -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.Ctx) (*struct{}, api.Err) { pathHandler := func(ctx api.Context) (*struct{}, api.Err) {
return nil, api.ErrNotImplemented return nil, api.ErrNotImplemented
} }

13
internal/ctx/ctx.go Normal file
View File

@ -0,0 +1,13 @@
package ctx
// Key defines a custom context key type
type Key int
const (
// Request is the key for the current *http.Request
Request Key = iota
// Response is the key for the associated http.ResponseWriter
Response
// Auth is the key for the request's authentication information
Auth
)

View File

@ -41,7 +41,7 @@ func Build(fn interface{}, service config.Service) (*Handler, error) {
return nil, errHandlerNotFunc return nil, errHandlerNotFunc
} }
h.hasContext = impl.NumIn() >= 1 && reflect.TypeOf(api.Ctx{}).AssignableTo(impl.In(0)) h.hasContext = impl.NumIn() >= 1 && reflect.TypeOf(api.Context{}).AssignableTo(impl.In(0))
if h.hasContext { if h.hasContext {
h.dataIndex = 1 h.dataIndex = 1
} }
@ -57,7 +57,7 @@ func Build(fn interface{}, service config.Service) (*Handler, error) {
} }
// Handle binds input @data into the dynamic function and returns map output // Handle binds input @data into the dynamic function and returns map output
func (h *Handler) Handle(ctx api.Ctx, 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 ert = reflect.TypeOf(api.Err{})
var fnv = reflect.ValueOf(h.fn) var fnv = reflect.ValueOf(h.fn)

View File

@ -138,7 +138,7 @@ func TestInput(t *testing.T) {
input[key] = val input[key] = val
} }
var output, err = handler.Handle(api.Ctx{}, 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)
} }

View File

@ -21,21 +21,21 @@ 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() {},
FnCtx: func(api.Ctx) {}, 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(int) {},
FnCtx: func(api.Ctx, 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(int, string) {},
FnCtx: func(api.Ctx, int, string) {}, FnCtx: func(api.Context, int, string) {},
Err: errUnexpectedInput, Err: errUnexpectedInput,
}, },
{ {
@ -44,7 +44,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(int(0)), "Test1": reflect.TypeOf(int(0)),
}, },
Fn: func() {}, Fn: func() {},
FnCtx: func(api.Ctx) {}, FnCtx: func(api.Context) {},
Err: errMissingHandlerArgumentParam, Err: errMissingHandlerArgumentParam,
}, },
{ {
@ -53,7 +53,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(int(0)), "Test1": reflect.TypeOf(int(0)),
}, },
Fn: func(int) {}, Fn: func(int) {},
FnCtx: func(api.Ctx, int) {}, FnCtx: func(api.Context, int) {},
Err: errMissingParamArgument, Err: errMissingParamArgument,
}, },
{ {
@ -62,7 +62,7 @@ func TestInputCheck(t *testing.T) {
"test1": reflect.TypeOf(int(0)), "test1": reflect.TypeOf(int(0)),
}, },
Fn: func(struct{}) {}, Fn: func(struct{}) {},
FnCtx: func(api.Ctx, struct{}) {}, FnCtx: func(api.Context, struct{}) {},
Err: errUnexportedName, Err: errUnexportedName,
}, },
{ {
@ -71,7 +71,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(int(0)), "Test1": reflect.TypeOf(int(0)),
}, },
Fn: func(struct{}) {}, Fn: func(struct{}) {},
FnCtx: func(api.Ctx, struct{}) {}, FnCtx: func(api.Context, struct{}) {},
Err: errMissingParamFromConfig, Err: errMissingParamFromConfig,
}, },
{ {
@ -80,7 +80,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(int(0)), "Test1": reflect.TypeOf(int(0)),
}, },
Fn: func(struct{ Test1 string }) {}, Fn: func(struct{ Test1 string }) {},
FnCtx: func(api.Ctx, struct{ Test1 string }) {}, FnCtx: func(api.Context, struct{ Test1 string }) {},
Err: errWrongParamTypeFromConfig, Err: errWrongParamTypeFromConfig,
}, },
{ {
@ -89,7 +89,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(int(0)), "Test1": reflect.TypeOf(int(0)),
}, },
Fn: func(struct{ Test1 int }) {}, Fn: func(struct{ Test1 int }) {},
FnCtx: func(api.Ctx, struct{ Test1 int }) {}, FnCtx: func(api.Context, struct{ Test1 int }) {},
Err: nil, Err: nil,
}, },
{ {
@ -98,7 +98,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new(int)), "Test1": reflect.TypeOf(new(int)),
}, },
Fn: func(struct{}) {}, Fn: func(struct{}) {},
FnCtx: func(api.Ctx, struct{}) {}, FnCtx: func(api.Context, struct{}) {},
Err: errMissingParamFromConfig, Err: errMissingParamFromConfig,
}, },
{ {
@ -107,7 +107,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new(int)), "Test1": reflect.TypeOf(new(int)),
}, },
Fn: func(struct{ Test1 string }) {}, Fn: func(struct{ Test1 string }) {},
FnCtx: func(api.Ctx, struct{ Test1 string }) {}, FnCtx: func(api.Context, struct{ Test1 string }) {},
Err: errWrongParamTypeFromConfig, Err: errWrongParamTypeFromConfig,
}, },
{ {
@ -116,7 +116,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new(int)), "Test1": reflect.TypeOf(new(int)),
}, },
Fn: func(struct{ Test1 *string }) {}, Fn: func(struct{ Test1 *string }) {},
FnCtx: func(api.Ctx, struct{ Test1 *string }) {}, FnCtx: func(api.Context, struct{ Test1 *string }) {},
Err: errWrongParamTypeFromConfig, Err: errWrongParamTypeFromConfig,
}, },
{ {
@ -125,7 +125,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new(int)), "Test1": reflect.TypeOf(new(int)),
}, },
Fn: func(struct{ Test1 *int }) {}, Fn: func(struct{ Test1 *int }) {},
FnCtx: func(api.Ctx, struct{ Test1 *int }) {}, FnCtx: func(api.Context, struct{ Test1 *int }) {},
Err: nil, Err: nil,
}, },
{ {
@ -134,7 +134,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(string("")), "Test1": reflect.TypeOf(string("")),
}, },
Fn: func(struct{ Test1 string }) {}, Fn: func(struct{ Test1 string }) {},
FnCtx: func(api.Ctx, struct{ Test1 string }) {}, FnCtx: func(api.Context, struct{ Test1 string }) {},
Err: nil, Err: nil,
}, },
{ {
@ -143,7 +143,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(uint(0)), "Test1": reflect.TypeOf(uint(0)),
}, },
Fn: func(struct{ Test1 uint }) {}, Fn: func(struct{ Test1 uint }) {},
FnCtx: func(api.Ctx, struct{ Test1 uint }) {}, FnCtx: func(api.Context, struct{ Test1 uint }) {},
Err: nil, Err: nil,
}, },
{ {
@ -152,7 +152,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(float64(0)), "Test1": reflect.TypeOf(float64(0)),
}, },
Fn: func(struct{ Test1 float64 }) {}, Fn: func(struct{ Test1 float64 }) {},
FnCtx: func(api.Ctx, struct{ Test1 float64 }) {}, FnCtx: func(api.Context, struct{ Test1 float64 }) {},
Err: nil, Err: nil,
}, },
{ {
@ -161,7 +161,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf([]byte("")), "Test1": reflect.TypeOf([]byte("")),
}, },
Fn: func(struct{ Test1 []byte }) {}, Fn: func(struct{ Test1 []byte }) {},
FnCtx: func(api.Ctx, struct{ Test1 []byte }) {}, FnCtx: func(api.Context, struct{ Test1 []byte }) {},
Err: nil, Err: nil,
}, },
{ {
@ -170,7 +170,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf([]rune("")), "Test1": reflect.TypeOf([]rune("")),
}, },
Fn: func(struct{ Test1 []rune }) {}, Fn: func(struct{ Test1 []rune }) {},
FnCtx: func(api.Ctx, struct{ Test1 []rune }) {}, FnCtx: func(api.Context, struct{ Test1 []rune }) {},
Err: nil, Err: nil,
}, },
{ {
@ -179,7 +179,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new(string)), "Test1": reflect.TypeOf(new(string)),
}, },
Fn: func(struct{ Test1 *string }) {}, Fn: func(struct{ Test1 *string }) {},
FnCtx: func(api.Ctx, struct{ Test1 *string }) {}, FnCtx: func(api.Context, struct{ Test1 *string }) {},
Err: nil, Err: nil,
}, },
{ {
@ -188,7 +188,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new(uint)), "Test1": reflect.TypeOf(new(uint)),
}, },
Fn: func(struct{ Test1 *uint }) {}, Fn: func(struct{ Test1 *uint }) {},
FnCtx: func(api.Ctx, struct{ Test1 *uint }) {}, FnCtx: func(api.Context, struct{ Test1 *uint }) {},
Err: nil, Err: nil,
}, },
{ {
@ -197,7 +197,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new(float64)), "Test1": reflect.TypeOf(new(float64)),
}, },
Fn: func(struct{ Test1 *float64 }) {}, Fn: func(struct{ Test1 *float64 }) {},
FnCtx: func(api.Ctx, struct{ Test1 *float64 }) {}, FnCtx: func(api.Context, struct{ Test1 *float64 }) {},
Err: nil, Err: nil,
}, },
{ {
@ -206,7 +206,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new([]byte)), "Test1": reflect.TypeOf(new([]byte)),
}, },
Fn: func(struct{ Test1 *[]byte }) {}, Fn: func(struct{ Test1 *[]byte }) {},
FnCtx: func(api.Ctx, struct{ Test1 *[]byte }) {}, FnCtx: func(api.Context, struct{ Test1 *[]byte }) {},
Err: nil, Err: nil,
}, },
{ {
@ -215,7 +215,7 @@ func TestInputCheck(t *testing.T) {
"Test1": reflect.TypeOf(new([]rune)), "Test1": reflect.TypeOf(new([]rune)),
}, },
Fn: func(struct{ Test1 *[]rune }) {}, Fn: func(struct{ Test1 *[]rune }) {},
FnCtx: func(api.Ctx, struct{ Test1 *[]rune }) {}, FnCtx: func(api.Context, struct{ Test1 *[]rune }) {},
Err: nil, Err: nil,
}, },
} }