Skip to content

Why does Echo use 2 slices for params (pvalues, pnames) instead of a map? #1258

Closed
@andofcourse

Description

@andofcourse

Issue Description

It took me a while to realize how params actually function under the hood, and it seems very weird. Echo uses 2 slices (context.pname, context.pvalues) to keep track of params. This is already weird (why not just use a map?), but it actually gets weirder.

Basically, the router goes through all your routes, and increases e.maxParam (initially set to 0) to the highest number of params that you have in one route. When a user makes a request to your API, pvalues get instantiated to size of e.maxParam (even if the current request hits an endpoint with no params in the route). However, pnames has length set to the actual number of params in the current request - which means anytime you call c#Param(), you have to check that you're not going out of bounds.

Weird. But here's where it gets weirder.

If you want to modify the value of a param (for any reason), you actually can't without doing something hacky. So, you have to get pnames and pvalues using c#ParamNames() and c#ParamValues(). To set them (after you've modified the array), you'll have to use c#SetParamNames() and c#SetParamValues(). The problem is that c#ParamValues doesn't return the whole pvalues, but only the slice up to the length of pnames (c.pvalues[:len(c.pnames)]). This means when you go to set pvalues, you're actually changing the length - which breaks routing if you later hit an endpoint with more params than you just reset the length to.

This results in having to either keep track of e.maxParams yourself or setting the length of pvalues to some very large number.

Here's a code example from some middleware that you can write that breaks it:

names, vals := c.ParamNames(), c.ParamValues() //both slices are same length n
for i, n := range names {
   vals[i] = fmt.Sprintf("%s123", vals[i]) // changes a value of "asdf" to "asdf123"
}
c.SetParamValues(vals...) 

If this runs on an api with an endpoint like /test/:param1 then /test/:param1/:param2, it will break, because pvalues is now of length 1.

The "correct" way to do this would be:

names, vals, newVals := c.ParamNames(), c.ParamValues(), make([]string, n) // where n is a large number
for i, n := range names {
   newVals[i] = fmt.Sprintf("%s123", vals[i]) // changes a value of "asdf" to "asdf123"
}
c.SetParamValues(newVals...) 

This will work on the above scenario, but will not work if you hit an endpoint with n+1 params.

Is there a reason this isn't done with maps? Seems like it would fix this whole issue.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions