Merge branch 'master' into upgrade_validator

This commit is contained in:
田欧 2019-05-26 08:27:05 +08:00 committed by GitHub
commit 823bb20c68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 134 additions and 68 deletions

View File

@ -252,6 +252,11 @@ func main() {
c.String(http.StatusOK, message) c.String(http.StatusOK, message)
}) })
// For each matched request Context will hold the route definition
router.POST("/user/:name/*action", func(c *gin.Context) {
c.FullPath() == "/user/:name/*action" // true
})
router.Run(":8080") router.Run(":8080")
} }
``` ```
@ -1694,7 +1699,7 @@ func main() {
quit := make(chan os.Signal) quit := make(chan os.Signal)
// kill (no param) default send syscall.SIGTERM // kill (no param) default send syscall.SIGTERM
// kill -2 is syscall.SIGINT // kill -2 is syscall.SIGINT
// kill -9 is syscall.SIGKILL but can"t be catch, so don't need add it // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit <-quit
log.Println("Shutdown Server ...") log.Println("Shutdown Server ...")

View File

@ -126,9 +126,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
for len(opts) > 0 { for len(opts) > 0 {
opt, opts = head(opts, ",") opt, opts = head(opts, ",")
k, v := head(opt, "=") if k, v := head(opt, "="); k == "default" {
switch k {
case "default":
setOpt.isDefaultExists = true setOpt.isDefaultExists = true
setOpt.defaultValue = v setOpt.defaultValue = v
} }

View File

@ -48,6 +48,7 @@ type Context struct {
Params Params Params Params
handlers HandlersChain handlers HandlersChain
index int8 index int8
fullPath string
engine *Engine engine *Engine
@ -70,6 +71,7 @@ func (c *Context) reset() {
c.Params = c.Params[0:0] c.Params = c.Params[0:0]
c.handlers = nil c.handlers = nil
c.index = -1 c.index = -1
c.fullPath = ""
c.Keys = nil c.Keys = nil
c.Errors = c.Errors[0:0] c.Errors = c.Errors[0:0]
c.Accepted = nil c.Accepted = nil
@ -111,6 +113,15 @@ func (c *Context) Handler() HandlerFunc {
return c.handlers.Last() return c.handlers.Last()
} }
// FullPath returns a matched route full path. For not found routes
// returns an empty string.
// router.GET("/user/:id", func(c *gin.Context) {
// c.FullPath() == "/user/:id" // true
// })
func (c *Context) FullPath() string {
return c.fullPath
}
/************************************/ /************************************/
/*********** FLOW CONTROL ***********/ /*********** FLOW CONTROL ***********/
/************************************/ /************************************/
@ -671,7 +682,7 @@ func (c *Context) ContentType() string {
// handshake is being initiated by the client. // handshake is being initiated by the client.
func (c *Context) IsWebsocket() bool { func (c *Context) IsWebsocket() bool {
if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") && if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") &&
strings.ToLower(c.requestHeader("Upgrade")) == "websocket" { strings.EqualFold(c.requestHeader("Upgrade"), "websocket") {
return true return true
} }
return false return false

View File

@ -660,7 +660,7 @@ func TestContextRenderJSON(t *testing.T) {
c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"}) c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
} }
@ -688,7 +688,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) {
c.JSONP(http.StatusCreated, H{"foo": "bar"}) c.JSONP(http.StatusCreated, H{"foo": "bar"})
assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
} }
@ -714,7 +714,7 @@ func TestContextRenderAPIJSON(t *testing.T) {
c.JSON(http.StatusCreated, H{"foo": "bar"}) c.JSON(http.StatusCreated, H{"foo": "bar"})
assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type"))
} }
@ -1117,7 +1117,7 @@ func TestContextNegotiationWithJSON(t *testing.T) {
}) })
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
} }
@ -1281,7 +1281,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
_, err := buf.ReadFrom(w.Body) _, err := buf.ReadFrom(w.Body)
assert.NoError(t, err) assert.NoError(t, err)
jsonStringBody := buf.String() jsonStringBody := buf.String()
assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody) assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}\n"), jsonStringBody)
} }
func TestContextError(t *testing.T) { func TestContextError(t *testing.T) {

14
gin.go
View File

@ -252,6 +252,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
root := engine.trees.get(method) root := engine.trees.get(method)
if root == nil { if root == nil {
root = new(node) root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root}) engine.trees = append(engine.trees, methodTree{method: method, root: root})
} }
root.addRoute(path, handlers) root.addRoute(path, handlers)
@ -382,16 +383,17 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
} }
root := t[i].root root := t[i].root
// Find route in tree // Find route in tree
handlers, params, tsr := root.getValue(rPath, c.Params, unescape) value := root.getValue(rPath, c.Params, unescape)
if handlers != nil { if value.handlers != nil {
c.handlers = handlers c.handlers = value.handlers
c.Params = params c.Params = value.params
c.fullPath = value.fullPath
c.Next() c.Next()
c.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()
return return
} }
if httpMethod != "CONNECT" && rPath != "/" { if httpMethod != "CONNECT" && rPath != "/" {
if tsr && engine.RedirectTrailingSlash { if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c) redirectTrailingSlash(c)
return return
} }
@ -407,7 +409,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
if tree.method == httpMethod { if tree.method == httpMethod {
continue continue
} }
if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil { if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body) serveError(c, http.StatusMethodNotAllowed, default405Body)
return return

View File

@ -369,15 +369,15 @@ func TestErrorLogger(t *testing.T) {
w := performRequest(router, "GET", "/error") w := performRequest(router, "GET", "/error")
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) assert.Equal(t, "{\"error\":\"this is an error\"}\n", w.Body.String())
w = performRequest(router, "GET", "/abort") w = performRequest(router, "GET", "/abort")
assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) assert.Equal(t, "{\"error\":\"no authorized\"}\n", w.Body.String())
w = performRequest(router, "GET", "/print") w = performRequest(router, "GET", "/print")
assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) assert.Equal(t, "hola!{\"error\":\"this is an error\"}\n", w.Body.String())
} }
func TestLoggerWithWriterSkippingPaths(t *testing.T) { func TestLoggerWithWriterSkippingPaths(t *testing.T) {

View File

@ -246,5 +246,5 @@ func TestMiddlewareWrite(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}\n{\"foo\":\"bar\"}\nevent:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
} }

View File

@ -68,11 +68,8 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
// WriteJSON marshals the given interface object and writes it with custom ContentType. // WriteJSON marshals the given interface object and writes it with custom ContentType.
func WriteJSON(w http.ResponseWriter, obj interface{}) error { func WriteJSON(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, jsonContentType) writeContentType(w, jsonContentType)
jsonBytes, err := json.Marshal(obj) encoder := json.NewEncoder(w)
if err != nil { err := encoder.Encode(&obj)
return err
}
_, err = w.Write(jsonBytes)
return err return err
} }

View File

@ -62,7 +62,7 @@ func TestRenderJSON(t *testing.T) {
err := (JSON{data}).Render(w) err := (JSON{data}).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
} }

View File

@ -554,3 +554,38 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) {
assert.Equal(t, 421, w.Code) assert.Equal(t, 421, w.Code)
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
func TestRouteContextHoldsFullPath(t *testing.T) {
router := New()
// Test routes
routes := []string{
"/",
"/simple",
"/project/:name",
"/project/:name/build/*params",
}
for _, route := range routes {
actualRoute := route
router.GET(route, func(c *Context) {
// For each defined route context should contain its full path
assert.Equal(t, actualRoute, c.FullPath())
c.AbortWithStatus(http.StatusOK)
})
}
for _, route := range routes {
w := performRequest(router, "GET", route)
assert.Equal(t, http.StatusOK, w.Code)
}
// Test not found
router.Use(func(c *Context) {
// For not found routes full path is empty
assert.Equal(t, "", c.FullPath())
})
w := performRequest(router, "GET", "/not-found")
assert.Equal(t, http.StatusNotFound, w.Code)
}

76
tree.go
View File

@ -94,6 +94,7 @@ type node struct {
nType nodeType nType nodeType
maxParams uint8 maxParams uint8
wildChild bool wildChild bool
fullPath string
} }
// increments priority of the given child and reorders if necessary. // increments priority of the given child and reorders if necessary.
@ -154,6 +155,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
children: n.children, children: n.children,
handlers: n.handlers, handlers: n.handlers,
priority: n.priority - 1, priority: n.priority - 1,
fullPath: fullPath,
} }
// Update maxParams (max of all children) // Update maxParams (max of all children)
@ -229,6 +231,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
n.indices += string([]byte{c}) n.indices += string([]byte{c})
child := &node{ child := &node{
maxParams: numParams, maxParams: numParams,
fullPath: fullPath,
} }
n.children = append(n.children, child) n.children = append(n.children, child)
n.incrementChildPrio(len(n.indices) - 1) n.incrementChildPrio(len(n.indices) - 1)
@ -296,6 +299,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
child := &node{ child := &node{
nType: param, nType: param,
maxParams: numParams, maxParams: numParams,
fullPath: fullPath,
} }
n.children = []*node{child} n.children = []*node{child}
n.wildChild = true n.wildChild = true
@ -312,6 +316,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
child := &node{ child := &node{
maxParams: numParams, maxParams: numParams,
priority: 1, priority: 1,
fullPath: fullPath,
} }
n.children = []*node{child} n.children = []*node{child}
n = child n = child
@ -339,6 +344,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
wildChild: true, wildChild: true,
nType: catchAll, nType: catchAll,
maxParams: 1, maxParams: 1,
fullPath: fullPath,
} }
n.children = []*node{child} n.children = []*node{child}
n.indices = string(path[i]) n.indices = string(path[i])
@ -352,6 +358,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
maxParams: 1, maxParams: 1,
handlers: handlers, handlers: handlers,
priority: 1, priority: 1,
fullPath: fullPath,
} }
n.children = []*node{child} n.children = []*node{child}
@ -364,13 +371,21 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
n.handlers = handlers n.handlers = handlers
} }
// nodeValue holds return values of (*Node).getValue method
type nodeValue struct {
handlers HandlersChain
params Params
tsr bool
fullPath string
}
// getValue returns the handle registered with the given path (key). The values of // getValue returns the handle registered with the given path (key). The values of
// wildcards are saved to a map. // wildcards are saved to a map.
// If no handle can be found, a TSR (trailing slash redirect) recommendation is // If no handle can be found, a TSR (trailing slash redirect) recommendation is
// made if a handle exists with an extra (without the) trailing slash for the // made if a handle exists with an extra (without the) trailing slash for the
// given path. // given path.
func (n *node) getValue(path string, po Params, unescape bool) (handlers HandlersChain, p Params, tsr bool) { func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) {
p = po value.params = po
walk: // Outer loop for walking the tree walk: // Outer loop for walking the tree
for { for {
if len(path) > len(n.path) { if len(path) > len(n.path) {
@ -391,7 +406,7 @@ walk: // Outer loop for walking the tree
// Nothing found. // Nothing found.
// We can recommend to redirect to the same URL without a // We can recommend to redirect to the same URL without a
// trailing slash if a leaf exists for that path. // trailing slash if a leaf exists for that path.
tsr = path == "/" && n.handlers != nil value.tsr = path == "/" && n.handlers != nil
return return
} }
@ -406,20 +421,20 @@ walk: // Outer loop for walking the tree
} }
// save param value // save param value
if cap(p) < int(n.maxParams) { if cap(value.params) < int(n.maxParams) {
p = make(Params, 0, n.maxParams) value.params = make(Params, 0, n.maxParams)
} }
i := len(p) i := len(value.params)
p = p[:i+1] // expand slice within preallocated capacity value.params = value.params[:i+1] // expand slice within preallocated capacity
p[i].Key = n.path[1:] value.params[i].Key = n.path[1:]
val := path[:end] val := path[:end]
if unescape { if unescape {
var err error var err error
if p[i].Value, err = url.QueryUnescape(val); err != nil { if value.params[i].Value, err = url.QueryUnescape(val); err != nil {
p[i].Value = val // fallback, in case of error value.params[i].Value = val // fallback, in case of error
} }
} else { } else {
p[i].Value = val value.params[i].Value = val
} }
// we need to go deeper! // we need to go deeper!
@ -431,40 +446,42 @@ walk: // Outer loop for walking the tree
} }
// ... but we can't // ... but we can't
tsr = len(path) == end+1 value.tsr = len(path) == end+1
return return
} }
if handlers = n.handlers; handlers != nil { if value.handlers = n.handlers; value.handlers != nil {
value.fullPath = n.fullPath
return return
} }
if len(n.children) == 1 { if len(n.children) == 1 {
// No handle found. Check if a handle for this path + a // No handle found. Check if a handle for this path + a
// trailing slash exists for TSR recommendation // trailing slash exists for TSR recommendation
n = n.children[0] n = n.children[0]
tsr = n.path == "/" && n.handlers != nil value.tsr = n.path == "/" && n.handlers != nil
} }
return return
case catchAll: case catchAll:
// save param value // save param value
if cap(p) < int(n.maxParams) { if cap(value.params) < int(n.maxParams) {
p = make(Params, 0, n.maxParams) value.params = make(Params, 0, n.maxParams)
} }
i := len(p) i := len(value.params)
p = p[:i+1] // expand slice within preallocated capacity value.params = value.params[:i+1] // expand slice within preallocated capacity
p[i].Key = n.path[2:] value.params[i].Key = n.path[2:]
if unescape { if unescape {
var err error var err error
if p[i].Value, err = url.QueryUnescape(path); err != nil { if value.params[i].Value, err = url.QueryUnescape(path); err != nil {
p[i].Value = path // fallback, in case of error value.params[i].Value = path // fallback, in case of error
} }
} else { } else {
p[i].Value = path value.params[i].Value = path
} }
handlers = n.handlers value.handlers = n.handlers
value.fullPath = n.fullPath
return return
default: default:
@ -474,12 +491,13 @@ walk: // Outer loop for walking the tree
} else if path == n.path { } else if path == n.path {
// 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 handlers = n.handlers; handlers != nil { if value.handlers = n.handlers; value.handlers != nil {
value.fullPath = n.fullPath
return return
} }
if path == "/" && n.wildChild && n.nType != root { if path == "/" && n.wildChild && n.nType != root {
tsr = true value.tsr = true
return return
} }
@ -488,7 +506,7 @@ walk: // Outer loop for walking the tree
for i := 0; i < len(n.indices); i++ { for i := 0; i < len(n.indices); i++ {
if n.indices[i] == '/' { if n.indices[i] == '/' {
n = n.children[i] n = n.children[i]
tsr = (len(n.path) == 1 && n.handlers != nil) || value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
(n.nType == catchAll && n.children[0].handlers != nil) (n.nType == catchAll && n.children[0].handlers != nil)
return return
} }
@ -499,7 +517,7 @@ walk: // Outer loop for walking the tree
// Nothing found. We can recommend to redirect to the same URL with an // Nothing found. We can recommend to redirect to the same URL with an
// extra trailing slash if a leaf exists for that path // extra trailing slash if a leaf exists for that path
tsr = (path == "/") || value.tsr = (path == "/") ||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' && (len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
path == n.path[:len(n.path)-1] && n.handlers != nil) path == n.path[:len(n.path)-1] && n.handlers != nil)
return return
@ -514,7 +532,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
// Outer loop for walking the tree // Outer loop for walking the tree
for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) { for len(path) >= len(n.path) && strings.EqualFold(path[:len(n.path)], n.path) {
path = path[len(n.path):] path = path[len(n.path):]
ciPath = append(ciPath, n.path...) ciPath = append(ciPath, n.path...)
@ -618,7 +636,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
return ciPath, true return ciPath, true
} }
if len(path)+1 == len(n.path) && n.path[len(path)] == '/' && if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) && strings.EqualFold(path, n.path[:len(path)]) &&
n.handlers != nil { n.handlers != nil {
return append(ciPath, n.path...), true return append(ciPath, n.path...), true
} }

View File

@ -35,22 +35,22 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ..
} }
for _, request := range requests { for _, request := range requests {
handler, ps, _ := tree.getValue(request.path, nil, unescape) value := tree.getValue(request.path, nil, unescape)
if handler == nil { if value.handlers == nil {
if !request.nilHandler { if !request.nilHandler {
t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path) t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
} }
} else if request.nilHandler { } else if request.nilHandler {
t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path) t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
} else { } else {
handler[0](nil) value.handlers[0](nil)
if fakeHandlerValue != request.route { if fakeHandlerValue != request.route {
t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route) t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
} }
} }
if !reflect.DeepEqual(ps, request.ps) { if !reflect.DeepEqual(value.params, request.ps) {
t.Errorf("Params mismatch for route '%s'", request.path) t.Errorf("Params mismatch for route '%s'", request.path)
} }
} }
@ -454,10 +454,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
"/doc/", "/doc/",
} }
for _, route := range tsrRoutes { for _, route := range tsrRoutes {
handler, _, tsr := tree.getValue(route, nil, false) value := tree.getValue(route, nil, false)
if handler != nil { if value.handlers != nil {
t.Fatalf("non-nil handler for TSR route '%s", route) t.Fatalf("non-nil handler for TSR route '%s", route)
} else if !tsr { } else if !value.tsr {
t.Errorf("expected TSR recommendation for route '%s'", route) t.Errorf("expected TSR recommendation for route '%s'", route)
} }
} }
@ -471,10 +471,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
"/api/world/abc", "/api/world/abc",
} }
for _, route := range noTsrRoutes { for _, route := range noTsrRoutes {
handler, _, tsr := tree.getValue(route, nil, false) value := tree.getValue(route, nil, false)
if handler != nil { if value.handlers != nil {
t.Fatalf("non-nil handler for No-TSR route '%s", route) t.Fatalf("non-nil handler for No-TSR route '%s", route)
} else if tsr { } else if value.tsr {
t.Errorf("expected no TSR recommendation for route '%s'", route) t.Errorf("expected no TSR recommendation for route '%s'", route)
} }
} }
@ -490,10 +490,10 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
t.Fatalf("panic inserting test route: %v", recv) t.Fatalf("panic inserting test route: %v", recv)
} }
handler, _, tsr := tree.getValue("/", nil, false) value := tree.getValue("/", nil, false)
if handler != nil { if value.handlers != nil {
t.Fatalf("non-nil handler") t.Fatalf("non-nil handler")
} else if tsr { } else if value.tsr {
t.Errorf("expected no TSR recommendation") t.Errorf("expected no TSR recommendation")
} }
} }