fix conflict

This commit is contained in:
thinkerou 2019-01-18 11:46:13 +08:00
commit c18fef0042
27 changed files with 273 additions and 157 deletions

View File

@ -19,6 +19,9 @@ test:
if grep -q "^--- FAIL" tmp.out; then \ if grep -q "^--- FAIL" tmp.out; then \
rm tmp.out; \ rm tmp.out; \
exit 1; \ exit 1; \
elif grep -q "build failed" tmp.out; then \
rm tmp.out; \
exit; \
fi; \ fi; \
if [ -f profile.out ]; then \ if [ -f profile.out ]; then \
cat profile.out | grep -v "mode:" >> coverage.out; \ cat profile.out | grep -v "mode:" >> coverage.out; \

View File

@ -11,6 +11,7 @@ import (
"io/ioutil" "io/ioutil"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"strconv"
"testing" "testing"
"time" "time"
@ -515,28 +516,28 @@ func createFormPostRequestFail() *http.Request {
return req return req
} }
func createFormMultipartRequest() *http.Request { func createFormMultipartRequest(t *testing.T) *http.Request {
boundary := "--testboundary" boundary := "--testboundary"
body := new(bytes.Buffer) body := new(bytes.Buffer)
mw := multipart.NewWriter(body) mw := multipart.NewWriter(body)
defer mw.Close() defer mw.Close()
mw.SetBoundary(boundary) assert.NoError(t, mw.SetBoundary(boundary))
mw.WriteField("foo", "bar") assert.NoError(t, mw.WriteField("foo", "bar"))
mw.WriteField("bar", "foo") assert.NoError(t, mw.WriteField("bar", "foo"))
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }
func createFormMultipartRequestFail() *http.Request { func createFormMultipartRequestFail(t *testing.T) *http.Request {
boundary := "--testboundary" boundary := "--testboundary"
body := new(bytes.Buffer) body := new(bytes.Buffer)
mw := multipart.NewWriter(body) mw := multipart.NewWriter(body)
defer mw.Close() defer mw.Close()
mw.SetBoundary(boundary) assert.NoError(t, mw.SetBoundary(boundary))
mw.WriteField("map_foo", "bar") assert.NoError(t, mw.WriteField("map_foo", "bar"))
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
@ -545,7 +546,7 @@ func createFormMultipartRequestFail() *http.Request {
func TestBindingFormPost(t *testing.T) { func TestBindingFormPost(t *testing.T) {
req := createFormPostRequest() req := createFormPostRequest()
var obj FooBarStruct var obj FooBarStruct
FormPost.Bind(req, &obj) assert.NoError(t, FormPost.Bind(req, &obj))
assert.Equal(t, "form-urlencoded", FormPost.Name()) assert.Equal(t, "form-urlencoded", FormPost.Name())
assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
@ -555,7 +556,7 @@ func TestBindingFormPost(t *testing.T) {
func TestBindingDefaultValueFormPost(t *testing.T) { func TestBindingDefaultValueFormPost(t *testing.T) {
req := createDefaultFormPostRequest() req := createDefaultFormPostRequest()
var obj FooDefaultBarStruct var obj FooDefaultBarStruct
FormPost.Bind(req, &obj) assert.NoError(t, FormPost.Bind(req, &obj))
assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, "hello", obj.Bar) assert.Equal(t, "hello", obj.Bar)
@ -569,9 +570,9 @@ func TestBindingFormPostFail(t *testing.T) {
} }
func TestBindingFormMultipart(t *testing.T) { func TestBindingFormMultipart(t *testing.T) {
req := createFormMultipartRequest() req := createFormMultipartRequest(t)
var obj FooBarStruct var obj FooBarStruct
FormMultipart.Bind(req, &obj) assert.NoError(t, FormMultipart.Bind(req, &obj))
assert.Equal(t, "multipart/form-data", FormMultipart.Name()) assert.Equal(t, "multipart/form-data", FormMultipart.Name())
assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
@ -579,7 +580,7 @@ func TestBindingFormMultipart(t *testing.T) {
} }
func TestBindingFormMultipartFail(t *testing.T) { func TestBindingFormMultipartFail(t *testing.T) {
req := createFormMultipartRequestFail() req := createFormMultipartRequestFail(t)
var obj FooStructForMapType var obj FooStructForMapType
err := FormMultipart.Bind(req, &obj) err := FormMultipart.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
@ -690,6 +691,28 @@ func TestUriBinding(t *testing.T) {
assert.Equal(t, map[string]interface{}(nil), not.Name) assert.Equal(t, map[string]interface{}(nil), not.Name)
} }
func TestUriInnerBinding(t *testing.T) {
type Tag struct {
Name string `uri:"name"`
S struct {
Age int `uri:"age"`
}
}
expectedName := "mike"
expectedAge := 25
m := map[string][]string{
"name": {expectedName},
"age": {strconv.Itoa(expectedAge)},
}
var tag Tag
assert.NoError(t, Uri.BindUri(m, &tag))
assert.Equal(t, tag.Name, expectedName)
assert.Equal(t, tag.S.Age, expectedAge)
}
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
b := Form b := Form
assert.Equal(t, "form", b.Name()) assert.Equal(t, "form", b.Name())

View File

@ -20,7 +20,11 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil { if err := req.ParseForm(); err != nil {
return err return err
} }
req.ParseMultipartForm(defaultMemory) if err := req.ParseMultipartForm(defaultMemory); err != nil {
if err != http.ErrNotMultipart {
return err
}
}
if err := mapForm(obj, req.Form); err != nil { if err := mapForm(obj, req.Form); err != nil {
return err return err
} }

View File

@ -55,7 +55,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
structFieldKind = structField.Kind() structFieldKind = structField.Kind()
} }
if structFieldKind == reflect.Struct { if structFieldKind == reflect.Struct {
err := mapForm(structField.Addr().Interface(), form) err := mapFormByTag(structField.Addr().Interface(), form, tag)
if err != nil { if err != nil {
return err return err
} }

View File

@ -105,8 +105,9 @@ func (c *Context) Handler() HandlerFunc {
// See example in GitHub. // See example in GitHub.
func (c *Context) Next() { func (c *Context) Next() {
c.index++ c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ { for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c) c.handlers[c.index](c)
c.index++
} }
} }
@ -415,7 +416,11 @@ func (c *Context) PostFormArray(key string) []string {
// a boolean value whether at least one value exists for the given key. // a boolean value whether at least one value exists for the given key.
func (c *Context) GetPostFormArray(key string) ([]string, bool) { func (c *Context) GetPostFormArray(key string) ([]string, bool) {
req := c.Request req := c.Request
req.ParseMultipartForm(c.engine.MaxMultipartMemory) if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
if err != http.ErrNotMultipart {
debugPrint("error on parse multipart form array: %v", err)
}
}
if values := req.PostForm[key]; len(values) > 0 { if values := req.PostForm[key]; len(values) > 0 {
return values, true return values, true
} }
@ -432,7 +437,11 @@ func (c *Context) PostFormMap(key string) map[string]string {
// whether at least one value exists for the given key. // whether at least one value exists for the given key.
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
req := c.Request req := c.Request
req.ParseMultipartForm(c.engine.MaxMultipartMemory) if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
if err != http.ErrNotMultipart {
debugPrint("error on parse multipart form map: %v", err)
}
}
return c.get(req.PostForm, key) return c.get(req.PostForm, key)
} }
@ -482,8 +491,8 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
} }
defer out.Close() defer out.Close()
io.Copy(out, src) _, err = io.Copy(out, src)
return nil return err
} }
// Bind checks the Content-Type to select a binding engine automatically, // Bind checks the Content-Type to select a binding engine automatically,
@ -523,7 +532,7 @@ func (c *Context) BindYAML(obj interface{}) error {
// It will abort the request with HTTP 400 if any error occurs. // It will abort the request with HTTP 400 if any error occurs.
func (c *Context) BindUri(obj interface{}) error { func (c *Context) BindUri(obj interface{}) error {
if err := c.ShouldBindUri(obj); err != nil { if err := c.ShouldBindUri(obj); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
return err return err
} }
return nil return nil
@ -534,7 +543,7 @@ func (c *Context) BindUri(obj interface{}) error {
// See the binding package. // See the binding package.
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
if err := c.ShouldBindWith(obj, b); err != nil { if err := c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
return err return err
} }
return nil return nil
@ -908,7 +917,7 @@ func (c *Context) Negotiate(code int, config Negotiate) {
c.XML(code, data) c.XML(code, data)
default: default:
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
} }
} }
@ -944,15 +953,15 @@ func (c *Context) SetAccepted(formats ...string) {
// Deadline returns the time when work done on behalf of this context // Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is // should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results. // set. Successive calls to Deadline return the same results.
func (c *Context) Deadline() (time.Time, bool) { func (c *Context) Deadline() (deadline time.Time, ok bool) {
return c.Request.Context().Deadline() return
} }
// Done returns a channel that's closed when work done on behalf of this // Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can // context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value. // never be canceled. Successive calls to Done return the same value.
func (c *Context) Done() <-chan struct{} { func (c *Context) Done() <-chan struct{} {
return c.Request.Context().Done() return nil
} }
// Err returns a non-nil error value after Done is closed, // Err returns a non-nil error value after Done is closed,
@ -962,7 +971,7 @@ func (c *Context) Done() <-chan struct{} {
// Canceled if the context was canceled // Canceled if the context was canceled
// or DeadlineExceeded if the context's deadline passed. // or DeadlineExceeded if the context's deadline passed.
func (c *Context) Err() error { func (c *Context) Err() error {
return c.Request.Context().Err() return nil
} }
// Value returns the value associated with this context for key, or nil // Value returns the value associated with this context for key, or nil

17
context_17.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2018 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
// +build go1.7
package gin
import (
"github.com/gin-gonic/gin/render"
)
// PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
func (c *Context) PureJSON(code int, obj interface{}) {
c.Render(code, render.PureJSON{Data: obj})
}

27
context_17_test.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2018 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
// +build go1.7
package gin
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
// Tests that the response is serialized as JSON
// and Content-Type is set to application/json
// and special HTML characters are preserved
func TestContextRenderPureJSON(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}

View File

@ -70,7 +70,8 @@ func TestContextFormFile(t *testing.T) {
mw := multipart.NewWriter(buf) mw := multipart.NewWriter(buf)
w, err := mw.CreateFormFile("file", "test") w, err := mw.CreateFormFile("file", "test")
if assert.NoError(t, err) { if assert.NoError(t, err) {
w.Write([]byte("test")) _, err = w.Write([]byte("test"))
assert.NoError(t, err)
} }
mw.Close() mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
@ -100,10 +101,11 @@ func TestContextFormFileFailed(t *testing.T) {
func TestContextMultipartForm(t *testing.T) { func TestContextMultipartForm(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf) mw := multipart.NewWriter(buf)
mw.WriteField("foo", "bar") assert.NoError(t, mw.WriteField("foo", "bar"))
w, err := mw.CreateFormFile("file", "test") w, err := mw.CreateFormFile("file", "test")
if assert.NoError(t, err) { if assert.NoError(t, err) {
w.Write([]byte("test")) _, err = w.Write([]byte("test"))
assert.NoError(t, err)
} }
mw.Close() mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
@ -137,7 +139,8 @@ func TestSaveUploadedCreateFailed(t *testing.T) {
mw := multipart.NewWriter(buf) mw := multipart.NewWriter(buf)
w, err := mw.CreateFormFile("file", "test") w, err := mw.CreateFormFile("file", "test")
if assert.NoError(t, err) { if assert.NoError(t, err) {
w.Write([]byte("test")) _, err = w.Write([]byte("test"))
assert.NoError(t, err)
} }
mw.Close() mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
@ -159,7 +162,7 @@ func TestContextReset(t *testing.T) {
c.index = 2 c.index = 2
c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()} c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()}
c.Params = Params{Param{}} c.Params = Params{Param{}}
c.Error(errors.New("test")) c.Error(errors.New("test")) // nolint: errcheck
c.Set("foo", "bar") c.Set("foo", "bar")
c.reset() c.reset()
@ -809,7 +812,7 @@ func TestContextRenderHTML2(t *testing.T) {
assert.Len(t, router.trees, 1) assert.Len(t, router.trees, 1)
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
re := captureOutput(func() { re := captureOutput(t, func() {
SetMode(DebugMode) SetMode(DebugMode)
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
SetMode(TestMode) SetMode(TestMode)
@ -1220,7 +1223,8 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
assert.Equal(t, "application/json; charset=utf-8", contentType) assert.Equal(t, "application/json; charset=utf-8", contentType)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
buf.ReadFrom(w.Body) _, err := buf.ReadFrom(w.Body)
assert.NoError(t, err)
jsonStringBody := buf.String() jsonStringBody := buf.String()
assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody) assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody)
} }
@ -1229,11 +1233,11 @@ func TestContextError(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
assert.Empty(t, c.Errors) assert.Empty(t, c.Errors)
c.Error(errors.New("first error")) c.Error(errors.New("first error")) // nolint: errcheck
assert.Len(t, c.Errors, 1) assert.Len(t, c.Errors, 1)
assert.Equal(t, "Error #01: first error\n", c.Errors.String()) assert.Equal(t, "Error #01: first error\n", c.Errors.String())
c.Error(&Error{ c.Error(&Error{ // nolint: errcheck
Err: errors.New("second error"), Err: errors.New("second error"),
Meta: "some data 2", Meta: "some data 2",
Type: ErrorTypePublic, Type: ErrorTypePublic,
@ -1255,13 +1259,13 @@ func TestContextError(t *testing.T) {
t.Error("didn't panic") t.Error("didn't panic")
} }
}() }()
c.Error(nil) c.Error(nil) // nolint: errcheck
} }
func TestContextTypedError(t *testing.T) { func TestContextTypedError(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck
for _, err := range c.Errors.ByType(ErrorTypePublic) { for _, err := range c.Errors.ByType(ErrorTypePublic) {
assert.Equal(t, ErrorTypePublic, err.Type) assert.Equal(t, ErrorTypePublic, err.Type)
@ -1276,7 +1280,7 @@ func TestContextAbortWithError(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck
assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, abortIndex, c.index) assert.Equal(t, abortIndex, c.index)
@ -1484,15 +1488,19 @@ func TestContextShouldBindWithQuery(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused"))
var obj struct { var obj struct {
Foo string `form:"foo"` Foo string `form:"foo"`
Bar string `form:"bar"` Bar string `form:"bar"`
Foo1 string `form:"Foo"`
Bar1 string `form:"Bar"`
} }
assert.NoError(t, c.ShouldBindQuery(&obj)) assert.NoError(t, c.ShouldBindQuery(&obj))
assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "foo", obj.Bar)
assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, "foo1", obj.Bar1)
assert.Equal(t, "bar1", obj.Foo1)
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
@ -1718,7 +1726,8 @@ func TestContextStream(t *testing.T) {
stopStream = false stopStream = false
}() }()
w.Write([]byte("test")) _, err := w.Write([]byte("test"))
assert.NoError(t, err)
return stopStream return stopStream
}) })
@ -1735,7 +1744,8 @@ func TestContextStreamWithClientGone(t *testing.T) {
w.closeClient() w.closeClient()
}() }()
writer.Write([]byte("test")) _, err := writer.Write([]byte("test"))
assert.NoError(t, err)
return true return true
}) })
@ -1743,48 +1753,14 @@ func TestContextStreamWithClientGone(t *testing.T) {
assert.Equal(t, "test", w.Body.String()) assert.Equal(t, "test", w.Body.String())
} }
func TestContextHTTPContext(t *testing.T) { func TestContextResetInHandler(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) w := CreateTestResponseRecorder()
req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c, _ := CreateTestContext(w)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
c.Request = req.WithContext(ctx)
assert.NoError(t, c.Err()) c.handlers = []HandlerFunc{
assert.NotNil(t, c.Done()) func(c *Context) { c.reset() },
select {
case <-c.Done():
assert.Fail(t, "context should not be canceled")
default:
} }
assert.NotPanics(t, func() {
ti, ok := c.Deadline() c.Next()
assert.Equal(t, ti, time.Time{}) })
assert.False(t, ok)
assert.Equal(t, c.Value(0), c.Request)
cancelFunc()
assert.NotNil(t, c.Done())
select {
case <-c.Done():
default:
assert.Fail(t, "context should be canceled")
}
}
func TestContextHTTPContextWithDeadline(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
location, _ := time.LoadLocation("Europe/Paris")
assert.NotNil(t, location)
date := time.Date(2031, 12, 27, 16, 00, 00, 00, location)
ctx, cancelFunc := context.WithDeadline(context.Background(), date)
defer cancelFunc()
c.Request = req.WithContext(ctx)
assert.NoError(t, c.Err())
ti, ok := c.Deadline()
assert.Equal(t, ti, date)
assert.True(t, ok)
} }

View File

@ -32,7 +32,7 @@ func TestIsDebugging(t *testing.T) {
} }
func TestDebugPrint(t *testing.T) { func TestDebugPrint(t *testing.T) {
re := captureOutput(func() { re := captureOutput(t, func() {
SetMode(DebugMode) SetMode(DebugMode)
SetMode(ReleaseMode) SetMode(ReleaseMode)
debugPrint("DEBUG this!") debugPrint("DEBUG this!")
@ -46,7 +46,7 @@ func TestDebugPrint(t *testing.T) {
} }
func TestDebugPrintError(t *testing.T) { func TestDebugPrintError(t *testing.T) {
re := captureOutput(func() { re := captureOutput(t, func() {
SetMode(DebugMode) SetMode(DebugMode)
debugPrintError(nil) debugPrintError(nil)
debugPrintError(errors.New("this is an error")) debugPrintError(errors.New("this is an error"))
@ -56,7 +56,7 @@ func TestDebugPrintError(t *testing.T) {
} }
func TestDebugPrintRoutes(t *testing.T) { func TestDebugPrintRoutes(t *testing.T) {
re := captureOutput(func() { re := captureOutput(t, func() {
SetMode(DebugMode) SetMode(DebugMode)
debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
SetMode(TestMode) SetMode(TestMode)
@ -65,7 +65,7 @@ func TestDebugPrintRoutes(t *testing.T) {
} }
func TestDebugPrintLoadTemplate(t *testing.T) { func TestDebugPrintLoadTemplate(t *testing.T) {
re := captureOutput(func() { re := captureOutput(t, func() {
SetMode(DebugMode) SetMode(DebugMode)
templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl"))
debugPrintLoadTemplate(templ) debugPrintLoadTemplate(templ)
@ -75,7 +75,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) {
} }
func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
re := captureOutput(func() { re := captureOutput(t, func() {
SetMode(DebugMode) SetMode(DebugMode)
debugPrintWARNINGSetHTMLTemplate() debugPrintWARNINGSetHTMLTemplate()
SetMode(TestMode) SetMode(TestMode)
@ -84,7 +84,7 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
} }
func TestDebugPrintWARNINGDefault(t *testing.T) { func TestDebugPrintWARNINGDefault(t *testing.T) {
re := captureOutput(func() { re := captureOutput(t, func() {
SetMode(DebugMode) SetMode(DebugMode)
debugPrintWARNINGDefault() debugPrintWARNINGDefault()
SetMode(TestMode) SetMode(TestMode)
@ -98,7 +98,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
} }
func TestDebugPrintWARNINGNew(t *testing.T) { func TestDebugPrintWARNINGNew(t *testing.T) {
re := captureOutput(func() { re := captureOutput(t, func() {
SetMode(DebugMode) SetMode(DebugMode)
debugPrintWARNINGNew() debugPrintWARNINGNew()
SetMode(TestMode) SetMode(TestMode)
@ -106,7 +106,7 @@ func TestDebugPrintWARNINGNew(t *testing.T) {
assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re) assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re)
} }
func captureOutput(f func()) string { func captureOutput(t *testing.T, f func()) string {
reader, writer, err := os.Pipe() reader, writer, err := os.Pipe()
if err != nil { if err != nil {
panic(err) panic(err)
@ -127,7 +127,8 @@ func captureOutput(f func()) string {
go func() { go func() {
var buf bytes.Buffer var buf bytes.Buffer
wg.Done() wg.Done()
io.Copy(&buf, reader) _, err := io.Copy(&buf, reader)
assert.NoError(t, err)
out <- buf.String() out <- buf.String()
}() }()
wg.Wait() wg.Wait()

View File

@ -34,7 +34,7 @@ func TestError(t *testing.T) {
jsonBytes, _ := json.Marshal(err) jsonBytes, _ := json.Marshal(err)
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
err.SetMeta(H{ err.SetMeta(H{ // nolint: errcheck
"status": "200", "status": "200",
"data": "some data", "data": "some data",
}) })
@ -44,7 +44,7 @@ func TestError(t *testing.T) {
"data": "some data", "data": "some data",
}, err.JSON()) }, err.JSON())
err.SetMeta(H{ err.SetMeta(H{ // nolint: errcheck
"error": "custom error", "error": "custom error",
"status": "200", "status": "200",
"data": "some data", "data": "some data",
@ -59,7 +59,7 @@ func TestError(t *testing.T) {
status string status string
data string data string
} }
err.SetMeta(customError{status: "200", data: "other data"}) err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck
assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON()) assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON())
} }

5
gin.go
View File

@ -422,7 +422,10 @@ func serveError(c *Context, code int, defaultMessage []byte) {
} }
if c.writermem.Status() == code { if c.writermem.Status() == code {
c.writermem.Header()["Content-Type"] = mimePlain c.writermem.Header()["Content-Type"] = mimePlain
c.Writer.Write(defaultMessage) _, err := c.Writer.Write(defaultMessage)
if err != nil {
debugPrint("cannot write message to writer during serve error: %v", err)
}
return return
} }
c.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()

View File

@ -87,7 +87,7 @@ func TestRunEmptyWithEnv(t *testing.T) {
func TestRunTooMuchParams(t *testing.T) { func TestRunTooMuchParams(t *testing.T) {
router := New() router := New()
assert.Panics(t, func() { assert.Panics(t, func() {
router.Run("2", "2") assert.NoError(t, router.Run("2", "2"))
}) })
} }
@ -137,7 +137,7 @@ func TestBadUnixSocket(t *testing.T) {
func TestFileDescriptor(t *testing.T) { func TestFileDescriptor(t *testing.T) {
router := New() router := New()
addr, err := net.ResolveTCPAddr("tcp", ":8000") addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
assert.NoError(t, err) assert.NoError(t, err)
listener, err := net.ListenTCP("tcp", addr) listener, err := net.ListenTCP("tcp", addr)
assert.NoError(t, err) assert.NoError(t, err)
@ -152,7 +152,7 @@ func TestFileDescriptor(t *testing.T) {
// otherwise the main thread will complete // otherwise the main thread will complete
time.Sleep(5 * time.Millisecond) time.Sleep(5 * time.Millisecond)
c, err := net.Dial("tcp", "localhost:8000") c, err := net.Dial("tcp", listener.Addr().String())
assert.NoError(t, err) assert.NoError(t, err)
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")

View File

@ -471,6 +471,23 @@ func TestListOfRoutes(t *testing.T) {
}) })
} }
func TestEngineHandleContext(t *testing.T) {
r := New()
r.GET("/", func(c *Context) {
c.Request.URL.Path = "/v2"
r.HandleContext(c)
})
v2 := r.Group("/v2")
{
v2.GET("/", func(c *Context) {})
}
assert.NotPanics(t, func() {
w := performRequest(r, "GET", "/")
assert.Equal(t, 301, w.Code)
})
}
func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) {
for _, gotRoute := range gotRoutes { for _, gotRoute := range gotRoutes {
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {

View File

@ -338,7 +338,7 @@ func TestBindUriError(t *testing.T) {
} }
router.Handle("GET", "/new/rest/:num", func(c *Context) { router.Handle("GET", "/new/rest/:num", func(c *Context) {
var m Member var m Member
c.BindUri(&m) assert.Error(t, c.BindUri(&m))
}) })
path1, _ := exampleFromPath("/new/rest/:num") path1, _ := exampleFromPath("/new/rest/:num")

View File

@ -35,9 +35,9 @@ type LoggerConfig struct {
// Optional. Default value is gin.DefaultWriter. // Optional. Default value is gin.DefaultWriter.
Output io.Writer Output io.Writer
// SkipPathes is a url path array which logs are not written. // SkipPaths is a url path array which logs are not written.
// Optional. // Optional.
SkipPathes []string SkipPaths []string
} }
// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
@ -46,13 +46,22 @@ type LogFormatter func(params LogFormatterParams) string
// LogFormatterParams is the structure any formatter will be handed when time to log comes // LogFormatterParams is the structure any formatter will be handed when time to log comes
type LogFormatterParams struct { type LogFormatterParams struct {
Request *http.Request Request *http.Request
// TimeStamp shows the time after the server returns a response.
TimeStamp time.Time TimeStamp time.Time
// StatusCode is HTTP response code.
StatusCode int StatusCode int
// Latency is how much time the server cost to process a certain request.
Latency time.Duration Latency time.Duration
// ClientIP equals Context's ClientIP method.
ClientIP string ClientIP string
// Method is the HTTP method given to the request.
Method string Method string
// Path is a path the client requests.
Path string Path string
// ErrorMessage is set if error has occurred in processing the request.
ErrorMessage string ErrorMessage string
// IsTerm shows whether does gin's output descriptor refers to a terminal.
IsTerm bool IsTerm bool
} }
@ -115,7 +124,7 @@ func LoggerWithFormatter(f LogFormatter) HandlerFunc {
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
return LoggerWithConfig(LoggerConfig{ return LoggerWithConfig(LoggerConfig{
Output: out, Output: out,
SkipPathes: notlogged, SkipPaths: notlogged,
}) })
} }
@ -131,7 +140,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
out = DefaultWriter out = DefaultWriter
} }
notlogged := conf.SkipPathes notlogged := conf.SkipPaths
isTerm := true isTerm := true

View File

@ -278,13 +278,13 @@ func TestErrorLogger(t *testing.T) {
router := New() router := New()
router.Use(ErrorLogger()) router.Use(ErrorLogger())
router.GET("/error", func(c *Context) { router.GET("/error", func(c *Context) {
c.Error(errors.New("this is an error")) c.Error(errors.New("this is an error")) // nolint: errcheck
}) })
router.GET("/abort", func(c *Context) { router.GET("/abort", func(c *Context) {
c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck
}) })
router.GET("/print", func(c *Context) { router.GET("/print", func(c *Context) {
c.Error(errors.New("this is an error")) c.Error(errors.New("this is an error")) // nolint: errcheck
c.String(http.StatusInternalServerError, "hola!") c.String(http.StatusInternalServerError, "hola!")
}) })
@ -321,7 +321,7 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
router := New() router := New()
router.Use(LoggerWithConfig(LoggerConfig{ router.Use(LoggerWithConfig(LoggerConfig{
Output: buffer, Output: buffer,
SkipPathes: []string{"/skipped"}, SkipPaths: []string{"/skipped"},
})) }))
router.GET("/logged", func(c *Context) {}) router.GET("/logged", func(c *Context) {})
router.GET("/skipped", func(c *Context) {}) router.GET("/skipped", func(c *Context) {})

View File

@ -208,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
router := New() router := New()
router.Use(func(context *Context) { router.Use(func(context *Context) {
signature += "A" signature += "A"
context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) // nolint: errcheck
}) })
router.Use(func(context *Context) { router.Use(func(context *Context) {
signature += "B" signature += "B"

View File

@ -66,7 +66,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
// If the connection is dead, we can't write a status to it. // If the connection is dead, we can't write a status to it.
if brokenPipe { if brokenPipe {
c.Error(err.(error)) c.Error(err.(error)) // nolint: errcheck
c.Abort() c.Abort()
} else { } else {
c.AbortWithStatus(http.StatusInternalServerError) c.AbortWithStatus(http.StatusInternalServerError)

View File

@ -72,8 +72,8 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error {
if err != nil { if err != nil {
return err return err
} }
w.Write(jsonBytes) _, err = w.Write(jsonBytes)
return nil return err
} }
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. // Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
@ -83,8 +83,8 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
if err != nil { if err != nil {
return err return err
} }
w.Write(jsonBytes) _, err = w.Write(jsonBytes)
return nil return err
} }
// WriteContentType (IndentedJSON) writes JSON ContentType. // WriteContentType (IndentedJSON) writes JSON ContentType.
@ -101,10 +101,13 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
} }
// if the jsonBytes is array values // if the jsonBytes is array values
if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) { if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) {
w.Write([]byte(r.Prefix)) _, err = w.Write([]byte(r.Prefix))
if err != nil {
return err
} }
w.Write(jsonBytes) }
return nil _, err = w.Write(jsonBytes)
return err
} }
// WriteContentType (SecureJSON) writes JSON ContentType. // WriteContentType (SecureJSON) writes JSON ContentType.
@ -121,15 +124,27 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
} }
if r.Callback == "" { if r.Callback == "" {
w.Write(ret) _, err = w.Write(ret)
return nil return err
} }
callback := template.JSEscapeString(r.Callback) callback := template.JSEscapeString(r.Callback)
w.Write([]byte(callback)) _, err = w.Write([]byte(callback))
w.Write([]byte("(")) if err != nil {
w.Write(ret) return err
w.Write([]byte(")")) }
_, err = w.Write([]byte("("))
if err != nil {
return err
}
_, err = w.Write(ret)
if err != nil {
return err
}
_, err = w.Write([]byte(")"))
if err != nil {
return err
}
return nil return nil
} }
@ -156,8 +171,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
buffer.WriteString(cvt) buffer.WriteString(cvt)
} }
w.Write(buffer.Bytes()) _, err = w.Write(buffer.Bytes())
return nil return err
} }
// WriteContentType (AsciiJSON) writes JSON ContentType. // WriteContentType (AsciiJSON) writes JSON ContentType.

View File

@ -26,8 +26,8 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error {
return err return err
} }
w.Write(bytes) _, err = w.Write(bytes)
return nil return err
} }
// WriteContentType (ProtoBuf) writes ProtoBuf ContentType. // WriteContentType (ProtoBuf) writes ProtoBuf ContentType.

View File

@ -71,7 +71,7 @@ func TestRenderJSONPanics(t *testing.T) {
data := make(chan int) data := make(chan int)
// json: unsupported type: chan int // json: unsupported type: chan int
assert.Panics(t, func() { (JSON{data}).Render(w) }) assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) })
} }
func TestRenderIndentedJSON(t *testing.T) { func TestRenderIndentedJSON(t *testing.T) {
@ -347,7 +347,7 @@ func TestRenderRedirect(t *testing.T) {
} }
w = httptest.NewRecorder() w = httptest.NewRecorder()
assert.Panics(t, func() { data2.Render(w) }) assert.Panics(t, func() { assert.NoError(t, data2.Render(w)) })
// only improve coverage // only improve coverage
data2.WriteContentType(w) data2.WriteContentType(w)

View File

@ -20,8 +20,7 @@ var plainContentType = []string{"text/plain; charset=utf-8"}
// Render (String) writes data with custom ContentType. // Render (String) writes data with custom ContentType.
func (r String) Render(w http.ResponseWriter) error { func (r String) Render(w http.ResponseWriter) error {
WriteString(w, r.Format, r.Data) return WriteString(w, r.Format, r.Data)
return nil
} }
// WriteContentType (String) writes Plain ContentType. // WriteContentType (String) writes Plain ContentType.
@ -30,11 +29,12 @@ func (r String) WriteContentType(w http.ResponseWriter) {
} }
// WriteString writes data according to its format and write custom ContentType. // WriteString writes data according to its format and write custom ContentType.
func WriteString(w http.ResponseWriter, format string, data []interface{}) { func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) {
writeContentType(w, plainContentType) writeContentType(w, plainContentType)
if len(data) > 0 { if len(data) > 0 {
fmt.Fprintf(w, format, data...) _, err = fmt.Fprintf(w, format, data...)
return return
} }
io.WriteString(w, format) _, err = io.WriteString(w, format)
return
} }

View File

@ -26,8 +26,8 @@ func (r YAML) Render(w http.ResponseWriter) error {
return err return err
} }
w.Write(bytes) _, err = w.Write(bytes)
return nil return err
} }
// WriteContentType (YAML) writes YAML ContentType for response. // WriteContentType (YAML) writes YAML ContentType for response.

View File

@ -103,7 +103,8 @@ func TestResponseWriterHijack(t *testing.T) {
w := ResponseWriter(writer) w := ResponseWriter(writer)
assert.Panics(t, func() { assert.Panics(t, func() {
w.Hijack() _, _, err := w.Hijack()
assert.NoError(t, err)
}) })
assert.True(t, w.Written()) assert.True(t, w.Written())

View File

@ -47,7 +47,7 @@ type RouterGroup struct {
var _ IRouter = &RouterGroup{} var _ IRouter = &RouterGroup{}
// Use adds middleware to the group, see example code in github. // Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...) group.Handlers = append(group.Handlers, middleware...)
return group.returnObj() return group.returnObj()
@ -78,7 +78,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl
// Handle registers a new request handle and middleware with the given path and method. // Handle registers a new request handle and middleware with the given path and method.
// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. // The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.
// See the example code in github. // See the example code in GitHub.
// //
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
// functions can be used. // functions can be used.

View File

@ -251,7 +251,8 @@ func TestRouteStaticFile(t *testing.T) {
t.Error(err) t.Error(err)
} }
defer os.Remove(f.Name()) defer os.Remove(f.Name())
f.WriteString("Gin Web Framework") _, err = f.WriteString("Gin Web Framework")
assert.NoError(t, err)
f.Close() f.Close()
dir, filename := filepath.Split(f.Name()) dir, filename := filepath.Split(f.Name())
@ -426,6 +427,16 @@ func TestRouterStaticFSNotFound(t *testing.T) {
assert.Equal(t, "non existent", w.Body.String()) assert.Equal(t, "non existent", w.Body.String())
} }
func TestRouterStaticFSFileNotFound(t *testing.T) {
router := New()
router.StaticFS("/", http.FileSystem(http.Dir(".")))
assert.NotPanics(t, func() {
performRequest(router, "GET", "/nonexistent")
})
}
func TestRouteRawPath(t *testing.T) { func TestRouteRawPath(t *testing.T) {
route := New() route := New()
route.UseRawPath = true route.UseRawPath = true

View File

@ -4,12 +4,12 @@
// +build tools // +build tools
// This file exists to cause `go mod` and `go get` to believe these tools // This package exists to cause `go mod` and `go get` to believe these tools
// are dependencies, even though they are not runtime dependencies of any // are dependencies, even though they are not runtime dependencies of any
// gin package. This means they will appear in `go.mod` file, but will not // gin package. This means they will appear in `go.mod` file, but will not
// be a part of the build. // be a part of the build.
package gin package tools
import ( import (
_ "github.com/campoy/embedmd" _ "github.com/campoy/embedmd"