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

refactor: add new Pattern type that replaces IPatternMatcher<> to simplify API. #111

Merged
merged 1 commit into from
Sep 8, 2024
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
8 changes: 8 additions & 0 deletions src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="IsExternalInit" Version="1.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="RequiredMemberAttribute" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<!--https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019#use-case-multi-level-merging -->
Expand Down
56 changes: 44 additions & 12 deletions src/MockHttp/Extensions/RequestMatchingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Text;
using MockHttp.Http;
using MockHttp.Matchers;
using MockHttp.Matchers.Patterns;
using MockHttp.Patterns;
using static MockHttp.Http.UriExtensions;

namespace MockHttp;
Expand Down Expand Up @@ -41,18 +41,13 @@ private static bool ContainsWildcard(this string value)
public static RequestMatching RequestUri(this RequestMatching builder, string requestUri, bool allowWildcards = true)
#pragma warning restore CA1054
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

if (requestUri is null)
{
throw new ArgumentNullException(nameof(requestUri));
}

return allowWildcards && requestUri.ContainsWildcard()
? builder.With(new UriMatcher(new UriStringPatternMatcher(uri => uri.ToString(), new WildcardPatternMatcher(requestUri)), requestUri))
? builder.RequestUri(WildcardPattern.Create(requestUri))
: builder.RequestUri(new Uri(requestUri, DotNetRelativeOrAbsolute));
}

Expand All @@ -64,17 +59,54 @@ public static RequestMatching RequestUri(this RequestMatching builder, string re
/// <returns>The request matching builder instance.</returns>
public static RequestMatching RequestUri(this RequestMatching builder, Uri requestUri)
{
if (builder is null)
if (requestUri is null)
{
throw new ArgumentNullException(nameof(builder));
throw new ArgumentNullException(nameof(requestUri));
}

if (requestUri is null)
return builder.RequestUri(GetAbsoluteOrRelativePattern(requestUri));

static Pattern GetAbsoluteOrRelativePattern(Uri pattern)
{
throw new ArgumentNullException(nameof(requestUri));
Uri patternRooted = pattern.EnsureIsRooted();
return new Pattern
{
Value = pattern.ToString(),
IsMatch = input =>
{
var uri = new Uri(input, DotNetRelativeOrAbsolute);
return IsAbsoluteUriMatch(patternRooted, uri) || IsRelativeUriMatch(patternRooted, uri);
}
};

static bool IsAbsoluteUriMatch(Uri pattern, Uri uri)
{
return pattern.IsAbsoluteUri && uri.Equals(pattern);
}

static bool IsRelativeUriMatch(Uri pattern, Uri uri)
{
return !pattern.IsAbsoluteUri
&& uri.IsBaseOf(pattern)
&& uri.ToString().EndsWith(pattern.ToString(), StringComparison.Ordinal);
}
}
}

/// <summary>
/// Matches a request by specified <paramref name="pattern" />.
/// </summary>
/// <param name="builder">The request matching builder instance.</param>
/// <param name="pattern">The pattern that must match the request URI.</param>
/// <returns>The request matching builder instance.</returns>
private static RequestMatching RequestUri(this RequestMatching builder, Pattern pattern)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.With(new UriMatcher(new RelativeOrAbsoluteUriPatternMatcher(requestUri), requestUri.ToString()));
return builder.With(new UriMatcher(pattern));
}

/// <summary>
Expand Down
12 changes: 6 additions & 6 deletions src/MockHttp/Http/HttpHeaderEqualityComparer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.ComponentModel;
using MockHttp.Matchers.Patterns;
using MockHttp.Patterns;

namespace MockHttp.Http;

Expand All @@ -22,7 +22,7 @@
internal sealed class HttpHeaderEqualityComparer : IEqualityComparer<KeyValuePair<string, IEnumerable<string>>>
{
private readonly HttpHeaderMatchType? _matchType;
private readonly IPatternMatcher<string>? _valuePatternMatcher;
private readonly Pattern? _valuePatternMatcher;

public HttpHeaderEqualityComparer(HttpHeaderMatchType matchType)
{
Expand All @@ -34,9 +34,9 @@
_matchType = matchType;
}

public HttpHeaderEqualityComparer(IPatternMatcher<string> valuePatternMatcher)
public HttpHeaderEqualityComparer(Pattern valuePatternMatcher)
{
_valuePatternMatcher = valuePatternMatcher ?? throw new ArgumentNullException(nameof(valuePatternMatcher));
_valuePatternMatcher = valuePatternMatcher;
}

public bool Equals(KeyValuePair<string, IEnumerable<string>> x, KeyValuePair<string, IEnumerable<string>> y)
Expand All @@ -62,8 +62,8 @@
.All(xValue =>
{
string[] headerValues = HttpHeadersCollection.ParseHttpHeaderValue(yValue).ToArray();
return _valuePatternMatcher is null && headerValues.Contains(xValue)
|| (_valuePatternMatcher is not null && headerValues.Any(_valuePatternMatcher.IsMatch));
return !_valuePatternMatcher.HasValue && headerValues.Contains(xValue)
|| (_valuePatternMatcher.HasValue && headerValues.Any(_valuePatternMatcher.Value.IsMatch));

Check warning on line 66 in src/MockHttp/Http/HttpHeaderEqualityComparer.cs

View workflow job for this annotation

GitHub Actions / analysis

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)

Check warning on line 66 in src/MockHttp/Http/HttpHeaderEqualityComparer.cs

View workflow job for this annotation

GitHub Actions / analysis

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)

Check warning on line 66 in src/MockHttp/Http/HttpHeaderEqualityComparer.cs

View workflow job for this annotation

GitHub Actions / analysis

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)
})
))
{
Expand Down
9 changes: 4 additions & 5 deletions src/MockHttp/Matchers/HttpHeadersMatcher.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Net.Http.Headers;
using MockHttp.Http;
using MockHttp.Matchers.Patterns;
using MockHttp.Patterns;
using MockHttp.Responses;

namespace MockHttp.Matchers;
Expand Down Expand Up @@ -41,10 +41,9 @@ public HttpHeadersMatcher(string name, string value, bool allowWildcards = false
throw new ArgumentNullException(nameof(name));
}

WildcardPatternMatcher? patternMatcher = allowWildcards ? new WildcardPatternMatcher(value) : null;
_equalityComparer = patternMatcher is null
? new HttpHeaderEqualityComparer(HttpHeaderMatchType.HeaderNameAndPartialValues)
: new HttpHeaderEqualityComparer(patternMatcher);
_equalityComparer = allowWildcards
? new HttpHeaderEqualityComparer(WildcardPattern.Create(value))
: new HttpHeaderEqualityComparer(HttpHeaderMatchType.HeaderNameAndPartialValues);

Value.Add(name, value);
}
Expand Down
11 changes: 0 additions & 11 deletions src/MockHttp/Matchers/Patterns/IPatternMatcher.cs

This file was deleted.

37 changes: 0 additions & 37 deletions src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs

This file was deleted.

This file was deleted.

19 changes: 0 additions & 19 deletions src/MockHttp/Matchers/Patterns/UriStringPatternMatcher.cs

This file was deleted.

29 changes: 19 additions & 10 deletions src/MockHttp/Matchers/UriMatcher.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices;
using MockHttp.Matchers.Patterns;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using MockHttp.Patterns;
using MockHttp.Responses;

namespace MockHttp.Matchers;
Expand All @@ -9,26 +10,28 @@ namespace MockHttp.Matchers;
/// </summary>
internal class UriMatcher : HttpRequestMatcher
{
private readonly IPatternMatcher<Uri> _patternMatcher;
private readonly string _name;
private readonly Pattern _pattern;
private readonly string _patternDescription;
private readonly Func<Uri, string>? _selectorFn;

/// <summary>
/// Initializes a new instance of the <see cref="UriMatcher" /> class.
/// </summary>
/// <param name="patternMatcher">A matcher implementation that validates the URI.</param>
/// <param name="patternDescription">A description of the pattern.</param>
/// <param name="pattern">The pattern to match.</param>
/// <param name="selector">An expression to extract the part of the URI to match against. If <see langword="null" />, uses the complete URI.</param>
/// <param name="name">The name of this matcher.</param>
/// <exception cref="ArgumentNullException">Thrown when a required argument is <see langword="null" />.</exception>
internal UriMatcher
(
IPatternMatcher<Uri> patternMatcher,
string patternDescription,
Pattern pattern,
Expression<Func<Uri, string>>? selector = null,
[CallerMemberName] string? name = null
)
{
_patternMatcher = patternMatcher ?? throw new ArgumentNullException(nameof(patternMatcher));
_patternDescription = patternDescription ?? throw new ArgumentNullException(nameof(patternDescription));
_pattern = pattern;
_selectorFn = selector?.Compile();
_patternDescription = pattern.Value;

name ??= GetType().Name;
if (name.EndsWith("Matcher", StringComparison.Ordinal))
Expand All @@ -48,7 +51,13 @@ public override bool IsMatch(MockHttpRequestContext requestContext)
}

Uri? uri = requestContext.Request.RequestUri;
return uri is not null && _patternMatcher.IsMatch(uri);
if (uri is null)
{
return false;
}

string value = _selectorFn?.Invoke(uri) ?? uri.ToString();
return _pattern.IsMatch(value);
}

/// <inheritdoc />
Expand Down
7 changes: 7 additions & 0 deletions src/MockHttp/Patterns/IPattern.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MockHttp.Patterns;

internal interface IPattern
{
string Value { get; }
Func<string, bool> IsMatch { get; }
}
22 changes: 22 additions & 0 deletions src/MockHttp/Patterns/Pattern.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace MockHttp.Patterns;

internal readonly record struct Pattern : IPattern
{
public required string Value { get; internal init; }
public required Func<string, bool> IsMatch { get; internal init; }

/// <inheritdoc />
public override string ToString()
{
return Value;
}

public static Pattern MatchExactly(string value)
{
return new Pattern
{
Value = value,
IsMatch = input => StringComparer.Ordinal.Equals(value, input)
};
}
}
Loading
Loading