Skip to content

Commit 5a1c172

Browse files
authored
Improve type assertion method argument validation (require zero) (graph-gophers#516)
Improve type assertion method argument validation (require zero) It's tempting to include a context argument (or think it's allowed), but not discover that this will fail until a query is executed. Validating the resolver during schema parsing reduces the chance of inadvertant errors here. Signed-off-by: Evan Owen <kainosnoema@gmail.com>
1 parent 3a8c713 commit 5a1c172

File tree

2 files changed

+111
-3
lines changed

2 files changed

+111
-3
lines changed

graphql_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3666,6 +3666,106 @@ func TestErrorPropagation(t *testing.T) {
36663666
})
36673667
}
36683668

3669+
type assertionResolver struct{}
3670+
3671+
func (r *assertionResolver) ToHuman() (*struct{ Name string }, bool) {
3672+
return &struct{ Name string }{Name: "Luke Skywalker"}, true
3673+
}
3674+
3675+
type assertionQueryResolver struct{}
3676+
3677+
func (*assertionQueryResolver) Character() *assertionResolver {
3678+
return &assertionResolver{}
3679+
}
3680+
3681+
type badAssertionResolver struct{}
3682+
3683+
func (r *badAssertionResolver) ToHuman(ctx context.Context) (*struct{ Name string }, bool) {
3684+
return &struct{ Name string }{Name: "Luke Skywalker"}, true
3685+
}
3686+
3687+
type badAssertionQueryResolver struct{}
3688+
3689+
func (*badAssertionQueryResolver) Character() *badAssertionResolver {
3690+
return &badAssertionResolver{}
3691+
}
3692+
3693+
func TestTypeAssertions(t *testing.T) {
3694+
assertionSchema := `
3695+
schema {
3696+
query: Query
3697+
}
3698+
3699+
type Query {
3700+
character: Character!
3701+
}
3702+
3703+
type Human {
3704+
name: String!
3705+
}
3706+
3707+
union Character = Human
3708+
`
3709+
query := `
3710+
query {
3711+
character {
3712+
... on Human {
3713+
name
3714+
}
3715+
}
3716+
}
3717+
`
3718+
3719+
gqltesting.RunTests(t, []*gqltesting.Test{
3720+
{
3721+
Schema: graphql.MustParseSchema(assertionSchema, &assertionQueryResolver{}, graphql.UseFieldResolvers()),
3722+
Query: query,
3723+
ExpectedResult: `
3724+
{
3725+
"character": {
3726+
"name": "Luke Skywalker"
3727+
}
3728+
}
3729+
`,
3730+
},
3731+
})
3732+
}
3733+
3734+
func TestPanicTypeAssertionArguments(t *testing.T) {
3735+
panicMessage := `*graphql_test.badAssertionResolver does not resolve "Character": method "ToHuman" should't have any arguments
3736+
used by (*graphql_test.badAssertionQueryResolver).Character`
3737+
3738+
defer func() {
3739+
r := recover()
3740+
if r == nil {
3741+
t.Fatal("expected schema parse to panic")
3742+
}
3743+
3744+
if r.(error).Error() != panicMessage {
3745+
t.Logf("got: %s", r)
3746+
t.Logf("want: %s", panicMessage)
3747+
t.Fail()
3748+
}
3749+
}()
3750+
3751+
schema := `
3752+
schema {
3753+
query: Query
3754+
}
3755+
3756+
type Query {
3757+
character: Character!
3758+
}
3759+
3760+
type Human {
3761+
name: String!
3762+
}
3763+
3764+
union Character = Human
3765+
`
3766+
graphql.MustParseSchema(schema, &badAssertionQueryResolver{}, graphql.UseFieldResolvers())
3767+
}
3768+
36693769
type ambiguousResolver struct {
36703770
Name string // ambiguous
36713771
University

internal/exec/resolvable/resolvable.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,15 @@ func (b *execBuilder) makeObjectExec(typeName string, fields types.FieldsDefinit
277277
if methodIndex == -1 {
278278
return nil, fmt.Errorf("%s does not resolve %q: missing method %q to convert to %q", resolverType, typeName, "To"+impl.Name, impl.Name)
279279
}
280-
if resolverType.Method(methodIndex).Type.NumOut() != 2 {
280+
m := resolverType.Method(methodIndex)
281+
expectedIn := 0
282+
if methodHasReceiver {
283+
expectedIn = 1
284+
}
285+
if m.Type.NumIn() != expectedIn {
286+
return nil, fmt.Errorf("%s does not resolve %q: method %q should't have any arguments", resolverType, typeName, "To"+impl.Name)
287+
}
288+
if m.Type.NumOut() != 2 {
281289
return nil, fmt.Errorf("%s does not resolve %q: method %q should return a value and a bool indicating success", resolverType, typeName, "To"+impl.Name)
282290
}
283291
a := &TypeAssertion{
@@ -324,7 +332,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *types.FieldDefinition, m
324332

325333
if len(f.Arguments) > 0 {
326334
if len(in) == 0 {
327-
return nil, fmt.Errorf("must have parameter for field arguments")
335+
return nil, fmt.Errorf("must have `args struct { ... }` argument for field arguments")
328336
}
329337
var err error
330338
argsPacker, err = b.packerBuilder.MakeStructPacker(f.Arguments, in[0])
@@ -335,7 +343,7 @@ func (b *execBuilder) makeFieldExec(typeName string, f *types.FieldDefinition, m
335343
}
336344

337345
if len(in) > 0 {
338-
return nil, fmt.Errorf("too many parameters")
346+
return nil, fmt.Errorf("too many arguments")
339347
}
340348

341349
maxNumOfReturns := 2

0 commit comments

Comments
 (0)