This commit is contained in:
Stef Heyenrath
2026-01-24 09:19:12 +01:00
62 changed files with 677 additions and 272 deletions

View File

@@ -68,7 +68,7 @@ public class ExactObjectMatcher : IObjectMatcher
equals = Equals(Value, input);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals));
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)));
}
/// <inheritdoc />

View File

@@ -8,66 +8,68 @@ namespace WireMock.Matchers.Helpers;
internal static class BodyDataMatchScoreCalculator
{
internal static MatchResult CalculateMatchScore(IBodyData? requestMessage, IMatcher matcher)
private static string _name = nameof(BodyDataMatchScoreCalculator);
internal static MatchResult CalculateMatchScore(IBodyData? bodyData, IMatcher matcher)
{
Guard.NotNull(matcher);
if (requestMessage == null)
if (bodyData == null)
{
return default;
return MatchResult.From(_name);
}
if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher)
{
switch (requestMessage.DetectedBodyType)
switch (bodyData.DetectedBodyType)
{
case BodyType.Json:
case BodyType.String:
case BodyType.FormUrlEncoded:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsString);
return notNullOrEmptyMatcher.IsMatch(bodyData.BodyAsString);
case BodyType.Bytes:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyAsBytes);
return notNullOrEmptyMatcher.IsMatch(bodyData.BodyAsBytes);
default:
return default;
return MatchResult.From(_name);
}
}
if (matcher is ExactObjectMatcher { Value: byte[] } exactObjectMatcher)
{
// If the body is a byte array, try to match.
return exactObjectMatcher.IsMatch(requestMessage.BodyAsBytes);
return exactObjectMatcher.IsMatch(bodyData.BodyAsBytes);
}
// Check if the matcher is a IObjectMatcher
if (matcher is IObjectMatcher objectMatcher)
{
// If the body is a JSON object, try to match.
if (requestMessage.DetectedBodyType == BodyType.Json)
if (bodyData.DetectedBodyType == BodyType.Json)
{
return objectMatcher.IsMatch(requestMessage.BodyAsJson);
return objectMatcher.IsMatch(bodyData.BodyAsJson);
}
// If the body is a byte array, try to match.
if (requestMessage.DetectedBodyType == BodyType.Bytes)
if (bodyData.DetectedBodyType == BodyType.Bytes)
{
return objectMatcher.IsMatch(requestMessage.BodyAsBytes);
return objectMatcher.IsMatch(bodyData.BodyAsBytes);
}
}
// In case the matcher is a IStringMatcher and If body is a Json or a String, use the BodyAsString to match on.
if (matcher is IStringMatcher stringMatcher && requestMessage.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
// In case the matcher is a IStringMatcher and if body is a Json or a String, use the BodyAsString to match on.
if (matcher is IStringMatcher stringMatcher && bodyData.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
{
return stringMatcher.IsMatch(requestMessage.BodyAsString);
return stringMatcher.IsMatch(bodyData.BodyAsString);
}
// In case the matcher is a IProtoBufMatcher, use the BodyAsBytes to match on.
if (matcher is IProtoBufMatcher protoBufMatcher)
{
return protoBufMatcher.IsMatchAsync(requestMessage.BodyAsBytes).GetAwaiter().GetResult();
return protoBufMatcher.IsMatchAsync(bodyData.BodyAsBytes).GetAwaiter().GetResult();
}
return default;
return MatchResult.From(_name);
}
}
}

View File

@@ -36,6 +36,6 @@ internal static class MatchBehaviourHelper
/// <returns>match result</returns>
internal static MatchResult Convert(MatchBehaviour matchBehaviour, MatchResult result)
{
return matchBehaviour == MatchBehaviour.AcceptOnMatch ? result : new MatchResult(Convert(matchBehaviour, result.Score), result.Exception);
return matchBehaviour == MatchBehaviour.AcceptOnMatch ? result : MatchResult.From(result.Name, Convert(matchBehaviour, result.Score), result.Exception);
}
}

View File

@@ -5,13 +5,14 @@ using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Matchers.Request;
namespace WireMock.Matchers;
/// <summary>
/// The MatchResult which contains the score (value between 0.0 - 1.0 of the similarity) and an optional error message.
/// </summary>
public struct MatchResult
public class MatchResult
{
/// <summary>
/// A value between 0.0 - 1.0 of the similarity.
@@ -25,46 +26,55 @@ public struct MatchResult
public Exception? Exception { get; set; }
/// <summary>
/// Create a MatchResult
/// The name or description of the matcher.
/// </summary>
/// <param name="score">A value between 0.0 - 1.0 of the similarity.</param>
/// <param name="exception">The exception in case the matching fails. [Optional]</param>
public MatchResult(double score, Exception? exception = null)
{
Score = score;
Exception = exception;
}
public required string Name { get; set; }
/// <summary>
/// Create a MatchResult
/// The child MatchResults in case of multiple matchers.
/// </summary>
/// <param name="exception">The exception in case the matching fails.</param>
public MatchResult(Exception exception)
{
Exception = Guard.NotNull(exception);
}
/// <summary>
/// Implicitly converts a double to a MatchResult.
/// </summary>
/// <param name="score">The score</param>
public static implicit operator MatchResult(double score)
{
return new MatchResult(score);
}
public MatchResult[]? MatchResults { get; set; }
/// <summary>
/// Is the value a perfect match?
/// </summary>
public bool IsPerfect() => MatchScores.IsPerfect(Score);
/// <summary>
/// Create a MatchResult.
/// </summary>
/// <param name="name">The name or description of the matcher.</param>
/// <param name="score">A value between 0.0 - 1.0 of the similarity.</param>
/// <param name="exception">The exception in case the matching fails. [Optional]</param>
public static MatchResult From(string name, double score = 0, Exception? exception = null)
{
return new MatchResult
{
Name = name,
Score = score,
Exception = exception
};
}
/// <summary>
/// Create a MatchResult from exception.
/// </summary>
/// <param name="name">The name or description of the matcher.</param>
/// <param name="exception">The exception in case the matching fails.</param>
/// <returns>MatchResult</returns>
public static MatchResult From(string name, Exception exception)
{
return From(name, MatchScores.Mismatch, exception);
}
/// <summary>
/// Create a MatchResult from multiple MatchResults.
/// </summary>
/// <param name="name">The name or description of the matcher.</param>
/// <param name="matchResults">A list of MatchResults.</param>
/// <param name="matchOperator">The MatchOperator</param>
/// <returns>MatchResult</returns>
public static MatchResult From(IReadOnlyList<MatchResult> matchResults, MatchOperator matchOperator)
public static MatchResult From(string name, IReadOnlyList<MatchResult> matchResults, MatchOperator matchOperator)
{
Guard.NotNullOrEmpty(matchResults);
@@ -75,6 +85,8 @@ public struct MatchResult
return new MatchResult
{
Name = name,
MatchResults = matchResults.ToArray(),
Score = MatchScores.ToScore(matchResults.Select(r => r.Score).ToArray(), matchOperator),
Exception = matchResults.Select(m => m.Exception).OfType<Exception>().ToArray().ToException()
};
@@ -88,4 +100,19 @@ public struct MatchResult
{
return (Score, Exception);
}
/// <summary>
/// Convert to <see cref="MatchResult"/>.
/// </summary>
public MatchDetail ToMatchDetail()
{
return new MatchDetail
{
Name = Name,
MatcherType = typeof(MatchResult),
Score = Score,
Exception = Exception,
MatchDetails = MatchResults?.Select(mr => mr.ToMatchDetail()).ToArray()
};
}
}

View File

@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using AnyOfTypes;
using WireMock.Extensions;
@@ -53,7 +52,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
break;
}
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)));
}
/// <inheritdoc />
@@ -61,7 +60,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
{
var match = !string.IsNullOrEmpty(input);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)));
}
/// <inheritdoc />

View File

@@ -111,7 +111,7 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
}
}
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />

View File

@@ -73,7 +73,7 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var results = CalculateMatchResults(requestMessage);
var (score, exception) = MatchResult.From(results, MatchOperator).Expand();
var (score, exception) = MatchResult.From(nameof(RequestMessageGraphQLMatcher), results, MatchOperator).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
@@ -86,12 +86,12 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
}
return default;
return MatchResult.From(nameof(RequestMessageGraphQLMatcher));
}
private IReadOnlyList<MatchResult> CalculateMatchResults(IRequestMessage requestMessage)
{
return Matchers == null ? [new MatchResult()] : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
return Matchers == null ? [MatchResult.From(nameof(RequestMessageGraphQLMatcher))] : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
}
private static IMatcher[] CreateMatcherArray(

View File

@@ -23,7 +23,7 @@ public interface IMultiPartRequestBuilder : IHttpVersionBuilder
/// <param name="matchBehaviour">The <see cref="MatchBehaviour"/> to use.</param>
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or);
IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.And);
/// <summary>
/// WithMultiPart: MatchBehaviour and IMatcher[]