more TODOs, and especially clarify the situation for input type names

This commit is contained in:
Ben Kraft
2021-03-22 18:45:37 -07:00
parent 088ec53ad5
commit 17a95fd4de
10 changed files with 115 additions and 22 deletions
+1
View File
@@ -105,6 +105,7 @@ We'll do something similar to Apollo's naming scheme. Specifically:
- The toplevel name will be `MyQueryResponse`, using the query-name.
- Further names will be `MyQueryFieldTypeFieldType`. We will not attempt to be super smart about avoiding conflicts.
- Fragments will have some naming scheme TBD but starting at the fragment.
- Input objects will have a name starting at the type, since they always have the same fields, and often have naming schemes like "MyFieldInput" already.
All of this may be configurable later.
+1 -1
View File
@@ -80,7 +80,7 @@ Config options:
- send hash rather than full query
- whether names should be exported
- default handling for optional fields (pointers, HasFoo, etc.)
- response/function-name format (e.g. force exported/unexported, change "Response" suffix, etc.)
- response/function-name format (e.g. force exported/unexported, change "Response" suffix, change how input objects work, etc.)
- generate mocks?
Other:
+5 -2
View File
@@ -2,6 +2,7 @@ package generate
import (
"fmt"
"go/token"
"io/ioutil"
"path/filepath"
@@ -42,7 +43,10 @@ func (c *Config) ValidateAndFillDefaults() error {
}
base := filepath.Base(abs)
// TODO: remove/replace bad chars, make sure there's something left?
if !token.IsIdentifier(base) {
return fmt.Errorf("unable to guess package-name: %v is not a valid identifier", base)
}
c.Package = base
}
@@ -69,7 +73,6 @@ func ReadAndValidateConfig(filename string) (*Config, error) {
}
// Make paths relative to config dir
// TODO: more principled typing here?
basename := filepath.Dir(filename)
config.Schema = filepath.Join(basename, config.Schema)
config.Queries = filepath.Join(basename, config.Queries)
+4 -6
View File
@@ -73,17 +73,15 @@ func (g *generator) Types() string {
return strings.Join(defs, "\n\n")
}
func (g *generator) getArgument(arg *ast.VariableDefinition) (argument, error) {
func (g *generator) getArgument(opName string, arg *ast.VariableDefinition) (argument, error) {
graphQLName := arg.Variable
firstRest := strings.SplitN(graphQLName, "", 2)
goName := strings.ToLower(firstRest[0]) + firstRest[1]
goType, err := g.getTypeForInputType(arg.Type)
goType, err := g.getTypeForInputType(opName, arg.Type)
if err != nil {
return argument{}, err
}
return argument{
GraphQLName: graphQLName,
GoName: goName,
GoName: lowerFirst(graphQLName),
GoType: goType,
}, nil
}
@@ -121,7 +119,7 @@ func (g *generator) addOperation(op *ast.OperationDefinition) error {
args := make([]argument, len(op.VariableDefinitions))
for i, arg := range op.VariableDefinitions {
var err error
args[i], err = g.getArgument(arg)
args[i], err = g.getArgument(op.Name, arg)
if err != nil {
return err
}
+4 -6
View File
@@ -2,6 +2,7 @@ package generate
import (
"fmt"
"io/ioutil"
"os"
)
@@ -16,15 +17,12 @@ func readConfigGenerateAndWrite(configFilename string) error {
return err
}
// Open out at the end -- decreases the chances we blank it if we err.
out, err := os.OpenFile(config.Generated, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
err = ioutil.WriteFile(config.Generated, code, 0o644)
if err != nil {
return fmt.Errorf("could not open generated file %v: %v",
return fmt.Errorf("could not write generated file %v: %v",
config.Generated, err)
}
_, err = out.Write(code)
return err
return nil
}
func Main() {
+5
View File
@@ -0,0 +1,5 @@
query unexported($query: UserQueryInput) {
user(query: $query) {
id
}
}
+48
View File
@@ -0,0 +1,48 @@
package test
// Code generated by github.com/Khan/genql, DO NOT EDIT.
import (
"context"
"github.com/Khan/genql/graphql"
)
type unexportedResponse struct {
User unexportedUser `json:"user"`
}
type unexportedUser struct {
Id string `json:"id"`
}
type userQueryInput struct {
Email string `json:"email"`
Name string `json:"name"`
Id string `json:"id"`
Role userQueryInputRole `json:"role"`
Names []string `json:"names"`
}
type userQueryInputRole string
const (
userQueryInputRoleStudent userQueryInputRole = "STUDENT"
userQueryInputRoleTeacher userQueryInputRole = "TEACHER"
)
func unexported(client *graphql.Client, query userQueryInput) (*unexportedResponse, error) {
variables := map[string]interface{}{
"query": query,
}
var retval unexportedResponse
err := client.MakeRequest(context.Background(), `
query unexported ($query: UserQueryInput) {
user(query: $query) {
id
}
}
`, &retval, variables)
return &retval, err
}
+10 -7
View File
@@ -101,10 +101,13 @@ func (g *generator) addTypeForDefinition(namePrefix, nameOverride string, typ *a
return name, nil
}
func (g *generator) getTypeForInputType(typ *ast.Type) (string, error) {
typeName := upperFirst(typ.Name())
builder := &typeBuilder{typeName: typeName, typeNamePrefix: typeName, generator: g}
err := builder.writeType("", typ, selectionsForType(g, typ))
func (g *generator) getTypeForInputType(opName string, typ *ast.Type) (string, error) {
// Sort of a hack: case the input type name to match the op-name.
name := matchFirst(typ.Name(), opName)
// TODO: we have to pass name 4 times, yuck
builder := &typeBuilder{typeName: name, typeNamePrefix: name, generator: g}
fmt.Println(name)
err := builder.writeType(name, name, typ, selectionsForType(g, typ))
return builder.String(), err
}
@@ -199,7 +202,7 @@ func (builder *typeBuilder) writeField(field field) error {
// `query q { a: f { b }, c: f { d } }` we need separate types for a
// and c, even though they are the same type in GraphQL, because they
// have different fields.
builder.typeNamePrefix+upperFirst(field.Alias()), typ, fields)
builder.typeNamePrefix+upperFirst(field.Alias()), "", typ, fields)
if err != nil {
return err
}
@@ -214,7 +217,7 @@ func (builder *typeBuilder) writeField(field field) error {
return nil
}
func (builder *typeBuilder) writeType(namePrefix string, typ *ast.Type, fields []field) error {
func (builder *typeBuilder) writeType(namePrefix, nameOverride string, typ *ast.Type, fields []field) error {
// gqlgen does slightly different things here, but its implementation may
// be useful to crib from:
// https://github.com/99designs/gqlgen/blob/master/plugin/modelgen/models.go#L113
@@ -229,7 +232,7 @@ func (builder *typeBuilder) writeType(namePrefix string, typ *ast.Type, fields [
def := builder.schema.Types[typ.Name()]
// Writes a typedef elsewhere (if not already defined)
name, err := builder.addTypeForDefinition(namePrefix, "", def, fields)
name, err := builder.addTypeForDefinition(namePrefix, nameOverride, def, fields)
if err != nil {
return err
}
+15
View File
@@ -29,6 +29,21 @@ func upperFirst(s string) string {
return changeFirst(strings.TrimLeft(s, "_"), unicode.ToUpper)
}
func matchFirst(s, tmpl string) string {
c, n := utf8.DecodeRuneInString(s)
t, _ := utf8.DecodeRuneInString(tmpl)
if c == utf8.RuneError || n == utf8.RuneError { // empty or invalid
return s
}
if unicode.IsUpper(t) {
c = unicode.ToUpper(c)
} else {
c = unicode.ToLower(c)
}
return string(c) + s[n:]
}
func goConstName(s string) string {
if strings.TrimLeft(s, "_") == "" {
return s
+22
View File
@@ -52,6 +52,28 @@ func TestUpperFirst(t *testing.T) {
testStringFunc(t, upperFirst, tests)
}
func TestMatchFirst(t *testing.T) {
tests := []struct {
name, in, out, match string
}{
{"Empty", "", "", ""},
{"LowerToUpper", "lower", "Lower", "Upper"},
{"UpperToUpper", "Upper", "Upper", "Upper"},
{"LowerToLower", "lower", "lower", "lower"},
{"UpperToLower", "Upper", "upper", "lower"},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
got := matchFirst(test.in, test.match)
if got != test.out {
t.Errorf("got %#v want %#v", got, test.out)
}
})
}
}
func TestGoConstName(t *testing.T) {
tests := []test{
{"Empty", "", ""},