package apierrors import ( "encoding/json" "net/http" "strings" "github.com/porter-dev/porter/api/server/shared/apierrors/alerter" "github.com/porter-dev/porter/api/types" "github.com/porter-dev/porter/pkg/logger" ) type RequestError interface { Error() string ExternalError() string InternalError() string GetStatusCode() int } type ErrInternal struct { err error } func NewErrInternal(err error) RequestError { return &ErrInternal{err} } func (e *ErrInternal) Error() string { return e.err.Error() } func (e *ErrInternal) InternalError() string { return e.err.Error() } func (e *ErrInternal) ExternalError() string { return "An internal error occurred." } func (e *ErrInternal) GetStatusCode() int { return http.StatusInternalServerError } type ErrForbidden struct { err error } func NewErrForbidden(err error) RequestError { return &ErrForbidden{err} } func (e *ErrForbidden) Error() string { return e.err.Error() } func (e *ErrForbidden) InternalError() string { return e.err.Error() } func (e *ErrForbidden) ExternalError() string { return "Forbidden" } func (e *ErrForbidden) GetStatusCode() int { return http.StatusForbidden } // errors that should be passed directly, with no filter type ErrPassThroughToClient struct { err error statusCode int errDetails []string } func NewErrPassThroughToClient(err error, statusCode int, details ...string) RequestError { return &ErrPassThroughToClient{err, statusCode, details} } func (e *ErrPassThroughToClient) Error() string { return e.err.Error() } func (e *ErrPassThroughToClient) InternalError() string { return e.err.Error() + strings.Join(e.errDetails, ",") } func (e *ErrPassThroughToClient) ExternalError() string { return e.err.Error() } func (e *ErrPassThroughToClient) GetStatusCode() int { return e.statusCode } // errors that denote that a resource was not found type ErrNotFound struct { err error } func NewErrNotFound(err error) RequestError { return &ErrNotFound{err} } func (e *ErrNotFound) Error() string { return e.err.Error() } func (e *ErrNotFound) InternalError() string { return e.err.Error() } func (e *ErrNotFound) ExternalError() string { return "Resource not found." } func (e *ErrNotFound) GetStatusCode() int { return http.StatusNotFound } type ErrorOpts struct { Code uint } func HandleAPIError( l *logger.Logger, al alerter.Alerter, w http.ResponseWriter, r *http.Request, err RequestError, writeErr bool, opts ...ErrorOpts, ) { extErrorStr := err.ExternalError() // log the internal error event := l.Warn(). Str("internal_error", err.InternalError()). Str("external_error", extErrorStr) data := logger.AddLoggingContextScopes(r.Context(), event) logger.AddLoggingRequestMeta(r, event) event.Send() // if the status code is internal server error, use alerter if err.GetStatusCode() == http.StatusInternalServerError && al != nil { data["method"] = r.Method data["url"] = r.URL.String() eventID := al.SendAlert(r.Context(), err, data) if _, ok := al.(*alerter.SentryAlerter); ok && eventID != nil { data["sentry_event_id"] = *eventID } } if writeErr { // send the external error resp := &types.ExternalError{ Error: extErrorStr, } if len(opts) > 0 { resp.Code = opts[0].Code } // write the status code w.WriteHeader(err.GetStatusCode()) writerErr := json.NewEncoder(w).Encode(resp) if writerErr != nil { event := l.Error(). Err(writerErr) logger.AddLoggingContextScopes(r.Context(), event) logger.AddLoggingRequestMeta(r, event) event.Send() } } }