big refactor to put the codegen onto methods of an object

This commit is contained in:
Ben Kraft
2020-04-10 15:21:23 -07:00
parent dfea9bf128
commit b600df7877
5 changed files with 78 additions and 59 deletions
+6 -6
View File
@@ -14,6 +14,12 @@ type getViewerResponse struct {
} `json:"viewer"` } `json:"viewer"`
} }
type getUserResponse struct {
User *struct {
TheirName *string `json:"theirName"`
} `json:"user"`
}
func getViewer(ctx context.Context, client *graphql.Client) (*getViewerResponse, error) { func getViewer(ctx context.Context, client *graphql.Client) (*getViewerResponse, error) {
var retval getViewerResponse var retval getViewerResponse
err := client.MakeRequest(ctx, ` err := client.MakeRequest(ctx, `
@@ -26,12 +32,6 @@ query getViewer {
return &retval, err return &retval, err
} }
type getUserResponse struct {
User *struct {
TheirName *string `json:"theirName"`
} `json:"user"`
}
// getUser gets the given user's name from their username. // getUser gets the given user's name from their username.
func getUser(ctx context.Context, client *graphql.Client, login string) (*getUserResponse, error) { func getUser(ctx context.Context, client *graphql.Client, login string) (*getUserResponse, error) {
variables := map[string]interface{}{ variables := map[string]interface{}{
+41 -37
View File
@@ -20,11 +20,16 @@ var tmplAbsFilename = filepath.Join(filepath.Dir(thisFilename), tmplRelFilename)
var tmpl = template.Must(template.ParseFiles(tmplAbsFilename)) var tmpl = template.Must(template.ParseFiles(tmplAbsFilename))
type templateParams struct { // generator is the context for the codegen process (and ends up getting passed
// to the template).
type generator struct {
// The name of the package into which to generate the operation-helpers. // The name of the package into which to generate the operation-helpers.
PackageName string PackageName string
// The list of operations for which to generate code. // The list of operations for which to generate code.
Operations []operation Operations []operation
// The types needed for these operations.
typeMap map[string]string
schema *ast.Schema
} }
type operation struct { type operation struct {
@@ -38,11 +43,8 @@ type operation struct {
Body string Body string
// The arguments to the operation. // The arguments to the operation.
Args []argument Args []argument
// The type-name for the operation's response type. // The type-name for the operation's response type.
ResponseName string ResponseName string
// The body of the operation's response type (e.g. struct { ... }).
ResponseType string
} }
type argument struct { type argument struct {
@@ -51,11 +53,27 @@ type argument struct {
GraphQLName string GraphQLName string
} }
func fromASTArg(arg *ast.VariableDefinition, schema *ast.Schema) (argument, error) { func newGenerator(packageName string, schema *ast.Schema) *generator {
return &generator{
PackageName: packageName,
typeMap: map[string]string{},
schema: schema,
}
}
func (g *generator) Types() string {
defs := make([]string, 0, len(g.typeMap))
for _, def := range g.typeMap {
defs = append(defs, def)
}
return strings.Join(defs, "\n\n")
}
func (g *generator) getArgument(arg *ast.VariableDefinition) (argument, error) {
graphQLName := arg.Variable graphQLName := arg.Variable
firstRest := strings.SplitN(graphQLName, "", 2) firstRest := strings.SplitN(graphQLName, "", 2)
goName := strings.ToLower(firstRest[0]) + firstRest[1] goName := strings.ToLower(firstRest[0]) + firstRest[1]
goType, err := typeForInputType(arg.Type, schema) goType, err := g.addTypeForInputType(arg.Type)
if err != nil { if err != nil {
return argument{}, err return argument{}, err
} }
@@ -66,13 +84,7 @@ func fromASTArg(arg *ast.VariableDefinition, schema *ast.Schema) (argument, erro
}, nil }, nil
} }
func reverse(slice []string) { func (g *generator) getDocComment(op *ast.OperationDefinition) string {
for left, right := 0, len(slice)-1; left < right; left, right = left+1, right-1 {
slice[left], slice[right] = slice[right], slice[left]
}
}
func getDocComment(op *ast.OperationDefinition) string {
var commentLines []string var commentLines []string
var sourceLines = strings.Split(op.Position.Src.Input, "\n") var sourceLines = strings.Split(op.Position.Src.Input, "\n")
for i := op.Position.Line - 1; i > 0; i-- { for i := op.Position.Line - 1; i > 0; i-- {
@@ -90,7 +102,7 @@ func getDocComment(op *ast.OperationDefinition) string {
return strings.Join(commentLines, "\n") return strings.Join(commentLines, "\n")
} }
func fromASTOperation(op *ast.OperationDefinition, schema *ast.Schema) (operation, error) { func (g *generator) addOperation(op *ast.OperationDefinition) error {
// TODO: we may have to actually get the precise query text, in case we // TODO: we may have to actually get the precise query text, in case we
// want to be hashing it or something like that. This is a bit tricky // want to be hashing it or something like that. This is a bit tricky
// because gqlparser's ast doesn't provide node end-position (only // because gqlparser's ast doesn't provide node end-position (only
@@ -105,30 +117,28 @@ func fromASTOperation(op *ast.OperationDefinition, schema *ast.Schema) (operatio
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 = fromASTArg(arg, schema) args[i], err = g.getArgument(arg)
if err != nil { if err != nil {
return operation{}, err return err
} }
} }
// TODO: configure ResponseName format responseName, err := g.addTypeForOperation(op)
responseName := op.Name + "Response"
typ, err := typeForOperation(responseName, op, schema)
if err != nil { if err != nil {
return operation{}, fmt.Errorf("could not compute return-type for query: %v", err) return err
} }
return operation{ g.Operations = append(g.Operations, operation{
Type: op.Operation, Type: op.Operation,
Name: op.Name, Name: op.Name,
Doc: getDocComment(op), Doc: g.getDocComment(op),
// The newline just makes it format a little nicer // The newline just makes it format a little nicer
Body: "\n" + builder.String(), Body: "\n" + builder.String(),
Args: args, Args: args,
ResponseName: responseName, ResponseName: responseName,
ResponseType: typ, })
}, nil
return nil
} }
func Generate(config *Config) ([]byte, error) { func Generate(config *Config) ([]byte, error) {
@@ -142,21 +152,15 @@ func Generate(config *Config) ([]byte, error) {
return nil, err return nil, err
} }
operations := make([]operation, len(document.Operations)) g := newGenerator(config.Package, schema)
for i, op := range document.Operations { for _, op := range document.Operations {
operations[i], err = fromASTOperation(op, schema) if err = g.addOperation(op); err != nil {
if err != nil {
return nil, err return nil, err
} }
} }
data := templateParams{
PackageName: config.Package,
Operations: operations,
}
var buf bytes.Buffer var buf bytes.Buffer
err = tmpl.Execute(&buf, data) err = tmpl.Execute(&buf, g)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not render template: %v", err) return nil, fmt.Errorf("could not render template: %v", err)
} }
+2 -2
View File
@@ -8,9 +8,9 @@ import (
"github.com/Khan/genql/graphql" "github.com/Khan/genql/graphql"
) )
{{range .Operations}} {{.Types}}
{{.ResponseType}}
{{range .Operations}}
{{.Doc}} {{.Doc}}
func {{.Name}}(ctx context.Context, client *graphql.Client{{range .Args}}, {{.GoName}} {{.GoType}}{{end}}) (*{{.ResponseName}}, error) { func {{.Name}}(ctx context.Context, client *graphql.Client{{range .Args}}, {{.GoName}} {{.GoType}}{{end}}) (*{{.ResponseName}}, error) {
{{- if .Args -}} {{- if .Args -}}
+26 -12
View File
@@ -9,32 +9,46 @@ import (
type typeBuilder struct { type typeBuilder struct {
strings.Builder strings.Builder
schema *ast.Schema *generator
} }
func (builder *typeBuilder) baseTypeForOperation(operation ast.Operation) *ast.Definition { func (g *generator) baseTypeForOperation(operation ast.Operation) *ast.Definition {
switch operation { switch operation {
case ast.Query: case ast.Query:
return builder.schema.Query return g.schema.Query
case ast.Mutation: case ast.Mutation:
return builder.schema.Mutation return g.schema.Mutation
case ast.Subscription: case ast.Subscription:
return builder.schema.Subscription return g.schema.Subscription
default: default:
panic(fmt.Sprintf("unexpected operation: %v", operation)) panic(fmt.Sprintf("unexpected operation: %v", operation))
} }
} }
func typeForOperation(name string, operation *ast.OperationDefinition, schema *ast.Schema) (string, error) { func (g *generator) addTypeForOperation(operation *ast.OperationDefinition) (name string, err error) {
builder := &typeBuilder{schema: schema} // TODO: configure ResponseName format
name = operation.Name + "Response"
if def, ok := g.typeMap[name]; ok {
// TODO: if the name is taken, maybe try to find another?
return "", fmt.Errorf("%s already defined:\n%s", name, def)
}
builder := &typeBuilder{generator: g}
fmt.Fprintf(builder, "type %s ", name) fmt.Fprintf(builder, "type %s ", name)
err := builder.writeTypedef( err = builder.writeTypedef(
builder.baseTypeForOperation(operation.Operation), operation.SelectionSet) g.baseTypeForOperation(operation.Operation), operation.SelectionSet)
return builder.String(), err if err != nil {
return "", err
}
def := builder.String()
g.typeMap[name] = def
return name, nil
} }
func typeForInputType(typ *ast.Type, schema *ast.Schema) (string, error) { func (g *generator) addTypeForInputType(typ *ast.Type) (string, error) {
builder := &typeBuilder{schema: schema} builder := &typeBuilder{generator: g}
// TODO: handle non-scalar types (by passing ...something... as the // TODO: handle non-scalar types (by passing ...something... as the
// SelectionSet?) // SelectionSet?)
+3 -2
View File
@@ -118,13 +118,14 @@ func TestTypeForOperation(t *testing.T) {
t.Fatalf("got %v operations, want 1", len(queryDoc.Operations)) t.Fatalf("got %v operations, want 1", len(queryDoc.Operations))
} }
goType, err := typeForOperation("Response", queryDoc.Operations[0], schema) g := newGenerator("test_package", schema)
name, err := g.addTypeForOperation(queryDoc.Operations[0])
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
// gofmt before comparing. // gofmt before comparing.
goType, err = gofmt(goType) goType, err := gofmt(g.typeMap[name])
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }