package httpapi import ( "encoding/json" "net/http" "strings" "time" ) const ( reasonMalformedRequest = "MALFORMED_REQUEST" reasonMethodNotAllowed = "METHOD_NOT_ALLOWED" reasonForbidden = "FORBIDDEN" reasonInternal = "INTERNAL" ) type apiError struct { Code int `json:"code"` Reason string `json:"reason"` Message string `json:"message"` Details map[string]any `json:"details,omitempty"` RequestID string `json:"request_id,omitempty"` ServerTime string `json:"server_time"` } func writeSuccess(w http.ResponseWriter, r *http.Request, data any) { writeJSON(w, r, http.StatusOK, map[string]any{ "code": 0, "data": data, "request_id": requestID(r), "server_time": time.Now().UTC().Format(time.RFC3339Nano), }) } func writeError(w http.ResponseWriter, r *http.Request, status, code int, reason, message string, details map[string]any) { writeJSON(w, r, status, apiError{Code: code, Reason: reason, Message: message, Details: details, RequestID: requestID(r), ServerTime: time.Now().UTC().Format(time.RFC3339Nano)}) } func writeJSON(w http.ResponseWriter, r *http.Request, status int, value any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") if requestID := requestID(r); requestID != "" { w.Header().Set("X-Request-ID", requestID) } w.WriteHeader(status) _ = json.NewEncoder(w).Encode(value) } func requestID(r *http.Request) string { return r.Header.Get("X-Request-ID") } func decodeBody(w http.ResponseWriter, r *http.Request, dst any) bool { defer r.Body.Close() decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(dst); err != nil { writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", nil) return false } return true } func methodNotAllowed(w http.ResponseWriter, r *http.Request, allowed ...string) { if len(allowed) == 0 { allowed = []string{http.MethodPost} } w.Header().Set("Allow", strings.Join(allowed, ", ")) writeError(w, r, http.StatusMethodNotAllowed, 30005, reasonMethodNotAllowed, "method not allowed", nil) }