From 4a1434ad5e5d05845e6331caa25a240f45c04e9f Mon Sep 17 00:00:00 2001 From: Dave Martorana Date: Mon, 19 Jun 2017 12:50:40 -0400 Subject: [PATCH] First pass at removing panics in Context.JSON and Context.Render --- context.go | 40 ++++++++++++++++++---------------------- context_test.go | 41 +++++++++++++++++++++++++---------------- render/redirect.go | 3 ++- 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/context.go b/context.go index df001a40..49cc83b1 100644 --- a/context.go +++ b/context.go @@ -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. @@ -472,8 +468,8 @@ 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, @@ -481,8 +477,8 @@ func (c *Context) Redirect(code int, location string) { } // 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, }) @@ -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, }) diff --git a/context_test.go b/context_test.go index 01ee6b83..7ecdf12a 100644 --- a/context_test.go +++ b/context_test.go @@ -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") @@ -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") @@ -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") @@ -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") @@ -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(), "bar") assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8") @@ -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") @@ -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") @@ -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") @@ -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) { diff --git a/render/redirect.go b/render/redirect.go index bd48d7d8..77aff354 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -5,6 +5,7 @@ package render import ( + "errors" "fmt" "net/http" ) @@ -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