diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/IUrlPathToUrlPathTemplateMatcher.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/IUrlPathToUrlPathTemplateMatcher.cs index 7a617e13..a5a9e25a 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/IUrlPathToUrlPathTemplateMatcher.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/IUrlPathToUrlPathTemplateMatcher.cs @@ -2,6 +2,6 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher { public interface IUrlPathToUrlPathTemplateMatcher { - bool Match(string urlPath, string urlPathTemplate); + UrlPathMatch Match(string urlPath, string urlPathTemplate); } } \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/TemplateVariableNameAndValue.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/TemplateVariableNameAndValue.cs new file mode 100644 index 00000000..f65ab514 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/TemplateVariableNameAndValue.cs @@ -0,0 +1,13 @@ +namespace Ocelot.Library.Infrastructure.UrlPathMatcher +{ + public class TemplateVariableNameAndValue + { + public TemplateVariableNameAndValue(string templateVariableName, string templateVariableValue) + { + TemplateVariableName = templateVariableName; + TemplateVariableValue = templateVariableValue; + } + public string TemplateVariableName {get;private set;} + public string TemplateVariableValue {get;private set;} + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs new file mode 100644 index 00000000..d783d683 --- /dev/null +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathMatch.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Ocelot.Library.Infrastructure.UrlPathMatcher +{ + public class UrlPathMatch + { + public UrlPathMatch(bool match, List templateVariableNameAndValues) + { + Match = match; + TemplateVariableNameAndValues = templateVariableNameAndValues; + } + public bool Match {get;private set;} + public List TemplateVariableNameAndValues {get;private set;} + } +} \ No newline at end of file diff --git a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs index 04189704..a062e2e5 100644 --- a/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs +++ b/src/Ocelot.Library/Infrastructure/UrlPathMatcher/UrlPathToUrlPathTemplateMatcher.cs @@ -1,9 +1,14 @@ +using System; +using System.Collections.Generic; + namespace Ocelot.Library.Infrastructure.UrlPathMatcher { public class UrlPathToUrlPathTemplateMatcher : IUrlPathToUrlPathTemplateMatcher { - public bool Match(string urlPath, string urlPathTemplate) + public UrlPathMatch Match(string urlPath, string urlPathTemplate) { + var templateKeysAndValues = new List(); + urlPath = urlPath.ToLower(); urlPathTemplate = urlPathTemplate.ToLower(); @@ -16,20 +21,52 @@ namespace Ocelot.Library.Infrastructure.UrlPathMatcher { if (IsPlaceholder(urlPathTemplate[counterForTemplate])) { + var variableName = GetPlaceholderVariableName(urlPathTemplate, counterForTemplate); + + var variableValue = GetPlaceholderVariableValue(urlPath, counterForUrl); + + var templateVariableNameAndValue = new TemplateVariableNameAndValue(variableName, variableValue); + + templateKeysAndValues.Add(templateVariableNameAndValue); + counterForTemplate = GetNextCounterPosition(urlPathTemplate, counterForTemplate, '}'); + counterForUrl = GetNextCounterPosition(urlPath, counterForUrl, '/'); + continue; } else { - return false; + return new UrlPathMatch(false, templateKeysAndValues); } } counterForUrl++; } - return true; + return new UrlPathMatch(true, templateKeysAndValues); } + private string GetPlaceholderVariableValue(string urlPath, int counterForUrl) + { + var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl); + + if(positionOfNextSlash == -1) + { + positionOfNextSlash = urlPath.Length; + } + + var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl); + + return variableValue; + } + + private string GetPlaceholderVariableName(string urlPathTemplate, int counterForTemplate) + { + var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1; + + var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate); + + return variableName; + } private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter) { var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate); diff --git a/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs b/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs index 2101e637..cfc63319 100644 --- a/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs +++ b/test/Ocelot.UnitTests/UrlPathToUrlPathTemplateMatcherTests.cs @@ -1,13 +1,17 @@ +using System.Collections.Generic; +using System.Linq; using Ocelot.Library.Infrastructure.UrlPathMatcher; using Shouldly; using Xunit; - + namespace Ocelot.UnitTests { public class UrlPathToUrlPathTemplateMatcherTests { private IUrlPathToUrlPathTemplateMatcher _urlMapper; - + private string _downstreamPath; + private string _downstreamPathTemplate; + private UrlPathMatch _result; public UrlPathToUrlPathTemplateMatcherTests() { _urlMapper = new UrlPathToUrlPathTemplateMatcher(); @@ -16,64 +20,137 @@ namespace Ocelot.UnitTests [Fact] public void can_match_down_stream_url_with_downstream_template_with_one_query_string_parameter() { - var downstreamUrl = "api/product/products/?soldout=false"; - var downstreamTemplate = "api/product/products/"; - var result = _urlMapper.Match(downstreamUrl, downstreamTemplate); - result.ShouldBeTrue(); + GivenIHaveADownstreamPath("api/product/products/?soldout=false"); + GivenIHaveAnDownstreamPathTemplate("api/product/products/"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(new List()); + } - [Fact] + [Fact] public void can_match_down_stream_url_with_downstream_template_with_one_query_string_parameter_and_one_template() { - var downstreamUrl = "api/product/products/1/variants/?soldout=false"; - var downstreamTemplate = "api/product/products/{productId}/variants/"; - var result = _urlMapper.Match(downstreamUrl, downstreamTemplate); - result.ShouldBeTrue(); + var expectedTemplates = new List + { + new TemplateVariableNameAndValue("{productid}", "1") + }; + + GivenIHaveADownstreamPath("api/product/products/1/variants/?soldout=false"); + GivenIHaveAnDownstreamPathTemplate("api/product/products/{productId}/variants/"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(expectedTemplates); } [Fact] public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() { - var downstreamUrl = "api/product/products/1"; - var downstreamTemplate = "api/product/products/{productId}"; - var result = _urlMapper.Match(downstreamUrl, downstreamTemplate); - result.ShouldBeTrue(); + var expectedTemplates = new List + { + new TemplateVariableNameAndValue("{productid}", "1") + }; + + GivenIHaveADownstreamPath("api/product/products/1"); + GivenIHaveAnDownstreamPathTemplate("api/product/products/{productId}"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(expectedTemplates); } [Fact] public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() { - var downstreamUrl = "api/product/products/1/2"; - var downstreamTemplate = "api/product/products/{productId}/{categoryId}"; - var result = _urlMapper.Match(downstreamUrl, downstreamTemplate); - result.ShouldBeTrue(); + var expectedTemplates = new List + { + new TemplateVariableNameAndValue("{productid}", "1"), + new TemplateVariableNameAndValue("{categoryid}", "2") + }; + + GivenIHaveADownstreamPath("api/product/products/1/2"); + GivenIHaveAnDownstreamPathTemplate("api/product/products/{productId}/{categoryId}"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(expectedTemplates); } [Fact] public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() { - var downstreamUrl = "api/product/products/1/categories/2"; - var downstreamTemplate = "api/product/products/{productId}/categories/{categoryId}"; - var result = _urlMapper.Match(downstreamUrl, downstreamTemplate); - result.ShouldBeTrue(); + var expectedTemplates = new List + { + new TemplateVariableNameAndValue("{productid}", "1"), + new TemplateVariableNameAndValue("{categoryid}", "2") + }; + + GivenIHaveADownstreamPath("api/product/products/1/categories/2"); + GivenIHaveAnDownstreamPathTemplate("api/product/products/{productId}/categories/{categoryId}"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(expectedTemplates); } [Fact] public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() { - var downstreamUrl = "api/product/products/1/categories/2/variant/123"; - var downstreamTemplate = "api/product/products/{productId}/categories/{categoryId}/variant/{variantId}"; - var result = _urlMapper.Match(downstreamUrl, downstreamTemplate); - result.ShouldBeTrue(); + var expectedTemplates = new List + { + new TemplateVariableNameAndValue("{productid}", "1"), + new TemplateVariableNameAndValue("{categoryid}", "2"), + new TemplateVariableNameAndValue("{variantid}", "123") + }; + + GivenIHaveADownstreamPath("api/product/products/1/categories/2/variant/123"); + GivenIHaveAnDownstreamPathTemplate("api/product/products/{productId}/categories/{categoryId}/variant/{variantId}"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(expectedTemplates); } [Fact] public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() { - var downstreamUrl = "api/product/products/1/categories/2/variant/"; - var downstreamTemplate = "api/product/products/{productId}/categories/{categoryId}/variant/"; - var result = _urlMapper.Match(downstreamUrl, downstreamTemplate); - result.ShouldBeTrue(); + var expectedTemplates = new List + { + new TemplateVariableNameAndValue("{productid}", "1"), + new TemplateVariableNameAndValue("{categoryid}", "2") + }; + + GivenIHaveADownstreamPath("api/product/products/1/categories/2/variant/"); + GivenIHaveAnDownstreamPathTemplate("api/product/products/{productId}/categories/{categoryId}/variant/"); + WhenIMatchThePaths(); + ThenTheResultIsTrue(); + ThenTheTemplatesDictionaryIs(expectedTemplates); + } + + private void ThenTheTemplatesDictionaryIs(List expectedResults) + { + foreach (var expectedResult in expectedResults) + { + var result = _result.TemplateVariableNameAndValues + .First(t => t.TemplateVariableName == expectedResult.TemplateVariableName); + result.TemplateVariableValue.ShouldBe(expectedResult.TemplateVariableValue); + } + } + + private void GivenIHaveADownstreamPath(string downstreamPath) + { + _downstreamPath = downstreamPath; + } + + private void GivenIHaveAnDownstreamPathTemplate(string downstreamTemplate) + { + _downstreamPathTemplate = downstreamTemplate; + } + + private void WhenIMatchThePaths() + { + _result = _urlMapper.Match(_downstreamPath, _downstreamPathTemplate); + } + + private void ThenTheResultIsTrue() + { + _result.Match.ShouldBeTrue(); } } } \ No newline at end of file