github.com/zak-blake/goa@v1.4.1/middleware/error_handler.go (about)

     1  package middleware
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	"context"
     8  
     9  	"github.com/goadesign/goa"
    10  )
    11  
    12  // ErrorHandler turns a Go error into an HTTP response. It should be placed in the middleware chain
    13  // below the logger middleware so the logger properly logs the HTTP response. ErrorHandler
    14  // understands instances of goa.ServiceError and returns the status and response body embodied in
    15  // them, it turns other Go error types into a 500 internal error response.
    16  // If verbose is false the details of internal errors is not included in HTTP responses.
    17  // If you use github.com/pkg/errors then wrapping the error will allow a trace to be printed to the logs
    18  func ErrorHandler(service *goa.Service, verbose bool) goa.Middleware {
    19  	return func(h goa.Handler) goa.Handler {
    20  		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
    21  			e := h(ctx, rw, req)
    22  			if e == nil {
    23  				return nil
    24  			}
    25  			cause := cause(e)
    26  			status := http.StatusInternalServerError
    27  			var respBody interface{}
    28  			if err, ok := cause.(goa.ServiceError); ok {
    29  				status = err.ResponseStatus()
    30  				respBody = err
    31  				goa.ContextResponse(ctx).ErrorCode = err.Token()
    32  				rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier)
    33  			} else {
    34  				respBody = e.Error()
    35  				rw.Header().Set("Content-Type", "text/plain")
    36  			}
    37  			if status == http.StatusInternalServerError {
    38  				reqID := ctx.Value(reqIDKey)
    39  				if reqID == nil {
    40  					reqID = shortID()
    41  					ctx = context.WithValue(ctx, reqIDKey, reqID)
    42  				}
    43  				goa.LogError(ctx, "uncaught error", "err", fmt.Sprintf("%+v", e), "id", reqID, "msg", respBody)
    44  				if !verbose {
    45  					rw.Header().Set("Content-Type", goa.ErrorMediaIdentifier)
    46  					msg := fmt.Sprintf("%s [%s]", http.StatusText(http.StatusInternalServerError), reqID)
    47  					respBody = goa.ErrInternal(msg)
    48  					// Preserve the ID of the original error as that's what gets logged, the client
    49  					// received error ID must match the original
    50  					if origErrID := goa.ContextResponse(ctx).ErrorCode; origErrID != "" {
    51  						respBody.(*goa.ErrorResponse).ID = origErrID
    52  					}
    53  				}
    54  			}
    55  			return service.Send(ctx, status, respBody)
    56  		}
    57  	}
    58  }
    59  
    60  // Cause returns the underlying cause of the error, if possible.
    61  // An error value has a cause if it implements the following
    62  // interface:
    63  //
    64  //     type causer interface {
    65  //            Cause() error
    66  //     }
    67  //
    68  // If the error does not implement Cause, the original error will
    69  // be returned. If the error is nil, nil will be returned without further
    70  // investigation.
    71  func cause(e error) error {
    72  	type causer interface {
    73  		Cause() error
    74  	}
    75  	for {
    76  		cause, ok := e.(causer)
    77  		if !ok {
    78  			break
    79  		}
    80  		c := cause.Cause()
    81  		if c == nil {
    82  			break
    83  		}
    84  		e = c
    85  	}
    86  	return e
    87  }