Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Refactor federation requires to populate on entity via a function #2676

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions _examples/federation/reviews/graph/federation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions _examples/federation/reviews/graph/federation.requires.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package graph

import (
"context"
"fmt"

"github.com/99designs/gqlgen/_examples/federation/reviews/graph/model"
)

// PopulateUserRequires is the requires populator for the User entity.
func (ec *executionContext) PopulateUserRequires(ctx context.Context, entity *model.User, reps map[string]interface{}) error {
panic(fmt.Errorf("not implemented: PopulateUserRequires"))
}
8 changes: 8 additions & 0 deletions plugin/federation/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package federation

import (
"go/types"
"strings"

"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
Expand All @@ -17,6 +18,7 @@ type Entity struct {
Resolvers []*EntityResolver
Requires []*Requires
Multi bool
Type types.Type
}

type EntityResolver struct {
Expand Down Expand Up @@ -115,3 +117,9 @@ func (e *Entity) keyFields() []string {
}
return keyFields
}

// GetTypeInfo - get the imported package & type name combo. package.TypeName
func (e Entity) GetTypeInfo() string {
typeParts := strings.Split(e.Type.String(), "/")
return typeParts[len(typeParts)-1]
}
96 changes: 96 additions & 0 deletions plugin/federation/federation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package federation
import (
_ "embed"
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
"strings"

Expand All @@ -11,6 +14,7 @@ import (
"github.com/99designs/gqlgen/codegen"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/rewrite"
"github.com/99designs/gqlgen/plugin"
"github.com/99designs/gqlgen/plugin/federation/fieldset"
)
Expand Down Expand Up @@ -233,6 +237,13 @@ type Entity {
}

func (f *federation) GenerateCode(data *codegen.Data) error {
// requires imports
requiresImports := make(map[string]bool, 0)
requiresImports["context"] = true
requiresImports["fmt"] = true

requiresEntities := make(map[string]*Entity, 0)

if len(f.Entities) > 0 {
if data.Objects.ByName("Entity") != nil {
data.Objects.ByName("Entity").Root = true
Expand Down Expand Up @@ -272,9 +283,19 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
fmt.Println("skipping @requires field " + reqField.Name + " in " + e.Def.Name)
continue
}
// keep track of which entities have requires
requiresEntities[e.Def.Name] = e
// make a proper import path
typeString := strings.Split(obj.Type.String(), ".")
requiresImports[strings.Join(typeString[:len(typeString)-1], ".")] = true

cgField := reqField.Field.TypeReference(obj, data.Objects)
reqField.Type = cgField.TypeReference
}

// add type info to entity
e.Type = obj.Type

}
}

Expand All @@ -295,6 +316,81 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
}
}

if len(requiresEntities) > 0 {
// check for existing requires functions
type Populator struct {
FuncName string
Exists bool
Comment string
Implementation string
Entity *Entity
}
populators := make([]Populator, 0)

rewriter, err := rewrite.New(data.Config.Federation.Dir())
if err != nil {
return err
}

for name, entity := range requiresEntities {
populator := Populator{
FuncName: fmt.Sprintf("Populate%sRequires", name),
Entity: entity,
}

populator.Comment = strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment("executionContext", populator.FuncName), `\`))
populator.Implementation = strings.TrimSpace(rewriter.GetMethodBody("executionContext", populator.FuncName))

if populator.Implementation == "" {
populator.Exists = false
populator.Implementation = fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v\"))", populator.FuncName)
}
populators = append(populators, populator)
}

// find and read requires template
_, callerFile, _, _ := runtime.Caller(0)
currentDir := filepath.Dir(callerFile)
requiresTemplate, err := os.ReadFile(currentDir + "/requires.gotpl")

if err != nil {
return err
}

requiresFile := data.Config.Federation.Dir() + "/federation.requires.go"
existingImports := rewriter.ExistingImports(requiresFile)
for _, imp := range existingImports {
if imp.Alias == "" {
if _, ok := requiresImports[imp.ImportPath]; ok {
// import exists in both places, remove
delete(requiresImports, imp.ImportPath)
}
Comment on lines +364 to +367
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if _, ok := requiresImports[imp.ImportPath]; ok {
// import exists in both places, remove
delete(requiresImports, imp.ImportPath)
}
delete(requiresImports, imp.ImportPath)

You don't need the conditional check, as delete will no-op if the key does not exist

}
}

for k := range requiresImports {
existingImports = append(existingImports, rewrite.Import{ImportPath: k})
}

// render requires populators
err = templates.Render(templates.Options{
PackageName: data.Config.Federation.Package,
Filename: requiresFile,
Data: struct {
federation
ExistingImports []rewrite.Import
Populators []Populator
OriginalSource string
}{*f, existingImports, populators, ""},
GeneratedHeader: false,
Packages: data.Config.Packages,
Template: string(requiresTemplate),
})
if err != nil {
return err
}
}

return templates.Render(templates.Options{
PackageName: data.Config.Federation.Package,
Filename: data.Config.Federation.Filename,
Expand Down
6 changes: 3 additions & 3 deletions plugin/federation/federation.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
if err != nil {
return fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err)
}
{{ range $entity.Requires }}
entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
{{ if $entity.Requires }}
err = ec.Populate{{$entity.Def.Name}}Requires(ctx, entity, rep)
StevenACoffman marked this conversation as resolved.
Show resolved Hide resolved
StevenACoffman marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
return fmt.Errorf(`populating requires for Entity "{{$entity.Def.Name}}": %w`, err)
}
{{- end }}
list[idx[i]] = entity
Expand Down
20 changes: 20 additions & 0 deletions plugin/federation/requires.gotpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{ range .ExistingImports }}
{{ if ne .Alias "" }}
{{ reserveImport .ImportPath .Alias }}
{{ else }}
{{ reserveImport .ImportPath }}
{{ end }}
{{ end }}

{{ range .Populators -}}
{{ if .Comment -}}
// {{.Comment}}
{{- else -}}
// {{.FuncName}} is the requires populator for the {{.Entity.Def.Name}} entity.
{{- end }}
func (ec *executionContext) {{.FuncName}}(ctx context.Context, entity *{{.Entity.GetTypeInfo}}, reps map[string]interface{}) error {
{{.Implementation}}
}
{{ end }}

{{ .OriginalSource }}
16 changes: 6 additions & 10 deletions plugin/federation/testdata/entityresolver/generated/federation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package generated

import (
"context"
"fmt"

"github.com/99designs/gqlgen/plugin/federation/testdata/entityresolver/generated/model"
)

// PopulateMultiHelloMultipleRequiresRequires is the requires populator for the MultiHelloMultipleRequires entity.
func (ec *executionContext) PopulateMultiHelloMultipleRequiresRequires(ctx context.Context, entity *model.MultiHelloMultipleRequires, reps map[string]interface{}) error {
panic(fmt.Errorf("not implemented: PopulateMultiHelloMultipleRequiresRequires"))
}

// PopulateMultiHelloRequiresRequires is the requires populator for the MultiHelloRequires entity.
func (ec *executionContext) PopulateMultiHelloRequiresRequires(ctx context.Context, entity *model.MultiHelloRequires, reps map[string]interface{}) error {
StevenACoffman marked this conversation as resolved.
Show resolved Hide resolved
panic(fmt.Errorf("not implemented: PopulateMultiHelloRequiresRequires"))
}

// PopulateMultiPlanetRequiresNestedRequires is the requires populator for the MultiPlanetRequiresNested entity.
func (ec *executionContext) PopulateMultiPlanetRequiresNestedRequires(ctx context.Context, entity *model.MultiPlanetRequiresNested, reps map[string]interface{}) error {
StevenACoffman marked this conversation as resolved.
Show resolved Hide resolved
panic(fmt.Errorf("not implemented: PopulateMultiPlanetRequiresNestedRequires"))
}

// PopulatePlanetMultipleRequiresRequires is the requires populator for the PlanetMultipleRequires entity.
func (ec *executionContext) PopulatePlanetMultipleRequiresRequires(ctx context.Context, entity *model.PlanetMultipleRequires, reps map[string]interface{}) error {
panic(fmt.Errorf("not implemented: PopulatePlanetMultipleRequiresRequires"))
}

// PopulatePlanetRequiresRequires is the requires populator for the PlanetRequires entity.
func (ec *executionContext) PopulatePlanetRequiresRequires(ctx context.Context, entity *model.PlanetRequires, reps map[string]interface{}) error {
panic(fmt.Errorf("not implemented: PopulatePlanetRequiresRequires"))
}

// PopulatePlanetRequiresNestedRequires is the requires populator for the PlanetRequiresNested entity.
func (ec *executionContext) PopulatePlanetRequiresNestedRequires(ctx context.Context, entity *model.PlanetRequiresNested, reps map[string]interface{}) error {
panic(fmt.Errorf("not implemented: PopulatePlanetRequiresNestedRequires"))
}