diff --git a/Rest.Net.Tests/Rest.Net.Tests.sln b/Rest.Net.Tests/Rest.Net.Tests.sln
new file mode 100644
index 0000000..0ab01b1
--- /dev/null
+++ b/Rest.Net.Tests/Rest.Net.Tests.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.32112.339
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rest.Net.Tests", "Rest.Net.Tests\Rest.Net.Tests.csproj", "{729431C7-DA13-4668-9D6A-C785C5AB4064}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {729431C7-DA13-4668-9D6A-C785C5AB4064}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {729431C7-DA13-4668-9D6A-C785C5AB4064}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {729431C7-DA13-4668-9D6A-C785C5AB4064}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {729431C7-DA13-4668-9D6A-C785C5AB4064}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0DB589F0-19C2-46AC-9CAB-903F66E22B52}
+ EndGlobalSection
+EndGlobal
diff --git a/Rest.Net.Tests/Rest.Net.Tests/Rest.Net.Tests.csproj b/Rest.Net.Tests/Rest.Net.Tests/Rest.Net.Tests.csproj
new file mode 100644
index 0000000..b5624ef
--- /dev/null
+++ b/Rest.Net.Tests/Rest.Net.Tests/Rest.Net.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net6.0
+ enable
+
+ false
+
+ MontoyaTech.Rest.Net.Tests
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/Rest.Net.Tests/Rest.Net.Tests/RouteMatcherTests.cs b/Rest.Net.Tests/Rest.Net.Tests/RouteMatcherTests.cs
new file mode 100644
index 0000000..30d9237
--- /dev/null
+++ b/Rest.Net.Tests/Rest.Net.Tests/RouteMatcherTests.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Xunit;
+
+namespace MontoyaTech.Rest.Net.Tests
+{
+ public class RouteMatcherTests
+ {
+ [Fact]
+ public void SyntaxWithRootShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/", "/", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithRootCatchAllShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/test1/test2", "/**", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithRootWildcardShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/test1", "/*", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithRootWildcardShouldNotMatch()
+ {
+ RouteMatcher.Matches("http://localhost/test1/test2", "/*", out _).Should().BeFalse();
+ }
+
+ [Fact]
+ public void SyntaxWithRootNotShouldNotMatch()
+ {
+ RouteMatcher.Matches("http://localhost/test1", "/!test1", out _).Should().BeFalse();
+ }
+
+ [Fact]
+ public void SyntaxWithRootNotShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/test2", "/!test1", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithArgumentShouldNotMatch()
+ {
+ RouteMatcher.Matches("http://localhost/test1/test2", "/{a}", out _).Should().BeFalse();
+ }
+
+ [Fact]
+ public void SyntaxWithArgumentShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/test1", "/{a}", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithWildcardShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/1/2/3", "/1/*/3", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithOrShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/a/b", "/a/b|c", out _).Should().BeTrue();
+ RouteMatcher.Matches("http://localhost/a/c", "/a/b|c", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithOrShouldNotMatch()
+ {
+ RouteMatcher.Matches("http://localhost/a/d", "/a/b|c", out _).Should().BeFalse();
+ }
+
+ [Fact]
+ public void SyntaxWithNotAndShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/a/d", "/a/!b&!c", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithNotAndShouldNotMatch()
+ {
+ RouteMatcher.Matches("http://localhost/a/b", "/a/!b&!c", out _).Should().BeFalse();
+ }
+
+ [Fact]
+ public void SyntaxWithSegmentShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/a", "/a", out _).Should().BeTrue();
+ }
+
+ [Fact]
+ public void SyntaxWithMultipleSegmentsShouldMatch()
+ {
+ RouteMatcher.Matches("http://localhost/a/b/c", "/a/b/c", out _).Should().BeTrue();
+ }
+ }
+}
diff --git a/Rest.Net.sln b/Rest.Net.sln
index 86a46f8..2cdab73 100644
--- a/Rest.Net.sln
+++ b/Rest.Net.sln
@@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rest.Net", "Rest.Net\Rest.N
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rest.Net.Example", "Rest.Net.Example\Rest.Net.Example\Rest.Net.Example.csproj", "{D476199D-526A-4831-866F-790676F8BC37}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rest.Net.Tests", "Rest.Net.Tests\Rest.Net.Tests\Rest.Net.Tests.csproj", "{CBA3A43C-3987-433D-9924-3486E9782E2F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
{D476199D-526A-4831-866F-790676F8BC37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D476199D-526A-4831-866F-790676F8BC37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D476199D-526A-4831-866F-790676F8BC37}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CBA3A43C-3987-433D-9924-3486E9782E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBA3A43C-3987-433D-9924-3486E9782E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBA3A43C-3987-433D-9924-3486E9782E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBA3A43C-3987-433D-9924-3486E9782E2F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Rest.Net/Rest.Net/RouteMatcher.cs b/Rest.Net/Rest.Net/RouteMatcher.cs
index 4f9052f..b1d797d 100644
--- a/Rest.Net/Rest.Net/RouteMatcher.cs
+++ b/Rest.Net/Rest.Net/RouteMatcher.cs
@@ -17,8 +17,8 @@ namespace MontoyaTech.Rest.Net
/// ** = anything from this point forward is accepted
/// {} = route parameter
/// ! = must not match
- /// || = logical or
- /// && = logical and
+ /// | = logical or
+ /// & = logical and
///
/// Note:
/// If route parameter doesn't end with /, then the parameter will be the rest of the url.
@@ -37,6 +37,18 @@ namespace MontoyaTech.Rest.Net
url = url.Trim();
syntax = syntax.Trim();
+ //If the url starts with http or https remove that so we just have the path left.
+ if (url.StartsWith("http://"))
+ {
+ url = url.Substring(7);
+ url = url.Substring(url.IndexOf('/'));
+ }
+ else if (url.StartsWith("https://"))
+ {
+ url = url.Substring(8);
+ url = url.Substring(url.IndexOf('/'));
+ }
+
//Split the url and the syntax into path segments
var urlSegments = url.Split('/').Where(segment => segment.Length > 0).Select(segment => segment.Trim()).ToArray();
var syntaxSegments = syntax.Split('/').Where(segment => segment.Length > 0).Select(segment => segment.Trim()).ToArray();
@@ -47,10 +59,10 @@ namespace MontoyaTech.Rest.Net
//If we have no syntax segments this is not a match.
else if (syntaxSegments.Length == 0)
return false;
-
- //If we have segments and the url does not then this may not be a match.
- if (urlSegments.Length == 0 && syntaxSegments[0] == "**")
+ //If the url has no segments but the syntax is a double wild card then this is a match.
+ else if (urlSegments.Length == 0 && syntaxSegments[0] == "**")
return true;
+ //If the url has no segments but the syntax is a wildcard then this is a match.
else if (urlSegments.Length == 0 && syntaxSegments.Length == 1 && syntaxSegments[0] == "*")
return true;
@@ -64,26 +76,71 @@ namespace MontoyaTech.Rest.Net
{
var syntaxSegment = syntaxSegments[i];
+ var urlSegment = urlSegments[i];
+
+ //If the segments syntax is a double wild card then everything after this is a match.
if (syntaxSegment == "**")
{
return true;
}
- else if (syntaxSegment.StartsWith("{") && syntaxSegment.EndsWith("}") && i + 1 >= syntaxSegments.Length && syntax.EndsWith("/") == false)
+ else
{
- //Special case, syntax ends with a parameter, so recombine the rest of the url and add it as a parameter.
- var key = syntaxSegment.Substring(1, syntaxSegment.Length - 2);
+ var conditions = new List();
var builder = new StringBuilder();
+ for (int c = 0; c < syntaxSegment.Length; c++)
+ {
+ if (syntaxSegment[c] == '|' || syntaxSegment[c] == '&')
+ {
+ conditions.Add(builder.ToString());
+ conditions.Add(syntaxSegment[c].ToString());
+ builder.Clear();
+ }
+ else if (syntaxSegment[c] != ' ')
+ {
+ builder.Append(syntaxSegment[c]);
+ }
+ }
- for (int i2 = i; i2 < urlSegments.Length; i2++)
- builder.Append(urlSegments[i2]).Append(i2 + 1 < urlSegments.Length ? "/" : "");
+ if (builder.Length > 0)
+ conditions.Add(builder.ToString());
- arguments[argumentIndex++] = builder.ToString();
+ bool match = false;
+ foreach (var condition in conditions)
+ {
+ if (condition == "*")
+ {
+ match = true;
+ }
+ else if (condition == "**")
+ {
+ match = true;
+ }
+ else if (condition.StartsWith("!") && condition.Substring(1) != urlSegment)
+ {
+ match = true;
+ }
+ else if (condition.StartsWith("{") && condition.EndsWith("}"))
+ {
+ arguments[argumentIndex++] = urlSegment;
- return true;
- }
- else if (SegmentMatches(urlSegments[i], syntaxSegment, arguments, ref argumentIndex) == false)
- {
- return false;
+ match = true;
+ }
+ else if (condition == "&" && !match)
+ {
+ break;
+ }
+ else if (condition == "|" && match)
+ {
+ break;
+ }
+ else if (condition == urlSegment)
+ {
+ match = true;
+ }
+ }
+
+ if (!match)
+ return false;
}
}
@@ -94,58 +151,5 @@ namespace MontoyaTech.Rest.Net
else
return true;
}
-
- private static bool SegmentMatches(string segment, string syntax, string[] arguments, ref int argumentIndex)
- {
- //Split the syntax into conditions
- string[] conditions = syntax.Split(' ').Where(condition => condition.Length > 0).ToArray();
-
- //Based off the matches, see if the segment matches.
- bool match = false;
- for (int i = 0; i < conditions.Length; i++)
- {
- var condition = conditions[i];
-
- if (condition == "*")
- {
- match = true;
- }
- else if (condition == "**")
- {
- return true;
- }
- else if (condition.StartsWith("!"))
- {
- if (condition.Substring(1) == segment)
- match = false;
- else
- match = true;
- }
- else if (condition.StartsWith("{") && condition.EndsWith("}"))
- {
- var key = condition.Substring(1, condition.Length - 2);
-
- arguments[argumentIndex++] = segment;
-
- match = true;
- }
- else if (condition == "&&")
- {
- if (match == false)
- return false;
- }
- else if (condition == "||")
- {
- if (match)
- return true;
- }
- else if (condition == segment)
- {
- return true;
- }
- }
-
- return match;
- }
}
}