total rewrite to interface handling; not complete but it compiles
This commit is contained in:
@@ -51,7 +51,7 @@ For a complete working example, see `example/`.
|
||||
|
||||
## Tests
|
||||
|
||||
`go test ./...` does some perfunctory tests. (This is run by GitHub Actions.)
|
||||
`go test ./...` tests code generation. (This is run by GitHub Actions.)
|
||||
|
||||
`make example` tests that everything wires up to a real API correctly.
|
||||
|
||||
@@ -69,6 +69,7 @@ Config options:
|
||||
- proper config/arguments setup (e.g. with [viper](https://github.com/spf13/viper)
|
||||
|
||||
Other:
|
||||
- naming collisions are a mess
|
||||
- error-checking/validation/etc. everywhere
|
||||
- more tests
|
||||
- documentation
|
||||
|
||||
+10
-6
@@ -8,16 +8,20 @@ import (
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
MyName *string
|
||||
}
|
||||
|
||||
type User1 struct {
|
||||
TheirName *string `json:"theirName"`
|
||||
}
|
||||
|
||||
type getUserResponse struct {
|
||||
User *struct {
|
||||
TheirName *string `json:"theirName"`
|
||||
} `json:"user"`
|
||||
User *User1 `json:"user"`
|
||||
}
|
||||
|
||||
type getViewerResponse struct {
|
||||
Viewer struct {
|
||||
MyName *string
|
||||
} `json:"viewer"`
|
||||
Viewer User `json:"viewer"`
|
||||
}
|
||||
|
||||
func getViewer(ctx context.Context, client *graphql.Client) (*getViewerResponse, error) {
|
||||
|
||||
+5
-12
@@ -4,22 +4,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/vektah/gqlparser/ast"
|
||||
"github.com/vektah/gqlparser/formatter"
|
||||
)
|
||||
|
||||
// TODO: package template into the binary using one of those asset thingies
|
||||
var _, thisFilename, _, _ = runtime.Caller(0)
|
||||
var tmplRelFilename = "operation.go.tmpl"
|
||||
var tmplAbsFilename = filepath.Join(filepath.Dir(thisFilename), tmplRelFilename)
|
||||
|
||||
var tmpl = template.Must(template.ParseFiles(tmplAbsFilename))
|
||||
var fileTemplate = mustTemplate("operation.go.tmpl")
|
||||
|
||||
// generator is the context for the codegen process (and ends up getting passed
|
||||
// to the template).
|
||||
@@ -29,8 +21,9 @@ type generator struct {
|
||||
// The list of operations for which to generate code.
|
||||
Operations []operation
|
||||
// The types needed for these operations.
|
||||
typeMap map[string]string
|
||||
schema *ast.Schema
|
||||
typeMap map[string]string
|
||||
ImportJSON bool
|
||||
schema *ast.Schema
|
||||
}
|
||||
|
||||
type operation struct {
|
||||
@@ -165,7 +158,7 @@ func Generate(config *Config) ([]byte, error) {
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = tmpl.Execute(&buf, g)
|
||||
err = fileTemplate.Execute(&buf, g)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not render template: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package {{$.Config.Package}}
|
||||
package {{.Config.Package}}
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
{{- if .ImportJSON -}}
|
||||
"encoding/json"
|
||||
{{end}}
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// TODO: package templates into the binary using one of those asset thingies
|
||||
var _, thisFilename, _, _ = runtime.Caller(0)
|
||||
var thisDir = filepath.Dir(thisFilename)
|
||||
|
||||
func mustTemplate(relFilename string) *template.Template {
|
||||
return template.Must(template.ParseFiles(filepath.Join(thisDir, relFilename)))
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
query InterfaceNoFragmentsQuery {
|
||||
root {
|
||||
id
|
||||
name
|
||||
children {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (v Article) implementsGraphQLInterfaceContent() {}
|
||||
|
||||
type Content interface {
|
||||
implementsGraphQLInterfaceContent()
|
||||
}
|
||||
|
||||
type InterfaceNoFragmentsQueryResponse struct {
|
||||
Root Topic `json:"root"`
|
||||
}
|
||||
|
||||
type Topic struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Children []Content `json:"-"`
|
||||
}
|
||||
|
||||
func (v *Topic) UnmarshalJSON(b []byte) error {
|
||||
var firstPass struct {
|
||||
*Topic
|
||||
Children json.RawMessage `json:"children"`
|
||||
}
|
||||
firstPass.Topic = v
|
||||
|
||||
err := json.Unmarshal(b, &typenames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tn struct {
|
||||
TypeName string `json:"__typename"`
|
||||
}
|
||||
err = json.Unmarshal(firstPass.Children, &tn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tn.TypeName {
|
||||
|
||||
case "Article":
|
||||
v.Children = Article{}
|
||||
err = json.Unmarshal(
|
||||
firstPass.Children, &v.Children)
|
||||
|
||||
case "Video":
|
||||
v.Children = Video{}
|
||||
err = json.Unmarshal(
|
||||
firstPass.Children, &v.Children)
|
||||
|
||||
case "Topic":
|
||||
v.Children = Topic{}
|
||||
err = json.Unmarshal(
|
||||
firstPass.Children, &v.Children)
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type Video struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (v Video) implementsGraphQLInterfaceContent() {}
|
||||
|
||||
func InterfaceNoFragmentsQuery(client *graphql.Client) (*InterfaceNoFragmentsQueryResponse, error) {
|
||||
var retval InterfaceNoFragmentsQueryResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query InterfaceNoFragmentsQuery {
|
||||
root {
|
||||
id
|
||||
name
|
||||
children {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{ User: user { ID: id } }
|
||||
query QueryWithAlias { User: user { ID: id } }
|
||||
|
||||
+27
-3
@@ -1,5 +1,29 @@
|
||||
type Response struct {
|
||||
User *struct {
|
||||
ID string
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type QueryWithAliasResponse struct {
|
||||
User *User
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func QueryWithAlias(client *graphql.Client) (*QueryWithAliasResponse, error) {
|
||||
var retval QueryWithAliasResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query QueryWithAlias {
|
||||
User: user {
|
||||
ID: id
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
query QueryWithDoubleAlias {
|
||||
user {
|
||||
ID: id
|
||||
AlsoID: id
|
||||
|
||||
+31
-6
@@ -1,6 +1,31 @@
|
||||
type Response struct {
|
||||
User *struct {
|
||||
ID string
|
||||
AlsoID string
|
||||
} `json:"user"`
|
||||
}
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type QueryWithDoubleAliasResponse struct {
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string
|
||||
AlsoID string
|
||||
}
|
||||
|
||||
func QueryWithDoubleAlias(client *graphql.Client) (*QueryWithDoubleAliasResponse, error) {
|
||||
var retval QueryWithDoubleAliasResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query QueryWithDoubleAlias {
|
||||
user {
|
||||
ID: id
|
||||
AlsoID: id
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
query QueryWithEnums {
|
||||
user {
|
||||
roles
|
||||
}
|
||||
|
||||
+32
-8
@@ -1,12 +1,36 @@
|
||||
type Response struct {
|
||||
User *struct {
|
||||
Roles []role `json:"roles"`
|
||||
} `json:"user"`
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type QueryWithEnumsResponse struct {
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type role string
|
||||
type Role string
|
||||
|
||||
const (
|
||||
studentRole role = "STUDENT"
|
||||
teacherRole role = "TEACHER"
|
||||
)
|
||||
StudentRole Role = "STUDENT"
|
||||
TeacherRole Role = "TEACHER"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
func QueryWithEnums(client *graphql.Client) (*QueryWithEnumsResponse, error) {
|
||||
var retval QueryWithEnumsResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query QueryWithEnums {
|
||||
user {
|
||||
roles
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
query ($query: UserQueryInput) {
|
||||
query QueryWithInput($query: UserQueryInput) {
|
||||
user(query: $query) {
|
||||
id
|
||||
}
|
||||
|
||||
+40
-12
@@ -1,19 +1,47 @@
|
||||
type Response struct {
|
||||
User *struct {
|
||||
Id string `json:"id"`
|
||||
} `json:"user"`
|
||||
}
|
||||
package test
|
||||
|
||||
type role string
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
const (
|
||||
studentRole role = "STUDENT"
|
||||
teacherRole role = "TEACHER"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type userQueryInput struct {
|
||||
type QueryWithInputResponse struct {
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
StudentRole Role = "STUDENT"
|
||||
TeacherRole Role = "TEACHER"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
type UserQueryInput struct {
|
||||
Email *string `json:"email"`
|
||||
Name *string `json:"name"`
|
||||
Id *string `json:"id"`
|
||||
Role *role `json:"role"`
|
||||
}
|
||||
Role *Role `json:"role"`
|
||||
}
|
||||
|
||||
func QueryWithInput(client *graphql.Client, query *UserQueryInput) (*QueryWithInputResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"query": query,
|
||||
}
|
||||
|
||||
var retval QueryWithInputResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query QueryWithInput ($query: UserQueryInput) {
|
||||
user(query: $query) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`, &retval, variables)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
query QueryWithSlices {
|
||||
user {
|
||||
emails
|
||||
emailsOrNull
|
||||
|
||||
+35
-8
@@ -1,8 +1,35 @@
|
||||
type Response struct {
|
||||
User *struct {
|
||||
Emails []string `json:"emails"`
|
||||
EmailsOrNull []string `json:"emailsOrNull"`
|
||||
EmailsWithNulls []*string `json:"emailsWithNulls"`
|
||||
EmailsWithNullsOrNull []*string `json:"emailsWithNullsOrNull"`
|
||||
} `json:"user"`
|
||||
}
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type QueryWithSlicesResponse struct {
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Emails []string `json:"emails"`
|
||||
EmailsOrNull []string `json:"emailsOrNull"`
|
||||
EmailsWithNulls []*string `json:"emailsWithNulls"`
|
||||
EmailsWithNullsOrNull []*string `json:"emailsWithNullsOrNull"`
|
||||
}
|
||||
|
||||
func QueryWithSlices(client *graphql.Client) (*QueryWithSlicesResponse, error) {
|
||||
var retval QueryWithSlicesResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query QueryWithSlices {
|
||||
user {
|
||||
emails
|
||||
emailsOrNull
|
||||
emailsWithNulls
|
||||
emailsWithNullsOrNull
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
query QueryWithStructs {
|
||||
user {
|
||||
authMethods {
|
||||
provider
|
||||
|
||||
+37
-8
@@ -1,8 +1,37 @@
|
||||
type Response struct {
|
||||
User *struct {
|
||||
AuthMethods []struct {
|
||||
Provider *string `json:"provider"`
|
||||
Email *string `json:"email"`
|
||||
} `json:"authMethods"`
|
||||
} `json:"user"`
|
||||
}
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type AuthMethod struct {
|
||||
Provider *string `json:"provider"`
|
||||
Email *string `json:"email"`
|
||||
}
|
||||
|
||||
type QueryWithStructsResponse struct {
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
AuthMethods []AuthMethod `json:"authMethods"`
|
||||
}
|
||||
|
||||
func QueryWithStructs(client *graphql.Client) (*QueryWithStructsResponse, error) {
|
||||
var retval QueryWithStructsResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query QueryWithStructs {
|
||||
user {
|
||||
authMethods {
|
||||
provider
|
||||
email
|
||||
}
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{ user { id } }
|
||||
query SimpleQuery { user { id } }
|
||||
|
||||
+28
-4
@@ -1,5 +1,29 @@
|
||||
type Response struct {
|
||||
User *struct {
|
||||
Id string `json:"id"`
|
||||
} `json:"user"`
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type SimpleQueryResponse struct {
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
func SimpleQuery(client *graphql.Client) (*SimpleQueryResponse, error) {
|
||||
var retval SimpleQueryResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query SimpleQuery {
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
query TypeNameQuery {
|
||||
user {
|
||||
__typename
|
||||
id
|
||||
|
||||
+31
-6
@@ -1,6 +1,31 @@
|
||||
type Response struct {
|
||||
User *struct {
|
||||
Typename *string `json:"__typename"`
|
||||
Id string `json:"id"`
|
||||
} `json:"user"`
|
||||
}
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type TypeNameQueryResponse struct {
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Typename *string `json:"__typename"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
func TypeNameQuery(client *graphql.Client) (*TypeNameQueryResponse, error) {
|
||||
var retval TypeNameQueryResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query TypeNameQuery {
|
||||
user {
|
||||
__typename
|
||||
id
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
query UnionNoFragmentsQuery {
|
||||
randomLeaf {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
Typename *string `json:"__typename"`
|
||||
}
|
||||
|
||||
func (v Article) implementsGraphQLInterfaceLeafContent() {}
|
||||
|
||||
type LeafContent interface {
|
||||
implementsGraphQLInterfaceLeafContent()
|
||||
}
|
||||
|
||||
type UnionNoFragmentsQueryResponse struct {
|
||||
RandomLeaf LeafContent `json:"-"`
|
||||
}
|
||||
|
||||
func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
|
||||
var firstPass struct {
|
||||
*UnionNoFragmentsQueryResponse
|
||||
RandomLeaf json.RawMessage `json:"randomLeaf"`
|
||||
}
|
||||
firstPass.UnionNoFragmentsQueryResponse = v
|
||||
|
||||
err := json.Unmarshal(b, &typenames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tn struct {
|
||||
TypeName string `json:"__typename"`
|
||||
}
|
||||
err = json.Unmarshal(firstPass.RandomLeaf, &tn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tn.TypeName {
|
||||
|
||||
case "Article":
|
||||
v.RandomLeaf = Article{}
|
||||
err = json.Unmarshal(
|
||||
firstPass.RandomLeaf, &v.RandomLeaf)
|
||||
|
||||
case "Video":
|
||||
v.RandomLeaf = Video{}
|
||||
err = json.Unmarshal(
|
||||
firstPass.RandomLeaf, &v.RandomLeaf)
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type Video struct {
|
||||
Typename *string `json:"__typename"`
|
||||
}
|
||||
|
||||
func (v Video) implementsGraphQLInterfaceLeafContent() {}
|
||||
|
||||
func UnionNoFragmentsQuery(client *graphql.Client) (*UnionNoFragmentsQueryResponse, error) {
|
||||
var retval UnionNoFragmentsQueryResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query UnionNoFragmentsQuery {
|
||||
randomLeaf {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
query UsesEnumTwiceQuery {
|
||||
Me: user { roles }
|
||||
OtherUser: user { roles }
|
||||
}
|
||||
|
||||
+48
-12
@@ -1,15 +1,51 @@
|
||||
type Response struct {
|
||||
Me *struct {
|
||||
Roles []role `json:"roles"`
|
||||
}
|
||||
OtherUser *struct {
|
||||
Roles []role `json:"roles"`
|
||||
}
|
||||
}
|
||||
package test
|
||||
|
||||
type role string
|
||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Khan/genql/graphql"
|
||||
)
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
studentRole role = "STUDENT"
|
||||
teacherRole role = "TEACHER"
|
||||
)
|
||||
StudentRole Role = "STUDENT"
|
||||
TeacherRole Role = "TEACHER"
|
||||
)
|
||||
|
||||
type Role1 string
|
||||
|
||||
const (
|
||||
StudentRole1 Role1 = "STUDENT"
|
||||
TeacherRole1 Role1 = "TEACHER"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
type User1 struct {
|
||||
Roles []Role1 `json:"roles"`
|
||||
}
|
||||
|
||||
type UsesEnumTwiceQueryResponse struct {
|
||||
Me *User
|
||||
OtherUser *User1
|
||||
}
|
||||
|
||||
func UsesEnumTwiceQuery(client *graphql.Client) (*UsesEnumTwiceQueryResponse, error) {
|
||||
var retval UsesEnumTwiceQueryResponse
|
||||
err := client.MakeRequest(context.Background(), `
|
||||
query UsesEnumTwiceQuery {
|
||||
Me: user {
|
||||
roles
|
||||
}
|
||||
OtherUser: user {
|
||||
roles
|
||||
}
|
||||
}
|
||||
`, &retval, nil)
|
||||
return &retval, err
|
||||
}
|
||||
|
||||
+57
-32
@@ -2,6 +2,7 @@ package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/vektah/gqlparser/ast"
|
||||
@@ -54,11 +55,16 @@ func (g *generator) addTypeForDefinition(nameOverride string, typ *ast.Definitio
|
||||
name = nameOverride
|
||||
} else {
|
||||
// TODO: casing should be configurable
|
||||
name = lowerFirst(typ.Name)
|
||||
name = upperFirst(typ.Name)
|
||||
}
|
||||
|
||||
if _, ok := g.typeMap[name]; ok {
|
||||
return name, nil
|
||||
// TODO: in some cases we can deduplicate, do that
|
||||
// TODO: nicer naming scheme
|
||||
i := 0
|
||||
origName := name
|
||||
for g.typeMap[name] != "" {
|
||||
i++
|
||||
name = origName + strconv.Itoa(i)
|
||||
}
|
||||
|
||||
builder := &typeBuilder{typeName: name, generator: g}
|
||||
@@ -73,22 +79,27 @@ func (g *generator) addTypeForDefinition(nameOverride string, typ *ast.Definitio
|
||||
}
|
||||
|
||||
func (g *generator) getTypeForInputType(typ *ast.Type) (string, error) {
|
||||
builder := &typeBuilder{typeName: lowerFirst(typ.Name()), generator: g}
|
||||
err := builder.writeType(typ, selectionsForType(g, typ), false)
|
||||
builder := &typeBuilder{typeName: upperFirst(typ.Name()), generator: g}
|
||||
err := builder.writeType(typ, selectionsForType(g, typ))
|
||||
return builder.String(), err
|
||||
}
|
||||
|
||||
// TODO: this is really "field" now, rename it
|
||||
type selection interface {
|
||||
Alias() string
|
||||
Name() string
|
||||
Type() *ast.Type
|
||||
SelectionSet() ([]selection, error)
|
||||
}
|
||||
|
||||
type field struct{ field *ast.Field }
|
||||
|
||||
func (s field) Alias() string { return s.field.Alias }
|
||||
func (s field) Name() string { return s.field.Name }
|
||||
func (s field) Alias() string {
|
||||
if s.field.Alias != "" {
|
||||
return s.field.Alias
|
||||
}
|
||||
// TODO: is this case needed? tests don't seem to get here.
|
||||
return s.field.Name
|
||||
}
|
||||
|
||||
func (s field) Type() *ast.Type {
|
||||
if s.field.Definition == nil {
|
||||
@@ -122,7 +133,6 @@ type inputField struct {
|
||||
}
|
||||
|
||||
func (s inputField) Alias() string { return s.field.Name }
|
||||
func (s inputField) Name() string { return s.field.Name }
|
||||
func (s inputField) Type() *ast.Type { return s.field.Type }
|
||||
|
||||
func (s inputField) SelectionSet() ([]selection, error) {
|
||||
@@ -139,13 +149,7 @@ func selectionsForType(g *generator, typ *ast.Type) []selection {
|
||||
}
|
||||
|
||||
func (builder *typeBuilder) writeField(selection selection) error {
|
||||
var jsonName string
|
||||
if selection.Alias() != "" {
|
||||
jsonName = selection.Alias()
|
||||
} else {
|
||||
// TODO: is this case needed? tests don't seem to get here.
|
||||
jsonName = selection.Name()
|
||||
}
|
||||
jsonName := selection.Alias()
|
||||
// We need an exportable name for JSON-marshaling.
|
||||
goName := upperFirst(jsonName)
|
||||
|
||||
@@ -156,7 +160,7 @@ func (builder *typeBuilder) writeField(selection selection) error {
|
||||
if typ == nil {
|
||||
// Unclear why gqlparser hasn't already rejected this,
|
||||
// but empirically it might not.
|
||||
return fmt.Errorf("undefined field %v", selection.Name())
|
||||
return fmt.Errorf("undefined field %v", selection.Alias())
|
||||
}
|
||||
|
||||
selectionSet, err := selection.SelectionSet()
|
||||
@@ -164,12 +168,15 @@ func (builder *typeBuilder) writeField(selection selection) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = builder.writeType(typ, selectionSet, true)
|
||||
err = builder.writeType(typ, selectionSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonName != goName {
|
||||
if builder.schema.Types[typ.Name()].IsAbstractType() {
|
||||
// abstract types are handled in our UnmarshalJSON
|
||||
builder.WriteString(" `json:\"-\"`")
|
||||
} else if jsonName != goName {
|
||||
fmt.Fprintf(builder, " `json:\"%s\"`", jsonName)
|
||||
}
|
||||
builder.WriteRune('\n')
|
||||
@@ -184,7 +191,7 @@ var builtinTypes = map[string]string{
|
||||
"ID": "string", // TODO: named type for IDs?
|
||||
}
|
||||
|
||||
func (builder *typeBuilder) writeType(typ *ast.Type, selectionSet []selection, inline bool) error {
|
||||
func (builder *typeBuilder) writeType(typ *ast.Type, selectionSet []selection) error {
|
||||
// gqlgen does slightly different things here since it defines names for
|
||||
// all the intermediate types, but its implementation may be useful to crib
|
||||
// from:
|
||||
@@ -200,16 +207,6 @@ func (builder *typeBuilder) writeType(typ *ast.Type, selectionSet []selection, i
|
||||
}
|
||||
|
||||
def := builder.schema.Types[typ.Name()]
|
||||
// TODO: set inline = false for nested types
|
||||
switch def.Kind {
|
||||
case ast.Scalar, ast.Enum, ast.Union, ast.Interface:
|
||||
inline = false
|
||||
}
|
||||
|
||||
if inline {
|
||||
return builder.writeTypedef(def, selectionSet)
|
||||
}
|
||||
|
||||
// Writes a typedef elsewhere (if not already defined)
|
||||
name, err := builder.addTypeForDefinition("", def, selectionSet)
|
||||
if err != nil {
|
||||
@@ -231,7 +228,35 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, selectionSet [
|
||||
}
|
||||
}
|
||||
builder.WriteString("}")
|
||||
|
||||
// If any field is abstract, we need an UnmarshalJSON method to handle
|
||||
// it.
|
||||
return builder.maybeWriteUnmarshal(selectionSet)
|
||||
|
||||
case ast.Interface, ast.Union:
|
||||
// First, write the interface type.
|
||||
builder.WriteString("interface {\n")
|
||||
implementsMethodName := fmt.Sprintf("implementsGraphQLInterface%v", builder.typeName)
|
||||
// TODO: Also write GetX() accessor methods for fields of the interface
|
||||
builder.WriteString(implementsMethodName)
|
||||
builder.WriteString("()\n")
|
||||
builder.WriteString("}")
|
||||
|
||||
// Then, write the implementations.
|
||||
// TODO(benkraft): Put a doc-comment somewhere with the list.
|
||||
for _, impldef := range builder.schema.GetPossibleTypes(typedef) {
|
||||
name, err := builder.addTypeForDefinition("", impldef, selectionSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// HACK HACK HACK
|
||||
builder.typeMap[name] += fmt.Sprintf(
|
||||
"\nfunc (v %v) %v() {}", name, implementsMethodName)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case ast.Enum:
|
||||
// All GraphQL enums have underlying type string (in the Go sense).
|
||||
builder.WriteString("string\n")
|
||||
@@ -244,8 +269,8 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, selectionSet [
|
||||
}
|
||||
builder.WriteString(")\n")
|
||||
return nil
|
||||
case ast.Scalar, ast.Union, ast.Interface:
|
||||
// TODO(benkraft): Handle custom scalars, unions, and interfaces.
|
||||
case ast.Scalar:
|
||||
// TODO(benkraft): Handle custom scalars.
|
||||
return fmt.Errorf("not implemented: %v", typedef.Kind)
|
||||
default:
|
||||
return fmt.Errorf("unexpected kind: %v", typedef.Kind)
|
||||
|
||||
+16
-38
@@ -55,8 +55,6 @@ func TestTypeForOperation(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
schemaText := readFile(t, "schema.graphql", false)
|
||||
|
||||
for _, file := range files {
|
||||
graphqlFilename := file.Name()
|
||||
if graphqlFilename == "schema.graphql" || !strings.HasSuffix(graphqlFilename, ".graphql") {
|
||||
@@ -65,45 +63,25 @@ func TestTypeForOperation(t *testing.T) {
|
||||
goFilename := graphqlFilename + ".go"
|
||||
|
||||
t.Run(graphqlFilename, func(t *testing.T) {
|
||||
expectedGoType, err := gofmt(readFile(t, goFilename, update))
|
||||
expectedGoCode, err := gofmt(readFile(t, goFilename, update))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
schema, graphqlError := gqlparser.LoadSchema(
|
||||
&ast.Source{Name: "test schema", Input: schemaText})
|
||||
if graphqlError != nil {
|
||||
t.Fatal(graphqlError)
|
||||
}
|
||||
|
||||
queryDoc, graphqlListError := gqlparser.LoadQuery(
|
||||
schema, readFile(t, graphqlFilename, false))
|
||||
if graphqlListError != nil {
|
||||
t.Fatal(graphqlListError)
|
||||
}
|
||||
|
||||
if len(queryDoc.Operations) != 1 {
|
||||
t.Fatalf("got %v operations, want 1", len(queryDoc.Operations))
|
||||
}
|
||||
|
||||
g := newGenerator(&Config{Package: "test_package"}, schema)
|
||||
err = g.addOperation(queryDoc.Operations[0])
|
||||
goCode, err := Generate(&Config{
|
||||
Schema: filepath.Join("testdata", "schema.graphql"),
|
||||
Queries: filepath.Join("testdata", graphqlFilename),
|
||||
Package: "test",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// gofmt before comparing.
|
||||
goType, err := gofmt(g.Types())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if goType != expectedGoType {
|
||||
t.Errorf("got:\n%v\nwant:\n%v\n", goType, expectedGoType)
|
||||
if string(goCode) != expectedGoCode {
|
||||
t.Errorf("got:\n%v\nwant:\n%v\n", string(goCode), expectedGoCode)
|
||||
if update {
|
||||
t.Log("Updating testdata dir to match")
|
||||
err = ioutil.WriteFile(
|
||||
filepath.Join(dataDir, goFilename), []byte(goType), 0644)
|
||||
err = ioutil.WriteFile(filepath.Join(dataDir, goFilename), goCode, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Unable to update testdata dir: %v", err)
|
||||
}
|
||||
@@ -138,18 +116,18 @@ func TestTypeForInputType(t *testing.T) {
|
||||
}, {
|
||||
`DefinedType`,
|
||||
`UserQueryInput`,
|
||||
`*userQueryInput`,
|
||||
`*UserQueryInput`,
|
||||
[]string{
|
||||
`type role string
|
||||
`type Role string
|
||||
const (
|
||||
studentRole role = "STUDENT"
|
||||
teacherRole role = "TEACHER"
|
||||
StudentRole Role = "STUDENT"
|
||||
TeacherRole Role = "TEACHER"
|
||||
)`,
|
||||
`type userQueryInput struct {
|
||||
`type UserQueryInput struct {
|
||||
Email *string ` + "`json:\"email\"`" + `
|
||||
Name *string ` + "`json:\"name\"`" + `
|
||||
Id *string ` + "`json:\"id\"`" + `
|
||||
Role *role ` + "`json:\"role\"`" + `
|
||||
Role *Role ` + "`json:\"role\"`" + `
|
||||
}`,
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package generate
|
||||
|
||||
var unmarshalTemplate = mustTemplate("unmarshal.go.tmpl")
|
||||
|
||||
type templateData struct {
|
||||
// Go type to which the method will be added
|
||||
Type string
|
||||
// Abstract fields of the type, which need special handling.
|
||||
Fields []abstractField
|
||||
}
|
||||
|
||||
type abstractField struct {
|
||||
// Name of the field, in Go and JSON
|
||||
GoName, JSONName string
|
||||
// Concrete types the field might take.
|
||||
ConcreteTypes []concreteType
|
||||
}
|
||||
|
||||
type concreteType struct {
|
||||
// Name of the type, in Go and GraphQL
|
||||
GoName, GraphQLName string
|
||||
}
|
||||
|
||||
func (builder *typeBuilder) maybeWriteUnmarshal(fields []selection) error {
|
||||
data := templateData{Type: builder.typeName}
|
||||
for _, field := range fields {
|
||||
typedef := builder.schema.Types[field.Type().Name()]
|
||||
if typedef.IsAbstractType() {
|
||||
fieldInfo := abstractField{
|
||||
GoName: upperFirst(field.Alias()),
|
||||
JSONName: field.Alias(),
|
||||
}
|
||||
for _, typedef := range builder.schema.GetPossibleTypes(typedef) {
|
||||
fieldInfo.ConcreteTypes = append(fieldInfo.ConcreteTypes,
|
||||
concreteType{
|
||||
// TODO: lies! We might have added "1" or something.
|
||||
GoName: upperFirst(typedef.Name),
|
||||
GraphQLName: typedef.Name,
|
||||
})
|
||||
}
|
||||
data.Fields = append(data.Fields, fieldInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if len(data.Fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
builder.WriteString("\n\n")
|
||||
return unmarshalTemplate.Execute(builder, data)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
func (v *{{.Type}}) UnmarshalJSON(b []byte) error {
|
||||
var firstPass struct{
|
||||
*{{.Type}}
|
||||
{{range .Fields -}}
|
||||
{{.GoName}} json.RawMessage `json:"{{.JSONName}}"`
|
||||
{{end}}
|
||||
}
|
||||
firstPass.{{.Type}} = v
|
||||
|
||||
err := json.Unmarshal(b, &typenames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
{{range .Fields -}}
|
||||
var tn struct { TypeName string `json:"__typename"` }
|
||||
err = json.Unmarshal(firstPass.{{.GoName}}, &tn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tn.TypeName {
|
||||
{{with $field := .}}
|
||||
{{range $field.ConcreteTypes}}
|
||||
case "{{.GraphQLName}}":
|
||||
v.{{$field.GoName}} = {{.GoName}}{}
|
||||
err = json.Unmarshal(
|
||||
firstPass.{{$field.GoName}}, &v.{{$field.GoName}})
|
||||
{{end}}
|
||||
{{end}}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
}
|
||||
+5
-4
@@ -29,14 +29,15 @@ func upperFirst(s string) string {
|
||||
}
|
||||
|
||||
func goConstName(s string) string {
|
||||
if strings.TrimLeft(s, "_") == "" {
|
||||
return s
|
||||
}
|
||||
var prev rune
|
||||
return strings.Map(func(r rune) rune {
|
||||
var ret rune
|
||||
if prev == 0 && r == '_' {
|
||||
return '_' // still treat next char as first
|
||||
} else if r == '_' {
|
||||
if r == '_' {
|
||||
ret = -1
|
||||
} else if prev == '_' {
|
||||
} else if prev == '_' || prev == 0 {
|
||||
ret = unicode.ToUpper(r)
|
||||
} else {
|
||||
ret = unicode.ToLower(r)
|
||||
|
||||
@@ -55,10 +55,10 @@ func TestUpperFirst(t *testing.T) {
|
||||
func TestGoConstName(t *testing.T) {
|
||||
tests := []test{
|
||||
{"Empty", "", ""},
|
||||
{"AllCaps", "ASDF", "asdf"},
|
||||
{"AllCapsWithUnderscore", "ASDF_GH", "asdfGh"},
|
||||
{"AllCaps", "ASDF", "Asdf"},
|
||||
{"AllCapsWithUnderscore", "ASDF_GH", "AsdfGh"},
|
||||
{"JustUnderscore", "_", "_"},
|
||||
{"LeadingUnderscore", "_ASDF_GH", "_asdfGh"},
|
||||
{"LeadingUnderscore", "_ASDF_GH", "AsdfGh"},
|
||||
}
|
||||
|
||||
testStringFunc(t, goConstName, tests)
|
||||
|
||||
Reference in New Issue
Block a user