optimized render.JSON (#929)

* api/render: render JSON directly to the underlying writer

* also consider json.MarshalerError a panic
This commit is contained in:
Panagiotis Siatras 2023-05-04 22:16:12 +03:00 committed by GitHub
parent ef951f2075
commit 2139121683
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 11 deletions

View file

@ -2,7 +2,6 @@
package render package render
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http" "net/http"
@ -24,14 +23,25 @@ func JSON(w http.ResponseWriter, v interface{}) {
// JSONStatus sets the Content-Type of w to application/json unless one is // JSONStatus sets the Content-Type of w to application/json unless one is
// specified. // specified.
func JSONStatus(w http.ResponseWriter, v interface{}, status int) { func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
var b bytes.Buffer setContentTypeUnlessPresent(w, "application/json")
if err := json.NewEncoder(&b).Encode(v); err != nil { w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
var errUnsupportedType *json.UnsupportedTypeError
if errors.As(err, &errUnsupportedType) {
panic(err) panic(err)
} }
setContentTypeUnlessPresent(w, "application/json") var errUnsupportedValue *json.UnsupportedValueError
w.WriteHeader(status) if errors.As(err, &errUnsupportedValue) {
_, _ = b.WriteTo(w) panic(err)
}
var errMarshalError *json.MarshalerError
if errors.As(err, &errMarshalError) {
panic(err)
}
}
log.EnabledResponse(w, v) log.EnabledResponse(w, v)
} }

View file

@ -1,8 +1,10 @@
package render package render
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"math"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strconv" "strconv"
@ -26,10 +28,43 @@ func TestJSON(t *testing.T) {
assert.Empty(t, rw.Fields()) assert.Empty(t, rw.Fields())
} }
func TestJSONPanics(t *testing.T) { func TestJSONPanicsOnUnsupportedType(t *testing.T) {
assert.Panics(t, func() { jsonPanicTest[json.UnsupportedTypeError](t, make(chan struct{}))
JSON(httptest.NewRecorder(), make(chan struct{})) }
})
func TestJSONPanicsOnUnsupportedValue(t *testing.T) {
jsonPanicTest[json.UnsupportedValueError](t, math.NaN())
}
func TestJSONPanicsOnMarshalerError(t *testing.T) {
var v erroneousJSONMarshaler
jsonPanicTest[json.MarshalerError](t, v)
}
type erroneousJSONMarshaler struct{}
func (erroneousJSONMarshaler) MarshalJSON() ([]byte, error) {
return nil, assert.AnError
}
func jsonPanicTest[T json.UnsupportedTypeError | json.UnsupportedValueError | json.MarshalerError](t *testing.T, v any) {
t.Helper()
defer func() {
var err error
if r := recover(); r == nil {
t.Fatal("expected panic")
} else if e, ok := r.(error); !ok {
t.Fatalf("did not panic with an error (%T)", r)
} else {
err = e
}
var e *T
assert.ErrorAs(t, err, &e)
}()
JSON(httptest.NewRecorder(), v)
} }
type renderableError struct { type renderableError struct {