Skip to content

Commit

Permalink
introduce exec middleware for schema
Browse files Browse the repository at this point in the history
Signed-off-by: Krzysztof Niepokojczycki <[email protected]>
  • Loading branch information
KNiepok committed Dec 7, 2022
1 parent 1cac0f0 commit ab0ae7d
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 1 deletion.
18 changes: 17 additions & 1 deletion graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func ParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) (
tracer: noop.Tracer{},
logger: &log.DefaultLogger{},
panicHandler: &errors.DefaultPanicHandler{},
middlewares: []Middleware{},
}
for _, opt := range opts {
opt(s)
Expand Down Expand Up @@ -88,6 +89,7 @@ type Schema struct {
useStringDescriptions bool
disableIntrospection bool
subscribeResolverTimeout time.Duration
middlewares []Middleware
}

func (s *Schema) ASTSchema() *types.Schema {
Expand Down Expand Up @@ -172,6 +174,16 @@ func DisableIntrospection() SchemaOpt {
}
}

// WithMiddlewares assigns middlewares to Schema.
// Middlewares will be assigned using a for loop.
// If we provide a slice of 2 middlewares [m1, m2], the resulting function will be
// m2(m1(exec())), where exec() is the original call to the resolver.
func WithMiddlewares(middlewares ...Middleware) SchemaOpt {
return func(s *Schema) {
s.middlewares = middlewares
}
}

// SubscribeResolverTimeout is an option to control the amount of time
// we allow for a single subscribe message resolver to complete it's job
// before it times out and returns an error to the subscriber.
Expand Down Expand Up @@ -214,7 +226,11 @@ func (s *Schema) Exec(ctx context.Context, queryString string, operationName str
if !s.res.Resolver.IsValid() {
panic("schema created without resolver, can not exec")
}
return s.exec(ctx, queryString, operationName, variables, s.res)
execF := s.exec
for _, m := range s.middlewares {
execF = m(execF)
}
return execF(ctx, queryString, operationName, variables, s.res)
}

func (s *Schema) exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) *Response {
Expand Down
47 changes: 47 additions & 0 deletions graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -3899,6 +3900,52 @@ func TestSubscriptions_In_Exec(t *testing.T) {
})
}

// This test assigns a hello resolver to the schema, along with 2 middlewares injected via SchemaOpt.
// Then the test asserts that both middlewares were called and resolver output still works as expected.
func TestMiddlewares_In_Exec(t *testing.T) {
resolver := &helloResolver{}
r := &struct {
*helloResolver
}{
helloResolver: resolver,
}
var m1Called, m2Called bool
gqltesting.RunTest(t, &gqltesting.Test{
Schema: graphql.MustParseSchema(`
type Query {
hello: String!
}
`, r,
graphql.WithMiddlewares(
func(next graphql.Exec) graphql.Exec {
return func(ctx context.Context, q string, o string, v map[string]interface{}, r *resolvable.Schema) *graphql.Response {
m1Called = true
return next(ctx, q, o, v, r)
}
},
func(next graphql.Exec) graphql.Exec {
return func(ctx context.Context, q string, o string, v map[string]interface{}, r *resolvable.Schema) *graphql.Response {
m2Called = true
return next(ctx, q, o, v, r)
}
},
),
),
ExpectedResult: fmt.Sprintf(`{"hello":"%s"}`, resolver.Hello()),
Query: `
query {
hello
}
`,
})
if !m1Called {
t.Fatalf("middleware1 was set but was not called")
}
if !m2Called {
t.Fatalf("middleware2 was set but was not called")
}
}

type nilPointerReturnValue struct{}

func (r *nilPointerReturnValue) Value() *string {
Expand Down
26 changes: 26 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package graphql

import (
"context"
"github.com/graph-gophers/graphql-go/errors"
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
)

// Exec executes the given query with the schema's resolver.
type Exec func(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) *Response

// Middleware can wrap Exec to add additional behaviour
type Middleware func(next Exec) Exec

func ParseErrorsMiddleware(parseErrors func([]*errors.QueryError) []*errors.QueryError) Middleware {
return func(next Exec) Exec {
return func(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) *Response {
// perform the original query
response := next(ctx, queryString, operationName, variables, res)
// mutate the errors
response.Errors = parseErrors(response.Errors)
// return the response
return response
}
}
}

0 comments on commit ab0ae7d

Please sign in to comment.