|
// EXAMPLES |
|
// |
|
// ERROR RESPONSE: |
|
// response := ErrorResponse{Message: "error reading request body", Details: err.Error()} |
|
// httpx.WriteJSON(l, w, r, http.StatusBadRequest, response) |
|
// |
|
// SUCCESS RESPONSE: |
|
// response := map[string]string{"message": "updated order status to trigger certificate issuance"} |
|
// httpx.WriteJSON(l, w, r, http.StatusOK, response) |
|
|
|
package httpx |
|
|
|
import ( |
|
"bytes" |
|
"encoding/json" |
|
"fmt" |
|
"log/slog" |
|
"net/http" |
|
) |
|
|
|
// WriteJSON encodes v as JSON and writes to w. |
|
// It ensures the correct status code is written even if JSON encoding fails. |
|
// Will write a [http.StatusInternalServerError] if there is an error. |
|
// Otherwise, it'll write the JSON response with specified code status. |
|
// |
|
// WARNING: The response status code is explicitly sent before the body. |
|
// |
|
// We have to do this because we don't want the first call to |
|
// [http.ResponseWriter.Write] to call `WriteHeader(http.StatusOK)`. |
|
// |
|
// This means there is the potential for the incorrect status code to be sent. |
|
// If, the call to [bytes.Buffer.WriteTo] fails, then we've already set the |
|
// response status code. We now can't change the status, as Go ignores |
|
// subsequent calls to [http.ResponseWriter.WriteHeader]. The best we can do is |
|
// catch and log the error. |
|
func WriteJSON(l *slog.Logger, w http.ResponseWriter, r *http.Request, code int, v any) { |
|
ctx := r.Context() |
|
w.Header().Set("Content-Type", "application/json") |
|
|
|
var buf bytes.Buffer |
|
if err := json.NewEncoder(&buf).Encode(v); err != nil { |
|
l.LogAttrs(ctx, slog.LevelError, "encode_json_response", slog.Any("err", err)) |
|
w.WriteHeader(http.StatusInternalServerError) |
|
fmt.Fprintf(w, `{"error": %q}`, err) |
|
return |
|
} |
|
|
|
w.WriteHeader(code) |
|
|
|
if _, err := buf.WriteTo(w); err != nil { |
|
l.LogAttrs(ctx, slog.LevelError, "write_buffered_response", slog.Any("err", err)) |
|
fmt.Fprintf(w, `{"error": %q}`, err) |
|
return |
|
// Alternatively, instead of writing the error and returning... |
|
// panic(http.ErrAbortHandler) |
|
// ...but you should probably have some Panic Recovery middleware in your stack. |
|
} |
|
} |