mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-03-24 01:51:02 +01:00
Swagger support (#749)
* r * fix * sw * x * s * . * . * . * CreateTypeFromJObject * . * . * f * c * . * . * . * . * . * . * ok * , * . * . * . * . * n * pact * fix * schema * null * fluent * r * -p * . * . * refs * .
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
namespace WireMock.Admin.Mappings
|
||||
namespace WireMock.Admin.Mappings
|
||||
{
|
||||
/// <summary>
|
||||
/// Body Model
|
||||
@@ -9,11 +9,11 @@
|
||||
/// <summary>
|
||||
/// Gets or sets the matcher.
|
||||
/// </summary>
|
||||
public MatcherModel Matcher { get; set; }
|
||||
public MatcherModel? Matcher { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the matchers.
|
||||
/// </summary>
|
||||
public MatcherModel[] Matchers { get; set; }
|
||||
public MatcherModel[]? Matchers { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WireMock.Admin.Mappings;
|
||||
|
||||
/// <summary>
|
||||
/// Cookie Model
|
||||
/// </summary>
|
||||
[FluentBuilder.AutoGenerateBuilder]
|
||||
public class CookieModel
|
||||
namespace WireMock.Admin.Mappings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// Cookie Model
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
[FluentBuilder.AutoGenerateBuilder]
|
||||
public class CookieModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the matchers.
|
||||
/// </summary>
|
||||
public IList<MatcherModel>? Matchers { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the matchers.
|
||||
/// </summary>
|
||||
public IList<MatcherModel>? Matchers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ignore case.
|
||||
/// </summary>
|
||||
public bool? IgnoreCase { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the ignore case.
|
||||
/// </summary>
|
||||
public bool? IgnoreCase { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reject on match.
|
||||
/// </summary>
|
||||
public bool? RejectOnMatch { get; set; }
|
||||
/// <summary>
|
||||
/// Reject on match.
|
||||
/// </summary>
|
||||
public bool? RejectOnMatch { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,17 @@ namespace WireMock.Admin.Mappings
|
||||
/// <summary>
|
||||
/// Gets or sets the pattern. Can be a string (default) or an object.
|
||||
/// </summary>
|
||||
public object Pattern { get; set; }
|
||||
public object? Pattern { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the patterns. Can be array of strings (default) or an array of objects.
|
||||
/// </summary>
|
||||
public object[] Patterns { get; set; }
|
||||
public object[]? Patterns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pattern as a file.
|
||||
/// </summary>
|
||||
public string PatternAsFile { get; set; }
|
||||
public string? PatternAsFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ignore case.
|
||||
@@ -36,4 +36,4 @@ namespace WireMock.Admin.Mappings
|
||||
/// </summary>
|
||||
public bool? RejectOnMatch { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace WireMock.Admin.Mappings
|
||||
namespace WireMock.Admin.Mappings
|
||||
{
|
||||
/// <summary>
|
||||
/// PathModel
|
||||
@@ -9,6 +9,6 @@
|
||||
/// <summary>
|
||||
/// Gets or sets the matchers.
|
||||
/// </summary>
|
||||
public MatcherModel[] Matchers { get; set; }
|
||||
public MatcherModel[]? Matchers { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Linq;
|
||||
using WireMock.Admin.Mappings;
|
||||
|
||||
namespace WireMock.Extensions;
|
||||
|
||||
public static class RequestModelExtensions
|
||||
{
|
||||
public static string? GetPathAsString(this RequestModel request)
|
||||
{
|
||||
var path = request.Path switch
|
||||
{
|
||||
string pathAsString => pathAsString,
|
||||
PathModel pathModel => pathModel.Matchers?.FirstOrDefault()?.Pattern as string,
|
||||
_ => null
|
||||
};
|
||||
|
||||
return FixPath(path);
|
||||
}
|
||||
|
||||
private static string? FixPath(string? path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return path!.StartsWith("/") ? path : $"/{path}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using WireMock.Admin.Mappings;
|
||||
|
||||
namespace WireMock.Extensions;
|
||||
|
||||
public static class ResponseModelExtensions
|
||||
{
|
||||
private const string DefaultStatusCode = "200";
|
||||
|
||||
public static string GetStatusCodeAsString(this ResponseModel response)
|
||||
{
|
||||
return response.StatusCode switch
|
||||
{
|
||||
string statusCodeAsString => statusCodeAsString,
|
||||
|
||||
int statusCodeAsInt => statusCodeAsInt.ToString(),
|
||||
|
||||
_ => response.StatusCode?.ToString() ?? DefaultStatusCode
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using WireMock.ResponseBuilders;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
@@ -13,7 +13,7 @@ namespace WireMock
|
||||
/// <summary>
|
||||
/// The Body.
|
||||
/// </summary>
|
||||
IBodyData BodyData { get; }
|
||||
IBodyData? BodyData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the body destination (SameAsSource, String or Bytes).
|
||||
|
||||
@@ -1,130 +1,132 @@
|
||||
#pragma warning disable CS1591
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using FluentAssertions.Execution;
|
||||
using WireMock.Server;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace WireMock.FluentAssertions
|
||||
namespace WireMock.FluentAssertions;
|
||||
|
||||
public class WireMockAssertions
|
||||
{
|
||||
public class WireMockAssertions
|
||||
private readonly IWireMockServer _subject;
|
||||
private readonly int? _callsCount;
|
||||
|
||||
public WireMockAssertions(IWireMockServer subject, int? callsCount)
|
||||
{
|
||||
private readonly IWireMockServer _subject;
|
||||
_subject = subject;
|
||||
_callsCount = callsCount;
|
||||
}
|
||||
|
||||
public WireMockAssertions(IWireMockServer subject, int? callsCount)
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList())
|
||||
.ForCondition(requests => requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.",
|
||||
absoluteUrl)
|
||||
.Then
|
||||
.ForCondition(x => _callsCount == null && x.Any(y => y.AbsoluteUrl == absoluteUrl) || _callsCount == x.Count(y => y.AbsoluteUrl == absoluteUrl))
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.",
|
||||
_ => absoluteUrl, requests => requests.Select(request => request.AbsoluteUrl));
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> AtUrl(string url, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList())
|
||||
.ForCondition(requests => requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but no calls were made.",
|
||||
url)
|
||||
.Then
|
||||
.ForCondition(x => _callsCount == null && x.Any(y => y.Url == url) || _callsCount == x.Count(y => y.Url == url))
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but didn't find it among the calls to {1}.",
|
||||
_ => url, requests => requests.Select(request => request.Url));
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList())
|
||||
.ForCondition(requests => requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but no calls were made.",
|
||||
proxyUrl)
|
||||
.Then
|
||||
.ForCondition(x => _callsCount == null && x.Any(y => y.ProxyUrl == proxyUrl) || _callsCount == x.Count(y => y.ProxyUrl == proxyUrl))
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but didn't find it among the calls with {1}.",
|
||||
_ => proxyUrl, requests => requests.Select(request => request.ProxyUrl));
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> FromClientIP(string clientIP, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList())
|
||||
.ForCondition(requests => requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but no calls were made.",
|
||||
clientIP)
|
||||
.Then
|
||||
.ForCondition(x => _callsCount == null && x.Any(y => y.ClientIP == clientIP) || _callsCount == x.Count(y => y.ClientIP == clientIP))
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but didn't find it among the calls from IP(s) {1}.",
|
||||
_ => clientIP, requests => requests.Select(request => request.ClientIP));
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string value, string because = "", params object[] becauseArgs)
|
||||
=> WithHeader(expectedKey, new[] { value }, because, becauseArgs);
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var headersDictionary = _subject.LogEntries.SelectMany(x => x.RequestMessage.Headers)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
using (new AssertionScope("headers from requests sent"))
|
||||
{
|
||||
_subject = subject;
|
||||
headersDictionary.Should().ContainKey(expectedKey, because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs)
|
||||
using (new AssertionScope($"header \"{expectedKey}\" from requests sent with value(s)"))
|
||||
{
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList())
|
||||
.ForCondition(requests => requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.",
|
||||
absoluteUrl)
|
||||
.Then
|
||||
.ForCondition(x => x.Any(y => y.AbsoluteUrl == absoluteUrl))
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but didn't find it among the calls to {1}.",
|
||||
_ => absoluteUrl, requests => requests.Select(request => request.AbsoluteUrl));
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string value, string because = "", params object[] becauseArgs)
|
||||
=> WithHeader(expectedKey, new[] { value }, because, becauseArgs);
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithHeader(string expectedKey, string[] expectedValues, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var headersDictionary = _subject.LogEntries.SelectMany(x => x.RequestMessage.Headers)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
using (new AssertionScope("headers from requests sent"))
|
||||
if (expectedValues.Length == 1)
|
||||
{
|
||||
headersDictionary.Should().ContainKey(expectedKey, because, becauseArgs);
|
||||
headersDictionary[expectedKey].Should().Contain(expectedValues.First());
|
||||
}
|
||||
|
||||
using (new AssertionScope($"header \"{expectedKey}\" from requests sent with value(s)"))
|
||||
else
|
||||
{
|
||||
if (expectedValues.Length == 1)
|
||||
var trimmedHeaderValues = string.Join(",", headersDictionary[expectedKey].Select(x => x)).Split(',')
|
||||
.Select(x => x.Trim())
|
||||
.ToList();
|
||||
foreach (var expectedValue in expectedValues)
|
||||
{
|
||||
headersDictionary[expectedKey].Should().Contain(expectedValues.First());
|
||||
}
|
||||
else
|
||||
{
|
||||
var trimmedHeaderValues = string.Join(",", headersDictionary[expectedKey].Select(x => x)).Split(',')
|
||||
.Select(x => x.Trim())
|
||||
.ToList();
|
||||
foreach (var expectedValue in expectedValues)
|
||||
{
|
||||
trimmedHeaderValues.Should().Contain(expectedValue);
|
||||
}
|
||||
trimmedHeaderValues.Should().Contain(expectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> AtUrl(string url, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList())
|
||||
.ForCondition(requests => requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but no calls were made.",
|
||||
url)
|
||||
.Then
|
||||
.ForCondition(x => x.Any(y => y.Url == url))
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called at address matching the url {0}{reason}, but didn't find it among the calls to {1}.",
|
||||
_ => url, requests => requests.Select(request => request.Url));
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList())
|
||||
.ForCondition(requests => requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but no calls were made.",
|
||||
proxyUrl)
|
||||
.Then
|
||||
.ForCondition(x => x.Any(y => y.ProxyUrl == proxyUrl))
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called with proxy url {0}{reason}, but didn't find it among the calls with {1}.",
|
||||
_ => proxyUrl, requests => requests.Select(request => request.ProxyUrl));
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> FromClientIP(string clientIP, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
Execute.Assertion
|
||||
.BecauseOf(because, becauseArgs)
|
||||
.Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList())
|
||||
.ForCondition(requests => requests.Any())
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but no calls were made.",
|
||||
clientIP)
|
||||
.Then
|
||||
.ForCondition(x => x.Any(y => y.ClientIP == clientIP))
|
||||
.FailWith(
|
||||
"Expected {context:wiremockserver} to have been called from client IP {0}{reason}, but didn't find it among the calls from IP(s) {1}.",
|
||||
_ => clientIP, requests => requests.Select(request => request.ClientIP));
|
||||
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
return new AndConstraint<WireMockAssertions>(this);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,15 @@ namespace WireMock.FluentAssertions
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts if <see cref="IWireMockServer"/> has received no calls.
|
||||
/// </summary>
|
||||
/// <returns><see cref="WireMockAssertions"/></returns>
|
||||
public WireMockAssertions HaveReceivedNoCalls()
|
||||
{
|
||||
return new WireMockAssertions(Subject, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts if <see cref="IWireMockServer"/> has received a call.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
namespace WireMock.Constants
|
||||
namespace WireMock.Constants;
|
||||
|
||||
internal static class WireMockConstants
|
||||
{
|
||||
internal static class WireMockConstants
|
||||
{
|
||||
public const int AdminPriority = int.MinValue;
|
||||
public const int MinPriority = -1_000_000;
|
||||
public const int ProxyPriority = -2_000_000;
|
||||
}
|
||||
public const int AdminPriority = int.MinValue;
|
||||
public const int MinPriority = -1_000_000;
|
||||
public const int ProxyPriority = -2_000_000;
|
||||
|
||||
public const string ContentTypeJson = "application/json";
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WireMock.Util;
|
||||
using Stef.Validation;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers
|
||||
{
|
||||
@@ -38,7 +37,7 @@ namespace WireMock.Matchers
|
||||
/// <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="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
public JsonMatcher([NotNull] string value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException)
|
||||
public JsonMatcher(string value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -48,7 +47,7 @@ namespace WireMock.Matchers
|
||||
/// <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="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
public JsonMatcher([NotNull] object value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException)
|
||||
public JsonMatcher(object value, bool ignoreCase = false, bool throwException = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, throwException)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -59,7 +58,7 @@ namespace WireMock.Matchers
|
||||
/// <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="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
public JsonMatcher(MatchBehaviour matchBehaviour, [NotNull] object value, bool ignoreCase = false, bool throwException = false)
|
||||
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool throwException = false)
|
||||
{
|
||||
Guard.NotNull(value, nameof(value));
|
||||
|
||||
@@ -75,12 +74,12 @@ namespace WireMock.Matchers
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
|
||||
public double IsMatch(object input)
|
||||
public double IsMatch(object? input)
|
||||
{
|
||||
bool match = false;
|
||||
|
||||
// When input is null or byte[], return Mismatch.
|
||||
if (input != null && !(input is byte[]))
|
||||
if (input != null && input is not byte[])
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -132,7 +131,7 @@ namespace WireMock.Matchers
|
||||
}
|
||||
}
|
||||
|
||||
private static string ToUpper(string input)
|
||||
private static string? ToUpper(string? input)
|
||||
{
|
||||
return input?.ToUpperInvariant();
|
||||
}
|
||||
|
||||
@@ -8,108 +8,107 @@ using WireMock.Models;
|
||||
using WireMock.RegularExpressions;
|
||||
using Stef.Validation;
|
||||
|
||||
namespace WireMock.Matchers
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// Regular Expression Matcher
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IStringMatcher"/>
|
||||
/// <inheritdoc cref="IIgnoreCaseMatcher"/>
|
||||
public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
private readonly Regex[] _expressions;
|
||||
|
||||
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <inheritdoc cref="IMatcher.ThrowException"/>
|
||||
public bool ThrowException { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Regular Expression Matcher
|
||||
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="IStringMatcher"/>
|
||||
/// <inheritdoc cref="IIgnoreCaseMatcher"/>
|
||||
public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
|
||||
public RegexMatcher([NotNull, RegexPattern] AnyOf<string, StringPattern> pattern, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true) :
|
||||
this(MatchBehaviour.AcceptOnMatch, new[] { pattern }, ignoreCase, throwException, useRegexExtended)
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
private readonly Regex[] _expressions;
|
||||
|
||||
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
|
||||
public MatchBehaviour MatchBehaviour { get; }
|
||||
|
||||
/// <inheritdoc cref="IMatcher.ThrowException"/>
|
||||
public bool ThrowException { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
|
||||
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
|
||||
public RegexMatcher([NotNull, RegexPattern] AnyOf<string, StringPattern> pattern, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true) :
|
||||
this(MatchBehaviour.AcceptOnMatch, new[] { pattern }, ignoreCase, throwException, useRegexExtended)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexMatcher"/> 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="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
|
||||
public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] AnyOf<string, StringPattern> pattern, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true) :
|
||||
this(matchBehaviour, new[] { pattern }, ignoreCase, throwException, useRegexExtended)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexMatcher"/> 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="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
|
||||
public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true)
|
||||
{
|
||||
Guard.NotNull(patterns, nameof(patterns));
|
||||
|
||||
_patterns = patterns;
|
||||
IgnoreCase = ignoreCase;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
ThrowException = throwException;
|
||||
|
||||
RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline;
|
||||
|
||||
if (ignoreCase)
|
||||
{
|
||||
options |= RegexOptions.IgnoreCase;
|
||||
}
|
||||
|
||||
_expressions = patterns.Select(p => useRegexExtended ? new RegexExtended(p.GetPattern(), options) : new Regex(p.GetPattern(), options)).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
|
||||
public virtual double IsMatch(string input)
|
||||
{
|
||||
double match = MatchScores.Mismatch;
|
||||
if (input != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
match = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (ThrowException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
|
||||
public virtual AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IMatcher.Name"/>
|
||||
public virtual string Name => "RegexMatcher";
|
||||
|
||||
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
|
||||
public bool IgnoreCase { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexMatcher"/> 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="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
|
||||
public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] AnyOf<string, StringPattern> pattern, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true) :
|
||||
this(matchBehaviour, new[] { pattern }, ignoreCase, throwException, useRegexExtended)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegexMatcher"/> 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="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
/// <param name="useRegexExtended">Use RegexExtended (default = true).</param>
|
||||
public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false, bool throwException = false, bool useRegexExtended = true)
|
||||
{
|
||||
Guard.NotNull(patterns, nameof(patterns));
|
||||
|
||||
_patterns = patterns;
|
||||
IgnoreCase = ignoreCase;
|
||||
MatchBehaviour = matchBehaviour;
|
||||
ThrowException = throwException;
|
||||
|
||||
RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline;
|
||||
|
||||
if (ignoreCase)
|
||||
{
|
||||
options |= RegexOptions.IgnoreCase;
|
||||
}
|
||||
|
||||
_expressions = patterns.Select(p => useRegexExtended ? new RegexExtended(p.GetPattern(), options) : new Regex(p.GetPattern(), options)).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
|
||||
public virtual double IsMatch(string input)
|
||||
{
|
||||
double match = MatchScores.Mismatch;
|
||||
if (input != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
match = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (ThrowException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Name => nameof(RegexMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IgnoreCase { get; }
|
||||
}
|
||||
@@ -5,75 +5,74 @@ using JetBrains.Annotations;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
|
||||
namespace WireMock.Matchers
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// WildcardMatcher
|
||||
/// </summary>
|
||||
/// <seealso cref="RegexMatcher" />
|
||||
public class WildcardMatcher : RegexMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
/// <summary>
|
||||
/// WildcardMatcher
|
||||
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="RegexMatcher" />
|
||||
public class WildcardMatcher : RegexMatcher
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase</param>
|
||||
public WildcardMatcher([NotNull] AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase)
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase</param>
|
||||
public WildcardMatcher([NotNull] AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase</param>
|
||||
public WildcardMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="pattern">The pattern.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase</param>
|
||||
public WildcardMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf<string, StringPattern> pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase</param>
|
||||
public WildcardMatcher([NotNull] AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase</param>
|
||||
public WildcardMatcher([NotNull] AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase</param>
|
||||
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
public WildcardMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false, bool throwException = false) :
|
||||
base(matchBehaviour, CreateArray(patterns), ignoreCase, throwException)
|
||||
{
|
||||
_patterns = patterns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WildcardMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="matchBehaviour">The match behaviour.</param>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
/// <param name="ignoreCase">IgnoreCase</param>
|
||||
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
|
||||
public WildcardMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf<string, StringPattern>[] patterns, bool ignoreCase = false, bool throwException = false) :
|
||||
base(matchBehaviour, CreateArray(patterns), ignoreCase, throwException)
|
||||
{
|
||||
_patterns = patterns;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
|
||||
public override AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override string Name => nameof(WildcardMatcher);
|
||||
|
||||
/// <inheritdoc cref="IMatcher.Name"/>
|
||||
public override string Name => "WildcardMatcher";
|
||||
|
||||
private static AnyOf<string, StringPattern>[] CreateArray(AnyOf<string, StringPattern>[] patterns)
|
||||
{
|
||||
return patterns.Select(pattern => new AnyOf<string, StringPattern>(
|
||||
private static AnyOf<string, StringPattern>[] CreateArray(AnyOf<string, StringPattern>[] patterns)
|
||||
{
|
||||
return patterns.Select(pattern => new AnyOf<string, StringPattern>(
|
||||
new StringPattern
|
||||
{
|
||||
Pattern = "^" + Regex.Escape(pattern.GetPattern()).Replace(@"\*", ".*").Replace(@"\?", ".") + "$",
|
||||
PatternAsFile = pattern.IsSecond ? pattern.Second.PatternAsFile : null
|
||||
}))
|
||||
.ToArray();
|
||||
}
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,6 @@ namespace WireMock.Owin.Mappers
|
||||
/// </summary>
|
||||
/// <param name="responseMessage">The ResponseMessage</param>
|
||||
/// <param name="response">The OwinResponse/HttpResponse</param>
|
||||
Task MapAsync(IResponseMessage responseMessage, IResponse response);
|
||||
Task MapAsync(IResponseMessage? responseMessage, IResponse response);
|
||||
}
|
||||
}
|
||||
@@ -47,20 +47,18 @@ namespace WireMock.Owin.Mappers
|
||||
/// <param name="options">The IWireMockMiddlewareOptions.</param>
|
||||
public OwinResponseMapper(IWireMockMiddlewareOptions options)
|
||||
{
|
||||
Guard.NotNull(options, nameof(options));
|
||||
|
||||
_options = options;
|
||||
_options = Guard.NotNull(options);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IOwinResponseMapper.MapAsync"/>
|
||||
public async Task MapAsync(IResponseMessage responseMessage, IResponse response)
|
||||
public async Task MapAsync(IResponseMessage? responseMessage, IResponse response)
|
||||
{
|
||||
if (responseMessage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes;
|
||||
byte[]? bytes;
|
||||
switch (responseMessage.FaultType)
|
||||
{
|
||||
case FaultType.EMPTY_RESPONSE:
|
||||
@@ -122,33 +120,28 @@ namespace WireMock.Owin.Mappers
|
||||
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
|
||||
}
|
||||
|
||||
private byte[] GetNormalBody(IResponseMessage responseMessage)
|
||||
private byte[]? GetNormalBody(IResponseMessage responseMessage)
|
||||
{
|
||||
byte[] bytes = null;
|
||||
switch (responseMessage.BodyData?.DetectedBodyType)
|
||||
{
|
||||
case BodyType.String:
|
||||
bytes = (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString);
|
||||
break;
|
||||
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString);
|
||||
|
||||
case BodyType.Json:
|
||||
Formatting formatting = responseMessage.BodyData.BodyAsJsonIndented == true
|
||||
var formatting = responseMessage.BodyData.BodyAsJsonIndented == true
|
||||
? Formatting.Indented
|
||||
: Formatting.None;
|
||||
string jsonBody = JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
|
||||
bytes = (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
|
||||
break;
|
||||
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
|
||||
|
||||
case BodyType.Bytes:
|
||||
bytes = responseMessage.BodyData.BodyAsBytes;
|
||||
break;
|
||||
return responseMessage.BodyData.BodyAsBytes;
|
||||
|
||||
case BodyType.File:
|
||||
bytes = _options.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile);
|
||||
break;
|
||||
return _options.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void SetResponseHeaders(IResponseMessage responseMessage, IResponse response)
|
||||
|
||||
@@ -72,8 +72,8 @@ namespace WireMock.Owin
|
||||
var request = await _requestMapper.MapAsync(ctx.Request, _options).ConfigureAwait(false);
|
||||
|
||||
var logRequest = false;
|
||||
IResponseMessage response = null;
|
||||
(MappingMatcherResult Match, MappingMatcherResult Partial) result = (null, null);
|
||||
IResponseMessage? response = null;
|
||||
(MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null);
|
||||
try
|
||||
{
|
||||
foreach (var mapping in _options.Mappings.Values.Where(m => m?.Scenario != null))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
|
||||
// This source file is 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.
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -27,7 +27,7 @@ namespace WireMock
|
||||
public string BodyDestination { get; set; }
|
||||
|
||||
/// <inheritdoc cref="IResponseMessage.BodyData" />
|
||||
public IBodyData BodyData { get; set; }
|
||||
public IBodyData? BodyData { get; set; }
|
||||
|
||||
/// <inheritdoc cref="IResponseMessage.FaultType" />
|
||||
public FaultType FaultType { get; set; }
|
||||
|
||||
@@ -1,50 +1,49 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Constants;
|
||||
using WireMock.Http;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock
|
||||
namespace WireMock;
|
||||
|
||||
internal static class ResponseMessageBuilder
|
||||
{
|
||||
internal static class ResponseMessageBuilder
|
||||
private static readonly IDictionary<string, WireMockList<string>> ContentTypeJsonHeaders = new Dictionary<string, WireMockList<string>>
|
||||
{
|
||||
private static string ContentTypeJson = "application/json";
|
||||
private static readonly IDictionary<string, WireMockList<string>> ContentTypeJsonHeaders = new Dictionary<string, WireMockList<string>>
|
||||
{ HttpKnownHeaderNames.ContentType, new WireMockList<string> { WireMockConstants.ContentTypeJson } }
|
||||
};
|
||||
|
||||
internal static ResponseMessage Create(string? message, int statusCode = 200, Guid? guid = null)
|
||||
{
|
||||
var response = new ResponseMessage
|
||||
{
|
||||
{ HttpKnownHeaderNames.ContentType, new WireMockList<string> { ContentTypeJson } }
|
||||
StatusCode = statusCode,
|
||||
Headers = ContentTypeJsonHeaders
|
||||
};
|
||||
|
||||
internal static ResponseMessage Create(string message, int statusCode = 200, Guid? guid = null)
|
||||
if (message != null)
|
||||
{
|
||||
var response = new ResponseMessage
|
||||
response.BodyData = new BodyData
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Headers = ContentTypeJsonHeaders
|
||||
};
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
response.BodyData = new BodyData
|
||||
DetectedBodyType = BodyType.Json,
|
||||
BodyAsJson = new StatusModel
|
||||
{
|
||||
DetectedBodyType = BodyType.Json,
|
||||
BodyAsJson = new StatusModel
|
||||
{
|
||||
Guid = guid,
|
||||
Status = message
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
internal static ResponseMessage Create(int statusCode)
|
||||
{
|
||||
return new ResponseMessage
|
||||
{
|
||||
StatusCode = statusCode
|
||||
Guid = guid,
|
||||
Status = message
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
internal static ResponseMessage Create(int statusCode)
|
||||
{
|
||||
return new ResponseMessage
|
||||
{
|
||||
StatusCode = statusCode
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,34 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace WireMock.Serialization
|
||||
namespace WireMock.Serialization;
|
||||
|
||||
internal static class JsonSerializationConstants
|
||||
{
|
||||
internal static class JsonSerializationConstants
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new()
|
||||
{
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Include
|
||||
};
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Include
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new JsonSerializerSettings
|
||||
{
|
||||
DateParseHandling = DateParseHandling.None
|
||||
};
|
||||
public static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new()
|
||||
{
|
||||
DateParseHandling = DateParseHandling.None
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsPact = new JsonSerializerSettings
|
||||
public static readonly JsonSerializerSettings JsonSerializerSettingsPact = new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
}
|
||||
NamingStrategy = new CamelCaseNamingStrategy()
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using JetBrains.Annotations;
|
||||
using SimMetrics.Net;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Extensions;
|
||||
@@ -22,12 +21,12 @@ namespace WireMock.Serialization
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
public IMatcher[] Map([CanBeNull] IEnumerable<MatcherModel> matchers)
|
||||
public IMatcher[] Map(IEnumerable<MatcherModel>? matchers)
|
||||
{
|
||||
return matchers?.Select(Map).Where(m => m != null).ToArray();
|
||||
}
|
||||
|
||||
public IMatcher Map([CanBeNull] MatcherModel matcher)
|
||||
public IMatcher? Map(MatcherModel? matcher)
|
||||
{
|
||||
if (matcher == null)
|
||||
{
|
||||
@@ -36,7 +35,7 @@ namespace WireMock.Serialization
|
||||
|
||||
string[] parts = matcher.Name.Split('.');
|
||||
string matcherName = parts[0];
|
||||
string matcherType = parts.Length > 1 ? parts[1] : null;
|
||||
string? matcherType = parts.Length > 1 ? parts[1] : null;
|
||||
var stringPatterns = ParseStringPatterns(matcher);
|
||||
var matchBehaviour = matcher.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch;
|
||||
bool ignoreCase = matcher.IgnoreCase == true;
|
||||
@@ -69,16 +68,16 @@ namespace WireMock.Serialization
|
||||
return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, throwExceptionWhenMatcherFails, useRegexExtended);
|
||||
|
||||
case nameof(JsonMatcher):
|
||||
object valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonMatcher(matchBehaviour, valueForJsonMatcher, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
var valueForJsonMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
|
||||
case nameof(JsonPartialMatcher):
|
||||
object valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
var valueForJsonPartialMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
|
||||
case nameof(JsonPartialWildcardMatcher):
|
||||
object valueForJsonPartialWildcardMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
var valueForJsonPartialWildcardMatcher = matcher.Pattern ?? matcher.Patterns;
|
||||
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, throwExceptionWhenMatcherFails);
|
||||
|
||||
case nameof(JsonPathMatcher):
|
||||
return new JsonPathMatcher(matchBehaviour, throwExceptionWhenMatcherFails, stringPatterns);
|
||||
@@ -114,12 +113,12 @@ namespace WireMock.Serialization
|
||||
}
|
||||
}
|
||||
|
||||
public MatcherModel[] Map([CanBeNull] IEnumerable<IMatcher> matchers)
|
||||
public MatcherModel[] Map(IEnumerable<IMatcher>? matchers)
|
||||
{
|
||||
return matchers?.Select(Map).Where(m => m != null).ToArray();
|
||||
}
|
||||
|
||||
public MatcherModel Map([CanBeNull] IMatcher matcher)
|
||||
public MatcherModel? Map(IMatcher? matcher)
|
||||
{
|
||||
if (matcher == null)
|
||||
{
|
||||
|
||||
@@ -2,28 +2,25 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Pact.Models.V2;
|
||||
using WireMock.Server;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Server;
|
||||
namespace WireMock.Serialization;
|
||||
|
||||
public partial class WireMockServer
|
||||
internal static class PactMapper
|
||||
{
|
||||
private const string DefaultPath = "/";
|
||||
private const string DefaultMethod = "GET";
|
||||
private const int DefaultStatus = 200;
|
||||
private const int DefaultStatusCode = 200;
|
||||
private const string DefaultConsumer = "Default Consumer";
|
||||
private const string DefaultProvider = "Default Provider";
|
||||
|
||||
/// <summary>
|
||||
/// Save the mappings as a Pact Json file V2.
|
||||
/// </summary>
|
||||
/// <param name="folder">The folder to save the pact file.</param>
|
||||
/// <param name="filename">The filename for the .json file [optional].</param>
|
||||
public void SavePact(string folder, string? filename = null)
|
||||
public static (string FileName, byte[] Bytes) ToPact(WireMockServer server, string? filename = null)
|
||||
{
|
||||
var consumer = Consumer ?? DefaultConsumer;
|
||||
var provider = Provider ?? DefaultProvider;
|
||||
var consumer = server.Consumer ?? DefaultConsumer;
|
||||
var provider = server.Provider ?? DefaultProvider;
|
||||
|
||||
filename ??= $"{consumer} - {provider}.json";
|
||||
|
||||
@@ -33,41 +30,31 @@ public partial class WireMockServer
|
||||
Provider = new Pacticipant { Name = provider }
|
||||
};
|
||||
|
||||
foreach (var mapping in MappingModels)
|
||||
foreach (var mapping in server.MappingModels.OrderBy(m => m.Guid))
|
||||
{
|
||||
var path = mapping.Request.GetPathAsString();
|
||||
if (path == null)
|
||||
{
|
||||
// Path is null (probably a Func<>), skip this.
|
||||
continue;
|
||||
}
|
||||
|
||||
var interaction = new Interaction
|
||||
{
|
||||
Description = mapping.Description,
|
||||
ProviderState = mapping.Title,
|
||||
Request = MapRequest(mapping.Request),
|
||||
Request = MapRequest(mapping.Request, path),
|
||||
Response = MapResponse(mapping.Response)
|
||||
};
|
||||
|
||||
pact.Interactions.Add(interaction);
|
||||
}
|
||||
|
||||
var bytes = JsonUtils.SerializeAsPactFile(pact);
|
||||
_settings.FileSystemHandler.WriteFile(folder, filename, bytes);
|
||||
return (filename, JsonUtils.SerializeAsPactFile(pact));
|
||||
}
|
||||
|
||||
private static Request MapRequest(RequestModel request)
|
||||
private static Request MapRequest(RequestModel request, string path)
|
||||
{
|
||||
string path;
|
||||
switch (request.Path)
|
||||
{
|
||||
case string pathAsString:
|
||||
path = pathAsString;
|
||||
break;
|
||||
|
||||
case PathModel pathModel:
|
||||
path = GetPatternAsStringFromMatchers(pathModel.Matchers, DefaultPath);
|
||||
break;
|
||||
|
||||
default:
|
||||
path = DefaultPath;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Request
|
||||
{
|
||||
Method = request.Methods?.FirstOrDefault() ?? DefaultMethod,
|
||||
@@ -97,7 +84,7 @@ public partial class WireMockServer
|
||||
{
|
||||
if (statusCode is string statusCodeAsString)
|
||||
{
|
||||
return int.TryParse(statusCodeAsString, out var statusCodeAsInt) ? statusCodeAsInt : DefaultStatus;
|
||||
return int.TryParse(statusCodeAsString, out var statusCodeAsInt) ? statusCodeAsInt : DefaultStatusCode;
|
||||
}
|
||||
|
||||
if (statusCode != null)
|
||||
@@ -106,7 +93,7 @@ public partial class WireMockServer
|
||||
return Convert.ToInt32(statusCode);
|
||||
}
|
||||
|
||||
return DefaultStatus;
|
||||
return DefaultStatusCode;
|
||||
}
|
||||
|
||||
private static string? MapQueryParameters(IList<ParamModel>? queryParameters)
|
||||
@@ -118,41 +105,37 @@ public partial class WireMockServer
|
||||
|
||||
var values = queryParameters
|
||||
.Where(qp => qp.Matchers != null && qp.Matchers.Any() && qp.Matchers[0].Pattern is string)
|
||||
.Select(param => $"{Uri.EscapeDataString(param.Name)}={Uri.EscapeDataString((string)param.Matchers![0].Pattern)}");
|
||||
.Select(param => $"{Uri.EscapeDataString(param.Name)}={Uri.EscapeDataString((string)param.Matchers![0].Pattern!)}");
|
||||
|
||||
return string.Join("&", values);
|
||||
}
|
||||
|
||||
private static IDictionary<string, string>? MapRequestHeaders(IList<HeaderModel>? headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var validHeaders = headers.Where(h => h.Matchers != null && h.Matchers.Any() && h.Matchers[0].Pattern is string);
|
||||
return validHeaders.ToDictionary(x => x.Name, y => (string)y.Matchers![0].Pattern);
|
||||
var validHeaders = headers?.Where(h => h.Matchers != null && h.Matchers.Any() && h.Matchers[0].Pattern is string);
|
||||
return validHeaders?.ToDictionary(x => x.Name, y => (string)y.Matchers![0].Pattern!);
|
||||
}
|
||||
|
||||
private static IDictionary<string, string>? MapResponseHeaders(IDictionary<string, object>? headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var validHeaders = headers.Where(h => h.Value is string);
|
||||
return validHeaders.ToDictionary(x => x.Key, y => (string)y.Value);
|
||||
var validHeaders = headers?.Where(h => h.Value is string);
|
||||
return validHeaders?.ToDictionary(x => x.Key, y => (string)y.Value);
|
||||
}
|
||||
|
||||
private static object? MapBody(BodyModel? body)
|
||||
{
|
||||
if (body == null || body.Matcher.Name != "JsonMatcher")
|
||||
if (body?.Matcher == null || body.Matchers == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return body.Matcher.Pattern;
|
||||
if (body.Matcher is { Name: nameof(JsonMatcher) })
|
||||
{
|
||||
return body.Matcher.Pattern;
|
||||
}
|
||||
|
||||
var jsonMatcher = body.Matchers.FirstOrDefault(m => m.Name == nameof(JsonMatcher));
|
||||
return jsonMatcher?.Pattern;
|
||||
}
|
||||
|
||||
private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
|
||||
325
src/WireMock.Net/Serialization/SwaggerMapper.cs
Normal file
325
src/WireMock.Net/Serialization/SwaggerMapper.cs
Normal file
@@ -0,0 +1,325 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using NJsonSchema;
|
||||
using NJsonSchema.Extensions;
|
||||
using NSwag;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Constants;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Server;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Serialization;
|
||||
|
||||
internal static class SwaggerMapper
|
||||
{
|
||||
private const string DefaultMethod = "GET";
|
||||
private const string Generator = "WireMock.Net";
|
||||
|
||||
private static readonly JsonSchema JsonSchemaString = new() { Type = JsonObjectType.String };
|
||||
|
||||
public static string ToSwagger(WireMockServer server)
|
||||
{
|
||||
var openApiDocument = new OpenApiDocument
|
||||
{
|
||||
Generator = Generator,
|
||||
Info = new OpenApiInfo
|
||||
{
|
||||
Title = $"{Generator} Mappings Swagger specification",
|
||||
Version = SystemUtils.Version
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var url in server.Urls)
|
||||
{
|
||||
openApiDocument.Servers.Add(new OpenApiServer
|
||||
{
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var mapping in server.MappingModels)
|
||||
{
|
||||
var path = mapping.Request.GetPathAsString();
|
||||
if (path == null)
|
||||
{
|
||||
// Path is null (probably a Func<>), skip this.
|
||||
continue;
|
||||
}
|
||||
|
||||
var operation = new OpenApiOperation();
|
||||
foreach (var openApiParameter in MapRequestQueryParameters(mapping.Request.Params))
|
||||
{
|
||||
operation.Parameters.Add(openApiParameter);
|
||||
}
|
||||
foreach (var openApiParameter in MapRequestHeaders(mapping.Request.Headers))
|
||||
{
|
||||
operation.Parameters.Add(openApiParameter);
|
||||
}
|
||||
foreach (var openApiParameter in MapRequestCookies(mapping.Request.Cookies))
|
||||
{
|
||||
operation.Parameters.Add(openApiParameter);
|
||||
}
|
||||
|
||||
operation.RequestBody = MapRequestBody(mapping.Request);
|
||||
|
||||
var response = MapResponse(mapping.Response);
|
||||
if (response != null)
|
||||
{
|
||||
operation.Responses.Add(mapping.Response.GetStatusCodeAsString(), response);
|
||||
}
|
||||
|
||||
var method = mapping.Request.Methods?.FirstOrDefault() ?? DefaultMethod;
|
||||
if (!openApiDocument.Paths.ContainsKey(path))
|
||||
{
|
||||
var openApiPathItem = new OpenApiPathItem
|
||||
{
|
||||
{ method, operation }
|
||||
};
|
||||
|
||||
openApiDocument.Paths.Add(path, openApiPathItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The combination of path+method uniquely identify an operation. Duplicates are not allowed.
|
||||
if (!openApiDocument.Paths[path].ContainsKey(method))
|
||||
{
|
||||
openApiDocument.Paths[path].Add(method, operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return openApiDocument.ToJson(SchemaType.OpenApi3, Formatting.Indented);
|
||||
}
|
||||
|
||||
private static IEnumerable<OpenApiParameter> MapRequestQueryParameters(IList<ParamModel>? queryParameters)
|
||||
{
|
||||
if (queryParameters == null)
|
||||
{
|
||||
return new List<OpenApiParameter>();
|
||||
}
|
||||
|
||||
return queryParameters
|
||||
.Where(x => x.Matchers != null && x.Matchers.Any())
|
||||
.Select(x => new
|
||||
{
|
||||
x.Name,
|
||||
Details = GetDetailsFromMatcher(x.Matchers![0])
|
||||
})
|
||||
.Select(x => new OpenApiParameter
|
||||
{
|
||||
Name = x.Name,
|
||||
Example = x.Details.Example,
|
||||
Description = x.Details.Description,
|
||||
Kind = OpenApiParameterKind.Query,
|
||||
Schema = x.Details.JsonSchemaRegex,
|
||||
IsRequired = !x.Details.Reject
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<OpenApiParameter> MapRequestHeaders(IList<HeaderModel>? headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
return new List<OpenApiHeader>();
|
||||
}
|
||||
|
||||
return headers
|
||||
.Where(x => x.Matchers != null && x.Matchers.Any())
|
||||
.Select(x => new
|
||||
{
|
||||
x.Name,
|
||||
Details = GetDetailsFromMatcher(x.Matchers![0])
|
||||
})
|
||||
.Select(x => new OpenApiHeader
|
||||
{
|
||||
Name = x.Name,
|
||||
Example = x.Details.Example,
|
||||
Description = x.Details.Description,
|
||||
Kind = OpenApiParameterKind.Header,
|
||||
Schema = x.Details.JsonSchemaRegex,
|
||||
IsRequired = !x.Details.Reject
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<OpenApiParameter> MapRequestCookies(IList<CookieModel>? cookies)
|
||||
{
|
||||
if (cookies == null)
|
||||
{
|
||||
return new List<OpenApiParameter>();
|
||||
}
|
||||
|
||||
return cookies
|
||||
.Where(x => x.Matchers != null && x.Matchers.Any())
|
||||
.Select(x => new
|
||||
{
|
||||
x.Name,
|
||||
Details = GetDetailsFromMatcher(x.Matchers![0])
|
||||
})
|
||||
.Select(x => new OpenApiParameter
|
||||
{
|
||||
Name = x.Name,
|
||||
Example = x.Details.Example,
|
||||
Description = x.Details.Description,
|
||||
Kind = OpenApiParameterKind.Cookie,
|
||||
Schema = x.Details.JsonSchemaRegex,
|
||||
IsRequired = !x.Details.Reject
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static (JsonSchema JsonSchemaRegex, string? Example, string? Description, bool Reject) GetDetailsFromMatcher(MatcherModel matcher)
|
||||
{
|
||||
var pattern = GetPatternAsStringFromMatcher(matcher);
|
||||
var reject = matcher.RejectOnMatch == true;
|
||||
var description = $"{matcher.Name} with RejectOnMatch = '{reject}' and Pattern = '{pattern}'";
|
||||
|
||||
return matcher.Name is nameof(RegexMatcher) ?
|
||||
(new JsonSchema { Type = JsonObjectType.String, Format = "regex", Pattern = pattern }, pattern, description, reject) :
|
||||
(JsonSchemaString, pattern, description, reject);
|
||||
}
|
||||
|
||||
private static OpenApiRequestBody? MapRequestBody(RequestModel request)
|
||||
{
|
||||
var body = MapRequestBody(request.Body);
|
||||
if (body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var openApiMediaType = new OpenApiMediaType
|
||||
{
|
||||
Schema = GetJsonSchema(body)
|
||||
};
|
||||
|
||||
var requestBodyPost = new OpenApiRequestBody();
|
||||
requestBodyPost.Content.Add(GetContentType(request), openApiMediaType);
|
||||
|
||||
return requestBodyPost;
|
||||
}
|
||||
|
||||
private static OpenApiResponse? MapResponse(ResponseModel response)
|
||||
{
|
||||
if (response.Body != null)
|
||||
{
|
||||
return new OpenApiResponse
|
||||
{
|
||||
Schema = new JsonSchemaProperty
|
||||
{
|
||||
Type = JsonObjectType.String,
|
||||
Example = response.Body
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.BodyAsBytes != null)
|
||||
{
|
||||
// https://stackoverflow.com/questions/62794949/how-to-define-byte-array-in-openapi-3-0
|
||||
return new OpenApiResponse
|
||||
{
|
||||
Schema = new JsonSchemaProperty
|
||||
{
|
||||
Type = JsonObjectType.Array,
|
||||
Items =
|
||||
{
|
||||
new JsonSchema
|
||||
{
|
||||
Type = JsonObjectType.String,
|
||||
Format = JsonFormatStrings.Byte
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.BodyAsJson == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OpenApiResponse
|
||||
{
|
||||
Schema = GetJsonSchema(response.BodyAsJson)
|
||||
};
|
||||
}
|
||||
|
||||
private static JsonSchema GetJsonSchema(object instance)
|
||||
{
|
||||
switch (instance)
|
||||
{
|
||||
case string instanceAsString:
|
||||
try
|
||||
{
|
||||
var value = JsonConvert.DeserializeObject(instanceAsString);
|
||||
return GetJsonSchema(value!);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return JsonSchemaString;
|
||||
}
|
||||
|
||||
default:
|
||||
return instance.ToJsonSchema();
|
||||
}
|
||||
}
|
||||
|
||||
private static object? MapRequestBody(BodyModel? body)
|
||||
{
|
||||
if (body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var matcher = GetMatcher(body.Matcher, body.Matchers);
|
||||
if (matcher is { Name: nameof(JsonMatcher) })
|
||||
{
|
||||
var pattern = GetPatternAsStringFromMatcher(matcher);
|
||||
if (JsonUtils.TryParseAsJObject(pattern, out var jObject))
|
||||
{
|
||||
return jObject;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetContentType(RequestModel request)
|
||||
{
|
||||
var contentType = request.Headers?.FirstOrDefault(h => h.Name == "Content-Type");
|
||||
|
||||
return contentType == null ?
|
||||
WireMockConstants.ContentTypeJson :
|
||||
GetPatternAsStringFromMatchers(contentType.Matchers, WireMockConstants.ContentTypeJson);
|
||||
}
|
||||
|
||||
private static string GetPatternAsStringFromMatchers(IList<MatcherModel>? matchers, string defaultValue)
|
||||
{
|
||||
if (matchers == null || !matchers.Any())
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return GetPatternAsStringFromMatcher(matchers.First()) ?? defaultValue;
|
||||
}
|
||||
|
||||
private static string? GetPatternAsStringFromMatcher(MatcherModel matcher)
|
||||
{
|
||||
if (matcher.Pattern is string patternAsString)
|
||||
{
|
||||
return patternAsString;
|
||||
}
|
||||
|
||||
return matcher.Patterns?.FirstOrDefault() as string;
|
||||
}
|
||||
|
||||
private static MatcherModel? GetMatcher(MatcherModel? matcher, MatcherModel[]? matchers)
|
||||
{
|
||||
return matcher ?? matchers?.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ namespace WireMock.Server;
|
||||
public partial class WireMockServer
|
||||
{
|
||||
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
|
||||
private const string ContentTypeJson = "application/json";
|
||||
private const string AdminFiles = "/__admin/files";
|
||||
private const string AdminMappings = "/__admin/mappings";
|
||||
private const string AdminMappingsWireMockOrg = "/__admin/mappings/wiremock.org";
|
||||
@@ -45,7 +44,7 @@ public partial class WireMockServer
|
||||
private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
|
||||
|
||||
private readonly Guid _proxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
|
||||
private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(ContentTypeJson, true);
|
||||
private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
|
||||
private readonly RegexMatcher _adminMappingsGuidPathMatcher = new(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
|
||||
private readonly RegexMatcher _adminRequestsGuidPathMatcher = new(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
|
||||
|
||||
@@ -73,7 +72,10 @@ public partial class WireMockServer
|
||||
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
|
||||
|
||||
// __admin/mappings/save
|
||||
Given(Request.Create().WithPath(AdminMappings + "/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave));
|
||||
Given(Request.Create().WithPath($"{AdminMappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave));
|
||||
|
||||
// __admin/mappings/swagger
|
||||
Given(Request.Create().WithPath($"{AdminMappings}/swagger").UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SwaggerGet));
|
||||
|
||||
// __admin/requests
|
||||
Given(Request.Create().WithPath(AdminRequests).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet));
|
||||
@@ -148,7 +150,7 @@ public partial class WireMockServer
|
||||
|
||||
/// <inheritdoc cref="IWireMockServer.WatchStaticMappings" />
|
||||
[PublicAPI]
|
||||
public void WatchStaticMappings([CanBeNull] string folder = null)
|
||||
public void WatchStaticMappings(string? folder = null)
|
||||
{
|
||||
if (folder == null)
|
||||
{
|
||||
@@ -379,6 +381,20 @@ public partial class WireMockServer
|
||||
#endregion Mapping/{guid}
|
||||
|
||||
#region Mappings
|
||||
private IResponseMessage SwaggerGet(IRequestMessage requestMessage)
|
||||
{
|
||||
return new ResponseMessage
|
||||
{
|
||||
BodyData = new BodyData
|
||||
{
|
||||
DetectedBodyType = BodyType.String,
|
||||
BodyAsString = SwaggerMapper.ToSwagger(this)
|
||||
},
|
||||
StatusCode = (int)HttpStatusCode.OK,
|
||||
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
|
||||
};
|
||||
}
|
||||
|
||||
private IResponseMessage MappingsSave(IRequestMessage requestMessage)
|
||||
{
|
||||
SaveStaticMappings();
|
||||
@@ -667,6 +683,19 @@ public partial class WireMockServer
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Pact
|
||||
/// <summary>
|
||||
/// Save the mappings as a Pact Json file V2.
|
||||
/// </summary>
|
||||
/// <param name="folder">The folder to save the pact file.</param>
|
||||
/// <param name="filename">The filename for the .json file [optional].</param>
|
||||
[PublicAPI]
|
||||
public void SavePact(string folder, string? filename = null)
|
||||
{
|
||||
var (filenameUpdated, bytes) = PactMapper.ToPact(this, filename);
|
||||
_settings.FileSystemHandler.WriteFile(folder, filenameUpdated, bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This stores details about the consumer of the interaction.
|
||||
/// </summary>
|
||||
@@ -688,7 +717,7 @@ public partial class WireMockServer
|
||||
Provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
private IRequestBuilder? InitRequestBuilder(RequestModel requestModel, bool pathOrUrlRequired)
|
||||
{
|
||||
IRequestBuilder requestBuilder = Request.Create();
|
||||
@@ -904,68 +933,6 @@ public partial class WireMockServer
|
||||
return responseBuilder;
|
||||
}
|
||||
|
||||
private ResponseMessage ToJson<T>(T result, bool keepNullValues = false)
|
||||
{
|
||||
return new ResponseMessage
|
||||
{
|
||||
BodyData = new BodyData
|
||||
{
|
||||
DetectedBodyType = BodyType.String,
|
||||
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault)
|
||||
},
|
||||
StatusCode = (int)HttpStatusCode.OK,
|
||||
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(ContentTypeJson) } }
|
||||
};
|
||||
}
|
||||
|
||||
private Encoding? ToEncoding(EncodingModel? encodingModel)
|
||||
{
|
||||
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
|
||||
}
|
||||
|
||||
private T? DeserializeObject<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String)
|
||||
{
|
||||
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString);
|
||||
}
|
||||
|
||||
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json)
|
||||
{
|
||||
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>();
|
||||
}
|
||||
|
||||
return default(T);
|
||||
}
|
||||
|
||||
private T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json)
|
||||
{
|
||||
var bodyAsJson = requestMessage.BodyData.BodyAsJson;
|
||||
|
||||
return DeserializeObjectToArray<T>(bodyAsJson);
|
||||
}
|
||||
|
||||
return default(T[]);
|
||||
}
|
||||
|
||||
private T[] DeserializeObjectToArray<T>(object value)
|
||||
{
|
||||
if (value is JArray jArray)
|
||||
{
|
||||
return jArray.ToObject<T[]>();
|
||||
}
|
||||
|
||||
var singleResult = ((JObject)value).ToObject<T>();
|
||||
return new[] { singleResult };
|
||||
}
|
||||
|
||||
private T[] DeserializeJsonToArray<T>(string value)
|
||||
{
|
||||
return DeserializeObjectToArray<T>(JsonUtils.DeserializeObject(value));
|
||||
}
|
||||
|
||||
private void DisposeEnhancedFileSystemWatcher()
|
||||
{
|
||||
if (_enhancedFileSystemWatcher != null)
|
||||
@@ -1012,4 +979,66 @@ public partial class WireMockServer
|
||||
DeleteMapping(args.FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static Encoding? ToEncoding(EncodingModel? encodingModel)
|
||||
{
|
||||
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
|
||||
}
|
||||
|
||||
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false)
|
||||
{
|
||||
return new ResponseMessage
|
||||
{
|
||||
BodyData = new BodyData
|
||||
{
|
||||
DetectedBodyType = BodyType.String,
|
||||
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault)
|
||||
},
|
||||
StatusCode = (int)HttpStatusCode.OK,
|
||||
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
|
||||
};
|
||||
}
|
||||
|
||||
private static T? DeserializeObject<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String)
|
||||
{
|
||||
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString);
|
||||
}
|
||||
|
||||
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json)
|
||||
{
|
||||
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>();
|
||||
}
|
||||
|
||||
return default(T);
|
||||
}
|
||||
|
||||
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json)
|
||||
{
|
||||
var bodyAsJson = requestMessage.BodyData.BodyAsJson;
|
||||
|
||||
return DeserializeObjectToArray<T>(bodyAsJson);
|
||||
}
|
||||
|
||||
return default(T[]);
|
||||
}
|
||||
|
||||
private static T[] DeserializeJsonToArray<T>(string value)
|
||||
{
|
||||
return DeserializeObjectToArray<T>(JsonUtils.DeserializeObject(value));
|
||||
}
|
||||
|
||||
private static T[] DeserializeObjectToArray<T>(object value)
|
||||
{
|
||||
if (value is JArray jArray)
|
||||
{
|
||||
return jArray.ToObject<T[]>();
|
||||
}
|
||||
|
||||
var singleResult = ((JObject)value).ToObject<T>();
|
||||
return new[] { singleResult };
|
||||
}
|
||||
}
|
||||
@@ -5,87 +5,89 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Stef.Validation;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Matchers.Request;
|
||||
|
||||
namespace WireMock.Server
|
||||
namespace WireMock.Server;
|
||||
|
||||
public partial class WireMockServer
|
||||
{
|
||||
public partial class WireMockServer
|
||||
/// <inheritdoc cref="IWireMockServer.LogEntriesChanged" />
|
||||
[PublicAPI]
|
||||
public event NotifyCollectionChangedEventHandler LogEntriesChanged
|
||||
{
|
||||
/// <inheritdoc cref="IWireMockServer.LogEntriesChanged" />
|
||||
[PublicAPI]
|
||||
public event NotifyCollectionChangedEventHandler LogEntriesChanged
|
||||
add
|
||||
{
|
||||
add
|
||||
_options.LogEntries.CollectionChanged += (sender, eventRecordArgs) =>
|
||||
{
|
||||
_options.LogEntries.CollectionChanged += (sender, eventRecordArgs) =>
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
value(sender, eventRecordArgs);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
remove => _options.LogEntries.CollectionChanged -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IWireMockServer.LogEntries" />
|
||||
[PublicAPI]
|
||||
public IEnumerable<ILogEntry> LogEntries => new ReadOnlyCollection<LogEntry>(_options.LogEntries.ToList());
|
||||
|
||||
/// <summary>
|
||||
/// The search log-entries based on matchers.
|
||||
/// </summary>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
/// <returns>The <see cref="IEnumerable"/>.</returns>
|
||||
[PublicAPI]
|
||||
public IEnumerable<LogEntry> FindLogEntries([NotNull] params IRequestMatcher[] matchers)
|
||||
{
|
||||
var results = new Dictionary<LogEntry, RequestMatchResult>();
|
||||
|
||||
foreach (var log in _options.LogEntries.ToList())
|
||||
{
|
||||
var requestMatchResult = new RequestMatchResult();
|
||||
foreach (var matcher in matchers)
|
||||
{
|
||||
matcher.GetMatchingScore(log.RequestMessage, requestMatchResult);
|
||||
value(sender, eventRecordArgs);
|
||||
}
|
||||
|
||||
if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect)
|
||||
catch (Exception exception)
|
||||
{
|
||||
results.Add(log, requestMatchResult);
|
||||
_options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<LogEntry>(results.OrderBy(x => x.Value).Select(x => x.Key).ToList());
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IWireMockServer.ResetLogEntries" />
|
||||
[PublicAPI]
|
||||
public void ResetLogEntries()
|
||||
{
|
||||
_options.LogEntries.Clear();
|
||||
}
|
||||
remove => _options.LogEntries.CollectionChanged -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IWireMockServer.DeleteLogEntry" />
|
||||
[PublicAPI]
|
||||
public bool DeleteLogEntry(Guid guid)
|
||||
/// <inheritdoc cref="IWireMockServer.LogEntries" />
|
||||
[PublicAPI]
|
||||
public IEnumerable<ILogEntry> LogEntries => new ReadOnlyCollection<LogEntry>(_options.LogEntries.ToList());
|
||||
|
||||
/// <summary>
|
||||
/// The search log-entries based on matchers.
|
||||
/// </summary>
|
||||
/// <param name="matchers">The matchers.</param>
|
||||
/// <returns>The <see cref="IEnumerable"/>.</returns>
|
||||
[PublicAPI]
|
||||
public IEnumerable<LogEntry> FindLogEntries(params IRequestMatcher[] matchers)
|
||||
{
|
||||
Guard.NotNull(matchers);
|
||||
|
||||
var results = new Dictionary<LogEntry, RequestMatchResult>();
|
||||
|
||||
foreach (var log in _options.LogEntries.ToList())
|
||||
{
|
||||
// Check a LogEntry exists with the same GUID, if so, remove it.
|
||||
var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid);
|
||||
if (existing != null)
|
||||
var requestMatchResult = new RequestMatchResult();
|
||||
foreach (var matcher in matchers)
|
||||
{
|
||||
_options.LogEntries.Remove(existing);
|
||||
return true;
|
||||
matcher.GetMatchingScore(log.RequestMessage, requestMatchResult);
|
||||
}
|
||||
|
||||
return false;
|
||||
if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect)
|
||||
{
|
||||
results.Add(log, requestMatchResult);
|
||||
}
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<LogEntry>(results.OrderBy(x => x.Value).Select(x => x.Key).ToList());
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IWireMockServer.ResetLogEntries" />
|
||||
[PublicAPI]
|
||||
public void ResetLogEntries()
|
||||
{
|
||||
_options.LogEntries.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IWireMockServer.DeleteLogEntry" />
|
||||
[PublicAPI]
|
||||
public bool DeleteLogEntry(Guid guid)
|
||||
{
|
||||
// Check a LogEntry exists with the same GUID, if so, remove it.
|
||||
var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid);
|
||||
if (existing != null)
|
||||
{
|
||||
_options.LogEntries.Remove(existing);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,49 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WireMock.Pact.Models.V2;
|
||||
using WireMock.Serialization;
|
||||
|
||||
namespace WireMock.Util;
|
||||
|
||||
internal static class JsonUtils
|
||||
{
|
||||
public static bool TryParseAsComplexObject(string strInput, [NotNullWhen(true)] out JToken? token)
|
||||
public static Type CreateTypeFromJObject(JObject instance, string? fullName = null)
|
||||
{
|
||||
token = null;
|
||||
static Type ConvertType(JToken value, string? propertyName = null)
|
||||
{
|
||||
var type = value.Type;
|
||||
return type switch
|
||||
{
|
||||
JTokenType.Array => value.HasValues ? ConvertType(value.First!, propertyName).MakeArrayType() : typeof(object).MakeArrayType(),
|
||||
JTokenType.Boolean => typeof(bool),
|
||||
JTokenType.Bytes => typeof(byte[]),
|
||||
JTokenType.Date => typeof(DateTime),
|
||||
JTokenType.Guid => typeof(Guid),
|
||||
JTokenType.Float => typeof(float),
|
||||
JTokenType.Integer => typeof(long),
|
||||
JTokenType.Null => typeof(object),
|
||||
JTokenType.Object => CreateTypeFromJObject((JObject)value, propertyName),
|
||||
JTokenType.String => typeof(string),
|
||||
JTokenType.TimeSpan => typeof(TimeSpan),
|
||||
JTokenType.Uri => typeof(string),
|
||||
_ => typeof(object)
|
||||
};
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(strInput))
|
||||
var properties = new Dictionary<string, Type>();
|
||||
foreach (var item in instance.Properties())
|
||||
{
|
||||
properties.Add(item.Name, ConvertType(item.Value, item.Name));
|
||||
}
|
||||
|
||||
return TypeBuilderUtils.BuildType(properties, fullName) ?? throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public static bool TryParseAsJObject(string? strInput, [NotNullWhen(true)] out JObject? value)
|
||||
{
|
||||
value = null;
|
||||
|
||||
if (strInput == null || string.IsNullOrWhiteSpace(strInput))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -30,7 +61,7 @@ internal static class JsonUtils
|
||||
try
|
||||
{
|
||||
// Try to convert this string into a JToken
|
||||
token = JToken.Parse(strInput);
|
||||
value = JObject.Parse(strInput);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -105,17 +136,19 @@ internal static class JsonUtils
|
||||
|
||||
private static void WalkNode(JToken node, string? path, string? propertyName, List<string> lines)
|
||||
{
|
||||
if (node.Type == JTokenType.Object)
|
||||
switch (node.Type)
|
||||
{
|
||||
ProcessObject(node, propertyName, lines);
|
||||
}
|
||||
else if (node.Type == JTokenType.Array)
|
||||
{
|
||||
ProcessArray(node, propertyName, lines);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessItem(node, path ?? "it", propertyName, lines);
|
||||
case JTokenType.Object:
|
||||
ProcessObject(node, propertyName, lines);
|
||||
break;
|
||||
|
||||
case JTokenType.Array:
|
||||
ProcessArray(node, propertyName, lines);
|
||||
break;
|
||||
|
||||
default:
|
||||
ProcessItem(node, path ?? "it", propertyName, lines);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +158,7 @@ internal static class JsonUtils
|
||||
var text = new StringBuilder("new (");
|
||||
|
||||
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
|
||||
foreach (JProperty child in node.Children<JProperty>().ToArray())
|
||||
foreach (var child in node.Children<JProperty>().ToArray())
|
||||
{
|
||||
WalkNode(child.Value, child.Path, child.Name, items);
|
||||
}
|
||||
@@ -147,8 +180,8 @@ internal static class JsonUtils
|
||||
var text = new StringBuilder("(new [] { ");
|
||||
|
||||
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
|
||||
int idx = 0;
|
||||
foreach (JToken child in node.Children().ToArray())
|
||||
var idx = 0;
|
||||
foreach (var child in node.Children().ToArray())
|
||||
{
|
||||
WalkNode(child, $"{node.Path}[{idx}]", null, items);
|
||||
idx++;
|
||||
@@ -165,50 +198,21 @@ internal static class JsonUtils
|
||||
lines.Add(text.ToString());
|
||||
}
|
||||
|
||||
private static void ProcessItem(JToken node, string path, string propertyName, List<string> lines)
|
||||
private static void ProcessItem(JToken node, string path, string? propertyName, List<string> lines)
|
||||
{
|
||||
string castText;
|
||||
switch (node.Type)
|
||||
var castText = node.Type switch
|
||||
{
|
||||
case JTokenType.Boolean:
|
||||
castText = $"bool({path})";
|
||||
break;
|
||||
|
||||
case JTokenType.Date:
|
||||
castText = $"DateTime({path})";
|
||||
break;
|
||||
|
||||
case JTokenType.Float:
|
||||
castText = $"double({path})";
|
||||
break;
|
||||
|
||||
case JTokenType.Guid:
|
||||
castText = $"Guid({path})";
|
||||
break;
|
||||
|
||||
case JTokenType.Integer:
|
||||
castText = $"long({path})";
|
||||
break;
|
||||
|
||||
case JTokenType.Null:
|
||||
castText = "null";
|
||||
break;
|
||||
|
||||
case JTokenType.String:
|
||||
castText = $"string({path})";
|
||||
break;
|
||||
|
||||
case JTokenType.TimeSpan:
|
||||
castText = $"TimeSpan({path})";
|
||||
break;
|
||||
|
||||
case JTokenType.Uri:
|
||||
castText = $"Uri({path})";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"JTokenType '{node.Type}' cannot be converted to a Dynamic Linq cast operator.");
|
||||
}
|
||||
JTokenType.Boolean => $"bool({path})",
|
||||
JTokenType.Date => $"DateTime({path})",
|
||||
JTokenType.Float => $"double({path})",
|
||||
JTokenType.Guid => $"Guid({path})",
|
||||
JTokenType.Integer => $"long({path})",
|
||||
JTokenType.Null => "null",
|
||||
JTokenType.String => $"string({path})",
|
||||
JTokenType.TimeSpan => $"TimeSpan({path})",
|
||||
JTokenType.Uri => $"Uri({path})",
|
||||
_ => throw new NotSupportedException($"JTokenType '{node.Type}' cannot be converted to a Dynamic Linq cast operator.")
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(propertyName))
|
||||
{
|
||||
|
||||
8
src/WireMock.Net/Util/SystemUtils.cs
Normal file
8
src/WireMock.Net/Util/SystemUtils.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace WireMock.Util;
|
||||
|
||||
internal static class SystemUtils
|
||||
{
|
||||
public static readonly string Version = typeof(SystemUtils).GetTypeInfo().Assembly.GetName().Version.ToString();
|
||||
}
|
||||
118
src/WireMock.Net/Util/TypeBuilderUtils.cs
Normal file
118
src/WireMock.Net/Util/TypeBuilderUtils.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace WireMock.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Code based on https://stackoverflow.com/questions/40507909/convert-jobject-to-anonymous-object
|
||||
/// </summary>
|
||||
internal static class TypeBuilderUtils
|
||||
{
|
||||
private static readonly ConcurrentDictionary<IDictionary<string, Type>, Type> Types = new();
|
||||
|
||||
private static readonly ModuleBuilder ModuleBuilder =
|
||||
AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("WireMock.Net.Reflection"), AssemblyBuilderAccess.Run)
|
||||
.DefineDynamicModule("WireMock.Net.Reflection.Module");
|
||||
|
||||
public static Type BuildType(IDictionary<string, Type> properties, string? name = null)
|
||||
{
|
||||
var keyExists = Types.Keys.FirstOrDefault(k => Compare(k, properties));
|
||||
if (keyExists != null)
|
||||
{
|
||||
return Types[keyExists];
|
||||
}
|
||||
|
||||
var typeBuilder = GetTypeBuilder(name ?? Guid.NewGuid().ToString());
|
||||
foreach (var property in properties)
|
||||
{
|
||||
CreateGetSetMethods(typeBuilder, property.Key, property.Value);
|
||||
}
|
||||
|
||||
var type = typeBuilder.CreateTypeInfo().AsType();
|
||||
|
||||
Types.TryAdd(properties, type);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://stackoverflow.com/questions/3804367/testing-for-equality-between-dictionaries-in-c-sharp
|
||||
/// </summary>
|
||||
private static bool Compare<TKey, TValue>(IDictionary<TKey, TValue> dict1, IDictionary<TKey, TValue> dict2)
|
||||
{
|
||||
if (dict1 == dict2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dict1.Count != dict2.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueComparer = EqualityComparer<TValue>.Default;
|
||||
|
||||
foreach (var kvp in dict1)
|
||||
{
|
||||
if (!dict2.TryGetValue(kvp.Key, out var value2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!valueComparer.Equals(kvp.Value, value2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static TypeBuilder GetTypeBuilder(string name)
|
||||
{
|
||||
return ModuleBuilder.DefineType(name,
|
||||
TypeAttributes.Public |
|
||||
TypeAttributes.Class |
|
||||
TypeAttributes.AutoClass |
|
||||
TypeAttributes.AnsiClass |
|
||||
TypeAttributes.BeforeFieldInit |
|
||||
TypeAttributes.AutoLayout,
|
||||
null);
|
||||
}
|
||||
|
||||
private static void CreateGetSetMethods(TypeBuilder typeBuilder, string propertyName, Type propertyType)
|
||||
{
|
||||
var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
|
||||
|
||||
var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
|
||||
|
||||
var getPropertyMethodBuilder = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
|
||||
var getIl = getPropertyMethodBuilder.GetILGenerator();
|
||||
|
||||
getIl.Emit(OpCodes.Ldarg_0);
|
||||
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
|
||||
getIl.Emit(OpCodes.Ret);
|
||||
|
||||
var setPropertyMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { propertyType });
|
||||
var setIl = setPropertyMethodBuilder.GetILGenerator();
|
||||
var modifyProperty = setIl.DefineLabel();
|
||||
|
||||
var exitSet = setIl.DefineLabel();
|
||||
|
||||
setIl.MarkLabel(modifyProperty);
|
||||
setIl.Emit(OpCodes.Ldarg_0);
|
||||
setIl.Emit(OpCodes.Ldarg_1);
|
||||
setIl.Emit(OpCodes.Stfld, fieldBuilder);
|
||||
|
||||
setIl.Emit(OpCodes.Nop);
|
||||
setIl.MarkLabel(exitSet);
|
||||
setIl.Emit(OpCodes.Ret);
|
||||
|
||||
propertyBuilder.SetGetMethod(getPropertyMethodBuilder);
|
||||
propertyBuilder.SetSetMethod(setPropertyMethodBuilder);
|
||||
}
|
||||
}
|
||||
@@ -58,11 +58,12 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NJsonSchema.Extensions" Version="0.1.0" />
|
||||
<PackageReference Include="NSwag.Core" Version="13.15.10" />
|
||||
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
||||
<PackageReference Include="Stef.Validation" Version="0.1.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.18" />
|
||||
<!--<PackageReference Include="RandomDataGenerator.Net" Version="1.0.15" />-->
|
||||
<PackageReference Include="JmesPath.Net" Version="1.0.125" />
|
||||
<PackageReference Include="AnyOf" Version="0.3.0" />
|
||||
<PackageReference Include="TinyMapper" Version="3.0.3" />
|
||||
|
||||
Reference in New Issue
Block a user