add option to force using a pointer
This commit is contained in:
+42
-8
@@ -50,7 +50,31 @@ type GenqlientDirective struct {
|
|||||||
// value otherwise.
|
// value otherwise.
|
||||||
//
|
//
|
||||||
// Only applicable to arguments of nullable types.
|
// Only applicable to arguments of nullable types.
|
||||||
Omitempty bool
|
Omitempty *bool
|
||||||
|
|
||||||
|
// If set, this argument or field will use a pointer type in Go. Response
|
||||||
|
// types always use pointers, but otherwise we typically do not.
|
||||||
|
//
|
||||||
|
// This can be useful if it's a type you'll need to pass around (and want a
|
||||||
|
// pointer to save copies) or if you wish to distinguish between the Go
|
||||||
|
// zero value and null (for nullable fields).
|
||||||
|
Pointer *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GenqlientDirective) GetOmitempty() bool { return g.Omitempty != nil && *g.Omitempty }
|
||||||
|
func (g *GenqlientDirective) GetPointer() bool { return g.Pointer != nil && *g.Pointer }
|
||||||
|
|
||||||
|
func setBool(dst **bool, v *ast.Value) error {
|
||||||
|
ei, err := v.Value(nil) // no vars allowed
|
||||||
|
// TODO: here and below, put positions on these errors
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid boolean value %v: %w", v, err)
|
||||||
|
}
|
||||||
|
if b, ok := ei.(bool); ok {
|
||||||
|
*dst = &b
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("expected boolean, got non-boolean value %T(%v)", ei, ei)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
||||||
@@ -61,13 +85,20 @@ func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var retval GenqlientDirective
|
var retval GenqlientDirective
|
||||||
|
var err error
|
||||||
for _, arg := range dir.Arguments {
|
for _, arg := range dir.Arguments {
|
||||||
switch arg.Name {
|
switch arg.Name {
|
||||||
|
// TODO: reflect and struct tags?
|
||||||
case "omitempty":
|
case "omitempty":
|
||||||
retval.Omitempty = true
|
err = setBool(&retval.Omitempty, arg.Value)
|
||||||
|
case "pointer":
|
||||||
|
err = setBool(&retval.Pointer, arg.Value)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown argument %v for @genqlient", arg.Name)
|
return nil, fmt.Errorf("unknown argument %v for @genqlient", arg.Name)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &retval, nil
|
return &retval, nil
|
||||||
}
|
}
|
||||||
@@ -79,12 +110,12 @@ func (dir *GenqlientDirective) validate(node interface{}) error {
|
|||||||
// whatever it is relevant to.
|
// whatever it is relevant to.
|
||||||
return nil
|
return nil
|
||||||
case *ast.VariableDefinition:
|
case *ast.VariableDefinition:
|
||||||
if dir.Omitempty && node.Type.NonNull {
|
if dir.Omitempty != nil && node.Type.NonNull {
|
||||||
return fmt.Errorf("omitempty may only be used on optional arguments")
|
return fmt.Errorf("omitempty may only be used on optional arguments")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case *ast.Field:
|
case *ast.Field:
|
||||||
if dir.Omitempty {
|
if dir.Omitempty != nil {
|
||||||
return fmt.Errorf("omitempty is not appilcable to fields")
|
return fmt.Errorf("omitempty is not appilcable to fields")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -94,11 +125,13 @@ func (dir *GenqlientDirective) validate(node interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dir *GenqlientDirective) merge(other *GenqlientDirective) *GenqlientDirective {
|
func (dir *GenqlientDirective) merge(other *GenqlientDirective) *GenqlientDirective {
|
||||||
if dir == nil {
|
retval := *dir
|
||||||
return other
|
if other.Omitempty != nil {
|
||||||
|
retval.Omitempty = other.Omitempty
|
||||||
|
}
|
||||||
|
if other.Pointer != nil {
|
||||||
|
retval.Pointer = other.Pointer
|
||||||
}
|
}
|
||||||
var retval GenqlientDirective
|
|
||||||
retval.Omitempty = dir.Omitempty || other.Omitempty
|
|
||||||
return &retval
|
return &retval
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +139,7 @@ func (g *generator) parsePrecedingComment(
|
|||||||
node interface{},
|
node interface{},
|
||||||
pos *ast.Position,
|
pos *ast.Position,
|
||||||
) (comment string, directive *GenqlientDirective, err error) {
|
) (comment string, directive *GenqlientDirective, err error) {
|
||||||
|
directive = new(GenqlientDirective)
|
||||||
var commentLines []string
|
var commentLines []string
|
||||||
sourceLines := strings.Split(pos.Src.Input, "\n")
|
sourceLines := strings.Split(pos.Src.Input, "\n")
|
||||||
for i := pos.Line - 1; i > 0; i-- {
|
for i := pos.Line - 1; i > 0; i-- {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ type argument struct {
|
|||||||
GoName string
|
GoName string
|
||||||
GoType string
|
GoType string
|
||||||
GraphQLName string
|
GraphQLName string
|
||||||
Omitempty bool
|
Options *GenqlientDirective
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGenerator(config *Config, schema *ast.Schema) *generator {
|
func newGenerator(config *Config, schema *ast.Schema) *generator {
|
||||||
@@ -117,14 +117,10 @@ func (g *generator) getArgument(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return argument{}, err
|
return argument{}, err
|
||||||
}
|
}
|
||||||
directive = operationDirective.merge(directive)
|
|
||||||
omitempty := false
|
|
||||||
if directive != nil {
|
|
||||||
omitempty = directive.Omitempty
|
|
||||||
}
|
|
||||||
|
|
||||||
graphQLName := arg.Variable
|
graphQLName := arg.Variable
|
||||||
goType, err := g.getTypeForInputType(opName, arg.Type)
|
goType, err := g.getTypeForInputType(
|
||||||
|
opName, arg.Type, directive, operationDirective)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return argument{}, err
|
return argument{}, err
|
||||||
}
|
}
|
||||||
@@ -132,7 +128,7 @@ func (g *generator) getArgument(
|
|||||||
GraphQLName: graphQLName,
|
GraphQLName: graphQLName,
|
||||||
GoName: lowerFirst(graphQLName),
|
GoName: lowerFirst(graphQLName),
|
||||||
GoType: goType,
|
GoType: goType,
|
||||||
Omitempty: omitempty,
|
Options: directive,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +159,7 @@ func (g *generator) addOperation(op *ast.OperationDefinition) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responseName, err := g.getTypeForOperation(op)
|
responseName, err := g.getTypeForOperation(op, directive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,8 +95,11 @@ func TestGenerate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if string(content) != expectedContent {
|
if string(content) != expectedContent {
|
||||||
t.Errorf("mismatch in %v\ngot:\n%v\nwant:\n%v\n",
|
t.Errorf("mismatch in %v", filename)
|
||||||
filename, string(content), expectedContent)
|
if testing.Verbose() {
|
||||||
|
t.Errorf("got:\n%v\nwant:\n%v\n",
|
||||||
|
string(content), expectedContent)
|
||||||
|
}
|
||||||
if update {
|
if update {
|
||||||
t.Log("Updating testdata dir to match")
|
t.Log("Updating testdata dir to match")
|
||||||
err = ioutil.WriteFile(filepath.Join(dataDir, filename), content, 0o644)
|
err = ioutil.WriteFile(filepath.Join(dataDir, filename), content, 0o644)
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ func {{.Name}}(
|
|||||||
{{- if .Args -}}
|
{{- if .Args -}}
|
||||||
variables := map[string]interface{}{
|
variables := map[string]interface{}{
|
||||||
{{range .Args -}}
|
{{range .Args -}}
|
||||||
"{{.GraphQLName}}": {{if .Omitempty}}nil{{else}}{{.GoName}}{{end}},
|
{{if not .Options.GetOmitempty -}}
|
||||||
|
"{{.GraphQLName}}": {{.GoName}},
|
||||||
|
{{end -}}
|
||||||
{{end}}
|
{{end}}
|
||||||
}
|
}
|
||||||
{{range .Args -}}
|
{{range .Args -}}
|
||||||
{{if .Omitempty -}}
|
{{if .Options.GetOmitempty -}}
|
||||||
{{/* zero_{{.GoType}} would be a better name, but {{.GoType}} would require
|
{{/* zero_{{.GoType}} would be a better name, but {{.GoType}} would require
|
||||||
munging since it might be, say, `time.Time`. */}}
|
munging since it might be, say, `time.Time`. */}}
|
||||||
var zero_{{.GoName}} {{.GoType}}
|
var zero_{{.GoName}} {{.GoType}}
|
||||||
|
|||||||
+3
@@ -3,9 +3,12 @@ query OmitEmptyQuery(
|
|||||||
$query: UserQueryInput,
|
$query: UserQueryInput,
|
||||||
$dt: DateTime,
|
$dt: DateTime,
|
||||||
$tz: String,
|
$tz: String,
|
||||||
|
# @genqlient(omitempty: false)
|
||||||
|
$tzNoOmitEmpty: String,
|
||||||
) {
|
) {
|
||||||
user(query: $query) {
|
user(query: $query) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
maybeConvert(dt: $dt, tz: $tz)
|
maybeConvert(dt: $dt, tz: $tz)
|
||||||
|
convert2: maybeConvert(dt: $dt, tz: $tzNoOmitEmpty)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-19
@@ -12,6 +12,7 @@ import (
|
|||||||
type OmitEmptyQueryResponse struct {
|
type OmitEmptyQueryResponse struct {
|
||||||
User OmitEmptyQueryUser `json:"user"`
|
User OmitEmptyQueryUser `json:"user"`
|
||||||
MaybeConvert time.Time `json:"maybeConvert"`
|
MaybeConvert time.Time `json:"maybeConvert"`
|
||||||
|
Convert2 time.Time `json:"convert2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OmitEmptyQueryUser struct {
|
type OmitEmptyQueryUser struct {
|
||||||
@@ -38,26 +39,13 @@ func OmitEmptyQuery(
|
|||||||
query UserQueryInput,
|
query UserQueryInput,
|
||||||
dt time.Time,
|
dt time.Time,
|
||||||
tz string,
|
tz string,
|
||||||
|
tzNoOmitEmpty string,
|
||||||
) (*OmitEmptyQueryResponse, error) {
|
) (*OmitEmptyQueryResponse, error) {
|
||||||
variables := map[string]interface{}{
|
variables := map[string]interface{}{
|
||||||
"query": nil,
|
"query": query,
|
||||||
"dt": nil,
|
"dt": dt,
|
||||||
"tz": nil,
|
"tz": tz,
|
||||||
}
|
"tzNoOmitEmpty": tzNoOmitEmpty,
|
||||||
|
|
||||||
var zero_query UserQueryInput
|
|
||||||
if query != zero_query {
|
|
||||||
variables["query"] = query
|
|
||||||
}
|
|
||||||
|
|
||||||
var zero_dt time.Time
|
|
||||||
if dt != zero_dt {
|
|
||||||
variables["dt"] = dt
|
|
||||||
}
|
|
||||||
|
|
||||||
var zero_tz string
|
|
||||||
if tz != zero_tz {
|
|
||||||
variables["tz"] = tz
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var retval OmitEmptyQueryResponse
|
var retval OmitEmptyQueryResponse
|
||||||
@@ -65,11 +53,12 @@ func OmitEmptyQuery(
|
|||||||
nil,
|
nil,
|
||||||
"OmitEmptyQuery",
|
"OmitEmptyQuery",
|
||||||
`
|
`
|
||||||
query OmitEmptyQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {
|
query OmitEmptyQuery ($query: UserQueryInput, $dt: DateTime, $tz: String, $tzNoOmitEmpty: String) {
|
||||||
user(query: $query) {
|
user(query: $query) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
maybeConvert(dt: $dt, tz: $tz)
|
maybeConvert(dt: $dt, tz: $tz)
|
||||||
|
convert2: maybeConvert(dt: $dt, tz: $tzNoOmitEmpty)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
&retval,
|
&retval,
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"operationName": "OmitEmptyQuery",
|
"operationName": "OmitEmptyQuery",
|
||||||
"query": "\nquery OmitEmptyQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {\n\tuser(query: $query) {\n\t\tid\n\t}\n\tmaybeConvert(dt: $dt, tz: $tz)\n}\n",
|
"query": "\nquery OmitEmptyQuery ($query: UserQueryInput, $dt: DateTime, $tz: String, $tzNoOmitEmpty: String) {\n\tuser(query: $query) {\n\t\tid\n\t}\n\tmaybeConvert(dt: $dt, tz: $tz)\n\tconvert2: maybeConvert(dt: $dt, tz: $tzNoOmitEmpty)\n}\n",
|
||||||
"sourceLocation": "testdata/queries/Omitempty.graphql"
|
"sourceLocation": "testdata/queries/Omitempty.graphql"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
# @genqlient(pointer: true)
|
||||||
|
query PointersQuery(
|
||||||
|
$query: UserQueryInput,
|
||||||
|
# @genqlient(pointer: false)
|
||||||
|
$dt: DateTime,
|
||||||
|
$tz: String,
|
||||||
|
) {
|
||||||
|
user(query: $query) {
|
||||||
|
id
|
||||||
|
roles
|
||||||
|
name
|
||||||
|
emails
|
||||||
|
# @genqlient(pointer: false)
|
||||||
|
emailsNoPtr: emails
|
||||||
|
}
|
||||||
|
otherUser: user(query: $query) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
maybeConvert(dt: $dt, tz: $tz)
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
// Code generated by github.com/Khan/genqlient, DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Khan/genqlient/graphql"
|
||||||
|
"github.com/me/mypkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PointersQueryOtherUser struct {
|
||||||
|
Id *mypkg.ID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PointersQueryResponse struct {
|
||||||
|
User *PointersQueryUser `json:"user"`
|
||||||
|
OtherUser *PointersQueryOtherUser `json:"otherUser"`
|
||||||
|
MaybeConvert *time.Time `json:"maybeConvert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PointersQueryUser struct {
|
||||||
|
Id *mypkg.ID `json:"id"`
|
||||||
|
Roles []*Role `json:"roles"`
|
||||||
|
Name *string `json:"name"`
|
||||||
|
Emails []*string `json:"emails"`
|
||||||
|
EmailsNoPtr []string `json:"emailsNoPtr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleStudent Role = "STUDENT"
|
||||||
|
RoleTeacher Role = "TEACHER"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserQueryInput struct {
|
||||||
|
Email *string `json:"email"`
|
||||||
|
Name *string `json:"name"`
|
||||||
|
Id *mypkg.ID `json:"id"`
|
||||||
|
Role *Role `json:"role"`
|
||||||
|
Names []*string `json:"names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PointersQuery(
|
||||||
|
client graphql.Client,
|
||||||
|
query UserQueryInput,
|
||||||
|
dt time.Time,
|
||||||
|
tz string,
|
||||||
|
) (*PointersQueryResponse, error) {
|
||||||
|
variables := map[string]interface{}{
|
||||||
|
"query": query,
|
||||||
|
"dt": dt,
|
||||||
|
"tz": tz,
|
||||||
|
}
|
||||||
|
|
||||||
|
var retval PointersQueryResponse
|
||||||
|
err := client.MakeRequest(
|
||||||
|
nil,
|
||||||
|
"PointersQuery",
|
||||||
|
`
|
||||||
|
query PointersQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {
|
||||||
|
user(query: $query) {
|
||||||
|
id
|
||||||
|
roles
|
||||||
|
name
|
||||||
|
emails
|
||||||
|
emailsNoPtr: emails
|
||||||
|
}
|
||||||
|
otherUser: user(query: $query) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
maybeConvert(dt: $dt, tz: $tz)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
&retval,
|
||||||
|
variables,
|
||||||
|
)
|
||||||
|
return &retval, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"operationName": "PointersQuery",
|
||||||
|
"query": "\nquery PointersQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {\n\tuser(query: $query) {\n\t\tid\n\t\troles\n\t\tname\n\t\temails\n\t\temailsNoPtr: emails\n\t}\n\totherUser: user(query: $query) {\n\t\tid\n\t}\n\tmaybeConvert(dt: $dt, tz: $tz)\n}\n",
|
||||||
|
"sourceLocation": "testdata/queries/Pointers.graphql"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
query PointersQuery(
|
||||||
|
# @genqlient(pointer: true)
|
||||||
|
$query: UserQueryInput,
|
||||||
|
# @genqlient(pointer: true)
|
||||||
|
$dt: DateTime,
|
||||||
|
$tz: String,
|
||||||
|
) {
|
||||||
|
# @genqlient(pointer: true)
|
||||||
|
user(query: $query) {
|
||||||
|
id
|
||||||
|
roles
|
||||||
|
# @genqlient(pointer: true)
|
||||||
|
name
|
||||||
|
# @genqlient(pointer: true)
|
||||||
|
emails
|
||||||
|
# @genqlient(pointer: true)
|
||||||
|
emailsNoPtr: emails
|
||||||
|
}
|
||||||
|
# @genqlient(pointer: true)
|
||||||
|
otherUser: user(query: $query) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
maybeConvert(dt: $dt, tz: $tz)
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
// Code generated by github.com/Khan/genqlient, DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Khan/genqlient/graphql"
|
||||||
|
"github.com/me/mypkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PointersQueryOtherUser struct {
|
||||||
|
Id mypkg.ID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PointersQueryResponse struct {
|
||||||
|
User *PointersQueryUser `json:"user"`
|
||||||
|
OtherUser *PointersQueryOtherUser `json:"otherUser"`
|
||||||
|
MaybeConvert time.Time `json:"maybeConvert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PointersQueryUser struct {
|
||||||
|
Id mypkg.ID `json:"id"`
|
||||||
|
Roles []Role `json:"roles"`
|
||||||
|
Name *string `json:"name"`
|
||||||
|
Emails []*string `json:"emails"`
|
||||||
|
EmailsNoPtr []*string `json:"emailsNoPtr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleStudent Role = "STUDENT"
|
||||||
|
RoleTeacher Role = "TEACHER"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserQueryInput struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Id mypkg.ID `json:"id"`
|
||||||
|
Role Role `json:"role"`
|
||||||
|
Names []string `json:"names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PointersQuery(
|
||||||
|
client graphql.Client,
|
||||||
|
query *UserQueryInput,
|
||||||
|
dt *time.Time,
|
||||||
|
tz string,
|
||||||
|
) (*PointersQueryResponse, error) {
|
||||||
|
variables := map[string]interface{}{
|
||||||
|
"query": query,
|
||||||
|
"dt": dt,
|
||||||
|
"tz": tz,
|
||||||
|
}
|
||||||
|
|
||||||
|
var retval PointersQueryResponse
|
||||||
|
err := client.MakeRequest(
|
||||||
|
nil,
|
||||||
|
"PointersQuery",
|
||||||
|
`
|
||||||
|
query PointersQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {
|
||||||
|
user(query: $query) {
|
||||||
|
id
|
||||||
|
roles
|
||||||
|
name
|
||||||
|
emails
|
||||||
|
emailsNoPtr: emails
|
||||||
|
}
|
||||||
|
otherUser: user(query: $query) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
maybeConvert(dt: $dt, tz: $tz)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
&retval,
|
||||||
|
variables,
|
||||||
|
)
|
||||||
|
return &retval, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"operationName": "PointersQuery",
|
||||||
|
"query": "\nquery PointersQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {\n\tuser(query: $query) {\n\t\tid\n\t\troles\n\t\tname\n\t\temails\n\t\temailsNoPtr: emails\n\t}\n\totherUser: user(query: $query) {\n\t\tid\n\t}\n\tmaybeConvert(dt: $dt, tz: $tz)\n}\n",
|
||||||
|
"sourceLocation": "testdata/queries/PointersInline.graphql"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+61
-24
@@ -30,7 +30,7 @@ func (g *generator) baseTypeForOperation(operation ast.Operation) (*ast.Definiti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *generator) getTypeForOperation(operation *ast.OperationDefinition) (name string, err error) {
|
func (g *generator) getTypeForOperation(operation *ast.OperationDefinition, queryOptions *GenqlientDirective) (name string, err error) {
|
||||||
// TODO: configure ResponseName format
|
// TODO: configure ResponseName format
|
||||||
name = operation.Name + "Response"
|
name = operation.Name + "Response"
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ func (g *generator) getTypeForOperation(operation *ast.OperationDefinition) (nam
|
|||||||
return "", fmt.Errorf("%s defined twice:\n%s", name, def)
|
return "", fmt.Errorf("%s defined twice:\n%s", name, def)
|
||||||
}
|
}
|
||||||
|
|
||||||
fields, err := selections(operation.SelectionSet)
|
fields, err := selections(g, operation.SelectionSet, queryOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ func (g *generator) getTypeForOperation(operation *ast.OperationDefinition) (nam
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.addTypeForDefinition(operation.Name, name, baseType, fields)
|
return g.addTypeForDefinition(operation.Name, name, baseType, fields, queryOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
var builtinTypes = map[string]string{
|
var builtinTypes = map[string]string{
|
||||||
@@ -61,7 +61,7 @@ var builtinTypes = map[string]string{
|
|||||||
"ID": "string",
|
"ID": "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, options *GenqlientDirective) (name string, err error) {
|
||||||
// If this is a builtin type or custom scalar, just refer to it.
|
// If this is a builtin type or custom scalar, just refer to it.
|
||||||
goName, ok := g.Config.Scalars[typ.Name]
|
goName, ok := g.Config.Scalars[typ.Name]
|
||||||
if ok {
|
if ok {
|
||||||
@@ -114,7 +114,7 @@ func (g *generator) addTypeForDefinition(namePrefix, nameOverride string, typ *a
|
|||||||
// name.
|
// name.
|
||||||
builder := &typeBuilder{typeName: name, typeNamePrefix: namePrefix, generator: g}
|
builder := &typeBuilder{typeName: name, typeNamePrefix: namePrefix, generator: g}
|
||||||
fmt.Fprintf(builder, "type %s ", name)
|
fmt.Fprintf(builder, "type %s ", name)
|
||||||
err = builder.writeTypedef(typ, fields)
|
err = builder.writeTypedef(typ, fields, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -124,22 +124,36 @@ func (g *generator) addTypeForDefinition(namePrefix, nameOverride string, typ *a
|
|||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *generator) getTypeForInputType(opName string, typ *ast.Type) (string, error) {
|
func (g *generator) getTypeForInputType(opName string, typ *ast.Type, options, queryOptions *GenqlientDirective) (string, error) {
|
||||||
// Sort of a hack: case the input type name to match the op-name.
|
// Sort of a hack: case the input type name to match the op-name.
|
||||||
name := matchFirst(typ.Name(), opName)
|
name := matchFirst(typ.Name(), opName)
|
||||||
// TODO: we have to pass name 4 times, yuck
|
// TODO: we have to pass name 4 times, yuck
|
||||||
builder := &typeBuilder{typeName: name, typeNamePrefix: name, generator: g}
|
builder := &typeBuilder{typeName: name, typeNamePrefix: name, generator: g}
|
||||||
err := builder.writeType(name, name, typ, selectionsForType(g, typ))
|
// TODO: passing options is actually kinda wrong, because it means we could
|
||||||
|
// break the "there is only Go type for each input type" rule. In practice
|
||||||
|
// it's probably rare that you use the same input type twice in a query and
|
||||||
|
// want different settings, though, and it just means we choose one or the
|
||||||
|
// other set of options.
|
||||||
|
// TODO: it's also awkward because you have no way to pass an option for an
|
||||||
|
// individual input-type field.
|
||||||
|
// TODO: should we use pointers by default for input-types if they're
|
||||||
|
// structs?
|
||||||
|
err := builder.writeType(name, name, typ, selectionsForType(g, typ, queryOptions), options)
|
||||||
return builder.String(), err
|
return builder.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
type field interface {
|
type field interface {
|
||||||
Alias() string
|
Alias() string
|
||||||
|
Options() (*GenqlientDirective, error)
|
||||||
Type() *ast.Type
|
Type() *ast.Type
|
||||||
SubFields() ([]field, error)
|
SubFields() ([]field, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type outputField struct{ field *ast.Field }
|
type outputField struct {
|
||||||
|
*generator
|
||||||
|
queryOptions *GenqlientDirective
|
||||||
|
field *ast.Field
|
||||||
|
}
|
||||||
|
|
||||||
func (s outputField) Alias() string {
|
func (s outputField) Alias() string {
|
||||||
// gqlparser sets Alias even if the field is not aliased, see e.g.
|
// gqlparser sets Alias even if the field is not aliased, see e.g.
|
||||||
@@ -147,6 +161,14 @@ func (s outputField) Alias() string {
|
|||||||
return s.field.Alias
|
return s.field.Alias
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s outputField) Options() (*GenqlientDirective, error) {
|
||||||
|
_, directive, err := s.generator.parsePrecedingComment(s.field, s.field.Position)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.queryOptions.merge(directive), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s outputField) Type() *ast.Type {
|
func (s outputField) Type() *ast.Type {
|
||||||
if s.field.Definition == nil {
|
if s.field.Definition == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -155,15 +177,15 @@ func (s outputField) Type() *ast.Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s outputField) SubFields() ([]field, error) {
|
func (s outputField) SubFields() ([]field, error) {
|
||||||
return selections(s.field.SelectionSet)
|
return selections(s.generator, s.field.SelectionSet, s.queryOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selections(selectionSet ast.SelectionSet) ([]field, error) {
|
func selections(g *generator, selectionSet ast.SelectionSet, options *GenqlientDirective) ([]field, error) {
|
||||||
retval := make([]field, len(selectionSet))
|
retval := make([]field, len(selectionSet))
|
||||||
for i, selection := range selectionSet {
|
for i, selection := range selectionSet {
|
||||||
switch selection := selection.(type) {
|
switch selection := selection.(type) {
|
||||||
case *ast.Field:
|
case *ast.Field:
|
||||||
retval[i] = outputField{selection}
|
retval[i] = outputField{g, options, selection}
|
||||||
case *ast.FragmentSpread, *ast.InlineFragment:
|
case *ast.FragmentSpread, *ast.InlineFragment:
|
||||||
return nil, fmt.Errorf("not implemented: %T", selection)
|
return nil, fmt.Errorf("not implemented: %T", selection)
|
||||||
default:
|
default:
|
||||||
@@ -175,21 +197,29 @@ func selections(selectionSet ast.SelectionSet) ([]field, error) {
|
|||||||
|
|
||||||
type inputField struct {
|
type inputField struct {
|
||||||
*generator
|
*generator
|
||||||
field *ast.FieldDefinition
|
field *ast.FieldDefinition
|
||||||
|
queryOptions *GenqlientDirective
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s inputField) Alias() string { return s.field.Name }
|
func (s inputField) Alias() string { return s.field.Name }
|
||||||
|
func (s inputField) Options() (*GenqlientDirective, error) {
|
||||||
|
_, directive, err := s.generator.parsePrecedingComment(s.field, s.field.Position)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.queryOptions.merge(directive), nil
|
||||||
|
}
|
||||||
func (s inputField) Type() *ast.Type { return s.field.Type }
|
func (s inputField) Type() *ast.Type { return s.field.Type }
|
||||||
|
|
||||||
func (s inputField) SubFields() ([]field, error) {
|
func (s inputField) SubFields() ([]field, error) {
|
||||||
return selectionsForType(s.generator, s.field.Type), nil
|
return selectionsForType(s.generator, s.field.Type, s.queryOptions), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectionsForType(g *generator, typ *ast.Type) []field {
|
func selectionsForType(g *generator, typ *ast.Type, queryOptions *GenqlientDirective) []field {
|
||||||
def := g.schema.Types[typ.Name()]
|
def := g.schema.Types[typ.Name()]
|
||||||
fields := make([]field, len(def.Fields))
|
fields := make([]field, len(def.Fields))
|
||||||
for i, field := range def.Fields {
|
for i, field := range def.Fields {
|
||||||
fields[i] = inputField{g, field}
|
fields[i] = inputField{g, field, queryOptions}
|
||||||
}
|
}
|
||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
@@ -214,6 +244,11 @@ func (builder *typeBuilder) writeField(field field) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options, err := field.Options()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = builder.writeType(
|
err = builder.writeType(
|
||||||
// Note we don't deduplicate suffixes here -- if our prefix is GetUser
|
// Note we don't deduplicate suffixes here -- if our prefix is GetUser
|
||||||
// and the field name is User, we do GetUserUser. This is important
|
// and the field name is User, we do GetUserUser. This is important
|
||||||
@@ -224,7 +259,7 @@ func (builder *typeBuilder) writeField(field field) error {
|
|||||||
// `query q { a: f { b }, c: f { d } }` we need separate types for a
|
// `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
|
// and c, even though they are the same type in GraphQL, because they
|
||||||
// have different fields.
|
// have different fields.
|
||||||
builder.typeNamePrefix+upperFirst(field.Alias()), "", typ, fields)
|
builder.typeNamePrefix+upperFirst(field.Alias()), "", typ, fields, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -239,7 +274,7 @@ func (builder *typeBuilder) writeField(field field) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (builder *typeBuilder) writeType(namePrefix, nameOverride string, typ *ast.Type, fields []field) error {
|
func (builder *typeBuilder) writeType(namePrefix, nameOverride string, typ *ast.Type, fields []field, options *GenqlientDirective) error {
|
||||||
// gqlgen does slightly different things here, but its implementation may
|
// gqlgen does slightly different things here, but its implementation may
|
||||||
// be useful to crib from:
|
// 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
|
||||||
@@ -248,13 +283,15 @@ func (builder *typeBuilder) writeType(namePrefix, nameOverride string, typ *ast.
|
|||||||
builder.WriteString("[]")
|
builder.WriteString("[]")
|
||||||
typ = typ.Elem
|
typ = typ.Elem
|
||||||
}
|
}
|
||||||
// TODO: allow an option to make the Go type a pointer, if you want to do
|
if options.GetPointer() {
|
||||||
// optionality that way, or perhaps others
|
// TODO: this does []*T, you might in principle want *[]T or
|
||||||
// if !typ.NonNull { builder.WriteString("*") }
|
// *[]*T.
|
||||||
|
builder.WriteString("*")
|
||||||
|
}
|
||||||
|
|
||||||
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(namePrefix, nameOverride, def, fields)
|
name, err := builder.addTypeForDefinition(namePrefix, nameOverride, def, fields, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -263,7 +300,7 @@ func (builder *typeBuilder) writeType(namePrefix, nameOverride string, typ *ast.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field) error {
|
func (builder *typeBuilder) writeTypedef(typedef *ast.Definition, fields []field, options *GenqlientDirective) error {
|
||||||
switch typedef.Kind {
|
switch typedef.Kind {
|
||||||
case ast.Object, ast.InputObject:
|
case ast.Object, ast.InputObject:
|
||||||
builder.WriteString("struct {\n")
|
builder.WriteString("struct {\n")
|
||||||
@@ -295,7 +332,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(builder.typeNamePrefix, "", impldef, fields)
|
name, err := builder.addTypeForDefinition(builder.typeNamePrefix, "", impldef, fields, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user