Echo: Route conflict

Created on 10 Mar 2017  ยท  4Comments  ยท  Source: labstack/echo

Description

If I have two routes defined, one with a static value and one with a path param, the static value cannot be used as the path param even if the two routes do not have the same number of path segments.

For example, if I define these two routes:

GET /users/:id/comments   // 3 path segments
GET /:resourceName/:id    // 2 path segments

The second route fails when :resourceId is users (but works for any other value). I'm confused by this behavior. I know the matching order is static -> param -> any, but I would have thought that if no match was found down the static /users path, the router would then attempt to match down the /:resourceId path.

Checklist

  • [x] Dependencies installed
  • [x] No typos
  • [x] Searched existing issues and docs

Expected behaviour

All of these requests should work:

GET /users/1/comments
GET /boozers/1
GET /users/1

Actual behaviour

These two work:

GET /users/1/comments
GET /boozers/1

But this fails:

GET /users/1

Steps to reproduce

Working code to debug

package main

import (
    "testing"

    "github.com/labstack/echo"
    "github.com/stretchr/testify/assert"
)

func TestRouteConflict(t *testing.T) {
    e := echo.New()
    r := e.Router()

    e.GET("/users/:id/comments", func(c echo.Context) error {
        c.Set("r", 1)
        return nil
    })

    e.GET("/:resourceName/:id", func(c echo.Context) error {
        c.Set("r", 2)
        return nil
    })

    c := e.NewContext(nil, nil)
    r.Find(echo.GET, "/users/1/comments", c)
    c.Handler()(c)
    assert.Equal(t, 1, c.Get("r"))

    c = e.NewContext(nil, nil)
    r.Find(echo.GET, "/boozers/1", c)
    c.Handler()(c)
    assert.Equal(t, 2, c.Get("r"))

    c = e.NewContext(nil, nil)
    r.Find(echo.GET, "/users/1", c)
    c.Handler()(c)
    assert.Equal(t, 2, c.Get("r")) // this assertion fails
}

Version/commit

3.1.0-rc1

wontfix

Most helpful comment

Echo already does better than httprouter in this regard. From httprouter README:

For example you can not register the patterns /user/new and /user/:user for the same request method at the same time.

Echo uses the same patterns as a _working_ example on the Routing page of the guide:

e.GET("/users/:id", func(c echo.Context) error {
    return c.String(http.StatusOK, "/users/:id")
})

e.GET("/users/new", func(c echo.Context) error {
    return c.String(http.StatusOK, "/users/new")
})

e.GET("/users/1/files/*", func(c echo.Context) error {
    return c.String(http.StatusOK, "/users/1/files/*")
})

All 4 comments

httprouter has this problem which is a radix tree based router also.

Echo already does better than httprouter in this regard. From httprouter README:

For example you can not register the patterns /user/new and /user/:user for the same request method at the same time.

Echo uses the same patterns as a _working_ example on the Routing page of the guide:

e.GET("/users/:id", func(c echo.Context) error {
    return c.String(http.StatusOK, "/users/:id")
})

e.GET("/users/new", func(c echo.Context) error {
    return c.String(http.StatusOK, "/users/new")
})

e.GET("/users/1/files/*", func(c echo.Context) error {
    return c.String(http.StatusOK, "/users/1/files/*")
})

The reason for this is the way the radix tree is created.
Running your test file the radix tree looks like such -

โ””โ”€โ”€ /, pnames=[] ppath= handler=&{<nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil>}
 โ”œโ”€โ”€ users/, pnames=[] ppath= handler=&{<nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil>}
 โ”‚ โ””โ”€โ”€ :, pnames=[id] ppath=/users/:id/comments handler=&{<nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil>}
 โ”‚  โ””โ”€โ”€ /comments, pnames=[id] ppath=/users/:id/comments handler=&{<nil> <nil> 0x135ff50 <nil> <nil> <nil> <nil> <nil> <nil> <nil>}
 โ””โ”€โ”€ :, pnames=[resourceName] ppath=/:resourceName/:id handler=&{<nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil>}
  โ””โ”€โ”€ /, pnames=[] ppath= handler=&{<nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil>}
   โ””โ”€โ”€ :, pnames=[resourceName id] ppath=/:resourceName/:id handler=&{<nil> <nil> 0x135ff50 <nil> <nil> <nil> <nil> <nil> <nil> <nil>}

your 3rd test hits on /users/: which has no handlers associated with it since you didn't actually create a handler for that path. It just happened to be an intermediate step in the creation of the tree.

I'm not sure if this is a limitation of the radix tree, or something specific to echo, but I figured I'd at least comment for those that see this in the future.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

younisshah picture younisshah  ยท  4Comments

wangxianzhuo picture wangxianzhuo  ยท  4Comments

montanaflynn picture montanaflynn  ยท  3Comments

dre1080 picture dre1080  ยท  4Comments

toorop picture toorop  ยท  4Comments