// Package httpx provides the HTTP transport layer: a Chi-based router // skeleton and Google API style JSON response helpers (WriteJSON, // WriteError). It deliberately uses package name "httpx" to avoid // shadowing the standard library "net/http" package at call sites. package httpx import ( "encoding/json" "net/http" "github.com/yan1h/agent-coding-workflow/internal/infra/errs" ) // WriteJSON writes body as JSON with the given HTTP status. The // Content-Type header is set to application/json before WriteHeader is // called. Encode errors are intentionally ignored: by the time the // status has been written the connection is committed, and the caller // has no meaningful recovery path. func WriteJSON(w http.ResponseWriter, status int, body any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(body) } // errorResponse is the Google API style flat error body. type errorResponse struct { Code string `json:"code"` Message string `json:"message"` Fields map[string]any `json:"fields,omitempty"` RequestID string `json:"request_id,omitempty"` } // WriteError translates err into a JSON error response. *errs.AppError // values are rendered with their declared Code/Message/Fields and the // HTTP status mapped via errs.HTTPStatus. Any other error is reported // as a generic 500 internal error so internal details don't leak. func WriteError(w http.ResponseWriter, requestID string, err error) { if ae, ok := errs.As(err); ok { WriteJSON(w, errs.HTTPStatus(ae.Code), errorResponse{ Code: string(ae.Code), Message: ae.Message, Fields: ae.Fields, RequestID: requestID, }) return } WriteJSON(w, http.StatusInternalServerError, errorResponse{ Code: string(errs.CodeInternal), Message: "internal server error", RequestID: requestID, }) }