From 5e217c265fa7f47f6feaac6fa309b02e45a44aba Mon Sep 17 00:00:00 2001 From: Vincent Composieux Date: Thu, 8 Apr 2021 17:30:02 +0200 Subject: [PATCH 01/20] Added support of custom directives based on graph-gophers/graphql-go#446 and work by @eko --- graphql.go | 10 +++ graphql_test.go | 150 +++++++++++++++++++++++++++++++++++++++++- internal/exec/exec.go | 72 ++++++++++++++++++++ types/directive.go | 11 +++- 4 files changed, 241 insertions(+), 2 deletions(-) diff --git a/graphql.go b/graphql.go index 891c0379..5768c177 100644 --- a/graphql.go +++ b/graphql.go @@ -83,6 +83,7 @@ type Schema struct { useStringDescriptions bool disableIntrospection bool subscribeResolverTimeout time.Duration + visitors map[string]types.DirectiveVisitor } func (s *Schema) ASTSchema() *types.Schema { @@ -169,6 +170,14 @@ func SubscribeResolverTimeout(timeout time.Duration) SchemaOpt { } } +// DirectiveVisitors allows to pass custom directive visitors that will be able to handle +// your GraphQL schema directives. +func DirectiveVisitors(visitors map[string]types.DirectiveVisitor) SchemaOpt { + return func(s *Schema) { + s.visitors = visitors + } +} + // Response represents a typical response of a GraphQL server. It may be encoded to JSON directly or // it may be further processed to a custom response type, for example to include custom error data. // Errors are intentionally serialized first based on the advice in https://github.com/facebook/graphql/commit/7b40390d48680b15cb93e02d46ac5eb249689876#diff-757cea6edf0288677a9eea4cfc801d87R107 @@ -258,6 +267,7 @@ func (s *Schema) exec(ctx context.Context, queryString string, operationName str Tracer: s.tracer, Logger: s.logger, PanicHandler: s.panicHandler, + Visitors: s.visitors, } varTypes := make(map[string]*introspection.Type) for _, v := range op.Vars { diff --git a/graphql_test.go b/graphql_test.go index c12334c8..6133aff3 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -14,6 +14,7 @@ import ( "github.com/graph-gophers/graphql-go/gqltesting" "github.com/graph-gophers/graphql-go/introspection" "github.com/graph-gophers/graphql-go/trace/tracer" + "github.com/graph-gophers/graphql-go/types" ) type helloWorldResolver1 struct{} @@ -48,6 +49,27 @@ func (r *helloSnakeResolver2) SayHello(ctx context.Context, args struct{ FullNam return "Hello " + args.FullName + "!", nil } +type customDirectiveVisitor struct { + beforeWasCalled bool +} + +func (v *customDirectiveVisitor) Before(ctx context.Context, directive *types.Directive, input interface{}) error { + v.beforeWasCalled = true + return nil +} + +func (v *customDirectiveVisitor) After(ctx context.Context, directive *types.Directive, output interface{}) (interface{}, error) { + if v.beforeWasCalled == false { + return nil, errors.New("Before directive visitor method wasn't called.") + } + + if value, ok := directive.Arguments.Get("customAttribute"); ok { + return fmt.Sprintf("Directive '%s' (with arg '%s') modified result: %s", directive.Name.Name, value.String(), output.(string)), nil + } else { + return fmt.Sprintf("Directive '%s' modified result: %s", directive.Name.Name, output.(string)), nil + } +} + type theNumberResolver struct { number int32 } @@ -191,7 +213,6 @@ func TestHelloWorld(t *testing.T) { } `, }, - { Schema: graphql.MustParseSchema(` schema { @@ -216,6 +237,67 @@ func TestHelloWorld(t *testing.T) { }) } +func TestCustomDirective(t *testing.T) { + t.Parallel() + + gqltesting.RunTests(t, []*gqltesting.Test{ + { + Schema: graphql.MustParseSchema(` + directive @customDirective on FIELD_DEFINITION + + schema { + query: Query + } + + type Query { + hello_html: String! @customDirective + } + `, &helloSnakeResolver1{}, + graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ + "customDirective": &customDirectiveVisitor{}, + })), + Query: ` + { + hello_html + } + `, + ExpectedResult: ` + { + "hello_html": "Directive 'customDirective' modified result: Hello snake!" + } + `, + }, + { + Schema: graphql.MustParseSchema(` + directive @customDirective( + customAttribute: String! + ) on FIELD_DEFINITION + + schema { + query: Query + } + + type Query { + say_hello(full_name: String!): String! @customDirective(customAttribute: hi) + } + `, &helloSnakeResolver1{}, + graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ + "customDirective": &customDirectiveVisitor{}, + })), + Query: ` + { + say_hello(full_name: "Johnny") + } + `, + ExpectedResult: ` + { + "say_hello": "Directive 'customDirective' (with arg 'hi') modified result: Hello Johnny!" + } + `, + }, + }) +} + func TestHelloSnake(t *testing.T) { t.Parallel() @@ -4550,3 +4632,69 @@ func TestQueryService(t *testing.T) { }, }) } + +type StructFieldResolver struct { + Hello string +} + +func TestStructFieldResolver(t *testing.T) { + gqltesting.RunTests(t, []*gqltesting.Test{ + { + Schema: graphql.MustParseSchema(` + schema { + query: Query + } + + type Query { + hello: String! + } + `, &StructFieldResolver{Hello: "Hello world!"}, graphql.UseFieldResolvers()), + Query: ` + { + hello + } + `, + ExpectedResult: ` + { + "hello": "Hello world!" + } + `, + }, + }) +} + +func TestDirectiveStructFieldResolver(t *testing.T) { + schemaOpt := []graphql.SchemaOpt{ + graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ + "customDirective": &customDirectiveVisitor{}, + }), + graphql.UseFieldResolvers(), + } + + gqltesting.RunTests(t, []*gqltesting.Test{ + + { + Schema: graphql.MustParseSchema(` + directive @customDirective on FIELD_DEFINITION + + schema { + query: Query + } + + type Query { + hello: String! @customDirective + } + `, &StructFieldResolver{Hello: "Hello world!"}, schemaOpt...), + Query: ` + { + hello + } + `, + ExpectedResult: ` + { + "hello": "Directive 'customDirective' modified result: Hello world!" + } + `, + }}) + +} diff --git a/internal/exec/exec.go b/internal/exec/exec.go index e9056c53..fa592252 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -25,6 +25,7 @@ type Request struct { Logger log.Logger PanicHandler errors.PanicHandler SubscribeResolverTimeout time.Duration + Visitors map[string]types.DirectiveVisitor } func (r *Request) handlePanic(ctx context.Context) { @@ -208,8 +209,48 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if f.field.ArgsPacker != nil { in = append(in, f.field.PackedArgs) } + + // Before hook directive visitor + if len(f.field.Directives) > 0 { + for _, directive := range f.field.Directives { + if visitor, ok := r.Visitors[directive.Name.Name]; ok { + values := make([]interface{}, 0, len(in)) + for _, inValue := range in { + values = append(values, inValue.Interface()) + } + + visitorErr := visitor.Before(ctx, directive, values) + if visitorErr != nil { + err := errors.Errorf("%s", visitorErr) + err.Path = path.toSlice() + err.ResolverError = visitorErr + return err + } + } + } + } + + // Call method callOut := res.Method(f.field.MethodIndex).Call(in) result = callOut[0] + + // After hook directive visitor (when no error is returned from resolver) + if !f.field.HasError && len(f.field.Directives) > 0 { + for _, directive := range f.field.Directives { + if visitor, ok := r.Visitors[directive.Name.Name]; ok { + returned, visitorErr := visitor.After(ctx, directive, result.Interface()) + if visitorErr != nil { + err := errors.Errorf("%s", visitorErr) + err.Path = path.toSlice() + err.ResolverError = visitorErr + return err + } else { + result = reflect.ValueOf(returned) + } + } + } + } + if f.field.HasError && !callOut[1].IsNil() { resolverErr := callOut[1].Interface().(error) err := errors.Errorf("%s", resolverErr) @@ -225,7 +266,38 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if res.Kind() == reflect.Ptr { res = res.Elem() } + // Before hook directive visitor struct field + if len(f.field.Directives) > 0 { + for _, directive := range f.field.Directives { + if visitor, ok := r.Visitors[directive.Name.Name]; ok { + // TODO check that directive arity == 0-that should be an error at schema init time + visitorErr := visitor.Before(ctx, directive, nil) + if visitorErr != nil { + err := errors.Errorf("%s", visitorErr) + err.Path = path.toSlice() + err.ResolverError = visitorErr + return err + } + } + } + } result = res.FieldByIndex(f.field.FieldIndex) + // After hook directive visitor (when no error is returned from resolver) + if !f.field.HasError && len(f.field.Directives) > 0 { + for _, directive := range f.field.Directives { + if visitor, ok := r.Visitors[directive.Name.Name]; ok { + returned, visitorErr := visitor.After(ctx, directive, result.Interface()) + if visitorErr != nil { + err := errors.Errorf("%s", visitorErr) + err.Path = path.toSlice() + err.ResolverError = visitorErr + return err + } else { + result = reflect.ValueOf(returned) + } + } + } + } } return nil }() diff --git a/types/directive.go b/types/directive.go index 7b62d51e..56233866 100644 --- a/types/directive.go +++ b/types/directive.go @@ -1,6 +1,10 @@ package types -import "github.com/graph-gophers/graphql-go/errors" +import ( + "context" + + "github.com/graph-gophers/graphql-go/errors" +) // Directive is a representation of the GraphQL Directive. // @@ -24,6 +28,11 @@ type DirectiveDefinition struct { type DirectiveList []*Directive +type DirectiveVisitor interface { + Before(ctx context.Context, directive *Directive, input interface{}) error + After(ctx context.Context, directive *Directive, output interface{}) (interface{}, error) +} + // Returns the Directive in the DirectiveList by name or nil if not found. func (l DirectiveList) Get(name string) *Directive { for _, d := range l { From fd885f00c85fbe9616aac34c3668020f8498d4fc Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Thu, 22 Dec 2022 12:23:48 -0800 Subject: [PATCH 02/20] simplify tests, improve organization of test file --- graphql_test.go | 164 ++++++++++++++++++++++++++++-------------------- 1 file changed, 96 insertions(+), 68 deletions(-) diff --git a/graphql_test.go b/graphql_test.go index 6133aff3..40345c6f 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -49,6 +49,10 @@ func (r *helloSnakeResolver2) SayHello(ctx context.Context, args struct{ FullNam return "Hello " + args.FullName + "!", nil } +type structFieldResolver struct { + Hello string +} + type customDirectiveVisitor struct { beforeWasCalled bool } @@ -65,9 +69,8 @@ func (v *customDirectiveVisitor) After(ctx context.Context, directive *types.Dir if value, ok := directive.Arguments.Get("customAttribute"); ok { return fmt.Sprintf("Directive '%s' (with arg '%s') modified result: %s", directive.Name.Name, value.String(), output.(string)), nil - } else { - return fmt.Sprintf("Directive '%s' modified result: %s", directive.Name.Name, output.(string)), nil } + return fmt.Sprintf("Directive '%s' modified result: %s", directive.Name.Name, output.(string)), nil } type theNumberResolver struct { @@ -237,6 +240,34 @@ func TestHelloWorld(t *testing.T) { }) } +func TestHelloWorldStructFieldResolver(t *testing.T) { + gqltesting.RunTests(t, []*gqltesting.Test{ + { + Schema: graphql.MustParseSchema(` + schema { + query: Query + } + + type Query { + hello: String! + } + `, + &structFieldResolver{Hello: "Hello world!"}, + graphql.UseFieldResolvers()), + Query: ` + { + hello + } + `, + ExpectedResult: ` + { + "hello": "Hello world!" + } + `, + }, + }) +} + func TestCustomDirective(t *testing.T) { t.Parallel() @@ -295,6 +326,69 @@ func TestCustomDirective(t *testing.T) { } `, }, + + // tests for struct field resolvers + + }) +} + +func TestCustomDirectiveStructFieldResolver(t *testing.T) { + schemaOpt := []graphql.SchemaOpt{ + graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ + "customDirective": &customDirectiveVisitor{}, + }), + graphql.UseFieldResolvers(), + } + + gqltesting.RunTests(t, []*gqltesting.Test{ + { + Schema: graphql.MustParseSchema(` + directive @customDirective on FIELD_DEFINITION + + schema { + query: Query + } + + type Query { + hello: String! @customDirective + } + `, &structFieldResolver{Hello: "Hello world!"}, schemaOpt...), + Query: ` + { + hello + } + `, + ExpectedResult: ` + { + "hello": "Directive 'customDirective' modified result: Hello world!" + } + `, + }, + { + Schema: graphql.MustParseSchema(` + directive @customDirective( + customAttribute: String! + ) on FIELD_DEFINITION + + schema { + query: Query + } + + type Query { + hello: String! @customDirective(customAttribute: hi) + } + `, &structFieldResolver{Hello: "Hello world!"}, schemaOpt...), + Query: ` + { + hello + } + `, + ExpectedResult: ` + { + "hello": "Directive 'customDirective' (with arg 'hi') modified result: Hello world!" + } + `, + }, }) } @@ -4632,69 +4726,3 @@ func TestQueryService(t *testing.T) { }, }) } - -type StructFieldResolver struct { - Hello string -} - -func TestStructFieldResolver(t *testing.T) { - gqltesting.RunTests(t, []*gqltesting.Test{ - { - Schema: graphql.MustParseSchema(` - schema { - query: Query - } - - type Query { - hello: String! - } - `, &StructFieldResolver{Hello: "Hello world!"}, graphql.UseFieldResolvers()), - Query: ` - { - hello - } - `, - ExpectedResult: ` - { - "hello": "Hello world!" - } - `, - }, - }) -} - -func TestDirectiveStructFieldResolver(t *testing.T) { - schemaOpt := []graphql.SchemaOpt{ - graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ - "customDirective": &customDirectiveVisitor{}, - }), - graphql.UseFieldResolvers(), - } - - gqltesting.RunTests(t, []*gqltesting.Test{ - - { - Schema: graphql.MustParseSchema(` - directive @customDirective on FIELD_DEFINITION - - schema { - query: Query - } - - type Query { - hello: String! @customDirective - } - `, &StructFieldResolver{Hello: "Hello world!"}, schemaOpt...), - Query: ` - { - hello - } - `, - ExpectedResult: ` - { - "hello": "Directive 'customDirective' modified result: Hello world!" - } - `, - }}) - -} From da343da73faf2ffa0b8b339c15eb38c16df84771 Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Thu, 22 Dec 2022 12:54:24 -0800 Subject: [PATCH 03/20] add t.Parellel() --- graphql_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graphql_test.go b/graphql_test.go index 40345c6f..1bd1b3f5 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -333,6 +333,8 @@ func TestCustomDirective(t *testing.T) { } func TestCustomDirectiveStructFieldResolver(t *testing.T) { + t.Parallel() + schemaOpt := []graphql.SchemaOpt{ graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ "customDirective": &customDirectiveVisitor{}, From 64aa7b92e0e1727f09b0052782b9400f129d6f38 Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Thu, 22 Dec 2022 12:54:53 -0800 Subject: [PATCH 04/20] remove useless comment --- graphql_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/graphql_test.go b/graphql_test.go index 1bd1b3f5..c82925f0 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -326,9 +326,6 @@ func TestCustomDirective(t *testing.T) { } `, }, - - // tests for struct field resolvers - }) } From 748ce7a1c6d698d8dec388a0ef1944f0861c2bc0 Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Thu, 22 Dec 2022 12:56:29 -0800 Subject: [PATCH 05/20] parallel some more --- graphql_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graphql_test.go b/graphql_test.go index c82925f0..d922c34e 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -241,6 +241,8 @@ func TestHelloWorld(t *testing.T) { } func TestHelloWorldStructFieldResolver(t *testing.T) { + t.Parallel() + gqltesting.RunTests(t, []*gqltesting.Test{ { Schema: graphql.MustParseSchema(` From 0bd7a0775b07ca13b9fcf17287f81a1c726e2b5a Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Thu, 22 Dec 2022 14:06:29 -0800 Subject: [PATCH 06/20] rename --- internal/exec/exec.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/exec/exec.go b/internal/exec/exec.go index fa592252..70e96acf 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -238,14 +238,14 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if !f.field.HasError && len(f.field.Directives) > 0 { for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { - returned, visitorErr := visitor.After(ctx, directive, result.Interface()) + modified, visitorErr := visitor.After(ctx, directive, result.Interface()) if visitorErr != nil { err := errors.Errorf("%s", visitorErr) err.Path = path.toSlice() err.ResolverError = visitorErr return err } else { - result = reflect.ValueOf(returned) + result = reflect.ValueOf(modified) } } } @@ -286,14 +286,14 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if !f.field.HasError && len(f.field.Directives) > 0 { for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { - returned, visitorErr := visitor.After(ctx, directive, result.Interface()) + modified, visitorErr := visitor.After(ctx, directive, result.Interface()) if visitorErr != nil { err := errors.Errorf("%s", visitorErr) err.Path = path.toSlice() err.ResolverError = visitorErr return err } else { - result = reflect.ValueOf(returned) + result = reflect.ValueOf(modified) } } } From ddc1b7ac14be8b621cc7f42e5242e56e85c88a97 Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Thu, 22 Dec 2022 14:12:18 -0800 Subject: [PATCH 07/20] go 1.18 and go mod tidy --- go.mod | 7 ++++++- go.sum | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 423043e8..0441e012 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,14 @@ module github.com/graph-gophers/graphql-go -go 1.13 +go 1.18 require ( github.com/opentracing/opentracing-go v1.2.0 go.opentelemetry.io/otel v1.6.3 go.opentelemetry.io/otel/trace v1.6.3 ) + +require ( + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect +) diff --git a/go.sum b/go.sum index b987a5d2..ddb252ae 100644 --- a/go.sum +++ b/go.sum @@ -19,9 +19,7 @@ go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc= go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From cf821db3292632418bb4c50c10974e1d1f75f537 Mon Sep 17 00:00:00 2001 From: Pavel Nikolov Date: Sat, 24 Dec 2022 16:07:29 +0200 Subject: [PATCH 08/20] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0441e012..26711d92 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/graph-gophers/graphql-go -go 1.18 +go 1.13 require ( github.com/opentracing/opentracing-go v1.2.0 From 77530e9c09d5696c037069f7c49ad9d06054f42f Mon Sep 17 00:00:00 2001 From: Pavel Nikolov Date: Sat, 24 Dec 2022 16:08:09 +0200 Subject: [PATCH 09/20] Update go.mod --- go.mod | 5 ----- 1 file changed, 5 deletions(-) diff --git a/go.mod b/go.mod index 26711d92..423043e8 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,3 @@ require ( go.opentelemetry.io/otel v1.6.3 go.opentelemetry.io/otel/trace v1.6.3 ) - -require ( - github.com/go-logr/logr v1.2.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect -) From 483881cb88b802035d9efd72821608fef01b6272 Mon Sep 17 00:00:00 2001 From: Pavel Nikolov Date: Sat, 24 Dec 2022 16:10:09 +0200 Subject: [PATCH 10/20] Update go.sum --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index ddb252ae..b987a5d2 100644 --- a/go.sum +++ b/go.sum @@ -19,7 +19,9 @@ go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc= go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 54416fdac2d788fe819d4d5a469b0a29105ebe83 Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Sat, 24 Dec 2022 08:07:53 -0800 Subject: [PATCH 11/20] move directives to separate package the types package is for types only, not implementation details. --- directives/doc.go | 5 +++++ directives/visitor.go | 14 ++++++++++++++ graphql.go | 5 +++-- graphql_test.go | 7 ++++--- internal/exec/exec.go | 3 ++- types/directive.go | 7 ------- 6 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 directives/doc.go create mode 100644 directives/visitor.go diff --git a/directives/doc.go b/directives/doc.go new file mode 100644 index 00000000..5990a655 --- /dev/null +++ b/directives/doc.go @@ -0,0 +1,5 @@ +/* + package directives contains a Visitor Pattern implementation of Schema Directives for Fields + */ +package directives + diff --git a/directives/visitor.go b/directives/visitor.go new file mode 100644 index 00000000..1305bb92 --- /dev/null +++ b/directives/visitor.go @@ -0,0 +1,14 @@ +package directives + +import ( + "context" + + "github.com/graph-gophers/graphql-go/types" +) + +// Visitor defines the interface that clients should use to implement a Directive +// see the graphql.DirectiveVisitors() Schema Option +type Visitor interface { + Before(ctx context.Context, directive *types.Directive, input interface{}) error + After(ctx context.Context, directive *types.Directive, output interface{}) (interface{}, error) +} diff --git a/graphql.go b/graphql.go index 5768c177..107701c2 100644 --- a/graphql.go +++ b/graphql.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/graph-gophers/graphql-go/directives" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/common" "github.com/graph-gophers/graphql-go/internal/exec" @@ -83,7 +84,7 @@ type Schema struct { useStringDescriptions bool disableIntrospection bool subscribeResolverTimeout time.Duration - visitors map[string]types.DirectiveVisitor + visitors map[string]directives.Visitor } func (s *Schema) ASTSchema() *types.Schema { @@ -172,7 +173,7 @@ func SubscribeResolverTimeout(timeout time.Duration) SchemaOpt { // DirectiveVisitors allows to pass custom directive visitors that will be able to handle // your GraphQL schema directives. -func DirectiveVisitors(visitors map[string]types.DirectiveVisitor) SchemaOpt { +func DirectiveVisitors(visitors map[string]directives.Visitor) SchemaOpt { return func(s *Schema) { s.visitors = visitors } diff --git a/graphql_test.go b/graphql_test.go index d922c34e..20f38940 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/directives" gqlerrors "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/example/starwars" "github.com/graph-gophers/graphql-go/gqltesting" @@ -286,7 +287,7 @@ func TestCustomDirective(t *testing.T) { hello_html: String! @customDirective } `, &helloSnakeResolver1{}, - graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ + graphql.DirectiveVisitors(map[string]directives.Visitor{ "customDirective": &customDirectiveVisitor{}, })), Query: ` @@ -314,7 +315,7 @@ func TestCustomDirective(t *testing.T) { say_hello(full_name: String!): String! @customDirective(customAttribute: hi) } `, &helloSnakeResolver1{}, - graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ + graphql.DirectiveVisitors(map[string]directives.Visitor{ "customDirective": &customDirectiveVisitor{}, })), Query: ` @@ -335,7 +336,7 @@ func TestCustomDirectiveStructFieldResolver(t *testing.T) { t.Parallel() schemaOpt := []graphql.SchemaOpt{ - graphql.DirectiveVisitors(map[string]types.DirectiveVisitor{ + graphql.DirectiveVisitors(map[string]directives.Visitor{ "customDirective": &customDirectiveVisitor{}, }), graphql.UseFieldResolvers(), diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 70e96acf..330ab72d 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/graph-gophers/graphql-go/directives" "github.com/graph-gophers/graphql-go/errors" "github.com/graph-gophers/graphql-go/internal/exec/resolvable" "github.com/graph-gophers/graphql-go/internal/exec/selected" @@ -25,7 +26,7 @@ type Request struct { Logger log.Logger PanicHandler errors.PanicHandler SubscribeResolverTimeout time.Duration - Visitors map[string]types.DirectiveVisitor + Visitors map[string]directives.Visitor } func (r *Request) handlePanic(ctx context.Context) { diff --git a/types/directive.go b/types/directive.go index 56233866..1077cf2b 100644 --- a/types/directive.go +++ b/types/directive.go @@ -1,8 +1,6 @@ package types import ( - "context" - "github.com/graph-gophers/graphql-go/errors" ) @@ -28,11 +26,6 @@ type DirectiveDefinition struct { type DirectiveList []*Directive -type DirectiveVisitor interface { - Before(ctx context.Context, directive *Directive, input interface{}) error - After(ctx context.Context, directive *Directive, output interface{}) (interface{}, error) -} - // Returns the Directive in the DirectiveList by name or nil if not found. func (l DirectiveList) Get(name string) *Directive { for _, d := range l { From 870991a5130f45033985c60d3fa43508c366e597 Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Sat, 24 Dec 2022 08:09:23 -0800 Subject: [PATCH 12/20] newline --- directives/doc.go | 1 - 1 file changed, 1 deletion(-) diff --git a/directives/doc.go b/directives/doc.go index 5990a655..2e2bbe68 100644 --- a/directives/doc.go +++ b/directives/doc.go @@ -2,4 +2,3 @@ package directives contains a Visitor Pattern implementation of Schema Directives for Fields */ package directives - From 945f124064c2ae1a9e7d92aeaba3468dbb9a277d Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Sat, 24 Dec 2022 08:11:51 -0800 Subject: [PATCH 13/20] clean up --- graphql.go | 4 ++-- types/directive.go | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/graphql.go b/graphql.go index 107701c2..4d46adcc 100644 --- a/graphql.go +++ b/graphql.go @@ -171,8 +171,8 @@ func SubscribeResolverTimeout(timeout time.Duration) SchemaOpt { } } -// DirectiveVisitors allows to pass custom directive visitors that will be able to handle -// your GraphQL schema directives. +// DirectiveVisitors defines the implementation for each directive. +// Per the GraphQL specification, each Field Directive in the schema must have an implementation here. func DirectiveVisitors(visitors map[string]directives.Visitor) SchemaOpt { return func(s *Schema) { s.visitors = visitors diff --git a/types/directive.go b/types/directive.go index 1077cf2b..7b62d51e 100644 --- a/types/directive.go +++ b/types/directive.go @@ -1,8 +1,6 @@ package types -import ( - "github.com/graph-gophers/graphql-go/errors" -) +import "github.com/graph-gophers/graphql-go/errors" // Directive is a representation of the GraphQL Directive. // From 92a4068ba3ff5dcab91754134b15425e1cb91d94 Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Sat, 24 Dec 2022 08:47:36 -0800 Subject: [PATCH 14/20] more docs --- directives/visitor.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/directives/visitor.go b/directives/visitor.go index 1305bb92..dc5d37af 100644 --- a/directives/visitor.go +++ b/directives/visitor.go @@ -9,6 +9,9 @@ import ( // Visitor defines the interface that clients should use to implement a Directive // see the graphql.DirectiveVisitors() Schema Option type Visitor interface { + // Before() is always called when the operation includes a directive matching this implementation's name + // Errors in Before() will prevent field resolution Before(ctx context.Context, directive *types.Directive, input interface{}) error + // After is called if Before() *and* the field resolver do not error After(ctx context.Context, directive *types.Directive, output interface{}) (interface{}, error) } From a1157faceeee8e0cd120f5186d43674cb0e83024 Mon Sep 17 00:00:00 2001 From: Sean Sorrell Date: Wed, 28 Dec 2022 17:32:51 -0500 Subject: [PATCH 15/20] demo for caching --- directives/visitor.go | 5 ++-- graphql_test.go | 56 +++++++++++++++++++++++++++++++++++++++++-- internal/exec/exec.go | 53 ++++++++++++++++++++++++++++------------ 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/directives/visitor.go b/directives/visitor.go index dc5d37af..00585300 100644 --- a/directives/visitor.go +++ b/directives/visitor.go @@ -10,8 +10,9 @@ import ( // see the graphql.DirectiveVisitors() Schema Option type Visitor interface { // Before() is always called when the operation includes a directive matching this implementation's name + // When the first return value is true, the field resolver will not be called. // Errors in Before() will prevent field resolution - Before(ctx context.Context, directive *types.Directive, input interface{}) error + Before(ctx context.Context, directive *types.Directive, input interface{}) (skipResolver bool, err error) // After is called if Before() *and* the field resolver do not error - After(ctx context.Context, directive *types.Directive, output interface{}) (interface{}, error) + After(ctx context.Context, directive *types.Directive, output interface{}) (modified interface{}, err error) } diff --git a/graphql_test.go b/graphql_test.go index 20f38940..5b18e1c9 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -58,9 +58,9 @@ type customDirectiveVisitor struct { beforeWasCalled bool } -func (v *customDirectiveVisitor) Before(ctx context.Context, directive *types.Directive, input interface{}) error { +func (v *customDirectiveVisitor) Before(ctx context.Context, directive *types.Directive, input interface{}) (bool, error) { v.beforeWasCalled = true - return nil + return false, nil } func (v *customDirectiveVisitor) After(ctx context.Context, directive *types.Directive, output interface{}) (interface{}, error) { @@ -74,6 +74,30 @@ func (v *customDirectiveVisitor) After(ctx context.Context, directive *types.Dir return fmt.Sprintf("Directive '%s' modified result: %s", directive.Name.Name, output.(string)), nil } +type cachedDirectiveVisitor struct { + cachedValue interface{} +} + +func (v *cachedDirectiveVisitor) Before(ctx context.Context, directive *types.Directive, input interface{}) (bool, error) { + s := "valueFromCache" + v.cachedValue = s + return true, nil +} + +func (v *cachedDirectiveVisitor) After(ctx context.Context, directive *types.Directive, output interface{}) (interface{}, error) { + return v.cachedValue, nil +} + +type cachedDirectiveResolver struct { + t *testing.T +} + +func (r *cachedDirectiveResolver) Hello(ctx context.Context, args struct{ FullName string }) string { + r.t.Error("expected cached resolver to not be called, but it was") + + return "" +} + type theNumberResolver struct { number int32 } @@ -329,6 +353,34 @@ func TestCustomDirective(t *testing.T) { } `, }, + { + Schema: graphql.MustParseSchema(` + directive @cached( + key: String! + ) on FIELD_DEFINITION + + schema { + query: Query + } + + type Query { + hello(full_name: String!): String! @cached(key: "notcheckedintest") + } + `, &cachedDirectiveResolver{t: t}, + graphql.DirectiveVisitors(map[string]directives.Visitor{ + "cached": &cachedDirectiveVisitor{}, + })), + Query: ` + { + hello(full_name: "Full Name") + } + `, + ExpectedResult: ` + { + "hello": "valueFromCache" + } + `, + }, }) } diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 330ab72d..03bb3338 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -203,7 +203,13 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f res := f.resolver if f.field.UseMethodResolver() { - var in []reflect.Value + var ( + skipResolver bool + in []reflect.Value + callOut []reflect.Value + visitorErr error + modified interface{} + ) if f.field.HasContext { in = append(in, reflect.ValueOf(traceCtx)) } @@ -219,8 +225,7 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f for _, inValue := range in { values = append(values, inValue.Interface()) } - - visitorErr := visitor.Before(ctx, directive, values) + skipResolver, visitorErr = visitor.Before(ctx, directive, values) if visitorErr != nil { err := errors.Errorf("%s", visitorErr) err.Path = path.toSlice() @@ -231,27 +236,36 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f } } - // Call method - callOut := res.Method(f.field.MethodIndex).Call(in) - result = callOut[0] + // Call method unless the Before visitor tells us not to + if !skipResolver { + callOut = res.Method(f.field.MethodIndex).Call(in) + result = callOut[0] + } // After hook directive visitor (when no error is returned from resolver) if !f.field.HasError && len(f.field.Directives) > 0 { for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { - modified, visitorErr := visitor.After(ctx, directive, result.Interface()) + if (result.IsValid() && !result.IsZero()) && result.CanInterface() { + modified, visitorErr = visitor.After(ctx, directive, result.Interface()) + } else { + modified, visitorErr = visitor.After(ctx, directive, nil) + } + if visitorErr != nil { err := errors.Errorf("%s", visitorErr) err.Path = path.toSlice() err.ResolverError = visitorErr return err - } else { - result = reflect.ValueOf(modified) } + result = reflect.ValueOf(modified) } } } + if skipResolver { + return nil + } if f.field.HasError && !callOut[1].IsNil() { resolverErr := callOut[1].Interface().(error) err := errors.Errorf("%s", resolverErr) @@ -263,6 +277,11 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f return err } } else { + var ( + skipResolver bool + visitorErr error + modified interface{} + ) // TODO extract out unwrapping ptr logic to a common place if res.Kind() == reflect.Ptr { res = res.Elem() @@ -271,8 +290,7 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if len(f.field.Directives) > 0 { for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { - // TODO check that directive arity == 0-that should be an error at schema init time - visitorErr := visitor.Before(ctx, directive, nil) + skipResolver, visitorErr = visitor.Before(ctx, directive, nil) if visitorErr != nil { err := errors.Errorf("%s", visitorErr) err.Path = path.toSlice() @@ -282,20 +300,25 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f } } } - result = res.FieldByIndex(f.field.FieldIndex) + if !skipResolver { + result = res.FieldByIndex(f.field.FieldIndex) + } // After hook directive visitor (when no error is returned from resolver) if !f.field.HasError && len(f.field.Directives) > 0 { for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { - modified, visitorErr := visitor.After(ctx, directive, result.Interface()) + if (result.IsValid() && !result.IsZero()) && result.CanInterface() { + modified, visitorErr = visitor.After(ctx, directive, result.Interface()) + } else { + modified, visitorErr = visitor.After(ctx, directive, nil) + } if visitorErr != nil { err := errors.Errorf("%s", visitorErr) err.Path = path.toSlice() err.ResolverError = visitorErr return err - } else { - result = reflect.ValueOf(modified) } + result = reflect.ValueOf(modified) } } } From 3b84a57f9efa37b060cdc6107f227862b7f917c0 Mon Sep 17 00:00:00 2001 From: pavelnikolov Date: Tue, 17 Jan 2023 13:42:57 +0200 Subject: [PATCH 16/20] Refactoring --- internal/exec/exec.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 03bb3338..321118f9 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -208,7 +208,6 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f in []reflect.Value callOut []reflect.Value visitorErr error - modified interface{} ) if f.field.HasContext { in = append(in, reflect.ValueOf(traceCtx)) @@ -236,7 +235,7 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f } } - // Call method unless the Before visitor tells us not to + // Call resolver method unless a Before visitor tells us not to if !skipResolver { callOut = res.Method(f.field.MethodIndex).Call(in) result = callOut[0] @@ -244,9 +243,10 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f // After hook directive visitor (when no error is returned from resolver) if !f.field.HasError && len(f.field.Directives) > 0 { + var modified interface{} for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { - if (result.IsValid() && !result.IsZero()) && result.CanInterface() { + if !skipResolver { modified, visitorErr = visitor.After(ctx, directive, result.Interface()) } else { modified, visitorErr = visitor.After(ctx, directive, nil) @@ -262,7 +262,6 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f } } } - if skipResolver { return nil } @@ -307,7 +306,7 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if !f.field.HasError && len(f.field.Directives) > 0 { for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { - if (result.IsValid() && !result.IsZero()) && result.CanInterface() { + if !skipResolver { modified, visitorErr = visitor.After(ctx, directive, result.Interface()) } else { modified, visitorErr = visitor.After(ctx, directive, nil) From 9d127cee5313d3f402c4f971cb295b6e1c6e7eed Mon Sep 17 00:00:00 2001 From: pavelnikolov Date: Tue, 17 Jan 2023 15:08:48 +0200 Subject: [PATCH 17/20] Add example hasRole directive --- example/directives/authorization/README.md | 128 ++++++++++++++++++ .../directives/authorization/authorization.go | 57 ++++++++ .../graphiql-has-role-example.png | Bin 0 -> 128077 bytes .../directives/authorization/server/server.go | 79 +++++++++++ example/directives/authorization/user/user.go | 37 +++++ 5 files changed, 301 insertions(+) create mode 100644 example/directives/authorization/README.md create mode 100644 example/directives/authorization/authorization.go create mode 100644 example/directives/authorization/graphiql-has-role-example.png create mode 100644 example/directives/authorization/server/server.go create mode 100644 example/directives/authorization/user/user.go diff --git a/example/directives/authorization/README.md b/example/directives/authorization/README.md new file mode 100644 index 00000000..fc328f11 --- /dev/null +++ b/example/directives/authorization/README.md @@ -0,0 +1,128 @@ +# @hasRole directive + +## Overview +A simple example of naive authorization directive which returns an error if the user in the context doesn't have the required role. Make sure that in production applications you use thread-safe maps for roles as an instance of the user struct might be accessed from multiple goroutines. In this naive example we use a simeple map which is not thread-safe. The required role to access a resolver is passed as an argument to the role, for example, `@hasRole(role: ADMIN)`. + +## Getting started +To run this server + +`go run ./example/directives/authorization/server/server.go` + +Navigate to https://localhost:8080 in your browser to interact with the GraphiQL UI. + +## Testing with curl +Access public resolver: +``` +$ curl 'http://localhost:8080/query' \ + -H 'Accept: application/json' \ + --data-raw '{"query":"# mutation {\nquery {\n publicGreet(name: \"John\")\n}","variables":null}' + +{"data":{"publicGreet":"Hello from the public resolver, John!"}} +``` +Try accessing protected resolver without required role: +``` +$ curl 'http://localhost:8080/query' \ + -H 'Accept: application/json' \ + --data-raw '{"query":"# mutation {\nquery {\n privateGreet(name: \"John\")\n}","variables":null}' +{"errors":[{"message":"access denied, \"admin\" role required","path":["privateGreet"]}],"data":null} +``` +Try accessing protected resolver again with appropriate role: +``` +$ curl 'http://localhost:8080/query' \ + -H 'Accept: application/json' \ + -H 'role: admin' \ + --data-raw '{"query":"# mutation {\nquery {\n privateGreet(name: \"John\")\n}","variables":null}' +{"data":{"privateGreet":"Hi from the protected resolver, John!"}} +``` + +## Implementation details + +1. Add directive definition to your shema: + ```graphql + directive @hasRole(role: Role!) on FIELD_DEFINITION + ``` + +2. Add directive to the protected fields in the schema: + ```graphql + type Query { + # other field resolvers here + privateGreet(name: String!): String! @hasRole(role: ADMIN) + } + ``` + +3. Define a user Go type which can have a slice of roles where each role is a string: + ```go + type User struct { + ID string + Roles map[string]struct{} + } + + func (u *User) AddRole(r string) { + if u.Roles == nil { + u.Roles = map[string]struct{}{} + } + u.Roles[r] = struct{}{} + } + + func (u *User) HasRole(r string) bool { + _, ok := u.Roles[r] + return ok + } + ``` + +4. Define a Go type which implements the DirevtiveVisitor interface: + ```go + type HasRoleDirective struct{} + + func (h *HasRoleDirective) Before(ctx context.Context, directive *types.Directive, input interface{}) (bool, error) { + u, ok := user.FromContext(ctx) + if !ok { + return true, fmt.Errorf("user not provided in cotext") + } + role := strings.ToLower((directive.Arguments.MustGet("role").String()) + if !u.HasRole(role) { + return true, fmt.Errorf("access denied, %q role required", role) + } + return false, nil + } + + // After is a no-op and returns the output unchanged. + func (h *HasRoleDirective) After(ctx context.Context, directive *types.Directive, output interface{}) (interface{}, error) { + return output, nil + } + ``` + +5. Pay attention to the schmema options. Directive visitors are added as schema option: + ```go + opts := []graphql.SchemaOpt{ + graphql.DirectiveVisitors(map[string]directives.Visitor{ + "hasRole": &authorization.HasRoleDirective{}, + }), + // other options go here + } + schema := graphql.MustParseSchema(authorization.Schema, &authorization.Resolver{}, opts...) + ``` + +6. Add a middleware to the HTTP handler which would read the `role` HTTP header and add that role to the slice of user roles. This naive middleware assumes that there is authentication proxy (e.g. Nginx, Envoy, Contour etc.) in front of this server which would authenticate the user and add their role in a header. In production application it would be fine if the same application handles the authentication and adds the user to the context. This is the middleware in this example: + ```go + func auth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + u := &user.User{} + role := r.Header.Get("role") + if role != "" { + u.AddRole(role) + } + ctx := user.AddToContext(context.Background(), u) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } + ``` + +7. Wrap the GraphQL handler with the auth middleware: + ```go + http.Handle("/query", auth(&relay.Handler{Schema: schema})) + ``` + +8. In order to access the private resolver add a role header like below: + +![accessing a private resolver using role header](graphiql-has-role-example.png) \ No newline at end of file diff --git a/example/directives/authorization/authorization.go b/example/directives/authorization/authorization.go new file mode 100644 index 00000000..fc9981a8 --- /dev/null +++ b/example/directives/authorization/authorization.go @@ -0,0 +1,57 @@ +// Package authorization contains a simple GraphQL schema using directives. +package authorization + +import ( + "context" + "fmt" + "strings" + + "github.com/graph-gophers/graphql-go/example/directives/authorization/user" + "github.com/graph-gophers/graphql-go/types" +) + +const Schema = ` + schema { + query: Query + } + + directive @hasRole(role: Role!) on FIELD_DEFINITION + + type Query { + publicGreet(name: String!): String! + privateGreet(name: String!): String! @hasRole(role: ADMIN) + } + + enum Role { + ADMIN + USER + } +` + +type HasRoleDirective struct{} + +func (h *HasRoleDirective) Before(ctx context.Context, directive *types.Directive, input interface{}) (bool, error) { + u, ok := user.FromContext(ctx) + if !ok { + return true, fmt.Errorf("user not provided in cotext") + } + role := strings.ToLower(directive.Arguments.MustGet("role").String()) + if !u.HasRole(role) { + return true, fmt.Errorf("access denied, %q role required", role) + } + return false, nil +} + +func (h *HasRoleDirective) After(ctx context.Context, directive *types.Directive, output interface{}) (interface{}, error) { + return output, nil +} + +type Resolver struct{} + +func (r *Resolver) PublicGreet(ctx context.Context, args struct{ Name string }) string { + return fmt.Sprintf("Hello from the public resolver, %s!", args.Name) +} + +func (r *Resolver) PrivateGreet(ctx context.Context, args struct{ Name string }) string { + return fmt.Sprintf("Hi from the protected resolver, %s!", args.Name) +} diff --git a/example/directives/authorization/graphiql-has-role-example.png b/example/directives/authorization/graphiql-has-role-example.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb008b473334a651fc0ecedfadff9b208b2042a GIT binary patch literal 128077 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 + + + GraphiQL + + + + + + +
Loading...
+ + + + +`) diff --git a/example/directives/authorization/user/user.go b/example/directives/authorization/user/user.go new file mode 100644 index 00000000..e8ba6111 --- /dev/null +++ b/example/directives/authorization/user/user.go @@ -0,0 +1,37 @@ +// package user contains a naive implementation of an user with roles. +// Each user can be assigned roles and added to/retrieved from context. +package user + +import ( + "context" +) + +type userKey string + +const contextKey userKey = "user" + +type User struct { + ID string + Roles map[string]struct{} +} + +func (u *User) AddRole(r string) { + if u.Roles == nil { + u.Roles = map[string]struct{}{} + } + u.Roles[r] = struct{}{} +} + +func (u *User) HasRole(r string) bool { + _, ok := u.Roles[r] + return ok +} + +func AddToContext(ctx context.Context, u *User) context.Context { + return context.WithValue(ctx, contextKey, u) +} + +func FromContext(ctx context.Context) (*User, bool) { + u, ok := ctx.Value(contextKey).(*User) + return u, ok +} From b290e8b698049e3734d4ec2a481b2ef9f8c41c6e Mon Sep 17 00:00:00 2001 From: pavelnikolov Date: Tue, 17 Jan 2023 15:42:52 +0200 Subject: [PATCH 18/20] Pass traceCtx to directive visitors --- internal/exec/exec.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 321118f9..55b0a7fe 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -224,7 +224,7 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f for _, inValue := range in { values = append(values, inValue.Interface()) } - skipResolver, visitorErr = visitor.Before(ctx, directive, values) + skipResolver, visitorErr = visitor.Before(traceCtx, directive, values) if visitorErr != nil { err := errors.Errorf("%s", visitorErr) err.Path = path.toSlice() @@ -247,9 +247,9 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { if !skipResolver { - modified, visitorErr = visitor.After(ctx, directive, result.Interface()) + modified, visitorErr = visitor.After(traceCtx, directive, result.Interface()) } else { - modified, visitorErr = visitor.After(ctx, directive, nil) + modified, visitorErr = visitor.After(traceCtx, directive, nil) } if visitorErr != nil { @@ -289,7 +289,7 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f if len(f.field.Directives) > 0 { for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { - skipResolver, visitorErr = visitor.Before(ctx, directive, nil) + skipResolver, visitorErr = visitor.Before(traceCtx, directive, nil) if visitorErr != nil { err := errors.Errorf("%s", visitorErr) err.Path = path.toSlice() @@ -307,9 +307,9 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f for _, directive := range f.field.Directives { if visitor, ok := r.Visitors[directive.Name.Name]; ok { if !skipResolver { - modified, visitorErr = visitor.After(ctx, directive, result.Interface()) + modified, visitorErr = visitor.After(traceCtx, directive, result.Interface()) } else { - modified, visitorErr = visitor.After(ctx, directive, nil) + modified, visitorErr = visitor.After(traceCtx, directive, nil) } if visitorErr != nil { err := errors.Errorf("%s", visitorErr) From 2af43e4bdceb3294ac0514ca84ce92e13c9d4ab7 Mon Sep 17 00:00:00 2001 From: pavelnikolov Date: Tue, 17 Jan 2023 16:45:54 +0200 Subject: [PATCH 19/20] Fix a typo --- example/directives/authorization/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/directives/authorization/README.md b/example/directives/authorization/README.md index fc328f11..a99816c7 100644 --- a/example/directives/authorization/README.md +++ b/example/directives/authorization/README.md @@ -1,7 +1,7 @@ # @hasRole directive ## Overview -A simple example of naive authorization directive which returns an error if the user in the context doesn't have the required role. Make sure that in production applications you use thread-safe maps for roles as an instance of the user struct might be accessed from multiple goroutines. In this naive example we use a simeple map which is not thread-safe. The required role to access a resolver is passed as an argument to the role, for example, `@hasRole(role: ADMIN)`. +A simple example of naive authorization directive which returns an error if the user in the context doesn't have the required role. Make sure that in production applications you use thread-safe maps for roles as an instance of the user struct might be accessed from multiple goroutines. In this naive example we use a simeple map which is not thread-safe. The required role to access a resolver is passed as an argument to the directive, for example, `@hasRole(role: ADMIN)`. ## Getting started To run this server From 12642b15f85d0c69d9d8c60c855193cc64fa49bf Mon Sep 17 00:00:00 2001 From: pavelnikolov Date: Thu, 19 Jan 2023 15:20:53 +0200 Subject: [PATCH 20/20] Improve documentation --- README.md | 8 ++++---- directives/doc.go | 4 ++-- directives/visitor.go | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6c9d2d8a..837656aa 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ The goal of this project is to provide full support of the [GraphQL draft specification](https://facebook.github.io/graphql/draft) with a set of idiomatic, easy to use Go packages. -While still under heavy development (`internal` APIs are almost certainly subject to change), this library is -safe for production use. +While still under development (`internal` and `directives` APIs are almost certainly subject to change), this library is safe for production use. ## Features @@ -17,14 +16,15 @@ safe for production use. - handles panics in resolvers - parallel execution of resolvers - subscriptions - - [sample WS transport](https://github.com/graph-gophers/graphql-transport-ws) + - [sample WS transport](https://github.com/graph-gophers/graphql-transport-ws) +- directive visitors on fields (the API is subject to change in future versions) ## Roadmap We're trying out the GitHub Project feature to manage `graphql-go`'s [development roadmap](https://github.com/graph-gophers/graphql-go/projects/1). Feedback is welcome and appreciated. -## (Some) Documentation +## (Some) Documentation [![GoDoc](https://godoc.org/github.com/graph-gophers/graphql-go?status.svg)](https://godoc.org/github.com/graph-gophers/graphql-go) ### Getting started diff --git a/directives/doc.go b/directives/doc.go index 2e2bbe68..39a1c61e 100644 --- a/directives/doc.go +++ b/directives/doc.go @@ -1,4 +1,4 @@ /* - package directives contains a Visitor Pattern implementation of Schema Directives for Fields - */ +package directives contains a Visitor Pattern implementation of Schema Directives for Fields. +*/ package directives diff --git a/directives/visitor.go b/directives/visitor.go index 00585300..729b6724 100644 --- a/directives/visitor.go +++ b/directives/visitor.go @@ -7,12 +7,12 @@ import ( ) // Visitor defines the interface that clients should use to implement a Directive -// see the graphql.DirectiveVisitors() Schema Option +// see the graphql.DirectiveVisitors() Schema Option. type Visitor interface { - // Before() is always called when the operation includes a directive matching this implementation's name + // Before() is always called when the operation includes a directive matching this implementation's name. // When the first return value is true, the field resolver will not be called. - // Errors in Before() will prevent field resolution + // Errors in Before() will prevent field resolution. Before(ctx context.Context, directive *types.Directive, input interface{}) (skipResolver bool, err error) - // After is called if Before() *and* the field resolver do not error + // After is called if Before() *and* the field resolver do not error. After(ctx context.Context, directive *types.Directive, output interface{}) (modified interface{}, err error) }