big refactor to put the codegen onto methods of an object
This commit is contained in:
@@ -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
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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?)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user