add support for custom scalars -- mainly adding proper import machinery

This commit is contained in:
Ben Kraft
2021-04-08 12:40:53 -07:00
parent e68787ad35
commit b4e8316c6a
31 changed files with 283 additions and 89 deletions
-1
View File
@@ -112,7 +112,6 @@ Config options:
- whether names should be exported - whether names should be exported
- default handling for optional fields? (maybe generate a HasFoo, you can always ignore if you don't care) - default handling for optional fields? (maybe generate a HasFoo, you can always ignore if you don't care)
- generate mocks? - generate mocks?
- custom scalar types (or custom mappings for standard scalars, if you want a special ID type say)
Runtime: Runtime:
- (+) basic tests for graphql package - (+) basic tests for graphql package
+2 -2
View File
@@ -48,7 +48,7 @@ func Main() {
if err != nil { if err != nil {
return return
} }
fmt.Println("you are", viewerResp.Viewer.MyName) fmt.Println("you are", viewerResp.Viewer.MyName, "created on", viewerResp.Viewer.CreatedAt.Format("2006-01-02"))
case 2: case 2:
username := os.Args[1] username := os.Args[1]
@@ -56,7 +56,7 @@ func Main() {
if err != nil { if err != nil {
return return
} }
fmt.Println(username, "is", userResp.User.TheirName) fmt.Println(username, "is", userResp.User.TheirName, "created on", userResp.User.CreatedAt.Format("2006-01-02"))
default: default:
err = fmt.Errorf("usage: %v [username]", os.Args[0]) err = fmt.Errorf("usage: %v [username]", os.Args[0])
+7 -2
View File
@@ -4,6 +4,7 @@ package example
import ( import (
"context" "context"
"time"
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
) )
@@ -13,7 +14,8 @@ type getUserResponse struct {
} }
type getUserUser struct { type getUserUser struct {
TheirName string `json:"theirName"` TheirName string `json:"theirName"`
CreatedAt time.Time `json:"createdAt"`
} }
type getViewerResponse struct { type getViewerResponse struct {
@@ -21,7 +23,8 @@ type getViewerResponse struct {
} }
type getViewerViewerUser struct { type getViewerViewerUser struct {
MyName string MyName string
CreatedAt time.Time `json:"createdAt"`
} }
func getViewer( func getViewer(
@@ -36,6 +39,7 @@ func getViewer(
query getViewer { query getViewer {
viewer { viewer {
MyName: name MyName: name
createdAt
} }
} }
`, `,
@@ -63,6 +67,7 @@ func getUser(
query getUser ($Login: String!) { query getUser ($Login: String!) {
user(login: $Login) { user(login: $Login) {
theirName: name theirName: name
createdAt
} }
} }
`, `,
+2
View File
@@ -1,6 +1,7 @@
query getViewer { query getViewer {
viewer { viewer {
MyName: name MyName: name
createdAt
} }
} }
@@ -8,5 +9,6 @@ query getViewer {
query getUser($Login: String!) { query getUser($Login: String!) {
user(login: $Login) { user(login: $Login) {
theirName: name theirName: name
createdAt
} }
} }
+5
View File
@@ -4,3 +4,8 @@ schema: schema.graphql
queries: queries:
- genqlient.graphql - genqlient.graphql
generated: generated.go generated: generated.go
# We map github's DateTime type to Go's time.Time (which conveniently already
# defines MarshalJSON and UnmarshalJSAON).
scalars:
DateTime: time.Time
+13 -24
View File
@@ -5,7 +5,6 @@ import (
"go/token" "go/token"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"strings"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@@ -71,24 +70,32 @@ type Config struct {
// TODO: what if you want to return err? // TODO: what if you want to return err?
ClientGetter string `yaml:"client_getter"` ClientGetter string `yaml:"client_getter"`
// A map from GraphQL scalar type name to Go fully-qualified type name for
// the types to use for any custom or builtin scalars. By default, builtin
// scalars are mapped to the obvious Go types (String and ID to string, Int
// to int, Float to float64, and Boolean to bool), but this setting will
// extend or override those mappings. These types must define MarshalJSON
// and UnmarshalJSON methods, or otherwise be convertible to JSON.
Scalars map[string]string `yaml:"scalars"`
// Set automatically to the filename of the config file itself. // Set automatically to the filename of the config file itself.
configFilename string configFilename string
} }
// BaseDir returns the directory of the config-file (relative to which // baseDir returns the directory of the config-file (relative to which
// all the other paths are resolved). // all the other paths are resolved).
func (c *Config) BaseDir() string { func (c *Config) baseDir() string {
return filepath.Dir(c.configFilename) return filepath.Dir(c.configFilename)
} }
func (c *Config) ValidateAndFillDefaults(configFilename string) error { func (c *Config) ValidateAndFillDefaults(configFilename string) error {
c.configFilename = configFilename c.configFilename = configFilename
// Make paths relative to config dir // Make paths relative to config dir
c.Schema = filepath.Join(c.BaseDir(), c.Schema) c.Schema = filepath.Join(c.baseDir(), c.Schema)
for i := range c.Operations { for i := range c.Operations {
c.Operations[i] = filepath.Join(c.BaseDir(), c.Operations[i]) c.Operations[i] = filepath.Join(c.baseDir(), c.Operations[i])
} }
c.Generated = filepath.Join(c.BaseDir(), c.Generated) c.Generated = filepath.Join(c.baseDir(), c.Generated)
if c.Package == "" { if c.Package == "" {
abs, err := filepath.Abs(c.Generated) abs, err := filepath.Abs(c.Generated)
@@ -107,24 +114,6 @@ func (c *Config) ValidateAndFillDefaults(configFilename string) error {
return nil return nil
} }
func (c *Config) ContextPackage() string {
if c.ContextType == "" {
return ""
}
i := strings.LastIndex(c.ContextType, ".")
return c.ContextType[:i]
}
func (c *Config) ContextTypeReference() string {
if c.ContextType == "" {
return ""
}
i := strings.LastIndex(c.ContextType, "/")
return c.ContextType[i+1:]
}
func ReadAndValidateConfig(filename string) (*Config, error) { func ReadAndValidateConfig(filename string) (*Config, error) {
config := *defaultConfig config := *defaultConfig
if filename != "" { if filename != "" {
+1 -1
View File
@@ -59,7 +59,7 @@ func TestRunExample(t *testing.T) {
} }
got := strings.TrimSpace(string(out)) got := strings.TrimSpace(string(out))
want := "benjaminjkraft is Ben Kraft" want := "benjaminjkraft is Ben Kraft created on 2009-08-03"
if got != want { if got != want {
t.Errorf("output incorrect\ngot:\n%s\nwant:\n%s", got, want) t.Errorf("output incorrect\ngot:\n%s\nwant:\n%s", got, want)
} }
+40 -12
View File
@@ -7,16 +7,16 @@ import (
"go/format" "go/format"
"sort" "sort"
"strings" "strings"
"text/template"
"github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/formatter" "github.com/vektah/gqlparser/v2/formatter"
"golang.org/x/tools/imports"
) )
// Set to true to test features that aren't yet really ready. // Set to true to test features that aren't yet really ready.
var allowBrokenFeatures = false var allowBrokenFeatures = false
var fileTemplate = mustTemplate("operation.go.tmpl")
// 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).
type generator struct { type generator struct {
@@ -25,9 +25,14 @@ 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
ImportJSON bool // Imports needed for these operations, path -> alias and alias -> true
schema *ast.Schema imports map[string]string
usedAliases map[string]bool
// Cache of loaded templates.
templateCache map[string]*template.Template
// Schema we are generating code against
schema *ast.Schema
} }
// JSON tags in operation are for ExportOperations (see Config for details). // JSON tags in operation are for ExportOperations (see Config for details).
@@ -59,11 +64,29 @@ type argument struct {
} }
func newGenerator(config *Config, schema *ast.Schema) *generator { func newGenerator(config *Config, schema *ast.Schema) *generator {
return &generator{ g := generator{
Config: config, Config: config,
typeMap: map[string]string{}, typeMap: map[string]string{},
schema: schema, imports: map[string]string{},
usedAliases: map[string]bool{},
templateCache: map[string]*template.Template{},
schema: schema,
} }
if g.Config.ClientGetter == "" {
_, err := g.addRef("github.com/Khan/genqlient/graphql.Client")
if err != nil {
panic(err)
}
}
if g.Config.ContextType != "" {
_, err := g.addRef(g.Config.ContextType)
if err != nil {
panic(err)
}
}
return &g
} }
func (g *generator) Types() string { func (g *generator) Types() string {
@@ -163,7 +186,7 @@ func Generate(config *Config) (map[string][]byte, error) {
return nil, err return nil, err
} }
document, err := getAndValidateQueries(config.BaseDir(), config.Operations, schema) document, err := getAndValidateQueries(config.baseDir(), config.Operations, schema)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -187,7 +210,7 @@ func Generate(config *Config) (map[string][]byte, error) {
} }
var buf bytes.Buffer var buf bytes.Buffer
err = fileTemplate.Execute(&buf, g) err = g.execute("operation.go.tmpl", &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)
} }
@@ -198,9 +221,14 @@ func Generate(config *Config) (map[string][]byte, error) {
return nil, fmt.Errorf("could not gofmt code: %v\n---unformatted code---\n%v", return nil, fmt.Errorf("could not gofmt code: %v\n---unformatted code---\n%v",
err, string(unformatted)) err, string(unformatted))
} }
importsed, err := imports.Process(config.Generated, formatted, nil)
if err != nil {
return nil, fmt.Errorf("could not goimports code: %v\n---unimportsed code---\n%v",
err, string(formatted))
}
retval := map[string][]byte{ retval := map[string][]byte{
config.Generated: formatted, config.Generated: importsed,
} }
if config.ExportOperations != "" { if config.ExportOperations != "" {
+4
View File
@@ -71,6 +71,10 @@ func TestGenerate(t *testing.T) {
Package: "test", Package: "test",
Generated: goFilename, Generated: goFilename,
ExportOperations: queriesFilename, ExportOperations: queriesFilename,
Scalars: map[string]string{
"ID": "github.com/me/mypkg.ID",
"DateTime": "time.Time",
},
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+81
View File
@@ -0,0 +1,81 @@
package generate
import (
"fmt"
"go/types"
"strconv"
"strings"
)
func (g *generator) addImportFor(pkgPath string) (alias string) {
if alias, ok := g.imports[pkgPath]; ok {
return alias
}
pkgName := pkgPath[strings.LastIndex(pkgPath, "/")+1:]
alias = pkgName
suffix := 2
for g.usedAliases[alias] {
alias = pkgName + strconv.Itoa(suffix)
}
g.imports[pkgPath] = alias
g.usedAliases[alias] = true
return alias
}
// addRef adds any imports necessary to refer to the given name, and returns a
// reference alias.Name for it.
func (g *generator) addRef(fullyQualifiedName string) (qualifiedName string, err error) {
return g.getRef(fullyQualifiedName, true)
}
// ref returns a reference alias.Name for the given import, if its package was
// already added (e.g. via addRef), and an error if not.
func (g *generator) ref(fullyQualifiedName string) (qualifiedName string, err error) {
return g.getRef(fullyQualifiedName, false)
}
func (g *generator) getRef(fullyQualifiedName string, addImport bool) (qualifiedName string, err error) {
i := strings.LastIndex(fullyQualifiedName, ".")
if i == -1 {
if types.Universe.Lookup(fullyQualifiedName) == nil {
return "", fmt.Errorf(
`unknown name "%v"; expected a builtin or path/to/package.Name`, fullyQualifiedName)
}
return fullyQualifiedName, nil
}
pkgPath := fullyQualifiedName[:i]
localName := fullyQualifiedName[i+1:]
var alias string
if addImport {
alias = g.addImportFor(pkgPath)
} else {
var ok bool
alias, ok = g.imports[pkgPath]
if !ok {
return "", fmt.Errorf(`no alias defined for package "%v"`, pkgPath)
}
}
return alias + "." + localName, nil
}
// Returns the import-clause to use in the generated code.
func (g *generator) Imports() string {
if len(g.imports) == 0 {
return ""
}
var builder strings.Builder
builder.WriteString("import (\n")
for path, alias := range g.imports {
if path == alias || strings.HasSuffix(path, "/"+alias) {
builder.WriteString("\t" + strconv.Quote(path) + "\n")
} else {
builder.WriteString("\t" + alias + " " + strconv.Quote(path) + "\n")
}
}
builder.WriteString(")\n\n")
return builder.String()
}
+3 -12
View File
@@ -2,16 +2,7 @@ package {{.Config.Package}}
// Code generated by github.com/Khan/genqlient, DO NOT EDIT. // Code generated by github.com/Khan/genqlient, DO NOT EDIT.
import ( {{.Imports}}
{{if .Config.ContextType -}}
"{{.Config.ContextPackage}}"
{{end}}
{{- if .ImportJSON -}}
"encoding/json"
{{end}}
"github.com/Khan/genqlient/graphql"
)
{{/* TODO: type-assert that your ctx type implements context.Context */}} {{/* TODO: type-assert that your ctx type implements context.Context */}}
@@ -21,10 +12,10 @@ import (
{{.Doc}} {{.Doc}}
func {{.Name}}( func {{.Name}}(
{{if $.Config.ContextType -}} {{if $.Config.ContextType -}}
ctx {{$.Config.ContextTypeReference}}, ctx {{ref $.Config.ContextType}},
{{end}} {{end}}
{{- if not $.Config.ClientGetter -}} {{- if not $.Config.ClientGetter -}}
client graphql.Client, client {{ref "github.com/Khan/genqlient/graphql.Client"}},
{{end}} {{end}}
{{- range .Args -}} {{- range .Args -}}
{{.GoName}} {{.GoType}}, {{.GoName}} {{.GoType}},
+22 -2
View File
@@ -1,6 +1,8 @@
package generate package generate
import ( import (
"fmt"
"io"
"path/filepath" "path/filepath"
"runtime" "runtime"
"text/template" "text/template"
@@ -13,6 +15,24 @@ var (
thisDir = filepath.Dir(thisFilename) thisDir = filepath.Dir(thisFilename)
) )
func mustTemplate(relFilename string) *template.Template { // execute executes the given template with the funcs from this generator.
return template.Must(template.ParseFiles(filepath.Join(thisDir, relFilename))) func (g *generator) execute(tmplRelFilename string, w io.Writer, data interface{}) error {
tmpl := g.templateCache[tmplRelFilename]
if tmpl == nil {
absFilename := filepath.Join(thisDir, tmplRelFilename)
funcMap := template.FuncMap{
"ref": g.ref,
}
var err error
tmpl, err = template.New(tmplRelFilename).Funcs(funcMap).ParseFiles(absFilename)
if err != nil {
return fmt.Errorf("could not load template %v: %v", absFilename, err)
}
g.templateCache[tmplRelFilename] = tmpl
}
err := tmpl.Execute(w, data)
if err != nil {
return fmt.Errorf("could not render template: %v", err)
}
return nil
} }
+3
View File
@@ -0,0 +1,3 @@
query convertTimezone($dt: DateTime!, $tz: String) {
convert(dt: $dt, tz: $tz)
}
+38
View File
@@ -0,0 +1,38 @@
package test
// Code generated by github.com/Khan/genqlient, DO NOT EDIT.
import (
"time"
"github.com/Khan/genqlient/graphql"
)
type convertTimezoneResponse struct {
Convert time.Time `json:"convert"`
}
func convertTimezone(
client graphql.Client,
dt time.Time,
tz string,
) (*convertTimezoneResponse, error) {
variables := map[string]interface{}{
"dt": dt,
"tz": tz,
}
var retval convertTimezoneResponse
err := client.MakeRequest(
nil,
"convertTimezone",
`
query convertTimezone ($dt: DateTime!, $tz: String) {
convert(dt: $dt, tz: $tz)
}
`,
&retval,
variables,
)
return &retval, err
}
+9
View File
@@ -0,0 +1,9 @@
{
"operations": [
{
"operationName": "convertTimezone",
"query": "\nquery convertTimezone ($dt: DateTime!, $tz: String) {\n\tconvert(dt: $dt, tz: $tz)\n}\n",
"sourceLocation": "testdata/queries/DateTime.graphql"
}
]
}
+3 -2
View File
@@ -4,6 +4,7 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type InputObjectQueryResponse struct { type InputObjectQueryResponse struct {
@@ -11,7 +12,7 @@ type InputObjectQueryResponse struct {
} }
type InputObjectQueryUser struct { type InputObjectQueryUser struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
} }
type Role string type Role string
@@ -24,7 +25,7 @@ const (
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 mypkg.ID `json:"id"`
Role Role `json:"role"` Role Role `json:"role"`
Names []string `json:"names"` Names []string `json:"names"`
} }
+8 -7
View File
@@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type InterfaceNoFragmentsQueryResponse struct { type InterfaceNoFragmentsQueryResponse struct {
@@ -13,7 +14,7 @@ type InterfaceNoFragmentsQueryResponse struct {
} }
type InterfaceNoFragmentsQueryRootTopic struct { type InterfaceNoFragmentsQueryRootTopic struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Children []InterfaceNoFragmentsQueryRootTopicChildrenContent `json:"-"` Children []InterfaceNoFragmentsQueryRootTopicChildrenContent `json:"-"`
} }
@@ -66,8 +67,8 @@ func (v *InterfaceNoFragmentsQueryRootTopic) UnmarshalJSON(b []byte) error {
} }
type InterfaceNoFragmentsQueryRootTopicChildrenArticle struct { type InterfaceNoFragmentsQueryRootTopicChildrenArticle struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} }
func (v InterfaceNoFragmentsQueryRootTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() { func (v InterfaceNoFragmentsQueryRootTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
@@ -78,16 +79,16 @@ type InterfaceNoFragmentsQueryRootTopicChildrenContent interface {
} }
type InterfaceNoFragmentsQueryRootTopicChildrenTopic struct { type InterfaceNoFragmentsQueryRootTopicChildrenTopic struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} }
func (v InterfaceNoFragmentsQueryRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() { func (v InterfaceNoFragmentsQueryRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
} }
type InterfaceNoFragmentsQueryRootTopicChildrenVideo struct { type InterfaceNoFragmentsQueryRootTopicChildrenVideo struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} }
func (v InterfaceNoFragmentsQueryRootTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() { func (v InterfaceNoFragmentsQueryRootTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
+2 -1
View File
@@ -4,6 +4,7 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type ListInputQueryResponse struct { type ListInputQueryResponse struct {
@@ -11,7 +12,7 @@ type ListInputQueryResponse struct {
} }
type ListInputQueryUser struct { type ListInputQueryUser struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
} }
func ListInputQuery( func ListInputQuery(
+2 -1
View File
@@ -4,6 +4,7 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type QueryWithAliasResponse struct { type QueryWithAliasResponse struct {
@@ -11,7 +12,7 @@ type QueryWithAliasResponse struct {
} }
type QueryWithAliasUser struct { type QueryWithAliasUser struct {
ID string ID mypkg.ID
} }
func QueryWithAlias( func QueryWithAlias(
+3 -2
View File
@@ -4,6 +4,7 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type QueryWithDoubleAliasResponse struct { type QueryWithDoubleAliasResponse struct {
@@ -11,8 +12,8 @@ type QueryWithDoubleAliasResponse struct {
} }
type QueryWithDoubleAliasUser struct { type QueryWithDoubleAliasUser struct {
ID string ID mypkg.ID
AlsoID string AlsoID mypkg.ID
} }
func QueryWithDoubleAlias( func QueryWithDoubleAlias(
+2 -1
View File
@@ -4,6 +4,7 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type SimpleInputQueryResponse struct { type SimpleInputQueryResponse struct {
@@ -11,7 +12,7 @@ type SimpleInputQueryResponse struct {
} }
type SimpleInputQueryUser struct { type SimpleInputQueryUser struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
} }
func SimpleInputQuery( func SimpleInputQuery(
+3 -2
View File
@@ -4,11 +4,12 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type SimpleMutationCreateUser struct { type SimpleMutationCreateUser struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} }
type SimpleMutationResponse struct { type SimpleMutationResponse struct {
+2 -1
View File
@@ -4,6 +4,7 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type SimpleQueryResponse struct { type SimpleQueryResponse struct {
@@ -11,7 +12,7 @@ type SimpleQueryResponse struct {
} }
type SimpleQueryUser struct { type SimpleQueryUser struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
} }
func SimpleQuery( func SimpleQuery(
+3 -2
View File
@@ -4,6 +4,7 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type TypeNameQueryResponse struct { type TypeNameQueryResponse struct {
@@ -11,8 +12,8 @@ type TypeNameQueryResponse struct {
} }
type TypeNameQueryUser struct { type TypeNameQueryUser struct {
Typename string `json:"__typename"` Typename string `json:"__typename"`
Id string `json:"id"` Id mypkg.ID `json:"id"`
} }
func TypeNameQuery( func TypeNameQuery(
+3
View File
@@ -1,3 +1,5 @@
scalar DateTime
enum Role { enum Role {
STUDENT STUDENT
TEACHER TEACHER
@@ -60,6 +62,7 @@ type Query {
user(query: UserQueryInput): User user(query: UserQueryInput): User
root: Topic! root: Topic!
randomLeaf: LeafContent! randomLeaf: LeafContent!
convert(dt: DateTime!, tz: String): DateTime!
} }
type Mutation { type Mutation {
+3 -2
View File
@@ -4,6 +4,7 @@ package test
import ( import (
"github.com/Khan/genqlient/graphql" "github.com/Khan/genqlient/graphql"
"github.com/me/mypkg"
) )
type Role string type Role string
@@ -18,13 +19,13 @@ type unexportedResponse struct {
} }
type unexportedUser struct { type unexportedUser struct {
Id string `json:"id"` Id mypkg.ID `json:"id"`
} }
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 mypkg.ID `json:"id"`
Role Role `json:"role"` Role Role `json:"role"`
Names []string `json:"names"` Names []string `json:"names"`
} }
+7 -4
View File
@@ -62,8 +62,12 @@ var builtinTypes = map[string]string{
} }
func (g *generator) addTypeForDefinition(namePrefix, nameOverride string, typ *ast.Definition, fields []field) (name string, err error) { func (g *generator) addTypeForDefinition(namePrefix, nameOverride string, typ *ast.Definition, fields []field) (name string, err error) {
// If this is a builtin type, just refer to it. // If this is a builtin type or custom scalar, just refer to it.
goName, ok := builtinTypes[typ.Name] goName, ok := g.Config.Scalars[typ.Name]
if ok {
return g.addRef(goName)
}
goName, ok = builtinTypes[typ.Name]
if ok { if ok {
return goName, nil return goName, nil
} }
@@ -314,8 +318,7 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field
builder.WriteString(")\n") builder.WriteString(")\n")
return nil return nil
case ast.Scalar: case ast.Scalar:
// TODO(benkraft): Handle custom scalars. return fmt.Errorf("unknown scalar %v: please add it to genqlient.yaml", typedef.Name)
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)
} }
+6 -4
View File
@@ -1,7 +1,5 @@
package generate package generate
var unmarshalTemplate = mustTemplate("unmarshal.go.tmpl")
type templateData struct { type templateData struct {
// Go type to which the method will be added // Go type to which the method will be added
Type string Type string
@@ -47,7 +45,11 @@ func (builder *typeBuilder) maybeWriteUnmarshal(fields []field) error {
return nil return nil
} }
builder.ImportJSON = true _, err := builder.addRef("encoding/json.Unmarshal")
if err != nil {
return err
}
builder.WriteString("\n\n") builder.WriteString("\n\n")
return unmarshalTemplate.Execute(builder, data) return builder.execute("unmarshal.go.tmpl", builder, data)
} }
+4 -4
View File
@@ -2,19 +2,19 @@ func (v *{{.Type}}) UnmarshalJSON(b []byte) error {
var firstPass struct{ var firstPass struct{
*{{.Type}} *{{.Type}}
{{range .Fields -}} {{range .Fields -}}
{{.GoName}} json.RawMessage `json:"{{.JSONName}}"` {{.GoName}} {{ref "encoding/json.RawMessage"}} `json:"{{.JSONName}}"`
{{end}} {{end}}
} }
firstPass.{{.Type}} = v firstPass.{{.Type}} = v
err := json.Unmarshal(b, &firstPass) err := {{ref "encoding/json.Unmarshal"}}(b, &firstPass)
if err != nil { if err != nil {
return err return err
} }
{{range .Fields -}} {{range .Fields -}}
var tn struct { TypeName string `json:"__typename"` } var tn struct { TypeName string `json:"__typename"` }
err = json.Unmarshal(firstPass.{{.GoName}}, &tn) err = {{ref "encoding/json.Unmarshal"}}(firstPass.{{.GoName}}, &tn)
if err != nil { if err != nil {
return err return err
} }
@@ -24,7 +24,7 @@ func (v *{{.Type}}) UnmarshalJSON(b []byte) error {
case "{{.GraphQLName}}": case "{{.GraphQLName}}":
{{/* TODO: handle repeated fields! */}} {{/* TODO: handle repeated fields! */}}
v.{{$field.GoName}} = {{.GoName}}{} v.{{$field.GoName}} = {{.GoName}}{}
err = json.Unmarshal( err = {{ref "encoding/json.Unmarshal"}}(
firstPass.{{$field.GoName}}, &v.{{$field.GoName}}) firstPass.{{$field.GoName}}, &v.{{$field.GoName}})
{{end}} {{end}}
{{end}} {{end}}
+1
View File
@@ -4,5 +4,6 @@ go 1.13
require ( require (
github.com/vektah/gqlparser/v2 v2.1.0 github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6
gopkg.in/yaml.v2 v2.2.4 gopkg.in/yaml.v2 v2.2.4
) )
+1
View File
@@ -20,6 +20,7 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns= github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=