Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
196
pkg/loggerdataresp/data_error_resp.go
Normal file
196
pkg/loggerdataresp/data_error_resp.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package loggerdataresp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"fiskerinc.com/modules/common/staticerrors"
|
||||
er "fiskerinc.com/modules/errors"
|
||||
e "fiskerinc.com/modules/mongo/error"
|
||||
"fiskerinc.com/modules/validator"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"fiskerinc.com/modules/logger"
|
||||
"fiskerinc.com/modules/utils"
|
||||
cKafka "github.com/confluentinc/confluent-kafka-go/v2/kafka"
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Maybe if i move this out we will see all our problems solved
|
||||
|
||||
var MongoUpdateErrorCheck = ErrorCheck{Err: e.ErrUnableToUpdate, Status: http.StatusNotFound}
|
||||
var MongoDeleteErrorCheck = ErrorCheck{Err: e.ErrUnableToDelete, Status: http.StatusNotFound}
|
||||
var PostgresNoRowsErrorCheck = ErrorCheck{Err: pg.ErrNoRows, Status: http.StatusNotFound}
|
||||
var EofErrorCheck = ErrorCheck{Err: io.EOF, Status: http.StatusNotFound}
|
||||
var CustomErrorCheck = ErrorCheck{}
|
||||
|
||||
type ErrorCheck struct {
|
||||
Err error
|
||||
Status int
|
||||
}
|
||||
|
||||
type respErr func(w http.ResponseWriter, status int, message string)
|
||||
|
||||
// log an error message without populating an HTTP response
|
||||
func BadDataError(err error, options ...ErrorCheck) bool {
|
||||
return badDataErrorResp(nil, err, 0, nil, options)
|
||||
}
|
||||
|
||||
// log an error message and populate an HTTP response
|
||||
func BadDataErrorResp(w http.ResponseWriter, err error, defaultStatus int, options ...ErrorCheck) bool {
|
||||
return badDataErrorResp(w, err, defaultStatus, utils.RespError, options)
|
||||
}
|
||||
|
||||
// log an error message and populate an XML HTTP response
|
||||
func BadDataErrorXMLResp(w http.ResponseWriter, err error, defaultStatus int, options ...ErrorCheck) bool {
|
||||
return badDataErrorResp(w, err, defaultStatus, utils.RespXMLError, options)
|
||||
}
|
||||
|
||||
// log an error message and populate an JSON RPC response
|
||||
func BadDataErrorJSONRPCResp(w http.ResponseWriter, errRPC error, err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
utils.RespJSONRPCError(w, errRPC, err.Error())
|
||||
|
||||
if errRPC == utils.ErrJSONRPCServerError || errRPC == utils.ErrJSONRPCInternalError {
|
||||
logger.Error().Err(err).Send()
|
||||
} else {
|
||||
logger.Warn().Err(err).Send()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func badDataErrorResp(w http.ResponseWriter, err error, defaultStatus int, errFunc respErr, errorChecks []ErrorCheck) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cause := errors.Cause(err)
|
||||
|
||||
if ok, msg := validator.GetValidationErrorMsg(cause); ok {
|
||||
logError(err, msg, http.StatusBadRequest, errFunc, w)
|
||||
return true
|
||||
}
|
||||
|
||||
if customErr, ok := cause.(*er.CustomError); ok {
|
||||
logError(customErr.Err(), customErr.Message, customErr.Status, errFunc, w)
|
||||
return true
|
||||
}
|
||||
|
||||
for _, errorCheck := range errorChecks {
|
||||
if errors.Is(errorCheck.Err, cause) {
|
||||
logError(errorCheck.Err, errorCheck.Err.Error(), errorCheck.Status, errFunc, w)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if handleMongoErrors(err, errFunc, w) {
|
||||
return true
|
||||
}
|
||||
|
||||
if handlePgErrors(err, errFunc, w) {
|
||||
return true
|
||||
}
|
||||
|
||||
if handleRedisErrors(err, errFunc, w) {
|
||||
return true
|
||||
}
|
||||
|
||||
if handleSAPErrors(err, errFunc, w) {
|
||||
return true
|
||||
}
|
||||
|
||||
if handleKafkaErrors(err, errFunc, w) {
|
||||
return true
|
||||
}
|
||||
|
||||
logError(err, err.Error(), defaultStatus, errFunc, w)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func handleMongoErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
|
||||
if mongo.IsDuplicateKeyError(err) {
|
||||
logError(err, err.Error(), http.StatusBadRequest, errFunc, w)
|
||||
return true
|
||||
}
|
||||
|
||||
if mongo.IsTimeout(err) {
|
||||
logError(err, err.Error(), http.StatusServiceUnavailable, errFunc, w)
|
||||
return true
|
||||
}
|
||||
|
||||
if mongo.IsNetworkError(err) {
|
||||
logError(err, err.Error(), http.StatusServiceUnavailable, errFunc, w)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func handlePgErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
|
||||
if pgErr, ok := err.(pg.Error); ok {
|
||||
switch pgErr.Field('V') {
|
||||
case "FATAL", "PANIC":
|
||||
logError(pgErr, pgErr.Error(), http.StatusServiceUnavailable, errFunc, w)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func handleRedisErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
|
||||
if opError, ok := err.(*net.OpError); ok {
|
||||
logError(opError, opError.Error(), http.StatusServiceUnavailable, errFunc, w)
|
||||
return true
|
||||
}
|
||||
|
||||
if dnsError, ok := err.(*net.DNSError); ok {
|
||||
logError(dnsError, dnsError.Error(), http.StatusServiceUnavailable, errFunc, w)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func handleSAPErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
|
||||
if strings.HasPrefix(err.Error(), staticerrors.ErrorSAPFailedCall) {
|
||||
logError(err, err.Error(), http.StatusServiceUnavailable, errFunc, w)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func handleKafkaErrors(err error, errFunc respErr, w http.ResponseWriter) bool {
|
||||
if kafkaErr, ok := err.(cKafka.Error); ok {
|
||||
if kafkaErr.IsFatal() {
|
||||
logError(kafkaErr, kafkaErr.Error(), http.StatusServiceUnavailable, errFunc, w)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func logError(err error, errMessage string, status int, errFunc respErr, w http.ResponseWriter) {
|
||||
switch status {
|
||||
case http.StatusServiceUnavailable, http.StatusInternalServerError:
|
||||
logger.Error().Err(err).Send()
|
||||
case http.StatusNotFound, http.StatusBadRequest, http.StatusForbidden:
|
||||
logger.Warn().Err(err).Send()
|
||||
default:
|
||||
logger.Warn().Err(err).Send()
|
||||
}
|
||||
|
||||
if errFunc != nil && w != nil {
|
||||
errFunc(w, status, errMessage)
|
||||
}
|
||||
}
|
||||
102
pkg/loggerdataresp/data_error_resp_test.go
Normal file
102
pkg/loggerdataresp/data_error_resp_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package loggerdataresp_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"fiskerinc.com/modules/testhelper"
|
||||
ve "fiskerinc.com/modules/validator"
|
||||
"fiskerinc.com/modules/loggerdataresp"
|
||||
)
|
||||
|
||||
func TestBadDataErrorResp(t *testing.T) {
|
||||
type testcase struct {
|
||||
Name string
|
||||
Err error
|
||||
ExpectedBody string
|
||||
ExpectedStatus int
|
||||
}
|
||||
|
||||
tests := []testcase{
|
||||
{
|
||||
Name: "Field",
|
||||
Err: &ve.FieldError{
|
||||
ErrorMsg: "TEST ERROR",
|
||||
},
|
||||
ExpectedBody: `{"message":"TEST ERROR","error":"Bad Request"}`,
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "Postgres Constraint",
|
||||
Err: &MockPGError{
|
||||
FieldDesc: "THIS IS A TEST",
|
||||
Integrity: true,
|
||||
},
|
||||
ExpectedBody: `{"message":"THIS IS A TEST","error":"Bad Request"}`,
|
||||
ExpectedStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
Name: "Postgres Non-Constraint",
|
||||
Err: &MockPGError{
|
||||
ErrorDesc: "GENERAL ERROR",
|
||||
Integrity: false,
|
||||
},
|
||||
ExpectedBody: `{"message":"GENERAL ERROR","error":"Service Unavailable"}`,
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
{
|
||||
Name: "SAP not available error",
|
||||
Err: errors.New("calling SAP failed"),
|
||||
ExpectedBody: `{"message":"calling SAP failed","error":"Service Unavailable"}`,
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
{
|
||||
Name: "EOF error check",
|
||||
Err: io.EOF,
|
||||
ExpectedBody: `{"message":"EOF","error":"Not Found"}`,
|
||||
ExpectedStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
Name: "General",
|
||||
Err: fmt.Errorf("GENERAL ERROR"),
|
||||
ExpectedBody: `{"message":"GENERAL ERROR","error":"Service Unavailable"}`,
|
||||
ExpectedStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
res := httptest.NewRecorder()
|
||||
loggerdataresp.BadDataErrorResp(res, test.Err, http.StatusServiceUnavailable, loggerdataresp.EofErrorCheck)
|
||||
|
||||
if res.Result().StatusCode != test.ExpectedStatus {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedStatus, res.Result().StatusCode)
|
||||
}
|
||||
|
||||
body := res.Body.String()
|
||||
if body != test.ExpectedBody {
|
||||
t.Errorf(testhelper.TestErrorTemplate, test.Name, test.ExpectedBody, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type MockPGError struct {
|
||||
ErrorDesc string
|
||||
FieldDesc string
|
||||
Integrity bool
|
||||
}
|
||||
|
||||
func (mpg *MockPGError) Error() string {
|
||||
return mpg.ErrorDesc
|
||||
}
|
||||
|
||||
func (mpg *MockPGError) Field(field byte) string {
|
||||
return mpg.FieldDesc
|
||||
}
|
||||
|
||||
func (mpg *MockPGError) IntegrityViolation() bool {
|
||||
return mpg.Integrity
|
||||
}
|
||||
2
pkg/loggerdataresp/readme.md
Normal file
2
pkg/loggerdataresp/readme.md
Normal file
@@ -0,0 +1,2 @@
|
||||
A set of checks that filters the error that is returned to the user in a html request
|
||||
also changes what is being logged
|
||||
Reference in New Issue
Block a user