Skip to content

Commit 4a97316

Browse files
committed
Added support of custom directives
1 parent 21b77bd commit 4a97316

25 files changed

+166
-45
lines changed

example/caching/server/server.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/graph-gophers/graphql-go"
1010
"github.com/graph-gophers/graphql-go/example/caching"
1111
"github.com/graph-gophers/graphql-go/example/caching/cache"
12+
"github.com/graph-gophers/graphql-go/pkg/common"
1213
)
1314

1415
var schema *graphql.Schema
@@ -40,12 +41,12 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
4041
var hint *cache.Hint
4142
if cacheable(r) {
4243
ctx, hints, done := cache.Hintable(r.Context())
43-
response = h.Schema.Exec(ctx, p.Query, p.OperationName, p.Variables)
44+
response = h.Schema.Exec(ctx, p.Query, p.OperationName, p.Variables, map[string]common.DirectiveVisitor{})
4445
done()
4546
v := <-hints
4647
hint = &v
4748
} else {
48-
response = h.Schema.Exec(r.Context(), p.Query, p.OperationName, p.Variables)
49+
response = h.Schema.Exec(r.Context(), p.Query, p.OperationName, p.Variables, map[string]common.DirectiveVisitor{})
4950
}
5051
responseJSON, err := json.Marshal(response)
5152
if err != nil {

gqltesting/testing.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@ import (
1212

1313
graphql "github.com/graph-gophers/graphql-go"
1414
"github.com/graph-gophers/graphql-go/errors"
15+
"github.com/graph-gophers/graphql-go/pkg/common"
1516
)
1617

1718
// Test is a GraphQL test case to be used with RunTest(s).
1819
type Test struct {
19-
Context context.Context
20-
Schema *graphql.Schema
21-
Query string
22-
OperationName string
23-
Variables map[string]interface{}
24-
ExpectedResult string
25-
ExpectedErrors []*errors.QueryError
26-
RawResponse bool
20+
Context context.Context
21+
Schema *graphql.Schema
22+
Query string
23+
OperationName string
24+
Variables map[string]interface{}
25+
ExpectedResult string
26+
ExpectedErrors []*errors.QueryError
27+
RawResponse bool
28+
DirectiveVisitors map[string]common.DirectiveVisitor
2729
}
2830

2931
// RunTests runs the given GraphQL test cases as subtests.
@@ -45,7 +47,7 @@ func RunTest(t *testing.T, test *Test) {
4547
if test.Context == nil {
4648
test.Context = context.Background()
4749
}
48-
result := test.Schema.Exec(test.Context, test.Query, test.OperationName, test.Variables)
50+
result := test.Schema.Exec(test.Context, test.Query, test.OperationName, test.Variables, test.DirectiveVisitors)
4951

5052
checkErrors(t, test.ExpectedErrors, result.Errors)
5153

graphql.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"time"
99

1010
"github.com/graph-gophers/graphql-go/errors"
11-
"github.com/graph-gophers/graphql-go/internal/common"
1211
"github.com/graph-gophers/graphql-go/internal/exec"
1312
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
1413
"github.com/graph-gophers/graphql-go/internal/exec/selected"
@@ -17,6 +16,7 @@ import (
1716
"github.com/graph-gophers/graphql-go/internal/validation"
1817
"github.com/graph-gophers/graphql-go/introspection"
1918
"github.com/graph-gophers/graphql-go/log"
19+
"github.com/graph-gophers/graphql-go/pkg/common"
2020
"github.com/graph-gophers/graphql-go/trace"
2121
)
2222

@@ -181,14 +181,14 @@ func (s *Schema) ValidateWithVariables(queryString string, variables map[string]
181181
// Exec executes the given query with the schema's resolver. It panics if the schema was created
182182
// without a resolver. If the context get cancelled, no further resolvers will be called and a
183183
// the context error will be returned as soon as possible (not immediately).
184-
func (s *Schema) Exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) *Response {
184+
func (s *Schema) Exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, visitors map[string]common.DirectiveVisitor) *Response {
185185
if s.res.Resolver == (reflect.Value{}) {
186186
panic("schema created without resolver, can not exec")
187187
}
188-
return s.exec(ctx, queryString, operationName, variables, s.res)
188+
return s.exec(ctx, queryString, operationName, variables, visitors, s.res)
189189
}
190190

191-
func (s *Schema) exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) *Response {
191+
func (s *Schema) exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, visitors map[string]common.DirectiveVisitor, res *resolvable.Schema) *Response {
192192
doc, qErr := query.Parse(queryString)
193193
if qErr != nil {
194194
return &Response{Errors: []*errors.QueryError{qErr}}
@@ -239,9 +239,10 @@ func (s *Schema) exec(ctx context.Context, queryString string, operationName str
239239
Schema: s.schema,
240240
DisableIntrospection: s.disableIntrospection,
241241
},
242-
Limiter: make(chan struct{}, s.maxParallelism),
243-
Tracer: s.tracer,
244-
Logger: s.logger,
242+
Limiter: make(chan struct{}, s.maxParallelism),
243+
Tracer: s.tracer,
244+
Logger: s.logger,
245+
Visitors: visitors,
245246
}
246247
varTypes := make(map[string]*introspection.Type)
247248
for _, v := range op.Vars {

graphql_test.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
gqlerrors "github.com/graph-gophers/graphql-go/errors"
1212
"github.com/graph-gophers/graphql-go/example/starwars"
1313
"github.com/graph-gophers/graphql-go/gqltesting"
14+
"github.com/graph-gophers/graphql-go/pkg/common"
1415
)
1516

1617
type helloWorldResolver1 struct{}
@@ -45,6 +46,20 @@ func (r *helloSnakeResolver2) SayHello(ctx context.Context, args struct{ FullNam
4546
return "Hello " + args.FullName + "!", nil
4647
}
4748

49+
type customDirectiveVisitor struct{}
50+
51+
func (v customDirectiveVisitor) Before(directive *common.Directive, input interface{}) error {
52+
return nil
53+
}
54+
55+
func (v customDirectiveVisitor) After(directive *common.Directive, output interface{}) (interface{}, error) {
56+
if value, ok := directive.Args.Get("customAttribute"); ok {
57+
return fmt.Sprintf("Directive '%s' (with arg '%s') modified result: %s", directive.Name.Name, value.String(), output.(string)), nil
58+
} else {
59+
return fmt.Sprintf("Directive '%s' modified result: %s", directive.Name.Name, output.(string)), nil
60+
}
61+
}
62+
4863
type theNumberResolver struct {
4964
number int32
5065
}
@@ -213,6 +228,67 @@ func TestHelloWorld(t *testing.T) {
213228
})
214229
}
215230

231+
func TestCustomDirective(t *testing.T) {
232+
t.Parallel()
233+
234+
gqltesting.RunTests(t, []*gqltesting.Test{
235+
{
236+
Schema: graphql.MustParseSchema(`
237+
directive @customDirective on FIELD_DEFINITION
238+
239+
schema {
240+
query: Query
241+
}
242+
243+
type Query {
244+
hello_html: String! @customDirective
245+
}
246+
`, &helloSnakeResolver1{}),
247+
Query: `
248+
{
249+
hello_html
250+
}
251+
`,
252+
ExpectedResult: `
253+
{
254+
"hello_html": "Directive 'customDirective' modified result: Hello snake!"
255+
}
256+
`,
257+
DirectiveVisitors: map[string]common.DirectiveVisitor{
258+
"customDirective": customDirectiveVisitor{},
259+
},
260+
},
261+
{
262+
Schema: graphql.MustParseSchema(`
263+
directive @customDirective(
264+
customAttribute: String!
265+
) on FIELD_DEFINITION
266+
267+
schema {
268+
query: Query
269+
}
270+
271+
type Query {
272+
say_hello(full_name: String!): String! @customDirective(customAttribute: hi)
273+
}
274+
`, &helloSnakeResolver1{}),
275+
Query: `
276+
{
277+
say_hello(full_name: "Johnny")
278+
}
279+
`,
280+
ExpectedResult: `
281+
{
282+
"say_hello": "Directive 'customDirective' (with arg 'hi') modified result: Hello Johnny!"
283+
}
284+
`,
285+
DirectiveVisitors: map[string]common.DirectiveVisitor{
286+
"customDirective": customDirectiveVisitor{},
287+
},
288+
},
289+
})
290+
}
291+
216292
func TestHelloSnake(t *testing.T) {
217293
t.Parallel()
218294

@@ -3728,7 +3804,7 @@ func TestSchema_Exec_without_resolver(t *testing.T) {
37283804
t.Fail()
37293805
}
37303806
}()
3731-
_ = s.Exec(context.Background(), tt.Args.Query, "", map[string]interface{}{})
3807+
_ = s.Exec(context.Background(), tt.Args.Query, "", map[string]interface{}{}, map[string]common.DirectiveVisitor{})
37323808
})
37333809
}
37343810
}

internal/exec/exec.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import (
1010
"time"
1111

1212
"github.com/graph-gophers/graphql-go/errors"
13-
"github.com/graph-gophers/graphql-go/internal/common"
1413
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
1514
"github.com/graph-gophers/graphql-go/internal/exec/selected"
1615
"github.com/graph-gophers/graphql-go/internal/query"
1716
"github.com/graph-gophers/graphql-go/internal/schema"
1817
"github.com/graph-gophers/graphql-go/log"
18+
"github.com/graph-gophers/graphql-go/pkg/common"
1919
"github.com/graph-gophers/graphql-go/trace"
2020
)
2121

@@ -25,6 +25,7 @@ type Request struct {
2525
Tracer trace.Tracer
2626
Logger log.Logger
2727
SubscribeResolverTimeout time.Duration
28+
Visitors map[string]common.DirectiveVisitor
2829
}
2930

3031
func (r *Request) handlePanic(ctx context.Context) {
@@ -207,8 +208,42 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f
207208
if f.field.ArgsPacker != nil {
208209
in = append(in, f.field.PackedArgs)
209210
}
211+
212+
// Before hook directive visitor
213+
if len(f.field.Directives) > 0 {
214+
for _, directive := range f.field.Directives {
215+
if visitor, ok := r.Visitors[directive.Name.Name]; ok {
216+
var values = make([]interface{}, 0)
217+
for _, inValue := range in {
218+
values = append(values, inValue.Interface())
219+
}
220+
221+
if err := visitor.Before(directive, values); err != nil {
222+
return nil
223+
}
224+
}
225+
}
226+
}
227+
228+
// Call method
210229
callOut := res.Method(f.field.MethodIndex).Call(in)
211230
result = callOut[0]
231+
232+
// After hook directive visitor (when no error is returned from resolver)
233+
if !f.field.HasError && len(f.field.Directives) > 0 {
234+
for _, directive := range f.field.Directives {
235+
if visitor, ok := r.Visitors[directive.Name.Name]; ok {
236+
returned, err := visitor.After(directive, result.Interface())
237+
if err != nil {
238+
f.field.HasError = true
239+
callOut[1] = reflect.ValueOf(err)
240+
} else {
241+
result = reflect.ValueOf(returned)
242+
}
243+
}
244+
}
245+
}
246+
212247
if f.field.HasError && !callOut[1].IsNil() {
213248
resolverErr := callOut[1].Interface().(error)
214249
err := errors.Errorf("%s", resolverErr)

internal/exec/packer/packer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
"strings"
88

99
"github.com/graph-gophers/graphql-go/errors"
10-
"github.com/graph-gophers/graphql-go/internal/common"
1110
"github.com/graph-gophers/graphql-go/internal/schema"
11+
"github.com/graph-gophers/graphql-go/pkg/common"
1212
)
1313

1414
type packer interface {

internal/exec/resolvable/meta.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import (
44
"fmt"
55
"reflect"
66

7-
"github.com/graph-gophers/graphql-go/internal/common"
87
"github.com/graph-gophers/graphql-go/internal/schema"
98
"github.com/graph-gophers/graphql-go/introspection"
9+
"github.com/graph-gophers/graphql-go/pkg/common"
1010
)
1111

1212
// Meta defines the details of the metadata schema for introspection.

internal/exec/resolvable/resolvable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
"reflect"
77
"strings"
88

9-
"github.com/graph-gophers/graphql-go/internal/common"
109
"github.com/graph-gophers/graphql-go/internal/exec/packer"
1110
"github.com/graph-gophers/graphql-go/internal/schema"
11+
"github.com/graph-gophers/graphql-go/pkg/common"
1212
)
1313

1414
type Schema struct {

internal/exec/selected/selected.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import (
66
"sync"
77

88
"github.com/graph-gophers/graphql-go/errors"
9-
"github.com/graph-gophers/graphql-go/internal/common"
109
"github.com/graph-gophers/graphql-go/internal/exec/packer"
1110
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
1211
"github.com/graph-gophers/graphql-go/internal/query"
1312
"github.com/graph-gophers/graphql-go/internal/schema"
1413
"github.com/graph-gophers/graphql-go/introspection"
14+
"github.com/graph-gophers/graphql-go/pkg/common"
1515
)
1616

1717
type Request struct {

internal/exec/subscribe.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"time"
1010

1111
"github.com/graph-gophers/graphql-go/errors"
12-
"github.com/graph-gophers/graphql-go/internal/common"
1312
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
1413
"github.com/graph-gophers/graphql-go/internal/exec/selected"
1514
"github.com/graph-gophers/graphql-go/internal/query"
15+
"github.com/graph-gophers/graphql-go/pkg/common"
1616
)
1717

1818
type Response struct {

internal/query/query.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"text/scanner"
66

77
"github.com/graph-gophers/graphql-go/errors"
8-
"github.com/graph-gophers/graphql-go/internal/common"
8+
"github.com/graph-gophers/graphql-go/pkg/common"
99
)
1010

1111
type Document struct {

internal/schema/schema.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"text/scanner"
66

77
"github.com/graph-gophers/graphql-go/errors"
8-
"github.com/graph-gophers/graphql-go/internal/common"
8+
"github.com/graph-gophers/graphql-go/pkg/common"
99
)
1010

1111
// Schema represents a GraphQL service's collective type system capabilities.

internal/schema/schema_internal_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"testing"
55

66
"github.com/graph-gophers/graphql-go/errors"
7-
"github.com/graph-gophers/graphql-go/internal/common"
7+
"github.com/graph-gophers/graphql-go/pkg/common"
88
)
99

1010
func TestParseInterfaceDef(t *testing.T) {

internal/validation/validation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"text/scanner"
1010

1111
"github.com/graph-gophers/graphql-go/errors"
12-
"github.com/graph-gophers/graphql-go/internal/common"
1312
"github.com/graph-gophers/graphql-go/internal/query"
1413
"github.com/graph-gophers/graphql-go/internal/schema"
14+
"github.com/graph-gophers/graphql-go/pkg/common"
1515
)
1616

1717
type varSet map[*common.InputValue]struct{}

introspection.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
88
"github.com/graph-gophers/graphql-go/introspection"
9+
"github.com/graph-gophers/graphql-go/pkg/common"
910
)
1011

1112
// Inspect allows inspection of the given schema.
@@ -15,7 +16,7 @@ func (s *Schema) Inspect() *introspection.Schema {
1516

1617
// ToJSON encodes the schema in a JSON format used by tools like Relay.
1718
func (s *Schema) ToJSON() ([]byte, error) {
18-
result := s.exec(context.Background(), introspectionQuery, "", nil, &resolvable.Schema{
19+
result := s.exec(context.Background(), introspectionQuery, "", nil, map[string]common.DirectiveVisitor{}, &resolvable.Schema{
1920
Meta: s.res.Meta,
2021
Query: &resolvable.Object{},
2122
Schema: *s.schema,

0 commit comments

Comments
 (0)