From 53c352d22112facbafa6667500c204174b947a07 Mon Sep 17 00:00:00 2001 From: Alam Date: Thu, 17 Mar 2022 16:29:06 +0700 Subject: [PATCH 1/5] add meta field _service --- internal/exec/resolvable/meta.go | 18 ++++++++++++++++++ internal/exec/selected/selected.go | 11 +++++++++++ internal/schema/meta.go | 4 ++++ internal/schema/schema.go | 2 ++ internal/validation/validation.go | 5 +++++ introspection/introspection.go | 13 +++++++++++++ types/schema.go | 1 + 7 files changed, 54 insertions(+) diff --git a/internal/exec/resolvable/meta.go b/internal/exec/resolvable/meta.go index 02d5e262..aafe9acd 100644 --- a/internal/exec/resolvable/meta.go +++ b/internal/exec/resolvable/meta.go @@ -12,8 +12,10 @@ type Meta struct { FieldSchema Field FieldType Field FieldTypename Field + FieldService Field Schema *Object Type *Object + Service *Object } func newMeta(s *types.Schema) *Meta { @@ -32,6 +34,12 @@ func newMeta(s *types.Schema) *Meta { panic(err) } + metaService := s.Types["_Service"].(*types.ObjectTypeDefinition) + sv, err := b.makeObjectExec(metaService.Name, metaService.Fields, nil, false, reflect.TypeOf(&introspection.Service{})) + if err != nil { + panic(err) + } + if err := b.finish(); err != nil { panic(err) } @@ -60,11 +68,21 @@ func newMeta(s *types.Schema) *Meta { TraceLabel: "GraphQL field: __type", } + fieldService := Field{ + FieldDefinition: types.FieldDefinition{ + Name: "_service", + Type: s.Types["_Service"], + }, + TraceLabel: "GraphQL field: _service", + } + return &Meta{ FieldSchema: fieldSchema, FieldTypename: fieldTypename, FieldType: fieldType, + FieldService: fieldService, Schema: so, Type: t, + Service: sv, } } diff --git a/internal/exec/selected/selected.go b/internal/exec/selected/selected.go index 9b96d2b6..868dc1e9 100644 --- a/internal/exec/selected/selected.go +++ b/internal/exec/selected/selected.go @@ -121,6 +121,17 @@ func applySelectionSet(r *Request, s *resolvable.Schema, e *resolvable.Object, s }) } + case "_service": + if !r.DisableIntrospection { + flattenedSels = append(flattenedSels, &SchemaField{ + Field: s.Meta.FieldService, + Alias: field.Alias.Name, + Sels: applySelectionSet(r, s, s.Meta.Service, field.SelectionSet), + Async: true, + FixedResult: reflect.ValueOf(introspection.WrapService(r.Schema)), + }) + } + default: fe := e.Fields[field.Name.Name] diff --git a/internal/schema/meta.go b/internal/schema/meta.go index 9f5bba56..2268b7e7 100644 --- a/internal/schema/meta.go +++ b/internal/schema/meta.go @@ -200,4 +200,8 @@ var metaSrc = ` # Indicates this type is a non-null. ` + "`" + `ofType` + "`" + ` is a valid field. NON_NULL } + + type _Service { + sdl: String! + } ` diff --git a/internal/schema/schema.go b/internal/schema/schema.go index e1c77032..d7791999 100644 --- a/internal/schema/schema.go +++ b/internal/schema/schema.go @@ -161,6 +161,8 @@ func Parse(s *types.Schema, schemaString string, useStringDescriptions bool) err } } + s.SchemaString = schemaString + return nil } diff --git a/internal/validation/validation.go b/internal/validation/validation.go index e3672638..0f07e349 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -327,6 +327,11 @@ func validateSelection(c *opContext, sel types.Selection, t types.NamedType) { }, Type: c.schema.Types["__Type"], } + case "_service": + f = &types.FieldDefinition{ + Name: "_service", + Type: c.schema.Types["_Service"], + } default: f = fields(t).Get(fieldName) if f == nil && t != nil { diff --git a/introspection/introspection.go b/introspection/introspection.go index a0a2fa9b..3df66055 100644 --- a/introspection/introspection.go +++ b/introspection/introspection.go @@ -310,3 +310,16 @@ func (r *Directive) Args() []*InputValue { } return l } + +type Service struct { + schema *types.Schema +} + +// WrapService is only used internally. +func WrapService(schema *types.Schema) *Service { + return &Service{schema} +} + +func (r *Service) SDL() string { + return r.schema.SchemaString +} diff --git a/types/schema.go b/types/schema.go index 06811a97..349c112b 100644 --- a/types/schema.go +++ b/types/schema.go @@ -35,6 +35,7 @@ type Schema struct { Unions []*Union Enums []*EnumTypeDefinition Extensions []*Extension + SchemaString string } func (s *Schema) Resolve(name string) Type { From 5759e511c11ff5ee0d50d50f0ba11a5ab266ff11 Mon Sep 17 00:00:00 2001 From: Alam Date: Thu, 17 Mar 2022 18:01:52 +0700 Subject: [PATCH 2/5] unit test --- example/social/introspect.json | 27 +++++++++++++++++++++++++ example/starwars/introspect.json | 27 +++++++++++++++++++++++++ graphql_test.go | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/example/social/introspect.json b/example/social/introspect.json index 330564ec..f344b600 100644 --- a/example/social/introspect.json +++ b/example/social/introspect.json @@ -589,6 +589,33 @@ "name": "User", "possibleTypes": null }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "sdl", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "_Service", + "possibleTypes": null + }, { "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior\nin ways field arguments will not suffice, such as conditionally including or\nskipping a field. Directives provide this by describing additional information\nto the executor.", "enumValues": null, diff --git a/example/starwars/introspect.json b/example/starwars/introspect.json index 2b955ee9..89ed5a3f 100644 --- a/example/starwars/introspect.json +++ b/example/starwars/introspect.json @@ -1231,6 +1231,33 @@ "name": "String", "possibleTypes": null }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "sdl", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "_Service", + "possibleTypes": null + }, { "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior\nin ways field arguments will not suffice, such as conditionally including or\nskipping a field. Directives provide this by describing additional information\nto the executor.", "enumValues": null, diff --git a/graphql_test.go b/graphql_test.go index c8d9593b..ee1a74a7 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -2144,6 +2144,7 @@ func TestIntrospection(t *testing.T) { { "name": "SearchResult" }, { "name": "Starship" }, { "name": "String" }, + { "name": "_Service" }, { "name": "__Directive" }, { "name": "__DirectiveLocation" }, { "name": "__EnumValue" }, @@ -4326,3 +4327,36 @@ func TestCircularFragmentMaxDepth(t *testing.T) { }, }) } + +func TestQueryService(t *testing.T) { + t.Parallel() + + schemaString := ` + schema { + query: Query + } + + type Query { + hello: String! + }` + + gqltesting.RunTests(t, []*gqltesting.Test{ + { + Schema: graphql.MustParseSchema(schemaString, &helloWorldResolver1{}), + Query: ` + { + _service{ + sdl + } + } + `, + ExpectedResult: fmt.Sprintf(` + { + "_service": { + "sdl": "\n\tschema {\n\t\tquery: Query\n\t}\n\n\ttype Query {\n\t\thello: String!\n\t}" + } + } + `), + }, + }) +} From 821c0e2d6c21e48079b2e25e8ba73f1df04f4e8e Mon Sep 17 00:00:00 2001 From: aeramu Date: Fri, 18 Mar 2022 14:31:21 +0700 Subject: [PATCH 3/5] add example for apollo federation --- example/apollo_federation/README.md | 15 ++++ example/apollo_federation/gateway/index.js | 20 +++++ .../apollo_federation/gateway/package.json | 14 ++++ .../apollo_federation/subgraph_one/server.go | 76 +++++++++++++++++++ .../apollo_federation/subgraph_two/server.go | 76 +++++++++++++++++++ 5 files changed, 201 insertions(+) create mode 100644 example/apollo_federation/README.md create mode 100644 example/apollo_federation/gateway/index.js create mode 100644 example/apollo_federation/gateway/package.json create mode 100644 example/apollo_federation/subgraph_one/server.go create mode 100644 example/apollo_federation/subgraph_two/server.go diff --git a/example/apollo_federation/README.md b/example/apollo_federation/README.md new file mode 100644 index 00000000..b56e3ade --- /dev/null +++ b/example/apollo_federation/README.md @@ -0,0 +1,15 @@ +### Apollo Federation + +A simple example of integration with apollo federation as subgraph. + +To run this server + +`go run ./example/apollo_federation/subgraph_one/server.go` + +`go run ./example/apollo_federation/subgraph_two/server.go` + +`cd example/apollo_federation/gateway` + +`yarn start` + +and go to localhost:4000 to interact diff --git a/example/apollo_federation/gateway/index.js b/example/apollo_federation/gateway/index.js new file mode 100644 index 00000000..6c8619e4 --- /dev/null +++ b/example/apollo_federation/gateway/index.js @@ -0,0 +1,20 @@ +const { ApolloServer } = require('apollo-server') +const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway'); + +const gateway = new ApolloGateway({ + supergraphSdl: new IntrospectAndCompose({ + subgraphs: [ + { name: 'one', url: 'http://localhost:4001' }, + { name: 'two', url: 'http://localhost:4002' }, + ], + }), +}); + +const server = new ApolloServer({ + gateway, + subscriptions: false, +}); + +server.listen().then(({ url }) => { + console.log(`Server ready at ${url}`); +}); \ No newline at end of file diff --git a/example/apollo_federation/gateway/package.json b/example/apollo_federation/gateway/package.json new file mode 100644 index 00000000..58962a38 --- /dev/null +++ b/example/apollo_federation/gateway/package.json @@ -0,0 +1,14 @@ +{ + "name": "apollo-federation-gateway", + "version": "1.0.0", + "description": "Graphql Federation", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@apollo/gateway": "^0.24.4", + "apollo-server": "^2.21.1", + "graphql": "^15.5.0" + } +} \ No newline at end of file diff --git a/example/apollo_federation/subgraph_one/server.go b/example/apollo_federation/subgraph_one/server.go new file mode 100644 index 00000000..aaa807a5 --- /dev/null +++ b/example/apollo_federation/subgraph_one/server.go @@ -0,0 +1,76 @@ +package main + +import ( + "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/relay" + "log" + "net/http" +) + +var schema = ` + schema { + query: Query + } + + type Query { + hello: String! + } +` + +type resolver struct {} + +func (r *resolver) Hello() string { + return "Hello, world! from subgraph one." +} + +func main() { + opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)} + schema := graphql.MustParseSchema(schema, &resolver{}, opts...) + + http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(page) + })) + + http.Handle("/query", &relay.Handler{Schema: schema}) + + log.Fatal(http.ListenAndServe(":4001", nil)) +} + +var page = []byte(` + + + + + + + + + + + +
Loading...
+ + + +`) diff --git a/example/apollo_federation/subgraph_two/server.go b/example/apollo_federation/subgraph_two/server.go new file mode 100644 index 00000000..413c6526 --- /dev/null +++ b/example/apollo_federation/subgraph_two/server.go @@ -0,0 +1,76 @@ +package main + +import ( + "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/relay" + "log" + "net/http" +) + +var schema = ` + schema { + query: Query + } + + type Query { + hi: String! + } +` + +type resolver struct {} + +func (r *resolver) Hi() string { + return "Hi, world! from subgraph two." +} + +func main() { + opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)} + schema := graphql.MustParseSchema(schema, &resolver{}, opts...) + + http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(page) + })) + + http.Handle("/query", &relay.Handler{Schema: schema}) + + log.Fatal(http.ListenAndServe(":4002", nil)) +} + +var page = []byte(` + + + + + + + + + + + +
Loading...
+ + + +`) From 3fa26351e386607cf168173f40ad163c7c83faa8 Mon Sep 17 00:00:00 2001 From: pavelnikolov Date: Sun, 20 Mar 2022 16:21:17 +0200 Subject: [PATCH 4/5] Fix formatting and dependencies version --- example/apollo_federation/gateway/index.js | 6 +-- .../apollo_federation/gateway/package.json | 4 +- .../apollo_federation/subgraph_one/server.go | 52 ++----------------- .../apollo_federation/subgraph_two/server.go | 52 ++----------------- 4 files changed, 15 insertions(+), 99 deletions(-) diff --git a/example/apollo_federation/gateway/index.js b/example/apollo_federation/gateway/index.js index 6c8619e4..f46b00a1 100644 --- a/example/apollo_federation/gateway/index.js +++ b/example/apollo_federation/gateway/index.js @@ -4,8 +4,8 @@ const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway'); const gateway = new ApolloGateway({ supergraphSdl: new IntrospectAndCompose({ subgraphs: [ - { name: 'one', url: 'http://localhost:4001' }, - { name: 'two', url: 'http://localhost:4002' }, + { name: 'one', url: 'http://localhost:4001/query' }, + { name: 'two', url: 'http://localhost:4002/query' }, ], }), }); @@ -17,4 +17,4 @@ const server = new ApolloServer({ server.listen().then(({ url }) => { console.log(`Server ready at ${url}`); -}); \ No newline at end of file +}); diff --git a/example/apollo_federation/gateway/package.json b/example/apollo_federation/gateway/package.json index 58962a38..b2e5af49 100644 --- a/example/apollo_federation/gateway/package.json +++ b/example/apollo_federation/gateway/package.json @@ -7,8 +7,8 @@ "start": "node index.js" }, "dependencies": { - "@apollo/gateway": "^0.24.4", + "@apollo/gateway": "^0.49.0", "apollo-server": "^2.21.1", "graphql": "^15.5.0" } -} \ No newline at end of file +} diff --git a/example/apollo_federation/subgraph_one/server.go b/example/apollo_federation/subgraph_one/server.go index aaa807a5..c46634d3 100644 --- a/example/apollo_federation/subgraph_one/server.go +++ b/example/apollo_federation/subgraph_one/server.go @@ -1,10 +1,11 @@ package main import ( - "github.com/graph-gophers/graphql-go" - "github.com/graph-gophers/graphql-go/relay" "log" "net/http" + + "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/relay" ) var schema = ` @@ -17,60 +18,17 @@ var schema = ` } ` -type resolver struct {} +type resolver struct{} func (r *resolver) Hello() string { - return "Hello, world! from subgraph one." + return "Hello from subgraph one!" } func main() { opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)} schema := graphql.MustParseSchema(schema, &resolver{}, opts...) - http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(page) - })) - http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":4001", nil)) } - -var page = []byte(` - - - - - - - - - - - -
Loading...
- - - -`) diff --git a/example/apollo_federation/subgraph_two/server.go b/example/apollo_federation/subgraph_two/server.go index 413c6526..03a56813 100644 --- a/example/apollo_federation/subgraph_two/server.go +++ b/example/apollo_federation/subgraph_two/server.go @@ -1,10 +1,11 @@ package main import ( - "github.com/graph-gophers/graphql-go" - "github.com/graph-gophers/graphql-go/relay" "log" "net/http" + + "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/relay" ) var schema = ` @@ -17,60 +18,17 @@ var schema = ` } ` -type resolver struct {} +type resolver struct{} func (r *resolver) Hi() string { - return "Hi, world! from subgraph two." + return "Hi from subgraph two!" } func main() { opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)} schema := graphql.MustParseSchema(schema, &resolver{}, opts...) - http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(page) - })) - http.Handle("/query", &relay.Handler{Schema: schema}) log.Fatal(http.ListenAndServe(":4002", nil)) } - -var page = []byte(` - - - - - - - - - - - -
Loading...
- - - -`) From 261edf5d2a341643544766030528930c62061eb5 Mon Sep 17 00:00:00 2001 From: pavelnikolov Date: Sun, 20 Mar 2022 17:44:33 +0200 Subject: [PATCH 5/5] Improve readme instructions. --- example/apollo_federation/README.md | 24 ++++++++++++++++++-- example/apollo_federation/gateway/.gitignore | 1 + 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 example/apollo_federation/gateway/.gitignore diff --git a/example/apollo_federation/README.md b/example/apollo_federation/README.md index b56e3ade..04bc15bd 100644 --- a/example/apollo_federation/README.md +++ b/example/apollo_federation/README.md @@ -1,6 +1,6 @@ -### Apollo Federation +# Apollo Federation -A simple example of integration with apollo federation as subgraph. +A simple example of integration with apollo federation as subgraph. Tested with Go v1.18, Node.js v16.14.2 and yarn 1.22.18. To run this server @@ -13,3 +13,23 @@ To run this server `yarn start` and go to localhost:4000 to interact + +Execute the query: + +``` +query { + hello + hi +} +``` + +and you should see a result similar to this: + +```json +{ + "data": { + "hello": "Hello from subgraph one!", + "hi": "Hi from subgraph two!" + } +} +``` diff --git a/example/apollo_federation/gateway/.gitignore b/example/apollo_federation/gateway/.gitignore new file mode 100644 index 00000000..07e6e472 --- /dev/null +++ b/example/apollo_federation/gateway/.gitignore @@ -0,0 +1 @@ +/node_modules