github.com/fedir/buffalo@v0.11.1/errors.go (about)

     1  package buffalo
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/gobuffalo/plush"
    11  	"github.com/gobuffalo/x/httpx"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // HTTPError a typed error returned by http Handlers and used for choosing error handlers
    16  type HTTPError struct {
    17  	Status int   `json:"status"`
    18  	Cause  error `json:"error"`
    19  }
    20  
    21  func (h HTTPError) Error() string {
    22  	return h.Cause.Error()
    23  }
    24  
    25  // ErrorHandler interface for handling an error for a
    26  // specific status code.
    27  type ErrorHandler func(int, error, Context) error
    28  
    29  // ErrorHandlers is used to hold a list of ErrorHandler
    30  // types that can be used to handle specific status codes.
    31  /*
    32  	a.ErrorHandlers[500] = func(status int, err error, c buffalo.Context) error {
    33  		res := c.Response()
    34  		res.WriteHeader(status)
    35  		res.Write([]byte(err.Error()))
    36  		return nil
    37  	}
    38  */
    39  type ErrorHandlers map[int]ErrorHandler
    40  
    41  // Get a registered ErrorHandler for this status code. If
    42  // no ErrorHandler has been registered, a default one will
    43  // be returned.
    44  func (e ErrorHandlers) Get(status int) ErrorHandler {
    45  	if eh, ok := e[status]; ok {
    46  		return eh
    47  	}
    48  	return defaultErrorHandler
    49  }
    50  
    51  // PanicHandler recovers from panics gracefully and calls
    52  // the error handling code for a 500 error.
    53  func (a *App) PanicHandler(next Handler) Handler {
    54  	return func(c Context) error {
    55  		defer func() { //catch or finally
    56  			r := recover()
    57  			var err error
    58  			if r != nil { //catch
    59  				switch t := r.(type) {
    60  				case error:
    61  					err = errors.WithStack(t)
    62  				case string:
    63  					err = errors.WithStack(errors.New(t))
    64  				default:
    65  					err = errors.New(fmt.Sprint(t))
    66  				}
    67  				eh := a.ErrorHandlers.Get(500)
    68  				eh(500, err, c)
    69  			}
    70  		}()
    71  		return next(c)
    72  	}
    73  }
    74  
    75  func defaultErrorHandler(status int, err error, c Context) error {
    76  	env := c.Value("env")
    77  	c.Logger().Error(err)
    78  	if env != nil && env.(string) == "production" {
    79  		c.Response().WriteHeader(status)
    80  		c.Response().Write([]byte(prodErrorTmpl))
    81  		return nil
    82  	}
    83  	c.Response().WriteHeader(status)
    84  
    85  	msg := fmt.Sprintf("%+v", err)
    86  	ct := httpx.ContentType(c.Request())
    87  	switch strings.ToLower(ct) {
    88  	case "application/json", "text/json", "json":
    89  		err = json.NewEncoder(c.Response()).Encode(map[string]interface{}{
    90  			"error": msg,
    91  			"code":  status,
    92  		})
    93  	case "application/xml", "text/xml", "xml":
    94  	default:
    95  		err := c.Request().ParseForm()
    96  		routes := c.Value("routes")
    97  		if cd, ok := c.(*DefaultContext); ok {
    98  			delete(cd.data, "app")
    99  			delete(cd.data, "routes")
   100  		}
   101  		data := map[string]interface{}{
   102  			"routes":      routes,
   103  			"error":       msg,
   104  			"status":      status,
   105  			"data":        c.Data(),
   106  			"params":      c.Params(),
   107  			"posted_form": c.Request().Form,
   108  			"context":     c,
   109  			"headers":     inspectHeaders(c.Request().Header),
   110  			"inspect": func(v interface{}) string {
   111  				return fmt.Sprintf("%+v", v)
   112  			},
   113  		}
   114  		ctx := plush.NewContextWith(data)
   115  		t, err := plush.Render(devErrorTmpl, ctx)
   116  		if err != nil {
   117  			return errors.WithStack(err)
   118  		}
   119  		res := c.Response()
   120  		_, err = res.Write([]byte(t))
   121  		return err
   122  	}
   123  	return err
   124  }
   125  
   126  type inspectHeaders http.Header
   127  
   128  func (i inspectHeaders) String() string {
   129  
   130  	bb := make([]string, 0, len(i))
   131  
   132  	for k, v := range i {
   133  		bb = append(bb, fmt.Sprintf("%s: %s", k, v))
   134  	}
   135  	sort.Strings(bb)
   136  	return strings.Join(bb, "\n\n")
   137  }
   138  
   139  var devErrorTmpl = `
   140  <html>
   141  <head>
   142    <title><%= status %> - ERROR!</title>
   143    <style>html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}header{display:block}a{background-color:transparent}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}img{border:0}pre{overflow:auto}code,pre{font-family:monospace,monospace;font-size:1em}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h3{orphans:3;widows:3}h3{page-break-after:avoid}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}h1,h3{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1,h3{margin-top:20px;margin-bottom:10px}h1{font-size:36px}h3{font-size:24px}code,pre{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.row{margin-right:-15px;margin-left:-15px}.col-md-1,.col-md-10,.col-md-12,.col-sm-2,.col-sm-6,.col-xs-3,.col-xs-7{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-3,.col-xs-7{float:left}.col-xs-7{width:58.33333333%}.col-xs-3{width:25%}@media (min-width:768px){.col-sm-2,.col-sm-6{float:left}.col-sm-6{width:50%}.col-sm-2{width:16.66666667%}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-12{float:left}.col-md-12{width:100%}.col-md-10{width:83.33333333%}.col-md-1{width:8.33333333%}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>thead:first-child>tr:first-child>th{border-top:0}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.container:after,.container:before,.row:after,.row:before{display:table;content:" "}.container:after,.row:after{clear:both}@-ms-viewport{width:device-width}
   144  	h1{margin-top:20px}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff;margin:0}h1{margin-bottom:10px;font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.table{margin-bottom:20px}h1{font-size:36px}a{color:#337ab7;text-decoration:none}a:hover{color:#23527c}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.table{width:100%;max-width:100%;background-color:transparent;border-spacing:0;border-collapse:collapse}.table-striped>tbody{background-color:#f9f9f9}.table>tbody>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{border-top:0;vertical-align:bottom;border-bottom:2px solid #ddd;text-align:left}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace}.row{margin-right:-15px;margin-left:-15px}.col-md-10{float:left;position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-md-10{width:83.33333333%}img{vertical-align:middle;border:0}.container{min-width:320px}body{font-family:helvetica}table{font-size:14px}table.table tbody tr td{border-top:0;padding:10px}pre{white-space:pre-line;margin-bottom:10px;max-height:275px;overflow:scroll}header{background-color:#ed605e;padding:10px 20px;box-sizing:border-box}.logo img{width:80px}.titles h1{font-size:30px;font-weight:300;color:#fff;margin:24px 0}.content h3{color:gray;margin:25px 0}.foot{padding:5px 0 20px;text-align:right;color:#c5c5c5;font-weight:300}.foot a{color:#8b8b8b;text-decoration:underline}.centered{text-align:center}@media all and (max-width:500px){.titles h1{font-size:25px;margin:26px 0}}@media all and (max-width:530px){.titles h1{font-size:20px;margin:24px 0}.logo{padding:0}.logo img{width:100%;max-width:80px}}
   145    </style>
   146  </head>
   147  
   148  <body>
   149    <header>
   150      <div class="container">
   151        <div class="row">
   152          <div class="col-md-1 col-sm-2 col-xs-3 logo">
   153            <a href="/"><img src="https://gobuffalo.io/assets/images/logo_med.png" alt=""></a>
   154          </div>
   155          <div class="col-md-10 col-sm-6 col-xs-7 titles">
   156            <h1>
   157              <%= status %> - ERROR!
   158            </h1>
   159          </div>
   160        </div>
   161      </div>
   162    </header>
   163  
   164    <div class="container content">
   165      <div class="row">
   166        <div class="col-md-12">
   167          <h3>Error Trace</h3>
   168          <pre><%= error %></pre>
   169  
   170          <h3>Context</h3>
   171          <pre><%= inspect(context) %></pre>
   172  
   173          <h3>Parameters</h3>
   174          <pre><%= inspect(params) %></pre>
   175  
   176          <h3>Headers</h3>
   177          <pre><%= inspect(headers) %></pre>
   178  
   179          <h3>Form</h3>
   180          <pre><%= inspect(posted_form) %></pre>
   181  
   182          <h3>Routes</h3>
   183          <table class="table table-striped">
   184            <thead>
   185              <tr text-align="left">
   186                <th class="centered">METHOD</th>
   187                <th>PATH</th>
   188                <th>NAME</th>
   189                <th>HANDLER</th>
   190              </tr>
   191            </thead>
   192            <tbody>
   193  
   194              <%= for (r) in routes { %>
   195                <tr>
   196                  <td class="centered">
   197                    <%= r.Method %>
   198                  </td>
   199                  <td>
   200                    <%= if (r.Method != "GET" || r.Path ~= "{") { %>
   201                      <%= r.Path %>
   202                    <% } else { %>
   203                      <a href="<%= r.Path %>"><%= r.Path %></a>
   204                    <% } %>
   205                  </td>
   206                  <td>
   207                    <%= r.PathName %>
   208                  </td>
   209                  <td><code><%= r.HandlerName %></code></td>
   210                </tr>
   211              <% } %>
   212  
   213            </tbody>
   214          </table>
   215        </div>
   216      </div>
   217      <div class="foot"> <span> Powered by <a href="http://gobuffalo.io/">gobuffalo.io</a></span></div>
   218    </div>
   219  </body>
   220  </html>
   221  `
   222  var prodErrorTmpl = `
   223  <h1>We're Sorry!</h1>
   224  <p>
   225  It looks like something went wrong! Don't worry, we are aware of the problem and are looking into it.
   226  </p>
   227  <p>
   228  Sorry if this has caused you any problems. Please check back again later.
   229  </p>
   230  `