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

Consider go type name when autobinding #2812

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 3 additions & 41 deletions codegen/config/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
"fmt"
"go/token"
"go/types"
"strings"

"github.com/vektah/gqlparser/v2/ast"
"golang.org/x/tools/go/packages"

"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/code"
"github.com/vektah/gqlparser/v2/ast"
)

var ErrTypeNotFound = errors.New("unable to find type")
Expand Down Expand Up @@ -285,7 +285,7 @@ func (ref *TypeReference) UniquenessKey() string {
// Fix for #896
elemNullability = "ᚄ"
}
return nullability + ref.Definition.Name + "2" + TypeIdentifier(ref.GO) + elemNullability
return nullability + ref.Definition.Name + "2" + templates.TypeIdentifier(ref.GO) + elemNullability
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved due to cyclic dep, only ref is in templates, so thought it fits there well

}

func (ref *TypeReference) MarshalFunc() string {
Expand Down Expand Up @@ -540,41 +540,3 @@ func basicUnderlying(it types.Type) *types.Basic {

return nil
}

var pkgReplacer = strings.NewReplacer(
"/", "ᚋ",
".", "ᚗ",
"-", "ᚑ",
"~", "א",
)

func TypeIdentifier(t types.Type) string {
res := ""
for {
switch it := t.(type) {
case *types.Pointer:
t.Underlying()
res += "ᚖ"
t = it.Elem()
case *types.Slice:
res += "ᚕ"
t = it.Elem()
case *types.Named:
res += pkgReplacer.Replace(it.Obj().Pkg().Path())
res += "ᚐ"
res += it.Obj().Name()
return res
case *types.Basic:
res += it.Name()
return res
case *types.Map:
res += "map"
return res
case *types.Interface:
res += "interface"
return res
default:
panic(fmt.Errorf("unexpected type %T", it))
}
}
}
23 changes: 20 additions & 3 deletions codegen/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ package config
import (
"bytes"
"fmt"
"go/types"
"io"
"os"
"path/filepath"
"regexp"
"sort"
"strings"

"github.com/99designs/gqlgen/internal/code"
"github.com/vektah/gqlparser/v2"
"github.com/vektah/gqlparser/v2/ast"
"golang.org/x/tools/go/packages"
"gopkg.in/yaml.v3"

"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/code"
)

type Config struct {
Expand Down Expand Up @@ -608,8 +612,10 @@ func (c *Config) autobind() error {
if p == nil || p.Module == nil {
return fmt.Errorf("unable to load %s - make sure you're using an import path to a package that exists", c.AutoBind[i])
}
if t := p.Types.Scope().Lookup(t.Name); t != nil {
c.Models.Add(t.Name(), t.Pkg().Path()+"."+t.Name())

autobindType := c.lookupAutobindType(p, t)
if autobindType != nil {
c.Models.Add(t.Name, autobindType.Pkg().Path()+"."+autobindType.Name())
break
}
}
Expand Down Expand Up @@ -643,6 +649,17 @@ func (c *Config) autobind() error {
return nil
}

func (c *Config) lookupAutobindType(p *packages.Package, schemaType *ast.Definition) types.Object {
// Try binding to either the original schema type name, or the normalized go type name
for _, lookupName := range []string{schemaType.Name, templates.ToGo(schemaType.Name)} {
if t := p.Types.Scope().Lookup(lookupName); t != nil {
return t
}
}

return nil
}

func (c *Config) injectBuiltins() {
builtins := TypeMap{
"__Directive": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Directive"}},
Expand Down
25 changes: 25 additions & 0 deletions codegen/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,31 @@ func TestAutobinding(t *testing.T) {
require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.Message", cfg.Models["Message"].Model[0])
})

t.Run("normalized type names", func(t *testing.T) {
cfg := Config{
Models: TypeMap{},
AutoBind: []string{
"github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat",
"github.com/99designs/gqlgen/codegen/config/testdata/autobinding/scalars/model",
},
Packages: code.NewPackages(),
}

cfg.Schema = gqlparser.MustLoadSchema(&ast.Source{Name: "TestAutobinding.schema", Input: `
scalar Banned
type Message { id: ID }
enum ProductSKU { ProductSkuTrial }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one here is an example that wouldn't had worked sans the change; SKU is not a default initialism, let's imagine the user didn't add it to their config - gqlgen will generate the type as ProductSku.

Now imagine this type goes to a shared library which is autobound in another place - ProductSku will be regenerated, as the schema defines it as ProductSKU, and no go type by this name is found to auto-bind to.

type ChatAPI { id: ID }
`})

require.NoError(t, cfg.autobind())

require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/scalars/model.Banned", cfg.Models["Banned"].Model[0])
require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.Message", cfg.Models["Message"].Model[0])
require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.ProductSku", cfg.Models["ProductSKU"].Model[0])
require.Equal(t, "github.com/99designs/gqlgen/codegen/config/testdata/autobinding/chat.ChatAPI", cfg.Models["ChatAPI"].Model[0])
})

t.Run("with file path", func(t *testing.T) {
cfg := Config{
Models: TypeMap{},
Expand Down
66 changes: 7 additions & 59 deletions codegen/config/initialisms.go
Original file line number Diff line number Diff line change
@@ -1,62 +1,10 @@
package config

import "strings"
import (
"strings"

// commonInitialisms is a set of common initialisms.
// Only add entries that are highly unlikely to be non-initialisms.
// For instance, "ID" is fine (Freudian code is rare), but "AND" is not.
var commonInitialisms = map[string]bool{
"ACL": true,
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"CSV": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTP": true,
"HTTPS": true,
"ICMP": true,
"ID": true,
"IP": true,
"JSON": true,
"KVK": true,
"LHS": true,
"PDF": true,
"PGP": true,
"QPS": true,
"QR": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"SVG": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UI": true,
"UID": true,
"URI": true,
"URL": true,
"UTF8": true,
"UUID": true,
"VM": true,
"XML": true,
"XMPP": true,
"XSRF": true,
"XSS": true,
}

// GetInitialisms returns the initialisms to capitalize in Go names. If unchanged, default initialisms will be returned
var GetInitialisms = func() map[string]bool {
return commonInitialisms
}
"github.com/99designs/gqlgen/codegen/templates"
)

// GoInitialismsConfig allows to modify the default behavior of naming Go methods, types and properties
type GoInitialismsConfig struct {
Expand All @@ -69,7 +17,7 @@ type GoInitialismsConfig struct {
// setInitialisms adjustes GetInitialisms based on its settings.
func (i GoInitialismsConfig) setInitialisms() {
toUse := i.determineGoInitialisms()
GetInitialisms = func() map[string]bool {
templates.GetInitialisms = func() map[string]bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved due to cyclic dep, only ref is in templates, so thought it fits there well

return toUse
}
}
Expand All @@ -82,8 +30,8 @@ func (i GoInitialismsConfig) determineGoInitialisms() (initialismsToUse map[stri
initialismsToUse[strings.ToUpper(initialism)] = true
}
} else {
initialismsToUse = make(map[string]bool, len(commonInitialisms)+len(i.Initialisms))
for initialism, value := range commonInitialisms {
initialismsToUse = make(map[string]bool, len(templates.CommonInitialisms)+len(i.Initialisms))
for initialism, value := range templates.CommonInitialisms {
initialismsToUse[strings.ToUpper(initialism)] = value
}
for _, initialism := range i.Initialisms {
Expand Down
6 changes: 4 additions & 2 deletions codegen/config/initialisms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/99designs/gqlgen/codegen/templates"
)

func TestGoInitialismsConfig(t *testing.T) {
Expand All @@ -17,12 +19,12 @@ func TestGoInitialismsConfig(t *testing.T) {
t.Run("empty initialism config doesn't change anything", func(t *testing.T) {
tt := GoInitialismsConfig{}
result := tt.determineGoInitialisms()
assert.Equal(t, len(commonInitialisms), len(result))
assert.Equal(t, len(templates.CommonInitialisms), len(result))
})
t.Run("initialism config appends if desired", func(t *testing.T) {
tt := GoInitialismsConfig{ReplaceDefaults: false, Initialisms: []string{"ASDF"}}
result := tt.determineGoInitialisms()
assert.Equal(t, len(commonInitialisms)+1, len(result))
assert.Equal(t, len(templates.CommonInitialisms)+1, len(result))
assert.True(t, result["ASDF"])
})
t.Run("initialism config replaces if desired", func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ type Message struct {
CreatedBy string `json:"createdBy"`
CreatedAt time.Time `json:"createdAt"`
}

type ProductSku string

const (
ProductSkuTrial ProductSku = "Trial"
)

type ChatAPI struct {
ID string `json:"id"`
}