snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
167
server/pkg/errors/http.go
Normal file
167
server/pkg/errors/http.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
)
|
||||
|
||||
type (
|
||||
translatable interface {
|
||||
Translate(tr func(string, string, ...string) string) error
|
||||
}
|
||||
)
|
||||
|
||||
// ServeHTTP Prepares and encodes given error for HTTP transport
|
||||
//
|
||||
// mask arg hides extra/debug info
|
||||
//
|
||||
// Proper HTTP status codes are generally not used in the API due to compatibility issues
|
||||
// This should be addressed in the future versions when/if we restructure the API
|
||||
func ServeHTTP(w http.ResponseWriter, r *http.Request, err error, mask bool) {
|
||||
// due to backward compatibility,
|
||||
// custom HTTP statuses are disabled for now.
|
||||
ServeHTTPWithCode(w, r, http.StatusOK, err, mask)
|
||||
}
|
||||
|
||||
// ProperlyServeHTTP Prepares and encodes given error for HTTP transport, same as ServeHTTP but with proper status codes
|
||||
func ProperlyServeHTTP(w http.ResponseWriter, r *http.Request, err error, mask bool) {
|
||||
var (
|
||||
code = http.StatusInternalServerError
|
||||
)
|
||||
|
||||
if e, is := err.(*Error); is {
|
||||
code = e.kind.httpStatus()
|
||||
}
|
||||
|
||||
ServeHTTPWithCode(w, r, code, err, mask)
|
||||
}
|
||||
|
||||
// Serves error via
|
||||
func ServeHTTPWithCode(w http.ResponseWriter, r *http.Request, code int, err error, mask bool) {
|
||||
var (
|
||||
// Very naive approach on parsing accept headers
|
||||
acceptsJson = strings.Contains(r.Header.Get("accept"), "application/json")
|
||||
)
|
||||
|
||||
if !mask && !acceptsJson {
|
||||
// Prettify error for plain text debug output
|
||||
w.Header().Set("Content-Type", "plain/text")
|
||||
w.WriteHeader(code)
|
||||
writeHttpPlain(w, err, mask)
|
||||
_, _ = 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(r.Context(), w, err, mask)
|
||||
}
|
||||
|
||||
func writeHttpPlain(w io.Writer, err error, mask bool) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// handle client-aborted connections with no output
|
||||
if errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {
|
||||
return
|
||||
}
|
||||
|
||||
write("Error: ")
|
||||
write(err.Error())
|
||||
write("\n")
|
||||
|
||||
if _, is := err.(interface{ Safe() bool }); !is || mask {
|
||||
// do not output any details on un-safe errors or
|
||||
// when a masked output is preferred
|
||||
return
|
||||
}
|
||||
|
||||
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, mask)
|
||||
}
|
||||
}
|
||||
write(dlmt + "\n")
|
||||
|
||||
}
|
||||
|
||||
// writeHttpJSON
|
||||
func writeHttpJSON(ctx context.Context, w io.Writer, err error, mask bool) {
|
||||
var (
|
||||
wrap = struct {
|
||||
Error interface{} `json:"error"`
|
||||
}{}
|
||||
)
|
||||
|
||||
// handle client-aborted connections with no output
|
||||
if errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {
|
||||
return
|
||||
}
|
||||
|
||||
if se, is := err.(interface{ Safe() bool }); !is || !se.Safe() || mask {
|
||||
// trim error details when not debugging or error is not safe or maske
|
||||
err = errors.New(err.Error())
|
||||
}
|
||||
|
||||
// if error is translatable, pass in the lambda that returns
|
||||
// translated error
|
||||
//
|
||||
// we're using global locale service directly for now
|
||||
if tr, is := err.(translatable); is {
|
||||
err = tr.Translate(func(ns string, key string, pairs ...string) string {
|
||||
return locale.Global().T(ctx, ns, key, pairs...)
|
||||
})
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user