package api import ( "encoding/json" "net/http" "time" "github.com/go-chi/chi/v5/middleware" ) // Response is the standard envelope for all API responses. type Response struct { Data any `json:"data,omitempty"` Error *Error `json:"error,omitempty"` Meta Meta `json:"meta"` } // Error represents an API error. type Error struct { Code string `json:"code"` Message string `json:"message"` Details []any `json:"details,omitempty"` } // Meta contains response metadata. type Meta struct { RequestID string `json:"request_id,omitempty"` Timestamp string `json:"timestamp"` } // newMeta creates a Meta with current timestamp and request ID from context. func newMeta(r *http.Request) Meta { return Meta{ RequestID: middleware.GetReqID(r.Context()), Timestamp: time.Now().UTC().Format(time.RFC3339), } } // WriteJSON writes a JSON response with the given status code. func WriteJSON(w http.ResponseWriter, r *http.Request, status int, data any) { resp := Response{ Data: data, Meta: newMeta(r), } writeResponse(w, status, resp) } // WriteSuccess writes a successful JSON response with status 200 OK. func WriteSuccess(w http.ResponseWriter, r *http.Request, data any) { WriteJSON(w, r, http.StatusOK, data) } // WriteCreated writes a successful JSON response with status 201 Created. func WriteCreated(w http.ResponseWriter, r *http.Request, data any) { WriteJSON(w, r, http.StatusCreated, data) } // WriteNoContent writes a successful response with status 204 No Content. func WriteNoContent(w http.ResponseWriter) { w.WriteHeader(http.StatusNoContent) } // WriteError writes an error response with the given status code. func WriteError(w http.ResponseWriter, r *http.Request, status int, code, message string, details ...any) { resp := Response{ Error: &Error{ Code: code, Message: message, Details: details, }, Meta: newMeta(r), } writeResponse(w, status, resp) } // WriteBadRequest writes a 400 Bad Request error response. func WriteBadRequest(w http.ResponseWriter, r *http.Request, message string, details ...any) { WriteError(w, r, http.StatusBadRequest, "BAD_REQUEST", message, details...) } // WriteNotFound writes a 404 Not Found error response. func WriteNotFound(w http.ResponseWriter, r *http.Request, message string) { WriteError(w, r, http.StatusNotFound, "NOT_FOUND", message) } // WriteUnauthorized writes a 401 Unauthorized error response. func WriteUnauthorized(w http.ResponseWriter, r *http.Request, message string) { WriteError(w, r, http.StatusUnauthorized, "UNAUTHORIZED", message) } // WriteForbidden writes a 403 Forbidden error response. func WriteForbidden(w http.ResponseWriter, r *http.Request, message string) { WriteError(w, r, http.StatusForbidden, "FORBIDDEN", message) } // WriteInternalError writes a 500 Internal Server Error response. func WriteInternalError(w http.ResponseWriter, r *http.Request, message string) { WriteError(w, r, http.StatusInternalServerError, "INTERNAL_ERROR", message) } // writeResponse marshals and writes the response. func writeResponse(w http.ResponseWriter, status int, resp Response) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) if err := json.NewEncoder(w).Encode(resp); err != nil { return } }