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
|
## 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.
|
`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)
|
- proper config/arguments setup (e.g. with [viper](https://github.com/spf13/viper)
|
||||||
|
|
||||||
Other:
|
Other:
|
||||||
|
- naming collisions are a mess
|
||||||
- error-checking/validation/etc. everywhere
|
- error-checking/validation/etc. everywhere
|
||||||
- more tests
|
- more tests
|
||||||
- documentation
|
- documentation
|
||||||
|
|||||||
+10
-6
@@ -8,16 +8,20 @@ import (
|
|||||||
"github.com/Khan/genql/graphql"
|
"github.com/Khan/genql/graphql"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
MyName *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User1 struct {
|
||||||
|
TheirName *string `json:"theirName"`
|
||||||
|
}
|
||||||
|
|
||||||
type getUserResponse struct {
|
type getUserResponse struct {
|
||||||
User *struct {
|
User *User1 `json:"user"`
|
||||||
TheirName *string `json:"theirName"`
|
|
||||||
} `json:"user"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type getViewerResponse struct {
|
type getViewerResponse struct {
|
||||||
Viewer struct {
|
Viewer User `json:"viewer"`
|
||||||
MyName *string
|
|
||||||
} `json:"viewer"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getViewer(ctx context.Context, client *graphql.Client) (*getViewerResponse, error) {
|
func getViewer(ctx context.Context, client *graphql.Client) (*getViewerResponse, error) {
|
||||||
|
|||||||
+5
-12
@@ -4,22 +4,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/format"
|
"go/format"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/vektah/gqlparser/ast"
|
"github.com/vektah/gqlparser/ast"
|
||||||
"github.com/vektah/gqlparser/formatter"
|
"github.com/vektah/gqlparser/formatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: package template into the binary using one of those asset thingies
|
var fileTemplate = mustTemplate("operation.go.tmpl")
|
||||||
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))
|
|
||||||
|
|
||||||
// generator is the context for the codegen process (and ends up getting passed
|
// generator is the context for the codegen process (and ends up getting passed
|
||||||
// to the template).
|
// to the template).
|
||||||
@@ -29,8 +21,9 @@ type generator struct {
|
|||||||
// 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.
|
// The types needed for these operations.
|
||||||
typeMap map[string]string
|
typeMap map[string]string
|
||||||
schema *ast.Schema
|
ImportJSON bool
|
||||||
|
schema *ast.Schema
|
||||||
}
|
}
|
||||||
|
|
||||||
type operation struct {
|
type operation struct {
|
||||||
@@ -165,7 +158,7 @@ func Generate(config *Config) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = tmpl.Execute(&buf, g)
|
err = fileTemplate.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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package {{$.Config.Package}}
|
package {{.Config.Package}}
|
||||||
|
|
||||||
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
{{- if .ImportJSON -}}
|
||||||
|
"encoding/json"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
"github.com/Khan/genql/graphql"
|
"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 {
|
package test
|
||||||
User *struct {
|
|
||||||
ID string
|
// 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 {
|
user {
|
||||||
ID: id
|
ID: id
|
||||||
AlsoID: id
|
AlsoID: id
|
||||||
|
|||||||
+31
-6
@@ -1,6 +1,31 @@
|
|||||||
type Response struct {
|
package test
|
||||||
User *struct {
|
|
||||||
ID string
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
AlsoID string
|
|
||||||
} `json:"user"`
|
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 {
|
user {
|
||||||
roles
|
roles
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-8
@@ -1,12 +1,36 @@
|
|||||||
type Response struct {
|
package test
|
||||||
User *struct {
|
|
||||||
Roles []role `json:"roles"`
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
} `json:"user"`
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/Khan/genql/graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryWithEnumsResponse struct {
|
||||||
|
User *User `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type role string
|
type Role string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
studentRole role = "STUDENT"
|
StudentRole Role = "STUDENT"
|
||||||
teacherRole role = "TEACHER"
|
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) {
|
user(query: $query) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-12
@@ -1,19 +1,47 @@
|
|||||||
type Response struct {
|
package test
|
||||||
User *struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
} `json:"user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type role string
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
|
|
||||||
const (
|
import (
|
||||||
studentRole role = "STUDENT"
|
"context"
|
||||||
teacherRole role = "TEACHER"
|
|
||||||
|
"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"`
|
Email *string `json:"email"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Id *string `json:"id"`
|
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 {
|
user {
|
||||||
emails
|
emails
|
||||||
emailsOrNull
|
emailsOrNull
|
||||||
|
|||||||
+35
-8
@@ -1,8 +1,35 @@
|
|||||||
type Response struct {
|
package test
|
||||||
User *struct {
|
|
||||||
Emails []string `json:"emails"`
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
EmailsOrNull []string `json:"emailsOrNull"`
|
|
||||||
EmailsWithNulls []*string `json:"emailsWithNulls"`
|
import (
|
||||||
EmailsWithNullsOrNull []*string `json:"emailsWithNullsOrNull"`
|
"context"
|
||||||
} `json:"user"`
|
|
||||||
}
|
"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 {
|
user {
|
||||||
authMethods {
|
authMethods {
|
||||||
provider
|
provider
|
||||||
|
|||||||
+37
-8
@@ -1,8 +1,37 @@
|
|||||||
type Response struct {
|
package test
|
||||||
User *struct {
|
|
||||||
AuthMethods []struct {
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
Provider *string `json:"provider"`
|
|
||||||
Email *string `json:"email"`
|
import (
|
||||||
} `json:"authMethods"`
|
"context"
|
||||||
} `json:"user"`
|
|
||||||
}
|
"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 {
|
package test
|
||||||
User *struct {
|
|
||||||
Id string `json:"id"`
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
} `json:"user"`
|
|
||||||
|
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 {
|
user {
|
||||||
__typename
|
__typename
|
||||||
id
|
id
|
||||||
|
|||||||
+31
-6
@@ -1,6 +1,31 @@
|
|||||||
type Response struct {
|
package test
|
||||||
User *struct {
|
|
||||||
Typename *string `json:"__typename"`
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
Id string `json:"id"`
|
|
||||||
} `json:"user"`
|
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 }
|
Me: user { roles }
|
||||||
OtherUser: user { roles }
|
OtherUser: user { roles }
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-12
@@ -1,15 +1,51 @@
|
|||||||
type Response struct {
|
package test
|
||||||
Me *struct {
|
|
||||||
Roles []role `json:"roles"`
|
|
||||||
}
|
|
||||||
OtherUser *struct {
|
|
||||||
Roles []role `json:"roles"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type role string
|
// Code generated by github.com/Khan/genql, DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/Khan/genql/graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Role string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
studentRole role = "STUDENT"
|
StudentRole Role = "STUDENT"
|
||||||
teacherRole role = "TEACHER"
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/vektah/gqlparser/ast"
|
"github.com/vektah/gqlparser/ast"
|
||||||
@@ -54,11 +55,16 @@ func (g *generator) addTypeForDefinition(nameOverride string, typ *ast.Definitio
|
|||||||
name = nameOverride
|
name = nameOverride
|
||||||
} else {
|
} else {
|
||||||
// TODO: casing should be configurable
|
// TODO: casing should be configurable
|
||||||
name = lowerFirst(typ.Name)
|
name = upperFirst(typ.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := g.typeMap[name]; ok {
|
// TODO: in some cases we can deduplicate, do that
|
||||||
return name, nil
|
// TODO: nicer naming scheme
|
||||||
|
i := 0
|
||||||
|
origName := name
|
||||||
|
for g.typeMap[name] != "" {
|
||||||
|
i++
|
||||||
|
name = origName + strconv.Itoa(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder := &typeBuilder{typeName: name, generator: g}
|
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) {
|
func (g *generator) getTypeForInputType(typ *ast.Type) (string, error) {
|
||||||
builder := &typeBuilder{typeName: lowerFirst(typ.Name()), generator: g}
|
builder := &typeBuilder{typeName: upperFirst(typ.Name()), generator: g}
|
||||||
err := builder.writeType(typ, selectionsForType(g, typ), false)
|
err := builder.writeType(typ, selectionsForType(g, typ))
|
||||||
return builder.String(), err
|
return builder.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this is really "field" now, rename it
|
||||||
type selection interface {
|
type selection interface {
|
||||||
Alias() string
|
Alias() string
|
||||||
Name() string
|
|
||||||
Type() *ast.Type
|
Type() *ast.Type
|
||||||
SelectionSet() ([]selection, error)
|
SelectionSet() ([]selection, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type field struct{ field *ast.Field }
|
type field struct{ field *ast.Field }
|
||||||
|
|
||||||
func (s field) Alias() string { return s.field.Alias }
|
func (s field) Alias() string {
|
||||||
func (s field) Name() string { return s.field.Name }
|
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 {
|
func (s field) Type() *ast.Type {
|
||||||
if s.field.Definition == nil {
|
if s.field.Definition == nil {
|
||||||
@@ -122,7 +133,6 @@ type inputField struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s inputField) Alias() string { return s.field.Name }
|
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) Type() *ast.Type { return s.field.Type }
|
||||||
|
|
||||||
func (s inputField) SelectionSet() ([]selection, error) {
|
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 {
|
func (builder *typeBuilder) writeField(selection selection) error {
|
||||||
var jsonName string
|
jsonName := selection.Alias()
|
||||||
if selection.Alias() != "" {
|
|
||||||
jsonName = selection.Alias()
|
|
||||||
} else {
|
|
||||||
// TODO: is this case needed? tests don't seem to get here.
|
|
||||||
jsonName = selection.Name()
|
|
||||||
}
|
|
||||||
// We need an exportable name for JSON-marshaling.
|
// We need an exportable name for JSON-marshaling.
|
||||||
goName := upperFirst(jsonName)
|
goName := upperFirst(jsonName)
|
||||||
|
|
||||||
@@ -156,7 +160,7 @@ func (builder *typeBuilder) writeField(selection selection) error {
|
|||||||
if typ == nil {
|
if typ == nil {
|
||||||
// Unclear why gqlparser hasn't already rejected this,
|
// Unclear why gqlparser hasn't already rejected this,
|
||||||
// but empirically it might not.
|
// 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()
|
selectionSet, err := selection.SelectionSet()
|
||||||
@@ -164,12 +168,15 @@ func (builder *typeBuilder) writeField(selection selection) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = builder.writeType(typ, selectionSet, true)
|
err = builder.writeType(typ, selectionSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
fmt.Fprintf(builder, " `json:\"%s\"`", jsonName)
|
||||||
}
|
}
|
||||||
builder.WriteRune('\n')
|
builder.WriteRune('\n')
|
||||||
@@ -184,7 +191,7 @@ var builtinTypes = map[string]string{
|
|||||||
"ID": "string", // TODO: named type for IDs?
|
"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
|
// gqlgen does slightly different things here since it defines names for
|
||||||
// all the intermediate types, but its implementation may be useful to crib
|
// all the intermediate types, but its implementation may be useful to crib
|
||||||
// from:
|
// from:
|
||||||
@@ -200,16 +207,6 @@ func (builder *typeBuilder) writeType(typ *ast.Type, selectionSet []selection, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
def := builder.schema.Types[typ.Name()]
|
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)
|
// Writes a typedef elsewhere (if not already defined)
|
||||||
name, err := builder.addTypeForDefinition("", def, selectionSet)
|
name, err := builder.addTypeForDefinition("", def, selectionSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -231,7 +228,35 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, selectionSet [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.WriteString("}")
|
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
|
return nil
|
||||||
|
|
||||||
case ast.Enum:
|
case ast.Enum:
|
||||||
// All GraphQL enums have underlying type string (in the Go sense).
|
// All GraphQL enums have underlying type string (in the Go sense).
|
||||||
builder.WriteString("string\n")
|
builder.WriteString("string\n")
|
||||||
@@ -244,8 +269,8 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, selectionSet [
|
|||||||
}
|
}
|
||||||
builder.WriteString(")\n")
|
builder.WriteString(")\n")
|
||||||
return nil
|
return nil
|
||||||
case ast.Scalar, ast.Union, ast.Interface:
|
case ast.Scalar:
|
||||||
// TODO(benkraft): Handle custom scalars, unions, and interfaces.
|
// TODO(benkraft): Handle custom scalars.
|
||||||
return fmt.Errorf("not implemented: %v", typedef.Kind)
|
return fmt.Errorf("not implemented: %v", typedef.Kind)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected kind: %v", typedef.Kind)
|
return fmt.Errorf("unexpected kind: %v", typedef.Kind)
|
||||||
|
|||||||
+16
-38
@@ -55,8 +55,6 @@ func TestTypeForOperation(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
schemaText := readFile(t, "schema.graphql", false)
|
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
graphqlFilename := file.Name()
|
graphqlFilename := file.Name()
|
||||||
if graphqlFilename == "schema.graphql" || !strings.HasSuffix(graphqlFilename, ".graphql") {
|
if graphqlFilename == "schema.graphql" || !strings.HasSuffix(graphqlFilename, ".graphql") {
|
||||||
@@ -65,45 +63,25 @@ func TestTypeForOperation(t *testing.T) {
|
|||||||
goFilename := graphqlFilename + ".go"
|
goFilename := graphqlFilename + ".go"
|
||||||
|
|
||||||
t.Run(graphqlFilename, func(t *testing.T) {
|
t.Run(graphqlFilename, func(t *testing.T) {
|
||||||
expectedGoType, err := gofmt(readFile(t, goFilename, update))
|
expectedGoCode, err := gofmt(readFile(t, goFilename, update))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
schema, graphqlError := gqlparser.LoadSchema(
|
goCode, err := Generate(&Config{
|
||||||
&ast.Source{Name: "test schema", Input: schemaText})
|
Schema: filepath.Join("testdata", "schema.graphql"),
|
||||||
if graphqlError != nil {
|
Queries: filepath.Join("testdata", graphqlFilename),
|
||||||
t.Fatal(graphqlError)
|
Package: "test",
|
||||||
}
|
})
|
||||||
|
|
||||||
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])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gofmt before comparing.
|
if string(goCode) != expectedGoCode {
|
||||||
goType, err := gofmt(g.Types())
|
t.Errorf("got:\n%v\nwant:\n%v\n", string(goCode), expectedGoCode)
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if goType != expectedGoType {
|
|
||||||
t.Errorf("got:\n%v\nwant:\n%v\n", goType, expectedGoType)
|
|
||||||
if update {
|
if update {
|
||||||
t.Log("Updating testdata dir to match")
|
t.Log("Updating testdata dir to match")
|
||||||
err = ioutil.WriteFile(
|
err = ioutil.WriteFile(filepath.Join(dataDir, goFilename), goCode, 0644)
|
||||||
filepath.Join(dataDir, goFilename), []byte(goType), 0644)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unable to update testdata dir: %v", err)
|
t.Errorf("Unable to update testdata dir: %v", err)
|
||||||
}
|
}
|
||||||
@@ -138,18 +116,18 @@ func TestTypeForInputType(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
`DefinedType`,
|
`DefinedType`,
|
||||||
`UserQueryInput`,
|
`UserQueryInput`,
|
||||||
`*userQueryInput`,
|
`*UserQueryInput`,
|
||||||
[]string{
|
[]string{
|
||||||
`type role string
|
`type Role string
|
||||||
const (
|
const (
|
||||||
studentRole role = "STUDENT"
|
StudentRole Role = "STUDENT"
|
||||||
teacherRole role = "TEACHER"
|
TeacherRole Role = "TEACHER"
|
||||||
)`,
|
)`,
|
||||||
`type userQueryInput struct {
|
`type UserQueryInput struct {
|
||||||
Email *string ` + "`json:\"email\"`" + `
|
Email *string ` + "`json:\"email\"`" + `
|
||||||
Name *string ` + "`json:\"name\"`" + `
|
Name *string ` + "`json:\"name\"`" + `
|
||||||
Id *string ` + "`json:\"id\"`" + `
|
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 {
|
func goConstName(s string) string {
|
||||||
|
if strings.TrimLeft(s, "_") == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
var prev rune
|
var prev rune
|
||||||
return strings.Map(func(r rune) rune {
|
return strings.Map(func(r rune) rune {
|
||||||
var ret rune
|
var ret rune
|
||||||
if prev == 0 && r == '_' {
|
if r == '_' {
|
||||||
return '_' // still treat next char as first
|
|
||||||
} else if r == '_' {
|
|
||||||
ret = -1
|
ret = -1
|
||||||
} else if prev == '_' {
|
} else if prev == '_' || prev == 0 {
|
||||||
ret = unicode.ToUpper(r)
|
ret = unicode.ToUpper(r)
|
||||||
} else {
|
} else {
|
||||||
ret = unicode.ToLower(r)
|
ret = unicode.ToLower(r)
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ func TestUpperFirst(t *testing.T) {
|
|||||||
func TestGoConstName(t *testing.T) {
|
func TestGoConstName(t *testing.T) {
|
||||||
tests := []test{
|
tests := []test{
|
||||||
{"Empty", "", ""},
|
{"Empty", "", ""},
|
||||||
{"AllCaps", "ASDF", "asdf"},
|
{"AllCaps", "ASDF", "Asdf"},
|
||||||
{"AllCapsWithUnderscore", "ASDF_GH", "asdfGh"},
|
{"AllCapsWithUnderscore", "ASDF_GH", "AsdfGh"},
|
||||||
{"JustUnderscore", "_", "_"},
|
{"JustUnderscore", "_", "_"},
|
||||||
{"LeadingUnderscore", "_ASDF_GH", "_asdfGh"},
|
{"LeadingUnderscore", "_ASDF_GH", "AsdfGh"},
|
||||||
}
|
}
|
||||||
|
|
||||||
testStringFunc(t, goConstName, tests)
|
testStringFunc(t, goConstName, tests)
|
||||||
|
|||||||
Reference in New Issue
Block a user