diff --git a/aerr/aerr.go b/aerr/aerr.go new file mode 100644 index 0000000..f6c4538 --- /dev/null +++ b/aerr/aerr.go @@ -0,0 +1,109 @@ +package aerr + +import ( + "encoding/json" + "errors" + "fmt" +) + +// Error represent a API Error. It will mainly be used in the Handler. +type Error struct { + // shown in Json + Code int `json:"code"` + DebugId string `json:"debug_id"` + Msg string `json:"message"` + + // not in Json + httpCode int `json:"-"` + origin error `json:"-"` +} + +// Error implements error interface. +func (e *Error) Error() string { + s := fmt.Sprintf("Error: %d.", e.Code) + + // if we have a message, add it + if e.Msg != "" { + s = fmt.Sprintf("%s Message: %s.", s, e.Msg) + } + + // if we have an origin message, add it. + if e.origin != nil { + s = fmt.Sprintf("%s Origin Message: %s.", s, e.origin.Error()) + } + + // add debugId + if e.DebugId != "" { + s = fmt.Sprintf("%s - %s", s, e.DebugId) + } + + return s +} + +// MarshalJSON will JSON marshal an Error. It will try to get message from the origin if there is none in the current Error. +func (e *Error) MarshalJSON() ([]byte, error) { + type TmpJson Error + + // let's get the origin message if the current message is empty + if e.Msg == "" && e.origin != nil { + var originE *Error + + if errors.As(e.origin, &originE) { + e.Msg = originE.Message() + } else { + e.Msg = e.origin.Error() + } + } + + return json.Marshal((*TmpJson)(e)) +} + +// Add a bunch of setter / Getter + +// SetDebugID allow to overide/set a debug Id and return the error. +func (e *Error) SetDebugID(d string) *Error { + e.DebugId = d + return e +} + +// SetOrigin allow to set an origin error to the error and return that error. +func (e *Error) SetOrigin(origin error) *Error { + e.origin = origin + return e +} + +// SetMessage allow to set a message to the error and return that error. +func (e *Error) SetMessage(msg string) *Error { + e.Msg = msg + return e +} + +// SetCode allow to set a specific code to the error and return that error. +func (e *Error) SetCode(code int) *Error { + e.httpCode = code + return e +} + +// Message will get the current error Msg. If none, it will try to go to origins until finding a message. +func (e *Error) Message() string { + if e.Msg == "" && e.origin != nil { + var originE *Error + + if errors.As(e.origin, &originE) { + return originE.Message() + } + + return e.origin.Error() + } + return e.Msg +} + +// HttpCode will return the http code of the error. +func (e *Error) HttpCode() int { + return e.httpCode +} + +// Unwrap will return the origin error. +func (e *Error) Unwrap() error { + return e.origin +} diff --git a/aerr/client.go b/aerr/client.go new file mode 100644 index 0000000..6321cd8 --- /dev/null +++ b/aerr/client.go @@ -0,0 +1,425 @@ +package aerr + +import ( + "fmt" +) + +// NewBadRequest will generate an HTTP Error 400. +func NewBadRequest() *Error { + return &Error{ + Code: 400, + httpCode: 400, + Msg: "Bad Request", + } +} + +// NewBadRequestf will generate an HTTP Error 400 with a custom message. +func NewBadRequestf(format string, a ...any) *Error { + aErr := NewBadRequest() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewUnauthorized will generate an HTTP Error 401. +func NewUnauthorized() *Error { + return &Error{ + Code: 401, + httpCode: 401, + Msg: "Unauthorized", + } +} + +// NewUnauthorizedf will generate an HTTP Error 401 with a custom message. +func NewUnauthorizedf(format string, a ...any) *Error { + aErr := NewUnauthorized() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewPaymentRequired will generate an HTTP Error 402. +func NewPaymentRequired() *Error { + return &Error{ + Code: 402, + httpCode: 402, + Msg: "Payment Required", + } +} + +// NewPaymentRequiredf will generate an HTTP Error 402 with a custom message. +func NewPaymentRequiredf(format string, a ...any) *Error { + aErr := NewPaymentRequired() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewForbidden will generate an HTTP Error 403. +func NewForbidden() *Error { + return &Error{ + Code: 403, + httpCode: 403, + Msg: "Forbidden", + } +} + +// NewForbiddenf will generate an HTTP Error 403 with a custom message. +func NewForbiddenf(format string, a ...any) *Error { + aErr := NewForbidden() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewNotFound will generate an HTTP Error 404. +func NewNotFound() *Error { + return &Error{ + Code: 404, + httpCode: 404, + Msg: "Not Found", + } +} + +// NewNotFoundf will generate an HTTP Error 404 with a custom message. +func NewNotFoundf(format string, a ...any) *Error { + aErr := NewNotFound() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewMethodNotAllowed will generate an HTTP Error 405. +func NewMethodNotAllowed() *Error { + return &Error{ + Code: 405, + httpCode: 405, + Msg: "Method Not Allowed", + } +} + +// NewMethodNotAllowedf will generate an HTTP Error 405 with a custom message. +func NewMethodNotAllowedf(format string, a ...any) *Error { + aErr := NewMethodNotAllowed() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewNotAcceptable will generate an HTTP Error 406. +func NewNotAcceptable() *Error { + return &Error{ + Code: 406, + httpCode: 406, + Msg: "Not Acceptable", + } +} + +// NewNotAcceptablef will generate an HTTP Error 406 with a custom message. +func NewNotAcceptablef(format string, a ...any) *Error { + aErr := NewNotAcceptable() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewProxyAuthenticationRequired will generate an HTTP Error 407. +func NewProxyAuthenticationRequired() *Error { + return &Error{ + Code: 407, + httpCode: 407, + Msg: "Proxy Authentication Required", + } +} + +// NewProxyAuthenticationRequiredf will generate an HTTP Error 407 with a custom message. +func NewProxyAuthenticationRequiredf(format string, a ...any) *Error { + aErr := NewProxyAuthenticationRequired() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewRequestTimeout will generate an HTTP Error 408. +func NewRequestTimeout() *Error { + return &Error{ + Code: 408, + httpCode: 408, + Msg: "Request Timeout", + } +} + +// NewRequestTimeoutf will generate an HTTP Error 408 with a custom message. +func NewRequestTimeoutf(format string, a ...any) *Error { + aErr := NewRequestTimeout() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewConflict will generate an HTTP Error 409. +func NewConflict() *Error { + return &Error{ + Code: 409, + httpCode: 409, + Msg: "Conflict", + } +} + +// NewConflictf will generate an HTTP Error 409 with a custom message. +func NewConflictf(format string, a ...any) *Error { + aErr := NewConflict() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewGone will generate an HTTP Error 410. +func NewGone() *Error { + return &Error{ + Code: 410, + httpCode: 410, + Msg: "Gone", + } +} + +// NewGonef will generate an HTTP Error 410 with a custom message. +func NewGonef(format string, a ...any) *Error { + aErr := NewGone() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewLengthRequired will generate an HTTP Error 411. +func NewLengthRequired() *Error { + return &Error{ + Code: 411, + httpCode: 411, + Msg: "Length Required", + } +} + +// NewLengthRequiredf will generate an HTTP Error 411 with a custom message. +func NewLengthRequiredf(format string, a ...any) *Error { + aErr := NewLengthRequired() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewPreconditionFailed will generate an HTTP Error 412. +func NewPreconditionFailed() *Error { + return &Error{ + Code: 412, + httpCode: 412, + Msg: "Precondition Failed", + } +} + +// NewPreconditionFailedf will generate an HTTP Error 412 with a custom message. +func NewPreconditionFailedf(format string, a ...any) *Error { + aErr := NewPreconditionFailed() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewPayloadTooLarge will generate an HTTP Error 413. +func NewPayloadTooLarge() *Error { + return &Error{ + Code: 413, + httpCode: 413, + Msg: "Payload Too Large", + } +} + +// NewPayloadTooLargef will generate an HTTP Error 413 with a custom message. +func NewPayloadTooLargef(format string, a ...any) *Error { + aErr := NewPayloadTooLarge() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewURITooLong will generate an HTTP Error 414. +func NewURITooLong() *Error { + return &Error{ + Code: 414, + httpCode: 414, + Msg: "URI Too Long", + } +} + +// NewURITooLongf will generate an HTTP Error 414 with a custom message. +func NewURITooLongf(format string, a ...any) *Error { + aErr := NewURITooLong() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewUnsupportedMediaType will generate an HTTP Error 415. +func NewUnsupportedMediaType() *Error { + return &Error{ + Code: 415, + httpCode: 415, + Msg: "Unsupported Media Type", + } +} + +// NewUnsupportedMediaTypef will generate an HTTP Error 415 with a custom message. +func NewUnsupportedMediaTypef(format string, a ...any) *Error { + aErr := NewUnsupportedMediaType() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewRangeNotSatisfiable will generate an HTTP Error 416. +func NewRangeNotSatisfiable() *Error { + return &Error{ + Code: 416, + httpCode: 416, + Msg: "Range Not Satisfiable", + } +} + +// NewRangeNotSatisfiablef will generate an HTTP Error 416 with a custom message. +func NewRangeNotSatisfiablef(format string, a ...any) *Error { + aErr := NewRangeNotSatisfiable() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewExpectationFailed will generate an HTTP Error 417. +func NewExpectationFailed() *Error { + return &Error{ + Code: 417, + httpCode: 417, + Msg: "Expectation Failed", + } +} + +// NewExpectationFailedf will generate an HTTP Error 417 with a custom message. +func NewExpectationFailedf(format string, a ...any) *Error { + aErr := NewExpectationFailed() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewMisdirectedRequest will generate an HTTP Error 421. +func NewMisdirectedRequest() *Error { + return &Error{ + Code: 421, + httpCode: 421, + Msg: "Misdirected Request", + } +} + +// NewMisdirectedRequestf will generate an HTTP Error 421 with a custom message. +func NewMisdirectedRequestf(format string, a ...any) *Error { + aErr := NewMisdirectedRequest() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewUnprocessableEntity will generate an HTTP Error 422. +func NewUnprocessableEntity() *Error { + return &Error{ + Code: 422, + httpCode: 422, + Msg: "Unprocessable Entity", + } +} + +// NewUnprocessableEntityf will generate an HTTP Error 422 with a custom message. +func NewUnprocessableEntityf(format string, a ...any) *Error { + aErr := NewUnprocessableEntity() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewLocked will generate an HTTP Error 423. +func NewLocked() *Error { + return &Error{ + Code: 423, + httpCode: 423, + Msg: "Locked", + } +} + +// NewLockedf will generate an HTTP Error 423 with a custom message. +func NewLockedf(format string, a ...any) *Error { + aErr := NewLocked() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewFailedDependency will generate an HTTP Error 424. +func NewFailedDependency() *Error { + return &Error{ + Code: 424, + httpCode: 424, + Msg: "Failed Dependency", + } +} + +// NewFailedDependencyf will generate an HTTP Error 424 with a custom message. +func NewFailedDependencyf(format string, a ...any) *Error { + aErr := NewFailedDependency() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewTooEarly will generate an HTTP Error 425. +func NewTooEarly() *Error { + return &Error{ + Code: 425, + httpCode: 425, + Msg: "Too Early", + } +} + +// NewTooEarlyf will generate an HTTP Error 425 with a custom message. +func NewTooEarlyf(format string, a ...any) *Error { + aErr := NewTooEarly() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewUpgradeRequired will generate an HTTP Error 426. +func NewUpgradeRequired() *Error { + return &Error{ + Code: 426, + httpCode: 426, + Msg: "Upgrade Required", + } +} + +// NewUpgradeRequiredf will generate an HTTP Error 426 with a custom message. +func NewUpgradeRequiredf(format string, a ...any) *Error { + aErr := NewUpgradeRequired() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewPreconditionRequired will generate an HTTP Error 428. +func NewPreconditionRequired() *Error { + return &Error{ + Code: 428, + httpCode: 428, + Msg: "Precondition Required", + } +} + +// NewPreconditionRequiredf will generate an HTTP Error 428 with a custom message. +func NewPreconditionRequiredf(format string, a ...any) *Error { + aErr := NewPreconditionRequired() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewTooManyRequests will generate an HTTP Error 429. +func NewTooManyRequests() *Error { + return &Error{ + Code: 429, + httpCode: 429, + Msg: "Too Many Requests", + } +} + +// NewTooManyRequestsf will generate an HTTP Error 429 with a custom message. +func NewTooManyRequestsf(format string, a ...any) *Error { + aErr := NewTooManyRequests() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewRequestHeaderFieldsTooLarge will generate an HTTP Error 431. +func NewRequestHeaderFieldsTooLarge() *Error { + return &Error{ + Code: 431, + httpCode: 431, + Msg: "Request Header Fields Too Large", + } +} + +// NewRequestHeaderFieldsTooLargef will generate an HTTP Error 431 with a custom message. +func NewRequestHeaderFieldsTooLargef(format string, a ...any) *Error { + aErr := NewRequestHeaderFieldsTooLarge() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewUnavailableForLegalReasons will generate an HTTP Error 451. +func NewUnavailableForLegalReasons() *Error { + return &Error{ + Code: 451, + httpCode: 451, + Msg: "Unavailable For Legal Reasons", + } +} + +// NewUnavailableForLegalReasonsf will generate an HTTP Error 451 with a custom message. +func NewUnavailableForLegalReasonsf(format string, a ...any) *Error { + aErr := NewUnavailableForLegalReasons() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} diff --git a/aerr/client.txt b/aerr/client.txt new file mode 100644 index 0000000..845f5f6 --- /dev/null +++ b/aerr/client.txt @@ -0,0 +1,28 @@ +400BadRequest Bad Request +401Unauthorized Unauthorized +402PaymentRequired Payment Required +403Forbidden Forbidden +404NotFound Not Found +405MethodNotAllowed Method Not Allowed +406NotAcceptable Not Acceptable +407ProxyAuthenticationRequired Proxy Authentication Required +408RequestTimeout Request Timeout +409Conflict Conflict +410Gone Gone +411LengthRequired Length Required +412PreconditionFailed Precondition Failed +413PayloadTooLarge Payload Too Large +414URITooLong URI Too Long +415UnsupportedMediaType Unsupported Media Type +416RangeNotSatisfiable Range Not Satisfiable +417ExpectationFailed Expectation Failed +421MisdirectedRequest Misdirected Request +422UnprocessableEntity Unprocessable Entity +423Locked Locked +424FailedDependency Failed Dependency +425TooEarly Too Early +426UpgradeRequired Upgrade Required +428PreconditionRequired Precondition Required +429TooManyRequests Too Many Requests +431RequestHeaderFieldsTooLarge Request Header Fields Too Large +451UnavailableForLegalReasons Unavailable For Legal Reasons \ No newline at end of file diff --git a/aerr/note.txt b/aerr/note.txt new file mode 100644 index 0000000..5c3db02 --- /dev/null +++ b/aerr/note.txt @@ -0,0 +1,18 @@ +IN +(\d+)([a-zA-Z]+) (.*) + +OUT + +// New$2 will generate an HTTP Error $1. +func New$2() *Error { + return &Error{ + Code: $1, + httpCode: $1, + Msg: "$3", + } +} +// New$2f will generate an HTTP Error $1 with a custom message. +func New$2f(format string, a ...any) *Error { + aErr := New$2() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} diff --git a/aerr/server.go b/aerr/server.go new file mode 100644 index 0000000..39ae82b --- /dev/null +++ b/aerr/server.go @@ -0,0 +1,155 @@ +package aerr + +import ( + "fmt" +) + +// NewInternalServerError will generate an HTTP Error 500. +func NewInternalServerError() *Error { + return &Error{ + Code: 500, + httpCode: 500, + Msg: "Internal Server Error", + } +} + +// NewInternalServerErrorf will generate an HTTP Error 500 with a custom message. +func NewInternalServerErrorf(format string, a ...any) *Error { + aErr := NewInternalServerError() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewNotImplemented will generate an HTTP Error 501. +func NewNotImplemented() *Error { + return &Error{ + Code: 501, + httpCode: 501, + Msg: "Not Implemented", + } +} + +// NewNotImplementedf will generate an HTTP Error 501 with a custom message. +func NewNotImplementedf(format string, a ...any) *Error { + aErr := NewNotImplemented() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewBadGateway will generate an HTTP Error 502. +func NewBadGateway() *Error { + return &Error{ + Code: 502, + httpCode: 502, + Msg: "Bad Gateway", + } +} + +// NewBadGatewayf will generate an HTTP Error 502 with a custom message. +func NewBadGatewayf(format string, a ...any) *Error { + aErr := NewBadGateway() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewServiceUnavailable will generate an HTTP Error 503. +func NewServiceUnavailable() *Error { + return &Error{ + Code: 503, + httpCode: 503, + Msg: "Service Unavailable", + } +} + +// NewServiceUnavailablef will generate an HTTP Error 503 with a custom message. +func NewServiceUnavailablef(format string, a ...any) *Error { + aErr := NewServiceUnavailable() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewGatewayTimeout will generate an HTTP Error 504. +func NewGatewayTimeout() *Error { + return &Error{ + Code: 504, + httpCode: 504, + Msg: "Gateway Timeout", + } +} + +// NewGatewayTimeoutf will generate an HTTP Error 504 with a custom message. +func NewGatewayTimeoutf(format string, a ...any) *Error { + aErr := NewGatewayTimeout() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewHTTPVersionNotSupported will generate an HTTP Error 505. +func NewHTTPVersionNotSupported() *Error { + return &Error{ + Code: 505, + httpCode: 505, + Msg: "HTTP Version Not Supported", + } +} + +// NewHTTPVersionNotSupportedf will generate an HTTP Error 505 with a custom message. +func NewHTTPVersionNotSupportedf(format string, a ...any) *Error { + aErr := NewHTTPVersionNotSupported() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewVariantAlsoNegotiates will generate an HTTP Error 506. +func NewVariantAlsoNegotiates() *Error { + return &Error{ + Code: 506, + httpCode: 506, + Msg: "Variant Also Negotiates", + } +} + +// NewVariantAlsoNegotiatesf will generate an HTTP Error 506 with a custom message. +func NewVariantAlsoNegotiatesf(format string, a ...any) *Error { + aErr := NewVariantAlsoNegotiates() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewInsufficientStorage will generate an HTTP Error 507. +func NewInsufficientStorage() *Error { + return &Error{ + Code: 507, + httpCode: 507, + Msg: "Insufficient Storage", + } +} + +// NewInsufficientStoragef will generate an HTTP Error 507 with a custom message. +func NewInsufficientStoragef(format string, a ...any) *Error { + aErr := NewInsufficientStorage() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewLoopDetected will generate an HTTP Error 508. +func NewLoopDetected() *Error { + return &Error{ + Code: 508, + httpCode: 508, + Msg: "Loop Detected", + } +} + +// NewLoopDetectedf will generate an HTTP Error 508 with a custom message. +func NewLoopDetectedf(format string, a ...any) *Error { + aErr := NewLoopDetected() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} + +// NewNotExtended will generate an HTTP Error 510. +func NewNotExtended() *Error { + return &Error{ + Code: 510, + httpCode: 510, + Msg: "Not Extended", + } +} + +// NewNotExtendedf will generate an HTTP Error 510 with a custom message. +func NewNotExtendedf(format string, a ...any) *Error { + aErr := NewNotExtended() + return aErr.SetMessage(fmt.Sprintf(format, a...)) +} diff --git a/aerr/server.txt b/aerr/server.txt new file mode 100644 index 0000000..6a2f860 --- /dev/null +++ b/aerr/server.txt @@ -0,0 +1,10 @@ +500InternalServerError Internal Server Error +501NotImplemented Not Implemented +502BadGateway Bad Gateway +503ServiceUnavailable Service Unavailable +504GatewayTimeout Gateway Timeout +505HTTPVersionNotSupported HTTP Version Not Supported +506VariantAlsoNegotiates Variant Also Negotiates +507InsufficientStorage Insufficient Storage +508LoopDetected Loop Detected +510NotExtended Not Extended \ No newline at end of file diff --git a/commonctx/request_id.go b/commonctx/request_id.go index fbb1213..e633f42 100644 --- a/commonctx/request_id.go +++ b/commonctx/request_id.go @@ -12,7 +12,12 @@ func AddRequestID(ctx context.Context, requestID string) context.Context { // GetRequestID retrieve a requestID from the context. func GetRequestID(ctx context.Context) *string { if requestID := ctx.Value(ContextKey_RequestID); requestID != nil { - return requestID.(*string) + requestIDStr, cast := requestID.(string) + if !cast { + return nil + } + + return &requestIDStr } return nil } diff --git a/ginutils/constant.go b/constant/constant.go similarity index 88% rename from ginutils/constant.go rename to constant/constant.go index d88e7c2..f68b7fa 100644 --- a/ginutils/constant.go +++ b/constant/constant.go @@ -1,4 +1,4 @@ -package ginutils +package constant type contextKey string @@ -28,4 +28,6 @@ const ( LogField_Username logField = "username" LogField_IP logField = "real_ip" LogField_Duration logField = "duration_ms" + LogField_Error logField = "error" + LogField_ErrorCode logField = "error_code" ) diff --git a/ginutils/auth.go b/ginutils/auth.go index b2aa3f5..0e798ed 100644 --- a/ginutils/auth.go +++ b/ginutils/auth.go @@ -1,6 +1,8 @@ package ginutils import ( + "git.dev.m-and-m.ovh/mderasse/gocommon/aerr" + "git.dev.m-and-m.ovh/mderasse/gocommon/constant" "github.com/gin-gonic/gin" ) @@ -8,15 +10,12 @@ import ( func SimpleTokens(tokens []string, forbiddenHandler gin.HandlerFunc) gin.HandlerFunc { if forbiddenHandler == nil { forbiddenHandler = func(c *gin.Context) { - c.AbortWithStatusJSON(403, map[string]string{ - "message": "Forbidden", - "debugId": c.GetString(string(ContextKey_RequestID)), - }) + errorHandler(c, aerr.NewForbidden()) } } return func(c *gin.Context) { - requestToken := c.GetHeader(string(HeaderKey_Token)) + requestToken := c.GetHeader(string(constant.HeaderKey_Token)) isAuthorized := false for _, key := range tokens { if key == requestToken { diff --git a/ginutils/logger.go b/ginutils/logger.go index 1574d0d..77caef7 100644 --- a/ginutils/logger.go +++ b/ginutils/logger.go @@ -3,6 +3,7 @@ package ginutils import ( "time" + "git.dev.m-and-m.ovh/mderasse/gocommon/constant" "git.dev.m-and-m.ovh/mderasse/gocommon/webserver" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" @@ -16,17 +17,17 @@ func Log(l *logrus.Entry) gin.HandlerFunc { // construct default fields fields := logrus.Fields{ - string(LogField_IP): webserver.GetClientIP(c.Request), + string(constant.LogField_IP): webserver.GetClientIP(c.Request), // Request information - string(LogField_Method): c.Request.Method, - string(LogField_CanonPath): c.FullPath(), - string(LogField_Path): c.Request.URL.String(), - string(LogField_RequestID): c.GetString(string(ContextKey_RequestID)), + string(constant.LogField_Method): c.Request.Method, + string(constant.LogField_CanonPath): c.FullPath(), + string(constant.LogField_Path): c.Request.URL.String(), + string(constant.LogField_RequestID): c.GetString(string(constant.ContextKey_RequestID)), } // create the logrus entry and add it to gin context log := l.Logger.WithFields(fields) - c.Set(string(ContextKey_Logger), log) + c.Set(string(constant.ContextKey_Logger), log) log.Info("[start]") @@ -34,15 +35,15 @@ func Log(l *logrus.Entry) gin.HandlerFunc { log.WithFields( logrus.Fields{ - string(LogField_Duration): time.Since(start).Microseconds(), - string(LogField_StatusCode): c.Writer.Status(), + string(constant.LogField_Duration): time.Since(start).Microseconds(), + string(constant.LogField_StatusCode): c.Writer.Status(), }).Info("[end]") } } // GetLogger will retrieve a logger instance from gin context and return it or return false. func GetLogger(c *gin.Context) (*logrus.Entry, bool) { - if log, exist := c.Get(string(ContextKey_Logger)); exist { + if log, exist := c.Get(string(constant.ContextKey_Logger)); exist { return log.(*logrus.Entry), true } @@ -51,7 +52,7 @@ func GetLogger(c *gin.Context) (*logrus.Entry, bool) { // GetLoggerWithField will add a key and value field to the logger, update the gin context, and return the new logger. func GetLoggerWithField(c *gin.Context, key string, value interface{}) *logrus.Entry { - iLog, exist := c.Get(string(ContextKey_Logger)) + iLog, exist := c.Get(string(constant.ContextKey_Logger)) if !exist { panic("no logger in context") } @@ -62,13 +63,13 @@ func GetLoggerWithField(c *gin.Context, key string, value interface{}) *logrus.E } log = log.WithField(key, value) - c.Set(string(ContextKey_Logger), log) + c.Set(string(constant.ContextKey_Logger), log) return log } // GetLoggerWithFields will add provided fields to the logger, update the gin context, and return the new logger. func GetLoggerWithFields(c *gin.Context, fields logrus.Fields) *logrus.Entry { - iLog, exist := c.Get(string(ContextKey_Logger)) + iLog, exist := c.Get(string(constant.ContextKey_Logger)) if !exist { panic("no logger in context") } @@ -79,6 +80,6 @@ func GetLoggerWithFields(c *gin.Context, fields logrus.Fields) *logrus.Entry { } log = log.WithFields(fields) - c.Set(string(ContextKey_Logger), log) + c.Set(string(constant.ContextKey_Logger), log) return log } diff --git a/ginutils/recovery.go b/ginutils/recovery.go index f2c47f7..d9953d6 100644 --- a/ginutils/recovery.go +++ b/ginutils/recovery.go @@ -5,6 +5,7 @@ import ( "runtime/debug" "strings" + "git.dev.m-and-m.ovh/mderasse/gocommon/aerr" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) @@ -15,10 +16,7 @@ import ( func Recovery(handler gin.HandlerFunc) gin.HandlerFunc { if handler == nil { handler = func(c *gin.Context) { - c.AbortWithStatusJSON(500, map[string]string{ - "message": "Internal Server Error", - "debugId": c.GetString(string(ContextKey_RequestID)), - }) + errorHandler(c, aerr.NewInternalServerError()) } } return func(c *gin.Context) { @@ -37,7 +35,6 @@ func Recovery(handler gin.HandlerFunc) gin.HandlerFunc { } }() c.Next() - gin.Recovery() } } diff --git a/ginutils/requestid.go b/ginutils/requestid.go index a4a4ea3..083310e 100644 --- a/ginutils/requestid.go +++ b/ginutils/requestid.go @@ -1,6 +1,7 @@ package ginutils import ( + "git.dev.m-and-m.ovh/mderasse/gocommon/constant" "github.com/gin-gonic/gin" "github.com/google/uuid" ) @@ -10,16 +11,16 @@ import ( func RequestID() gin.HandlerFunc { return func(c *gin.Context) { - requestID := c.GetHeader(string(HeaderKey_RequestID)) + requestID := c.GetHeader(string(constant.HeaderKey_RequestID)) if _, err := uuid.Parse(requestID); err != nil { // no request ID or invalid. let's generate a new one requestID = uuid.New().String() } // Adding to context - c.Set(string(ContextKey_RequestID), requestID) + c.Set(string(constant.ContextKey_RequestID), requestID) // Add request-id to the header before sending the response - c.Header(string(HeaderKey_RequestID), requestID) + c.Header(string(constant.HeaderKey_RequestID), requestID) // Let's the next part run c.Next() } diff --git a/ginutils/wrapper.go b/ginutils/wrapper.go new file mode 100644 index 0000000..66c5b4e --- /dev/null +++ b/ginutils/wrapper.go @@ -0,0 +1,50 @@ +package ginutils + +import ( + "net/http" + + "git.dev.m-and-m.ovh/mderasse/gocommon/aerr" + "git.dev.m-and-m.ovh/mderasse/gocommon/constant" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +// HandlerFunc represent the type of function expected by the wrapper. +type HandlerFunc func(*gin.Context) error + +// HandlerWrapper will wrap a handler to handle error. +func HandlerWrapper(h HandlerFunc) gin.HandlerFunc { + return func(c *gin.Context) { + err := h(c) + if err != nil { + errorHandler(c, err) + } + } +} + +func errorHandler(c *gin.Context, err error) { + //nolint: errorlint // Ignore linter to avoid creating useless complexity in the code. + switch e := err.(type) { + case *aerr.Error: + log := GetLoggerWithFields(c, logrus.Fields{ + string(constant.LogField_Error): e.Error(), + string(constant.LogField_ErrorCode): e.Code, + }) + log.Warn("An error occurred") + if e.DebugId == "" { + e = e.SetDebugID(c.GetString(string(constant.ContextKey_RequestID))) + } + c.AbortWithStatusJSON(e.HttpCode(), e) + default: + log := GetLoggerWithFields(c, logrus.Fields{ + string(constant.LogField_Error): e.Error(), + string(constant.LogField_ErrorCode): "500", + }) + log.Warn("An error occurred") + c.AbortWithStatusJSON(http.StatusInternalServerError, aerr.Error{ + Msg: e.Error(), + Code: 500, + DebugId: c.GetString(string(constant.ContextKey_RequestID)), + }) + } +} diff --git a/go.mod b/go.mod index b4da9bd..7738135 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/gemnasium/logrus-graylog-hook/v3 v3.1.1 github.com/gin-gonic/gin v1.9.1 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.3.1 github.com/juju/errors v1.0.0 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/sirupsen/logrus v1.9.3 @@ -27,7 +27,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.15.0 // indirect + github.com/go-playground/validator/v10 v10.15.1 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/jonboulle/clockwork v0.3.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 700ff0d..3469f25 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjAR7VgKoNB6ryXfw= -github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= +github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -41,8 +41,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=