github.com/maeglindeveloper/gqlgen@v0.13.1-0.20210413081235-57808b12a0a0/plugin/federation/federation.go (about)

     1  package federation
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/vektah/gqlparser/v2/ast"
     9  
    10  	"github.com/99designs/gqlgen/codegen"
    11  	"github.com/99designs/gqlgen/codegen/config"
    12  	"github.com/99designs/gqlgen/codegen/templates"
    13  	"github.com/99designs/gqlgen/plugin"
    14  )
    15  
    16  type federation struct {
    17  	Entities []*Entity
    18  }
    19  
    20  // New returns a federation plugin that injects
    21  // federated directives and types into the schema
    22  func New() plugin.Plugin {
    23  	return &federation{}
    24  }
    25  
    26  // Name returns the plugin name
    27  func (f *federation) Name() string {
    28  	return "federation"
    29  }
    30  
    31  // MutateConfig mutates the configuration
    32  func (f *federation) MutateConfig(cfg *config.Config) error {
    33  	builtins := config.TypeMap{
    34  		"_Service": {
    35  			Model: config.StringList{
    36  				"github.com/99designs/gqlgen/plugin/federation/fedruntime.Service",
    37  			},
    38  		},
    39  		"_Entity": {
    40  			Model: config.StringList{
    41  				"github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity",
    42  			},
    43  		},
    44  		"Entity": {
    45  			Model: config.StringList{
    46  				"github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity",
    47  			},
    48  		},
    49  		"_Any": {
    50  			Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"},
    51  		},
    52  	}
    53  	for typeName, entry := range builtins {
    54  		if cfg.Models.Exists(typeName) {
    55  			return fmt.Errorf("%v already exists which must be reserved when Federation is enabled", typeName)
    56  		}
    57  		cfg.Models[typeName] = entry
    58  	}
    59  	cfg.Directives["external"] = config.DirectiveConfig{SkipRuntime: true}
    60  	cfg.Directives["requires"] = config.DirectiveConfig{SkipRuntime: true}
    61  	cfg.Directives["provides"] = config.DirectiveConfig{SkipRuntime: true}
    62  	cfg.Directives["key"] = config.DirectiveConfig{SkipRuntime: true}
    63  	cfg.Directives["extends"] = config.DirectiveConfig{SkipRuntime: true}
    64  
    65  	return nil
    66  }
    67  
    68  func (f *federation) InjectSourceEarly() *ast.Source {
    69  	return &ast.Source{
    70  		Name: "federation/directives.graphql",
    71  		Input: `
    72  scalar _Any
    73  scalar _FieldSet
    74  
    75  directive @external on FIELD_DEFINITION
    76  directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
    77  directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
    78  directive @key(fields: _FieldSet!) on OBJECT | INTERFACE
    79  directive @extends on OBJECT
    80  `,
    81  		BuiltIn: true,
    82  	}
    83  }
    84  
    85  // InjectSources creates a GraphQL Entity type with all
    86  // the fields that had the @key directive
    87  func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source {
    88  	f.setEntities(schema)
    89  
    90  	entities := ""
    91  	resolvers := ""
    92  	for i, e := range f.Entities {
    93  		if i != 0 {
    94  			entities += " | "
    95  		}
    96  		entities += e.Name
    97  
    98  		if e.ResolverName != "" {
    99  			resolverArgs := ""
   100  			for _, field := range e.KeyFields {
   101  				resolverArgs += fmt.Sprintf("%s: %s,", field.Field.Name, field.Field.Type.String())
   102  			}
   103  			resolvers += fmt.Sprintf("\t%s(%s): %s!\n", e.ResolverName, resolverArgs, e.Def.Name)
   104  		}
   105  
   106  	}
   107  
   108  	if len(f.Entities) == 0 {
   109  		// It's unusual for a service not to have any entities, but
   110  		// possible if it only exports top-level queries and mutations.
   111  		return nil
   112  	}
   113  
   114  	// resolvers can be empty if a service defines only "empty
   115  	// extend" types.  This should be rare.
   116  	if resolvers != "" {
   117  		resolvers = `
   118  # fake type to build resolver interfaces for users to implement
   119  type Entity {
   120  	` + resolvers + `
   121  }
   122  `
   123  	}
   124  
   125  	return &ast.Source{
   126  		Name:    "federation/entity.graphql",
   127  		BuiltIn: true,
   128  		Input: `
   129  # a union of all types that use the @key directive
   130  union _Entity = ` + entities + `
   131  ` + resolvers + `
   132  type _Service {
   133    sdl: String
   134  }
   135  
   136  extend type Query {
   137    _entities(representations: [_Any!]!): [_Entity]!
   138    _service: _Service!
   139  }
   140  `,
   141  	}
   142  }
   143  
   144  // Entity represents a federated type
   145  // that was declared in the GQL schema.
   146  type Entity struct {
   147  	Name         string      // The same name as the type declaration
   148  	KeyFields    []*KeyField // The fields declared in @key.
   149  	ResolverName string      // The resolver name, such as FindUserByID
   150  	Def          *ast.Definition
   151  	Requires     []*Requires
   152  }
   153  
   154  type KeyField struct {
   155  	Field         *ast.FieldDefinition
   156  	TypeReference *config.TypeReference // The Go representation of that field type
   157  }
   158  
   159  // Requires represents an @requires clause
   160  type Requires struct {
   161  	Name   string          // the name of the field
   162  	Fields []*RequireField // the name of the sibling fields
   163  }
   164  
   165  // RequireField is similar to an entity but it is a field not
   166  // an object
   167  type RequireField struct {
   168  	Name          string                // The same name as the type declaration
   169  	NameGo        string                // The Go struct field name
   170  	TypeReference *config.TypeReference // The Go representation of that field type
   171  }
   172  
   173  func (e *Entity) allFieldsAreExternal() bool {
   174  	for _, field := range e.Def.Fields {
   175  		if field.Directives.ForName("external") == nil {
   176  			return false
   177  		}
   178  	}
   179  	return true
   180  }
   181  
   182  func (f *federation) GenerateCode(data *codegen.Data) error {
   183  	if len(f.Entities) > 0 {
   184  		if data.Objects.ByName("Entity") != nil {
   185  			data.Objects.ByName("Entity").Root = true
   186  		}
   187  		for _, e := range f.Entities {
   188  			obj := data.Objects.ByName(e.Def.Name)
   189  			for _, field := range obj.Fields {
   190  				// Storing key fields in a slice rather than a map
   191  				// to preserve insertion order at the tradeoff of higher
   192  				// lookup complexity.
   193  				keyField := f.getKeyField(e.KeyFields, field.Name)
   194  				if keyField != nil {
   195  					keyField.TypeReference = field.TypeReference
   196  				}
   197  				for _, r := range e.Requires {
   198  					for _, rf := range r.Fields {
   199  						if rf.Name == field.Name {
   200  							rf.TypeReference = field.TypeReference
   201  							rf.NameGo = field.GoFieldName
   202  						}
   203  					}
   204  				}
   205  			}
   206  		}
   207  	}
   208  
   209  	return templates.Render(templates.Options{
   210  		PackageName:     data.Config.Federation.Package,
   211  		Filename:        data.Config.Federation.Filename,
   212  		Data:            f,
   213  		GeneratedHeader: true,
   214  		Packages:        data.Config.Packages,
   215  	})
   216  }
   217  
   218  func (f *federation) getKeyField(keyFields []*KeyField, fieldName string) *KeyField {
   219  	for _, field := range keyFields {
   220  		if field.Field.Name == fieldName {
   221  			return field
   222  		}
   223  	}
   224  	return nil
   225  }
   226  
   227  func (f *federation) setEntities(schema *ast.Schema) {
   228  	for _, schemaType := range schema.Types {
   229  		if schemaType.Kind == ast.Object {
   230  			dir := schemaType.Directives.ForName("key") // TODO: interfaces
   231  			if dir != nil {
   232  				if len(dir.Arguments) > 1 {
   233  					panic("Multiple arguments are not currently supported in @key declaration.")
   234  				}
   235  				fieldName := dir.Arguments[0].Value.Raw // TODO: multiple arguments
   236  				if strings.Contains(fieldName, "{") {
   237  					panic("Nested fields are not currently supported in @key declaration.")
   238  				}
   239  
   240  				requires := []*Requires{}
   241  				for _, f := range schemaType.Fields {
   242  					dir := f.Directives.ForName("requires")
   243  					if dir == nil {
   244  						continue
   245  					}
   246  					fields := strings.Split(dir.Arguments[0].Value.Raw, " ")
   247  					requireFields := []*RequireField{}
   248  					for _, f := range fields {
   249  						requireFields = append(requireFields, &RequireField{
   250  							Name: f,
   251  						})
   252  					}
   253  					requires = append(requires, &Requires{
   254  						Name:   f.Name,
   255  						Fields: requireFields,
   256  					})
   257  				}
   258  
   259  				fieldNames := strings.Split(fieldName, " ")
   260  				keyFields := make([]*KeyField, len(fieldNames))
   261  				resolverName := fmt.Sprintf("find%sBy", schemaType.Name)
   262  				for i, f := range fieldNames {
   263  					field := schemaType.Fields.ForName(f)
   264  
   265  					keyFields[i] = &KeyField{Field: field}
   266  					if i > 0 {
   267  						resolverName += "And"
   268  					}
   269  					resolverName += templates.ToGo(f)
   270  
   271  				}
   272  
   273  				e := &Entity{
   274  					Name:         schemaType.Name,
   275  					KeyFields:    keyFields,
   276  					Def:          schemaType,
   277  					ResolverName: resolverName,
   278  					Requires:     requires,
   279  				}
   280  				// If our schema has a field with a type defined in
   281  				// another service, then we need to define an "empty
   282  				// extend" of that type in this service, so this service
   283  				// knows what the type is like.  But the graphql-server
   284  				// will never ask us to actually resolve this "empty
   285  				// extend", so we don't require a resolver function for
   286  				// it.  (Well, it will never ask in practice; it's
   287  				// unclear whether the spec guarantees this.  See
   288  				// https://github.com/apollographql/apollo-server/issues/3852
   289  				// ).  Example:
   290  				//    type MyType {
   291  				//       myvar: TypeDefinedInOtherService
   292  				//    }
   293  				//    // Federation needs this type, but
   294  				//    // it doesn't need a resolver for it!
   295  				//    extend TypeDefinedInOtherService @key(fields: "id") {
   296  				//       id: ID @external
   297  				//    }
   298  				if e.allFieldsAreExternal() {
   299  					e.ResolverName = ""
   300  				}
   301  
   302  				f.Entities = append(f.Entities, e)
   303  			}
   304  		}
   305  	}
   306  
   307  	// make sure order remains stable across multiple builds
   308  	sort.Slice(f.Entities, func(i, j int) bool {
   309  		return f.Entities[i].Name < f.Entities[j].Name
   310  	})
   311  }