mirror of https://github.com/gin-gonic/gin.git
Merge branch 'master' into master
This commit is contained in:
commit
ec4e6deada
|
@ -3,8 +3,6 @@ language: go
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- go: 1.6.x
|
|
||||||
- go: 1.7.x
|
|
||||||
- go: 1.8.x
|
- go: 1.8.x
|
||||||
- go: 1.9.x
|
- go: 1.9.x
|
||||||
- go: 1.10.x
|
- go: 1.10.x
|
||||||
|
|
58
CHANGELOG.md
58
CHANGELOG.md
|
@ -1,4 +1,60 @@
|
||||||
# CHANGELOG
|
|
||||||
|
### Gin 1.4.0
|
||||||
|
|
||||||
|
- [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569)
|
||||||
|
- [NEW] Refactor of form mapping multipart requesta [#1829](https://github.com/gin-gonic/gin/pull/1829)
|
||||||
|
- [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830)
|
||||||
|
- [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802)
|
||||||
|
- [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264)
|
||||||
|
- [NEW] Add support for mapping arrays [#1797](https://github.com/gin-gonic/gin/pull/1797)
|
||||||
|
- [FIX] Readme updates [#1793](https://github.com/gin-gonic/gin/pull/1793) [#1788](https://github.com/gin-gonic/gin/pull/1788) [1789](https://github.com/gin-gonic/gin/pull/1789)
|
||||||
|
- [FIX] StaticFS: Fixed Logging two log lines on 404. [#1805](https://github.com/gin-gonic/gin/pull/1805), [#1804](https://github.com/gin-gonic/gin/pull/1804)
|
||||||
|
- [NEW] Make context.Keys available as LogFormatterParams [#1779](https://github.com/gin-gonic/gin/pull/1779)
|
||||||
|
- [NEW] Use internal/json for Marshal/Unmarshal [#1791](https://github.com/gin-gonic/gin/pull/1791)
|
||||||
|
- [NEW] Support mapping time.Duration [#1794](https://github.com/gin-gonic/gin/pull/1794)
|
||||||
|
- [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749)
|
||||||
|
- [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252)
|
||||||
|
- [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775)
|
||||||
|
- [NEW] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260)
|
||||||
|
- [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112)
|
||||||
|
- [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238)
|
||||||
|
- [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020)
|
||||||
|
- [NEW] Add context.HandlerNames() [#1729](https://github.com/gin-gonic/gin/pull/1729)
|
||||||
|
- [FIX] Change color methods to public in the defaultLogger. [#1771](https://github.com/gin-gonic/gin/pull/1771)
|
||||||
|
- [FIX] Update writeHeaders method to use http.Header.Set [#1722](https://github.com/gin-gonic/gin/pull/1722)
|
||||||
|
- [NEW] Add response size to LogFormatterParams [#1752](https://github.com/gin-gonic/gin/pull/1752)
|
||||||
|
- [NEW] Allow ignoring field on form mapping [#1733](https://github.com/gin-gonic/gin/pull/1733)
|
||||||
|
- [NEW] Add a function to force color in console output. [#1724](https://github.com/gin-gonic/gin/pull/1724)
|
||||||
|
- [FIX] Context.Next() - recheck len of handlers on every iteration. [#1745](https://github.com/gin-gonic/gin/pull/1745)
|
||||||
|
- [FIX] Fix all errcheck warnings [#1739](https://github.com/gin-gonic/gin/pull/1739) [#1653](https://github.com/gin-gonic/gin/pull/1653)
|
||||||
|
- [NEW] context: inherits context cancellation and deadline from http.Request context for Go>=1.7 [#1690](https://github.com/gin-gonic/gin/pull/1690)
|
||||||
|
- [NEW] Binding for URL Params [#1694](https://github.com/gin-gonic/gin/pull/1694)
|
||||||
|
- [NEW] Add LoggerWithFormatter method [#1677](https://github.com/gin-gonic/gin/pull/1677)
|
||||||
|
- [FIX] CI testing updates [#1671](https://github.com/gin-gonic/gin/pull/1671) [#1670](https://github.com/gin-gonic/gin/pull/1670) [#1682](https://github.com/gin-gonic/gin/pull/1682) [#1669](https://github.com/gin-gonic/gin/pull/1669)
|
||||||
|
- [FIX] StaticFS(): Send 404 when path does not exist [#1663](https://github.com/gin-gonic/gin/pull/1663)
|
||||||
|
- [FIX] Handle nil body for JSON binding [#1638](https://github.com/gin-gonic/gin/pull/1638)
|
||||||
|
- [FIX] Support bind uri param [#1612](https://github.com/gin-gonic/gin/pull/1612)
|
||||||
|
- [FIX] recovery: fix issue with syscall import on google app engine [#1640](https://github.com/gin-gonic/gin/pull/1640)
|
||||||
|
- [FIX] Make sure the debug log contains line breaks [#1650](https://github.com/gin-gonic/gin/pull/1650)
|
||||||
|
- [FIX] Panic stack trace being printed during recovery of broken pipe [#1089](https://github.com/gin-gonic/gin/pull/1089) [#1259](https://github.com/gin-gonic/gin/pull/1259)
|
||||||
|
- [NEW] RunFd method to run http.Server through a file descriptor [#1609](https://github.com/gin-gonic/gin/pull/1609)
|
||||||
|
- [NEW] Yaml binding support [#1618](https://github.com/gin-gonic/gin/pull/1618)
|
||||||
|
- [FIX] Pass MaxMultipartMemory when FormFile is called [#1600](https://github.com/gin-gonic/gin/pull/1600)
|
||||||
|
- [FIX] LoadHTML* tests [#1559](https://github.com/gin-gonic/gin/pull/1559)
|
||||||
|
- [FIX] Removed use of sync.pool from HandleContext [#1565](https://github.com/gin-gonic/gin/pull/1565)
|
||||||
|
- [FIX] Format output log to os.Stderr [#1571](https://github.com/gin-gonic/gin/pull/1571)
|
||||||
|
- [FIX] Make logger use a yellow background and a darkgray text for legibility [#1570](https://github.com/gin-gonic/gin/pull/1570)
|
||||||
|
- [FIX] Remove sensitive request information from panic log. [#1370](https://github.com/gin-gonic/gin/pull/1370)
|
||||||
|
- [FIX] log.Println() does not print timestamp [#829](https://github.com/gin-gonic/gin/pull/829) [#1560](https://github.com/gin-gonic/gin/pull/1560)
|
||||||
|
- [NEW] Add PureJSON renderer [#694](https://github.com/gin-gonic/gin/pull/694)
|
||||||
|
- [FIX] Add missing copyright and update if/else [#1497](https://github.com/gin-gonic/gin/pull/1497)
|
||||||
|
- [FIX] Update msgpack usage [#1498](https://github.com/gin-gonic/gin/pull/1498)
|
||||||
|
- [FIX] Use protobuf on render [#1496](https://github.com/gin-gonic/gin/pull/1496)
|
||||||
|
- [FIX] Add support for Protobuf format response [#1479](https://github.com/gin-gonic/gin/pull/1479)
|
||||||
|
- [NEW] Set default time format in form binding [#1487](https://github.com/gin-gonic/gin/pull/1487)
|
||||||
|
- [FIX] Add BindXML and ShouldBindXML [#1485](https://github.com/gin-gonic/gin/pull/1485)
|
||||||
|
- [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491)
|
||||||
|
|
||||||
|
|
||||||
### Gin 1.3.0
|
### Gin 1.3.0
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||||
|
|
||||||
To install Gin package, you need to install Go and set your Go workspace first.
|
To install Gin package, you need to install Go and set your Go workspace first.
|
||||||
|
|
||||||
1. Download and install it:
|
1. The first need [Go](https://golang.org/) installed (**version 1.8+ is required**), then you can use the below Go command to install Gin.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go get -u github.com/gin-gonic/gin
|
$ go get -u github.com/gin-gonic/gin
|
||||||
|
@ -119,10 +119,6 @@ $ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go
|
||||||
$ go run main.go
|
$ go run main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
|
@ -24,6 +24,16 @@ import (
|
||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type appkey struct {
|
||||||
|
Appkey string `json:"appkey" form:"appkey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryTest struct {
|
||||||
|
Page int `json:"page" form:"page"`
|
||||||
|
Size int `json:"size" form:"size"`
|
||||||
|
appkey
|
||||||
|
}
|
||||||
|
|
||||||
type FooStruct struct {
|
type FooStruct struct {
|
||||||
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"`
|
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"`
|
||||||
}
|
}
|
||||||
|
@ -114,71 +124,6 @@ type FooStructForBoolType struct {
|
||||||
BoolFoo bool `form:"bool_foo"`
|
BoolFoo bool `form:"bool_foo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooBarStructForIntType struct {
|
|
||||||
IntFoo int `form:"int_foo"`
|
|
||||||
IntBar int `form:"int_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForInt8Type struct {
|
|
||||||
Int8Foo int8 `form:"int8_foo"`
|
|
||||||
Int8Bar int8 `form:"int8_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForInt16Type struct {
|
|
||||||
Int16Foo int16 `form:"int16_foo"`
|
|
||||||
Int16Bar int16 `form:"int16_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForInt32Type struct {
|
|
||||||
Int32Foo int32 `form:"int32_foo"`
|
|
||||||
Int32Bar int32 `form:"int32_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForInt64Type struct {
|
|
||||||
Int64Foo int64 `form:"int64_foo"`
|
|
||||||
Int64Bar int64 `form:"int64_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUintType struct {
|
|
||||||
UintFoo uint `form:"uint_foo"`
|
|
||||||
UintBar uint `form:"uint_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUint8Type struct {
|
|
||||||
Uint8Foo uint8 `form:"uint8_foo"`
|
|
||||||
Uint8Bar uint8 `form:"uint8_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUint16Type struct {
|
|
||||||
Uint16Foo uint16 `form:"uint16_foo"`
|
|
||||||
Uint16Bar uint16 `form:"uint16_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUint32Type struct {
|
|
||||||
Uint32Foo uint32 `form:"uint32_foo"`
|
|
||||||
Uint32Bar uint32 `form:"uint32_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUint64Type struct {
|
|
||||||
Uint64Foo uint64 `form:"uint64_foo"`
|
|
||||||
Uint64Bar uint64 `form:"uint64_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForBoolType struct {
|
|
||||||
BoolFoo bool `form:"bool_foo"`
|
|
||||||
BoolBar bool `form:"bool_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForFloat32Type struct {
|
|
||||||
Float32Foo float32 `form:"float32_foo"`
|
|
||||||
Float32Bar float32 `form:"float32_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForFloat64Type struct {
|
|
||||||
Float64Foo float64 `form:"float64_foo"`
|
|
||||||
Float64Bar float64 `form:"float64_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooStructForStringPtrType struct {
|
type FooStructForStringPtrType struct {
|
||||||
PtrFoo *string `form:"ptr_foo"`
|
PtrFoo *string `form:"ptr_foo"`
|
||||||
PtrBar *string `form:"ptr_bar" binding:"required"`
|
PtrBar *string `form:"ptr_bar" binding:"required"`
|
||||||
|
@ -254,6 +199,18 @@ func TestBindingForm2(t *testing.T) {
|
||||||
"", "")
|
"", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindingFormEmbeddedStruct(t *testing.T) {
|
||||||
|
testFormBindingEmbeddedStruct(t, "POST",
|
||||||
|
"/", "/",
|
||||||
|
"page=1&size=2&appkey=test-appkey", "bar2=foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingFormEmbeddedStruct2(t *testing.T) {
|
||||||
|
testFormBindingEmbeddedStruct(t, "GET",
|
||||||
|
"/?page=1&size=2&appkey=test-appkey", "/?bar2=foo",
|
||||||
|
"", "")
|
||||||
|
}
|
||||||
|
|
||||||
func TestBindingFormDefaultValue(t *testing.T) {
|
func TestBindingFormDefaultValue(t *testing.T) {
|
||||||
testFormBindingDefaultValue(t, "POST",
|
testFormBindingDefaultValue(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
|
@ -335,110 +292,6 @@ func TestBindingFormForType(t *testing.T) {
|
||||||
"/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2",
|
"/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2",
|
||||||
"", "", "SliceMap")
|
"", "", "SliceMap")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int_foo=&int_bar=-12", "bar2=-123", "Int")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int_foo=&int_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int8_foo=&int8_bar=-12", "bar2=-123", "Int8")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int8_foo=&int8_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int8")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int16_foo=&int16_bar=-12", "bar2=-123", "Int16")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int16_foo=&int16_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int16")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int32_foo=&int32_bar=-12", "bar2=-123", "Int32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int32_foo=&int32_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int64_foo=&int64_bar=-12", "bar2=-123", "Int64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int64_foo=&int64_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint_foo=&uint_bar=12", "bar2=123", "Uint")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint_foo=&uint_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint8_foo=&uint8_bar=12", "bar2=123", "Uint8")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint8_foo=&uint8_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint8")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint16_foo=&uint16_bar=12", "bar2=123", "Uint16")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint16_foo=&uint16_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint16")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint32_foo=&uint32_bar=12", "bar2=123", "Uint32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint32_foo=&uint32_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint64_foo=&uint64_bar=12", "bar2=123", "Uint64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint64_foo=&uint64_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"bool_foo=&bool_bar=true", "bar2=true", "Bool")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?bool_foo=&bool_bar=true", "/?bar2=true",
|
|
||||||
"", "", "Bool")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3",
|
|
||||||
"", "", "Float32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3",
|
|
||||||
"", "", "Float64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"ptr_bar=test", "bar2=test", "Ptr")
|
"ptr_bar=test", "bar2=test", "Ptr")
|
||||||
|
@ -857,6 +710,23 @@ func TestUriInnerBinding(t *testing.T) {
|
||||||
assert.Equal(t, tag.S.Age, expectedAge)
|
assert.Equal(t, tag.S.Age, expectedAge)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) {
|
||||||
|
b := Form
|
||||||
|
assert.Equal(t, "form", b.Name())
|
||||||
|
|
||||||
|
obj := QueryTest{}
|
||||||
|
req := requestWithBody(method, path, body)
|
||||||
|
if method == "POST" {
|
||||||
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
|
}
|
||||||
|
err := b.Bind(req, &obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, obj.Page)
|
||||||
|
assert.Equal(t, 2, obj.Size)
|
||||||
|
assert.Equal(t, "test-appkey", obj.Appkey)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
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())
|
||||||
|
@ -1076,149 +946,6 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
switch typ {
|
switch typ {
|
||||||
case "Int":
|
|
||||||
obj := FooBarStructForIntType{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int(0), obj.IntFoo)
|
|
||||||
assert.Equal(t, int(-12), obj.IntBar)
|
|
||||||
|
|
||||||
obj = FooBarStructForIntType{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Int8":
|
|
||||||
obj := FooBarStructForInt8Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int8(0), obj.Int8Foo)
|
|
||||||
assert.Equal(t, int8(-12), obj.Int8Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForInt8Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Int16":
|
|
||||||
obj := FooBarStructForInt16Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int16(0), obj.Int16Foo)
|
|
||||||
assert.Equal(t, int16(-12), obj.Int16Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForInt16Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Int32":
|
|
||||||
obj := FooBarStructForInt32Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int32(0), obj.Int32Foo)
|
|
||||||
assert.Equal(t, int32(-12), obj.Int32Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForInt32Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Int64":
|
|
||||||
obj := FooBarStructForInt64Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int64(0), obj.Int64Foo)
|
|
||||||
assert.Equal(t, int64(-12), obj.Int64Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForInt64Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint":
|
|
||||||
obj := FooBarStructForUintType{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint(0x0), obj.UintFoo)
|
|
||||||
assert.Equal(t, uint(0xc), obj.UintBar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUintType{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint8":
|
|
||||||
obj := FooBarStructForUint8Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint8(0x0), obj.Uint8Foo)
|
|
||||||
assert.Equal(t, uint8(0xc), obj.Uint8Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUint8Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint16":
|
|
||||||
obj := FooBarStructForUint16Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint16(0x0), obj.Uint16Foo)
|
|
||||||
assert.Equal(t, uint16(0xc), obj.Uint16Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUint16Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint32":
|
|
||||||
obj := FooBarStructForUint32Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint32(0x0), obj.Uint32Foo)
|
|
||||||
assert.Equal(t, uint32(0xc), obj.Uint32Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUint32Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint64":
|
|
||||||
obj := FooBarStructForUint64Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint64(0x0), obj.Uint64Foo)
|
|
||||||
assert.Equal(t, uint64(0xc), obj.Uint64Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUint64Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Float32":
|
|
||||||
obj := FooBarStructForFloat32Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, float32(0.0), obj.Float32Foo)
|
|
||||||
assert.Equal(t, float32(-12.34), obj.Float32Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForFloat32Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Float64":
|
|
||||||
obj := FooBarStructForFloat64Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, float64(0.0), obj.Float64Foo)
|
|
||||||
assert.Equal(t, float64(-12.34), obj.Float64Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForFloat64Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Bool":
|
|
||||||
obj := FooBarStructForBoolType{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.False(t, obj.BoolFoo)
|
|
||||||
assert.True(t, obj.BoolBar)
|
|
||||||
|
|
||||||
obj = FooBarStructForBoolType{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Slice":
|
case "Slice":
|
||||||
obj := FooStructForSliceType{}
|
obj := FooStructForSliceType{}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
|
@ -1454,97 +1181,3 @@ func requestWithBody(method, path, body string) (req *http.Request) {
|
||||||
req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
|
req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCanSet(t *testing.T) {
|
|
||||||
type CanSetStruct struct {
|
|
||||||
lowerStart string `form:"lower"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var c CanSetStruct
|
|
||||||
assert.Nil(t, mapForm(&c, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func formPostRequest(path, body string) *http.Request {
|
|
||||||
req := requestWithBody("POST", path, body)
|
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindingSliceDefault(t *testing.T) {
|
|
||||||
var s struct {
|
|
||||||
Friends []string `form:"friends,default=mike"`
|
|
||||||
}
|
|
||||||
req := formPostRequest("", "")
|
|
||||||
err := Form.Bind(req, &s)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Len(t, s.Friends, 1)
|
|
||||||
assert.Equal(t, "mike", s.Friends[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindingStructField(t *testing.T) {
|
|
||||||
var s struct {
|
|
||||||
Opts struct {
|
|
||||||
Port int
|
|
||||||
} `form:"opts"`
|
|
||||||
}
|
|
||||||
req := formPostRequest("", `opts={"Port": 8000}`)
|
|
||||||
err := Form.Bind(req, &s)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, 8000, s.Opts.Port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindingUnknownTypeChan(t *testing.T) {
|
|
||||||
var s struct {
|
|
||||||
Stop chan bool `form:"stop"`
|
|
||||||
}
|
|
||||||
req := formPostRequest("", "stop=true")
|
|
||||||
err := Form.Bind(req, &s)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Equal(t, errUnknownType, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindingTimeDuration(t *testing.T) {
|
|
||||||
var s struct {
|
|
||||||
Timeout time.Duration `form:"timeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok
|
|
||||||
req := formPostRequest("", "timeout=5s")
|
|
||||||
err := Form.Bind(req, &s)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, 5*time.Second, s.Timeout)
|
|
||||||
|
|
||||||
// error
|
|
||||||
req = formPostRequest("", "timeout=wrong")
|
|
||||||
err = Form.Bind(req, &s)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindingArray(t *testing.T) {
|
|
||||||
var s struct {
|
|
||||||
Nums [2]int `form:"nums,default=4"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// default
|
|
||||||
req := formPostRequest("", "")
|
|
||||||
err := Form.Bind(req, &s)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Equal(t, [2]int{0, 0}, s.Nums)
|
|
||||||
|
|
||||||
// ok
|
|
||||||
req = formPostRequest("", "nums=3&nums=8")
|
|
||||||
err = Form.Bind(req, &s)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, [2]int{3, 8}, s.Nums)
|
|
||||||
|
|
||||||
// not enough vals
|
|
||||||
req = formPostRequest("", "nums=3")
|
|
||||||
err = Form.Bind(req, &s)
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
// error
|
|
||||||
req = formPostRequest("", "nums=3&nums=wrong")
|
|
||||||
err = Form.Bind(req, &s)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||||
return isSetted, nil
|
return isSetted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if vKind != reflect.Struct || !field.Anonymous {
|
||||||
ok, err := tryToSetValue(value, field, setter, tag)
|
ok, err := tryToSetValue(value, field, setter, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -77,13 +78,15 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||||
if ok {
|
if ok {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if vKind == reflect.Struct {
|
if vKind == reflect.Struct {
|
||||||
tValue := value.Type()
|
tValue := value.Type()
|
||||||
|
|
||||||
var isSetted bool
|
var isSetted bool
|
||||||
for i := 0; i < value.NumField(); i++ {
|
for i := 0; i < value.NumField(); i++ {
|
||||||
if !value.Field(i).CanSet() {
|
sf := tValue.Field(i)
|
||||||
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
|
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
|
||||||
|
|
|
@ -0,0 +1,271 @@
|
||||||
|
// Copyright 2019 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.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMappingBaseTypes(t *testing.T) {
|
||||||
|
intPtr := func(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
value interface{}
|
||||||
|
form string
|
||||||
|
expect interface{}
|
||||||
|
}{
|
||||||
|
{"base type", struct{ F int }{}, "9", int(9)},
|
||||||
|
{"base type", struct{ F int8 }{}, "9", int8(9)},
|
||||||
|
{"base type", struct{ F int16 }{}, "9", int16(9)},
|
||||||
|
{"base type", struct{ F int32 }{}, "9", int32(9)},
|
||||||
|
{"base type", struct{ F int64 }{}, "9", int64(9)},
|
||||||
|
{"base type", struct{ F uint }{}, "9", uint(9)},
|
||||||
|
{"base type", struct{ F uint8 }{}, "9", uint8(9)},
|
||||||
|
{"base type", struct{ F uint16 }{}, "9", uint16(9)},
|
||||||
|
{"base type", struct{ F uint32 }{}, "9", uint32(9)},
|
||||||
|
{"base type", struct{ F uint64 }{}, "9", uint64(9)},
|
||||||
|
{"base type", struct{ F bool }{}, "True", true},
|
||||||
|
{"base type", struct{ F float32 }{}, "9.1", float32(9.1)},
|
||||||
|
{"base type", struct{ F float64 }{}, "9.1", float64(9.1)},
|
||||||
|
{"base type", struct{ F string }{}, "test", string("test")},
|
||||||
|
{"base type", struct{ F *int }{}, "9", intPtr(9)},
|
||||||
|
|
||||||
|
// zero values
|
||||||
|
{"zero value", struct{ F int }{}, "", int(0)},
|
||||||
|
{"zero value", struct{ F uint }{}, "", uint(0)},
|
||||||
|
{"zero value", struct{ F bool }{}, "", false},
|
||||||
|
{"zero value", struct{ F float32 }{}, "", float32(0)},
|
||||||
|
} {
|
||||||
|
tp := reflect.TypeOf(tt.value)
|
||||||
|
testName := tt.name + ":" + tp.Field(0).Type.String()
|
||||||
|
|
||||||
|
val := reflect.New(reflect.TypeOf(tt.value))
|
||||||
|
val.Elem().Set(reflect.ValueOf(tt.value))
|
||||||
|
|
||||||
|
field := val.Elem().Type().Field(0)
|
||||||
|
|
||||||
|
_, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form")
|
||||||
|
assert.NoError(t, err, testName)
|
||||||
|
|
||||||
|
actual := val.Elem().Field(0).Interface()
|
||||||
|
assert.Equal(t, tt.expect, actual, testName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingDefault(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Int int `form:",default=9"`
|
||||||
|
Slice []int `form:",default=9"`
|
||||||
|
Array [1]int `form:",default=9"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 9, s.Int)
|
||||||
|
assert.Equal(t, []int{9}, s.Slice)
|
||||||
|
assert.Equal(t, [1]int{9}, s.Array)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingSkipField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
A int
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, s.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingIgnoreField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
A int `form:"A"`
|
||||||
|
B int `form:"-"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 9, s.A)
|
||||||
|
assert.Equal(t, 0, s.B)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingUnexportedField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
A int `form:"a"`
|
||||||
|
b int `form:"b"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 9, s.A)
|
||||||
|
assert.Equal(t, 0, s.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingPrivateField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
f int `form:"field"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"field": {"6"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int(0), s.f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingUnknownFieldType(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
U uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, errUnknownType, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingURI(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
F int `uri:"field"`
|
||||||
|
}
|
||||||
|
err := mapUri(&s, map[string][]string{"field": {"6"}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int(6), s.F)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingForm(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
F int `form:"field"`
|
||||||
|
}
|
||||||
|
err := mapForm(&s, map[string][]string{"field": {"6"}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int(6), s.F)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingTime(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Time time.Time
|
||||||
|
LocalTime time.Time `time_format:"2006-01-02"`
|
||||||
|
ZeroValue time.Time
|
||||||
|
CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"`
|
||||||
|
UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
time.Local, err = time.LoadLocation("Europe/Berlin")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = mapForm(&s, map[string][]string{
|
||||||
|
"Time": {"2019-01-20T16:02:58Z"},
|
||||||
|
"LocalTime": {"2019-01-20"},
|
||||||
|
"ZeroValue": {},
|
||||||
|
"CSTTime": {"2019-01-20"},
|
||||||
|
"UTCTime": {"2019-01-20"},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String())
|
||||||
|
assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String())
|
||||||
|
assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String())
|
||||||
|
assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String())
|
||||||
|
assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String())
|
||||||
|
assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String())
|
||||||
|
assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String())
|
||||||
|
|
||||||
|
// wrong location
|
||||||
|
var wrongLoc struct {
|
||||||
|
Time time.Time `time_location:"wrong"`
|
||||||
|
}
|
||||||
|
err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// wrong time value
|
||||||
|
var wrongTime struct {
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapiingTimeDuration(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
D time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok
|
||||||
|
err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 5*time.Second, s.D)
|
||||||
|
|
||||||
|
// error
|
||||||
|
err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingSlice(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Slice []int `form:"slice,default=9"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// default value
|
||||||
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []int{9}, s.Slice)
|
||||||
|
|
||||||
|
// ok
|
||||||
|
err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []int{3, 4}, s.Slice)
|
||||||
|
|
||||||
|
// error
|
||||||
|
err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingArray(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Array [2]int `form:"array,default=9"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrong default
|
||||||
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// ok
|
||||||
|
err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, [2]int{3, 4}, s.Array)
|
||||||
|
|
||||||
|
// error - not enough vals
|
||||||
|
err = mappingByPtr(&s, formSource{"array": {"3"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// error - wrong value
|
||||||
|
err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingStructField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
J struct {
|
||||||
|
I int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 9, s.J.I)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingMapField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
M map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, map[string]int{"one": 1}, s.M)
|
||||||
|
}
|
19
context.go
19
context.go
|
@ -473,11 +473,6 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||||
if values := req.PostForm[key]; len(values) > 0 {
|
if values := req.PostForm[key]; len(values) > 0 {
|
||||||
return values, true
|
return values, true
|
||||||
}
|
}
|
||||||
if req.MultipartForm != nil && req.MultipartForm.File != nil {
|
|
||||||
if values := req.MultipartForm.Value[key]; len(values) > 0 {
|
|
||||||
return values, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []string{}, false
|
return []string{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,13 +491,7 @@ func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
|
||||||
debugPrint("error on parse multipart form map: %v", err)
|
debugPrint("error on parse multipart form map: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dicts, exist := c.get(req.PostForm, key)
|
return c.get(req.PostForm, key)
|
||||||
|
|
||||||
if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil {
|
|
||||||
dicts, exist = c.get(req.MultipartForm.Value, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dicts, exist
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get is an internal method and returns a map which satisfy conditions.
|
// get is an internal method and returns a map which satisfy conditions.
|
||||||
|
@ -862,6 +851,12 @@ func (c *Context) AsciiJSON(code int, obj interface{}) {
|
||||||
c.Render(code, render.AsciiJSON{Data: obj})
|
c.Render(code, render.AsciiJSON{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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})
|
||||||
|
}
|
||||||
|
|
||||||
// XML serializes the given struct as XML into the response body.
|
// XML serializes the given struct as XML into the response body.
|
||||||
// It also sets the Content-Type as "application/xml".
|
// It also sets the Content-Type as "application/xml".
|
||||||
func (c *Context) XML(code int, obj interface{}) {
|
func (c *Context) XML(code int, obj interface{}) {
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
// 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})
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
// 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"))
|
|
||||||
}
|
|
|
@ -622,8 +622,7 @@ func TestContextGetCookie(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextBodyAllowedForStatus(t *testing.T) {
|
func TestContextBodyAllowedForStatus(t *testing.T) {
|
||||||
// todo(thinkerou): go1.6 not support StatusProcessing
|
assert.False(t, false, bodyAllowedForStatus(http.StatusProcessing))
|
||||||
assert.False(t, false, bodyAllowedForStatus(102))
|
|
||||||
assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent))
|
assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent))
|
||||||
assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified))
|
assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified))
|
||||||
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
||||||
|
@ -794,6 +793,18 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) {
|
||||||
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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"))
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that the response executes the templates
|
// Tests that the response executes the templates
|
||||||
// and responds with Content-Type set to text/html
|
// and responds with Content-Type set to text/html
|
||||||
func TestContextRenderHTML(t *testing.T) {
|
func TestContextRenderHTML(t *testing.T) {
|
||||||
|
@ -1092,9 +1103,7 @@ func TestContextRenderRedirectAll(t *testing.T) {
|
||||||
assert.Panics(t, func() { c.Redirect(299, "/resource") })
|
assert.Panics(t, func() { c.Redirect(299, "/resource") })
|
||||||
assert.Panics(t, func() { c.Redirect(309, "/resource") })
|
assert.Panics(t, func() { c.Redirect(309, "/resource") })
|
||||||
assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") })
|
assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") })
|
||||||
// todo(thinkerou): go1.6 not support StatusPermanentRedirect(308)
|
assert.NotPanics(t, func() { c.Redirect(http.StatusPermanentRedirect, "/resource") })
|
||||||
// when we upgrade go version we can use http.StatusPermanentRedirect
|
|
||||||
assert.NotPanics(t, func() { c.Redirect(308, "/resource") })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextNegotiationWithJSON(t *testing.T) {
|
func TestContextNegotiationWithJSON(t *testing.T) {
|
||||||
|
|
11
debug.go
11
debug.go
|
@ -8,13 +8,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 6
|
const ginSupportMinGoVer = 8
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
|
@ -54,7 +53,7 @@ func debugPrint(format string, values ...interface{}) {
|
||||||
if !strings.HasSuffix(format, "\n") {
|
if !strings.HasSuffix(format, "\n") {
|
||||||
format += "\n"
|
format += "\n"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...)
|
fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +68,7 @@ func getMinVer(v string) (uint64, error) {
|
||||||
|
|
||||||
func debugPrintWARNINGDefault() {
|
func debugPrintWARNINGDefault() {
|
||||||
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
||||||
debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
|
debugPrint(`[WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
@ -98,6 +97,8 @@ at initialization. ie. before any route is registered or the router is listening
|
||||||
|
|
||||||
func debugPrintError(err error) {
|
func debugPrintError(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debugPrint("[ERROR] %v\n", err)
|
if IsDebugging() {
|
||||||
|
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||||
})
|
})
|
||||||
m, e := getMinVer(runtime.Version())
|
m, e := getMinVer(runtime.Version())
|
||||||
if e == nil && m <= ginSupportMinGoVer {
|
if e == nil && m <= ginSupportMinGoVer {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
}
|
}
|
||||||
|
@ -111,15 +111,15 @@ func captureOutput(t *testing.T, f func()) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
stdout := os.Stdout
|
defaultWriter := DefaultWriter
|
||||||
stderr := os.Stderr
|
defaultErrorWriter := DefaultErrorWriter
|
||||||
defer func() {
|
defer func() {
|
||||||
os.Stdout = stdout
|
DefaultWriter = defaultWriter
|
||||||
os.Stderr = stderr
|
DefaultErrorWriter = defaultErrorWriter
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
}()
|
}()
|
||||||
os.Stdout = writer
|
DefaultWriter = writer
|
||||||
os.Stderr = writer
|
DefaultErrorWriter = writer
|
||||||
log.SetOutput(writer)
|
log.SetOutput(writer)
|
||||||
out := make(chan string)
|
out := make(chan string)
|
||||||
wg := new(sync.WaitGroup)
|
wg := new(sync.WaitGroup)
|
||||||
|
|
1
gin.go
1
gin.go
|
@ -372,6 +372,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
rPath = c.Request.URL.RawPath
|
rPath = c.Request.URL.RawPath
|
||||||
unescape = engine.UnescapePathValues
|
unescape = engine.UnescapePathValues
|
||||||
}
|
}
|
||||||
|
rPath = cleanPath(rPath)
|
||||||
|
|
||||||
// Find root of the tree for the given HTTP method
|
// Find root of the tree for the given HTTP method
|
||||||
t := engine.trees
|
t := engine.trees
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -69,6 +70,42 @@ func TestRunTLS(t *testing.T) {
|
||||||
testRequest(t, "https://localhost:8443/example")
|
testRequest(t, "https://localhost:8443/example")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPusher(t *testing.T) {
|
||||||
|
var html = template.Must(template.New("https").Parse(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Https Test</title>
|
||||||
|
<script src="/assets/app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 style="color:red;">Welcome, Ginner!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
router := New()
|
||||||
|
router.Static("./assets", "./assets")
|
||||||
|
router.SetHTMLTemplate(html)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
router.GET("/pusher", func(c *Context) {
|
||||||
|
if pusher := c.Writer.Pusher(); pusher != nil {
|
||||||
|
pusher.Push("/assets/app.js", nil)
|
||||||
|
}
|
||||||
|
c.String(http.StatusOK, "it worked")
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// have to wait for the goroutine to start and run the server
|
||||||
|
// otherwise the main thread will complete
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
|
||||||
|
assert.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||||
|
testRequest(t, "https://localhost:8449/pusher")
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunEmptyWithEnv(t *testing.T) {
|
func TestRunEmptyWithEnv(t *testing.T) {
|
||||||
os.Setenv("PORT", "3123")
|
os.Setenv("PORT", "3123")
|
||||||
router := New()
|
router := New()
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -5,9 +5,9 @@ go 1.12
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3
|
||||||
github.com/golang/protobuf v1.3.0
|
github.com/golang/protobuf v1.3.1
|
||||||
github.com/json-iterator/go v1.1.5
|
github.com/json-iterator/go v1.1.6
|
||||||
github.com/mattn/go-isatty v0.0.6
|
github.com/mattn/go-isatty v0.0.7
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -4,12 +4,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
|
||||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA=
|
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||||
github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -43,6 +43,11 @@ type AsciiJSON struct {
|
||||||
// SecureJSONPrefix is a string which represents SecureJSON prefix.
|
// SecureJSONPrefix is a string which represents SecureJSON prefix.
|
||||||
type SecureJSONPrefix string
|
type SecureJSONPrefix string
|
||||||
|
|
||||||
|
// PureJSON contains the given interface object.
|
||||||
|
type PureJSON struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
var jsonContentType = []string{"application/json; charset=utf-8"}
|
||||||
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||||
var jsonAsciiContentType = []string{"application/json"}
|
var jsonAsciiContentType = []string{"application/json"}
|
||||||
|
@ -174,3 +179,16 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
||||||
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, jsonAsciiContentType)
|
writeContentType(w, jsonAsciiContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||||
|
func (r PureJSON) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
return encoder.Encode(r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (PureJSON) writes custom ContentType.
|
||||||
|
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
// 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 render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PureJSON contains the given interface object.
|
|
||||||
type PureJSON struct {
|
|
||||||
Data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
|
||||||
func (r PureJSON) Render(w http.ResponseWriter) error {
|
|
||||||
r.WriteContentType(w)
|
|
||||||
encoder := json.NewEncoder(w)
|
|
||||||
encoder.SetEscapeHTML(false)
|
|
||||||
return encoder.Encode(r.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteContentType (PureJSON) writes custom ContentType.
|
|
||||||
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
|
|
||||||
writeContentType(w, jsonContentType)
|
|
||||||
}
|
|
|
@ -18,9 +18,7 @@ type Redirect struct {
|
||||||
|
|
||||||
// Render (Redirect) redirects the http request to new location and writes redirect response.
|
// Render (Redirect) redirects the http request to new location and writes redirect response.
|
||||||
func (r Redirect) Render(w http.ResponseWriter) error {
|
func (r Redirect) Render(w http.ResponseWriter) error {
|
||||||
// todo(thinkerou): go1.6 not support StatusPermanentRedirect(308)
|
if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {
|
||||||
// when we upgrade go version we can use http.StatusPermanentRedirect
|
|
||||||
if (r.Code < 300 || r.Code > 308) && r.Code != 201 {
|
|
||||||
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
||||||
}
|
}
|
||||||
http.Redirect(w, r.Request, r.Location, r.Code)
|
http.Redirect(w, r.Request, r.Location, r.Code)
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// 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 render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRenderPureJSON(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
"html": "<b>",
|
|
||||||
}
|
|
||||||
err := (PureJSON{data}).Render(w)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
|
||||||
}
|
|
|
@ -215,6 +215,18 @@ func TestRenderAsciiJSONFail(t *testing.T) {
|
||||||
assert.Error(t, (AsciiJSON{data}).Render(w))
|
assert.Error(t, (AsciiJSON{data}).Render(w))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderPureJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"html": "<b>",
|
||||||
|
}
|
||||||
|
err := (PureJSON{data}).Render(w)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
type xmlmap map[string]interface{}
|
type xmlmap map[string]interface{}
|
||||||
|
|
||||||
// Allows type H to be used with xml.Marshal
|
// Allows type H to be used with xml.Marshal
|
||||||
|
|
|
@ -16,7 +16,8 @@ const (
|
||||||
defaultStatus = http.StatusOK
|
defaultStatus = http.StatusOK
|
||||||
)
|
)
|
||||||
|
|
||||||
type responseWriterBase interface {
|
// ResponseWriter ...
|
||||||
|
type ResponseWriter interface {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
http.Hijacker
|
http.Hijacker
|
||||||
http.Flusher
|
http.Flusher
|
||||||
|
@ -37,6 +38,9 @@ type responseWriterBase interface {
|
||||||
|
|
||||||
// Forces to write the http header (status code + headers).
|
// Forces to write the http header (status code + headers).
|
||||||
WriteHeaderNow()
|
WriteHeaderNow()
|
||||||
|
|
||||||
|
// get the http.Pusher for server push
|
||||||
|
Pusher() http.Pusher
|
||||||
}
|
}
|
||||||
|
|
||||||
type responseWriter struct {
|
type responseWriter struct {
|
||||||
|
@ -113,3 +117,10 @@ func (w *responseWriter) Flush() {
|
||||||
w.WriteHeaderNow()
|
w.WriteHeaderNow()
|
||||||
w.ResponseWriter.(http.Flusher).Flush()
|
w.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Pusher() (pusher http.Pusher) {
|
||||||
|
if pusher, ok := w.ResponseWriter.(http.Pusher); ok {
|
||||||
|
return pusher
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
// +build !go1.8
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
// ResponseWriter ...
|
|
||||||
type ResponseWriter interface {
|
|
||||||
responseWriterBase
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResponseWriter ...
|
|
||||||
type ResponseWriter interface {
|
|
||||||
responseWriterBase
|
|
||||||
// get the http.Pusher for server push
|
|
||||||
Pusher() http.Pusher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *responseWriter) Pusher() (pusher http.Pusher) {
|
|
||||||
if pusher, ok := w.ResponseWriter.(http.Pusher); ok {
|
|
||||||
return pusher
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -22,7 +22,7 @@ type header struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
|
func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
|
||||||
req, _ := http.NewRequest(method, path, nil)
|
req := httptest.NewRequest(method, path, nil)
|
||||||
for _, h := range headers {
|
for _, h := range headers {
|
||||||
req.Header.Add(h.Key, h.Value)
|
req.Header.Add(h.Key, h.Value)
|
||||||
}
|
}
|
||||||
|
@ -257,6 +257,39 @@ func TestRouteParamsByName(t *testing.T) {
|
||||||
assert.Equal(t, "/is/super/great", wild)
|
assert.Equal(t, "/is/super/great", wild)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes.
|
||||||
|
func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
||||||
|
name := ""
|
||||||
|
lastName := ""
|
||||||
|
wild := ""
|
||||||
|
router := New()
|
||||||
|
router.GET("/test/:name/:last_name/*wild", func(c *Context) {
|
||||||
|
name = c.Params.ByName("name")
|
||||||
|
lastName = c.Params.ByName("last_name")
|
||||||
|
var ok bool
|
||||||
|
wild, ok = c.Params.Get("wild")
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, name, c.Param("name"))
|
||||||
|
assert.Equal(t, name, c.Param("name"))
|
||||||
|
assert.Equal(t, lastName, c.Param("last_name"))
|
||||||
|
|
||||||
|
assert.Empty(t, c.Param("wtf"))
|
||||||
|
assert.Empty(t, c.Params.ByName("wtf"))
|
||||||
|
|
||||||
|
wtf, ok := c.Params.Get("wtf")
|
||||||
|
assert.Empty(t, wtf)
|
||||||
|
assert.False(t, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
w := performRequest(router, "GET", "//test//john//smith//is//super//great")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, "john", name)
|
||||||
|
assert.Equal(t, "smith", lastName)
|
||||||
|
assert.Equal(t, "/is/super/great", wild)
|
||||||
|
}
|
||||||
|
|
||||||
// TestHandleStaticFile - ensure the static file handles properly
|
// TestHandleStaticFile - ensure the static file handles properly
|
||||||
func TestRouteStaticFile(t *testing.T) {
|
func TestRouteStaticFile(t *testing.T) {
|
||||||
// SETUP file
|
// SETUP file
|
||||||
|
@ -388,12 +421,11 @@ func TestRouterNotFound(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
|
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
|
||||||
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
|
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
|
||||||
{"", http.StatusMovedPermanently, "/"}, // TSR +/
|
|
||||||
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
|
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
|
||||||
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
|
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
|
||||||
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
|
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
|
||||||
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
|
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
|
||||||
{"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath
|
{"/../path", http.StatusOK, ""}, // CleanPath
|
||||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||||
}
|
}
|
||||||
for _, tr := range testRoutes {
|
for _, tr := range testRoutes {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"comment": "v1.3.0",
|
"comment": "v1.4.0",
|
||||||
"ignore": "test",
|
"ignore": "test",
|
||||||
"package": [
|
"package": [
|
||||||
{
|
{
|
||||||
|
@ -13,32 +13,44 @@
|
||||||
{
|
{
|
||||||
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
|
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
|
||||||
"path": "github.com/gin-contrib/sse",
|
"path": "github.com/gin-contrib/sse",
|
||||||
"revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae",
|
"revision": "5545eab6dad3bbbd6c5ae9186383c2a9d23c0dae",
|
||||||
"revisionTime": "2017-01-09T09:34:21Z"
|
"revisionTime": "2019-03-01T06:25:29Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "mE9XW26JSpe4meBObM6J/Oeq0eg=",
|
"checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=",
|
||||||
"path": "github.com/golang/protobuf/proto",
|
"path": "github.com/golang/protobuf/proto",
|
||||||
"revision": "aa810b61a9c79d51363740d207bb46cf8e620ed5",
|
"revision": "c823c79ea1570fb5ff454033735a8e68575d1d0f",
|
||||||
"revisionTime": "2018-08-14T21:14:27Z",
|
"revisionTime": "2019-02-05T22:20:52Z",
|
||||||
"version": "v1.2",
|
"version": "v1.3",
|
||||||
"versionExact": "v1.2.0"
|
"versionExact": "v1.3.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=",
|
"checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=",
|
||||||
"path": "github.com/json-iterator/go",
|
"path": "github.com/json-iterator/go",
|
||||||
"revision": "1624edc4454b8682399def8740d46db5e4362ba4",
|
"revision": "0ff49de124c6f76f8494e194af75bde0f1a49a29",
|
||||||
"revisionTime": "2018-08-06T06:07:27Z",
|
"revisionTime": "2019-03-06T14:29:09Z",
|
||||||
"version": "v1.1",
|
"version": "v1.1",
|
||||||
"versionExact": "v1.1.5"
|
"versionExact": "v1.1.6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=",
|
"checksumSHA1": "Ya+baVBU/RkXXUWD3LGFmGJiiIg=",
|
||||||
"path": "github.com/mattn/go-isatty",
|
"path": "github.com/mattn/go-isatty",
|
||||||
"revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c",
|
"revision": "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7",
|
||||||
"revisionTime": "2017-11-07T05:05:31Z",
|
"revisionTime": "2019-03-12T13:58:54Z",
|
||||||
"version": "v0.0",
|
"version": "v0.0",
|
||||||
"versionExact": "v0.0.4"
|
"versionExact": "v0.0.7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=",
|
||||||
|
"path": "github.com/modern-go/concurrent",
|
||||||
|
"revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94",
|
||||||
|
"revisionTime": "2018-03-06T01:26:44Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=",
|
||||||
|
"path": "github.com/modern-go/reflect2",
|
||||||
|
"revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8",
|
||||||
|
"revisionTime": "2018-07-18T01:23:57Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
||||||
|
@ -46,6 +58,20 @@
|
||||||
"revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc",
|
"revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc",
|
||||||
"revisionTime": "2018-12-26T10:54:42Z"
|
"revisionTime": "2018-12-26T10:54:42Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "cpNsoLqBprpKh+VZTBOZNVXzBEk=",
|
||||||
|
"path": "github.com/stretchr/objx",
|
||||||
|
"revision": "c61a9dfcced1815e7d40e214d00d1a8669a9f58c",
|
||||||
|
"revisionTime": "2019-02-11T16:23:28Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "DBdcVxnvaINHhWyyGgih/Mel6gE=",
|
||||||
|
"path": "github.com/stretchr/testify",
|
||||||
|
"revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053",
|
||||||
|
"revisionTime": "2018-12-05T02:12:43Z",
|
||||||
|
"version": "v1.3",
|
||||||
|
"versionExact": "v1.3.0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
|
"checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
|
||||||
"path": "github.com/stretchr/testify/assert",
|
"path": "github.com/stretchr/testify/assert",
|
||||||
|
@ -55,24 +81,24 @@
|
||||||
"versionExact": "v1.2.2"
|
"versionExact": "v1.2.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=",
|
"checksumSHA1": "csplo594qomjp2IZj82y7mTueOw=",
|
||||||
"path": "github.com/ugorji/go/codec",
|
"path": "github.com/ugorji/go/codec",
|
||||||
"revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab",
|
"revision": "2adff0894ba3bc2eeb9f9aea45fefd49802e1a13",
|
||||||
"revisionTime": "2018-04-07T10:07:33Z",
|
"revisionTime": "2019-04-08T19:08:48Z",
|
||||||
"version": "v1.1",
|
"version": "v1.1",
|
||||||
"versionExact": "v1.1.1"
|
"versionExact": "v1.1.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
|
"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
|
||||||
"path": "golang.org/x/net/context",
|
"path": "golang.org/x/net/context",
|
||||||
"revision": "49bb7cea24b1df9410e1712aa6433dae904ff66a",
|
"revision": "f4e77d36d62c17c2336347bb2670ddbd02d092b7",
|
||||||
"revisionTime": "2018-10-11T05:27:23Z"
|
"revisionTime": "2019-05-02T22:26:14Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "SiJNkx+YGtq3Gtr6Ldu6OW83O+U=",
|
"checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=",
|
||||||
"path": "golang.org/x/sys/unix",
|
"path": "golang.org/x/sys/unix",
|
||||||
"revision": "fa43e7bc11baaae89f3f902b2b4d832b68234844",
|
"revision": "a43fa875dd822b81eb6d2ad538bc1f4caba169bd",
|
||||||
"revisionTime": "2018-10-11T14:35:51Z"
|
"revisionTime": "2019-05-02T15:41:39Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
|
"checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
|
||||||
|
@ -83,12 +109,12 @@
|
||||||
"versionExact": "v8.18.2"
|
"versionExact": "v8.18.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=",
|
"checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=",
|
||||||
"path": "gopkg.in/yaml.v2",
|
"path": "gopkg.in/yaml.v2",
|
||||||
"revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183",
|
"revision": "51d6538a90f86fe93ac480b35f37b2be17fef232",
|
||||||
"revisionTime": "2018-03-28T19:50:20Z",
|
"revisionTime": "2018-11-15T11:05:04Z",
|
||||||
"version": "v2.2",
|
"version": "v2.2",
|
||||||
"versionExact": "v2.2.1"
|
"versionExact": "v2.2.2"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rootPath": "github.com/gin-gonic/gin"
|
"rootPath": "github.com/gin-gonic/gin"
|
||||||
|
|
Loading…
Reference in New Issue