feat(error): Create api error. WIP
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Matthieu 'JP' DERASSE 2023-08-21 07:08:54 +00:00
parent a7b1989231
commit 77b4351ef1
Signed by: mderasse
GPG Key ID: 55141C777B16A705
15 changed files with 834 additions and 34 deletions

109
aerr/aerr.go Normal file
View File

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

425
aerr/client.go Normal file
View File

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

28
aerr/client.txt Normal file
View File

@ -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

18
aerr/note.txt Normal file
View File

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

155
aerr/server.go Normal file
View File

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

10
aerr/server.txt Normal file
View File

@ -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

View File

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

View File

@ -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"
)

View File

@ -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 {

View File

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

View File

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

View File

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

50
ginutils/wrapper.go Normal file
View File

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

4
go.mod
View File

@ -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

8
go.sum
View File

@ -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=