Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUID Pattern support in RegexMatcher #699

Merged
merged 4 commits into from
Dec 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/WireMock.Net/Matchers/RegexMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using JetBrains.Annotations;
using WireMock.Extensions;
using WireMock.Models;
using WireMock.RegularExpressions;
using WireMock.Validation;

namespace WireMock.Matchers
Expand All @@ -17,7 +18,7 @@ namespace WireMock.Matchers
public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;
private readonly Regex[] _expressions;
private readonly RegexExtended[] _expressions;

/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
Expand Down Expand Up @@ -76,7 +77,7 @@ public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] AnyOf
options |= RegexOptions.IgnoreCase;
}

_expressions = patterns.Select(p => new Regex(p.GetPattern(), options)).ToArray();
_expressions = patterns.Select(p => new RegexExtended(p.GetPattern(), options)).ToArray();
}

/// <inheritdoc cref="IStringMatcher.IsMatch"/>
Expand Down
72 changes: 72 additions & 0 deletions src/WireMock.Net/RegularExpressions/RegexExtended.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using WireMock.Validation;

namespace WireMock.RegularExpressions
{
/// <summary>
/// Extension to the <see cref="Regex"/> object, adding support for GUID tokens for matching on.
/// </summary>
public class RegexExtended : Regex
{
/// <inheritdoc cref="Regex"/>
public RegexExtended(string pattern) : this(pattern, RegexOptions.None)
{
}

/// <inheritdoc cref="Regex"/>
public RegexExtended(string pattern,
RegexOptions options)
: this(pattern, options, Regex.InfiniteMatchTimeout)
{
}

/// <inheritdoc cref="Regex"/>
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<string, string> GuidTokenPatterns = new Dictionary<string, string>
{
// 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})\}\})" },
};

/// <summary>
/// Replaces all instances of valid GUID tokens with the correct regular expression to match.
/// </summary>
/// <param name="pattern">Pattern to replace token for.</param>
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;
}
}
}
100 changes: 100 additions & 0 deletions test/WireMock.Net.Tests/RegularExpressions/RegexExtendedTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using NFluent;
using WireMock.RegularExpressions;
using Xunit;

namespace WireMock.Net.Tests.RegularExpressions
{
public class RegexExtendedTests
{
/// <summary>
/// Input guid used for testing
/// </summary>
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);
}
}
}