redo type naming
This commit is contained in:
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user