// Package httputil holds the small HTTP response helpers shared by every API // handler package (docs, suggestions, vocab, …). Keeping them in one place means // JSON encoding and — crucially — error handling behave identically everywhere: // internal errors are logged in full but never leaked to the client. package httputil import ( "encoding/json" "log" "net/http" ) // WriteJSON encodes v as the response body with the given status code. func WriteJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } // ErrorJSON sends a {"error": msg} body with the given status. The message is // caller-chosen and safe to show the client. func ErrorJSON(w http.ResponseWriter, status int, msg string) { WriteJSON(w, status, map[string]string{"error": msg}) } // BadRequest is the common 400 shorthand. func BadRequest(w http.ResponseWriter, msg string) { ErrorJSON(w, http.StatusBadRequest, msg) } // ServerError logs the real error (with full detail, for the operator) and // returns a generic 500 to the client — raw database/internal errors must never // reach the browser, where they leak schema and implementation details. func ServerError(w http.ResponseWriter, err error) { log.Printf("internal error: %v", err) ErrorJSON(w, http.StatusInternalServerError, "something went wrong") } // UpstreamError is ServerError's counterpart for a dependency Petal calls out // to — the model, chiefly. Same discipline, and for a sharper reason: a dial // failure's error text contains the endpoint it failed to dial, so relaying it // hands anyone who can reach Petal the address of the inference box on the far // side of the VPN, along with which backend is running there. // // `what` names the pass for the operator's log ("checkpoint", "chat"). The // browser is told only that the helper is unreachable, which is all the client // ever did anything with: every LLM route's 502 renders as the same warm // "小助手在休息 · Petal's helper is resting". func UpstreamError(w http.ResponseWriter, what string, err error) { log.Printf("upstream error (%s): %v", what, err) ErrorJSON(w, http.StatusBadGateway, "Petal's helper is out of reach right now") }