redo type naming

This commit is contained in:
Ben Kraft
2021-03-19 18:40:58 -07:00
parent 58f4aff83b
commit fed38e4f55
20 changed files with 210 additions and 164 deletions
+5 -2
View File
@@ -53,7 +53,9 @@ For a complete working example, see `example/`.
`go test ./...` tests code generation. (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. Most of the tests are snapshot-based; they use the schema, queries, and snapshots in `generate/testdata`. The schema is in `schema.graphql`; the queries are in `TestName.graphql`. The test by default asserts that the output of the generator matches the snapshot `TestName.graphql.go`. To update the snapshots, run with `UPDATE_SNAPSHOTS=1`; it will fail the tests and print the diffs, but regenerate the snapshots. Make sure to check that the output is sensible!
`make example` rebuilds the example, and tests that everything wires up to a real API correctly.
TODO(benkraft): Figure out how to get GitHub Actions to run the example -- it needs a token. TODO(benkraft): Figure out how to get GitHub Actions to run the example -- it needs a token.
@@ -67,7 +69,6 @@ See [DESIGN.md](DESIGN.md) for documentation of major design decisions in this l
(+) denotes things we further need before recommending anyone else use this in prod (+) denotes things we further need before recommending anyone else use this in prod
Generated code: Generated code:
- (*) update type naming for new scheme
- (*) remove pointers for optionality (or put behind flag) - (*) remove pointers for optionality (or put behind flag)
- redo support for interfaces, unions, fragments (see DESIGN) - redo support for interfaces, unions, fragments (see DESIGN)
- (optional) collapsing -- should be able to have `mutation { myMutation { error { code } } }` just return `(code string, err error)` - (optional) collapsing -- should be able to have `mutation { myMutation { error { code } } }` just return `(code string, err error)`
@@ -77,6 +78,7 @@ Config options:
- (+) fix up context/client wiring (see DESIGN) - (+) fix up context/client wiring (see DESIGN)
- get schema via HTTP (perhaps even via GraphQL introspection) - get schema via HTTP (perhaps even via GraphQL introspection)
- send hash rather than full query - send hash rather than full query
- whether names should be exported
Other: Other:
- (*) error-checking/validation/etc. everywhere - (*) error-checking/validation/etc. everywhere
@@ -84,3 +86,4 @@ Other:
- (+) more tests - (+) more tests
- (+) documentation - (+) documentation
- custom scalar types - custom scalar types
- allow mapping a custom type to a particular val (if you want to use a named type for some string, say)
+11 -11
View File
@@ -8,24 +8,24 @@ import (
"github.com/Khan/genql/graphql" "github.com/Khan/genql/graphql"
) )
type User struct { type GetUserResponse struct {
MyName *string User *GetUserUser `json:"user"`
} }
type User1 struct { type GetUserUser struct {
TheirName *string `json:"theirName"` TheirName *string `json:"theirName"`
} }
type getUserResponse struct { type GetViewerResponse struct {
User *User1 `json:"user"` Viewer GetViewerViewerUser `json:"viewer"`
} }
type getViewerResponse struct { type GetViewerViewerUser struct {
Viewer User `json:"viewer"` MyName *string
} }
func getViewer(ctx context.Context, client *graphql.Client) (*getViewerResponse, error) { func getViewer(ctx context.Context, client *graphql.Client) (*GetViewerResponse, error) {
var retval getViewerResponse var retval GetViewerResponse
err := client.MakeRequest(ctx, ` err := client.MakeRequest(ctx, `
query getViewer { query getViewer {
viewer { viewer {
@@ -37,12 +37,12 @@ query getViewer {
} }
// getUser gets the given user's name from their username. // getUser gets the given user's name from their username.
func getUser(ctx context.Context, client *graphql.Client, login string) (*getUserResponse, error) { func getUser(ctx context.Context, client *graphql.Client, login string) (*GetUserResponse, error) {
variables := map[string]interface{}{ variables := map[string]interface{}{
"Login": login, "Login": login,
} }
var retval getUserResponse var retval GetUserResponse
err := client.MakeRequest(ctx, ` err := client.MakeRequest(ctx, `
query getUser ($Login: String!) { query getUser ($Login: String!) {
user(login: $Login) { user(login: $Login) {
+3 -7
View File
@@ -35,12 +35,6 @@ func gofmt(src string) (string, error) {
} }
func TestGenerate(t *testing.T) { func TestGenerate(t *testing.T) {
// This test uses the schema, queries, and expected-output in ./testdata.
// The schema is in schema.graphql. The queries are in TestName.graphql;
// the test asserts that the output of the generator for that query is
// matches TestName.graphql.go. To update the expected output, run the
// tests with UPDATE_SNAPSHOTS=1 (they will still fail, but also do the
// update, so you can see which updates were made).
update := (os.Getenv("UPDATE_SNAPSHOTS") == "1") update := (os.Getenv("UPDATE_SNAPSHOTS") == "1")
files, err := ioutil.ReadDir(dataDir) files, err := ioutil.ReadDir(dataDir)
@@ -74,12 +68,14 @@ func TestGenerate(t *testing.T) {
t.Errorf("got:\n%v\nwant:\n%v\n", string(goCode), expectedGoCode) t.Errorf("got:\n%v\nwant:\n%v\n", string(goCode), expectedGoCode)
if update { if update {
t.Log("Updating testdata dir to match") t.Log("Updating testdata dir to match")
err = ioutil.WriteFile(filepath.Join(dataDir, goFilename), goCode, 0644) err = ioutil.WriteFile(filepath.Join(dataDir, goFilename), goCode, 0o644)
if err != nil { if err != nil {
t.Errorf("Unable to update testdata dir: %v", err) t.Errorf("Unable to update testdata dir: %v", err)
} }
} }
} }
// TODO(benkraft): Also check that the code at least builds!
}) })
} }
} }
+14 -14
View File
@@ -9,28 +9,28 @@ import (
) )
type InputObjectQueryResponse struct { type InputObjectQueryResponse struct {
User *User `json:"user"` User *InputObjectQueryUser `json:"user"`
} }
type Role string type InputObjectQueryUser struct {
const (
StudentRole Role = "STUDENT"
TeacherRole Role = "TEACHER"
)
type User struct {
Id string `json:"id"` Id string `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 *string `json:"id"`
Role *Role `json:"role"` Role *UserQueryInputRole `json:"role"`
Names []*string `json:"names"` Names []*string `json:"names"`
} }
type UserQueryInputRole string
const (
UserQueryInputRoleStudent UserQueryInputRole = "STUDENT"
UserQueryInputRoleTeacher UserQueryInputRole = "TEACHER"
)
func InputObjectQuery(client *graphql.Client, query *UserQueryInput) (*InputObjectQueryResponse, error) { func InputObjectQuery(client *graphql.Client, query *UserQueryInput) (*InputObjectQueryResponse, error) {
variables := map[string]interface{}{ variables := map[string]interface{}{
"query": query, "query": query,
+39 -25
View File
@@ -8,35 +8,24 @@ import (
"github.com/Khan/genql/graphql" "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 { type InterfaceNoFragmentsQueryResponse struct {
Root Topic `json:"root"` Root InterfaceNoFragmentsQueryRootTopic `json:"root"`
} }
type Topic struct { type InterfaceNoFragmentsQueryRootTopic struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Children []Content `json:"-"` Children []InterfaceNoFragmentsQueryRootTopicChildrenContent `json:"-"`
} }
func (v *Topic) UnmarshalJSON(b []byte) error { func (v *InterfaceNoFragmentsQueryRootTopic) UnmarshalJSON(b []byte) error {
var firstPass struct { var firstPass struct {
*Topic *InterfaceNoFragmentsQueryRootTopic
Children json.RawMessage `json:"children"` Children json.RawMessage `json:"children"`
} }
firstPass.Topic = v firstPass.InterfaceNoFragmentsQueryRootTopic = v
err := json.Unmarshal(b, &typenames) err := json.Unmarshal(b, &firstPass)
if err != nil { if err != nil {
return err return err
} }
@@ -51,17 +40,20 @@ func (v *Topic) UnmarshalJSON(b []byte) error {
switch tn.TypeName { switch tn.TypeName {
case "Article": case "Article":
v.Children = Article{}
v.Children = InterfaceNoFragmentsQueryRootTopicChildrenArticle{}
err = json.Unmarshal( err = json.Unmarshal(
firstPass.Children, &v.Children) firstPass.Children, &v.Children)
case "Video": case "Video":
v.Children = Video{}
v.Children = InterfaceNoFragmentsQueryRootTopicChildrenVideo{}
err = json.Unmarshal( err = json.Unmarshal(
firstPass.Children, &v.Children) firstPass.Children, &v.Children)
case "Topic": case "Topic":
v.Children = Topic{}
v.Children = InterfaceNoFragmentsQueryRootTopicChildrenTopic{}
err = json.Unmarshal( err = json.Unmarshal(
firstPass.Children, &v.Children) firstPass.Children, &v.Children)
@@ -70,14 +62,36 @@ func (v *Topic) UnmarshalJSON(b []byte) error {
return err return err
} }
return nil
} }
type Video struct { type InterfaceNoFragmentsQueryRootTopicChildrenArticle struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} }
func (v Video) implementsGraphQLInterfaceContent() {} func (v InterfaceNoFragmentsQueryRootTopicChildrenArticle) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
}
type InterfaceNoFragmentsQueryRootTopicChildrenContent interface {
implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent()
}
type InterfaceNoFragmentsQueryRootTopicChildrenTopic struct {
Id string `json:"id"`
Name string `json:"name"`
}
func (v InterfaceNoFragmentsQueryRootTopicChildrenTopic) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
}
type InterfaceNoFragmentsQueryRootTopicChildrenVideo struct {
Id string `json:"id"`
Name string `json:"name"`
}
func (v InterfaceNoFragmentsQueryRootTopicChildrenVideo) implementsGraphQLInterfaceInterfaceNoFragmentsQueryRootTopicChildrenContent() {
}
func InterfaceNoFragmentsQuery(client *graphql.Client) (*InterfaceNoFragmentsQueryResponse, error) { func InterfaceNoFragmentsQuery(client *graphql.Client) (*InterfaceNoFragmentsQueryResponse, error) {
var retval InterfaceNoFragmentsQueryResponse var retval InterfaceNoFragmentsQueryResponse
+2 -2
View File
@@ -9,10 +9,10 @@ import (
) )
type ListInputQueryResponse struct { type ListInputQueryResponse struct {
User *User `json:"user"` User *ListInputQueryUser `json:"user"`
} }
type User struct { type ListInputQueryUser struct {
Id string `json:"id"` Id string `json:"id"`
} }
+2 -2
View File
@@ -9,10 +9,10 @@ import (
) )
type QueryWithAliasResponse struct { type QueryWithAliasResponse struct {
User *User User *QueryWithAliasUser
} }
type User struct { type QueryWithAliasUser struct {
ID string ID string
} }
+2 -2
View File
@@ -9,10 +9,10 @@ import (
) )
type QueryWithDoubleAliasResponse struct { type QueryWithDoubleAliasResponse struct {
User *User `json:"user"` User *QueryWithDoubleAliasUser `json:"user"`
} }
type User struct { type QueryWithDoubleAliasUser struct {
ID string ID string
AlsoID string AlsoID string
} }
+8 -8
View File
@@ -9,20 +9,20 @@ import (
) )
type QueryWithEnumsResponse struct { type QueryWithEnumsResponse struct {
User *User `json:"user"` User *QueryWithEnumsUser `json:"user"`
} }
type Role string type QueryWithEnumsUser struct {
Roles []QueryWithEnumsUserRolesRole `json:"roles"`
}
type QueryWithEnumsUserRolesRole string
const ( const (
StudentRole Role = "STUDENT" QueryWithEnumsUserRolesRoleStudent QueryWithEnumsUserRolesRole = "STUDENT"
TeacherRole Role = "TEACHER" QueryWithEnumsUserRolesRoleTeacher QueryWithEnumsUserRolesRole = "TEACHER"
) )
type User struct {
Roles []Role `json:"roles"`
}
func QueryWithEnums(client *graphql.Client) (*QueryWithEnumsResponse, error) { func QueryWithEnums(client *graphql.Client) (*QueryWithEnumsResponse, error) {
var retval QueryWithEnumsResponse var retval QueryWithEnumsResponse
err := client.MakeRequest(context.Background(), ` err := client.MakeRequest(context.Background(), `
+2 -2
View File
@@ -9,10 +9,10 @@ import (
) )
type QueryWithSlicesResponse struct { type QueryWithSlicesResponse struct {
User *User `json:"user"` User *QueryWithSlicesUser `json:"user"`
} }
type User struct { type QueryWithSlicesUser struct {
Emails []string `json:"emails"` Emails []string `json:"emails"`
EmailsOrNull []string `json:"emailsOrNull"` EmailsOrNull []string `json:"emailsOrNull"`
EmailsWithNulls []*string `json:"emailsWithNulls"` EmailsWithNulls []*string `json:"emailsWithNulls"`
+9 -9
View File
@@ -8,19 +8,19 @@ import (
"github.com/Khan/genql/graphql" "github.com/Khan/genql/graphql"
) )
type AuthMethod struct { type QueryWithStructsResponse struct {
User *QueryWithStructsUser `json:"user"`
}
type QueryWithStructsUser struct {
AuthMethods []QueryWithStructsUserAuthMethodsAuthMethod `json:"authMethods"`
}
type QueryWithStructsUserAuthMethodsAuthMethod struct {
Provider *string `json:"provider"` Provider *string `json:"provider"`
Email *string `json:"email"` 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) { func QueryWithStructs(client *graphql.Client) (*QueryWithStructsResponse, error) {
var retval QueryWithStructsResponse var retval QueryWithStructsResponse
err := client.MakeRequest(context.Background(), ` err := client.MakeRequest(context.Background(), `
+2 -2
View File
@@ -9,10 +9,10 @@ import (
) )
type SimpleInputQueryResponse struct { type SimpleInputQueryResponse struct {
User *User `json:"user"` User *SimpleInputQueryUser `json:"user"`
} }
type User struct { type SimpleInputQueryUser struct {
Id string `json:"id"` Id string `json:"id"`
} }
+2 -2
View File
@@ -9,10 +9,10 @@ import (
) )
type SimpleQueryResponse struct { type SimpleQueryResponse struct {
User *User `json:"user"` User *SimpleQueryUser `json:"user"`
} }
type User struct { type SimpleQueryUser struct {
Id string `json:"id"` Id string `json:"id"`
} }
+2 -2
View File
@@ -9,10 +9,10 @@ import (
) )
type TypeNameQueryResponse struct { type TypeNameQueryResponse struct {
User *User `json:"user"` User *TypeNameQueryUser `json:"user"`
} }
type User struct { type TypeNameQueryUser struct {
Typename *string `json:"__typename"` Typename *string `json:"__typename"`
Id string `json:"id"` Id string `json:"id"`
} }
+19 -14
View File
@@ -8,18 +8,26 @@ import (
"github.com/Khan/genql/graphql" "github.com/Khan/genql/graphql"
) )
type Article struct { type UnionNoFragmentsQueryRandomLeafArticle struct {
Typename *string `json:"__typename"` Typename *string `json:"__typename"`
} }
func (v Article) implementsGraphQLInterfaceLeafContent() {} func (v UnionNoFragmentsQueryRandomLeafArticle) implementsGraphQLInterfaceUnionNoFragmentsQueryRandomLeafLeafContent() {
}
type LeafContent interface { type UnionNoFragmentsQueryRandomLeafLeafContent interface {
implementsGraphQLInterfaceLeafContent() implementsGraphQLInterfaceUnionNoFragmentsQueryRandomLeafLeafContent()
}
type UnionNoFragmentsQueryRandomLeafVideo struct {
Typename *string `json:"__typename"`
}
func (v UnionNoFragmentsQueryRandomLeafVideo) implementsGraphQLInterfaceUnionNoFragmentsQueryRandomLeafLeafContent() {
} }
type UnionNoFragmentsQueryResponse struct { type UnionNoFragmentsQueryResponse struct {
RandomLeaf LeafContent `json:"-"` RandomLeaf UnionNoFragmentsQueryRandomLeafLeafContent `json:"-"`
} }
func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error { func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
@@ -29,7 +37,7 @@ func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
} }
firstPass.UnionNoFragmentsQueryResponse = v firstPass.UnionNoFragmentsQueryResponse = v
err := json.Unmarshal(b, &typenames) err := json.Unmarshal(b, &firstPass)
if err != nil { if err != nil {
return err return err
} }
@@ -44,12 +52,14 @@ func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
switch tn.TypeName { switch tn.TypeName {
case "Article": case "Article":
v.RandomLeaf = Article{}
v.RandomLeaf = UnionNoFragmentsQueryRandomLeafArticle{}
err = json.Unmarshal( err = json.Unmarshal(
firstPass.RandomLeaf, &v.RandomLeaf) firstPass.RandomLeaf, &v.RandomLeaf)
case "Video": case "Video":
v.RandomLeaf = Video{}
v.RandomLeaf = UnionNoFragmentsQueryRandomLeafVideo{}
err = json.Unmarshal( err = json.Unmarshal(
firstPass.RandomLeaf, &v.RandomLeaf) firstPass.RandomLeaf, &v.RandomLeaf)
@@ -58,14 +68,9 @@ func (v *UnionNoFragmentsQueryResponse) UnmarshalJSON(b []byte) error {
return err return err
} }
return nil
} }
type Video struct {
Typename *string `json:"__typename"`
}
func (v Video) implementsGraphQLInterfaceLeafContent() {}
func UnionNoFragmentsQuery(client *graphql.Client) (*UnionNoFragmentsQueryResponse, error) { func UnionNoFragmentsQuery(client *graphql.Client) (*UnionNoFragmentsQueryResponse, error) {
var retval UnionNoFragmentsQueryResponse var retval UnionNoFragmentsQueryResponse
err := client.MakeRequest(context.Background(), ` err := client.MakeRequest(context.Background(), `
+20 -20
View File
@@ -8,31 +8,31 @@ import (
"github.com/Khan/genql/graphql" "github.com/Khan/genql/graphql"
) )
type Role string type UsesEnumTwiceQueryMeUser struct {
Roles []UsesEnumTwiceQueryMeUserRolesRole `json:"roles"`
const (
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 { type UsesEnumTwiceQueryMeUserRolesRole string
Roles []Role1 `json:"roles"`
const (
UsesEnumTwiceQueryMeUserRolesRoleStudent UsesEnumTwiceQueryMeUserRolesRole = "STUDENT"
UsesEnumTwiceQueryMeUserRolesRoleTeacher UsesEnumTwiceQueryMeUserRolesRole = "TEACHER"
)
type UsesEnumTwiceQueryOtherUser struct {
Roles []UsesEnumTwiceQueryOtherUserRolesRole `json:"roles"`
} }
type UsesEnumTwiceQueryOtherUserRolesRole string
const (
UsesEnumTwiceQueryOtherUserRolesRoleStudent UsesEnumTwiceQueryOtherUserRolesRole = "STUDENT"
UsesEnumTwiceQueryOtherUserRolesRoleTeacher UsesEnumTwiceQueryOtherUserRolesRole = "TEACHER"
)
type UsesEnumTwiceQueryResponse struct { type UsesEnumTwiceQueryResponse struct {
Me *User Me *UsesEnumTwiceQueryMeUser
OtherUser *User1 OtherUser *UsesEnumTwiceQueryOtherUser
} }
func UsesEnumTwiceQuery(client *graphql.Client) (*UsesEnumTwiceQueryResponse, error) { func UsesEnumTwiceQuery(client *graphql.Client) (*UsesEnumTwiceQueryResponse, error) {
+61 -37
View File
@@ -2,14 +2,14 @@ package generate
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/vektah/gqlparser/ast" "github.com/vektah/gqlparser/ast"
) )
type typeBuilder struct { type typeBuilder struct {
typeName string typeName string
typeNamePrefix string
strings.Builder strings.Builder
*generator *generator
} }
@@ -29,7 +29,8 @@ func (g *generator) baseTypeForOperation(operation ast.Operation) *ast.Definitio
func (g *generator) getTypeForOperation(operation *ast.OperationDefinition) (name string, err error) { func (g *generator) getTypeForOperation(operation *ast.OperationDefinition) (name string, err error) {
// TODO: configure ResponseName format // TODO: configure ResponseName format
name = operation.Name + "Response" namePrefix := upperFirst(operation.Name)
name = namePrefix + "Response"
if def, ok := g.typeMap[name]; ok { if def, ok := g.typeMap[name]; ok {
// TODO: check for and handle conflicts a better way // TODO: check for and handle conflicts a better way
@@ -42,45 +43,68 @@ func (g *generator) getTypeForOperation(operation *ast.OperationDefinition) (nam
} }
return g.addTypeForDefinition( return g.addTypeForDefinition(
name, g.baseTypeForOperation(operation.Operation), fields) namePrefix, name, g.baseTypeForOperation(operation.Operation), fields)
} }
func (g *generator) addTypeForDefinition(nameOverride string, typ *ast.Definition, fields []field) (name string, err error) { var builtinTypes = map[string]string{
"Int": "int", // TODO: technically int32 is always enough, use that?
"Float": "float64",
"String": "string",
"Boolean": "bool",
"ID": "string", // TODO: named type for IDs?
}
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.
goName, ok := builtinTypes[typ.Name] goName, ok := builtinTypes[typ.Name]
if ok { if ok {
return goName, nil return goName, nil
} }
if nameOverride != "" { if nameOverride != "" {
// if we have an explicit name, the passed-in prefix is what we
// propagate forward
name = nameOverride name = nameOverride
} else { } else {
// TODO: casing should be configurable typeGoName := upperFirst(typ.Name)
name = upperFirst(typ.Name) if strings.HasSuffix(namePrefix, typeGoName) {
// If the field and type names are the same, we can avoid the
// duplication. (We include the field name in case there are
// multiple fields with the same type, and the type name because
// that's the actual name (the rest are really qualifiers); but if
// they are the same then including it once suffices for both
// purposes.)
name = namePrefix
} else {
name = namePrefix + typeGoName
}
if typ.Kind != ast.Interface && typ.Kind != ast.Union {
// for interface/union types, we do not add the type name to the
// name prefix; we want to have QueryFieldType rather than
// QueryFieldInterfaceType. Otherwise, the name will also be the
// prefix for the next type.
namePrefix = name
}
} }
// TODO: in some cases we can deduplicate, do that // Otherwise, build the type, put that in the type-map, and return its
// TODO: nicer naming scheme // name.
i := 0 builder := &typeBuilder{typeName: name, typeNamePrefix: namePrefix, generator: g}
origName := name
for g.typeMap[name] != "" {
i++
name = origName + strconv.Itoa(i)
}
builder := &typeBuilder{typeName: name, generator: g}
fmt.Fprintf(builder, "type %s ", name) fmt.Fprintf(builder, "type %s ", name)
err = builder.writeTypedef(typ, fields) err = builder.writeTypedef(typ, fields)
if err != nil { if err != nil {
return "", err return "", err
} }
g.typeMap[name] = builder.String() g.typeMap[name] = builder.String()
return name, nil return name, nil
} }
func (g *generator) getTypeForInputType(typ *ast.Type) (string, error) { func (g *generator) getTypeForInputType(typ *ast.Type) (string, error) {
builder := &typeBuilder{typeName: upperFirst(typ.Name()), generator: g} typeName := upperFirst(typ.Name())
err := builder.writeType(typ, selectionsForType(g, typ)) builder := &typeBuilder{typeName: typeName, typeNamePrefix: typeName, generator: g}
err := builder.writeType("", typ, selectionsForType(g, typ))
return builder.String(), err return builder.String(), err
} }
@@ -167,7 +191,17 @@ func (builder *typeBuilder) writeField(field field) error {
return err return err
} }
err = builder.writeType(typ, fields) err = builder.writeType(
// Note we don't deduplicate here -- if our prefix is GetUser and the
// field name is User, we do GetUserUser. This is important because if
// you have a field called user on a type called User we need
// `query q { user { user { id } } }` to generate two types, QUser and
// QUserUser.
// Note also this is the alias, not the field-name, because if we have
// `query q { a: f { b }, c: f { d } }` we need separate types for a
// and c, even though they are the same type in GraphQL, because they
// have different fields.
builder.typeNamePrefix+upperFirst(field.Alias()), typ, fields)
if err != nil { if err != nil {
return err return err
} }
@@ -182,20 +216,10 @@ func (builder *typeBuilder) writeField(field field) error {
return nil return nil
} }
var builtinTypes = map[string]string{ func (builder *typeBuilder) writeType(namePrefix string, typ *ast.Type, fields []field) error {
"Int": "int", // TODO: technically int32 is always enough, use that? // gqlgen does slightly different things here, but its implementation may
"Float": "float64", // be useful to crib from:
"String": "string",
"Boolean": "bool",
"ID": "string", // TODO: named type for IDs?
}
func (builder *typeBuilder) writeType(typ *ast.Type, fields []field) 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:
// https://github.com/99designs/gqlgen/blob/master/plugin/modelgen/models.go#L113 // https://github.com/99designs/gqlgen/blob/master/plugin/modelgen/models.go#L113
// TODO: or maybe we should do that?
if typ.Elem != nil { if typ.Elem != nil {
// Type is a list. // Type is a list.
builder.WriteString("[]") builder.WriteString("[]")
@@ -207,7 +231,7 @@ func (builder *typeBuilder) writeType(typ *ast.Type, fields []field) error {
def := builder.schema.Types[typ.Name()] def := builder.schema.Types[typ.Name()]
// Writes a typedef elsewhere (if not already defined) // Writes a typedef elsewhere (if not already defined)
name, err := builder.addTypeForDefinition("", def, fields) name, err := builder.addTypeForDefinition(namePrefix, "", def, fields)
if err != nil { if err != nil {
return err return err
} }
@@ -244,7 +268,7 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field
// Then, write the implementations. // Then, write the implementations.
// TODO(benkraft): Put a doc-comment somewhere with the list. // TODO(benkraft): Put a doc-comment somewhere with the list.
for _, impldef := range builder.schema.GetPossibleTypes(typedef) { for _, impldef := range builder.schema.GetPossibleTypes(typedef) {
name, err := builder.addTypeForDefinition("", impldef, fields) name, err := builder.addTypeForDefinition(builder.typeNamePrefix, "", impldef, fields)
if err != nil { if err != nil {
return err return err
} }
@@ -263,7 +287,7 @@ func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field
for _, val := range typedef.EnumValues { for _, val := range typedef.EnumValues {
// TODO: casing should be configurable // TODO: casing should be configurable
fmt.Fprintf(builder, "%s %s = \"%s\"\n", fmt.Fprintf(builder, "%s %s = \"%s\"\n",
goConstName(val.Name+"_"+builder.typeName), builder.typeNamePrefix+goConstName(val.Name),
builder.typeName, val.Name) builder.typeName, val.Name)
} }
builder.WriteString(")\n") builder.WriteString(")\n")
+3 -2
View File
@@ -33,8 +33,9 @@ func (builder *typeBuilder) maybeWriteUnmarshal(fields []field) error {
for _, typedef := range builder.schema.GetPossibleTypes(typedef) { for _, typedef := range builder.schema.GetPossibleTypes(typedef) {
fieldInfo.ConcreteTypes = append(fieldInfo.ConcreteTypes, fieldInfo.ConcreteTypes = append(fieldInfo.ConcreteTypes,
concreteType{ concreteType{
// TODO: lies! We might have added "1" or something. // TODO: this is quite fragile (and maybe wrong if the
GoName: upperFirst(typedef.Name), // field name + type name are the same)
GoName: builder.typeNamePrefix + fieldInfo.GoName + upperFirst(typedef.Name),
GraphQLName: typedef.Name, GraphQLName: typedef.Name,
}) })
} }
+3 -1
View File
@@ -7,7 +7,7 @@ func (v *{{.Type}}) UnmarshalJSON(b []byte) error {
} }
firstPass.{{.Type}} = v firstPass.{{.Type}} = v
err := json.Unmarshal(b, &typenames) err := json.Unmarshal(b, &firstPass)
if err != nil { if err != nil {
return err return err
} }
@@ -22,6 +22,7 @@ func (v *{{.Type}}) UnmarshalJSON(b []byte) error {
{{with $field := .}} {{with $field := .}}
{{range $field.ConcreteTypes}} {{range $field.ConcreteTypes}}
case "{{.GraphQLName}}": case "{{.GraphQLName}}":
{{/* TODO: handle repeated fields! */}}
v.{{$field.GoName}} = {{.GoName}}{} v.{{$field.GoName}} = {{.GoName}}{}
err = json.Unmarshal( err = json.Unmarshal(
firstPass.{{$field.GoName}}, &v.{{$field.GoName}}) firstPass.{{$field.GoName}}, &v.{{$field.GoName}})
@@ -32,4 +33,5 @@ func (v *{{.Type}}) UnmarshalJSON(b []byte) error {
return err return err
} }
{{end}} {{end}}
return nil
} }
+1
View File
@@ -25,6 +25,7 @@ func lowerFirst(s string) string {
} }
func upperFirst(s string) string { func upperFirst(s string) string {
// TODO: initialisms
return changeFirst(strings.TrimLeft(s, "_"), unicode.ToUpper) return changeFirst(strings.TrimLeft(s, "_"), unicode.ToUpper)
} }