Makes node prioritization insertion-independent

The order in which nodes were inserted into a tree might produce
failures in lookup mechanism.

    tree = Radix::Tree(Symbol).new
    tree.add "/one/:id", :one
    tree.add "/one-longer/:id", :two

    result = tree.find "/one-longer/10"

    # expected `true`
    result.found? # => false

In above example, reversing the order of insertion solved the issue,
but exposed the naive sorting/prioritization mechanism used.

This change improves that by properly identifying the kind of node
being evaluated and compared against others of the same kind.

It is now possible to know in advance if a node contains an special
condition (named parameter or globbing) or is a normal one:

    node = Radix::Node(Nil).new("a")
    node.normal? # => true

    node = Radix::Node(Nil).new(":query")
    node.named? # => true

    node = Radix::Node(Nil).new("*filepath")
    node.glob? # => true

Which helps out with prioritization of nodes:

- A normal node ranks higher than a named one
- A named node ranks higher than a glob one
- On two of same kind, ranks are based on priority

With this change in place, above example works as expected:

    tree = Radix::Tree(Symbol).new
    tree.add "/one/:id", :one
    tree.add "/one-longer/:id", :two

    result = tree.find "/one-longer/10"

    result.found? # => true

Fixes #18
This commit is contained in:
Luis Lavena 2017-02-04 12:10:51 -03:00
parent 0dc6e174d2
commit 1764332123
4 changed files with 187 additions and 68 deletions

View file

@ -2,6 +2,16 @@ require "../spec_helper"
module Radix
describe Node do
describe "#glob?" do
it "returns true when key contains a glob parameter (catch all)" do
node = Node(Nil).new("a")
node.glob?.should be_false
node = Node(Nil).new("*filepath")
node.glob?.should be_true
end
end
describe "#key=" do
it "accepts change of key after initialization" do
node = Node(Nil).new("abc")
@ -10,6 +20,38 @@ module Radix
node.key = "xyz"
node.key.should eq("xyz")
end
it "also changes kind when modified" do
node = Node(Nil).new("abc")
node.normal?.should be_true
node.key = ":query"
node.normal?.should be_false
node.named?.should be_true
end
end
describe "#named?" do
it "returns true when key contains a named parameter" do
node = Node(Nil).new("a")
node.named?.should be_false
node = Node(Nil).new(":query")
node.named?.should be_true
end
end
describe "#normal?" do
it "returns true when key does not contain named or glob parameters" do
node = Node(Nil).new("a")
node.normal?.should be_true
node = Node(Nil).new(":query")
node.normal?.should be_false
node = Node(Nil).new("*filepath")
node.normal?.should be_false
end
end
describe "#payload" do
@ -36,7 +78,7 @@ module Radix
end
describe "#priority" do
it "calculates it based on key size" do
it "calculates it based on key length" do
node = Node(Nil).new("a")
node.priority.should eq(1)
@ -44,20 +86,20 @@ module Radix
node.priority.should eq(3)
end
it "returns zero for catch all (globbed) key" do
node = Node(Nil).new("*filepath")
node.priority.should eq(-2)
it "considers key length up until named parameter presence" do
node = Node(Nil).new("/posts/:id")
node.priority.should eq(7)
node = Node(Nil).new("/src/*filepath")
node.priority.should eq(-2)
node = Node(Nil).new("/u/:username")
node.priority.should eq(3)
end
it "returns one for keys with named parameters" do
node = Node(Nil).new(":query")
node.priority.should eq(-1)
it "considers key length up until glob parameter presence" do
node = Node(Nil).new("/search/*query")
node.priority.should eq(8)
node = Node(Nil).new("/search/:query")
node.priority.should eq(-1)
node = Node(Nil).new("/*anything")
node.priority.should eq(1)
end
it "changes when key changes" do
@ -67,16 +109,16 @@ module Radix
node.key = "abc"
node.priority.should eq(3)
node.key = "*filepath"
node.priority.should eq(-2)
node.key = "/src/*filepath"
node.priority.should eq(5)
node.key = ":query"
node.priority.should eq(-1)
node.key = "/search/:query"
node.priority.should eq(8)
end
end
describe "#sort!" do
it "orders children by priority" do
it "orders children" do
root = Node(Int32).new("/")
node1 = Node(Int32).new("a", 1)
node2 = Node(Int32).new("bc", 2)
@ -90,7 +132,7 @@ module Radix
root.children[2].should eq(node1)
end
it "orders catch all and named parameters lower than others" do
it "orders catch all and named parameters lower than normal nodes" do
root = Node(Int32).new("/")
node1 = Node(Int32).new("*filepath", 1)
node2 = Node(Int32).new("abc", 2)

View file

@ -544,6 +544,19 @@ module Radix
result.payload.should eq(:featured)
end
end
context "dealing with named parameters and shared key" do
it "finds matching path" do
tree = Tree(Symbol).new
tree.add "/one/:id", :one
tree.add "/one-longer/:id", :two
result = tree.find "/one-longer/10"
result.found?.should be_true
result.key.should eq("/one-longer/:id")
result.params["id"].should eq("10")
end
end
end
end
end