mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-01 07:03:29 +02:00
Create WireMock.Net.MimePart project (#1300)
* Create WireMock.Net.MimePart project * . * REFACTOR * ILRepack * -- * ... * x * x * . * fix * public class MimePartMatcher * shared * min * . * <!--<DelaySign>true</DelaySign>--> * Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
108
src/WireMock.Net.Minimal/Matchers/AbstractJsonPartialMatcher.cs
Normal file
108
src/WireMock.Net.Minimal/Matchers/AbstractJsonPartialMatcher.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// Generic AbstractJsonPartialMatcher
|
||||
/// </summary>
|
||||
public abstract class AbstractJsonPartialMatcher : JsonMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
protected AbstractJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false) :
|
||||
base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The object value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
protected AbstractJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false) :
|
||||
base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="value">The value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
protected AbstractJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) :
|
||||
base(matchBehaviour, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(JToken value, JToken? input)
|
||||
{
|
||||
if (value == input)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Regex && value.Type == JTokenType.String && input != null)
|
||||
{
|
||||
var valueAsString = value.ToString();
|
||||
|
||||
var (valid, result) = RegexUtils.MatchRegex(valueAsString, input.ToString());
|
||||
if (valid)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (input != null &&
|
||||
((value.Type == JTokenType.Guid && input.Type == JTokenType.String) ||
|
||||
(value.Type == JTokenType.String && input.Type == JTokenType.Guid)))
|
||||
{
|
||||
return IsMatch(value.ToString().ToUpperInvariant(), input.ToString().ToUpperInvariant());
|
||||
}
|
||||
|
||||
if (input == null || value.Type != input.Type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (value.Type)
|
||||
{
|
||||
case JTokenType.Object:
|
||||
var nestedValues = value.ToObject<Dictionary<string, JToken>>();
|
||||
return nestedValues?.Any() != true ||
|
||||
nestedValues.All(pair => IsMatch(pair.Value, input.SelectToken(pair.Key) ?? input[pair.Key])); // First try to select based on JPath expression, else just get the value.
|
||||
|
||||
case JTokenType.Array:
|
||||
var valuesArray = value.ToObject<JToken[]>();
|
||||
var tokenArray = input.ToObject<JToken[]>();
|
||||
|
||||
if (valuesArray?.Any() != true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return tokenArray?.Any() == true &&
|
||||
valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken)));
|
||||
|
||||
default:
|
||||
return IsMatch(value.ToString(), input.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two strings are a match (matching can be done exact or wildcard)
|
||||
/// </summary>
|
||||
protected abstract bool IsMatch(string value, string input);
|
||||
}
|
||||
90
src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs
Normal file
90
src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
using AnyOfTypes;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// ContentTypeMatcher which accepts also all charsets
|
||||
/// </summary>
|
||||
/// <seealso cref="RegexMatcher" />
|
||||
public class ContentTypeMatcher : WildcardMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentTypeMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase (default false)</param>
|
||||
public ContentTypeMatcher(AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this([pattern], ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentTypeMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase (default false)</param>
|
||||
public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(matchBehaviour,
|
||||
[pattern], ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentTypeMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase (default false)</param>
|
||||
public ContentTypeMatcher(AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentTypeMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase (default false)</param>
|
||||
public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false) : base(matchBehaviour, patterns, ignoreCase)
|
||||
{
|
||||
_patterns = Guard.NotNull(patterns);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override MatchResult IsMatch(string? input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType))
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
}
|
||||
|
||||
return base.IsMatch(contentType.MediaType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => nameof(ContentTypeMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
107
src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs
Normal file
107
src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// ExactMatcher
|
||||
/// </summary>
|
||||
/// <seealso cref="IStringMatcher" /> and <seealso cref="IIgnoreCaseMatcher" />
|
||||
public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _values;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="value">The string value.</param>
|
||||
public ExactMatcher(MatchBehaviour matchBehaviour, string value) : this(matchBehaviour, true, MatchOperator.Or, new AnyOf<string, StringPattern>(value))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="values">The values.</param>
|
||||
public ExactMatcher(params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, values)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern(s).</param>
|
||||
/// <param name="values">The values.</param>
|
||||
public ExactMatcher(bool ignoreCase, params AnyOf<string, StringPattern>[] values) : this(MatchBehaviour.AcceptOnMatch, ignoreCase, MatchOperator.Or, values)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExactMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern(s).</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
/// <param name="values">The values.</param>
|
||||
public ExactMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
bool ignoreCase = false,
|
||||
MatchOperator matchOperator = MatchOperator.Or,
|
||||
params AnyOf<string, StringPattern>[] values)
|
||||
{
|
||||
_values = Guard.NotNull(values);
|
||||
|
||||
MatchBehaviour = matchBehaviour;
|
||||
IgnoreCase = ignoreCase;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
Func<string?, bool> equals = IgnoreCase
|
||||
? pattern => string.Equals(pattern, input, StringComparison.OrdinalIgnoreCase)
|
||||
: pattern => pattern == input;
|
||||
|
||||
var score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator);
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _values;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(ExactMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IgnoreCase { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_values)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
179
src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs
Normal file
179
src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// FormUrl Encoded fields Matcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IStringMatcher"/>
|
||||
/// <inheritdoc cref="IIgnoreCaseMatcher"/>
|
||||
public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use. (default = "Or")</param>
|
||||
public FormUrlEncodedMatcher(
|
||||
AnyOf<string, StringPattern> pattern,
|
||||
bool ignoreCase = false,
|
||||
MatchOperator matchOperator = MatchOperator.Or) :
|
||||
this(MatchBehaviour.AcceptOnMatch, [pattern], ignoreCase, matchOperator)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use. (default = "Or")</param>
|
||||
public FormUrlEncodedMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
AnyOf<string, StringPattern> pattern,
|
||||
bool ignoreCase = false,
|
||||
MatchOperator matchOperator = MatchOperator.Or) :
|
||||
this(matchBehaviour, [pattern], ignoreCase, matchOperator)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use. (default = "Or")</param>
|
||||
public FormUrlEncodedMatcher(
|
||||
AnyOf<string, StringPattern>[] patterns,
|
||||
bool ignoreCase = false,
|
||||
MatchOperator matchOperator = MatchOperator.Or) :
|
||||
this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase, matchOperator)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use. (default = "Or")</param>
|
||||
public FormUrlEncodedMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
AnyOf<string, StringPattern>[] patterns,
|
||||
bool ignoreCase = false,
|
||||
MatchOperator matchOperator = MatchOperator.Or)
|
||||
{
|
||||
_patterns = Guard.NotNull(patterns);
|
||||
IgnoreCase = ignoreCase;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
|
||||
foreach (var pattern in _patterns)
|
||||
{
|
||||
if (QueryStringParser.TryParse(pattern, IgnoreCase, out var nameValueCollection))
|
||||
{
|
||||
foreach (var nameValue in nameValueCollection)
|
||||
{
|
||||
var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Key], ignoreCase, MatchOperator);
|
||||
var valueMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Value], ignoreCase, MatchOperator);
|
||||
_pairs.Add((keyMatcher, valueMatcher));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
// Input is null or empty and if no patterns defined, return Perfect match.
|
||||
if (string.IsNullOrEmpty(input) && _patterns.Length == 0)
|
||||
{
|
||||
return new MatchResult(MatchScores.Perfect);
|
||||
}
|
||||
|
||||
if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection))
|
||||
{
|
||||
return new MatchResult(MatchScores.Mismatch);
|
||||
}
|
||||
|
||||
var matches = GetMatches(inputNameValueCollection);
|
||||
|
||||
var score = MatchScores.ToScore(matches, MatchOperator);
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
|
||||
}
|
||||
|
||||
private bool[] GetMatches(IDictionary<string, string> inputNameValueCollection)
|
||||
{
|
||||
var matches = new List<bool>();
|
||||
if (_pairs.Count > inputNameValueCollection.Count)
|
||||
{
|
||||
matches.AddRange(Enumerable.Repeat(false, _pairs.Count - inputNameValueCollection.Count));
|
||||
}
|
||||
|
||||
foreach (var inputKeyValuePair in inputNameValueCollection)
|
||||
{
|
||||
var match = false;
|
||||
foreach (var pair in _pairs)
|
||||
{
|
||||
var keyMatchResult = pair.Key.IsMatch(inputKeyValuePair.Key).IsPerfect();
|
||||
if (keyMatchResult)
|
||||
{
|
||||
match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false;
|
||||
if (match)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches.Add(match);
|
||||
}
|
||||
|
||||
return matches.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Name => nameof(FormUrlEncodedMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IgnoreCase { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
213
src/WireMock.Net.Minimal/Matchers/GraphQLMatcher.cs
Normal file
213
src/WireMock.Net.Minimal/Matchers/GraphQLMatcher.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
#if GRAPHQL
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using GraphQL;
|
||||
using GraphQL.Types;
|
||||
using GraphQLParser;
|
||||
using GraphQLParser.AST;
|
||||
using Newtonsoft.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Exceptions;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers.Models;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// GrapQLMatcher Schema Matcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IStringMatcher"/>
|
||||
public class GraphQLMatcher : IStringMatcher
|
||||
{
|
||||
private sealed class GraphQLRequest
|
||||
{
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Local
|
||||
public string? Query { get; set; }
|
||||
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Local
|
||||
public Dictionary<string, object?>? Variables { get; set; }
|
||||
}
|
||||
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
private readonly ISchema _schema;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional dictionary defining the custom Scalar and the type.
|
||||
/// </summary>
|
||||
public IDictionary<string, Type>? CustomScalars { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GraphQLMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="schema">The schema.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
public GraphQLMatcher(
|
||||
AnyOf<string, StringPattern, ISchema> schema,
|
||||
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
|
||||
MatchOperator matchOperator = MatchOperator.Or
|
||||
) : this(schema, null, matchBehaviour, matchOperator)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GraphQLMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="schema">The schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. (optional)</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
public GraphQLMatcher(
|
||||
AnyOf<string, StringPattern, ISchema> schema,
|
||||
IDictionary<string, Type>? customScalars,
|
||||
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
|
||||
MatchOperator matchOperator = MatchOperator.Or
|
||||
)
|
||||
{
|
||||
Guard.NotNull(schema);
|
||||
CustomScalars = customScalars;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
|
||||
var patterns = new List<AnyOf<string, StringPattern>>();
|
||||
switch (schema.CurrentType)
|
||||
{
|
||||
case AnyOfType.First:
|
||||
patterns.Add(schema.First);
|
||||
_schema = BuildSchema(schema);
|
||||
break;
|
||||
|
||||
case AnyOfType.Second:
|
||||
patterns.Add(schema.Second);
|
||||
_schema = BuildSchema(schema.Second.Pattern);
|
||||
break;
|
||||
|
||||
case AnyOfType.Third:
|
||||
_schema = schema.Third;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
_patterns = patterns.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
|
||||
if (input != null && TryGetGraphQLRequest(input, out var graphQLRequest))
|
||||
{
|
||||
try
|
||||
{
|
||||
var executionResult = new DocumentExecuter().ExecuteAsync(eo =>
|
||||
{
|
||||
eo.ThrowOnUnhandledException = true;
|
||||
|
||||
eo.Schema = _schema;
|
||||
eo.Query = graphQLRequest.Query;
|
||||
|
||||
if (graphQLRequest.Variables != null)
|
||||
{
|
||||
eo.Variables = new Inputs(graphQLRequest.Variables);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
if (executionResult.Errors == null || executionResult.Errors.Count == 0)
|
||||
{
|
||||
score = MatchScores.Perfect;
|
||||
}
|
||||
else
|
||||
{
|
||||
exception = executionResult.Errors.OfType<Exception>().ToArray().ToException();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(GraphQLMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return "NotImplemented";
|
||||
}
|
||||
|
||||
private static bool TryGetGraphQLRequest(string input, [NotNullWhen(true)] out GraphQLRequest? graphQLRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
graphQLRequest = JsonConvert.DeserializeObject<GraphQLRequest>(input);
|
||||
return graphQLRequest != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
graphQLRequest = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="typeDefinitions">A textual description of the schema in SDL (Schema Definition Language) format.</param>
|
||||
private ISchema BuildSchema(string typeDefinitions)
|
||||
{
|
||||
var schema = Schema.For(typeDefinitions);
|
||||
|
||||
// #984
|
||||
var graphTypes = schema.BuiltInTypeMappings.Select(tm => tm.graphType).ToArray();
|
||||
schema.RegisterTypes(graphTypes);
|
||||
|
||||
var doc = Parser.Parse(typeDefinitions);
|
||||
var scalarTypeDefinitions = doc.Definitions
|
||||
.Where(d => d.Kind == ASTNodeKind.ScalarTypeDefinition)
|
||||
.OfType<GraphQLTypeDefinition>();
|
||||
|
||||
foreach (var scalarTypeDefinitionName in scalarTypeDefinitions.Select(s => s.Name.StringValue))
|
||||
{
|
||||
var customScalarGraphTypeName = $"{scalarTypeDefinitionName}GraphType";
|
||||
if (graphTypes.All(t => t.Name != customScalarGraphTypeName)) // Only process when not built-in.
|
||||
{
|
||||
// Check if this custom Scalar is defined in the dictionary
|
||||
if (CustomScalars == null || !CustomScalars.TryGetValue(scalarTypeDefinitionName, out var clrType))
|
||||
{
|
||||
throw new WireMockException($"The GraphQL Scalar type '{scalarTypeDefinitionName}' is not defined in the CustomScalars dictionary.");
|
||||
}
|
||||
|
||||
// Create a this custom Scalar GraphType (extending the WireMockCustomScalarGraphType<{clrType}> class)
|
||||
var customScalarGraphType = ReflectionUtils.CreateGenericType(customScalarGraphTypeName, typeof(WireMockCustomScalarGraphType<>), clrType);
|
||||
schema.RegisterType(customScalarGraphType);
|
||||
}
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
src/WireMock.Net.Minimal/Matchers/IJsonMatcher.cs
Normal file
11
src/WireMock.Net.Minimal/Matchers/IJsonMatcher.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// IJsonMatcher
|
||||
/// <seealso cref="IObjectMatcher"/> and <seealso cref="IIgnoreCaseMatcher"/>.
|
||||
/// </summary>
|
||||
public interface IJsonMatcher : IObjectMatcher, IIgnoreCaseMatcher
|
||||
{
|
||||
}
|
||||
167
src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs
Normal file
167
src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// JsonPathMatcher
|
||||
/// </summary>
|
||||
/// <seealso cref="IStringMatcher" />
|
||||
/// <seealso cref="IObjectMatcher" />
|
||||
public class JsonPathMatcher : IStringMatcher, IObjectMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public JsonPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or,
|
||||
patterns.ToAnyOfPatterns())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public JsonPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch,
|
||||
MatchOperator.Or, patterns)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public JsonPathMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
MatchOperator matchOperator = MatchOperator.Or,
|
||||
params AnyOf<string, StringPattern>[] patterns)
|
||||
{
|
||||
_patterns = Guard.NotNull(patterns);
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
Value = patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
try
|
||||
{
|
||||
var jToken = JToken.Parse(input);
|
||||
score = IsMatch(jToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(object? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
|
||||
// When input is null or byte[], return Mismatch.
|
||||
if (input != null && input is not byte[])
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if JToken or object
|
||||
JToken jToken = input as JToken ?? JObject.FromObject(input);
|
||||
score = IsMatch(jToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(JsonPathMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
private double IsMatch(JToken jToken)
|
||||
{
|
||||
var array = ConvertJTokenToJArrayIfNeeded(jToken);
|
||||
|
||||
// The SelectToken method can accept a string path to a child token ( i.e. "Manufacturers[0].Products[0].Price").
|
||||
// In that case it will return a JValue (some type) which does not implement the IEnumerable interface.
|
||||
var values = _patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray();
|
||||
|
||||
return MatchScores.ToScore(values, MatchOperator);
|
||||
}
|
||||
|
||||
// https://github.com/wiremock/WireMock.Net/issues/965
|
||||
// https://stackoverflow.com/questions/66922188/newtonsoft-jsonpath-with-c-sharp-syntax
|
||||
// Filtering using SelectToken() isn't guaranteed to work for objects inside objects -- only objects inside arrays.
|
||||
// So this code checks if it's an JArray, if it's not an array, construct a new JArray.
|
||||
private static JToken ConvertJTokenToJArrayIfNeeded(JToken jToken)
|
||||
{
|
||||
if (jToken.Count() == 1)
|
||||
{
|
||||
var property = jToken.First();
|
||||
var item = property.First();
|
||||
if (item is JArray)
|
||||
{
|
||||
return jToken;
|
||||
}
|
||||
|
||||
return new JObject
|
||||
{
|
||||
[property.Path] = new JArray(item)
|
||||
};
|
||||
}
|
||||
|
||||
return jToken;
|
||||
}
|
||||
}
|
||||
130
src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs
Normal file
130
src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using DevLab.JmesPath;
|
||||
using Newtonsoft.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// http://jmespath.org/
|
||||
/// </summary>
|
||||
public class JmesPathMatcher : IStringMatcher, IObjectMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Value { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns.ToAnyOfPatterns())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public JmesPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public JmesPathMatcher(MatchOperator matchOperator = MatchOperator.Or, params AnyOf<string, StringPattern>[] patterns) :
|
||||
this(MatchBehaviour.AcceptOnMatch, matchOperator, patterns)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JmesPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public JmesPathMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
MatchOperator matchOperator = MatchOperator.Or,
|
||||
params AnyOf<string, StringPattern>[] patterns)
|
||||
{
|
||||
_patterns = Guard.NotNull(patterns);
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
Value = patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
try
|
||||
{
|
||||
var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray();
|
||||
score = MatchScores.ToScore(results, MatchOperator);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(object? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
|
||||
// When input is null or byte[], return Mismatch.
|
||||
if (input != null && !(input is byte[]))
|
||||
{
|
||||
var inputAsString = JsonConvert.SerializeObject(input);
|
||||
return IsMatch(inputAsString);
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, score);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(JmesPathMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
250
src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs
Normal file
250
src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
using JsonUtils = WireMock.Util.JsonUtils;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// JsonMatcher
|
||||
/// </summary>
|
||||
public class JsonMatcher : IJsonMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual string Name => nameof(JsonMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Value { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
|
||||
public bool IgnoreCase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Support Regex
|
||||
/// </summary>
|
||||
public bool Regex { get; }
|
||||
|
||||
private readonly JToken _valueAsJToken;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
public JsonMatcher(string value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The object value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
public JsonMatcher(object value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="value">The value to check for equality.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
|
||||
/// <param name="regex">Support Regex.</param>
|
||||
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
{
|
||||
Guard.NotNull(value);
|
||||
|
||||
MatchBehaviour = matchBehaviour;
|
||||
IgnoreCase = ignoreCase;
|
||||
Regex = regex;
|
||||
|
||||
Value = value;
|
||||
_valueAsJToken = JsonUtils.ConvertValueToJToken(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(object? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? error = null;
|
||||
|
||||
// When input is null or byte[], return Mismatch.
|
||||
if (input != null && input is not byte[])
|
||||
{
|
||||
try
|
||||
{
|
||||
var inputAsJToken = JsonUtils.ConvertValueToJToken(input);
|
||||
|
||||
var match = IsMatch(RenameJToken(_valueAsJToken), RenameJToken(inputAsJToken));
|
||||
score = MatchScores.ToScore(match);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the input against the matcher value
|
||||
/// </summary>
|
||||
/// <param name="value">Matcher value</param>
|
||||
/// <param name="input">Input value</param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool IsMatch(JToken value, JToken? input)
|
||||
{
|
||||
// If equal, return true.
|
||||
if (input == value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If input is null, return false.
|
||||
if (input == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If using Regex and the value is a string, use the MatchRegex method.
|
||||
if (Regex && value.Type == JTokenType.String)
|
||||
{
|
||||
var valueAsString = value.ToString();
|
||||
|
||||
var (valid, result) = RegexUtils.MatchRegex(valueAsString, input.ToString());
|
||||
if (valid)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// If the value is a Guid and the input is a string, or vice versa, convert them to strings and compare the string values.
|
||||
if ((value.Type == JTokenType.Guid && input.Type == JTokenType.String) || (value.Type == JTokenType.String && input.Type == JTokenType.Guid))
|
||||
{
|
||||
return JToken.DeepEquals(value.ToString().ToUpperInvariant(), input.ToString().ToUpperInvariant());
|
||||
}
|
||||
|
||||
switch (value.Type)
|
||||
{
|
||||
// If the value is an object, compare all properties.
|
||||
case JTokenType.Object:
|
||||
var valueProperties = value.ToObject<Dictionary<string, JToken>>() ?? new Dictionary<string, JToken>();
|
||||
var inputProperties = input.ToObject<Dictionary<string, JToken>>() ?? new Dictionary<string, JToken>();
|
||||
|
||||
// If the number of properties is different, return false.
|
||||
if (valueProperties.Count != inputProperties.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare all properties. The input must match all properties of the value.
|
||||
foreach (var pair in valueProperties)
|
||||
{
|
||||
if (!IsMatch(pair.Value, inputProperties[pair.Key]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
// If the value is an array, compare all elements.
|
||||
case JTokenType.Array:
|
||||
var valueArray = value.ToObject<JToken[]>() ?? [];
|
||||
var inputArray = input.ToObject<JToken[]>() ?? [];
|
||||
|
||||
// If the number of elements is different, return false.
|
||||
if (valueArray.Length != inputArray.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any();
|
||||
|
||||
default:
|
||||
// Use JToken.DeepEquals() for all other types.
|
||||
return JToken.DeepEquals(value, input);
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/11679804/json-net-rename-properties
|
||||
private JToken RenameJToken(JToken input)
|
||||
{
|
||||
if (!IgnoreCase)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
return input switch
|
||||
{
|
||||
JProperty property => RenameJProperty(property),
|
||||
JArray array => RenameJArray(array),
|
||||
JObject obj => RenameJObject(obj),
|
||||
_ => input
|
||||
};
|
||||
}
|
||||
|
||||
private JProperty RenameJProperty(JProperty property)
|
||||
{
|
||||
if (!IgnoreCase)
|
||||
{
|
||||
return property;
|
||||
}
|
||||
|
||||
var propertyValue = property.Value;
|
||||
if (propertyValue.Type == JTokenType.String && !Regex)
|
||||
{
|
||||
var stringValue = propertyValue.Value<string>()!;
|
||||
propertyValue = ToUpper(stringValue);
|
||||
}
|
||||
|
||||
return new JProperty(ToUpper(property.Name)!, RenameJToken(propertyValue));
|
||||
}
|
||||
|
||||
private JArray RenameJArray(JArray array)
|
||||
{
|
||||
if (Regex)
|
||||
{
|
||||
return array;
|
||||
}
|
||||
|
||||
var renamedValues = array.Select(RenameJToken);
|
||||
return new JArray(renamedValues);
|
||||
}
|
||||
|
||||
private JObject RenameJObject(JObject obj)
|
||||
{
|
||||
var renamedProperties = obj.Properties().Select(RenameJProperty);
|
||||
return new JObject(renamedProperties);
|
||||
}
|
||||
|
||||
private static string? ToUpper(string? input)
|
||||
{
|
||||
return input?.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
52
src/WireMock.Net.Minimal/Matchers/JsonPartialMatcher.cs
Normal file
52
src/WireMock.Net.Minimal/Matchers/JsonPartialMatcher.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// JsonPartialMatcher
|
||||
/// </summary>
|
||||
public class JsonPartialMatcher : AbstractJsonPartialMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => nameof(JsonPartialMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public JsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public JsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public JsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(matchBehaviour, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(string value, string input)
|
||||
{
|
||||
var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, IgnoreCase, MatchOperator.Or, value);
|
||||
return exactStringMatcher.IsMatch(input).IsPerfect();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// JsonPartialWildCardMatcher
|
||||
/// </summary>
|
||||
public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => nameof(JsonPartialWildcardMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public JsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public JsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public JsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(matchBehaviour, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(string value, string input)
|
||||
{
|
||||
var wildcardStringMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, IgnoreCase);
|
||||
return wildcardStringMatcher.IsMatch(input).IsPerfect();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
151
src/WireMock.Net.Minimal/Matchers/LinqMatcher.cs
Normal file
151
src/WireMock.Net.Minimal/Matchers/LinqMatcher.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using AnyOfTypes;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Json;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// System.Linq.Dynamic.Core Expression Matcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IObjectMatcher"/>
|
||||
/// <inheritdoc cref="IStringMatcher"/>
|
||||
public class LinqMatcher : IObjectMatcher, IStringMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
public LinqMatcher(AnyOf<string, StringPattern> pattern) : this(new[] { pattern })
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public LinqMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
public LinqMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern> pattern) : this(matchBehaviour, MatchOperator.Or, pattern)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinqMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public LinqMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
MatchOperator matchOperator = MatchOperator.Or,
|
||||
params AnyOf<string, StringPattern>[] patterns)
|
||||
{
|
||||
_patterns = Guard.NotNull(patterns);
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
Value = patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? error = null;
|
||||
|
||||
// Convert a single input string to a Queryable string-list with 1 entry.
|
||||
IQueryable queryable = new[] { input }.AsQueryable();
|
||||
|
||||
try
|
||||
{
|
||||
// Use the Any(...) method to check if the result matches
|
||||
score = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern())).ToArray(), MatchOperator);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
error = e;
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(object? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? error = null;
|
||||
|
||||
JArray jArray;
|
||||
try
|
||||
{
|
||||
jArray = new JArray { input };
|
||||
}
|
||||
catch
|
||||
{
|
||||
jArray = input == null ? new JArray() : new JArray { JToken.FromObject(input) };
|
||||
}
|
||||
|
||||
// Convert a single object to a Queryable JObject-list with 1 entry.
|
||||
var queryable = jArray.ToDynamicClassArray().AsQueryable();
|
||||
|
||||
try
|
||||
{
|
||||
var patternsAsStringArray = _patterns.Select(p => p.GetPattern()).ToArray();
|
||||
var scores = patternsAsStringArray.Select(p => queryable.Any(p)).ToArray();
|
||||
|
||||
score = MatchScores.ToScore(scores, MatchOperator);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
error = e;
|
||||
}
|
||||
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(LinqMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
20
src/WireMock.Net.Minimal/Matchers/MatcherExtensions.cs
Normal file
20
src/WireMock.Net.Minimal/Matchers/MatcherExtensions.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides some extension methods for matchers.
|
||||
/// </summary>
|
||||
public static class MatcherExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if the match result is a perfect match.
|
||||
/// </summary>
|
||||
/// <param name="matcher">The string matcher.</param>
|
||||
/// <param name="input">The input string to match.</param>
|
||||
/// <returns><c>true</c>> if the match is perfect; otherwise, <c>false</c>>.</returns>
|
||||
public static bool IsPerfectMatch(this IStringMatcher matcher, string? input)
|
||||
{
|
||||
return matcher.IsMatch(input).IsPerfect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
#if GRAPHQL
|
||||
using System;
|
||||
using GraphQL.Types;
|
||||
|
||||
namespace WireMock.Matchers.Models;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract class WireMockCustomScalarGraphType<T> : ScalarGraphType
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override object? ParseValue(object? value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case null:
|
||||
return null;
|
||||
|
||||
case T:
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value is string && typeof(T) != typeof(string))
|
||||
{
|
||||
throw new InvalidCastException($"Unable to convert value '{value}' of type '{typeof(string)}' to type '{typeof(T)}'.");
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
126
src/WireMock.Net.Minimal/Matchers/ProtoBufMatcher.cs
Normal file
126
src/WireMock.Net.Minimal/Matchers/ProtoBufMatcher.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
#if PROTOBUF
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ProtoBufJsonConverter;
|
||||
using ProtoBufJsonConverter.Models;
|
||||
using Stef.Validation;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// Grpc ProtoBuf Matcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IProtoBufMatcher"/>
|
||||
public class ProtoBufMatcher : IProtoBufMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(ProtoBufMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Func to define the proto definition as id or texts.
|
||||
/// </summary>
|
||||
public Func<IdOrTexts> ProtoDefinition { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".
|
||||
/// </summary>
|
||||
public string MessageType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Matcher to use (optional).
|
||||
/// </summary>
|
||||
public IObjectMatcher? Matcher { get; }
|
||||
|
||||
private static readonly Converter ProtoBufToJsonConverter = SingletonFactory<Converter>.GetInstance();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProtoBufMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="protoDefinition">The proto definition as id or text.</param>
|
||||
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <param name="matcher">The optional jsonMatcher to use to match the ProtoBuf as (json) object.</param>
|
||||
public ProtoBufMatcher(
|
||||
Func<IdOrTexts> protoDefinition,
|
||||
string messageType,
|
||||
MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch,
|
||||
IObjectMatcher? matcher = null
|
||||
)
|
||||
{
|
||||
ProtoDefinition = Guard.NotNull(protoDefinition);
|
||||
MessageType = Guard.NotNullOrWhiteSpace(messageType);
|
||||
Matcher = matcher;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<MatchResult> IsMatchAsync(byte[]? input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = new MatchResult();
|
||||
|
||||
if (input != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var instance = await DecodeAsync(input, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
result = Matcher?.IsMatch(instance) ?? new MatchResult(MatchScores.Perfect);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result = new MatchResult(MatchScores.Mismatch, e);
|
||||
}
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<object?> DecodeAsync(byte[]? input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return DecodeAsync(input, false, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return "NotImplemented";
|
||||
}
|
||||
|
||||
private async Task<object?> DecodeAsync(byte[]? input, bool throwException, CancellationToken cancellationToken)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var protoDefinitions = ProtoDefinition().Texts;
|
||||
|
||||
var resolver = new WireMockProtoFileResolver(protoDefinitions);
|
||||
var request = new ConvertToObjectRequest(protoDefinitions[0], MessageType, input)
|
||||
.WithProtoFileResolver(resolver);
|
||||
|
||||
try
|
||||
{
|
||||
return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (throwException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// CompositeMatcherType
|
||||
/// </summary>
|
||||
public enum CompositeMatcherType
|
||||
{
|
||||
/// <summary>
|
||||
/// And
|
||||
/// </summary>
|
||||
And = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Or
|
||||
/// </summary>
|
||||
Or = 1
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// RequestMatchResult
|
||||
/// </summary>
|
||||
public class RequestMatchResult : IRequestMatchResult
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public double TotalScore => MatchDetails.Sum(md => md.Score);
|
||||
|
||||
/// <inheritdoc />
|
||||
public int TotalNumber => MatchDetails.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPerfectMatch => Math.Abs(TotalScore - TotalNumber) < MatchScores.Tolerance;
|
||||
|
||||
/// <inheritdoc />
|
||||
public double AverageTotalScore => TotalNumber == 0 ? MatchScores.Mismatch : TotalScore / TotalNumber;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IList<MatchDetail> MatchDetails { get; } = new List<MatchDetail>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public double AddScore(Type matcherType, double score, Exception? exception)
|
||||
{
|
||||
MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score, Exception = exception });
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
|
||||
/// </summary>
|
||||
/// <param name="obj">An object to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="obj" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="obj" />. Greater than zero This instance follows <paramref name="obj" /> in the sort order.
|
||||
/// </returns>
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var compareObj = (RequestMatchResult)obj;
|
||||
|
||||
var averageTotalScoreResult = compareObj.AverageTotalScore.CompareTo(AverageTotalScore);
|
||||
|
||||
// In case the score is equal, prefer the one with the most matchers.
|
||||
return averageTotalScoreResult == 0 ? compareObj.TotalNumber.CompareTo(TotalNumber) : averageTotalScoreResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Matchers.Helpers;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request body matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageBodyMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The body function
|
||||
/// </summary>
|
||||
public Func<string?, bool>? Func { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for byte[]
|
||||
/// </summary>
|
||||
public Func<byte[]?, bool>? DataFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for json
|
||||
/// </summary>
|
||||
public Func<object?, bool>? JsonFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for BodyData
|
||||
/// </summary>
|
||||
public Func<IBodyData?, bool>? BodyDataFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The body data function for FormUrlEncoded
|
||||
/// </summary>
|
||||
public Func<IDictionary<string, string>?, bool>? FormUrlEncodedFunc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The matchers.
|
||||
/// </summary>
|
||||
public IMatcher[]? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="body">The body.</param>
|
||||
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, string body) :
|
||||
this(new[] { new WildcardMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="body">The body.</param>
|
||||
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, byte[] body) :
|
||||
this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="body">The body.</param>
|
||||
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, object body) :
|
||||
this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<string?, bool> func)
|
||||
{
|
||||
Func = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<byte[]?, bool> func)
|
||||
{
|
||||
DataFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<object?, bool> func)
|
||||
{
|
||||
JsonFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<IBodyData?, bool> func)
|
||||
{
|
||||
BodyDataFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
|
||||
{
|
||||
FormUrlEncodedFunc = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
public RequestMessageBodyMatcher(params IMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
public RequestMessageBodyMatcher(MatchOperator matchOperator, params IMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = CalculateMatchScore(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult CalculateMatchScore(IRequestMessage requestMessage)
|
||||
{
|
||||
if (Matchers != null && Matchers.Any())
|
||||
{
|
||||
var results = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray();
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
}
|
||||
|
||||
if (Func != null)
|
||||
{
|
||||
return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString));
|
||||
}
|
||||
|
||||
if (FormUrlEncodedFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded));
|
||||
}
|
||||
|
||||
if (JsonFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson));
|
||||
}
|
||||
|
||||
if (DataFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(DataFunc(requestMessage.BodyData?.BodyAsBytes));
|
||||
}
|
||||
|
||||
if (BodyDataFunc != null)
|
||||
{
|
||||
return MatchScores.ToScore(BodyDataFunc(requestMessage.BodyData));
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Stef.Validation;
|
||||
using WireMock.Models;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request clientIP matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageClientIPMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The matchers
|
||||
/// </summary>
|
||||
public IReadOnlyList<IStringMatcher>? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The clientIP functions
|
||||
/// </summary>
|
||||
public Func<string, bool>[]? Funcs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchBehaviour"/>
|
||||
/// </summary>
|
||||
public MatchBehaviour Behaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageClientIPMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <param name="clientIPs">The clientIPs.</param>
|
||||
public RequestMessageClientIPMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
MatchOperator matchOperator,
|
||||
params string[] clientIPs) :
|
||||
this(matchBehaviour, matchOperator, clientIPs
|
||||
.Select(clientIP => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { clientIP }, false, matchOperator))
|
||||
.Cast<IStringMatcher>().ToArray())
|
||||
{
|
||||
Behaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageClientIPMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
public RequestMessageClientIPMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IStringMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
Behaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageClientIPMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="funcs">The clientIP functions.</param>
|
||||
public RequestMessageClientIPMatcher(params Func<string, bool>[] funcs)
|
||||
{
|
||||
Funcs = Guard.NotNull(funcs);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
{
|
||||
if (Matchers != null)
|
||||
{
|
||||
var results = Matchers.Select(m => m.IsMatch(requestMessage.ClientIP)).ToArray();
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
}
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
var results = Funcs.Select(func => func(requestMessage.ClientIP)).ToArray();
|
||||
return MatchScores.ToScore(results, MatchOperator);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The composite request matcher.
|
||||
/// </summary>
|
||||
public abstract class RequestMessageCompositeMatcher : IRequestMatcher
|
||||
{
|
||||
private readonly CompositeMatcherType _type;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request matchers.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The request matchers.
|
||||
/// </value>
|
||||
private IEnumerable<IRequestMatcher> RequestMatchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requestMatchers">The request matchers.</param>
|
||||
/// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param>
|
||||
protected RequestMessageCompositeMatcher(IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
|
||||
{
|
||||
RequestMatchers = Guard.NotNull(requestMatchers);
|
||||
_type = type;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
if (!RequestMatchers.Any())
|
||||
{
|
||||
return MatchScores.Mismatch;
|
||||
}
|
||||
|
||||
if (_type == CompositeMatcherType.And)
|
||||
{
|
||||
return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
|
||||
}
|
||||
|
||||
return RequestMatchers.Max(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using Stef.Validation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request cookie matcher.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IRequestMatcher"/>
|
||||
public class RequestMessageCookieMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// MatchBehaviour
|
||||
/// </summary>
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// IgnoreCase
|
||||
/// </summary>
|
||||
public bool IgnoreCase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The functions
|
||||
/// </summary>
|
||||
public Func<IDictionary<string, string>, bool>[]? Funcs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <value>
|
||||
/// The matchers.
|
||||
/// </value>
|
||||
public IStringMatcher[]? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, string pattern, bool ignoreCase)
|
||||
{
|
||||
MatchBehaviour = matchBehaviour;
|
||||
IgnoreCase = ignoreCase;
|
||||
Name = Guard.NotNull(name);
|
||||
Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, Guard.NotNull(pattern), ignoreCase) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, bool ignoreCase, params string[] patterns) :
|
||||
this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast<IStringMatcher>().ToArray())
|
||||
{
|
||||
Guard.NotNull(patterns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, string name, bool ignoreCase, params IStringMatcher[] matchers)
|
||||
{
|
||||
MatchBehaviour = matchBehaviour;
|
||||
Name = Guard.NotNull(name);
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
IgnoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageCookieMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="funcs">The funcs.</param>
|
||||
public RequestMessageCookieMatcher(params Func<IDictionary<string, string>, bool>[] funcs)
|
||||
{
|
||||
Guard.NotNull(funcs);
|
||||
|
||||
Funcs = funcs;
|
||||
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage.Cookies == null)
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
}
|
||||
|
||||
// Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value
|
||||
var cookies = !IgnoreCase ? requestMessage.Cookies : new Dictionary<string, string>(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
return MatchScores.ToScore(Funcs.Any(f => f(cookies)));
|
||||
}
|
||||
|
||||
if (Matchers == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (!cookies.ContainsKey(Name))
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
}
|
||||
|
||||
return Matchers.Max(m => m.IsMatch(cookies[Name]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Types;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request body GraphQL matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageGraphQLMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The matchers.
|
||||
/// </summary>
|
||||
public IMatcher[]? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="schema">The schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. [optional]</param>
|
||||
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, string schema, IDictionary<string, Type>? customScalars = null) :
|
||||
this(CreateMatcherArray(matchBehaviour, schema, customScalars))
|
||||
{
|
||||
}
|
||||
|
||||
#if GRAPHQL
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="schema">The schema.</param>
|
||||
/// <param name="customScalars">A dictionary defining the custom scalars used in this schema. [optional]</param>
|
||||
public RequestMessageGraphQLMatcher(MatchBehaviour matchBehaviour, GraphQL.Types.ISchema schema, IDictionary<string, Type>? customScalars = null) :
|
||||
this(CreateMatcherArray(matchBehaviour, new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern, GraphQL.Types.ISchema>(schema), customScalars))
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
public RequestMessageGraphQLMatcher(params IMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageGraphQLMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
public RequestMessageGraphQLMatcher(MatchOperator matchOperator, params IMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var results = CalculateMatchResults(requestMessage);
|
||||
var (score, exception) = MatchResult.From(results, MatchOperator).Expand();
|
||||
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private static MatchResult CalculateMatchResult(IRequestMessage requestMessage, IMatcher matcher)
|
||||
{
|
||||
// In case the matcher is a IStringMatcher and the body is a Json or a String, use the BodyAsString to match on.
|
||||
if (matcher is IStringMatcher stringMatcher && requestMessage.BodyData?.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
|
||||
{
|
||||
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private IReadOnlyList<MatchResult> CalculateMatchResults(IRequestMessage requestMessage)
|
||||
{
|
||||
return Matchers == null ? new[] { new MatchResult() } : Matchers.Select(matcher => CalculateMatchResult(requestMessage, matcher)).ToArray();
|
||||
}
|
||||
|
||||
#if GRAPHQL
|
||||
private static IMatcher[] CreateMatcherArray(
|
||||
MatchBehaviour matchBehaviour,
|
||||
AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern, GraphQL.Types.ISchema> schema,
|
||||
IDictionary<string, Type>? customScalars
|
||||
)
|
||||
{
|
||||
return new[] { new GraphQLMatcher(schema, customScalars, matchBehaviour) }.Cast<IMatcher>().ToArray();
|
||||
}
|
||||
#else
|
||||
private static IMatcher[] CreateMatcherArray(MatchBehaviour matchBehaviour, object schema, IDictionary<string, Type>? customScalars)
|
||||
{
|
||||
throw new System.NotSupportedException("The GrapQLMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Types;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request header matcher.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IRequestMatcher"/>
|
||||
public class RequestMessageHeaderMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// MatchBehaviour
|
||||
/// </summary>
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// IgnoreCase
|
||||
/// </summary>
|
||||
public bool IgnoreCase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The functions
|
||||
/// </summary>
|
||||
public Func<IDictionary<string, string[]>, bool>[]? Funcs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <value>
|
||||
/// The matchers.
|
||||
/// </value>
|
||||
public IStringMatcher[]? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, string name, string pattern, bool ignoreCase)
|
||||
{
|
||||
Guard.NotNull(name);
|
||||
Guard.NotNull(pattern);
|
||||
|
||||
MatchBehaviour = matchBehaviour;
|
||||
IgnoreCase = ignoreCase;
|
||||
Name = name;
|
||||
Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, string name, bool ignoreCase, params string[] patterns) :
|
||||
this(matchBehaviour, matchOperator, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast<IStringMatcher>().ToArray())
|
||||
{
|
||||
Guard.NotNull(patterns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
public RequestMessageHeaderMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, string name, bool ignoreCase, params IStringMatcher[] matchers)
|
||||
{
|
||||
Guard.NotNull(name);
|
||||
Guard.NotNull(matchers);
|
||||
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
Name = name;
|
||||
Matchers = matchers;
|
||||
IgnoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageHeaderMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="funcs">The funcs.</param>
|
||||
public RequestMessageHeaderMatcher(params Func<IDictionary<string, string[]>, bool>[] funcs)
|
||||
{
|
||||
Funcs = Guard.NotNull(funcs);
|
||||
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage.Headers == null)
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
}
|
||||
|
||||
// Check if we want to use IgnoreCase to compare the Header-Name and Header-Value(s)
|
||||
var headers = !IgnoreCase ? requestMessage.Headers : new Dictionary<string, WireMockList<string>>(requestMessage.Headers, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
var funcResults = Funcs.Select(f => f(headers.ToDictionary(entry => entry.Key, entry => entry.Value.ToArray()))).ToArray();
|
||||
return MatchScores.ToScore(funcResults, MatchOperator);
|
||||
}
|
||||
|
||||
if (Matchers != null)
|
||||
{
|
||||
if (!headers.ContainsKey(Name))
|
||||
{
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
}
|
||||
|
||||
var results = new List<MatchResult>();
|
||||
foreach (var matcher in Matchers)
|
||||
{
|
||||
var resultsPerMatcher = headers[Name].Select(matcher.IsMatch).ToArray();
|
||||
|
||||
results.Add(MatchResult.From(resultsPerMatcher, MatchOperator.And));
|
||||
}
|
||||
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request HTTP Version matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageHttpVersionMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The matcher.
|
||||
/// </summary>
|
||||
public IStringMatcher? Matcher { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The func.
|
||||
/// </summary>
|
||||
public Func<string, bool>? Func { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchBehaviour"/>
|
||||
/// </summary>
|
||||
public MatchBehaviour Behaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP Version
|
||||
/// </summary>
|
||||
public string? HttpVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageHttpVersionMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="httpVersion">The HTTP Version.</param>
|
||||
public RequestMessageHttpVersionMatcher(MatchBehaviour matchBehaviour, string httpVersion) :
|
||||
this(matchBehaviour, new ExactMatcher(matchBehaviour, httpVersion))
|
||||
{
|
||||
HttpVersion = httpVersion;
|
||||
Behaviour = matchBehaviour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageClientIPMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matcher">The matcher.</param>
|
||||
public RequestMessageHttpVersionMatcher(MatchBehaviour matchBehaviour, IStringMatcher matcher)
|
||||
{
|
||||
Matcher = Guard.NotNull(matcher);
|
||||
Behaviour = matchBehaviour;
|
||||
HttpVersion = matcher.GetPatterns().FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageClientIPMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="func">The function.</param>
|
||||
public RequestMessageHttpVersionMatcher(Func<string, bool> func)
|
||||
{
|
||||
Func = Guard.NotNull(func);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
{
|
||||
if (Matcher != null)
|
||||
{
|
||||
return Matcher.IsMatch(requestMessage.HttpVersion);
|
||||
}
|
||||
|
||||
if (Func != null)
|
||||
{
|
||||
return MatchScores.ToScore(Func(requestMessage.HttpVersion));
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request method matcher.
|
||||
/// </summary>
|
||||
internal class RequestMessageMethodMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Matchers.MatchBehaviour"/>
|
||||
/// </summary>
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Matchers.MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The methods
|
||||
/// </summary>
|
||||
public string[] Methods { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageMethodMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="methods">The methods.</param>
|
||||
public RequestMessageMethodMatcher(params string[] methods) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, methods)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageMethodMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use.</param>
|
||||
/// <param name="methods">The methods.</param>
|
||||
public RequestMessageMethodMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params string[] methods)
|
||||
{
|
||||
Methods = Guard.NotNull(methods);
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var scores = Methods.Select(m => string.Equals(m, requestMessage.Method, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
var score = MatchScores.ToScore(scores, MatchOperator);
|
||||
return requestMatchResult.AddScore(GetType(), score, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request body MultiPart matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageMultiPartMatcher : IRequestMatcher
|
||||
{
|
||||
private static readonly IMimeKitUtils MimeKitUtils = TypeLoader.LoadStaticInstance<IMimeKitUtils>();
|
||||
|
||||
/// <summary>
|
||||
/// The matchers.
|
||||
/// </summary>
|
||||
public IMatcher[]? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchBehaviour"/>
|
||||
/// </summary>
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageMultiPartMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
public RequestMessageMultiPartMatcher(params IMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageMultiPartMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
public RequestMessageMultiPartMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
Exception? exception = null;
|
||||
|
||||
if (Matchers?.Any() != true)
|
||||
{
|
||||
return requestMatchResult.AddScore(GetType(), score, null);
|
||||
}
|
||||
|
||||
if (!MimeKitUtils.TryGetMimeMessage(requestMessage, out var message))
|
||||
{
|
||||
return requestMatchResult.AddScore(GetType(), score, null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var mimePartMatcher in Matchers.OfType<IMimePartMatcher>().ToArray())
|
||||
{
|
||||
score = MatchScores.Mismatch;
|
||||
foreach (var mimeBodyPart in MimeKitUtils.GetBodyParts(message))
|
||||
{
|
||||
var matchResult = mimePartMatcher.IsMatch(mimeBodyPart);
|
||||
if (matchResult.IsPerfect())
|
||||
{
|
||||
score = MatchScores.Perfect;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((MatchOperator == MatchOperator.Or && MatchScores.IsPerfect(score)) || (MatchOperator == MatchOperator.And && !MatchScores.IsPerfect(score)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Types;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request parameters matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageParamMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// MatchBehaviour
|
||||
/// </summary>
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The funcs
|
||||
/// </summary>
|
||||
public Func<IDictionary<string, WireMockList<string>>, bool>[]? Funcs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The key
|
||||
/// </summary>
|
||||
public string Key { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the key should be matched using case-ignore.
|
||||
/// </summary>
|
||||
public bool IgnoreCase { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The matchers.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IStringMatcher>? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageParamMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
|
||||
public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase) : this(matchBehaviour, key, ignoreCase, (IStringMatcher[]?)null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageParamMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, params string[]? values) :
|
||||
this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, ignoreCase, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageParamMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, params IStringMatcher[]? matchers)
|
||||
{
|
||||
MatchBehaviour = matchBehaviour;
|
||||
Key = Guard.NotNull(key);
|
||||
IgnoreCase = ignoreCase;
|
||||
Matchers = matchers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageParamMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="funcs">The funcs.</param>
|
||||
public RequestMessageParamMatcher(params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
|
||||
{
|
||||
Funcs = Guard.NotNull(funcs);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var score = GetMatchScore(requestMessage);
|
||||
return requestMatchResult.AddScore(GetType(), MatchBehaviourHelper.Convert(MatchBehaviour, score), null);
|
||||
}
|
||||
|
||||
private double GetMatchScore(IRequestMessage requestMessage)
|
||||
{
|
||||
if (Funcs != null)
|
||||
{
|
||||
return MatchScores.ToScore(requestMessage.Query != null && Funcs.Any(f => f(requestMessage.Query)));
|
||||
}
|
||||
|
||||
var valuesPresentInRequestMessage = ((RequestMessage)requestMessage).GetParameter(Key, IgnoreCase);
|
||||
if (valuesPresentInRequestMessage == null)
|
||||
{
|
||||
// Key is not present at all, just return Mismatch
|
||||
return MatchScores.Mismatch;
|
||||
}
|
||||
|
||||
if (Matchers == null || !Matchers.Any())
|
||||
{
|
||||
// Matchers are null or not defined, and Key is present, just return Perfect.
|
||||
return MatchScores.Perfect;
|
||||
}
|
||||
|
||||
// Return the score based on Matchers and valuesPresentInRequestMessage
|
||||
return CalculateScore(Matchers, valuesPresentInRequestMessage);
|
||||
}
|
||||
|
||||
private static double CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
|
||||
{
|
||||
var total = new List<double>();
|
||||
|
||||
// If the total patterns in all matchers > values in message, use the matcher as base
|
||||
if (matchers.Sum(m => m.GetPatterns().Length) > valuesPresentInRequestMessage.Count)
|
||||
{
|
||||
foreach (var matcher in matchers)
|
||||
{
|
||||
double score = 0d;
|
||||
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
|
||||
{
|
||||
score += matcher.IsMatch(valuePresentInRequestMessage).Score / matcher.GetPatterns().Length;
|
||||
}
|
||||
|
||||
total.Add(score);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
|
||||
{
|
||||
var score = matchers.Max(m => m.IsMatch(valuePresentInRequestMessage).Score);
|
||||
total.Add(score);
|
||||
}
|
||||
}
|
||||
|
||||
return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Stef.Validation;
|
||||
using WireMock.Models;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request path matcher.
|
||||
/// </summary>
|
||||
public class RequestMessagePathMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The matchers
|
||||
/// </summary>
|
||||
public IReadOnlyList<IStringMatcher>? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The path functions
|
||||
/// </summary>
|
||||
public Func<string, bool>[]? Funcs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchBehaviour"/>
|
||||
/// </summary>
|
||||
public MatchBehaviour Behaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <param name="paths">The paths.</param>
|
||||
public RequestMessagePathMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
MatchOperator matchOperator,
|
||||
params string[] paths) :
|
||||
this(matchBehaviour, matchOperator, paths
|
||||
.Select(path => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { path }, false, matchOperator))
|
||||
.Cast<IStringMatcher>().ToArray())
|
||||
{
|
||||
Behaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
public RequestMessagePathMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IStringMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
Behaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessagePathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="funcs">The path functions.</param>
|
||||
public RequestMessagePathMatcher(params Func<string, bool>[] funcs)
|
||||
{
|
||||
Funcs = Guard.NotNull(funcs);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
{
|
||||
if (Matchers != null)
|
||||
{
|
||||
var results = Matchers.Select(m => m.IsMatch(requestMessage.Path)).ToArray();
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
}
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
var results = Funcs.Select(func => func(requestMessage.Path)).ToArray();
|
||||
return MatchScores.ToScore(results, MatchOperator);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using WireMock.Models;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request body Grpc ProtoBuf matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageProtoBufMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The ProtoBufMatcher.
|
||||
/// </summary>
|
||||
public IProtoBufMatcher? Matcher { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageProtoBufMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour. (default = "AcceptOnMatch")</param>
|
||||
/// <param name="protoDefinition">The Func to define the proto definitions as id or text.</param>
|
||||
/// <param name="messageType">The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}".</param>
|
||||
/// <param name="matcher">The optional matcher to use to match the ProtoBuf as (json) object.</param>
|
||||
public RequestMessageProtoBufMatcher(MatchBehaviour matchBehaviour, Func<IdOrTexts> protoDefinition, string messageType, IObjectMatcher? matcher = null)
|
||||
{
|
||||
#if PROTOBUF
|
||||
Matcher = new ProtoBufMatcher(protoDefinition, messageType, matchBehaviour, matcher);
|
||||
#else
|
||||
throw new System.NotSupportedException("The ProtoBufMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower.");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
{
|
||||
return Matcher?.IsMatchAsync(requestMessage.BodyAsBytes).GetAwaiter().GetResult() ?? default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The scenario and state matcher.
|
||||
/// </summary>
|
||||
internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Execution state condition for the current mapping.
|
||||
/// </summary>
|
||||
private readonly string? _executionConditionState;
|
||||
|
||||
/// <summary>
|
||||
/// The next state which will be signaled after the current mapping execution.
|
||||
/// In case the value is null state will not be changed.
|
||||
/// </summary>
|
||||
private readonly string? _nextState;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageScenarioAndStateMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="nextState">The next state.</param>
|
||||
/// <param name="executionConditionState">Execution state condition for the current mapping.</param>
|
||||
public RequestMessageScenarioAndStateMatcher(string? nextState, string? executionConditionState)
|
||||
{
|
||||
_nextState = nextState;
|
||||
_executionConditionState = executionConditionState;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
return requestMatchResult.AddScore(GetType(), GetScore(), null);
|
||||
}
|
||||
|
||||
private double GetScore()
|
||||
{
|
||||
return Equals(_executionConditionState, _nextState) ? MatchScores.Perfect : MatchScores.Mismatch;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Stef.Validation;
|
||||
using WireMock.Models;
|
||||
|
||||
namespace WireMock.Matchers.Request;
|
||||
|
||||
/// <summary>
|
||||
/// The request url matcher.
|
||||
/// </summary>
|
||||
public class RequestMessageUrlMatcher : IRequestMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The matchers
|
||||
/// </summary>
|
||||
public IReadOnlyList<IStringMatcher>? Matchers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The url functions
|
||||
/// </summary>
|
||||
public Func<string, bool>[]? Funcs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchBehaviour"/>
|
||||
/// </summary>
|
||||
public MatchBehaviour Behaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MatchOperator"/>
|
||||
/// </summary>
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use.</param>
|
||||
/// <param name="urls">The urls.</param>
|
||||
public RequestMessageUrlMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
MatchOperator matchOperator,
|
||||
params string[] urls) :
|
||||
this(matchBehaviour, matchOperator, urls
|
||||
.Select(url => new WildcardMatcher(matchBehaviour, new AnyOf<string, StringPattern>[] { url }, false, matchOperator))
|
||||
.Cast<IStringMatcher>().ToArray())
|
||||
{
|
||||
Behaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="MatchOperator"/> to use. (default = "Or")</param>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
public RequestMessageUrlMatcher(MatchBehaviour matchBehaviour, MatchOperator matchOperator, params IStringMatcher[] matchers)
|
||||
{
|
||||
Matchers = Guard.NotNull(matchers);
|
||||
Behaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestMessageUrlMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="funcs">The url functions.</param>
|
||||
public RequestMessageUrlMatcher(params Func<string, bool>[] funcs)
|
||||
{
|
||||
Funcs = Guard.NotNull(funcs);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
|
||||
{
|
||||
var (score, exception) = GetMatchResult(requestMessage).Expand();
|
||||
return requestMatchResult.AddScore(GetType(), score, exception);
|
||||
}
|
||||
|
||||
private MatchResult GetMatchResult(IRequestMessage requestMessage)
|
||||
{
|
||||
if (Matchers != null)
|
||||
{
|
||||
var results = Matchers.Select(m => m.IsMatch(requestMessage.Url)).ToArray();
|
||||
return MatchResult.From(results, MatchOperator);
|
||||
}
|
||||
|
||||
if (Funcs != null)
|
||||
{
|
||||
var results = Funcs.Select(func => func(requestMessage.Url)).ToArray();
|
||||
return MatchScores.ToScore(results, MatchOperator);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Some source files in this folder are based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
|
||||
For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
|
||||
140
src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs
Normal file
140
src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using SimMetrics.Net;
|
||||
using SimMetrics.Net.API;
|
||||
using SimMetrics.Net.Metric;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// SimMetricsMatcher
|
||||
/// </summary>
|
||||
/// <seealso cref="IStringMatcher" />
|
||||
public class SimMetricsMatcher : IStringMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
private readonly SimMetricType _simMetricType;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimMetricsMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="simMetricType">The SimMetric Type</param>
|
||||
public SimMetricsMatcher(AnyOf<string, StringPattern> pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(new[] { pattern }, simMetricType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimMetricsMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="simMetricType">The SimMetric Type</param>
|
||||
public SimMetricsMatcher(MatchBehaviour matchBehaviour, AnyOf<string, StringPattern> pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(matchBehaviour, new[] { pattern }, simMetricType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimMetricsMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="simMetricType">The SimMetric Type</param>
|
||||
public SimMetricsMatcher(string[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns.ToAnyOfPatterns(), simMetricType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimMetricsMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="simMetricType">The SimMetric Type</param>
|
||||
public SimMetricsMatcher(AnyOf<string, StringPattern>[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns, simMetricType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SimMetricsMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="simMetricType">The SimMetric Type</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
public SimMetricsMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
AnyOf<string, StringPattern>[] patterns,
|
||||
SimMetricType simMetricType = SimMetricType.Levenstein,
|
||||
MatchOperator matchOperator = MatchOperator.Average)
|
||||
{
|
||||
_patterns = Guard.NotNull(patterns);
|
||||
_simMetricType = simMetricType;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
IStringMetric stringMetricType = GetStringMetricType();
|
||||
|
||||
var score = MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)).ToArray(), MatchOperator);
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, score);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
|
||||
$"{_simMetricType.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
private IStringMetric GetStringMetricType()
|
||||
{
|
||||
return _simMetricType switch
|
||||
{
|
||||
SimMetricType.BlockDistance => new BlockDistance(),
|
||||
SimMetricType.ChapmanLengthDeviation => new ChapmanLengthDeviation(),
|
||||
SimMetricType.CosineSimilarity => new CosineSimilarity(),
|
||||
SimMetricType.DiceSimilarity => new DiceSimilarity(),
|
||||
SimMetricType.EuclideanDistance => new EuclideanDistance(),
|
||||
SimMetricType.JaccardSimilarity => new JaccardSimilarity(),
|
||||
SimMetricType.Jaro => new Jaro(),
|
||||
SimMetricType.JaroWinkler => new JaroWinkler(),
|
||||
SimMetricType.MatchingCoefficient => new MatchingCoefficient(),
|
||||
SimMetricType.MongeElkan => new MongeElkan(),
|
||||
SimMetricType.NeedlemanWunch => new NeedlemanWunch(),
|
||||
SimMetricType.OverlapCoefficient => new OverlapCoefficient(),
|
||||
SimMetricType.QGramsDistance => new QGramsDistance(),
|
||||
SimMetricType.SmithWaterman => new SmithWaterman(),
|
||||
SimMetricType.SmithWatermanGotoh => new SmithWatermanGotoh(),
|
||||
SimMetricType.SmithWatermanGotohWindowedAffine => new SmithWatermanGotohWindowedAffine(),
|
||||
SimMetricType.ChapmanMeanLength => new ChapmanMeanLength(),
|
||||
_ => new Levenstein()
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; } = MatchOperator.Average;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => $"SimMetricsMatcher.{_simMetricType}";
|
||||
}
|
||||
184
src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs
Normal file
184
src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.XPath;
|
||||
using AnyOfTypes;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using Stef.Validation;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Util;
|
||||
#if !NETSTANDARD1_3
|
||||
using Wmhelp.XPath2;
|
||||
#endif
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// XPath2Matcher
|
||||
/// </summary>
|
||||
/// <seealso cref="IStringMatcher" />
|
||||
public class XPathMatcher : IStringMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Array of namespace prefix and uri.
|
||||
/// </summary>
|
||||
public XmlNamespace[]? XmlNamespaceMap { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public XPathMatcher(params AnyOf<string, StringPattern>[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, null, patterns)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
|
||||
/// <param name="xmlNamespaceMap">The xml namespaces of the xml document.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public XPathMatcher(
|
||||
MatchBehaviour matchBehaviour,
|
||||
MatchOperator matchOperator = MatchOperator.Or,
|
||||
XmlNamespace[]? xmlNamespaceMap = null,
|
||||
params AnyOf<string, StringPattern>[] patterns)
|
||||
{
|
||||
_patterns = Guard.NotNull(patterns);
|
||||
XmlNamespaceMap = xmlNamespaceMap;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
MatchOperator = matchOperator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchResult IsMatch(string? input)
|
||||
{
|
||||
var score = MatchScores.Mismatch;
|
||||
|
||||
if (input == null)
|
||||
{
|
||||
return CreateMatchResult(score);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var xPathEvaluator = new XPathEvaluator();
|
||||
xPathEvaluator.Load(input);
|
||||
|
||||
if (!xPathEvaluator.IsXmlDocumentLoaded)
|
||||
{
|
||||
return CreateMatchResult(score);
|
||||
}
|
||||
|
||||
score = MatchScores.ToScore(xPathEvaluator.Evaluate(_patterns, XmlNamespaceMap), MatchOperator);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
return CreateMatchResult(score, exception);
|
||||
}
|
||||
|
||||
return CreateMatchResult(score);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(XPathMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"null, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
private MatchResult CreateMatchResult(double score, Exception? exception = null)
|
||||
{
|
||||
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
private sealed class XPathEvaluator
|
||||
{
|
||||
private XmlDocument? _xmlDocument;
|
||||
private XPathNavigator? _xpathNavigator;
|
||||
|
||||
public bool IsXmlDocumentLoaded => _xmlDocument != null;
|
||||
|
||||
public void Load(string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
_xmlDocument = new XmlDocument { InnerXml = input };
|
||||
_xpathNavigator = _xmlDocument.CreateNavigator();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_xmlDocument = default;
|
||||
}
|
||||
}
|
||||
|
||||
public bool[] Evaluate(AnyOf<string, StringPattern>[] patterns, IEnumerable<XmlNamespace>? xmlNamespaceMap)
|
||||
{
|
||||
return _xpathNavigator == null ? [] : patterns.Select(pattern => true.Equals(Evaluate(_xpathNavigator, pattern, xmlNamespaceMap))).ToArray();
|
||||
}
|
||||
|
||||
private object Evaluate(XPathNavigator navigator, AnyOf<string, StringPattern> pattern, IEnumerable<XmlNamespace>? xmlNamespaceMap)
|
||||
{
|
||||
var xpath = $"boolean({pattern.GetPattern()})";
|
||||
|
||||
var xmlNamespaceManager = GetXmlNamespaceManager(xmlNamespaceMap);
|
||||
if (xmlNamespaceManager == null)
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
return navigator.Evaluate(xpath);
|
||||
#else
|
||||
return navigator.XPath2Evaluate(xpath);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NETSTANDARD1_3
|
||||
return navigator.Evaluate(xpath, xmlNamespaceManager);
|
||||
#else
|
||||
return navigator.XPath2Evaluate(xpath, xmlNamespaceManager);
|
||||
#endif
|
||||
}
|
||||
|
||||
private XmlNamespaceManager? GetXmlNamespaceManager(IEnumerable<XmlNamespace>? xmlNamespaceMap)
|
||||
{
|
||||
if (_xpathNavigator == null || xmlNamespaceMap == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var nsManager = new XmlNamespaceManager(_xpathNavigator.NameTable);
|
||||
foreach (var xmlNamespace in xmlNamespaceMap)
|
||||
{
|
||||
nsManager.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri);
|
||||
}
|
||||
|
||||
return nsManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user