Allow creating aliases for builtin types, using typename. (#133)
## Summary:
This lets you write code like:
```
query x {
# @genqlient(typename: "MyString")
someStringField
}
```
and genqlient will do
```
typename MyString string
type x struct {
someStringField MyString
}
```
This was not difficult to implement, though it required introducing a
new identifier type. The main difficulty I had was weird test
failures, that it turns out was due to the tests putting a bunch of
fields on the same line, so that the genqlient directive on the
previous line applied to all of them, accidentally. This became a
problem when `typename` suddenly started being respected for builtin
types! I fixed it by just spreading out the queries a bit.
Fixes #130
## Test plan:
make check
Author: csilvers
Reviewers: dnerdy, StevenACoffman, benjaminjkraft
Required Reviewers:
Approved By: StevenACoffman
Checks: ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint, ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint
Pull Request URL: https://github.com/Khan/genqlient/pull/133
This commit is contained in:
@@ -26,6 +26,8 @@ When releasing a new version:
|
|||||||
|
|
||||||
- genqlient now generates getter methods for all fields, even those which do not implement a genqlient-generated interface; this can be useful for callers who wish to define their own interface and have several unrelated genqlient types which have the same fields implement it.
|
- genqlient now generates getter methods for all fields, even those which do not implement a genqlient-generated interface; this can be useful for callers who wish to define their own interface and have several unrelated genqlient types which have the same fields implement it.
|
||||||
- genqlient config now accepts either a single or multiple schema files for the `schema` field.
|
- genqlient config now accepts either a single or multiple schema files for the `schema` field.
|
||||||
|
- The `typename` option can now be used on basic types (string, int, etc) as well as structs; this can be useful to have genqlient define new types like `type Language string` and use that type for specified fields.
|
||||||
|
|
||||||
### Bug fixes:
|
### Bug fixes:
|
||||||
|
|
||||||
- In certain very rare cases involving duplicate fields in fragment spreads, genqlient would generate code that failed to compile due to duplicate methods not getting promoted; genqlient now generates correct types. (See #126 for a more complete description.)
|
- In certain very rare cases involving duplicate fields in fragment spreads, genqlient would generate code that failed to compile due to duplicate methods not getting promoted; genqlient now generates correct types. (See #126 for a more complete description.)
|
||||||
|
|||||||
+7
-5
@@ -76,11 +76,13 @@ client_getter: "github.com/you/yourpkg.GetClient"
|
|||||||
# A map from GraphQL type name to Go fully-qualified type name to override
|
# A map from GraphQL type name to Go fully-qualified type name to override
|
||||||
# the Go type genqlient will use for this GraphQL type.
|
# the Go type genqlient will use for this GraphQL type.
|
||||||
#
|
#
|
||||||
# This is primarily used for custom scalars, or to map builtin scalars to
|
# This is primarily used for custom scalars, or to map builtin scalars
|
||||||
# a nonstandard type. By default, builtin scalars are mapped to the
|
# to a nonstandard type that is defined elsewhere. By default,
|
||||||
# obvious Go types (String and ID to string, Int to int, Float to float64,
|
# builtin scalars are mapped to the obvious Go types (String and ID to
|
||||||
# and Boolean to bool), but this setting will extend or override those
|
# string, Int to int, Float to float64, and Boolean to bool), but this
|
||||||
# mappings.
|
# setting will extend or override those mappings. (See also
|
||||||
|
# @genqlient(typename: ...), which can be used to map builtin scalars
|
||||||
|
# to a nonstandard type that genqlient defines for you.)
|
||||||
#
|
#
|
||||||
# genqlient does not validate these types in any way; they must define
|
# genqlient does not validate these types in any way; they must define
|
||||||
# whatever logic is needed (MarshalJSON/UnmarshalJSON or JSON tags) to
|
# whatever logic is needed (MarshalJSON/UnmarshalJSON or JSON tags) to
|
||||||
|
|||||||
@@ -164,6 +164,10 @@ directive genqlient(
|
|||||||
# you can do that, as long as its UnmarshalJSON method can accept a list
|
# you can do that, as long as its UnmarshalJSON method can accept a list
|
||||||
# of datetimes.)
|
# of datetimes.)
|
||||||
#
|
#
|
||||||
|
# Note that the type you bind to must be defined elsewhere in your code.
|
||||||
|
# If you want genqlient to create the type definition, use "typename"
|
||||||
|
# instead.
|
||||||
|
#
|
||||||
# See bindings in genqlient.yaml for more details; this is effectively to a
|
# See bindings in genqlient.yaml for more details; this is effectively to a
|
||||||
# local version of that global setting and should be used with similar care.
|
# local version of that global setting and should be used with similar care.
|
||||||
# If set to "-", overrides any such global setting and uses a
|
# If set to "-", overrides any such global setting and uses a
|
||||||
@@ -189,6 +193,26 @@ directive genqlient(
|
|||||||
# }
|
# }
|
||||||
# instead of its usual, more verbose type names.
|
# instead of its usual, more verbose type names.
|
||||||
#
|
#
|
||||||
|
# You may also use "typename" on basic types, and Go will create a
|
||||||
|
# type definition for that basic type. For instance:
|
||||||
|
# query MyQuery {
|
||||||
|
# user {
|
||||||
|
# # @genqlient(typename: "NameType")
|
||||||
|
# name
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# will cause gnqlient to generate:
|
||||||
|
# type Resp struct {
|
||||||
|
# User User
|
||||||
|
# }
|
||||||
|
# type NameType string
|
||||||
|
# type User struct {
|
||||||
|
# Name NameType
|
||||||
|
# }
|
||||||
|
# (Compare this to @genqlient(bind: "path/to/pkg.NameType"), which does
|
||||||
|
# something similar but depends on "NameType" being defined in some
|
||||||
|
# other package, rather than having genqlient define it for you.)
|
||||||
|
#
|
||||||
# With great power comes great responsibility: when using typename you'll
|
# With great power comes great responsibility: when using typename you'll
|
||||||
# need to avoid comments; genqlient will complain if you use the same
|
# need to avoid comments; genqlient will complain if you use the same
|
||||||
# type-name in multiple places unless they request the exact same fields, or
|
# type-name in multiple places unless they request the exact same fields, or
|
||||||
|
|||||||
+12
-1
@@ -289,7 +289,7 @@ func (g *generator) convertDefinition(
|
|||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
goBuiltinName, ok := builtinTypes[def.Name]
|
goBuiltinName, ok := builtinTypes[def.Name]
|
||||||
if ok {
|
if ok && options.TypeName == "" {
|
||||||
return &goOpaqueType{GoRef: goBuiltinName, GraphQLName: def.Name}, nil
|
return &goOpaqueType{GoRef: goBuiltinName, GraphQLName: def.Name}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,6 +479,17 @@ func (g *generator) convertDefinition(
|
|||||||
return g.addType(goType, goType.GoName, pos)
|
return g.addType(goType, goType.GoName, pos)
|
||||||
|
|
||||||
case ast.Scalar:
|
case ast.Scalar:
|
||||||
|
if builtinTypes[def.Name] != "" {
|
||||||
|
// In this case, the user asked for a custom Go type-name
|
||||||
|
// for a built-in type, e.g. `type MyString string`.
|
||||||
|
goType := &goTypenameForBuiltinType{
|
||||||
|
GoTypeName: name,
|
||||||
|
GoBuiltinName: builtinTypes[def.Name],
|
||||||
|
GraphQLName: def.Name,
|
||||||
|
}
|
||||||
|
return g.addType(goType, goType.GoTypeName, pos)
|
||||||
|
}
|
||||||
|
|
||||||
// (If you had an entry in bindings, we would have returned it above.)
|
// (If you had an entry in bindings, we would have returned it above.)
|
||||||
return nil, errorf(
|
return nil, errorf(
|
||||||
pos, `unknown scalar %v: please add it to "bindings" in genqlient.yaml`, def.Name)
|
pos, `unknown scalar %v: please add it to "bindings" in genqlient.yaml`, def.Name)
|
||||||
|
|||||||
+7
-2
@@ -1,6 +1,11 @@
|
|||||||
query ConflictingTypeNames {
|
query ConflictingTypeNames {
|
||||||
# @genqlient(typename: "T")
|
# @genqlient(typename: "T")
|
||||||
f { g }
|
f {
|
||||||
|
g
|
||||||
|
}
|
||||||
# @genqlient(typename: "T")
|
# @genqlient(typename: "T")
|
||||||
otherF: f { g h }
|
otherF: f {
|
||||||
|
g
|
||||||
|
h
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-3
@@ -1,10 +1,20 @@
|
|||||||
# @genqlient(typename: "Resp")
|
# @genqlient(typename: "Resp")
|
||||||
query TypeNames {
|
query TypeNames {
|
||||||
# @genqlient(typename: "User")
|
# @genqlient(typename: "User")
|
||||||
user { id name }
|
user {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
# @genqlient(typename: "Item")
|
# @genqlient(typename: "Item")
|
||||||
randomItem { id name }
|
randomItem {
|
||||||
|
id
|
||||||
|
# @genqlient(typename: "NameType")
|
||||||
|
name
|
||||||
|
}
|
||||||
# (ok to reuse the name as long as they match)
|
# (ok to reuse the name as long as they match)
|
||||||
# @genqlient(typename: "User")
|
# @genqlient(typename: "User")
|
||||||
users { id name }
|
users {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-7
@@ -29,7 +29,7 @@ type Item interface {
|
|||||||
// ID is the identifier of the content.
|
// ID is the identifier of the content.
|
||||||
GetId() testutil.ID
|
GetId() testutil.ID
|
||||||
// GetName returns the interface-field "name" from its implementation.
|
// GetName returns the interface-field "name" from its implementation.
|
||||||
GetName() string
|
GetName() NameType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ItemArticle) implementsGraphQLInterfaceItem() {}
|
func (v *ItemArticle) implementsGraphQLInterfaceItem() {}
|
||||||
@@ -109,7 +109,7 @@ type ItemArticle struct {
|
|||||||
Typename string `json:"__typename"`
|
Typename string `json:"__typename"`
|
||||||
// ID is the identifier of the content.
|
// ID is the identifier of the content.
|
||||||
Id testutil.ID `json:"id"`
|
Id testutil.ID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name NameType `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTypename returns ItemArticle.Typename, and is useful for accessing the field via an interface.
|
// GetTypename returns ItemArticle.Typename, and is useful for accessing the field via an interface.
|
||||||
@@ -119,14 +119,14 @@ func (v *ItemArticle) GetTypename() string { return v.Typename }
|
|||||||
func (v *ItemArticle) GetId() testutil.ID { return v.Id }
|
func (v *ItemArticle) GetId() testutil.ID { return v.Id }
|
||||||
|
|
||||||
// GetName returns ItemArticle.Name, and is useful for accessing the field via an interface.
|
// GetName returns ItemArticle.Name, and is useful for accessing the field via an interface.
|
||||||
func (v *ItemArticle) GetName() string { return v.Name }
|
func (v *ItemArticle) GetName() NameType { return v.Name }
|
||||||
|
|
||||||
// ItemTopic includes the requested fields of the GraphQL type Topic.
|
// ItemTopic includes the requested fields of the GraphQL type Topic.
|
||||||
type ItemTopic struct {
|
type ItemTopic struct {
|
||||||
Typename string `json:"__typename"`
|
Typename string `json:"__typename"`
|
||||||
// ID is the identifier of the content.
|
// ID is the identifier of the content.
|
||||||
Id testutil.ID `json:"id"`
|
Id testutil.ID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name NameType `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTypename returns ItemTopic.Typename, and is useful for accessing the field via an interface.
|
// GetTypename returns ItemTopic.Typename, and is useful for accessing the field via an interface.
|
||||||
@@ -136,14 +136,14 @@ func (v *ItemTopic) GetTypename() string { return v.Typename }
|
|||||||
func (v *ItemTopic) GetId() testutil.ID { return v.Id }
|
func (v *ItemTopic) GetId() testutil.ID { return v.Id }
|
||||||
|
|
||||||
// GetName returns ItemTopic.Name, and is useful for accessing the field via an interface.
|
// GetName returns ItemTopic.Name, and is useful for accessing the field via an interface.
|
||||||
func (v *ItemTopic) GetName() string { return v.Name }
|
func (v *ItemTopic) GetName() NameType { return v.Name }
|
||||||
|
|
||||||
// ItemVideo includes the requested fields of the GraphQL type Video.
|
// ItemVideo includes the requested fields of the GraphQL type Video.
|
||||||
type ItemVideo struct {
|
type ItemVideo struct {
|
||||||
Typename string `json:"__typename"`
|
Typename string `json:"__typename"`
|
||||||
// ID is the identifier of the content.
|
// ID is the identifier of the content.
|
||||||
Id testutil.ID `json:"id"`
|
Id testutil.ID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name NameType `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTypename returns ItemVideo.Typename, and is useful for accessing the field via an interface.
|
// GetTypename returns ItemVideo.Typename, and is useful for accessing the field via an interface.
|
||||||
@@ -153,7 +153,9 @@ func (v *ItemVideo) GetTypename() string { return v.Typename }
|
|||||||
func (v *ItemVideo) GetId() testutil.ID { return v.Id }
|
func (v *ItemVideo) GetId() testutil.ID { return v.Id }
|
||||||
|
|
||||||
// GetName returns ItemVideo.Name, and is useful for accessing the field via an interface.
|
// GetName returns ItemVideo.Name, and is useful for accessing the field via an interface.
|
||||||
func (v *ItemVideo) GetName() string { return v.Name }
|
func (v *ItemVideo) GetName() NameType { return v.Name }
|
||||||
|
|
||||||
|
type NameType string
|
||||||
|
|
||||||
// Resp is returned by TypeNames on success.
|
// Resp is returned by TypeNames on success.
|
||||||
type Resp struct {
|
type Resp struct {
|
||||||
|
|||||||
+47
-28
@@ -65,6 +65,14 @@ type (
|
|||||||
GraphQLName string
|
GraphQLName string
|
||||||
Marshaler, Unmarshaler string
|
Marshaler, Unmarshaler string
|
||||||
}
|
}
|
||||||
|
// goTypenameForBuiltinType represents a builtin type that was
|
||||||
|
// given a different name due to a `typename` directive. We
|
||||||
|
// create a type like `type MyString string` for it.
|
||||||
|
goTypenameForBuiltinType struct {
|
||||||
|
GoTypeName string
|
||||||
|
GoBuiltinName string
|
||||||
|
GraphQLName string
|
||||||
|
}
|
||||||
// goSliceType represents the Go type []Elem, used to represent GraphQL
|
// goSliceType represents the Go type []Elem, used to represent GraphQL
|
||||||
// list types.
|
// list types.
|
||||||
goSliceType struct{ Elem goType }
|
goSliceType struct{ Elem goType }
|
||||||
@@ -75,21 +83,29 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Opaque types are defined by the user; pointers and slices need no definition
|
// Opaque types are defined by the user; pointers and slices need no definition
|
||||||
func (typ *goOpaqueType) WriteDefinition(io.Writer, *generator) error { return nil }
|
func (typ *goOpaqueType) WriteDefinition(io.Writer, *generator) error { return nil }
|
||||||
|
|
||||||
|
func (typ *goTypenameForBuiltinType) WriteDefinition(w io.Writer, g *generator) error {
|
||||||
|
fmt.Fprintf(w, "type %s %s", typ.GoTypeName, typ.GoBuiltinName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func (typ *goSliceType) WriteDefinition(io.Writer, *generator) error { return nil }
|
func (typ *goSliceType) WriteDefinition(io.Writer, *generator) error { return nil }
|
||||||
func (typ *goPointerType) WriteDefinition(io.Writer, *generator) error { return nil }
|
func (typ *goPointerType) WriteDefinition(io.Writer, *generator) error { return nil }
|
||||||
|
|
||||||
func (typ *goOpaqueType) Reference() string { return typ.GoRef }
|
func (typ *goOpaqueType) Reference() string { return typ.GoRef }
|
||||||
func (typ *goSliceType) Reference() string { return "[]" + typ.Elem.Reference() }
|
func (typ *goTypenameForBuiltinType) Reference() string { return typ.GoTypeName }
|
||||||
func (typ *goPointerType) Reference() string { return "*" + typ.Elem.Reference() }
|
func (typ *goSliceType) Reference() string { return "[]" + typ.Elem.Reference() }
|
||||||
|
func (typ *goPointerType) Reference() string { return "*" + typ.Elem.Reference() }
|
||||||
|
|
||||||
func (typ *goOpaqueType) SelectionSet() ast.SelectionSet { return nil }
|
func (typ *goOpaqueType) SelectionSet() ast.SelectionSet { return nil }
|
||||||
func (typ *goSliceType) SelectionSet() ast.SelectionSet { return typ.Elem.SelectionSet() }
|
func (typ *goTypenameForBuiltinType) SelectionSet() ast.SelectionSet { return nil }
|
||||||
func (typ *goPointerType) SelectionSet() ast.SelectionSet { return typ.Elem.SelectionSet() }
|
func (typ *goSliceType) SelectionSet() ast.SelectionSet { return typ.Elem.SelectionSet() }
|
||||||
|
func (typ *goPointerType) SelectionSet() ast.SelectionSet { return typ.Elem.SelectionSet() }
|
||||||
|
|
||||||
func (typ *goOpaqueType) GraphQLTypeName() string { return typ.GraphQLName }
|
func (typ *goOpaqueType) GraphQLTypeName() string { return typ.GraphQLName }
|
||||||
func (typ *goSliceType) GraphQLTypeName() string { return typ.Elem.GraphQLTypeName() }
|
func (typ *goTypenameForBuiltinType) GraphQLTypeName() string { return typ.GraphQLName }
|
||||||
func (typ *goPointerType) GraphQLTypeName() string { return typ.Elem.GraphQLTypeName() }
|
func (typ *goSliceType) GraphQLTypeName() string { return typ.Elem.GraphQLTypeName() }
|
||||||
|
func (typ *goPointerType) GraphQLTypeName() string { return typ.Elem.GraphQLTypeName() }
|
||||||
|
|
||||||
// goEnumType represents a Go named-string type used to represent a GraphQL
|
// goEnumType represents a Go named-string type used to represent a GraphQL
|
||||||
// enum. In this case, we generate both the type (`type T string`) and also a
|
// enum. In this case, we generate both the type (`type T string`) and also a
|
||||||
@@ -502,26 +518,29 @@ func (typ *goInterfaceType) Reference() string { return typ.GoName
|
|||||||
func (typ *goInterfaceType) SelectionSet() ast.SelectionSet { return typ.Selection }
|
func (typ *goInterfaceType) SelectionSet() ast.SelectionSet { return typ.Selection }
|
||||||
func (typ *goInterfaceType) GraphQLTypeName() string { return typ.GraphQLName }
|
func (typ *goInterfaceType) GraphQLTypeName() string { return typ.GraphQLName }
|
||||||
|
|
||||||
func (typ *goOpaqueType) Unwrap() goType { return typ }
|
func (typ *goOpaqueType) Unwrap() goType { return typ }
|
||||||
func (typ *goSliceType) Unwrap() goType { return typ.Elem.Unwrap() }
|
func (typ *goTypenameForBuiltinType) Unwrap() goType { return typ }
|
||||||
func (typ *goPointerType) Unwrap() goType { return typ.Elem.Unwrap() }
|
func (typ *goSliceType) Unwrap() goType { return typ.Elem.Unwrap() }
|
||||||
func (typ *goEnumType) Unwrap() goType { return typ }
|
func (typ *goPointerType) Unwrap() goType { return typ.Elem.Unwrap() }
|
||||||
func (typ *goStructType) Unwrap() goType { return typ }
|
func (typ *goEnumType) Unwrap() goType { return typ }
|
||||||
func (typ *goInterfaceType) Unwrap() goType { return typ }
|
func (typ *goStructType) Unwrap() goType { return typ }
|
||||||
|
func (typ *goInterfaceType) Unwrap() goType { return typ }
|
||||||
|
|
||||||
func (typ *goOpaqueType) SliceDepth() int { return 0 }
|
func (typ *goOpaqueType) SliceDepth() int { return 0 }
|
||||||
func (typ *goSliceType) SliceDepth() int { return typ.Elem.SliceDepth() + 1 }
|
func (typ *goTypenameForBuiltinType) SliceDepth() int { return 0 }
|
||||||
func (typ *goPointerType) SliceDepth() int { return 0 }
|
func (typ *goSliceType) SliceDepth() int { return typ.Elem.SliceDepth() + 1 }
|
||||||
func (typ *goEnumType) SliceDepth() int { return 0 }
|
func (typ *goPointerType) SliceDepth() int { return 0 }
|
||||||
func (typ *goStructType) SliceDepth() int { return 0 }
|
func (typ *goEnumType) SliceDepth() int { return 0 }
|
||||||
func (typ *goInterfaceType) SliceDepth() int { return 0 }
|
func (typ *goStructType) SliceDepth() int { return 0 }
|
||||||
|
func (typ *goInterfaceType) SliceDepth() int { return 0 }
|
||||||
|
|
||||||
func (typ *goOpaqueType) IsPointer() bool { return false }
|
func (typ *goOpaqueType) IsPointer() bool { return false }
|
||||||
func (typ *goSliceType) IsPointer() bool { return typ.Elem.IsPointer() }
|
func (typ *goTypenameForBuiltinType) IsPointer() bool { return false }
|
||||||
func (typ *goPointerType) IsPointer() bool { return true }
|
func (typ *goSliceType) IsPointer() bool { return typ.Elem.IsPointer() }
|
||||||
func (typ *goEnumType) IsPointer() bool { return false }
|
func (typ *goPointerType) IsPointer() bool { return true }
|
||||||
func (typ *goStructType) IsPointer() bool { return false }
|
func (typ *goEnumType) IsPointer() bool { return false }
|
||||||
func (typ *goInterfaceType) IsPointer() bool { return false }
|
func (typ *goStructType) IsPointer() bool { return false }
|
||||||
|
func (typ *goInterfaceType) IsPointer() bool { return false }
|
||||||
|
|
||||||
func writeDescription(w io.Writer, desc string) {
|
func writeDescription(w io.Writer, desc string) {
|
||||||
if desc != "" {
|
if desc != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user