Skip to content

Commit

Permalink
Merge pull request #1 from jonsaw/middleware-intro
Browse files Browse the repository at this point in the history
Merge Middleware into Master
  • Loading branch information
jonsaw authored Sep 28, 2018
2 parents 08cb572 + 2847629 commit dd34e7d
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 32 deletions.
17 changes: 17 additions & 0 deletions dispatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package resolvers

import "fmt"

type dispatch struct {
repository *Repository
}

func (d dispatch) Serve(in Invocation) (interface{}, error) {
handler, found := d.repository.resolvers[in.Resolve]

if found {
return handler.call(in.payload())
}

return nil, fmt.Errorf("No resolver found: %s", in.Resolve)
}
15 changes: 15 additions & 0 deletions handle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package resolvers

// Handler responds to requests in resolvers
type Handler interface {
Serve(Invocation) (interface{}, error)
}

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions
type HandlerFunc func(Invocation) (interface{}, error)

// Serve calls from resolver
func (f HandlerFunc) Serve(in Invocation) (interface{}, error) {
return f(in)
}
14 changes: 8 additions & 6 deletions invocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ package resolvers

import "encoding/json"

type context struct {
// ContextData data received from AppSync
type ContextData struct {
Arguments json.RawMessage `json:"arguments"`
Source json.RawMessage `json:"source"`
}

type invocation struct {
Resolve string `json:"resolve"`
Context context `json:"context"`
// Invocation data received from AppSync
type Invocation struct {
Resolve string `json:"resolve"`
Context ContextData `json:"context"`
}

func (in invocation) isRoot() bool {
func (in Invocation) isRoot() bool {
return in.Context.Source == nil || string(in.Context.Source) == "null"
}

func (in invocation) payload() json.RawMessage {
func (in Invocation) payload() json.RawMessage {
if in.isRoot() {
return in.Context.Arguments
}
Expand Down
8 changes: 4 additions & 4 deletions invocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (

var _ = Describe("Invocation", func() {
Context("With Arguments", func() {
data := invocation{
data := Invocation{
Resolve: "exaple.resolver",
Context: context{
Context: ContextData{
Arguments: json.RawMessage(`{ "foo": "bar" }`),
},
}
Expand All @@ -26,9 +26,9 @@ var _ = Describe("Invocation", func() {
})

Context("With Source", func() {
data := invocation{
data := Invocation{
Resolve: "exaple.resolver",
Context: context{
Context: ContextData{
Source: json.RawMessage(`{ "bar": "foo" }`),
},
}
Expand Down
6 changes: 4 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package resolvers

// New returns a new Repository with a list of resolver
func New() Repository {
return Repository{}
func New() *Repository {
r := &Repository{}
r.buildChain()
return r
}
14 changes: 14 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package resolvers

// Use appends middleware to repository
func (r *Repository) Use(middleware func(Handler) Handler) {
r.middleware = append(r.middleware, middleware)
r.buildChain()
}

func (r *Repository) buildChain() {
r.handler = dispatch{repository: r}
for i := len(r.middleware) - 1; i >= 0; i-- {
r.handler = r.middleware[i](r.handler)
}
}
151 changes: 151 additions & 0 deletions middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package resolvers

import (
"encoding/json"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

type graphQLError struct {
Type string `json:"error_type"`
Message string `json:"error_message"`
Data interface{} `json:"error_data"`
}

func (e *graphQLError) Error() string {
return e.Message
}

func newGraphQLError(t string, m string, d interface{}) *graphQLError {
return &graphQLError{
Type: t,
Message: m,
Data: d,
}
}

func sequence(ch chan string, seq ...string) bool {
for _, str := range seq {
if msg := <-ch; msg != str {
return false
}
}
return true
}

var _ = Describe("Middleware", func() {
type arguments struct {
Bar string `json:"bar"`
}
type response struct {
Foo string
}

Context("With no hijacking", func() {
ch := make(chan string, 10)
r := New()
r.Add("example.resolver", func(arg arguments) (response, error) {
ch <- "handler"
return response{"bar"}, nil
})
r.Use(func(h Handler) Handler {
m := func(in Invocation) (interface{}, error) {
ch <- "before 1"
out, err := h.Serve(in)
ch <- "after 1"
return out, err
}
return HandlerFunc(m)
})
r.Use(func(h Handler) Handler {
m := func(in Invocation) (interface{}, error) {
ch <- "before 2"
out, err := h.Serve(in)
ch <- "after 2"
return out, err
}
return HandlerFunc(m)
})
res, err := r.Handle(Invocation{
Resolve: "example.resolver",
Context: ContextData{
Arguments: json.RawMessage(`{"bar":"foo"}`),
},
})

It("Should not error", func() {
Expect(err).ToNot(HaveOccurred())
})

It("Should have data", func() {
Expect(res.(response).Foo).To(Equal("bar"))
})

It("Should be in sequence", func() {
Expect(
sequence(ch,
"before 1",
"before 2",
"handler",
"after 2",
"after 1",
)).To(BeTrue())
})
})

Context("With custom error middleware", func() {
ch := make(chan string, 10)
r := New()
r.Add("example.resolver", func(arg arguments) (*response, error) {
ch <- "handler"
return nil, newGraphQLError("BAD_REQUEST", "Invalid type", response{"bar"})
})
r.Use(func(h Handler) Handler {
m := func(in Invocation) (interface{}, error) {
ch <- "before 1"
out, err := h.Serve(in)
ch <- "after 1"
return out, err
}
return HandlerFunc(m)
})
r.Use(func(h Handler) Handler {
m := func(in Invocation) (interface{}, error) {
out, err := h.Serve(in)
if err != nil {
if errData, ok := err.(*graphQLError); ok {
return errData, nil
}
}
return out, err
}
return HandlerFunc(m)
})
res, err := r.Handle(Invocation{
Resolve: "example.resolver",
Context: ContextData{
Arguments: json.RawMessage(`{"bar":"foo"}`),
},
})

It("Should not error", func() {
Expect(err).ToNot(HaveOccurred())
})

It("Should have error data", func() {
Expect(res.(*graphQLError).Message).To(Equal("Invalid type"))
Expect(res.(*graphQLError).Type).To(Equal("BAD_REQUEST"))
Expect(res.(*graphQLError).Data.(response).Foo).To(Equal("bar"))
})

It("Should be in sequence", func() {
Expect(
sequence(ch,
"before 1",
"handler",
"after 1",
)).To(BeTrue())
})
})
})
25 changes: 13 additions & 12 deletions repository.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
package resolvers

import (
"fmt"
"reflect"
)

// Repository stores all resolvers
type Repository map[string]resolver
type Repository struct {
handler Handler
middleware []func(Handler) Handler
resolvers map[string]resolver
}

// Add stores a new resolver
func (r Repository) Add(resolve string, handler interface{}) error {
func (r *Repository) Add(resolve string, handler interface{}) error {
if r.resolvers == nil {
r.resolvers = map[string]resolver{}
}

err := validators.run(reflect.TypeOf(handler))

if err == nil {
r[resolve] = resolver{handler}
r.resolvers[resolve] = resolver{handler}
}

return err
}

// Handle responds to the AppSync request
func (r Repository) Handle(in invocation) (interface{}, error) {
handler, found := r[in.Resolve]

if found {
return handler.call(in.payload())
}

return nil, fmt.Errorf("No resolver found: %s", in.Resolve)
func (r *Repository) Handle(in Invocation) (interface{}, error) {
return r.handler.Serve(in)
}
16 changes: 8 additions & 8 deletions repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ var _ = Describe("Repository", func() {
r.Add("example.resolver.with.error", func(arg arguments) (response, error) { return response{"bar"}, errors.New("Has Error") })

Context("Matching invocation", func() {
res, err := r.Handle(invocation{
res, err := r.Handle(Invocation{
Resolve: "example.resolver",
Context: context{
Context: ContextData{
Arguments: json.RawMessage(`{"bar":"foo"}`),
},
})
Expand All @@ -37,9 +37,9 @@ var _ = Describe("Repository", func() {
})

Context("Matching invocation with error", func() {
_, err := r.Handle(invocation{
_, err := r.Handle(Invocation{
Resolve: "example.resolver.with.error",
Context: context{
Context: ContextData{
Arguments: json.RawMessage(`{"bar":"foo"}`),
},
})
Expand All @@ -50,9 +50,9 @@ var _ = Describe("Repository", func() {
})

Context("Matching invocation with invalid payload", func() {
_, err := r.Handle(invocation{
_, err := r.Handle(Invocation{
Resolve: "example.resolver.with.error",
Context: context{
Context: ContextData{
Arguments: json.RawMessage(`{"bar:foo"}`),
},
})
Expand All @@ -63,9 +63,9 @@ var _ = Describe("Repository", func() {
})

Context("Not matching invocation", func() {
res, err := r.Handle(invocation{
res, err := r.Handle(Invocation{
Resolve: "example.resolver.not.found",
Context: context{
Context: ContextData{
Arguments: json.RawMessage(`{"bar":"foo"}`),
},
})
Expand Down

0 comments on commit dd34e7d

Please sign in to comment.