diff --git a/src/Nancy/Routing/Trie/MatchResult.cs b/src/Nancy/Routing/Trie/MatchResult.cs
index 05b007c9e6..c6e4d65bfd 100644
--- a/src/Nancy/Routing/Trie/MatchResult.cs
+++ b/src/Nancy/Routing/Trie/MatchResult.cs
@@ -65,37 +65,40 @@ public MatchResult()
/// An object to compare with this object.
public int CompareTo(MatchResult other)
{
- // Length of the route takes precedence over score
- if (this.RouteLength < other.RouteLength)
+ // Score of the route takes precedence over length
+ if (this.Score < other.Score)
{
return -1;
}
- if (this.RouteLength > other.RouteLength)
+ if (this.Score > other.Score)
{
return 1;
}
- if (this.Score < other.Score)
+ // If the score is the same, take the shortest route that matches
+ if (this.RouteLength < other.RouteLength)
{
- return -1;
+ return 1;
}
- if (this.Score > other.Score)
+ if (this.RouteLength > other.RouteLength)
{
- return 1;
+ return -1;
}
+
if (Equals(this.ModuleType, other.ModuleType))
{
+ //Otherwise, we need to take the route that was indexed first
if (this.RouteIndex < other.RouteIndex)
{
- return -1;
+ return 1;
}
if (this.RouteIndex > other.RouteIndex)
{
- return 1;
+ return -1;
}
}
diff --git a/test/Nancy.Tests/Unit/Routing/DefaultRouteResolverFixture.cs b/test/Nancy.Tests/Unit/Routing/DefaultRouteResolverFixture.cs
index 188c97fea5..9c278ec456 100644
--- a/test/Nancy.Tests/Unit/Routing/DefaultRouteResolverFixture.cs
+++ b/test/Nancy.Tests/Unit/Routing/DefaultRouteResolverFixture.cs
@@ -152,11 +152,11 @@ public async Task Should_resolve_optional_capture_with_default_with_optional_spe
//Then
if (ShouldBeFound(path, caseSensitive))
{
- result.Body.AsString().ShouldEqual("OptionalCaptureWithDefault " + expected);
+ result.Body.AsString().ShouldEqual("OptionalCaptureWithDefault " + expected);
}
else
{
- result.StatusCode.ShouldEqual(HttpStatusCode.NotFound);
+ result.StatusCode.ShouldEqual(HttpStatusCode.NotFound);
}
}
@@ -174,14 +174,73 @@ public async Task Should_resolve_optional_capture_with_default_with_optional_not
//Then
if (ShouldBeFound(path, caseSensitive))
{
- result.Body.AsString().ShouldEqual("OptionalCaptureWithDefault test");
+ result.Body.AsString().ShouldEqual("OptionalCaptureWithDefault test");
}
else
{
- result.StatusCode.ShouldEqual(HttpStatusCode.NotFound);
+ result.StatusCode.ShouldEqual(HttpStatusCode.NotFound);
}
}
+ [Theory]
+ [InlineData("/api", "Single optional segment")]
+ [InlineData("/api/", "Single optional segment")]
+ [InlineData("/api/arg1", "Single optional segment")]
+ [InlineData("/api/arg1/", "Single optional segment")]
+ [InlineData("/api/arg1/arg2", "Two optional segments")]
+ [InlineData("/api/arg1/arg2/", "Two optional segments")]
+ [InlineData("/api/arg1/arg2/arg3", "Three optional segments")]
+ [InlineData("/api/arg1/arg2/arg3/", "Three optional segments")]
+ public async Task Should_Resolve_Optionals_Correctly(string path, string expected)
+ {
+ var browser = InitBrowser(caseSensitive: false);
+ var result = await browser.Get(path);
+ result.Body.AsString().ShouldEqual(expected);
+ }
+
+ [Theory]
+ [InlineData("/api/greedy/arg1", "Greedy match")]
+ [InlineData("/api/greedy/arg1/arg2", "Greedy match")]
+ public async Task Should_Resolve_Greedy_Alongside_Optionals(string path, string expected)
+ {
+ var browser = InitBrowser(caseSensitive: false);
+ var result = await browser.Get(path);
+ result.Body.AsString().ShouldEqual(expected);
+ }
+
+ [Theory]
+ [InlineData("/optional/literal/bar", "Single optional segment, literal segment at end")]
+ [InlineData("/optional/literal/arg1/bar", "Single optional segment, literal segment at end")]
+ [InlineData("/optional/literal/arg1/arg2/bar", "Two optional segments, literal segment at end")]
+ public async Task Should_Resolve_Optionals_with_Literal_Ends(string path, string expected)
+ {
+ var browser = InitBrowser(caseSensitive: false);
+ var result = await browser.Get(path);
+ result.Body.AsString().ShouldEqual(expected);
+ }
+
+ [Theory]
+ [InlineData("/optional/variable/hello", "Single hello")]
+ [InlineData("/optional/variable/hello/there", "Single hello there")]
+ [InlineData("/optional/variable/hello/there/everybody", "Double hello there everybody")]
+ public async Task Should_Resolve_Optionals_with_Variable_Ends(string path, string expected)
+ {
+ var browser = InitBrowser(caseSensitive: false);
+ var result = await browser.Get(path);
+ result.Body.AsString().ShouldEqual(expected);
+ }
+
+ [Theory]
+ [InlineData("/too/greedy/arg1", "One arg1")]
+ [InlineData("/too/greedy/arg1/hello", "Literal arg1")]
+ public async Task Should_Not_Be_Too_Greedy(string path, string expected)
+ {
+ var browser = InitBrowser(caseSensitive: false);
+ var result = await browser.Get(path);
+ var woot = result.Body.AsString();
+ result.Body.AsString().ShouldEqual(expected);
+ }
+
[Theory]
[InlineData("/bleh/this/is/some/stuff", true, "this/is/some/stuff")]
[InlineData("/bleh/this/is/some/stuff", false, "this/is/some/stuff")]
@@ -442,6 +501,7 @@ public NoRootModule()
}
}
+
private class TestModule : NancyModule
{
public TestModule()
@@ -475,6 +535,26 @@ public TestModule()
Get("/capturenodewithliteral/{file}.html", args => "CaptureNodeWithLiteral " + args.file + ".html");
Get(@"/regex/(?\d{2,4})/{bar}", args => string.Format("RegEx {0} {1}", args.foo, args.bar));
+
+ Get("/api/{arg1?}", args => "Single optional segment");
+
+ Get("/api/{arg1?}/{arg2?}", args => "Two optional segments");
+
+ Get("/api/{arg1?}/{arg2?}/{arg3?}", args => "Three optional segments");
+
+ Get("/api/greedy/{something*}", args => "Greedy match");
+
+ Get("/optional/literal/{arg1?}/bar", args => "Single optional segment, literal segment at end");
+
+ Get("/optional/literal/{arg1?}/{arg2?}/bar", args => "Two optional segments, literal segment at end");
+
+ Get("/optional/variable/{arg1?}/{variable}", args => string.Format("Single {0} {1}", args.arg1.Value, args.variable.Value));
+
+ Get("/optional/variable/{arg1?}/{arg2?}/{variable}", args => string.Format("Double {0} {1} {2}", args.arg1.Value, args.arg2.Value, args.variable.Value));
+
+ Get("/too/greedy/{arg1*}", args => string.Format("One {0}", args.arg1.Value));
+
+ Get("/too/greedy/{arg1*}/hello", args => string.Format("Literal {0}", args.arg1.Value));
}
}
}