feat(error): Create api error. WIP
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Matthieu 'JP' DERASSE
2023-08-21 07:08:54 +00:00
parent a7b1989231
commit 77b4351ef1
15 changed files with 834 additions and 34 deletions

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

@ -1,31 +0,0 @@
package ginutils
type contextKey string
//nolint:exported // keeping the enum simple and readable.
const (
ContextKey_Logger contextKey = "logger"
ContextKey_RequestID contextKey = "requestID"
)
type headerKey string
//nolint:exported // keeping the enum simple and readable.
const (
HeaderKey_RequestID headerKey = "X-REQUEST-ID"
HeaderKey_Token headerKey = "X-TOKEN"
)
type logField string
//nolint:exported // keeping the enum simple and readable.
const (
LogField_RequestID logField = "request_id"
LogField_Method logField = "method"
LogField_CanonPath logField = "canon_path"
LogField_Path logField = "path"
LogField_StatusCode logField = "status_code"
LogField_Username logField = "username"
LogField_IP logField = "real_ip"
LogField_Duration logField = "duration_ms"
)

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