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: replaced RequestUriMatcher with UriMatcher #110

Merged
merged 4 commits into from
Sep 7, 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
27 changes: 24 additions & 3 deletions src/MockHttp/Extensions/RequestMatchingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Text;
using MockHttp.Http;
using MockHttp.Matchers;
using MockHttp.Matchers.Patterns;
using static MockHttp.Http.UriExtensions;

namespace MockHttp;

Expand All @@ -14,13 +16,30 @@ namespace MockHttp;
/// </summary>
public static class RequestMatchingExtensions
{
private static bool ContainsWildcard(this string value)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}

#if NETSTANDARD2_0 || NETFRAMEWORK
return value.Contains("*");
#else
return value.Contains('*', StringComparison.InvariantCultureIgnoreCase);
#endif
}

/// <summary>
/// Matches a request by specified <paramref name="requestUri" />.
/// </summary>
/// <param name="builder">The request matching builder instance.</param>
/// <param name="requestUri">The request URI or a URI wildcard.</param>
/// <param name="allowWildcards"><see langword="true" /> to allow wildcards, or <see langword="false" /> if exact matching.</param>
/// <returns>The request matching builder instance.</returns>
public static RequestMatching RequestUri(this RequestMatching builder, string requestUri)
#pragma warning disable CA1054
public static RequestMatching RequestUri(this RequestMatching builder, string requestUri, bool allowWildcards = true)
#pragma warning restore CA1054
{
if (builder is null)
{
Expand All @@ -32,7 +51,9 @@ public static RequestMatching RequestUri(this RequestMatching builder, string re
throw new ArgumentNullException(nameof(requestUri));
}

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

/// <summary>
Expand All @@ -53,7 +74,7 @@ public static RequestMatching RequestUri(this RequestMatching builder, Uri reque
throw new ArgumentNullException(nameof(requestUri));
}

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

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/MockHttp/Http/HttpHeaderEqualityComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
internal sealed class HttpHeaderEqualityComparer : IEqualityComparer<KeyValuePair<string, IEnumerable<string>>>
{
private readonly HttpHeaderMatchType? _matchType;
private readonly PatternMatcher? _valuePatternMatcher;
private readonly IPatternMatcher<string>? _valuePatternMatcher;

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

public HttpHeaderEqualityComparer(PatternMatcher valuePatternMatcher)
public HttpHeaderEqualityComparer(IPatternMatcher<string> valuePatternMatcher)
{
_valuePatternMatcher = valuePatternMatcher ?? throw new ArgumentNullException(nameof(valuePatternMatcher));
}
Expand Down Expand Up @@ -63,7 +63,7 @@
{
string[] headerValues = HttpHeadersCollection.ParseHttpHeaderValue(yValue).ToArray();
return _valuePatternMatcher is null && headerValues.Contains(xValue)
|| (_valuePatternMatcher is not null && headerValues.Any(_valuePatternMatcher.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)
})
))
{
Expand Down
34 changes: 34 additions & 0 deletions src/MockHttp/Http/UriExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace MockHttp.Http;

internal static class UriExtensions
{
private const char UriSegmentDelimiter = '/';
internal static readonly UriKind DotNetRelativeOrAbsolute = Type.GetType("Mono.Runtime") == null ? UriKind.RelativeOrAbsolute : (UriKind)300;

/// <summary>
/// If a relative URI, ensures it starts with a forward slash (/). If not, returns the original <paramref name="uri" />.
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
internal static Uri EnsureIsRooted(this Uri uri)
{
if (uri is null)
{
throw new ArgumentNullException(nameof(uri));
}

if (uri.IsAbsoluteUri)
{
return uri;
}

string relUri = uri.ToString();
if (relUri.Length > 0 && relUri[0] != UriSegmentDelimiter)
{
return new Uri($"{UriSegmentDelimiter}{relUri}", UriKind.Relative);
}

return uri;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
namespace MockHttp.Matchers.Patterns;

internal abstract class PatternMatcher
internal interface IPatternMatcher<in T>
{
/// <summary>
/// Tests if the specified <paramref name="value" /> matches the pattern.
/// </summary>
/// <param name="value">The value to test.</param>
/// <returns>Returns true if the value matches the pattern; otherwise returns false.</returns>
public abstract bool IsMatch(string value);

/// <inheritdoc />
public abstract override string ToString();
bool IsMatch(T value);
}
4 changes: 2 additions & 2 deletions src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace MockHttp.Matchers.Patterns;

internal class RegexPatternMatcher : PatternMatcher
internal class RegexPatternMatcher : IPatternMatcher<string>
{
public RegexPatternMatcher
(
Expand All @@ -12,7 +12,7 @@
#endif
string regex
)
: this(new Regex(regex, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline))

Check warning on line 15 in src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs

View workflow job for this annotation

GitHub Actions / analysis

Pass a timeout to limit the execution time. (https://rules.sonarsource.com/csharp/RSPEC-6444)

Check warning on line 15 in src/MockHttp/Matchers/Patterns/RegexPatternMatcher.cs

View workflow job for this annotation

GitHub Actions / analysis

Pass a timeout to limit the execution time. (https://rules.sonarsource.com/csharp/RSPEC-6444)
{
}

Expand All @@ -24,7 +24,7 @@
internal Regex Regex { get; }

/// <inheritdoc />
public override bool IsMatch(string value)
public virtual bool IsMatch(string value)
{
return Regex.IsMatch(value);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Diagnostics;
using MockHttp.Http;

namespace MockHttp.Matchers.Patterns;

[DebuggerDisplay($"{{{nameof(_originalUri)}}}")]
internal sealed class RelativeOrAbsoluteUriPatternMatcher : IPatternMatcher<Uri>
{
private readonly Uri _originalUri;
private readonly Uri _uri;

public RelativeOrAbsoluteUriPatternMatcher(Uri uri)
{
_originalUri = uri;
_uri = uri.EnsureIsRooted();
}

public bool IsMatch(Uri value)
{
return IsAbsoluteUriMatch(value) || IsRelativeUriMatch(value);
}

private bool IsAbsoluteUriMatch(Uri uri)
{
return _uri.IsAbsoluteUri && uri.Equals(_uri);
}

private bool IsRelativeUriMatch(Uri uri)
{
return !_uri.IsAbsoluteUri
&& uri.IsBaseOf(_uri)
&& uri.ToString().EndsWith(_uri.ToString(), StringComparison.Ordinal);
}
}
19 changes: 19 additions & 0 deletions src/MockHttp/Matchers/Patterns/UriStringPatternMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace MockHttp.Matchers.Patterns;

internal class UriStringPatternMatcher : IPatternMatcher<Uri>
{
private readonly Func<Uri, string> _selector;
private readonly IPatternMatcher<string> _stringPatternMatcher;

public UriStringPatternMatcher(Func<Uri, string> selector, IPatternMatcher<string> stringPatternMatcher)
{
_selector = selector ?? throw new ArgumentNullException(nameof(selector));
_stringPatternMatcher = stringPatternMatcher ?? throw new ArgumentNullException(nameof(stringPatternMatcher));
}

public bool IsMatch(Uri value)
{
string v = _selector(value);
return _stringPatternMatcher.IsMatch(v);
}
}
113 changes: 0 additions & 113 deletions src/MockHttp/Matchers/RequestUriMatcher.cs

This file was deleted.

59 changes: 59 additions & 0 deletions src/MockHttp/Matchers/UriMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Runtime.CompilerServices;
using MockHttp.Matchers.Patterns;
using MockHttp.Responses;

namespace MockHttp.Matchers;

/// <summary>
/// Matches a request by the URI.
/// </summary>
internal class UriMatcher : HttpRequestMatcher
{
private readonly IPatternMatcher<Uri> _patternMatcher;
private readonly string _name;
private readonly string _patternDescription;

/// <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="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,
[CallerMemberName] string? name = null
)
{
_patternMatcher = patternMatcher ?? throw new ArgumentNullException(nameof(patternMatcher));
_patternDescription = patternDescription ?? throw new ArgumentNullException(nameof(patternDescription));

name ??= GetType().Name;
if (name.EndsWith("Matcher", StringComparison.Ordinal))
{
name = name.Remove(name.Length - "Matcher".Length);
}

_name = name;
}

/// <inheritdoc />
public override bool IsMatch(MockHttpRequestContext requestContext)
{
if (requestContext is null)
{
throw new ArgumentNullException(nameof(requestContext));
}

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

/// <inheritdoc />
public override string ToString()
{
return $"{_name}: '{_patternDescription}'";
}
}
Loading
Loading