diff --git a/src/WireMock.Net/Matchers/RegexMatcher.cs b/src/WireMock.Net/Matchers/RegexMatcher.cs index 5f2d6e57..f4f5dc74 100644 --- a/src/WireMock.Net/Matchers/RegexMatcher.cs +++ b/src/WireMock.Net/Matchers/RegexMatcher.cs @@ -5,6 +5,7 @@ using AnyOfTypes; using JetBrains.Annotations; using WireMock.Extensions; using WireMock.Models; +using WireMock.RegularExpressions; using WireMock.Validation; namespace WireMock.Matchers @@ -17,7 +18,7 @@ namespace WireMock.Matchers public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher { private readonly AnyOf[] _patterns; - private readonly Regex[] _expressions; + private readonly RegexExtended[] _expressions; /// public MatchBehaviour MatchBehaviour { get; } @@ -76,7 +77,7 @@ namespace WireMock.Matchers options |= RegexOptions.IgnoreCase; } - _expressions = patterns.Select(p => new Regex(p.GetPattern(), options)).ToArray(); + _expressions = patterns.Select(p => new RegexExtended(p.GetPattern(), options)).ToArray(); } /// diff --git a/src/WireMock.Net/RegularExpressions/RegexExtended.cs b/src/WireMock.Net/RegularExpressions/RegexExtended.cs new file mode 100644 index 00000000..661459d3 --- /dev/null +++ b/src/WireMock.Net/RegularExpressions/RegexExtended.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using WireMock.Validation; + +namespace WireMock.RegularExpressions +{ + /// + /// Extension to the object, adding support for GUID tokens for matching on. + /// + public class RegexExtended : Regex + { + /// + public RegexExtended(string pattern) : this(pattern, RegexOptions.None) + { + } + + /// + public RegexExtended(string pattern, + RegexOptions options) + : this(pattern, options, Regex.InfiniteMatchTimeout) + { + } + + /// + public RegexExtended(string pattern, + RegexOptions options, + TimeSpan matchTimeout) + : base(ReplaceGuidPattern(pattern), options, matchTimeout) + { + } + + // Dictionary of various Guid tokens with a corresponding regular expression pattern to use instead. + private static readonly Dictionary GuidTokenPatterns = new Dictionary + { + // Lower case format `B` Guid pattern + { @"\guidb", @"(\{[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}\})" }, + // Upper case format `B` Guid pattern + { @"\GUIDB", @"(\{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}\})" }, + // Lower case format `D` Guid pattern + { @"\guidd", "([a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12})" }, + // Upper case format `D` Guid pattern + { @"\GUIDD", "([A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12})" }, + // Lower case format `N` Guid pattern + { @"\guidn", "([a-z0-9]{32})" }, + // Upper case format `N` Guid pattern + { @"\GUIDN", "([A-Z0-9]{32})" }, + // Lower case format `P` Guid pattern + { @"\guidp", @"(\([a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}\))" }, + // Upper case format `P` Guid pattern + { @"\GUIDP", @"(\([A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}\))" }, + // Lower case format `X` Guid pattern + { @"\guidx", @"(\{0x[a-f0-9]{8},0x[a-f0-9]{4},0x[a-f0-9]{4},\{(0x[a-f0-9]{2},){7}(0x[a-f0-9]{2})\}\})" }, + // Upper case format `X` Guid pattern + { @"\GUIDX", @"(\{0x[A-F0-9]{8},0x[A-F0-9]{4},0x[A-F0-9]{4},\{(0x[A-F0-9]{2},){7}(0x[A-F0-9]{2})\}\})" }, + }; + + /// + /// Replaces all instances of valid GUID tokens with the correct regular expression to match. + /// + /// Pattern to replace token for. + private static string ReplaceGuidPattern(string pattern) + { + Check.NotNull(pattern, nameof(pattern)); + foreach (var tokenPattern in GuidTokenPatterns) + { + pattern = pattern.Replace(tokenPattern.Key, tokenPattern.Value); + } + return pattern; + } + } +} diff --git a/test/WireMock.Net.Tests/RegularExpressions/RegexExtendedTests.cs b/test/WireMock.Net.Tests/RegularExpressions/RegexExtendedTests.cs new file mode 100644 index 00000000..8f6b7a98 --- /dev/null +++ b/test/WireMock.Net.Tests/RegularExpressions/RegexExtendedTests.cs @@ -0,0 +1,100 @@ +using System; +using NFluent; +using WireMock.RegularExpressions; +using Xunit; + +namespace WireMock.Net.Tests.RegularExpressions +{ + public class RegexExtendedTests + { + /// + /// Input guid used for testing + /// + public Guid InputGuid { get; } = Guid.NewGuid(); + + [Fact] + public void RegexExtended_GuidB_Pattern() + { + var guidbUpper = @".*\GUIDB.*"; + var guidbLower = @".*\guidb.*"; + + var inputLower = InputGuid.ToString("B"); + var inputUpper = InputGuid.ToString("B").ToUpper(); + var regexLower = new RegexExtended(guidbLower); + var regexUpper = new RegexExtended(guidbUpper); + + Check.That(regexLower.IsMatch(inputLower)).Equals(true); + Check.That(regexLower.IsMatch(inputUpper)).Equals(false); + Check.That(regexUpper.IsMatch(inputUpper)).Equals(true); + Check.That(regexUpper.IsMatch(inputLower)).Equals(false); + } + + [Fact] + public void RegexExtended_GuidD_Pattern() + { + var guiddUpper = @".*\GUIDD.*"; + var guiddLower = @".*\guidd.*"; + + var inputLower = InputGuid.ToString("D"); + var inputUpper = InputGuid.ToString("D").ToUpper(); + var regexLower = new RegexExtended(guiddLower); + var regexUpper = new RegexExtended(guiddUpper); + + Check.That(regexLower.IsMatch(inputLower)).Equals(true); + Check.That(regexLower.IsMatch(inputUpper)).Equals(false); + Check.That(regexUpper.IsMatch(inputUpper)).Equals(true); + Check.That(regexUpper.IsMatch(inputLower)).Equals(false); + } + + [Fact] + public void RegexExtended_GuidN_Pattern() + { + var guidnUpper = @".*\GUIDN.*"; + var guidnLower = @".*\guidn.*"; + + var inputLower = InputGuid.ToString("N"); + var inputUpper = InputGuid.ToString("N").ToUpper(); + var regexLower = new RegexExtended(guidnLower); + var regexUpper = new RegexExtended(guidnUpper); + + Check.That(regexLower.IsMatch(inputLower)).Equals(true); + Check.That(regexLower.IsMatch(inputUpper)).Equals(false); + Check.That(regexUpper.IsMatch(inputUpper)).Equals(true); + Check.That(regexUpper.IsMatch(inputLower)).Equals(false); + } + + [Fact] + public void RegexExtended_GuidP_Pattern() + { + var guidpUpper = @".*\GUIDP.*"; + var guidpLower = @".*\guidp.*"; + + var inputLower = InputGuid.ToString("P"); + var inputUpper = InputGuid.ToString("P").ToUpper(); + var regexLower = new RegexExtended(guidpLower); + var regexUpper = new RegexExtended(guidpUpper); + + Check.That(regexLower.IsMatch(inputLower)).Equals(true); + Check.That(regexLower.IsMatch(inputUpper)).Equals(false); + Check.That(regexUpper.IsMatch(inputUpper)).Equals(true); + Check.That(regexUpper.IsMatch(inputLower)).Equals(false); + } + + [Fact] + public void RegexExtended_GuidX_Pattern() + { + var guidxUpper = @".*\GUIDX.*"; + var guidxLower = @".*\guidx.*"; + + var inputLower = InputGuid.ToString("X"); + var inputUpper = InputGuid.ToString("X").ToUpper().Replace("X", "x"); + var regexLower = new RegexExtended(guidxLower); + var regexUpper = new RegexExtended(guidxUpper); + + Check.That(regexLower.IsMatch(inputLower)).Equals(true); + Check.That(regexLower.IsMatch(inputUpper)).Equals(false); + Check.That(regexUpper.IsMatch(inputUpper)).Equals(true); + Check.That(regexUpper.IsMatch(inputLower)).Equals(false); + } + } +}