Skip to content

Commit

Permalink
Rewrite handling (#2)
Browse files Browse the repository at this point in the history
* Rewrite handling

* Minor refactoring

* Rename variables

* Update README.md

* Update README.md
  • Loading branch information
sbstjn authored Jun 4, 2018
1 parent 41969ae commit 5e97bc8
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 32 deletions.
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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 (
Expand Down
30 changes: 30 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
@@ -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)
}
45 changes: 30 additions & 15 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package router
import (
"encoding/json"
"fmt"
"reflect"
)

// Request stores all information from AppSync request
Expand All @@ -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{}
}
45 changes: 35 additions & 10 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@ package router_test

import (
"encoding/json"
"errors"
"fmt"
"os"
"testing"

router "github.com/sbstjn/appsync-router"
"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 (
Expand All @@ -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) {
Expand All @@ -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())
}

0 comments on commit 5e97bc8

Please sign in to comment.