add omitempty
This commit is contained in:
+109
-4
@@ -8,18 +8,123 @@ import (
|
||||
"github.com/vektah/gqlparser/v2/parser"
|
||||
)
|
||||
|
||||
func (g *generator) parsePrecedingComment(pos *ast.Position) (comment string, directives []*ast.Directive, err error) {
|
||||
// GenqlientDirective represents the @genqlient quasi-directive, used to
|
||||
// configure genqlient on a query-by-query basis.
|
||||
//
|
||||
// The syntax of the directive is just like a GraphQL directive, except it goes
|
||||
// in a comment on the line immediately preceding the field. (This is because
|
||||
// GraphQL expects directives in queries to be defined by the server, not by
|
||||
// the client, so it would reject a real @genqlient directive as nonexistent.)
|
||||
//
|
||||
// Directives may be applied to fields, arguments, or the entire query.
|
||||
// Directives on the line preceding the query apply to all relevant nodes in
|
||||
// the query; other directives apply to all nodes on the following line. (In
|
||||
// all cases it's fine for there to be other comments in between the directive
|
||||
// and the node(s) to which it applies.) For example, in the following query:
|
||||
// # @genqlient(n: "a")
|
||||
//
|
||||
// # @genqlient(n: "b")
|
||||
// #
|
||||
// # Comment describing the query
|
||||
// #
|
||||
// # @genqlient(n: "c")
|
||||
// query MyQuery(arg1: String,
|
||||
// # @genqlient(n: "d")
|
||||
// arg2: String, arg3: String,
|
||||
// arg4: String,
|
||||
// ) {
|
||||
// # @genqlient(n: "e")
|
||||
// field1, field2
|
||||
// field3
|
||||
// }
|
||||
// the directive "a" is ignored, "b" and "c" apply to all relevant nodes in the
|
||||
// query, "d" applies to arg2 and arg3, and "e" applies to field1 and field2.
|
||||
type GenqlientDirective struct {
|
||||
// If set, this argument will be omitted if it's equal to its Go zero
|
||||
// value. For example, given the following query:
|
||||
// # @genqlient(omitempty: true)
|
||||
// query MyQuery(arg: String) { ... }
|
||||
// genqlient will generate a function
|
||||
// MyQuery(ctx context.Context, client graphql.Client, arg string) ...
|
||||
// which will pass {"arg": null} to GraphQL if arg is "", and the actual
|
||||
// value otherwise.
|
||||
//
|
||||
// Only applicable to arguments of nullable types.
|
||||
Omitempty bool
|
||||
}
|
||||
|
||||
func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
||||
if dir.Name != "genqlient" {
|
||||
// Actually we just won't get here; we only get here if the line starts
|
||||
// with "# @genqlient", unless there's some sort of bug.
|
||||
return nil, fmt.Errorf("the only valid comment-directive is @genqlient, got %v", dir.Name)
|
||||
}
|
||||
|
||||
var retval GenqlientDirective
|
||||
for _, arg := range dir.Arguments {
|
||||
switch arg.Name {
|
||||
case "omitempty":
|
||||
retval.Omitempty = true
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown argument %v for @genqlient", arg.Name)
|
||||
}
|
||||
}
|
||||
return &retval, nil
|
||||
}
|
||||
|
||||
func (dir *GenqlientDirective) validate(node interface{}) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.OperationDefinition:
|
||||
// Anything is valid on the entire operation; it will just apply to
|
||||
// whatever it is relevant to.
|
||||
return nil
|
||||
case *ast.VariableDefinition:
|
||||
if dir.Omitempty && node.Type.NonNull {
|
||||
return fmt.Errorf("omitempty may only be used on optional arguments")
|
||||
}
|
||||
return nil
|
||||
case *ast.Field:
|
||||
if dir.Omitempty {
|
||||
return fmt.Errorf("omitempty is not appilcable to fields")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid directive location: %T", node)
|
||||
}
|
||||
}
|
||||
|
||||
func (dir *GenqlientDirective) merge(other *GenqlientDirective) *GenqlientDirective {
|
||||
if dir == nil {
|
||||
return other
|
||||
}
|
||||
var retval GenqlientDirective
|
||||
retval.Omitempty = dir.Omitempty || other.Omitempty
|
||||
return &retval
|
||||
}
|
||||
|
||||
func (g *generator) parsePrecedingComment(
|
||||
node interface{},
|
||||
pos *ast.Position,
|
||||
) (comment string, directive *GenqlientDirective, err error) {
|
||||
var commentLines []string
|
||||
sourceLines := strings.Split(pos.Src.Input, "\n")
|
||||
for i := pos.Line - 1; i > 0; i-- {
|
||||
line := strings.TrimSpace(sourceLines[i-1])
|
||||
trimmed := strings.TrimSpace(strings.TrimPrefix(line, "#"))
|
||||
if strings.HasPrefix(line, "# @genqlient") {
|
||||
directive, err := parseDirective(trimmed, pos)
|
||||
graphQLDirective, err := parseDirective(trimmed, pos)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
directives = append(directives, directive)
|
||||
genqlientDirective, err := fromGraphQL(graphQLDirective)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
err = genqlientDirective.validate(node)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
directive = directive.merge(genqlientDirective)
|
||||
} else if strings.HasPrefix(line, "#") {
|
||||
commentLines = append(commentLines, trimmed)
|
||||
} else {
|
||||
@@ -29,7 +134,7 @@ func (g *generator) parsePrecedingComment(pos *ast.Position) (comment string, di
|
||||
|
||||
reverse(commentLines)
|
||||
|
||||
return strings.TrimSpace(strings.Join(commentLines, "\n")), directives, nil
|
||||
return strings.TrimSpace(strings.Join(commentLines, "\n")), directive, nil
|
||||
}
|
||||
|
||||
func parseDirective(line string, pos *ast.Position) (*ast.Directive, error) {
|
||||
|
||||
+23
-7
@@ -61,6 +61,7 @@ type argument struct {
|
||||
GoName string
|
||||
GoType string
|
||||
GraphQLName string
|
||||
Omitempty bool
|
||||
}
|
||||
|
||||
func newGenerator(config *Config, schema *ast.Schema) *generator {
|
||||
@@ -107,7 +108,21 @@ func (g *generator) Types() string {
|
||||
return strings.Join(defs, "\n\n")
|
||||
}
|
||||
|
||||
func (g *generator) getArgument(opName string, arg *ast.VariableDefinition) (argument, error) {
|
||||
func (g *generator) getArgument(
|
||||
opName string,
|
||||
arg *ast.VariableDefinition,
|
||||
operationDirective *GenqlientDirective,
|
||||
) (argument, error) {
|
||||
_, directive, err := g.parsePrecedingComment(arg, arg.Position)
|
||||
if err != nil {
|
||||
return argument{}, err
|
||||
}
|
||||
directive = operationDirective.merge(directive)
|
||||
omitempty := false
|
||||
if directive != nil {
|
||||
omitempty = directive.Omitempty
|
||||
}
|
||||
|
||||
graphQLName := arg.Variable
|
||||
goType, err := g.getTypeForInputType(opName, arg.Type)
|
||||
if err != nil {
|
||||
@@ -117,6 +132,7 @@ func (g *generator) getArgument(opName string, arg *ast.VariableDefinition) (arg
|
||||
GraphQLName: graphQLName,
|
||||
GoName: lowerFirst(graphQLName),
|
||||
GoType: goType,
|
||||
Omitempty: omitempty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -133,10 +149,15 @@ func (g *generator) addOperation(op *ast.OperationDefinition) error {
|
||||
// TODO: handle fragments
|
||||
})
|
||||
|
||||
commentLines, directive, err := g.parsePrecedingComment(op, op.Position)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := make([]argument, len(op.VariableDefinitions))
|
||||
for i, arg := range op.VariableDefinitions {
|
||||
var err error
|
||||
args[i], err = g.getArgument(op.Name, arg)
|
||||
args[i], err = g.getArgument(op.Name, arg, directive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -147,11 +168,6 @@ func (g *generator) addOperation(op *ast.OperationDefinition) error {
|
||||
return err
|
||||
}
|
||||
|
||||
commentLines, _, err := g.parsePrecedingComment(op.Position)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var docComment string
|
||||
if len(commentLines) > 0 {
|
||||
docComment = "// " + strings.ReplaceAll(commentLines, "\n", "\n// ")
|
||||
|
||||
@@ -24,9 +24,19 @@ func {{.Name}}(
|
||||
{{- if .Args -}}
|
||||
variables := map[string]interface{}{
|
||||
{{range .Args -}}
|
||||
"{{.GraphQLName}}": {{.GoName}},
|
||||
"{{.GraphQLName}}": {{if .Omitempty}}nil{{else}}{{.GoName}}{{end}},
|
||||
{{end}}
|
||||
}
|
||||
{{range .Args -}}
|
||||
{{if .Omitempty -}}
|
||||
{{/* zero_{{.GoType}} would be a better name, but {{.GoType}} would require
|
||||
munging since it might be, say, `time.Time`. */}}
|
||||
var zero_{{.GoName}} {{.GoType}}
|
||||
if {{.GoName}} != zero_{{.GoName}} {
|
||||
variables["{{.GraphQLName}}"] = {{.GoName}}
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
var retval {{.ResponseName}}
|
||||
err := {{if $.Config.ClientGetter}}{{$.Config.ClientGetter}}{{else}}client{{end}}.MakeRequest(
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# @genqlient(omitempty: true)
|
||||
query OmitEmptyQuery(
|
||||
$query: UserQueryInput,
|
||||
$dt: DateTime,
|
||||
$tz: String,
|
||||
) {
|
||||
user(query: $query) {
|
||||
id
|
||||
}
|
||||
maybeConvert(dt: $dt, tz: $tz)
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package test
|
||||
|
||||
// Code generated by github.com/Khan/genqlient, DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
"github.com/me/mypkg"
|
||||
)
|
||||
|
||||
type OmitEmptyQueryResponse struct {
|
||||
User OmitEmptyQueryUser `json:"user"`
|
||||
MaybeConvert time.Time `json:"maybeConvert"`
|
||||
}
|
||||
|
||||
type OmitEmptyQueryUser struct {
|
||||
Id mypkg.ID `json:"id"`
|
||||
}
|
||||
|
||||
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 OmitEmptyQuery(
|
||||
client graphql.Client,
|
||||
query UserQueryInput,
|
||||
dt time.Time,
|
||||
tz string,
|
||||
) (*OmitEmptyQueryResponse, error) {
|
||||
variables := map[string]interface{}{
|
||||
"query": nil,
|
||||
"dt": nil,
|
||||
"tz": nil,
|
||||
}
|
||||
|
||||
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
|
||||
err := client.MakeRequest(
|
||||
nil,
|
||||
"OmitEmptyQuery",
|
||||
`
|
||||
query OmitEmptyQuery ($query: UserQueryInput, $dt: DateTime, $tz: String) {
|
||||
user(query: $query) {
|
||||
id
|
||||
}
|
||||
maybeConvert(dt: $dt, tz: $tz)
|
||||
}
|
||||
`,
|
||||
&retval,
|
||||
variables,
|
||||
)
|
||||
return &retval, err
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"operations": [
|
||||
{
|
||||
"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",
|
||||
"sourceLocation": "testdata/queries/Omitempty.graphql"
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
@@ -63,6 +63,7 @@ type Query {
|
||||
root: Topic!
|
||||
randomLeaf: LeafContent!
|
||||
convert(dt: DateTime!, tz: String): DateTime!
|
||||
maybeConvert(dt: DateTime, tz: String): DateTime
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
|
||||
Reference in New Issue
Block a user