more TODOs, and especially clarify the situation for input type names
This commit is contained in:
@@ -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.
|
- 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.
|
- 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.
|
- 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.
|
All of this may be configurable later.
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Config options:
|
|||||||
- send hash rather than full query
|
- send hash rather than full query
|
||||||
- whether names should be exported
|
- whether names should be exported
|
||||||
- default handling for optional fields (pointers, HasFoo, etc.)
|
- 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?
|
- generate mocks?
|
||||||
|
|
||||||
Other:
|
Other:
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,7 @@ package generate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/token"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -42,7 +43,10 @@ func (c *Config) ValidateAndFillDefaults() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
base := filepath.Base(abs)
|
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
|
c.Package = base
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +73,6 @@ func ReadAndValidateConfig(filename string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make paths relative to config dir
|
// Make paths relative to config dir
|
||||||
// TODO: more principled typing here?
|
|
||||||
basename := filepath.Dir(filename)
|
basename := filepath.Dir(filename)
|
||||||
config.Schema = filepath.Join(basename, config.Schema)
|
config.Schema = filepath.Join(basename, config.Schema)
|
||||||
config.Queries = filepath.Join(basename, config.Queries)
|
config.Queries = filepath.Join(basename, config.Queries)
|
||||||
|
|||||||
@@ -73,17 +73,15 @@ func (g *generator) Types() string {
|
|||||||
return strings.Join(defs, "\n\n")
|
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
|
graphQLName := arg.Variable
|
||||||
firstRest := strings.SplitN(graphQLName, "", 2)
|
goType, err := g.getTypeForInputType(opName, arg.Type)
|
||||||
goName := strings.ToLower(firstRest[0]) + firstRest[1]
|
|
||||||
goType, err := g.getTypeForInputType(arg.Type)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return argument{}, err
|
return argument{}, err
|
||||||
}
|
}
|
||||||
return argument{
|
return argument{
|
||||||
GraphQLName: graphQLName,
|
GraphQLName: graphQLName,
|
||||||
GoName: goName,
|
GoName: lowerFirst(graphQLName),
|
||||||
GoType: goType,
|
GoType: goType,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -121,7 +119,7 @@ func (g *generator) addOperation(op *ast.OperationDefinition) error {
|
|||||||
args := make([]argument, len(op.VariableDefinitions))
|
args := make([]argument, len(op.VariableDefinitions))
|
||||||
for i, arg := range op.VariableDefinitions {
|
for i, arg := range op.VariableDefinitions {
|
||||||
var err error
|
var err error
|
||||||
args[i], err = g.getArgument(arg)
|
args[i], err = g.getArgument(op.Name, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-6
@@ -2,6 +2,7 @@ package generate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,15 +17,12 @@ func readConfigGenerateAndWrite(configFilename string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open out at the end -- decreases the chances we blank it if we err.
|
err = ioutil.WriteFile(config.Generated, code, 0o644)
|
||||||
out, err := os.OpenFile(config.Generated, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
||||||
if err != nil {
|
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)
|
config.Generated, err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
_, err = out.Write(code)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Main() {
|
func Main() {
|
||||||
|
|||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
query unexported($query: UserQueryInput) {
|
||||||
|
user(query: $query) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -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
@@ -101,10 +101,13 @@ func (g *generator) addTypeForDefinition(namePrefix, nameOverride string, typ *a
|
|||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *generator) getTypeForInputType(typ *ast.Type) (string, error) {
|
func (g *generator) getTypeForInputType(opName string, typ *ast.Type) (string, error) {
|
||||||
typeName := upperFirst(typ.Name())
|
// Sort of a hack: case the input type name to match the op-name.
|
||||||
builder := &typeBuilder{typeName: typeName, typeNamePrefix: typeName, generator: g}
|
name := matchFirst(typ.Name(), opName)
|
||||||
err := builder.writeType("", typ, selectionsForType(g, typ))
|
// 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
|
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
|
// `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
|
// and c, even though they are the same type in GraphQL, because they
|
||||||
// have different fields.
|
// have different fields.
|
||||||
builder.typeNamePrefix+upperFirst(field.Alias()), typ, fields)
|
builder.typeNamePrefix+upperFirst(field.Alias()), "", typ, fields)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -214,7 +217,7 @@ func (builder *typeBuilder) writeField(field field) error {
|
|||||||
return nil
|
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
|
// gqlgen does slightly different things here, but its implementation may
|
||||||
// be useful to crib from:
|
// be useful to crib from:
|
||||||
// https://github.com/99designs/gqlgen/blob/master/plugin/modelgen/models.go#L113
|
// 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()]
|
def := builder.schema.Types[typ.Name()]
|
||||||
// Writes a typedef elsewhere (if not already defined)
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,21 @@ func upperFirst(s string) string {
|
|||||||
return changeFirst(strings.TrimLeft(s, "_"), unicode.ToUpper)
|
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 {
|
func goConstName(s string) string {
|
||||||
if strings.TrimLeft(s, "_") == "" {
|
if strings.TrimLeft(s, "_") == "" {
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -52,6 +52,28 @@ func TestUpperFirst(t *testing.T) {
|
|||||||
testStringFunc(t, upperFirst, tests)
|
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) {
|
func TestGoConstName(t *testing.T) {
|
||||||
tests := []test{
|
tests := []test{
|
||||||
{"Empty", "", ""},
|
{"Empty", "", ""},
|
||||||
|
|||||||
Reference in New Issue
Block a user