99designs / 99designs/gqlgen

Errors from field resolvers in subscriptions are ignored

Open
#1,212 5 comments 2 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
10.8k
Forks
1.3k
Avg merge
2d 36m
Merged PRs (30d)
26

Description

### What happened?

It seems like errors (and panics) in a field resolver are ignored if the type is being returned as part of a subscription. Instead, it simply returns the zero value for the field. This is the case when either returning the error from the resolver function or using `graphql.Errorf`.

### What did you expect?

That errors and panics occurring during resolution of fields resolved as part of a subscription are returned in the `errors` key of the response.

Using the below, a subscription of:

```graphql
subscription {
todos {
id
name
}
}
```

Returns the following every one second:

```json
{
"data": {
"todos": {
"id": "",
"name": null
}
}
}
```

In contrast:

```graphql
query {
todos {
id
name
}
}
```

Returns

```json
{
"errors": [
{
"message": "context error!",
"path": [
"todos",
0,
"name"
]
},
{
"message": "error!",
"path": [
"todos",
0,
"name"
]
}
],
"data": {
"todos": [
{
"id": "",
"name": null
}
]
}
}
```

### Minimal graphql.schema and models to reproduce

graph/schema.graphqls

```graphql
directive @goField(
forceResolver: Boolean
name: String
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION

type Subscription {
todos: Todo!
}

type Query {
todos: [Todo!]!
}

type Todo {
id: ID!
name: String @goField(forceResolver: true)
}
```

graph/schema.resolvers.go

```go
package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.

import (
"context"
"errors"
"time"
"tmp/graph/generated"
"tmp/graph/model"

"github.com/99designs/gqlgen/graphql"
)

func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
return []*model.Todo{
{},
}, nil
}

func (r *subscriptionResolver) Todos(ctx context.Context) (<-chan *model.Todo, error) {
ch := make(chan *model.Todo)
go func() {
defer close(ch)
for {
ch <- &model.Todo{}
time.Sleep(time.Second)
}
}()
return ch, nil
}

func (r *todoResolver) Name(ctx context.Context, obj *model.Todo) (*string, error) {
//panic("panic!") // also ignored
graphql.AddErrorf(ctx, "context error!")
return nil, errors.New("error!")
}

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

// Subscription returns generated.SubscriptionResolver implementation.
func (r *Resolver) Subscription() generated.SubscriptionResolver { return &subscriptionResolver{r} }

// Todo returns generated.TodoResolver implementation.
func (r *Resolver) Todo() generated.TodoResolver { return &todoResolver{r} }

type queryResolver struct{ *Resolver }
type subscriptionResolver struct{ *Resolver }
type todoResolver struct{ *Resolver }
```

graph/model/models_gen.go
```go
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.

package model

type Todo struct {
ID string `json:"id"`
Name *string `json:"name"`
}
```

### versions
- gqlgen version: `v0.11.3`
- `go version go1.14.2 linux/amd64`

```
module tmp

go 1.14

require (
github.com/99designs/gqlgen v0.11.3
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/matryer/moq v0.0.0-20200607124540-4638a53893e6 // indirect
github.com/mitchellh/mapstructure v1.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/urfave/cli/v2 v2.2.0 // indirect
github.com/vektah/dataloaden v0.3.0 // indirect
github.com/vektah/gqlparser/v2 v2.0.1
golang.org/x/mod v0.3.0 // indirect
golang.org/x/tools v0.0.0-20200611191743-782c6b3cc724 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)
```

Contributor guide

Open the contributing guide

Research direction

The issue is in subscription field error handling. Look at the subscription resolver in graph/schema.resolvers.go and the generated code for subscriptions. The error from the todoResolver.Name is being ignored. Start by examining how gqlgen processes subscription payloads and where errors are collected. Check the execution engine for subscriptions, likely in the 'subscription' or 'execution' package. The test should verify that errors appear in the subscription response's errors key.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, graphql
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
40/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.