• With issues:
  • Use the search tool before opening a new issue.
  • Please provide source code and commit sha if you found a bug.
  • Review existing issues and provide feedback or react to them.

Description

Variable names have different meanings in different routes. After using generic variable names like 'id' and 'name' in the prefix tree, subsequent routes have to maintain the same prefix variable to avoid panics during prefix tree construction. However, this compromise results in decreased accuracy in expressing subsequent routes. It is desired to support multiple aliases for the same node in the prefix tree because, once a node is determined, aliases only affect the child nodes and handlers, without needing to prevent naming of sibling nodes.

How to reproduce

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    g := gin.Default()
    group := g.Group("groups")

    group.GET("/:id", func(c *gin.Context) {
        c.String(200, "group %s", c.Param("id"))
    })
    groupUsers := group.Group("/:group_id/users")
    groupUsers.GET("/:id", func(c *gin.Context) {
        c.String(200, "group %s user %s", c.Param("group_id"), c.Param("id"))
    })
    g.Run(":9000")
}

Expectations

$ curl http://localhost:9000/groups/1
group 1
$ curl http://localhost:9000/groups/1/users/2
group 1 user 2

Actual result

panic: ':group_id' in new path '/groups/:group_id/users/:id' conflicts with existing wildcard ':id' in existing prefix '/groups/:id'

Environment

  • go version: 1.20.3
  • gin version (or commit ref): 1.7.7

Comment From: AhmadGhadiri

The error makes sense as the parameter in the path for groups route should be consistent. in this line: group.GET("/:id", func(c *gin.Context) { the parameter after the /groups in route is defined as 'id', however in the below line: groupUsers := group.Group("/:group_id/users") it is defined as the group_id which will create confusion for the router. How about something like this:

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    g := gin.Default()
    group := g.Group("groups")

    group.GET("/:id", func(c *gin.Context) {
        c.String(200, "group %s", c.Param("id"))
    })
    groupUsers := group.Group("/:id/users")
    groupUsers.GET("/:user_id", func(c *gin.Context) {
        c.String(200, "group %s user %s", c.Param("id"), c.Param("user_id"))
    })
    g.Run(":9000")
}

result:

 $ curl -X GET http://localhost:9000/groups/1/users/2
group 1 user 2

Comment From: Laotree

The error makes sense as the parameter in the path for groups route should be consistent. in this line: group.GET("/:id", func(c *gin.Context) { the parameter after the /groups in route is defined as 'id', however in the below line: groupUsers := group.Group("/:group_id/users") it is defined as the group_id which will create confusion for the router. How about something like this:

``` package main

import ( "github.com/gin-gonic/gin" )

func main() { g := gin.Default() group := g.Group("groups")

group.GET("/:id", func(c gin.Context) { c.String(200, "group %s", c.Param("id")) }) groupUsers := group.Group("/:id/users") groupUsers.GET("/:user_id", func(c gin.Context) { c.String(200, "group %s user %s", c.Param("id"), c.Param("user_id")) }) g.Run(":9000") } ```

result:

$ curl -X GET http://localhost:9000/groups/1/users/2 group 1 user 2

thanks for reply, but it's not what I want. following codes both should work fine, in my opinion. since a/b/c is only alias, it's key point where they are in the tree, not the alias.

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    g := gin.Default()
    group := g.Group("groups")

    group.GET("/:a", func(c *gin.Context) {
        c.String(200, "group %s", c.Param("a"))
    })
    groupUsers := group.Group("/:a/users")
    groupUsers.GET("/:b", func(c *gin.Context) {
        c.String(200, "group %s user %s", c.Param("a"), c.Param("b"))
    })
    g.Run(":9000")
}
package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    g := gin.Default()
    group := g.Group("groups")

    group.GET("/:a", func(c *gin.Context) {
        c.String(200, "group %s", c.Param("a"))
    })
    groupUsers := group.Group("/:b/users")
    groupUsers.GET("/:c", func(c *gin.Context) {
        c.String(200, "group %s user %s", c.Param("b"), c.Param("c"))
    })
    g.Run(":9000")
}

Comment From: AhmadGhadiri

I understand the concept you explained.

To elaborate further, when examining the code, it appears that Gin utilizes a trie data structure to store its routes. In this structure, each node can have multiple child nodes, and the wildcard route is typically placed as the last child node.

During runtime, when trying to identify the appropriate handler for a given path, the aforementioned design allows for faster route resolution. The algorithm first searches for a specific child node that matches the path and, if unsuccessful, resorts to the wildcard route.

In order to bring about potential improvements, I believe it would be beneficial to reconsider the current design and explore alternatives such as pattern matching or other approaches. It would be interesting to hear the perspectives of the maintainers regarding this matter.