Skip to content

Commit 67f2575

Browse files
Reject the use of both typename and bind (#172)
In #133, Craig added support for a new use of typename, where it applies to a scalar and means that genqlient should generate a named type, e.g. `# @genqlient(typename: "MyString")` on a node of type string will generate and use `type MyString string`. But this gets a bit confusing if you mix it with `bind`; should `typename: "MyString", bind: "int32"` generate `type MyString int32`, or should one override the other, or what? Of course in practice you're not likely to write that all in one place, but you could via a global binding, or a `for` directive, and in that case probably it was a mistake. In #138, we looked at making them work together correctly, but it added complexity and got even more confusing. So instead, here, we just ban it; we can always add it back if it proves useful. (Or, you can make the `typename` win over a global binding by locally unbinding it via `bind: "-"`.) This required changes in surprisingly many places; I already knew the directive-validation code was due for a refactor but that will happen some other day. The tests show that it works, in any case. Interestingly, this problem actually could have arisen for a struct binding already, before #133. But all the same reasons it's confusing seem to apply, so I just banned it there too. This is technically a breaking change although I doubt anyone will hit it. Test plan: make check
1 parent 8aa56aa commit 67f2575

13 files changed

+81
-0
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ When releasing a new version:
2323
### Breaking changes:
2424

2525
- The `Config` fields `Schema` and `Operations` are now both of type `StringList`. This does not affect configuration via `genqlient.yaml`, only via the Go API.
26+
- The `typename` and `bind` options may no longer be combined; doing so will now result in an error. In practice, any such use was likely in error (and the rules for which would win were confusing and undocumented).
2627

2728
### New features:
2829

docs/genqlient_directive.graphql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@ directive genqlient(
226226
# Note that unlike most directives, if applied to the entire operation,
227227
# typename affects the overall response type, rather than being propagated
228228
# down to all child fields (which would cause conflicts).
229+
#
230+
# To avoid confusion, typename may not be combined with local or global
231+
# bindings; to use typename instead of a global binding do
232+
# `typename: "MyTypeName", bind: "-"`.
229233
typename: String
230234

231235
# Multiple genqlient directives are allowed in the same location, as long as

generate/convert.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,11 @@ func (g *generator) convertDefinition(
289289
// unless the binding is "-" which means "ignore the global binding".
290290
globalBinding, ok := g.Config.Bindings[def.Name]
291291
if ok && options.Bind != "-" {
292+
if options.TypeName != "" {
293+
return nil, errorf(pos,
294+
"typename option conflicts with global binding for %s; "+
295+
"use `bind: \"-\"` to override it", def.Name)
296+
}
292297
if def.Kind == ast.Object || def.Kind == ast.Interface || def.Kind == ast.Union {
293298
err := g.validateBindingSelection(
294299
def.Name, globalBinding, pos, selectionSet)

generate/genqlient_directive.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ func (dir *genqlientDirective) validate(node interface{}, schema *ast.Schema) er
213213
if fieldDir.Omitempty != nil && field.Type.NonNull {
214214
return errorf(fieldDir.pos, "omitempty may only be used on optional arguments")
215215
}
216+
217+
if fieldDir.TypeName != "" && fieldDir.Bind != "" && fieldDir.Bind != "-" {
218+
return errorf(fieldDir.pos, "typename and bind may not be used together")
219+
}
216220
}
217221
}
218222

@@ -255,6 +259,10 @@ func (dir *genqlientDirective) validate(node interface{}, schema *ast.Schema) er
255259
return errorf(dir.pos, "for is only applicable to operations and arguments")
256260
}
257261

262+
if dir.TypeName != "" && dir.Bind != "" && dir.Bind != "-" {
263+
return errorf(dir.pos, "typename and bind may not be used together")
264+
}
265+
258266
return nil
259267
case *ast.Field:
260268
if dir.Omitempty != nil {
@@ -278,6 +286,10 @@ func (dir *genqlientDirective) validate(node interface{}, schema *ast.Schema) er
278286
return errorf(dir.pos, "for is only applicable to operations and arguments")
279287
}
280288

289+
if dir.TypeName != "" && dir.Bind != "" && dir.Bind != "-" {
290+
return errorf(dir.pos, "typename and bind may not be used together")
291+
}
292+
281293
return nil
282294
default:
283295
return errorf(dir.pos, "invalid @genqlient directive location: %T", node)
@@ -490,6 +502,15 @@ func (g *generator) parsePrecedingComment(
490502
if queryOptions != nil {
491503
// If we are part of an operation/fragment, merge its options in.
492504
directive.mergeOperationDirective(node, parentIfInputField, queryOptions)
505+
506+
// TODO(benkraft): Really we should do all the validation after
507+
// merging, probably? But this is the only check that can fail only
508+
// after merging, and it's a bit tricky because the "does not apply"
509+
// checks may need to happen before merging so we know where the
510+
// directive "is".
511+
if directive.TypeName != "" && directive.Bind != "" && directive.Bind != "-" {
512+
return "", nil, errorf(directive.pos, "typename and bind may not be used together")
513+
}
493514
}
494515

495516
reverse(commentLines)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# @genqlient(for: "User.name", bind: "[]byte")
2+
query ConflictingTypeNameAndGlobalBind {
3+
user {
4+
# @genqlient(typename: "MyID")
5+
name
6+
}
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type Query {
2+
user: User
3+
}
4+
5+
type User {
6+
id: ID!
7+
name: String!
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
query ConflictingTypeNameAndGlobalBind {
2+
user {
3+
# @genqlient(typename: "OtherScalar")
4+
name
5+
}
6+
}
7+
8+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type Query {
2+
user: User
3+
}
4+
5+
type User {
6+
id: ID!
7+
name: ValidScalar!
8+
}
9+
10+
scalar ValidScalar
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
query ConflictingTypeNameAndGlobalBind {
2+
user {
3+
# @genqlient(bind: "[]byte", typename: "MyID")
4+
name
5+
}
6+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type Query {
2+
user: User
3+
}
4+
5+
type User {
6+
id: ID!
7+
name: String!
8+
}

0 commit comments

Comments
 (0)