Skip to content

Commit ec96144

Browse files
author
Nick Randall
committed
POC: custom error handlers
1 parent b7c59ab commit ec96144

File tree

4 files changed

+79
-53
lines changed

4 files changed

+79
-53
lines changed

errors/errors.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ package errors
33
import "fmt"
44

55
type QueryError struct {
6-
Message string `json:"message"`
7-
Locations []*Location `json:"locations,omitempty"`
6+
Message string `json:"message"`
7+
Locations []*Location `json:"locations,omitempty"`
8+
Data map[string]interface{} `json:"data,omitempty"`
89
}
910

1011
type Location struct {
1112
Line int `json:"line"`
1213
Column int `json:"column"`
1314
}
1415

16+
type ErrorHandler func(error) *QueryError
17+
1518
func Errorf(format string, a ...interface{}) *QueryError {
1619
return &QueryError{
1720
Message: fmt.Sprintf(format, a...),

graphql.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,15 @@ func (id *ID) UnmarshalGraphQL(input interface{}) error {
4141
}
4242
}
4343

44-
func ParseSchema(schemaString string, resolver interface{}) (*Schema, error) {
44+
type Option func(*Schema)
45+
46+
func WithCustomErrorHandler(errorHandler errors.ErrorHandler) Option {
47+
return func(s *Schema) {
48+
s.errorHandler = errorHandler
49+
}
50+
}
51+
52+
func ParseSchema(schemaString string, resolver interface{}, options ...Option) (*Schema, error) {
4553
s := &Schema{
4654
schema: schema.New(),
4755
}
@@ -57,20 +65,29 @@ func ParseSchema(schemaString string, resolver interface{}) (*Schema, error) {
5765
s.exec = e
5866
}
5967

68+
for _, apply := range options {
69+
apply(s)
70+
}
71+
72+
if s.errorHandler == nil {
73+
s.errorHandler = DefaultErrorHandler
74+
}
75+
6076
return s, nil
6177
}
6278

63-
func MustParseSchema(schemaString string, resolver interface{}) *Schema {
64-
s, err := ParseSchema(schemaString, resolver)
79+
func MustParseSchema(schemaString string, resolver interface{}, options ...Option) *Schema {
80+
s, err := ParseSchema(schemaString, resolver, options...)
6581
if err != nil {
6682
panic(err)
6783
}
6884
return s
6985
}
7086

7187
type Schema struct {
72-
schema *schema.Schema
73-
exec *exec.Exec
88+
schema *schema.Schema
89+
exec *exec.Exec
90+
errorHandler errors.ErrorHandler
7491
}
7592

7693
type Response struct {
@@ -87,7 +104,7 @@ func (s *Schema) Exec(ctx context.Context, queryString string, operationName str
87104
document, err := query.Parse(queryString, s.schema.Resolve)
88105
if err != nil {
89106
return &Response{
90-
Errors: []*errors.QueryError{err},
107+
Errors: []*errors.QueryError{s.errorHandler(err)},
91108
}
92109
}
93110

@@ -101,7 +118,7 @@ func (s *Schema) Exec(ctx context.Context, queryString string, operationName str
101118
}
102119
defer span.Finish()
103120

104-
data, errs := exec.ExecuteRequest(subCtx, s.exec, document, operationName, variables)
121+
data, errs := exec.ExecuteRequest(subCtx, s.exec, document, operationName, variables, s.errorHandler)
105122
if len(errs) != 0 {
106123
ext.Error.Set(span, true)
107124
span.SetTag(OpenTracingTagError, errs)
@@ -112,12 +129,18 @@ func (s *Schema) Exec(ctx context.Context, queryString string, operationName str
112129
}
113130
}
114131

132+
func DefaultErrorHandler(err error) *errors.QueryError {
133+
return &errors.QueryError{
134+
Message: fmt.Sprintf("%s", err),
135+
}
136+
}
137+
115138
func (s *Schema) Inspect() *introspection.Schema {
116139
return &introspection.Schema{Schema: s.schema}
117140
}
118141

119142
func (s *Schema) ToJSON() ([]byte, error) {
120-
result, err := exec.IntrospectSchema(s.schema)
143+
result, err := exec.IntrospectSchema(s.schema, s.errorHandler)
121144
if err != nil {
122145
return nil, err
123146
}

internal/exec/exec.go

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,10 @@ func (r *request) addError(err *errors.QueryError) {
300300
r.mu.Unlock()
301301
}
302302

303-
func (r *request) handlePanic() {
303+
func (r *request) handlePanic(errorHandler errors.ErrorHandler) {
304304
if err := recover(); err != nil {
305305
execErr := errors.Errorf("graphql: panic occured: %v", err)
306-
r.addError(execErr)
306+
r.addError(errorHandler(execErr))
307307

308308
const size = 64 << 10
309309
buf := make([]byte, size)
@@ -312,10 +312,10 @@ func (r *request) handlePanic() {
312312
}
313313
}
314314

315-
func ExecuteRequest(ctx context.Context, e *Exec, document *query.Document, operationName string, variables map[string]interface{}) (interface{}, []*errors.QueryError) {
315+
func ExecuteRequest(ctx context.Context, e *Exec, document *query.Document, operationName string, variables map[string]interface{}, errorHandler errors.ErrorHandler) (interface{}, []*errors.QueryError) {
316316
op, err := getOperation(document, operationName)
317317
if err != nil {
318-
return nil, []*errors.QueryError{errors.Errorf("%s", err)}
318+
return nil, []*errors.QueryError{errorHandler(err)}
319319
}
320320

321321
r := &request{
@@ -336,8 +336,8 @@ func ExecuteRequest(ctx context.Context, e *Exec, document *query.Document, oper
336336
}
337337

338338
data := func() interface{} {
339-
defer r.handlePanic()
340-
return opExec.exec(ctx, r, op.SelSet, e.resolver, serially)
339+
defer r.handlePanic(errorHandler)
340+
return opExec.exec(ctx, r, op.SelSet, e.resolver, serially, errorHandler)
341341
}()
342342

343343
return data, r.errs
@@ -365,12 +365,12 @@ func getOperation(document *query.Document, operationName string) (*query.Operat
365365
}
366366

367367
type iExec interface {
368-
exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool) interface{}
368+
exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool, errorHander errors.ErrorHandler) interface{}
369369
}
370370

371371
type scalarExec struct{}
372372

373-
func (e *scalarExec) exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool) interface{} {
373+
func (e *scalarExec) exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool, errorHandler errors.ErrorHandler) interface{} {
374374
return resolver.Interface()
375375
}
376376

@@ -379,7 +379,7 @@ type listExec struct {
379379
nonNull bool
380380
}
381381

382-
func (e *listExec) exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool) interface{} {
382+
func (e *listExec) exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool, errorHandler errors.ErrorHandler) interface{} {
383383
if !e.nonNull {
384384
if resolver.IsNil() {
385385
return nil
@@ -392,8 +392,8 @@ func (e *listExec) exec(ctx context.Context, r *request, selSet *query.Selection
392392
wg.Add(1)
393393
go func(i int) {
394394
defer wg.Done()
395-
defer r.handlePanic()
396-
l[i] = e.elem.exec(ctx, r, selSet, resolver.Index(i), false)
395+
defer r.handlePanic(errorHandler)
396+
l[i] = e.elem.exec(ctx, r, selSet, resolver.Index(i), false, errorHandler)
397397
}(i)
398398
}
399399
wg.Wait()
@@ -410,10 +410,10 @@ type objectExec struct {
410410

411411
type addResultFn func(key string, value interface{})
412412

413-
func (e *objectExec) exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool) interface{} {
413+
func (e *objectExec) exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool, errorHandler errors.ErrorHandler) interface{} {
414414
if resolver.IsNil() {
415415
if e.nonNull {
416-
r.addError(errors.Errorf("got nil for non-null %q", e.name))
416+
r.addError(errorHandler(fmt.Errorf("got nil for non-null %q", e.name)))
417417
}
418418
return nil
419419
}
@@ -424,31 +424,31 @@ func (e *objectExec) exec(ctx context.Context, r *request, selSet *query.Selecti
424424
results[key] = value
425425
mu.Unlock()
426426
}
427-
e.execSelectionSet(ctx, r, selSet, resolver, addResult, serially)
427+
e.execSelectionSet(ctx, r, selSet, resolver, addResult, serially, errorHandler)
428428
return results
429429
}
430430

431-
func (e *objectExec) execSelectionSet(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, addResult addResultFn, serially bool) {
431+
func (e *objectExec) execSelectionSet(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, addResult addResultFn, serially bool, errorHandler errors.ErrorHandler) {
432432
var wg sync.WaitGroup
433433
for _, sel := range selSet.Selections {
434434
execSel := func(f func()) {
435435
if serially {
436-
defer r.handlePanic()
436+
defer r.handlePanic(errorHandler)
437437
f()
438438
return
439439
}
440440

441441
wg.Add(1)
442442
go func() {
443443
defer wg.Done()
444-
defer r.handlePanic()
444+
defer r.handlePanic(errorHandler)
445445
f()
446446
}()
447447
}
448448

449449
switch sel := sel.(type) {
450450
case *query.Field:
451-
if !skipByDirective(r, sel.Directives) {
451+
if !skipByDirective(r, sel.Directives, errorHandler) {
452452
f := sel
453453
execSel(func() {
454454
switch f.Name {
@@ -467,45 +467,45 @@ func (e *objectExec) execSelectionSet(ctx context.Context, r *request, selSet *q
467467
}
468468

469469
case "__schema":
470-
addResult(f.Alias, introspectSchema(ctx, r, f.SelSet))
470+
addResult(f.Alias, introspectSchema(ctx, r, f.SelSet, errorHandler))
471471

472472
case "__type":
473473
p := valuePacker{valueType: stringType}
474474
v, err := p.pack(r, f.Arguments["name"])
475475
if err != nil {
476-
r.addError(errors.Errorf("%s", err))
476+
r.addError(errorHandler(err))
477477
addResult(f.Alias, nil)
478478
return
479479
}
480-
addResult(f.Alias, introspectType(ctx, r, v.String(), f.SelSet))
480+
addResult(f.Alias, introspectType(ctx, r, v.String(), f.SelSet, errorHandler))
481481

482482
default:
483483
fe, ok := e.fields[f.Name]
484484
if !ok {
485485
panic(fmt.Errorf("%q has no field %q", e.name, f.Name)) // TODO proper error handling
486486
}
487-
fe.execField(ctx, r, f, resolver, addResult)
487+
fe.execField(ctx, r, f, resolver, addResult, errorHandler)
488488
}
489489
})
490490
}
491491

492492
case *query.FragmentSpread:
493-
if !skipByDirective(r, sel.Directives) {
493+
if !skipByDirective(r, sel.Directives, errorHandler) {
494494
fs := sel
495495
execSel(func() {
496496
frag, ok := r.doc.Fragments[fs.Name]
497497
if !ok {
498498
panic(fmt.Errorf("fragment %q not found", fs.Name)) // TODO proper error handling
499499
}
500-
e.execFragment(ctx, r, &frag.Fragment, resolver, addResult)
500+
e.execFragment(ctx, r, &frag.Fragment, resolver, addResult, errorHandler)
501501
})
502502
}
503503

504504
case *query.InlineFragment:
505-
if !skipByDirective(r, sel.Directives) {
505+
if !skipByDirective(r, sel.Directives, errorHandler) {
506506
frag := sel
507507
execSel(func() {
508-
e.execFragment(ctx, r, &frag.Fragment, resolver, addResult)
508+
e.execFragment(ctx, r, &frag.Fragment, resolver, addResult, errorHandler)
509509
})
510510
}
511511

@@ -516,7 +516,7 @@ func (e *objectExec) execSelectionSet(ctx context.Context, r *request, selSet *q
516516
wg.Wait()
517517
}
518518

519-
func (e *objectExec) execFragment(ctx context.Context, r *request, frag *query.Fragment, resolver reflect.Value, addResult addResultFn) {
519+
func (e *objectExec) execFragment(ctx context.Context, r *request, frag *query.Fragment, resolver reflect.Value, addResult addResultFn, errorHandler errors.ErrorHandler) {
520520
if frag.On != "" && frag.On != e.name {
521521
a, ok := e.typeAssertions[frag.On]
522522
if !ok {
@@ -526,10 +526,10 @@ func (e *objectExec) execFragment(ctx context.Context, r *request, frag *query.F
526526
if !out[1].Bool() {
527527
return
528528
}
529-
a.typeExec.(*objectExec).execSelectionSet(ctx, r, frag.SelSet, out[0], addResult, false)
529+
a.typeExec.(*objectExec).execSelectionSet(ctx, r, frag.SelSet, out[0], addResult, false, errorHandler)
530530
return
531531
}
532-
e.execSelectionSet(ctx, r, frag.SelSet, resolver, addResult, false)
532+
e.execSelectionSet(ctx, r, frag.SelSet, resolver, addResult, false, errorHandler)
533533
}
534534

535535
type fieldExec struct {
@@ -542,7 +542,7 @@ type fieldExec struct {
542542
valueExec iExec
543543
}
544544

545-
func (e *fieldExec) execField(ctx context.Context, r *request, f *query.Field, resolver reflect.Value, addResult addResultFn) {
545+
func (e *fieldExec) execField(ctx context.Context, r *request, f *query.Field, resolver reflect.Value, addResult addResultFn, errorHandler errors.ErrorHandler) {
546546
span, spanCtx := opentracing.StartSpanFromContext(ctx, fmt.Sprintf("GraphQL field: %s.%s", e.typeName, e.field.Name))
547547
defer span.Finish()
548548
span.SetTag(OpenTracingTagType, e.typeName)
@@ -551,10 +551,10 @@ func (e *fieldExec) execField(ctx context.Context, r *request, f *query.Field, r
551551
span.SetTag(OpenTracingTagTrivial, true)
552552
}
553553

554-
result, err := e.execField2(spanCtx, r, f, resolver, span)
554+
result, err := e.execField2(spanCtx, r, f, resolver, span, errorHandler)
555555

556556
if err != nil {
557-
r.addError(errors.Errorf("%s", err))
557+
r.addError(errorHandler(err))
558558
addResult(f.Alias, nil) // TODO handle non-nil
559559

560560
ext.Error.Set(span, true)
@@ -565,7 +565,7 @@ func (e *fieldExec) execField(ctx context.Context, r *request, f *query.Field, r
565565
addResult(f.Alias, result)
566566
}
567567

568-
func (e *fieldExec) execField2(ctx context.Context, r *request, f *query.Field, resolver reflect.Value, span opentracing.Span) (interface{}, error) {
568+
func (e *fieldExec) execField2(ctx context.Context, r *request, f *query.Field, resolver reflect.Value, span opentracing.Span, errorHandler errors.ErrorHandler) (interface{}, error) {
569569
var in []reflect.Value
570570

571571
if e.hasContext {
@@ -589,20 +589,20 @@ func (e *fieldExec) execField2(ctx context.Context, r *request, f *query.Field,
589589
return nil, out[1].Interface().(error)
590590
}
591591

592-
return e.valueExec.exec(ctx, r, f.SelSet, out[0], false), nil
592+
return e.valueExec.exec(ctx, r, f.SelSet, out[0], false, errorHandler), nil
593593
}
594594

595595
type typeAssertExec struct {
596596
methodIndex int
597597
typeExec iExec
598598
}
599599

600-
func skipByDirective(r *request, d map[string]*query.Directive) bool {
600+
func skipByDirective(r *request, d map[string]*query.Directive, errorHandler errors.ErrorHandler) bool {
601601
if skip, ok := d["skip"]; ok {
602602
p := valuePacker{valueType: boolType}
603603
v, err := p.pack(r, skip.Arguments["if"])
604604
if err != nil {
605-
r.addError(errors.Errorf("%s", err))
605+
r.addError(errorHandler(err))
606606
}
607607
if err == nil && v.Bool() {
608608
return true
@@ -613,7 +613,7 @@ func skipByDirective(r *request, d map[string]*query.Directive) bool {
613613
p := valuePacker{valueType: boolType}
614614
v, err := p.pack(r, include.Arguments["if"])
615615
if err != nil {
616-
r.addError(errors.Errorf("%s", err))
616+
r.addError(errorHandler(err))
617617
}
618618
if err == nil && !v.Bool() {
619619
return true

0 commit comments

Comments
 (0)