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