Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass at removing panics in Context.JSON and Context.Render #952

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 18 additions & 22 deletions context.go
Expand Up @@ -422,47 +422,43 @@ func (c *Context) Cookie(name string) (string, error) {
return val, nil
}

func (c *Context) Render(code int, r render.Render) {
func (c *Context) Render(code int, r render.Render) error {
c.Status(code)
if err := r.Render(c.Writer); err != nil {
panic(err)
}
return r.Render(c.Writer)
}

// HTML renders the HTTP template specified by its file name.
// It also updates the HTTP code and sets the Content-Type as "text/html".
// See http://golang.org/doc/articles/wiki/
func (c *Context) HTML(code int, name string, obj interface{}) {
func (c *Context) HTML(code int, name string, obj interface{}) error {
instance := c.engine.HTMLRender.Instance(name, obj)
c.Render(code, instance)
return c.Render(code, instance)
}

// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
// It also sets the Content-Type as "application/json".
// WARNING: we recommend to use this only for development propuses since printing pretty JSON is
// more CPU and bandwidth consuming. Use Context.JSON() instead.
func (c *Context) IndentedJSON(code int, obj interface{}) {
c.Render(code, render.IndentedJSON{Data: obj})
func (c *Context) IndentedJSON(code int, obj interface{}) error {
return c.Render(code, render.IndentedJSON{Data: obj})
}

// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) {
func (c *Context) JSON(code int, obj interface{}) error {
c.Status(code)
if err := render.WriteJSON(c.Writer, obj); err != nil {
panic(err)
}
return render.WriteJSON(c.Writer, obj)
}

// XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) {
c.Render(code, render.XML{Data: obj})
func (c *Context) XML(code int, obj interface{}) error {
return c.Render(code, render.XML{Data: obj})
}

// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj interface{}) {
c.Render(code, render.YAML{Data: obj})
func (c *Context) YAML(code int, obj interface{}) error {
return c.Render(code, render.YAML{Data: obj})
}

// String writes the given string into the response body.
Expand All @@ -472,17 +468,17 @@ func (c *Context) String(code int, format string, values ...interface{}) {
}

// Redirect returns a HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) {
c.Render(-1, render.Redirect{
func (c *Context) Redirect(code int, location string) error {
return c.Render(-1, render.Redirect{
Code: code,
Location: location,
Request: c.Request,
})
}

// Data writes some data into the body stream and updates the HTTP code.
func (c *Context) Data(code int, contentType string, data []byte) {
c.Render(code, render.Data{
func (c *Context) Data(code int, contentType string, data []byte) error {
return c.Render(code, render.Data{
ContentType: contentType,
Data: data,
})
Expand All @@ -494,8 +490,8 @@ func (c *Context) File(filepath string) {
}

// SSEvent writes a Server-Sent Event into the body stream.
func (c *Context) SSEvent(name string, message interface{}) {
c.Render(-1, sse.Event{
func (c *Context) SSEvent(name string, message interface{}) error {
return c.Render(-1, sse.Event{
Event: name,
Data: message,
})
Expand Down
41 changes: 25 additions & 16 deletions context_test.go
Expand Up @@ -351,8 +351,9 @@ func TestContextGetCookie(t *testing.T) {
// and Content-Type is set to application/json
func TestContextRenderJSON(t *testing.T) {
c, w, _ := CreateTestContext()
c.JSON(201, H{"foo": "bar"})
e := c.JSON(201, H{"foo": "bar"})

assert.NoError(t, e)
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "{\"foo\":\"bar\"}\n")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
Expand All @@ -363,8 +364,9 @@ func TestContextRenderJSON(t *testing.T) {
func TestContextRenderAPIJSON(t *testing.T) {
c, w, _ := CreateTestContext()
c.Header("Content-Type", "application/vnd.api+json")
c.JSON(201, H{"foo": "bar"})
e := c.JSON(201, H{"foo": "bar"})

assert.NoError(t, e)
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "{\"foo\":\"bar\"}\n")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json")
Expand All @@ -374,8 +376,9 @@ func TestContextRenderAPIJSON(t *testing.T) {
// and Content-Type is set to application/json
func TestContextRenderIndentedJSON(t *testing.T) {
c, w, _ := CreateTestContext()
c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
err := c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})

assert.NoError(t, err)
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
Expand All @@ -388,8 +391,9 @@ func TestContextRenderHTML(t *testing.T) {
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ)

c.HTML(201, "t", H{"name": "alexandernyquist"})
err := c.HTML(201, "t", H{"name": "alexandernyquist"})

assert.NoError(t, err)
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
Expand All @@ -399,8 +403,9 @@ func TestContextRenderHTML(t *testing.T) {
// and Content-Type is set to application/xml
func TestContextRenderXML(t *testing.T) {
c, w, _ := CreateTestContext()
c.XML(201, H{"foo": "bar"})
err := c.XML(201, H{"foo": "bar"})

assert.NoError(t, err)
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "<map><foo>bar</foo></map>")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8")
Expand Down Expand Up @@ -433,8 +438,9 @@ func TestContextRenderHTMLString(t *testing.T) {
// with specified MIME type
func TestContextRenderData(t *testing.T) {
c, w, _ := CreateTestContext()
c.Data(201, "text/csv", []byte(`foo,bar`))
err := c.Data(201, "text/csv", []byte(`foo,bar`))

assert.NoError(t, err)
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "foo,bar")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv")
Expand Down Expand Up @@ -469,8 +475,9 @@ func TestContextRenderFile(t *testing.T) {
// and Content-Type is set to application/x-yaml
func TestContextRenderYAML(t *testing.T) {
c, w, _ := CreateTestContext()
c.YAML(201, H{"foo": "bar"})
err := c.YAML(201, H{"foo": "bar"})

assert.NoError(t, err)
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "foo: bar\n")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/x-yaml; charset=utf-8")
Expand All @@ -496,10 +503,12 @@ func TestContextHeaders(t *testing.T) {
func TestContextRenderRedirectWithRelativePath(t *testing.T) {
c, w, _ := CreateTestContext()
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
assert.Panics(t, func() { c.Redirect(299, "/new_path") })
assert.Panics(t, func() { c.Redirect(309, "/new_path") })
assert.Error(t, c.Redirect(299, "/new_path"))
assert.Error(t, c.Redirect(309, "/new_path"))

err := c.Redirect(301, "/path")
assert.NoError(t, err)

c.Redirect(301, "/path")
c.Writer.WriteHeaderNow()
assert.Equal(t, w.Code, 301)
assert.Equal(t, w.Header().Get("Location"), "/path")
Expand Down Expand Up @@ -528,12 +537,12 @@ func TestContextRenderRedirectWith201(t *testing.T) {
func TestContextRenderRedirectAll(t *testing.T) {
c, _, _ := CreateTestContext()
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
assert.Panics(t, func() { c.Redirect(200, "/resource") })
assert.Panics(t, func() { c.Redirect(202, "/resource") })
assert.Panics(t, func() { c.Redirect(299, "/resource") })
assert.Panics(t, func() { c.Redirect(309, "/resource") })
assert.NotPanics(t, func() { c.Redirect(300, "/resource") })
assert.NotPanics(t, func() { c.Redirect(308, "/resource") })
assert.Error(t, c.Redirect(200, "/resource"))
assert.Error(t, c.Redirect(202, "/resource"))
assert.Error(t, c.Redirect(299, "/resource"))
assert.Error(t, c.Redirect(309, "/resource"))
assert.NoError(t, c.Redirect(300, "/resource"))
assert.NoError(t, c.Redirect(308, "/resource"))
}

func TestContextNegotiationFormat(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion render/redirect.go
Expand Up @@ -5,6 +5,7 @@
package render

import (
"errors"
"fmt"
"net/http"
)
Expand All @@ -17,7 +18,7 @@ type Redirect struct {

func (r Redirect) Render(w http.ResponseWriter) error {
if (r.Code < 300 || r.Code > 308) && r.Code != 201 {
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
return errors.New(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
}
http.Redirect(w, r.Request, r.Location, r.Code)
return nil
Expand Down