From 5e97bc872b9f0e869ee4097faf4ef8aa9ab2acbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20M=C3=BCller?= Date: Mon, 4 Jun 2018 09:02:16 +0200 Subject: [PATCH] Rewrite handling (#2) * Rewrite handling * Minor refactoring * Rename variables * Update README.md * Update README.md --- README.md | 21 ++++++++++++++------- handler.go | 30 ++++++++++++++++++++++++++++++ router.go | 45 ++++++++++++++++++++++++++++++--------------- router_test.go | 45 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 handler.go diff --git a/README.md b/README.md index f1fc434..7003d30 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # AppSync Router -[![Build Status](https://img.shields.io/circleci/project/sbstjn/appsync-router.svg?maxAge=600)](https://circleci.com/gh/sbstjn/appsync-router) [![GitHub release](https://img.shields.io/github/release/sbstjn/appsync-router.svg?maxAge=600)](https://github.com/sbstjn/appsync-router/releases) -[![MIT License](https://img.shields.io/github/license/sbstjn/appsync-router.svg?maxAge=3600)](https://github.com/sbstjn/appsync-router/blob/master/LICENSE.md) [![GoDoc](https://godoc.org/github.com/sbstjn/appsync-router?status.svg)](https://godoc.org/github.com/sbstjn/appsync-router) [![Go Report Card](https://goreportcard.com/badge/github.com/sbstjn/appsync-router)](https://goreportcard.com/report/github.com/sbstjn/appsync-router) -[![Coverage Status](https://img.shields.io/coveralls/sbstjn/appsync-router.svg?maxAge=600)](https://coveralls.io/github/sbstjn/appsync-router) +[![MIT License](https://img.shields.io/github/license/sbstjn/appsync-router.svg?maxAge=3600)](https://github.com/sbstjn/appsync-router/blob/master/LICENSE.md) +[![Build Status](https://img.shields.io/circleci/project/sbstjn/appsync-router.svg?maxAge=600)](https://circleci.com/gh/sbstjn/appsync-router) Wrapper for routing AppSync resolvers with AWS Lambda using Go. @@ -24,12 +23,20 @@ import ( "github.com/sbstjn/appsync-router" ) -func handleRouteA(req json.RawMessage) (interface{}, error) { - return nil, errors.New("Nothing here in route A") +type ParamsRouteA struct { + Foo string `json:"foo"` +} + +type ParamsRouteB struct { + Bar string `json:"bar"` +} + +func handleRouteA(args ParamsRouteA) (interface{}, error) { + return nil, fmt.Errorf("Nothing here in route A: %s", args.Foo) } -func handleRouteB(req json.RawMessage) (interface{}, error) { - return nil, errors.New("Nothing here in route B") +func handleRouteB(args ParamsRouteB) (interface{}, error) { + return nil, fmt.Errorf("Nothing here in route B: %s", args.Bar) } var ( diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..00e45fd --- /dev/null +++ b/handler.go @@ -0,0 +1,30 @@ +package router + +import ( + "encoding/json" + "reflect" +) + +// Handler is an abstract function +type Handler struct { + function interface{} +} + +// Prepare parses event payload +func (h *Handler) Prepare(payload json.RawMessage) ([]reflect.Value, error) { + argsType := reflect.TypeOf(h.function).In(0) + args := reflect.New(argsType) + + if err := json.Unmarshal(payload, args.Interface()); err != nil { + return nil, err + } + + return append([]reflect.Value{}, args.Elem()), nil +} + +// Call the handler and pass event +func (h *Handler) Call(args []reflect.Value) (interface{}, error) { + response := reflect.ValueOf(h.function).Call(args) + + return response[0].Interface(), response[1].Interface().(error) +} diff --git a/router.go b/router.go index 8ca1b3d..d5fccd4 100644 --- a/router.go +++ b/router.go @@ -3,6 +3,7 @@ package router import ( "encoding/json" "fmt" + "reflect" ) // Request stores all information from AppSync request @@ -11,31 +12,45 @@ type Request struct { Arguments json.RawMessage `json:"arguments"` } -// RouteHandler defines the function to handle the request -type RouteHandler func(req json.RawMessage) (interface{}, error) - // Router stores all routes and handlers -type Router struct { - Routes map[string]RouteHandler -} +type Router map[string]Handler // Add stores a new route with handler -func (r *Router) Add(route string, handler RouteHandler) { - r.Routes[route] = handler +func (r Router) Add(route string, function interface{}) error { + if kind := reflect.TypeOf(function).Kind(); kind != reflect.Func { + return fmt.Errorf("Handler is not a function, but %s", kind) + } + + r[route] = Handler{function} + return nil +} + +// Get return handler for route or error +func (r Router) Get(route string) (*Handler, error) { + handler, found := r[route] + if !found { + return nil, fmt.Errorf("No handler for request found: %s", route) + } + + return &handler, nil } // Serve parses the AppSync request -func (r *Router) Serve(req Request) (interface{}, error) { - if handler, found := r.Routes[req.Field]; found { - return handler(req.Arguments) +func (r Router) Serve(req Request) (interface{}, error) { + handler, err := r.Get(req.Field) + if err != nil { + return nil, err } - return nil, fmt.Errorf("Unable to handle request: %s", req.Field) + arguments, err := handler.Prepare(req.Arguments) + if err != nil { + return nil, err + } + + return handler.Call(arguments) } // New returns a new Router func New() Router { - return Router{ - map[string]RouteHandler{}, - } + return Router{} } diff --git a/router_test.go b/router_test.go index 43ae9b1..eabc8dc 100644 --- a/router_test.go +++ b/router_test.go @@ -2,7 +2,7 @@ package router_test import ( "encoding/json" - "errors" + "fmt" "os" "testing" @@ -10,12 +10,20 @@ import ( "github.com/stretchr/testify/assert" ) -func handleRouteA(req json.RawMessage) (interface{}, error) { - return nil, errors.New("Nothing here in route A") +type ParamsRouteA struct { + Foo string `json:"foo"` } -func handleRouteB(req json.RawMessage) (interface{}, error) { - return nil, errors.New("Nothing here in route B") +type ParamsRouteB struct { + Bar string `json:"bar"` +} + +func handleRouteA(args ParamsRouteA) (interface{}, error) { + return nil, fmt.Errorf("Nothing here in route A: %s", args.Foo) +} + +func handleRouteB(args ParamsRouteB) (interface{}, error) { + return nil, fmt.Errorf("Nothing here in route B: %s", args.Bar) } var ( @@ -29,22 +37,29 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func TestRouteMustBeFunction(t *testing.T) { + err := r.Add("fieldD", true) + + assert.NotNil(t, err) + assert.Equal(t, "Handler is not a function, but bool", err.Error()) +} + func TestRouteMatch(t *testing.T) { routeA, err := r.Serve(router.Request{ Field: "fieldA", - Arguments: json.RawMessage(""), + Arguments: json.RawMessage("{\"foo\":\"bar\"}"), }) assert.Nil(t, routeA) - assert.Equal(t, errors.New("Nothing here in route A"), err) + assert.Equal(t, "Nothing here in route A: bar", err.Error()) routeB, err := r.Serve(router.Request{ Field: "fieldB", - Arguments: json.RawMessage(""), + Arguments: json.RawMessage("{\"bar\":\"foo\"}"), }) assert.Nil(t, routeB) - assert.Equal(t, errors.New("Nothing here in route B"), err) + assert.Equal(t, "Nothing here in route B: foo", err.Error()) } func TestRouteMiss(t *testing.T) { @@ -54,5 +69,15 @@ func TestRouteMiss(t *testing.T) { }) assert.Nil(t, routeC) - assert.Equal(t, errors.New("Unable to handle request: fieldC"), err) + assert.Equal(t, "No handler for request found: fieldC", err.Error()) +} + +func TestRouteMatchWithInvalidPayload(t *testing.T) { + routeA, err := r.Serve(router.Request{ + Field: "fieldA", + Arguments: json.RawMessage(""), + }) + + assert.Nil(t, routeA) + assert.Equal(t, "unexpected end of JSON input", err.Error()) }