github.com/geneva/gqlgen@v0.17.7-0.20230801155730-7b9317164836/plugin/federation/entity.go (about) 1 package federation 2 3 import ( 4 "go/types" 5 6 "github.com/geneva/gqlgen/codegen/config" 7 "github.com/geneva/gqlgen/codegen/templates" 8 "github.com/geneva/gqlgen/plugin/federation/fieldset" 9 "github.com/vektah/gqlparser/v2/ast" 10 ) 11 12 // Entity represents a federated type 13 // that was declared in the GQL schema. 14 type Entity struct { 15 Name string // The same name as the type declaration 16 Def *ast.Definition 17 Resolvers []*EntityResolver 18 Requires []*Requires 19 Multi bool 20 } 21 22 type EntityResolver struct { 23 ResolverName string // The resolver name, such as FindUserByID 24 KeyFields []*KeyField // The fields declared in @key. 25 InputType types.Type // The Go generated input type for multi entity resolvers 26 InputTypeName string 27 } 28 29 func (e *EntityResolver) LookupInputType() string { 30 return templates.CurrentImports.LookupType(e.InputType) 31 } 32 33 type KeyField struct { 34 Definition *ast.FieldDefinition 35 Field fieldset.Field // len > 1 for nested fields 36 Type *config.TypeReference // The Go representation of that field type 37 } 38 39 // Requires represents an @requires clause 40 type Requires struct { 41 Name string // the name of the field 42 Field fieldset.Field // source Field, len > 1 for nested fields 43 Type *config.TypeReference // The Go representation of that field type 44 } 45 46 func (e *Entity) allFieldsAreExternal(federationVersion int) bool { 47 for _, field := range e.Def.Fields { 48 if !e.isFieldImplicitlyExternal(field, federationVersion) && field.Directives.ForName("external") == nil { 49 return false 50 } 51 } 52 return true 53 } 54 55 // In federation v2, key fields are implicitly external. 56 func (e *Entity) isFieldImplicitlyExternal(field *ast.FieldDefinition, federationVersion int) bool { 57 // Key fields are only implicitly external in Federation 2 58 if federationVersion != 2 { 59 return false 60 } 61 // TODO: From the spec, it seems like if an entity is not resolvable then it should not only not have a resolver, but should not appear in the _Entitiy union. 62 // The current implementation is a less drastic departure from the previous behavior, but should probably be reviewed. 63 // See https://www.apollographql.com/docs/federation/subgraph-spec/ 64 if e.isResolvable() { 65 return false 66 } 67 // If the field is a key field, it is implicitly external 68 if e.isKeyField(field) { 69 return true 70 } 71 72 return false 73 } 74 75 // Determine if the entity is resolvable. 76 func (e *Entity) isResolvable() bool { 77 key := e.Def.Directives.ForName("key") 78 if key == nil { 79 // If there is no key directive, the entity is resolvable. 80 return true 81 } 82 resolvable := key.Arguments.ForName("resolvable") 83 if resolvable == nil { 84 // If there is no resolvable argument, the entity is resolvable. 85 return true 86 } 87 // only if resolvable: false has been set on the @key directive do we consider the entity non-resolvable. 88 return resolvable.Value.Raw != "false" 89 } 90 91 // Determine if a field is part of the entities key. 92 func (e *Entity) isKeyField(field *ast.FieldDefinition) bool { 93 for _, keyField := range e.keyFields() { 94 if keyField == field.Name { 95 return true 96 } 97 } 98 return false 99 } 100 101 // Get the key fields for this entity. 102 func (e *Entity) keyFields() []string { 103 key := e.Def.Directives.ForName("key") 104 if key == nil { 105 return []string{} 106 } 107 fields := key.Arguments.ForName("fields") 108 if fields == nil { 109 return []string{} 110 } 111 fieldSet := fieldset.New(fields.Value.Raw, nil) 112 keyFields := make([]string, len(fieldSet)) 113 for i, field := range fieldSet { 114 keyFields[i] = field[0] 115 } 116 return keyFields 117 }