3
0
corteza/pkg/errors/http.go
2020-11-27 18:59:07 +01:00

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))
}
}