From 9746c818fd8ba2eaa81f0a60a1d76b5f2462aee3 Mon Sep 17 00:00:00 2001 From: Ben Kraft Date: Thu, 2 Jan 2020 17:52:55 -0800 Subject: [PATCH] genql: quick hack at variables --- example/caller.go | 16 +++++++++-- example/generated.go | 28 ++++++++++++++++++- example/queries.graphql | 7 +++++ generate/generate.go | 57 +++++++++++++++++++++++++------------- generate/operation.go.tmpl | 13 +++++++-- generate/types.go | 12 +++++++- graphql/client.go | 8 +++--- 7 files changed, 111 insertions(+), 30 deletions(-) diff --git a/example/caller.go b/example/caller.go index 52bcd6e..693cc07 100644 --- a/example/caller.go +++ b/example/caller.go @@ -34,6 +34,12 @@ func Main() { return } + if len(os.Args) != 2 { + err = fmt.Errorf("usage: %v ", os.Args[0]) + return + } + username := os.Args[1] + httpClient := http.Client{ Transport: &authedTransport{ key: key, @@ -41,10 +47,16 @@ func Main() { }, } graphqlClient := graphql.NewClient("https://api.github.com/graphql", &httpClient) - resp, err := getViewer(context.Background(), graphqlClient) + + viewerResp, err := getViewer(context.Background(), graphqlClient) if err != nil { return } + fmt.Println("you are", *viewerResp.Viewer.Name) - fmt.Println("you are:", *resp.Viewer.Name) + userResp, err := getUser(context.Background(), graphqlClient, username) + if err != nil { + return + } + fmt.Println(username, "is", *userResp.User.Name) } diff --git a/example/generated.go b/example/generated.go index f16c12c..7d8808e 100644 --- a/example/generated.go +++ b/example/generated.go @@ -14,6 +14,7 @@ type getViewerResponse = struct { // TODO func getViewer(ctx context.Context, client *graphql.Client) (*getViewerResponse, error) { + var retval getViewerResponse err := client.MakeRequest(ctx, ` query getViewer { @@ -21,6 +22,31 @@ query getViewer { Name: name } } -`, &retval) +`, &retval, nil) + return &retval, err +} + +type getUserResponse = struct { + User *struct { + Name *string + } +} + +// TODO +func getUser(ctx context.Context, client *graphql.Client, login string) (*getUserResponse, error) { + + variables := map[string]interface{}{ + + "login": login, + } + + var retval getUserResponse + err := client.MakeRequest(ctx, ` +query getUser ($login: String!) { + User: user(login: $login) { + Name: name + } +} +`, &retval, variables) return &retval, err } diff --git a/example/queries.graphql b/example/queries.graphql index 484ce24..fa7eed4 100644 --- a/example/queries.graphql +++ b/example/queries.graphql @@ -4,3 +4,10 @@ query getViewer { Name: name } } + +# getUser gets the given user's name. +query getUser($login: String!) { + User: user(login: $login) { + Name: name + } +} diff --git a/generate/generate.go b/generate/generate.go index 1fe529d..e6d02c4 100644 --- a/generate/generate.go +++ b/generate/generate.go @@ -26,22 +26,31 @@ type TemplateParams struct { // The name of the package into which to generate the operation-helpers. PackageName string // The list of operations for which to generate code. - Operations []OperationParams + Operations []Operation } -type OperationParams struct { +type Operation struct { + // The type of the operation (query, mutation, or subscription). + Type ast.Operation + // The name of the operation, from GraphQL. + Name string + // The documentation for the operation, from GraphQL. + Doc string + // The body of the operation to send. + Body string + // The arguments to the operation. + Args []Argument + // The type-name for the operation's response type. ResponseName string // The body of the operation's response type (e.g. struct { ... }). ResponseType string - // The type of the operation (query, mutation, or subscription). - OperationType ast.Operation - // The name of the operation, from GraphQL. - OperationName string - // The documentation for the operation, from GraphQL. - OperationDoc string - // The body of the operation to send. - Operation string +} + +type Argument struct { + GoName string + GoType string + GraphQLName string } func Generate(specFilename, schemaFilename, generatedFilename string) error { @@ -98,7 +107,7 @@ func Generate(specFilename, schemaFilename, generatedFilename string) error { packageName := "example" // TODO: this should probably get factored out - operations := make([]OperationParams, len(document.Operations)) + operations := make([]Operation, len(document.Operations)) for i, operation := range document.Operations { // TODO: we may have to actually get the precise query text, in case we // want to be hashing it or something like that. Although maybe @@ -110,20 +119,30 @@ func Generate(specFilename, schemaFilename, generatedFilename string) error { Operations: ast.OperationList{operation}, // TODO: handle fragments }) - operations[i] = OperationParams{ - OperationType: operation.Operation, - OperationName: operation.Name, + + args := make([]Argument, len(operation.VariableDefinitions)) + for i, arg := range operation.VariableDefinitions { + args[i] = Argument{ + GraphQLName: arg.Variable, + GoName: arg.Variable, // TODO: normalize this to go-style + GoType: typeForInputType(arg.Type, schema), + // TODO: figure out what to do about defaults + } + } + operations[i] = Operation{ + Type: operation.Operation, + Name: operation.Name, // TODO: this is actually awkward, because GraphQL doesn't allow // for docstrings on queries (only schemas). So we have to extract // the comment, or omit doc-comments for now. - OperationDoc: "TODO", + Doc: "TODO", + // The newline just makes it format a little nicer + Body: "\n" + builder.String(), + Args: args, // TODO: configure ResponseName format ResponseName: operation.Name + "Response", - ResponseType: typeFor(operation, schema), - - // The newline just makes it format a little nicer - Operation: "\n" + builder.String(), + ResponseType: typeForOperation(operation, schema), } } diff --git a/generate/operation.go.tmpl b/generate/operation.go.tmpl index 451e687..a277f00 100644 --- a/generate/operation.go.tmpl +++ b/generate/operation.go.tmpl @@ -9,10 +9,17 @@ import ( {{range .Operations}} type {{.ResponseName}} = {{.ResponseType}} -// {{.OperationDoc}} -func {{.OperationName}}(ctx context.Context, client *graphql.Client) (*{{.ResponseName}}, error) { +// {{.Doc}} +func {{.Name}}(ctx context.Context, client *graphql.Client{{range .Args}}, {{.GoName}} {{.GoType}}{{end}}) (*{{.ResponseName}}, error) { + {{if .Args}} + variables := map[string]interface{}{ + {{range .Args}} + "{{.GraphQLName}}": {{.GoName}}, + {{end}} + } + {{end}} var retval {{.ResponseName}} - err := client.MakeRequest(ctx, `{{.Operation}}`, &retval) + err := client.MakeRequest(ctx, `{{.Body}}`, &retval, {{if .Args}}variables{{else}}nil{{end}}) return &retval, err } {{end}} diff --git a/generate/types.go b/generate/types.go index 77b7c56..7803ecc 100644 --- a/generate/types.go +++ b/generate/types.go @@ -7,7 +7,7 @@ import ( "github.com/vektah/gqlparser/ast" ) -func typeFor(operation *ast.OperationDefinition, schema *ast.Schema) string { +func typeForOperation(operation *ast.OperationDefinition, schema *ast.Schema) string { var builder strings.Builder writeSelectionSetStruct(&builder, operation.SelectionSet, schema) @@ -15,6 +15,16 @@ func typeFor(operation *ast.OperationDefinition, schema *ast.Schema) string { return builder.String() } +func typeForInputType(typ *ast.Type, schema *ast.Schema) string { + var builder strings.Builder + + // TODO: handle non-scalar types (by passing ...something... as the + // SelectionSet?) + writeType(&builder, typ, nil, schema) + + return builder.String() +} + func writeSelectionSetStruct(builder *strings.Builder, selectionSet ast.SelectionSet, schema *ast.Schema) { builder.WriteString("struct {\n") for _, selection := range selectionSet { diff --git a/graphql/client.go b/graphql/client.go index 0c493df..eacf1e2 100644 --- a/graphql/client.go +++ b/graphql/client.go @@ -25,14 +25,14 @@ func NewClient(endpoint string, httpClient *http.Client) *Client { } type payload struct { - Query string `json:"query"` - Variables map[string]string `json:"variables"` + Query string `json:"query"` + Variables map[string]interface{} `json:"variables"` } -func (client *Client) MakeRequest(ctx context.Context, query string, retval interface{}) error { +func (client *Client) MakeRequest(ctx context.Context, query string, retval interface{}, variables map[string]interface{}) error { body, err := json.Marshal(payload{ Query: query, - Variables: nil, // TODO + Variables: variables, }) if err != nil { return err