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 }