Skip to content

Commit 2139121

Browse files
authoredMay 4, 2023
optimized render.JSON (smallstep#929)
* api/render: render JSON directly to the underlying writer * also consider json.MarshalerError a panic
1 parent ef951f2 commit 2139121

File tree

2 files changed

+56
-11
lines changed

2 files changed

+56
-11
lines changed
 

‎api/render/render.go

+17-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
package render
33

44
import (
5-
"bytes"
65
"encoding/json"
76
"errors"
87
"net/http"
@@ -24,14 +23,25 @@ func JSON(w http.ResponseWriter, v interface{}) {
2423
// JSONStatus sets the Content-Type of w to application/json unless one is
2524
// specified.
2625
func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
27-
var b bytes.Buffer
28-
if err := json.NewEncoder(&b).Encode(v); err != nil {
29-
panic(err)
30-
}
31-
3226
setContentTypeUnlessPresent(w, "application/json")
3327
w.WriteHeader(status)
34-
_, _ = b.WriteTo(w)
28+
29+
if err := json.NewEncoder(w).Encode(v); err != nil {
30+
var errUnsupportedType *json.UnsupportedTypeError
31+
if errors.As(err, &errUnsupportedType) {
32+
panic(err)
33+
}
34+
35+
var errUnsupportedValue *json.UnsupportedValueError
36+
if errors.As(err, &errUnsupportedValue) {
37+
panic(err)
38+
}
39+
40+
var errMarshalError *json.MarshalerError
41+
if errors.As(err, &errMarshalError) {
42+
panic(err)
43+
}
44+
}
3545

3646
log.EnabledResponse(w, v)
3747
}

‎api/render/render_test.go

+39-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package render
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"io"
7+
"math"
68
"net/http"
79
"net/http/httptest"
810
"strconv"
@@ -26,10 +28,43 @@ func TestJSON(t *testing.T) {
2628
assert.Empty(t, rw.Fields())
2729
}
2830

29-
func TestJSONPanics(t *testing.T) {
30-
assert.Panics(t, func() {
31-
JSON(httptest.NewRecorder(), make(chan struct{}))
32-
})
31+
func TestJSONPanicsOnUnsupportedType(t *testing.T) {
32+
jsonPanicTest[json.UnsupportedTypeError](t, make(chan struct{}))
33+
}
34+
35+
func TestJSONPanicsOnUnsupportedValue(t *testing.T) {
36+
jsonPanicTest[json.UnsupportedValueError](t, math.NaN())
37+
}
38+
39+
func TestJSONPanicsOnMarshalerError(t *testing.T) {
40+
var v erroneousJSONMarshaler
41+
jsonPanicTest[json.MarshalerError](t, v)
42+
}
43+
44+
type erroneousJSONMarshaler struct{}
45+
46+
func (erroneousJSONMarshaler) MarshalJSON() ([]byte, error) {
47+
return nil, assert.AnError
48+
}
49+
50+
func jsonPanicTest[T json.UnsupportedTypeError | json.UnsupportedValueError | json.MarshalerError](t *testing.T, v any) {
51+
t.Helper()
52+
53+
defer func() {
54+
var err error
55+
if r := recover(); r == nil {
56+
t.Fatal("expected panic")
57+
} else if e, ok := r.(error); !ok {
58+
t.Fatalf("did not panic with an error (%T)", r)
59+
} else {
60+
err = e
61+
}
62+
63+
var e *T
64+
assert.ErrorAs(t, err, &e)
65+
}()
66+
67+
JSON(httptest.NewRecorder(), v)
3368
}
3469

3570
type renderableError struct {

0 commit comments

Comments
 (0)