118 lines
2.6 KiB
Go
118 lines
2.6 KiB
Go
package errors
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// ServeHTTP Prepares and encodes given error for HTTP transport
|
|
//
|
|
// mask arg hides extra/debug info
|
|
func ServeHTTP(w http.ResponseWriter, r *http.Request, err error, mask bool) {
|
|
var (
|
|
// Very naive approach on parsing accept headers
|
|
acceptsJson = strings.Contains(r.Header.Get("accept"), "application/json")
|
|
|
|
// due to backward compatibility,
|
|
// proper use of HTTP statuses is disabled for now.
|
|
code = http.StatusOK
|
|
//code = http.StatusInternalServerError
|
|
)
|
|
|
|
// due to backward compatibility,
|
|
// custom HTTP statuses are disabled for now.
|
|
//if e, is := err.(*Error); is {
|
|
// code = e.kind.httpStatus()
|
|
//}
|
|
|
|
if !mask && !acceptsJson {
|
|
// Prettify error for plain text debug output
|
|
w.Header().Set("Content-Type", "plain/text")
|
|
w.WriteHeader(code)
|
|
writeHttpPlain(w, err)
|
|
fmt.Fprintln(w, "Note: you are seeing this because system is running in development mode")
|
|
fmt.Fprintln(w, "and HTTP request is made without \"Accept: .../json\" headers")
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(code)
|
|
writeHttpJSON(w, err, mask)
|
|
}
|
|
|
|
func writeHttpPlain(w io.Writer, err error) {
|
|
var (
|
|
dlmt = strings.Repeat("-", 80)
|
|
|
|
write = func(s string, a ...interface{}) {
|
|
if _, err := fmt.Fprintf(w, s, a...); err != nil {
|
|
panic(fmt.Errorf("failed to write HTTP response: %w", err))
|
|
}
|
|
}
|
|
)
|
|
|
|
write("Error: ")
|
|
write(err.Error())
|
|
write("\n")
|
|
|
|
if err, is := err.(*Error); is {
|
|
write(dlmt + "\n")
|
|
|
|
var (
|
|
ml, kk = err.meta.StringKeys()
|
|
)
|
|
|
|
sort.Strings(kk)
|
|
|
|
for _, key := range kk {
|
|
write("%s:", key)
|
|
write(strings.Repeat(" ", ml-len(key)+1))
|
|
write("%v\n", err.meta[key])
|
|
}
|
|
|
|
if len(err.stack) > 0 {
|
|
write(dlmt + "\n")
|
|
write("Call stack:\n")
|
|
for i, f := range err.stack {
|
|
write("%3d. %s:%d\n %s()\n", len(err.stack)-i, f.File, f.Line, f.Func)
|
|
}
|
|
}
|
|
|
|
if we := err.Unwrap(); we != nil {
|
|
write(dlmt + "\n")
|
|
write("Wrapped error:\n\n")
|
|
writeHttpPlain(w, we)
|
|
}
|
|
}
|
|
write(dlmt + "\n")
|
|
|
|
}
|
|
|
|
func writeHttpJSON(w io.Writer, err error, mask bool) {
|
|
var (
|
|
wrap = struct {
|
|
Error interface{} `json:"error"`
|
|
}{}
|
|
)
|
|
|
|
if se, is := err.(interface{ Safe() bool }); !is || !se.Safe() {
|
|
// trim error details when not debugging or error is not safe
|
|
err = fmt.Errorf(err.Error())
|
|
}
|
|
|
|
if c, is := err.(json.Marshaler); is {
|
|
// take advantage of JSON marshaller on error
|
|
wrap.Error = c
|
|
} else {
|
|
wrap.Error = map[string]string{"message": err.Error()}
|
|
}
|
|
|
|
if err = json.NewEncoder(w).Encode(wrap); err != nil {
|
|
panic(fmt.Errorf("failed to encode error: %w", err))
|
|
}
|
|
}
|