Files
sxchnge/backend/pkg/api/http/response/response.go
T

226 lines
7.3 KiB
Go

package response
import (
"encoding/json"
"errors"
"fmt"
"net/http"
api "git.sendico.io/tech/sxchnge/bff/pkg/api/http"
"git.sendico.io/tech/sxchnge/bff/pkg/merrors"
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
"git.sendico.io/tech/sxchnge/bff/pkg/mservice"
"go.uber.org/zap"
)
// BaseResponse is a general structure for all API responses.
type BaseResponse struct {
Status string `json:"status"` // "success" or "error"
Data any `json:"data"` // The actual data payload or the error details
}
// ErrorResponse provides more details about an error.
type ErrorResponse struct {
Code int `json:"code"` // A unique identifier for the error type, useful for client handling
Error string `json:"error"`
Source string `json:"source"`
Details any `json:"details"` // Additional details or hints about the error, if necessary
}
func errMessage(err error) string {
if err != nil {
return err.Error()
}
return ""
}
func logRequest(logger mlogger.Logger, r *http.Request, message string) {
logger.Debug(
message,
zap.String("host", r.Host),
zap.String("address", r.RemoteAddr),
zap.String("method", r.Method),
zap.String("request_uri", r.RequestURI),
zap.String("proto", r.Proto),
zap.String("user_agent", r.UserAgent()),
)
}
func writeJSON(logger mlogger.Logger, w http.ResponseWriter, r *http.Request, code int, payload any) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Pragma", "no-cache")
w.WriteHeader(code)
if err := json.NewEncoder(w).Encode(&payload); err != nil {
logger.Warn("Failed to encode JSON response",
zap.Error(err),
zap.String("host", r.Host),
zap.String("address", r.RemoteAddr),
zap.String("method", r.Method),
zap.String("request_uri", r.RequestURI),
zap.String("proto", r.Proto),
zap.String("user_agent", r.UserAgent()))
}
}
func errorf(
logger mlogger.Logger,
w http.ResponseWriter, r *http.Request,
source mservice.Type, code int, message string, details any,
) {
logRequest(logger, r, message)
errorMessage := BaseResponse{
Status: api.MSError,
Data: ErrorResponse{
Code: code,
Details: details,
Source: source,
Error: message,
},
}
writeJSON(logger, w, r, code, errorMessage)
}
func Accepted(logger mlogger.Logger, data any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
resp := BaseResponse{
Status: api.MSSuccess,
Data: data,
}
writeJSON(logger, w, r, http.StatusAccepted, resp)
}
}
func Ok(logger mlogger.Logger, data any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
resp := BaseResponse{
Status: api.MSSuccess,
Data: data,
}
writeJSON(logger, w, r, http.StatusOK, resp)
}
}
func Created(logger mlogger.Logger, data any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
resp := BaseResponse{
Status: api.MSSuccess,
Data: data,
}
writeJSON(logger, w, r, http.StatusCreated, resp)
}
}
func Auto(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
if err == nil {
return Success(logger)
}
if errors.Is(err, merrors.ErrAccessDenied) {
return AccessDenied(logger, source, errMessage(err))
}
if errors.Is(err, merrors.ErrDataConflict) {
return DataConflict(logger, source, errMessage(err))
}
if errors.Is(err, merrors.ErrInvalidArg) {
return BadRequest(logger, source, "invalid_argument", errMessage(err))
}
if errors.Is(err, merrors.ErrNoData) {
return NotFound(logger, source, errMessage(err))
}
if errors.Is(err, merrors.ErrUnauthorized) {
return Unauthorized(logger, source, errMessage(err))
}
if errors.Is(err, merrors.ErrServiceUnavailable) {
return Error(logger, source, http.StatusServiceUnavailable, "service_unavailable", errMessage(err))
}
return Internal(logger, source, err)
}
func Internal(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, http.StatusInternalServerError, "internal_error", errMessage(err))
}
}
func NotImplemented(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, http.StatusNotImplemented, "not_implemented", hint)
}
}
func BadRequest(logger mlogger.Logger, source mservice.Type, err, hint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, http.StatusBadRequest, err, hint)
}
}
func Gone(logger mlogger.Logger, source mservice.Type, err, hint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, http.StatusGone, err, hint)
}
}
func BadQueryParam(logger mlogger.Logger, source mservice.Type, param string, err error) http.HandlerFunc {
return BadRequest(logger, source, "invalid_query_parameter", fmt.Sprintf("Failed to parse '%s': %v", param, err))
}
func BadReference(logger mlogger.Logger, source mservice.Type, refName, refVal string, err error) http.HandlerFunc {
return BadRequest(logger, source, "broken_reference",
fmt.Sprintf("broken object reference: %s = %s, error: %v", refName, refVal, err))
}
func BadPayload(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
return BadRequest(logger, source, "broken_payload",
fmt.Sprintf("broken '%s' object payload, error: %v", source, err))
}
func DataConflict(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, http.StatusConflict, "data_conflict", hint)
}
}
func Error(logger mlogger.Logger, source mservice.Type, code int, errType, hint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, code, errType, hint)
}
}
func ErrorWithDetails(logger mlogger.Logger, source mservice.Type, code int, errType string, details any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, code, errType, details)
}
}
func AccessDenied(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
return Error(logger, source, http.StatusForbidden, "access_denied", hint)
}
func Forbidden(logger mlogger.Logger, source mservice.Type, errType, hint string) http.HandlerFunc {
return Error(logger, source, http.StatusForbidden, errType, hint)
}
func TooManyRequests(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
return Error(logger, source, http.StatusTooManyRequests, "too_many_requests", hint)
}
func LicenseRequired(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, http.StatusPaymentRequired, "license_required", hint)
}
}
func Unauthorized(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, http.StatusUnauthorized, "unauthorized", hint)
}
}
func NotFound(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errorf(logger, w, r, source, http.StatusNotFound, "not_found", hint)
}
}