diff --git a/plugin/federation/federation.go b/plugin/federation/federation.go index 40e812a7bb0..c51e1fef211 100644 --- a/plugin/federation/federation.go +++ b/plugin/federation/federation.go @@ -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) } @@ -162,6 +164,10 @@ type RequireField struct { TypeReference *config.TypeReference // The Go representation of that field type } +func (e *Entity) allFieldsAreKeyFields() bool { + return len(e.Def.Fields) == len(e.KeyFields) +} + func (f *federation) GenerateCode(data *codegen.Data) error { if len(f.Entities) > 0 { data.Objects.ByName("Entity").Root = true @@ -251,13 +257,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 @extends + // } + if (e.allFieldsAreKeyFields()) { + e.ResolverName = "" + } + + f.Entities = append(f.Entities, e) } } } diff --git a/plugin/federation/federation.gotpl b/plugin/federation/federation.gotpl index 2661495a388..96c25e85723 100644 --- a/plugin/federation/federation.gotpl +++ b/plugin/federation/federation.gotpl @@ -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)