Skip to content

Commit

Permalink
Merge pull request #1121 from Khan/extern-only
Browse files Browse the repository at this point in the history
Do not require a resolver for "empty" extended types.
  • Loading branch information
lwc authored Jul 9, 2020
2 parents 555db6d + 55e0f0d commit acaee36
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 35 deletions.
68 changes: 55 additions & 13 deletions plugin/federation/federation.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,13 @@ func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source {
}
entities += e.Name

resolverArgs := ""
for _, field := range e.KeyFields {
resolverArgs += fmt.Sprintf("%s: %s,", field.Field.Name, field.Field.Type.String())
if e.ResolverName != "" {
resolverArgs := ""
for _, field := range e.KeyFields {
resolverArgs += fmt.Sprintf("%s: %s,", field.Field.Name, field.Field.Type.String())
}
resolvers += fmt.Sprintf("\t%s(%s): %s!\n", e.ResolverName, resolverArgs, e.Def.Name)
}
resolvers += fmt.Sprintf("\t%s(%s): %s!\n", e.ResolverName, resolverArgs, e.Def.Name)

}

Expand All @@ -109,18 +111,24 @@ func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source {
return nil
}

// resolvers can be empty if a service defines only "empty
// extend" types. This should be rare.
if resolvers != "" {
resolvers = `
# fake type to build resolver interfaces for users to implement
type Entity {
` + resolvers + `
}
`
}

return &ast.Source{
Name: "federation/entity.graphql",
BuiltIn: true,
Input: `
# a union of all types that use the @key directive
union _Entity = ` + entities + `
# fake type to build resolver interfaces for users to implement
type Entity {
` + resolvers + `
}
` + resolvers + `
type _Service {
sdl: String
}
Expand Down Expand Up @@ -162,9 +170,20 @@ type RequireField struct {
TypeReference *config.TypeReference // The Go representation of that field type
}

func (e *Entity) allFieldsAreExternal() bool {
for _, field := range e.Def.Fields {
if field.Directives.ForName("external") == nil {
return false
}
}
return true
}

func (f *federation) GenerateCode(data *codegen.Data) error {
if len(f.Entities) > 0 {
data.Objects.ByName("Entity").Root = true
if data.Objects.ByName("Entity") != nil {
data.Objects.ByName("Entity").Root = true
}
for _, e := range f.Entities {
obj := data.Objects.ByName(e.Def.Name)
for _, field := range obj.Fields {
Expand Down Expand Up @@ -251,13 +270,36 @@ func (f *federation) setEntities(schema *ast.Schema) {

}

f.Entities = append(f.Entities, &Entity{
e := &Entity{
Name: schemaType.Name,
KeyFields: keyFields,
Def: schemaType,
ResolverName: resolverName,
Requires: requires,
})
}
// If our schema has a field with a type defined in
// another service, then we need to define an "empty
// extend" of that type in this service, so this service
// knows what the type is like. But the graphql-server
// will never ask us to actually resolve this "empty
// extend", so we don't require a resolver function for
// it. (Well, it will never ask in practice; it's
// unclear whether the spec guarantees this. See
// https://github.com/apollographql/apollo-server/issues/3852
// ). Example:
// type MyType {
// myvar: TypeDefinedInOtherService
// }
// // Federation needs this type, but
// // it doesn't need a resolver for it!
// extend TypeDefinedInOtherService @key(fields: "id") {
// id: ID @external
// }
if e.allFieldsAreExternal() {
e.ResolverName = ""
}

f.Entities = append(f.Entities, e)
}
}
}
Expand Down
40 changes: 21 additions & 19 deletions plugin/federation/federation.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,31 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
}
switch typeName {
{{ range .Entities }}
case "{{.Def.Name}}":
{{ range $i, $keyField := .KeyFields -}}
id{{$i}}, err := ec.{{.TypeReference.UnmarshalFunc}}(ctx, rep["{{$keyField.Field.Name}}"])
if err != nil {
return nil, errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{$keyField.Field.Name}}"))
}
{{end}}

entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx,
{{ range $i, $_ := .KeyFields -}} id{{$i}}, {{end}})
if err != nil {
return nil, err
}

{{ range .Requires }}
{{ range .Fields}}
entity.{{.NameGo}}, err = ec.{{.TypeReference.UnmarshalFunc}}(ctx, rep["{{.Name}}"])
{{ if .ResolverName }}
case "{{.Def.Name}}":
{{ range $i, $keyField := .KeyFields -}}
id{{$i}}, err := ec.{{.TypeReference.UnmarshalFunc}}(ctx, rep["{{$keyField.Field.Name}}"])
if err != nil {
return nil, err
return nil, errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{$keyField.Field.Name}}"))
}
{{end}}

entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx,
{{ range $i, $_ := .KeyFields -}} id{{$i}}, {{end}})
if err != nil {
return nil, err
}

{{ range .Requires }}
{{ range .Fields}}
entity.{{.NameGo}}, err = ec.{{.TypeReference.UnmarshalFunc}}(ctx, rep["{{.Name}}"])
if err != nil {
return nil, err
}
{{ end }}
{{ end }}
list = append(list, entity)
{{ end }}
list = append(list, entity)
{{ end }}
default:
return nil, errors.New("unknown type: "+typeName)
Expand Down
3 changes: 2 additions & 1 deletion plugin/resolvergen/testdata/followschema/out/resolver.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package customresolver

// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.
package customresolver

type CustomResolverType struct{}
9 changes: 7 additions & 2 deletions plugin/resolvergen/testdata/singlefile/out/resolver.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
package customresolver

// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.

import (
"context"
)
Expand All @@ -10,11 +11,15 @@ type CustomResolverType struct{}
func (r *queryCustomResolverType) Resolver(ctx context.Context) (*Resolver, error) {
panic("not implemented")
}

func (r *resolverCustomResolverType) Name(ctx context.Context, obj *Resolver) (string, error) {
panic("not implemented")
}

func (r *CustomResolverType) Query() QueryResolver { return &queryCustomResolverType{r} }
// Query returns QueryResolver implementation.
func (r *CustomResolverType) Query() QueryResolver { return &queryCustomResolverType{r} }

// Resolver returns ResolverResolver implementation.
func (r *CustomResolverType) Resolver() ResolverResolver { return &resolverCustomResolverType{r} }

type queryCustomResolverType struct{ *CustomResolverType }
Expand Down

0 comments on commit acaee36

Please sign in to comment.