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  }