Skip to content

Commit 446a2dd

Browse files
authored
Add option for custom panic handler (#468)
Add option for custom panic handler
1 parent 5457f60 commit 446a2dd

File tree

6 files changed

+61
-9
lines changed

6 files changed

+61
-9
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {
109109
- `Tracer(tracer trace.Tracer)` is used to trace queries and fields. It defaults to `trace.OpenTracingTracer`.
110110
- `ValidationTracer(tracer trace.ValidationTracer)` is used to trace validation errors. It defaults to `trace.NoopValidationTracer`.
111111
- `Logger(logger log.Logger)` is used to log panics during query execution. It defaults to `exec.DefaultLogger`.
112+
- `PanicHandler(panicHandler errors.PanicHandler)` is used to transform panics into errors during query execution. It defaults to `errors.DefaultPanicHandler`.
112113
- `DisableIntrospection()` disables introspection queries.
113114

114115
### Custom Errors

errors/panic_handler.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package errors
2+
3+
import (
4+
"context"
5+
)
6+
7+
// PanicHandler is the interface used to create custom panic errors that occur during query execution
8+
type PanicHandler interface {
9+
MakePanicError(ctx context.Context, value interface{}) *QueryError
10+
}
11+
12+
// DefaultPanicHandler is the default PanicHandler
13+
type DefaultPanicHandler struct{}
14+
15+
// MakePanicError creates a new QueryError from a panic that occurred during execution
16+
func (h *DefaultPanicHandler) MakePanicError(ctx context.Context, value interface{}) *QueryError {
17+
return Errorf("panic occurred: %v", value)
18+
}

errors/panic_handler_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package errors
2+
3+
import (
4+
"context"
5+
"testing"
6+
)
7+
8+
func TestDefaultPanicHandler(t *testing.T) {
9+
handler := &DefaultPanicHandler{}
10+
qErr := handler.MakePanicError(context.Background(), "foo")
11+
if qErr == nil {
12+
t.Fatal("Panic error must not be nil")
13+
}
14+
const (
15+
expectedMessage = "panic occurred: foo"
16+
expectedError = "graphql: " + expectedMessage
17+
)
18+
if qErr.Error() != expectedError {
19+
t.Errorf("Unexpected panic error message: %q != %q", qErr.Error(), expectedError)
20+
}
21+
if qErr.Message != expectedMessage {
22+
t.Errorf("Unexpected panic QueryError.Message: %q != %q", qErr.Message, expectedMessage)
23+
}
24+
}

graphql.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func ParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) (
3030
maxParallelism: 10,
3131
tracer: trace.OpenTracingTracer{},
3232
logger: &log.DefaultLogger{},
33+
panicHandler: &errors.DefaultPanicHandler{},
3334
}
3435
for _, opt := range opts {
3536
opt(s)
@@ -78,6 +79,7 @@ type Schema struct {
7879
tracer trace.Tracer
7980
validationTracer trace.ValidationTracerContext
8081
logger log.Logger
82+
panicHandler errors.PanicHandler
8183
useStringDescriptions bool
8284
disableIntrospection bool
8385
subscribeResolverTimeout time.Duration
@@ -143,6 +145,14 @@ func Logger(logger log.Logger) SchemaOpt {
143145
}
144146
}
145147

148+
// PanicHandler is used to customize the panic errors during query execution.
149+
// It defaults to errors.DefaultPanicHandler.
150+
func PanicHandler(panicHandler errors.PanicHandler) SchemaOpt {
151+
return func(s *Schema) {
152+
s.panicHandler = panicHandler
153+
}
154+
}
155+
146156
// DisableIntrospection disables introspection queries.
147157
func DisableIntrospection() SchemaOpt {
148158
return func(s *Schema) {
@@ -244,9 +254,10 @@ func (s *Schema) exec(ctx context.Context, queryString string, operationName str
244254
Schema: s.schema,
245255
DisableIntrospection: s.disableIntrospection,
246256
},
247-
Limiter: make(chan struct{}, s.maxParallelism),
248-
Tracer: s.tracer,
249-
Logger: s.logger,
257+
Limiter: make(chan struct{}, s.maxParallelism),
258+
Tracer: s.tracer,
259+
Logger: s.logger,
260+
PanicHandler: s.panicHandler,
250261
}
251262
varTypes := make(map[string]*introspection.Type)
252263
for _, v := range op.Vars {

internal/exec/exec.go

+3-6
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,21 @@ type Request struct {
2323
Limiter chan struct{}
2424
Tracer trace.Tracer
2525
Logger log.Logger
26+
PanicHandler errors.PanicHandler
2627
SubscribeResolverTimeout time.Duration
2728
}
2829

2930
func (r *Request) handlePanic(ctx context.Context) {
3031
if value := recover(); value != nil {
3132
r.Logger.LogPanic(ctx, value)
32-
r.AddError(makePanicError(value))
33+
r.AddError(r.PanicHandler.MakePanicError(ctx, value))
3334
}
3435
}
3536

3637
type extensionser interface {
3738
Extensions() map[string]interface{}
3839
}
3940

40-
func makePanicError(value interface{}) *errors.QueryError {
41-
return errors.Errorf("panic occurred: %v", value)
42-
}
43-
4441
func (r *Request) Execute(ctx context.Context, s *resolvable.Schema, op *types.OperationDefinition) ([]byte, []*errors.QueryError) {
4542
var out bytes.Buffer
4643
func() {
@@ -188,7 +185,7 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f
188185
defer func() {
189186
if panicValue := recover(); panicValue != nil {
190187
r.Logger.LogPanic(ctx, panicValue)
191-
err = makePanicError(panicValue)
188+
err = r.PanicHandler.MakePanicError(ctx, panicValue)
192189
err.Path = path.toSlice()
193190
}
194191
}()

subscriptions.go

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func (s *Schema) subscribe(ctx context.Context, queryString string, operationNam
5757
Limiter: make(chan struct{}, s.maxParallelism),
5858
Tracer: s.tracer,
5959
Logger: s.logger,
60+
PanicHandler: s.panicHandler,
6061
SubscribeResolverTimeout: s.subscribeResolverTimeout,
6162
}
6263
varTypes := make(map[string]*introspection.Type)

0 commit comments

Comments
 (0)