Skip to content

Commit

Permalink
Merge pull request #10 from gebv/bug/ambiguity_between_const_and_param
Browse files Browse the repository at this point in the history
more major fixes for #7 and more tests
  • Loading branch information
gebv authored Jul 6, 2021
2 parents 2bf7927 + 80e8360 commit 34baa8c
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 15 deletions.
37 changes: 37 additions & 0 deletions httprouter/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,43 @@ func Test_TableTestRoutes(t *testing.T) {
}
}

func Test_issues7(t *testing.T) {
// https://github.com/gebv/strparam/issues/7
r := NewRouter()
r.NotFoundHandelr = fText200("not found")
r.Add(http.MethodGet, "/{foobar}", fText200("/{foobar} %v", "foobar"))
r.Add(http.MethodGet, "/", fText200("/"))
r.Add(http.MethodGet, "/foo/{bar}/", fText200("/foo/{bar} %v", "bar"))
r.Add(http.MethodGet, "/a", fText200("/a"))
r.Add(http.MethodGet, "/a/1", fText200("/a/1"))
r.Add(http.MethodGet, "/a/1/{param}", fText200("/a/1/{param} %v", "param"))
r.Add(http.MethodGet, "/b", fText200("/b"))
r.Add(http.MethodGet, "/b/1", fText200("/b/1"))
r.Add(http.MethodGet, "/b/1/{param}", fText200("/b/1/{param} %v", "param"))

t.Log("[INFO] schema", r.store.String())

cases := []struct {
in string
wantBody string
}{
{"/b/1/asd", "/b/1/{param} asd"},
{"/b/1", "/b/1"},
{"/b/", "not found"},
{"/baz", "/{foobar} baz"},
}

for _, case_ := range cases {
t.Run(case_.in, func(t *testing.T) {
recorder := httptest.NewRecorder()
request, err := http.NewRequest("GET", case_.in, nil)
require.NoError(t, err)
r.ServeHTTP(recorder, request)
assert.EqualValues(t, case_.wantBody, recorder.Body.String())
})
}
}

func BenchmarkSimpleRouting(b *testing.B) {
router := NewRouter()
router.ErrorHandler = func(w http.ResponseWriter, req *http.Request) {
Expand Down
59 changes: 49 additions & 10 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,29 @@ func lookupNextToken(in string, offset int, parent *node, res *[]Token, numParam
// -- -- -- {END}
// -- -- {CONST}
// -- -- {END}

if offset+child.Token.Len <= len(in) {
if child.nextEnd() && offset+child.Token.Len != len(in) {
// if the next token is END then the tail must match exactly
if child.nextSingleEnd() && offset+child.Token.Len != len(in) {
continue
}
if in[offset:offset+child.Token.Len] == child.Token.Raw {
*res = append(*res, child.Token)

// jump into the branch
lookupNextToken(in, offset+child.Token.Len, child, res, numParams)
if offset+child.Token.Len == len(in) {
// end of the list
*res = append(*res, child.Token)
lookupNextToken(in, offset+child.Token.Len, child, res, numParams)
return
}

if child.nextHas(PARAMETER) || child.nextPrefixMatch(in[offset+child.Token.Len:]) {
// childs has match token

*res = append(*res, child.Token)
// next params
lookupNextToken(in, offset+child.Token.Len, child, res, numParams)
// returns because we move deeper into the tree
return
}

// returns because we move deeper into the tree
return
}
}

Expand Down Expand Up @@ -186,7 +196,7 @@ func lookupNextToken(in string, offset int, parent *node, res *[]Token, numParam
func rightPath(in string, offset int, node *node) (*node, int) {
for _, child := range node.Childs {
switch child.Token.Mode {
// case PARAMETER:
case PARAMETER:
// // -- {CONST}
// // -- -- {PARAM} <- look here
// // -- -- -- {CONST}
Expand Down Expand Up @@ -292,13 +302,42 @@ func (n *node) lengthConstOrZero() int {
return len(n.Token.Raw)
}

func (n *node) nextEnd() bool {
func (n *node) nextSingleEnd() bool {
if len(n.Childs) != 1 {
return false
}
return n.Childs[0].Token.Mode == END
}

func (n *node) nextHas(find TokenMode) bool {
if len(n.Childs) == 0 {
return false
}
for _, child := range n.Childs {
if child.Token.Mode == find {
return true
}
}
return false
}

func (n *node) nextPrefixMatch(offseted string) bool {
if len(n.Childs) == 0 {
return false
}
for _, child := range n.Childs {
if child.Token.Mode == SEPARATOR || child.Token.Mode == CONST {
if len(offseted) < child.Token.Len {
continue
}
if offseted[:child.Token.Len] == child.Token.Raw {
return true
}
}
}
return false
}

// Less returns true if
// - left token type is CONST
// - more length of value of token (type is CONST) on the left than right
Expand Down
47 changes: 42 additions & 5 deletions store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,17 @@ func Test_StoreMultiple(t *testing.T) {
// {"d", "/baz"},
// {"e", "/ba{foobar}"},
}, "/baz", false, nil},
{[][]string{
{"a", "/{foobar}"},
{"b", "/"},
{"c", "/foo/{bar}"},
{"d", "/a"},
{"d1", "/a/1"},
{"d1*", "/a/1/{param}"},
{"e", "/e"},
{"e1", "/e/1"},
{"e1*", "/e/1/{param}"},
}, "/baz/123", true, Tokens{StartToken, ConstToken("/"), ParsedParameterToken("foobar", "baz/123"), NamedEndToken("a")}},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%q->%q", tt.namedPatterns, tt.in), func(t *testing.T) {
Expand Down Expand Up @@ -304,13 +315,39 @@ func Test_PatternWithSeparator(t *testing.T) {

func Test_nextEnd(t *testing.T) {
n := &node{}
assert.False(t, n.nextEnd())
assert.False(t, n.nextSingleEnd())
n = &node{Childs: []*node{{Token: ConstToken("!")}}}
assert.False(t, n.nextEnd())
assert.False(t, n.nextSingleEnd())
n = &node{Childs: []*node{{Token: SeparatorToken("a")}}}
assert.False(t, n.nextEnd())
assert.False(t, n.nextSingleEnd())
n = &node{Childs: []*node{{Token: EndToken}}}
assert.True(t, n.nextEnd())
assert.True(t, n.nextSingleEnd())
n = &node{Childs: []*node{{Token: NamedEndToken("abc")}}}
assert.True(t, n.nextEnd())
assert.True(t, n.nextSingleEnd())
}

func Test_nextPrefixMatch(t *testing.T) {
n := &node{Childs: []*node{{Token: ConstToken("b")}}}
assert.True(t, n.nextPrefixMatch("b"))
assert.True(t, n.nextPrefixMatch("ba"))
assert.False(t, n.nextPrefixMatch(""))
n = &node{Childs: []*node{{Token: ConstToken("ba")}}}
assert.False(t, n.nextPrefixMatch(""))
assert.False(t, n.nextPrefixMatch("b"))
assert.True(t, n.nextPrefixMatch("ba"))

n = &node{Childs: []*node{
{Token: ConstToken("b")},
{Token: ConstToken("ba")},
}}
assert.True(t, n.nextPrefixMatch("b"))
assert.True(t, n.nextPrefixMatch("ba"))
assert.True(t, n.nextPrefixMatch("bac"))
assert.False(t, n.nextPrefixMatch(""))
}

func Test_nextHas(t *testing.T) {
n := &node{Childs: []*node{{Token: ConstToken("b")}}}
assert.True(t, n.nextHas(CONST))
assert.False(t, n.nextHas(END))
}

0 comments on commit 34baa8c

Please # to comment.