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