package ginext import ( "fmt" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" "gogs.mikescher.com/BlackForestBytes/goext/ginext/commonapierr" json "gogs.mikescher.com/BlackForestBytes/goext/gojson" "gogs.mikescher.com/BlackForestBytes/goext/langext" "runtime/debug" "strings" ) type HTTPResponse interface { Write(g *gin.Context) } type jsonHTTPResponse struct { statusCode int data any } func (j jsonHTTPResponse) Write(g *gin.Context) { g.Render(j.statusCode, json.GoJsonRender{Data: j.data, NilSafeSlices: true, NilSafeMaps: true}) } type emptyHTTPResponse struct { statusCode int } func (j emptyHTTPResponse) Write(g *gin.Context) { g.Status(j.statusCode) } type textHTTPResponse struct { statusCode int data string } func (j textHTTPResponse) Write(g *gin.Context) { g.String(j.statusCode, "%s", j.data) } type dataHTTPResponse struct { statusCode int data []byte contentType string } func (j dataHTTPResponse) Write(g *gin.Context) { g.Data(j.statusCode, j.contentType, j.data) } type fileHTTPResponse struct { mimetype string filepath string filename *string } func (j fileHTTPResponse) Write(g *gin.Context) { g.Header("Content-Type", j.mimetype) // if we don't set it here gin does weird file-sniffing later... if j.filename != nil { g.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *j.filename)) } g.File(j.filepath) } type redirectHTTPResponse struct { statusCode int url string } func (j redirectHTTPResponse) Write(g *gin.Context) { g.Redirect(j.statusCode, j.url) } func Status(sc int) HTTPResponse { return &emptyHTTPResponse{statusCode: sc} } func JSON(sc int, data any) HTTPResponse { return &jsonHTTPResponse{statusCode: sc, data: data} } func Data(sc int, contentType string, data []byte) HTTPResponse { return &dataHTTPResponse{statusCode: sc, contentType: contentType, data: data} } func Text(sc int, data string) HTTPResponse { return &textHTTPResponse{statusCode: sc, data: data} } func File(mimetype string, filepath string) HTTPResponse { return &fileHTTPResponse{mimetype: mimetype, filepath: filepath} } func Download(mimetype string, filepath string, filename string) HTTPResponse { return &fileHTTPResponse{mimetype: mimetype, filepath: filepath, filename: &filename} } func Redirect(sc int, newURL string) HTTPResponse { return &redirectHTTPResponse{statusCode: sc, url: newURL} } func APIError(g *gin.Context, errcode commonapierr.APIErrorCode, msg string, e error) HTTPResponse { return createApiError(g, errcode, msg, e) } func NotImplemented(g *gin.Context) HTTPResponse { return createApiError(g, commonapierr.NotImplemented, "", nil) } func createApiError(g *gin.Context, errcode commonapierr.APIErrorCode, msg string, e error) HTTPResponse { reqUri := "" if g != nil && g.Request != nil { reqUri = g.Request.Method + " :: " + g.Request.RequestURI } log.Error(). Str("errorcode.key", errcode.Key). Int("errcode.status", errcode.HTTPStatusCode). Str("uri", reqUri). AnErr("err", e). Stack(). Msg(msg) var fapiMessage *string = nil if v, ok := e.(interface{ FAPIMessage() string }); ok { fapiMessage = langext.Ptr(v.FAPIMessage()) } if g.GetBool("__returnRawErrors") { return &jsonHTTPResponse{ statusCode: errcode.HTTPStatusCode, data: extAPIError{ ErrorCode: errcode.Key, Message: msg, RawError: langext.Ptr(langext.Conditional(e == nil, "", fmt.Sprintf("%+v", e))), FAPIErrorMessage: fapiMessage, Trace: strings.Split(string(debug.Stack()), "\n"), }, } } else { return &jsonHTTPResponse{ statusCode: errcode.HTTPStatusCode, data: apiError{ ErrorCode: errcode.Key, Message: msg, FAPIErrorMessage: fapiMessage, }, } } }