mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-17 22:32:26 +08:00
Merge branch 'master' of https://github.com/gin-gonic/gin into gin-gonic-master
This commit is contained in:
commit
e3f85d8161
@ -7,7 +7,7 @@ matrix:
|
|||||||
env: GO111MODULE=on
|
env: GO111MODULE=on
|
||||||
- go: 1.13.x
|
- go: 1.13.x
|
||||||
- go: 1.13.x
|
- go: 1.13.x
|
||||||
env:
|
env:
|
||||||
- TESTTAGS=nomsgpack
|
- TESTTAGS=nomsgpack
|
||||||
- go: 1.14.x
|
- go: 1.14.x
|
||||||
- go: 1.14.x
|
- go: 1.14.x
|
||||||
|
@ -156,7 +156,7 @@ People and companies, who have contributed, in alphabetical order.
|
|||||||
- Fix variadic parameter in the flexible render API
|
- Fix variadic parameter in the flexible render API
|
||||||
- Fix Corrupted plain render
|
- Fix Corrupted plain render
|
||||||
- Add Pluggable View Renderer Example
|
- Add Pluggable View Renderer Example
|
||||||
|
|
||||||
|
|
||||||
**@msemenistyi (Mykyta Semenistyi)**
|
**@msemenistyi (Mykyta Semenistyi)**
|
||||||
- update Readme.md. Add code to String method
|
- update Readme.md. Add code to String method
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
# Benchmark System
|
# Benchmark System
|
||||||
|
|
||||||
**VM HOST:** Travis
|
**VM HOST:** Travis
|
||||||
**Machine:** Ubuntu 16.04.6 LTS x64
|
**Machine:** Ubuntu 16.04.6 LTS x64
|
||||||
**Date:** May 04th, 2020
|
**Date:** May 04th, 2020
|
||||||
**Version:** Gin v1.6.3
|
**Version:** Gin v1.6.3
|
||||||
**Go Version:** 1.14.2 linux/amd64
|
**Go Version:** 1.14.2 linux/amd64
|
||||||
**Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)
|
**Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)
|
||||||
**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061)
|
**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061)
|
||||||
|
|
||||||
|
10
CHANGELOG.md
10
CHANGELOG.md
@ -215,12 +215,12 @@
|
|||||||
|
|
||||||
## Gin 1.1
|
## Gin 1.1
|
||||||
|
|
||||||
- [NEW] Implement QueryArray and PostArray methods
|
- [NEW] Implement QueryArray and PostArray methods
|
||||||
- [NEW] Refactor GetQuery and GetPostForm
|
- [NEW] Refactor GetQuery and GetPostForm
|
||||||
- [NEW] Add contribution guide
|
- [NEW] Add contribution guide
|
||||||
- [FIX] Corrected typos in README
|
- [FIX] Corrected typos in README
|
||||||
- [FIX] Removed additional Iota
|
- [FIX] Removed additional Iota
|
||||||
- [FIX] Changed imports to gopkg instead of github in README (#733)
|
- [FIX] Changed imports to gopkg instead of github in README (#733)
|
||||||
- [FIX] Logger: skip ANSI color commands if output is not a tty
|
- [FIX] Logger: skip ANSI color commands if output is not a tty
|
||||||
|
|
||||||
## Gin 1.0rc2 (...)
|
## Gin 1.0rc2 (...)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
- With issues:
|
- With issues:
|
||||||
- Use the search tool before opening a new issue.
|
- Use the search tool before opening a new issue.
|
||||||
|
55
README.md
55
README.md
@ -103,7 +103,7 @@ import "net/http"
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# assume the following codes in example.go file
|
# assume the following codes in example.go file
|
||||||
$ cat example.go
|
$ cat example.go
|
||||||
@ -588,44 +588,44 @@ func main() {
|
|||||||
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
|
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
|
||||||
```
|
```
|
||||||
|
|
||||||
### Controlling Log output coloring
|
### Controlling Log output coloring
|
||||||
|
|
||||||
By default, logs output on console should be colorized depending on the detected TTY.
|
By default, logs output on console should be colorized depending on the detected TTY.
|
||||||
|
|
||||||
Never colorize logs:
|
Never colorize logs:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
// Disable log's color
|
// Disable log's color
|
||||||
gin.DisableConsoleColor()
|
gin.DisableConsoleColor()
|
||||||
|
|
||||||
// Creates a gin router with default middleware:
|
// Creates a gin router with default middleware:
|
||||||
// logger and recovery (crash-free) middleware
|
// logger and recovery (crash-free) middleware
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(200, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Always colorize logs:
|
Always colorize logs:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
// Force log's color
|
// Force log's color
|
||||||
gin.ForceConsoleColor()
|
gin.ForceConsoleColor()
|
||||||
|
|
||||||
// Creates a gin router with default middleware:
|
// Creates a gin router with default middleware:
|
||||||
// logger and recovery (crash-free) middleware
|
// logger and recovery (crash-free) middleware
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(200, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -667,12 +667,12 @@ func main() {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if json.User != "manu" || json.Password != "123" {
|
if json.User != "manu" || json.Password != "123" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -688,12 +688,12 @@ func main() {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if xml.User != "manu" || xml.Password != "123" {
|
if xml.User != "manu" || xml.Password != "123" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -705,12 +705,12 @@ func main() {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.User != "manu" || form.Password != "123" {
|
if form.User != "manu" || form.Password != "123" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -807,7 +807,7 @@ $ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
|
|||||||
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
|
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
|
||||||
|
|
||||||
$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
|
$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
|
||||||
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
|
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
|
||||||
```
|
```
|
||||||
|
|
||||||
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
|
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
|
||||||
@ -1145,7 +1145,7 @@ func main() {
|
|||||||
data := gin.H{
|
data := gin.H{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
//callback is x
|
//callback is x
|
||||||
// Will output : x({\"foo\":\"bar\"})
|
// Will output : x({\"foo\":\"bar\"})
|
||||||
c.JSONP(http.StatusOK, data)
|
c.JSONP(http.StatusOK, data)
|
||||||
@ -1190,21 +1190,21 @@ This feature is unavailable in Go 1.6 and lower.
|
|||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
// Serves unicode entities
|
// Serves unicode entities
|
||||||
r.GET("/json", func(c *gin.Context) {
|
r.GET("/json", func(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"html": "<b>Hello, world!</b>",
|
"html": "<b>Hello, world!</b>",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Serves literal characters
|
// Serves literal characters
|
||||||
r.GET("/purejson", func(c *gin.Context) {
|
r.GET("/purejson", func(c *gin.Context) {
|
||||||
c.PureJSON(200, gin.H{
|
c.PureJSON(200, gin.H{
|
||||||
"html": "<b>Hello, world!</b>",
|
"html": "<b>Hello, world!</b>",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// listen and serve on 0.0.0.0:8080
|
// listen and serve on 0.0.0.0:8080
|
||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
}
|
}
|
||||||
@ -1793,8 +1793,8 @@ func main() {
|
|||||||
// Initializing the server in a goroutine so that
|
// Initializing the server in a goroutine so that
|
||||||
// it won't block the graceful shutdown handling below
|
// it won't block the graceful shutdown handling below
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Fatalf("listen: %s\n", err)
|
log.Printf("listen: %s\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -1812,10 +1812,11 @@ func main() {
|
|||||||
// the request it is currently handling
|
// the request it is currently handling
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := srv.Shutdown(ctx); err != nil {
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
log.Fatal("Server forced to shutdown:", err)
|
log.Fatal("Server forced to shutdown:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Server exiting")
|
log.Println("Server exiting")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
3
auth.go
3
auth.go
@ -5,6 +5,7 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -30,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
for _, pair := range a {
|
for _, pair := range a {
|
||||||
if pair.value == authValue {
|
if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 {
|
||||||
return pair.user, true
|
return pair.user, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
@ -51,7 +52,8 @@ type BindingUri interface {
|
|||||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
// https://github.com/go-playground/validator/tree/v8.18.2.
|
||||||
type StructValidator interface {
|
type StructValidator interface {
|
||||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
// If the received type is a slice|array, the validation should be performed travel on every element.
|
||||||
|
// If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
|
||||||
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
||||||
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
// Otherwise nil must be returned.
|
// Otherwise nil must be returned.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build nomsgpack
|
||||||
// +build nomsgpack
|
// +build nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
@ -35,7 +35,7 @@ type QueryTest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,max=32"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooBarStruct struct {
|
type FooBarStruct struct {
|
||||||
@ -181,6 +181,20 @@ func TestBindingJSON(t *testing.T) {
|
|||||||
`{"foo": "bar"}`, `{"bar": "foo"}`)
|
`{"foo": "bar"}`, `{"bar": "foo"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindingJSONSlice(t *testing.T) {
|
||||||
|
EnableDecoderDisallowUnknownFields = true
|
||||||
|
defer func() {
|
||||||
|
EnableDecoderDisallowUnknownFields = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[]`, ``)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{}]`)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": ""}]`)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": 123}]`)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"bar": 123}]`)
|
||||||
|
testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": "123456789012345678901234567890123"}]`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBindingJSONUseNumber(t *testing.T) {
|
func TestBindingJSONUseNumber(t *testing.T) {
|
||||||
testBodyBindingUseNumber(t,
|
testBodyBindingUseNumber(t,
|
||||||
JSON, "json",
|
JSON, "json",
|
||||||
@ -1181,6 +1195,20 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||||
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
|
var obj1 []FooStruct
|
||||||
|
req := requestWithBody("POST", path, body)
|
||||||
|
err := b.Bind(req, &obj1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var obj2 []FooStruct
|
||||||
|
req = requestWithBody("POST", badPath, badBody)
|
||||||
|
err = JSON.Bind(req, &obj2)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
|
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
|
||||||
obj := make(map[string]string)
|
obj := make(map[string]string)
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody("POST", path, body)
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@ -16,22 +18,54 @@ type defaultValidator struct {
|
|||||||
validate *validator.Validate
|
validate *validator.Validate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sliceValidateError []error
|
||||||
|
|
||||||
|
func (err sliceValidateError) Error() string {
|
||||||
|
var errMsgs []string
|
||||||
|
for i, e := range err {
|
||||||
|
if e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, e.Error()))
|
||||||
|
}
|
||||||
|
return strings.Join(errMsgs, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
var _ StructValidator = &defaultValidator{}
|
var _ StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
||||||
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
value := reflect.ValueOf(obj)
|
value := reflect.ValueOf(obj)
|
||||||
valueType := value.Kind()
|
switch value.Kind() {
|
||||||
if valueType == reflect.Ptr {
|
case reflect.Ptr:
|
||||||
valueType = value.Elem().Kind()
|
return v.ValidateStruct(value.Elem().Interface())
|
||||||
}
|
case reflect.Struct:
|
||||||
if valueType == reflect.Struct {
|
return v.validateStruct(obj)
|
||||||
v.lazyinit()
|
case reflect.Slice, reflect.Array:
|
||||||
if err := v.validate.Struct(obj); err != nil {
|
count := value.Len()
|
||||||
return err
|
validateRet := make(sliceValidateError, 0)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
||||||
|
validateRet = append(validateRet, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if len(validateRet) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return validateRet
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
// validateStruct receives struct type
|
||||||
|
func (v *defaultValidator) validateStruct(obj interface{}) error {
|
||||||
|
v.lazyinit()
|
||||||
|
return v.validate.Struct(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine returns the underlying validator engine which powers the default
|
// Engine returns the underlying validator engine which powers the default
|
||||||
|
68
binding/default_validator_test.go
Normal file
68
binding/default_validator_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2020 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 (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSliceValidateError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err sliceValidateError
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.err.Error(); got != tt.want {
|
||||||
|
t.Errorf("sliceValidateError.Error() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultValidator(t *testing.T) {
|
||||||
|
type exampleStruct struct {
|
||||||
|
A string `binding:"max=8"`
|
||||||
|
B int `binding:"gt=0"`
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
v *defaultValidator
|
||||||
|
obj interface{}
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"validate nil obj", &defaultValidator{}, nil, false},
|
||||||
|
{"validate int obj", &defaultValidator{}, 3, false},
|
||||||
|
{"validate struct failed-1", &defaultValidator{}, exampleStruct{A: "123456789", B: 1}, true},
|
||||||
|
{"validate struct failed-2", &defaultValidator{}, exampleStruct{A: "12345678", B: 0}, true},
|
||||||
|
{"validate struct passed", &defaultValidator{}, exampleStruct{A: "12345678", B: 1}, false},
|
||||||
|
{"validate *struct failed-1", &defaultValidator{}, &exampleStruct{A: "123456789", B: 1}, true},
|
||||||
|
{"validate *struct failed-2", &defaultValidator{}, &exampleStruct{A: "12345678", B: 0}, true},
|
||||||
|
{"validate *struct passed", &defaultValidator{}, &exampleStruct{A: "12345678", B: 1}, false},
|
||||||
|
{"validate []struct failed-1", &defaultValidator{}, []exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
|
{"validate []struct failed-2", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
|
{"validate []struct passed", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
{"validate []*struct failed-1", &defaultValidator{}, []*exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
|
{"validate []*struct failed-2", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
|
{"validate []*struct passed", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
{"validate *[]struct failed-1", &defaultValidator{}, &[]exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
|
{"validate *[]struct failed-2", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
|
{"validate *[]struct passed", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
{"validate *[]*struct failed-1", &defaultValidator{}, &[]*exampleStruct{{A: "123456789", B: 1}}, true},
|
||||||
|
{"validate *[]*struct failed-2", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 0}}, true},
|
||||||
|
{"validate *[]*struct passed", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 1}}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := tt.v.ValidateStruct(tt.obj); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("defaultValidator.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
// +build appengine
|
|
||||||
|
|
||||||
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||||
// 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.
|
||||||
|
|
||||||
|
//go:build appengine
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
4
debug.go
4
debug.go
@ -12,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 10
|
const ginSupportMinGoVer = 12
|
||||||
|
|
||||||
// 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.
|
||||||
@ -67,7 +67,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.11 or later and Go 1.12 will be required soon.
|
debugPrint(`[WARNING] Now Gin requires Go 1.12+.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,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.11 or later and Go 1.12 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.12+.\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)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build go1.13
|
||||||
// +build go1.13
|
// +build go1.13
|
||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
@ -5,16 +5,17 @@
|
|||||||
package bytesconv
|
package bytesconv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StringToBytes converts string to byte slice without a memory allocation.
|
// StringToBytes converts string to byte slice without a memory allocation.
|
||||||
func StringToBytes(s string) (b []byte) {
|
func StringToBytes(s string) []byte {
|
||||||
sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
|
return *(*[]byte)(unsafe.Pointer(
|
||||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
&struct {
|
||||||
bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len
|
string
|
||||||
return b
|
Cap int
|
||||||
|
}{s, len(s)},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BytesToString converts byte slice to string without a memory allocation.
|
// BytesToString converts byte slice to string without a memory allocation.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build !jsoniter
|
||||||
// +build !jsoniter
|
// +build !jsoniter
|
||||||
|
|
||||||
package json
|
package json
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build jsoniter
|
||||||
// +build jsoniter
|
// +build jsoniter
|
||||||
|
|
||||||
package json
|
package json
|
||||||
|
2
mode.go
2
mode.go
@ -63,7 +63,7 @@ func SetMode(value string) {
|
|||||||
case TestMode:
|
case TestMode:
|
||||||
ginMode = testCode
|
ginMode = testCode
|
||||||
default:
|
default:
|
||||||
panic("gin mode unknown: " + value)
|
panic("gin mode unknown: " + value + " (available mode: debug release test)")
|
||||||
}
|
}
|
||||||
|
|
||||||
modeName = value
|
modeName = value
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package render
|
package render
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
// +build !nomsgpack
|
// +build !nomsgpack
|
||||||
|
|
||||||
package render
|
package render
|
||||||
|
@ -238,7 +238,6 @@ func TestRouteParamsByName(t *testing.T) {
|
|||||||
|
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, name, c.Param("name"))
|
assert.Equal(t, name, c.Param("name"))
|
||||||
assert.Equal(t, name, c.Param("name"))
|
|
||||||
assert.Equal(t, lastName, c.Param("last_name"))
|
assert.Equal(t, lastName, c.Param("last_name"))
|
||||||
|
|
||||||
assert.Empty(t, c.Param("wtf"))
|
assert.Empty(t, c.Param("wtf"))
|
||||||
@ -272,7 +271,6 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
|||||||
|
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, name, c.Param("name"))
|
assert.Equal(t, name, c.Param("name"))
|
||||||
assert.Equal(t, name, c.Param("name"))
|
|
||||||
assert.Equal(t, lastName, c.Param("last_name"))
|
assert.Equal(t, lastName, c.Param("last_name"))
|
||||||
|
|
||||||
assert.Empty(t, c.Param("wtf"))
|
assert.Empty(t, c.Param("wtf"))
|
||||||
|
277
tree.go
277
tree.go
@ -119,7 +119,6 @@ func (n *node) incrementChildPrio(pos int) int {
|
|||||||
for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
|
for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
|
||||||
// Swap node positions
|
// Swap node positions
|
||||||
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
|
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build new index char string
|
// Build new index char string
|
||||||
@ -559,8 +558,8 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]by
|
|||||||
// Use a static sized buffer on the stack in the common case.
|
// Use a static sized buffer on the stack in the common case.
|
||||||
// If the path is too long, allocate a buffer on the heap instead.
|
// If the path is too long, allocate a buffer on the heap instead.
|
||||||
buf := make([]byte, 0, stackBufSize)
|
buf := make([]byte, 0, stackBufSize)
|
||||||
if l := len(path) + 1; l > stackBufSize {
|
if length := len(path) + 1; length > stackBufSize {
|
||||||
buf = make([]byte, 0, l)
|
buf = make([]byte, 0, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
ciPath := n.findCaseInsensitivePathRec(
|
ciPath := n.findCaseInsensitivePathRec(
|
||||||
@ -600,142 +599,7 @@ walk: // Outer loop for walking the tree
|
|||||||
path = path[npLen:]
|
path = path[npLen:]
|
||||||
ciPath = append(ciPath, n.path...)
|
ciPath = append(ciPath, n.path...)
|
||||||
|
|
||||||
if len(path) > 0 {
|
if len(path) == 0 {
|
||||||
// If this node does not have a wildcard (param or catchAll) child,
|
|
||||||
// we can just look up the next child node and continue to walk down
|
|
||||||
// the tree
|
|
||||||
if !n.wildChild {
|
|
||||||
// Skip rune bytes already processed
|
|
||||||
rb = shiftNRuneBytes(rb, npLen)
|
|
||||||
|
|
||||||
if rb[0] != 0 {
|
|
||||||
// Old rune not finished
|
|
||||||
idxc := rb[0]
|
|
||||||
for i, c := range []byte(n.indices) {
|
|
||||||
if c == idxc {
|
|
||||||
// continue with child node
|
|
||||||
n = n.children[i]
|
|
||||||
npLen = len(n.path)
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Process a new rune
|
|
||||||
var rv rune
|
|
||||||
|
|
||||||
// Find rune start.
|
|
||||||
// Runes are up to 4 byte long,
|
|
||||||
// -4 would definitely be another rune.
|
|
||||||
var off int
|
|
||||||
for max := min(npLen, 3); off < max; off++ {
|
|
||||||
if i := npLen - off; utf8.RuneStart(oldPath[i]) {
|
|
||||||
// read rune from cached path
|
|
||||||
rv, _ = utf8.DecodeRuneInString(oldPath[i:])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate lowercase bytes of current rune
|
|
||||||
lo := unicode.ToLower(rv)
|
|
||||||
utf8.EncodeRune(rb[:], lo)
|
|
||||||
|
|
||||||
// Skip already processed bytes
|
|
||||||
rb = shiftNRuneBytes(rb, off)
|
|
||||||
|
|
||||||
idxc := rb[0]
|
|
||||||
for i, c := range []byte(n.indices) {
|
|
||||||
// Lowercase matches
|
|
||||||
if c == idxc {
|
|
||||||
// must use a recursive approach since both the
|
|
||||||
// uppercase byte and the lowercase byte might exist
|
|
||||||
// as an index
|
|
||||||
if out := n.children[i].findCaseInsensitivePathRec(
|
|
||||||
path, ciPath, rb, fixTrailingSlash,
|
|
||||||
); out != nil {
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we found no match, the same for the uppercase rune,
|
|
||||||
// if it differs
|
|
||||||
if up := unicode.ToUpper(rv); up != lo {
|
|
||||||
utf8.EncodeRune(rb[:], up)
|
|
||||||
rb = shiftNRuneBytes(rb, off)
|
|
||||||
|
|
||||||
idxc := rb[0]
|
|
||||||
for i, c := range []byte(n.indices) {
|
|
||||||
// Uppercase matches
|
|
||||||
if c == idxc {
|
|
||||||
// Continue with child node
|
|
||||||
n = n.children[i]
|
|
||||||
npLen = len(n.path)
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL
|
|
||||||
// without a trailing slash if a leaf exists for that path
|
|
||||||
if fixTrailingSlash && path == "/" && n.handlers != nil {
|
|
||||||
return ciPath
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
n = n.children[0]
|
|
||||||
switch n.nType {
|
|
||||||
case param:
|
|
||||||
// Find param end (either '/' or path end)
|
|
||||||
end := 0
|
|
||||||
for end < len(path) && path[end] != '/' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add param value to case insensitive path
|
|
||||||
ciPath = append(ciPath, path[:end]...)
|
|
||||||
|
|
||||||
// We need to go deeper!
|
|
||||||
if end < len(path) {
|
|
||||||
if len(n.children) > 0 {
|
|
||||||
// Continue with child node
|
|
||||||
n = n.children[0]
|
|
||||||
npLen = len(n.path)
|
|
||||||
path = path[end:]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... but we can't
|
|
||||||
if fixTrailingSlash && len(path) == end+1 {
|
|
||||||
return ciPath
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.handlers != nil {
|
|
||||||
return ciPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if fixTrailingSlash && len(n.children) == 1 {
|
|
||||||
// No handle found. Check if a handle for this path + a
|
|
||||||
// trailing slash exists
|
|
||||||
n = n.children[0]
|
|
||||||
if n.path == "/" && n.handlers != nil {
|
|
||||||
return append(ciPath, '/')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case catchAll:
|
|
||||||
return append(ciPath, path...)
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("invalid node type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We should have reached the node containing the handle.
|
// We should have reached the node containing the handle.
|
||||||
// Check if this node has a handle registered.
|
// Check if this node has a handle registered.
|
||||||
if n.handlers != nil {
|
if n.handlers != nil {
|
||||||
@ -758,6 +622,141 @@ walk: // Outer loop for walking the tree
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this node does not have a wildcard (param or catchAll) child,
|
||||||
|
// we can just look up the next child node and continue to walk down
|
||||||
|
// the tree
|
||||||
|
if !n.wildChild {
|
||||||
|
// Skip rune bytes already processed
|
||||||
|
rb = shiftNRuneBytes(rb, npLen)
|
||||||
|
|
||||||
|
if rb[0] != 0 {
|
||||||
|
// Old rune not finished
|
||||||
|
idxc := rb[0]
|
||||||
|
for i, c := range []byte(n.indices) {
|
||||||
|
if c == idxc {
|
||||||
|
// continue with child node
|
||||||
|
n = n.children[i]
|
||||||
|
npLen = len(n.path)
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Process a new rune
|
||||||
|
var rv rune
|
||||||
|
|
||||||
|
// Find rune start.
|
||||||
|
// Runes are up to 4 byte long,
|
||||||
|
// -4 would definitely be another rune.
|
||||||
|
var off int
|
||||||
|
for max := min(npLen, 3); off < max; off++ {
|
||||||
|
if i := npLen - off; utf8.RuneStart(oldPath[i]) {
|
||||||
|
// read rune from cached path
|
||||||
|
rv, _ = utf8.DecodeRuneInString(oldPath[i:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate lowercase bytes of current rune
|
||||||
|
lo := unicode.ToLower(rv)
|
||||||
|
utf8.EncodeRune(rb[:], lo)
|
||||||
|
|
||||||
|
// Skip already processed bytes
|
||||||
|
rb = shiftNRuneBytes(rb, off)
|
||||||
|
|
||||||
|
idxc := rb[0]
|
||||||
|
for i, c := range []byte(n.indices) {
|
||||||
|
// Lowercase matches
|
||||||
|
if c == idxc {
|
||||||
|
// must use a recursive approach since both the
|
||||||
|
// uppercase byte and the lowercase byte might exist
|
||||||
|
// as an index
|
||||||
|
if out := n.children[i].findCaseInsensitivePathRec(
|
||||||
|
path, ciPath, rb, fixTrailingSlash,
|
||||||
|
); out != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found no match, the same for the uppercase rune,
|
||||||
|
// if it differs
|
||||||
|
if up := unicode.ToUpper(rv); up != lo {
|
||||||
|
utf8.EncodeRune(rb[:], up)
|
||||||
|
rb = shiftNRuneBytes(rb, off)
|
||||||
|
|
||||||
|
idxc := rb[0]
|
||||||
|
for i, c := range []byte(n.indices) {
|
||||||
|
// Uppercase matches
|
||||||
|
if c == idxc {
|
||||||
|
// Continue with child node
|
||||||
|
n = n.children[i]
|
||||||
|
npLen = len(n.path)
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found. We can recommend to redirect to the same URL
|
||||||
|
// without a trailing slash if a leaf exists for that path
|
||||||
|
if fixTrailingSlash && path == "/" && n.handlers != nil {
|
||||||
|
return ciPath
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n = n.children[0]
|
||||||
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// Find param end (either '/' or path end)
|
||||||
|
end := 0
|
||||||
|
for end < len(path) && path[end] != '/' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add param value to case insensitive path
|
||||||
|
ciPath = append(ciPath, path[:end]...)
|
||||||
|
|
||||||
|
// We need to go deeper!
|
||||||
|
if end < len(path) {
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
// Continue with child node
|
||||||
|
n = n.children[0]
|
||||||
|
npLen = len(n.path)
|
||||||
|
path = path[end:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but we can't
|
||||||
|
if fixTrailingSlash && len(path) == end+1 {
|
||||||
|
return ciPath
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.handlers != nil {
|
||||||
|
return ciPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if fixTrailingSlash && len(n.children) == 1 {
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists
|
||||||
|
n = n.children[0]
|
||||||
|
if n.path == "/" && n.handlers != nil {
|
||||||
|
return append(ciPath, '/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
return append(ciPath, path...)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing found.
|
// Nothing found.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user