diff --git a/.travis.yml b/.travis.yml index 644a178..6532a33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,13 +10,14 @@ git: depth: 3 install: - - go get -v github.com/kardianos/govendor - - govendor sync - - go get -u github.com/campoy/embedmd + - make install script: - - embedmd -d README.md - - go test -v -covermode=count -coverprofile=coverage.out + - make vet + - make fmt-check + - make embedmd + - make misspell-check + - make test after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/CHANGELOG.md b/CHANGELOG.md index 441df24..ee485ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,22 @@ - [NEW] Add support for Let's Encrypt via gin-gonic/autotls - [NEW] Improve README examples and add extra at examples folder - [NEW] Improved support with App Engine +- [NEW] Add custom template delimiters, see #860 +- [NEW] Add Template Func Maps, see #962 +- [NEW] Add \*context.Handler(), see #928 - [NEW] Add \*context.GetRawData() - [NEW] Add \*context.GetHeader() (request) - [NEW] Add \*context.AbortWithStatusJSON() (JSON content type) +- [NEW] Add \*context.Keys type cast helpers +- [NEW] Add \*context.ShouldBindWith() +- [NEW] Add \*context.MustBindWith() +- [NEW] Add \*engine.SetFuncMap() +- [DEPRECATE] On next release: \*context.BindWith(), see #855 - [FIX] Refactor render - [FIX] Reworked tests - [FIX] logger now supports cygwin - [FIX] Use X-Forwarded-For before X-Real-Ip +- [FIX] time.Time binding (#904) ### Gin 1.1.4 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9ba475a --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +GOFMT ?= gofmt "-s" +PACKAGES ?= $(shell go list ./... | grep -v /vendor/) +GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") + +all: build + +install: deps + govendor sync + +.PHONY: test +test: + go test -v -covermode=count -coverprofile=coverage.out + +.PHONY: fmt +fmt: + $(GOFMT) -w $(GOFILES) + +.PHONY: fmt-check +fmt-check: + # get all go files and run go fmt on them + @diff=$$($(GOFMT) -d $(GOFILES)); \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make fmt' and commit the result:"; \ + echo "$${diff}"; \ + exit 1; \ + fi; + +vet: + go vet $(PACKAGES) + +deps: + @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/kardianos/govendor; \ + fi + @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/campoy/embedmd; \ + fi + +embedmd: + embedmd -d *.md + +.PHONY: lint +lint: + @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/golang/lint/golint; \ + fi + for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; + +.PHONY: misspell-check +misspell-check: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/client9/misspell/cmd/misspell; \ + fi + misspell -error $(GOFILES) + +.PHONY: misspell +misspell: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/client9/misspell/cmd/misspell; \ + fi + misspell -w $(GOFILES) diff --git a/README.md b/README.md index 535ee4e..494cc8b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ -# Gin Web Framework +# Gin Web Framework -[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph Badge](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) + + +[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) + [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) + [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) + [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) + [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. @@ -13,7 +19,7 @@ $ cat test.go ```go package main -import "gopkg.in/gin-gonic/gin.v1" +import "github.com/gin-gonic/gin" func main() { r := gin.Default() @@ -82,13 +88,13 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648 1. Download and install it: ```sh -$ go get gopkg.in/gin-gonic/gin.v1 +$ go get github.com/gin-gonic/gin ``` 2. Import it in your code: ```go -import "gopkg.in/gin-gonic/gin.v1" +import "github.com/gin-gonic/gin" ``` 3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. @@ -97,12 +103,36 @@ import "gopkg.in/gin-gonic/gin.v1" import "net/http" ``` -4. (Optional) Use latest changes (note: they may be broken and/or unstable): +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) -```sh -$ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1 -$ git -C $GIN_PATH checkout develop -$ git -C $GIN_PATH pull origin develop +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin + +```sh +$ govendor init +$ govendor add github.com/gin-gonic/gin@v1.2 +``` + +4. Copy a starting template inside your project + +```sh +$ cp ~/go/src/github.com/gin-gonic/gin/examples/basic/* . +``` + +5. Run your project + +```sh +$ go run main.go ``` ## API Examples @@ -451,7 +481,7 @@ func startPage(c *gin.Context) { package main import ( - "gopkg.in/gin-gonic/gin.v1" + "github.com/gin-gonic/gin" ) type LoginForm struct { @@ -463,7 +493,7 @@ func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: - // c.BindWith(&form, binding.Form) + // c.MustBindWith(&form, binding.Form) // or you can simply use autobinding with Bind method: var form LoginForm // in this case proper binding will be automatically selected @@ -622,6 +652,54 @@ func main() { } ``` +You may use custom delims + +```go + r := gin.Default() + r.Delims("{[{", "}]}") + r.LoadHTMLGlob("/path/to/templates")) +``` + +#### Add custom template funcs + +main.go + +```go + ... + + func formatAsDate(t time.Time) string { + year, month, day := t.Date() + return fmt.Sprintf("%d/%02d/%02d", year, month, day) + } + + ... + + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + + ... + + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) + + ... +``` + +raw.tmpl + +```html +Date: {[{.now | formatAsDate}]} +``` + +Result: +``` +Date: 2017/07/01 +``` + ### Multitemplate Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. @@ -917,7 +995,7 @@ func main() { - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. - With pull requests: - - Open your pull request against develop + - Open your pull request against master - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. diff --git a/benchmarks_test.go b/benchmarks_test.go index ebe9804..a2c62ba 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -1,3 +1,7 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package gin import ( diff --git a/binding/binding.go b/binding/binding.go index d3a2c97..1dbf246 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -48,19 +48,19 @@ var ( func Default(method, contentType string) Binding { if method == "GET" { return Form - } else { - switch contentType { - case MIMEJSON: - return JSON - case MIMEXML, MIMEXML2: - return XML - case MIMEPROTOBUF: - return ProtoBuf - case MIMEMSGPACK, MIMEMSGPACK2: - return MsgPack - default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: - return Form - } + } + + switch contentType { + case MIMEJSON: + return JSON + case MIMEXML, MIMEXML2: + return XML + case MIMEPROTOBUF: + return ProtoBuf + case MIMEMSGPACK, MIMEMSGPACK2: + return MsgPack + default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: + return Form } } diff --git a/binding/binding_test.go b/binding/binding_test.go index cf00594..d7cdf77 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -12,9 +12,8 @@ import ( "github.com/gin-gonic/gin/binding/example" "github.com/golang/protobuf/proto" - "github.com/ugorji/go/codec" - "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" ) type FooStruct struct { diff --git a/binding/default_validator.go b/binding/default_validator.go index 760728b..19885f1 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -1,3 +1,7 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package binding import ( diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 1af8165..34f1267 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -152,7 +152,7 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val if timeFormat == "" { return errors.New("Blank time format") } - + if val == "" { value.Set(reflect.ValueOf(time.Time{})) return nil diff --git a/binding/json.go b/binding/json.go index 6e53244..486b973 100644 --- a/binding/json.go +++ b/binding/json.go @@ -6,7 +6,6 @@ package binding import ( "encoding/json" - "net/http" ) diff --git a/binding/protobuf.go b/binding/protobuf.go index 9f95622..c7eb84e 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -5,10 +5,10 @@ package binding import ( - "github.com/golang/protobuf/proto" - "io/ioutil" "net/http" + + "github.com/golang/protobuf/proto" ) type protobufBinding struct{} diff --git a/context.go b/context.go index dd0dc5d..5196bb1 100644 --- a/context.go +++ b/context.go @@ -16,9 +16,9 @@ import ( "strings" "time" + "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" - "gopkg.in/gin-contrib/sse.v0" ) // Content-Type MIME of the most common data formats @@ -85,6 +85,11 @@ func (c *Context) HandlerName() string { return nameOfFunction(c.handlers.Last()) } +// Handler returns the main handler. +func (c *Context) Handler() HandlerFunc { + return c.handlers.Last() +} + /************************************/ /*********** FLOW CONTROL ***********/ /************************************/ @@ -191,6 +196,94 @@ func (c *Context) MustGet(key string) interface{} { panic("Key \"" + key + "\" does not exist") } +// GetString returns the value associated with the key as a string. +func (c *Context) GetString(key string) (s string) { + if val, ok := c.Get(key); ok && val != nil { + s, _ = val.(string) + } + return +} + +// GetBool returns the value associated with the key as a boolean. +func (c *Context) GetBool(key string) (b bool) { + if val, ok := c.Get(key); ok && val != nil { + b, _ = val.(bool) + } + return +} + +// GetInt returns the value associated with the key as an integer. +func (c *Context) GetInt(key string) (i int) { + if val, ok := c.Get(key); ok && val != nil { + i, _ = val.(int) + } + return +} + +// GetInt64 returns the value associated with the key as an integer. +func (c *Context) GetInt64(key string) (i64 int64) { + if val, ok := c.Get(key); ok && val != nil { + i64, _ = val.(int64) + } + return +} + +// GetFloat64 returns the value associated with the key as a float64. +func (c *Context) GetFloat64(key string) (f64 float64) { + if val, ok := c.Get(key); ok && val != nil { + f64, _ = val.(float64) + } + return +} + +// GetTime returns the value associated with the key as time. +func (c *Context) GetTime(key string) (t time.Time) { + if val, ok := c.Get(key); ok && val != nil { + t, _ = val.(time.Time) + } + return +} + +// GetDuration returns the value associated with the key as a duration. +func (c *Context) GetDuration(key string) (d time.Duration) { + if val, ok := c.Get(key); ok && val != nil { + d, _ = val.(time.Duration) + } + return +} + +// GetStringSlice returns the value associated with the key as a slice of strings. +func (c *Context) GetStringSlice(key string) (ss []string) { + if val, ok := c.Get(key); ok && val != nil { + ss, _ = val.([]string) + } + return +} + +// GetStringMap returns the value associated with the key as a map of interfaces. +func (c *Context) GetStringMap(key string) (sm map[string]interface{}) { + if val, ok := c.Get(key); ok && val != nil { + sm, _ = val.(map[string]interface{}) + } + return +} + +// GetStringMapString returns the value associated with the key as a map of strings. +func (c *Context) GetStringMapString(key string) (sms map[string]string) { + if val, ok := c.Get(key); ok && val != nil { + sms, _ = val.(map[string]string) + } + return +} + +// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. +func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) { + if val, ok := c.Get(key); ok && val != nil { + smss, _ = val.(map[string][]string) + } + return +} + /************************************/ /************ INPUT DATA ************/ /************************************/ @@ -341,22 +434,30 @@ func (c *Context) MultipartForm() (*multipart.Form, error) { // Like ParseBody() but this method also writes a 400 error if the json is not valid. func (c *Context) Bind(obj interface{}) error { b := binding.Default(c.Request.Method, c.ContentType()) - return c.BindWith(obj, b) + return c.MustBindWith(obj, b) } -// BindJSON is a shortcut for c.BindWith(obj, binding.JSON) +// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON) func (c *Context) BindJSON(obj interface{}) error { - return c.BindWith(obj, binding.JSON) + return c.MustBindWith(obj, binding.JSON) } -// BindWith binds the passed struct pointer using the specified binding engine. +// MustBindWith binds the passed struct pointer using the specified binding +// engine. It will abort the request with HTTP 400 if any error ocurrs. // See the binding package. -func (c *Context) BindWith(obj interface{}, b binding.Binding) error { - if err := b.Bind(c.Request, obj); err != nil { +func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { + if err = c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(400, err).SetType(ErrorTypeBind) - return err } - return nil + + return +} + +// ShouldBindWith binds the passed struct pointer using the specified binding +// engine. +// See the binding package. +func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { + return b.Bind(c.Request, obj) } // ClientIP implements a best effort algorithm to return the real client IP, it parses diff --git a/context_appengine.go b/context_appengine.go index d9cb22f..38c189a 100644 --- a/context_appengine.go +++ b/context_appengine.go @@ -1,5 +1,9 @@ // +build appengine +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package gin func init() { diff --git a/context_test.go b/context_test.go index b4c1f08..fe31f09 100644 --- a/context_test.go +++ b/context_test.go @@ -12,13 +12,14 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "reflect" "strings" "testing" "time" + "github.com/gin-contrib/sse" "github.com/stretchr/testify/assert" "golang.org/x/net/context" - "gopkg.in/gin-contrib/sse.v0" ) var _ context.Context = &Context{} @@ -168,6 +169,85 @@ func TestContextSetGetValues(t *testing.T) { } +func TestContextGetString(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("string", "this is a string") + assert.Equal(t, "this is a string", c.GetString("string")) +} + +func TestContextSetGetBool(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("bool", true) + assert.Equal(t, true, c.GetBool("bool")) +} + +func TestContextGetInt(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("int", 1) + assert.Equal(t, 1, c.GetInt("int")) +} + +func TestContextGetInt64(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("int64", int64(42424242424242)) + assert.Equal(t, int64(42424242424242), c.GetInt64("int64")) +} + +func TestContextGetFloat64(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("float64", 4.2) + assert.Equal(t, 4.2, c.GetFloat64("float64")) +} + +func TestContextGetTime(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + t1, _ := time.Parse("1/2/2006 15:04:05", "01/01/2017 12:00:00") + c.Set("time", t1) + assert.Equal(t, t1, c.GetTime("time")) +} + +func TestContextGetDuration(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("duration", time.Second) + assert.Equal(t, time.Second, c.GetDuration("duration")) +} + +func TestContextGetStringSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("slice", []string{"foo"}) + assert.Equal(t, []string{"foo"}, c.GetStringSlice("slice")) +} + +func TestContextGetStringMap(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + var m = make(map[string]interface{}) + m["foo"] = 1 + c.Set("map", m) + + assert.Equal(t, m, c.GetStringMap("map")) + assert.Equal(t, 1, c.GetStringMap("map")["foo"]) +} + +func TestContextGetStringMapString(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + var m = make(map[string]string) + m["foo"] = "bar" + c.Set("map", m) + + assert.Equal(t, m, c.GetStringMapString("map")) + assert.Equal(t, "bar", c.GetStringMapString("map")["foo"]) +} + +func TestContextGetStringMapStringSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + var m = make(map[string][]string) + m["foo"] = []string{"foo"} + c.Set("map", m) + + assert.Equal(t, m, c.GetStringMapStringSlice("map")) + assert.Equal(t, []string{"foo"}, c.GetStringMapStringSlice("map")["foo"]) +} + func TestContextCopy(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.index = 2 @@ -198,6 +278,17 @@ func handlerNameTest(c *Context) { } +var handlerTest HandlerFunc = func(c *Context) { + +} + +func TestContextHandler(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.handlers = HandlersChain{func(c *Context) {}, handlerTest} + + assert.Equal(t, reflect.ValueOf(handlerTest).Pointer(), reflect.ValueOf(c.Handler()).Pointer()) +} + func TestContextQuery(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) @@ -384,12 +475,21 @@ func TestContextSetCookie(t *testing.T) { assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") } +func TestContextSetCookiePathEmpty(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.SetCookie("user", "gin", 1, "", "localhost", true, true) + assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") +} + func TestContextGetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("GET", "/get", nil) c.Request.Header.Set("Cookie", "user=gin") cookie, _ := c.Cookie("user") assert.Equal(t, cookie, "gin") + + _, err := c.Cookie("nokey") + assert.Error(t, err) } func TestContextBodyAllowedForStatus(t *testing.T) { @@ -737,6 +837,68 @@ func TestContextRenderRedirectAll(t *testing.T) { assert.NotPanics(t, func() { c.Redirect(308, "/resource") }) } +func TestContextNegotiationWithJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + + c.Negotiate(200, Negotiate{ + Offered: []string{MIMEJSON, MIMEXML}, + Data: H{"foo": "bar"}, + }) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + +func TestContextNegotiationWithXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + + c.Negotiate(200, Negotiate{ + Offered: []string{MIMEXML, MIMEJSON}, + Data: H{"foo": "bar"}, + }) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + +func TestContextNegotiationWithHTML(t *testing.T) { + w := httptest.NewRecorder() + c, router := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) + router.SetHTMLTemplate(templ) + + c.Negotiate(200, Negotiate{ + Offered: []string{MIMEHTML}, + Data: H{"name": "gin"}, + HTMLName: "t", + }) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "Hello gin", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + +func TestContextNegotiationNotSupport(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + + c.Negotiate(200, Negotiate{ + Offered: []string{MIMEPOSTForm}, + }) + + assert.Equal(t, 406, w.Code) + assert.Equal(t, c.index, abortIndex) + assert.True(t, c.IsAborted()) +} + func TestContextNegotiationFormat(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "", nil) diff --git a/debug_test.go b/debug_test.go index deceaa6..c30081c 100644 --- a/debug_test.go +++ b/debug_test.go @@ -7,6 +7,7 @@ package gin import ( "bytes" "errors" + "html/template" "io" "log" "os" @@ -66,6 +67,25 @@ func TestDebugPrintRoutes(t *testing.T) { assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, w.String()) } +func TestDebugPrintLoadTemplate(t *testing.T) { + var w bytes.Buffer + setup(&w) + defer teardown() + + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl")) + debugPrintLoadTemplate(templ) + assert.Equal(t, w.String(), "[GIN-debug] Loaded HTML Templates (2): \n\t- \n\t- hello.tmpl\n\n") +} + +func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { + var w bytes.Buffer + setup(&w) + defer teardown() + + debugPrintWARNINGSetHTMLTemplate() + assert.Equal(t, w.String(), "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n") +} + func setup(w io.Writer) { SetMode(DebugMode) log.SetOutput(w) diff --git a/deprecated.go b/deprecated.go index 0488a9b..27e8f55 100644 --- a/deprecated.go +++ b/deprecated.go @@ -4,9 +4,22 @@ package gin -import "log" +import ( + "github.com/gin-gonic/gin/binding" + "log" +) func (c *Context) GetCookie(name string) (string, error) { log.Println("GetCookie() method is deprecated. Use Cookie() instead.") return c.Cookie(name) } + +// BindWith binds the passed struct pointer using the specified binding engine. +// See the binding package. +func (c *Context) BindWith(obj interface{}, b binding.Binding) error { + log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to + be deprecated, please check issue #662 and either use MustBindWith() if you + want HTTP 400 to be automatically returned if any error occur, of use + ShouldBindWith() if you need to manage the error.`) + return c.MustBindWith(obj, b) +} diff --git a/errors.go b/errors.go index 694b428..896af6f 100644 --- a/errors.go +++ b/errors.go @@ -80,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool { return (msg.Type & flags) > 0 } -// Returns a readonly copy filterd the byte. +// Returns a readonly copy filtered the byte. // ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic func (a errorMsgs) ByType(typ ErrorType) errorMsgs { if len(a) == 0 { diff --git a/errors_test.go b/errors_test.go index c9a3407..1aa0cdd 100644 --- a/errors_test.go +++ b/errors_test.go @@ -54,6 +54,13 @@ func TestError(t *testing.T) { "status": "200", "data": "some data", }) + + type customError struct { + status string + data string + } + err.SetMeta(customError{status: "200", data: "other data"}) + assert.Equal(t, err.JSON(), customError{status: "200", data: "other data"}) } func TestErrorSlice(t *testing.T) { diff --git a/examples/app-engine/hello.go b/examples/app-engine/hello.go index a5e1796..da7e4ae 100644 --- a/examples/app-engine/hello.go +++ b/examples/app-engine/hello.go @@ -1,8 +1,9 @@ package hello import ( - "github.com/gin-gonic/gin" "net/http" + + "github.com/gin-gonic/gin" ) // This function's name is a must. App Engine uses it to drive the requests properly. diff --git a/examples/realtime-advanced/Makefile b/examples/realtime-advanced/Makefile new file mode 100644 index 0000000..104ce80 --- /dev/null +++ b/examples/realtime-advanced/Makefile @@ -0,0 +1,10 @@ +all: deps build + +.PHONY: deps +deps: + go get -d -v github.com/dustin/go-broadcast/... + go get -d -v github.com/manucorporat/stats/... + +.PHONY: build +build: deps + go build -o realtime-advanced main.go rooms.go routes.go stats.go diff --git a/examples/realtime-advanced/routes.go b/examples/realtime-advanced/routes.go index b187756..86da9be 100644 --- a/examples/realtime-advanced/routes.go +++ b/examples/realtime-advanced/routes.go @@ -11,7 +11,6 @@ import ( ) func rateLimit(c *gin.Context) { - ip := c.ClientIP() value := int(ips.Add(ip, 1)) if value%50 == 0 { diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go index 554ab86..4bca3ae 100644 --- a/examples/realtime-advanced/stats.go +++ b/examples/realtime-advanced/stats.go @@ -8,11 +8,13 @@ import ( "github.com/manucorporat/stats" ) -var ips = stats.New() -var messages = stats.New() -var users = stats.New() -var mutexStats sync.RWMutex -var savedStats map[string]uint64 +var ( + ips = stats.New() + messages = stats.New() + users = stats.New() + mutexStats sync.RWMutex + savedStats map[string]uint64 +) func statsWorker() { c := time.Tick(1 * time.Second) diff --git a/examples/realtime-chat/Makefile b/examples/realtime-chat/Makefile new file mode 100644 index 0000000..dea583d --- /dev/null +++ b/examples/realtime-chat/Makefile @@ -0,0 +1,9 @@ +all: deps build + +.PHONY: deps +deps: + go get -d -v github.com/dustin/go-broadcast/... + +.PHONY: build +build: deps + go build -o realtime-chat main.go rooms.go template.go diff --git a/fixtures/basic/hello.tmpl b/fixtures/basic/hello.tmpl new file mode 100644 index 0000000..0767ef3 --- /dev/null +++ b/fixtures/basic/hello.tmpl @@ -0,0 +1 @@ +