mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-05-30 10:30:45 +02:00
Add SystemTextJsonMatchers (#1447)
* SystemTextJsonMatcher * , * . * new projectx * Update test/WireMock.Net.Tests/Pact/PactTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/WireMock.Net/WireMock.Net.csproj Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/WireMock.Net.Minimal/Properties/AssemblyInfo.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * . * more tests * . * . * x * ... * . * fix tests * 0.11.0 * . * delete jsonutils.cs * s * fix findings * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Potential fix for pull request finding 'Missing Dispose call on local IDisposable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * JsonConverter 0.12.0 * -- tools --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
This commit is contained in:
@@ -31,19 +31,19 @@ public partial class WireMockAssertions
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(IJsonMatcher matcher, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
|
||||
|
||||
@@ -126,15 +126,44 @@ public partial class WireMockAssertions
|
||||
|
||||
private static string? FormatBody(object? body)
|
||||
{
|
||||
return body switch
|
||||
if (body == null)
|
||||
{
|
||||
null => null,
|
||||
string str => str,
|
||||
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
|
||||
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
|
||||
JToken jToken => jToken.ToString(Formatting.None),
|
||||
_ => JToken.FromObject(body).ToString(Formatting.None)
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
if (body is string str)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
if (body is AnyOf<string, StringPattern>[] stringPatterns)
|
||||
{
|
||||
return FormatBodies(stringPatterns.Select(p => p.GetPattern()));
|
||||
}
|
||||
|
||||
if (body is byte[] bytes)
|
||||
{
|
||||
return $"byte[{bytes.Length}] {{...}}";
|
||||
}
|
||||
|
||||
if (body is JToken jToken)
|
||||
{
|
||||
return jToken.ToString(Formatting.None);
|
||||
}
|
||||
|
||||
// System.IO.FileNotFoundException : Could not load file or assembly 'System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
|
||||
var typeName = body.GetType().FullName;
|
||||
if (typeName == "System.Text.Json.JsonElement")
|
||||
{
|
||||
return ((dynamic)body).GetRawText();
|
||||
}
|
||||
|
||||
if (typeName == "System.Text.Json.JsonDocument")
|
||||
{
|
||||
return ((dynamic)body).RootElement.GetRawText();
|
||||
}
|
||||
|
||||
return JToken.FromObject(body).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
private static string? FormatBodies(IEnumerable<object?> bodies)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description>
|
||||
<Authors>Gennadii Saltyshchak</Authors>
|
||||
@@ -25,7 +25,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonConverter.Abstractions" Version="0.9.0" />
|
||||
<PackageReference Include="JsonConverter.Abstractions" Version="0.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -31,19 +31,19 @@ public partial class WireMockAssertions
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAssertions> WithBodyAsJson(IJsonMatcher matcher, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
|
||||
|
||||
@@ -126,15 +126,44 @@ public partial class WireMockAssertions
|
||||
|
||||
private static string? FormatBody(object? body)
|
||||
{
|
||||
return body switch
|
||||
if (body == null)
|
||||
{
|
||||
null => null,
|
||||
string str => str,
|
||||
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
|
||||
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
|
||||
JToken jToken => jToken.ToString(Formatting.None),
|
||||
_ => JToken.FromObject(body).ToString(Formatting.None)
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
if (body is string str)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
if (body is AnyOf<string, StringPattern>[] stringPatterns)
|
||||
{
|
||||
return FormatBodies(stringPatterns.Select(p => p.GetPattern()));
|
||||
}
|
||||
|
||||
if (body is byte[] bytes)
|
||||
{
|
||||
return $"byte[{bytes.Length}] {{...}}";
|
||||
}
|
||||
|
||||
if (body is JToken jToken)
|
||||
{
|
||||
return jToken.ToString(Formatting.None);
|
||||
}
|
||||
|
||||
// System.IO.FileNotFoundException : Could not load file or assembly 'System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
|
||||
var typeName = body.GetType().FullName;
|
||||
if (typeName == "System.Text.Json.JsonElement")
|
||||
{
|
||||
return ((dynamic)body).GetRawText();
|
||||
}
|
||||
|
||||
if (typeName == "System.Text.Json.JsonDocument")
|
||||
{
|
||||
return ((dynamic)body).RootElement.GetRawText();
|
||||
}
|
||||
|
||||
return JToken.FromObject(body).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
private static string? FormatBodies(IEnumerable<object?> bodies)
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using AnyOfTypes;
|
||||
using Json.Path;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Models;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// SystemTextJsonPathMatcher - behaves the same as JsonPathMatcher but uses System.Text.Json and Json.Path instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISystemTextJsonPathMatcher" />
|
||||
public class SystemTextJsonPathMatcher : ISystemTextJsonPathMatcher
|
||||
{
|
||||
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="SystemTextJsonPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public SystemTextJsonPathMatcher(params string[] patterns)
|
||||
: this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns.ToAnyOfPatterns())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonPathMatcher"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patterns">The patterns.</param>
|
||||
public SystemTextJsonPathMatcher(params AnyOf<string, StringPattern>[] patterns)
|
||||
: this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonPathMatcher"/> 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 SystemTextJsonPathMatcher(
|
||||
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 node = JsonNode.Parse(input!);
|
||||
score = IsMatchInternal(node);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return MatchResult.From(Name, 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
|
||||
{
|
||||
JsonNode? node = input switch
|
||||
{
|
||||
JsonNode jsonNode => jsonNode,
|
||||
string str => JsonNode.Parse(str),
|
||||
_ => JsonNode.Parse(JsonSerializer.Serialize(input))
|
||||
};
|
||||
|
||||
score = IsMatchInternal(node);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public AnyOf<string, StringPattern>[] GetPatterns()
|
||||
{
|
||||
return _patterns;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MatchOperator MatchOperator { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(SystemTextJsonPathMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCSharpCodeArguments()
|
||||
{
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
|
||||
$")";
|
||||
}
|
||||
|
||||
private double IsMatchInternal(JsonNode? node)
|
||||
{
|
||||
// JsonPath.Net requires the node to be inside an object or array for filter expressions.
|
||||
// Similar to JsonPathMatcher's ConvertJTokenToJArrayIfNeeded, wrap a plain object in an array
|
||||
// when it's an object with a single non-array child property.
|
||||
var evaluationNode = WrapIfNeeded(node);
|
||||
|
||||
var values = _patterns
|
||||
.Select(pattern =>
|
||||
{
|
||||
var path = JsonPath.Parse(pattern.GetPattern());
|
||||
var result = path.Evaluate(evaluationNode);
|
||||
return result.Matches is { Count: > 0 };
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return MatchScores.ToScore(values, MatchOperator);
|
||||
}
|
||||
|
||||
// Mirrors JsonPathMatcher.ConvertJTokenToJArrayIfNeeded:
|
||||
// If the node is an object with exactly one property whose value is not already an array,
|
||||
// wrap that value in an array so that filter expressions (e.g. [?(@.x == y)]) can match.
|
||||
private static JsonNode? WrapIfNeeded(JsonNode? node)
|
||||
{
|
||||
if (node is not JsonObject obj)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var properties = obj.ToList();
|
||||
if (properties.Count != 1)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var single = properties[0];
|
||||
if (single.Value is JsonArray)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
var clonedValue = JsonNode.Parse(single.Value?.ToJsonString() ?? "null");
|
||||
return new JsonObject
|
||||
{
|
||||
[single.Key] = new JsonArray(clonedValue)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>A SystemTextJsonPathMatcher which can be used to match WireMock.Net Requests using JsonPath.Net.</Description>
|
||||
<AssemblyTitle>WireMock.Net.Matchers.SystemTextJsonPath</AssemblyTitle>
|
||||
<Authors>Stef Heyenrath</Authors>
|
||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>wiremock;matchers;matcher;jsonpath;systemtextjson</PackageTags>
|
||||
<RootNamespace>WireMock</RootNamespace>
|
||||
<PackageId>WireMock.Net.Matchers.SystemTextJsonPath</PackageId>
|
||||
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
|
||||
<!--<DelaySign>true</DelaySign>-->
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug - Sonar'">
|
||||
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WireMock.Net.Shared\WireMock.Net.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonPath.Net" Version="3.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JsonConverter.Abstractions;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using WireMock.Matchers.Helpers;
|
||||
using WireMock.Models.Mime;
|
||||
using WireMock.Util;
|
||||
@@ -15,6 +16,8 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
{
|
||||
private readonly IList<(string Name, Func<IMimePartData, MatchResult> func)> _matcherFunctions;
|
||||
|
||||
private readonly IJsonConverter _jsonConverter;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => nameof(MimePartMatcher);
|
||||
|
||||
@@ -41,7 +44,8 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
IStringMatcher? contentTypeMatcher,
|
||||
IStringMatcher? contentDispositionMatcher,
|
||||
IStringMatcher? contentTransferEncodingMatcher,
|
||||
IMatcher? contentMatcher
|
||||
IMatcher? contentMatcher,
|
||||
IJsonConverter? jsonConverter = null
|
||||
)
|
||||
{
|
||||
MatchBehaviour = matchBehaviour;
|
||||
@@ -49,6 +53,7 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
ContentDispositionMatcher = contentDispositionMatcher;
|
||||
ContentTransferEncodingMatcher = contentTransferEncodingMatcher;
|
||||
ContentMatcher = contentMatcher;
|
||||
_jsonConverter = jsonConverter ?? new NewtonsoftJsonConverter();
|
||||
|
||||
_matcherFunctions = [];
|
||||
if (ContentTypeMatcher != null)
|
||||
@@ -107,7 +112,8 @@ public class MimePartMatcher : IMimePartMatcher
|
||||
ContentType = GetContentTypeAsString(mimePart.ContentType),
|
||||
DeserializeJson = true,
|
||||
ContentEncoding = null, // mimePart.ContentType?.CharsetEncoding.ToString(),
|
||||
DecompressGZipAndDeflate = true
|
||||
DecompressGZipAndDeflate = true,
|
||||
DefaultJsonConverter = _jsonConverter
|
||||
};
|
||||
|
||||
var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using JsonConverter.Abstractions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Http;
|
||||
@@ -15,7 +14,8 @@ internal static class HttpResponseMessageHelper
|
||||
Uri originalUri,
|
||||
bool deserializeJson,
|
||||
bool decompressGzipAndDeflate,
|
||||
bool deserializeFormUrlEncoded)
|
||||
bool deserializeFormUrlEncoded,
|
||||
IJsonConverter jsonConverter)
|
||||
{
|
||||
var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode };
|
||||
|
||||
@@ -45,7 +45,8 @@ internal static class HttpResponseMessageHelper
|
||||
DeserializeJson = deserializeJson,
|
||||
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
|
||||
DecompressGZipAndDeflate = decompressGzipAndDeflate,
|
||||
DeserializeFormUrlEncoded = deserializeFormUrlEncoded
|
||||
DeserializeFormUrlEncoded = deserializeFormUrlEncoded,
|
||||
DefaultJsonConverter = jsonConverter
|
||||
};
|
||||
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WireMock.Util;
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Text.Json;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// Generic AbstractSystemTextJsonPartialMatcher - uses System.Text.Json instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
public abstract class AbstractSystemTextJsonPartialMatcher : SystemTextJsonMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
protected AbstractSystemTextJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
protected AbstractSystemTextJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
|
||||
/// </summary>
|
||||
protected AbstractSystemTextJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(matchBehaviour, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsMatch(JsonElement value, JsonElement? input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputElement = input.Value;
|
||||
|
||||
// Regex on a string value
|
||||
if (Regex && value.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var valueAsString = value.GetString()!;
|
||||
var inputAsString = GetStringValue(inputElement);
|
||||
|
||||
var (valid, result) = RegexUtils.MatchRegex(valueAsString, inputAsString);
|
||||
if (valid)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Guid comparison: both strings, both parseable as Guid
|
||||
if (value.ValueKind == JsonValueKind.String && inputElement.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var valueStr = value.GetString()!;
|
||||
var inputStr = inputElement.GetString()!;
|
||||
if (Guid.TryParse(valueStr, out var vg) && Guid.TryParse(inputStr, out var ig))
|
||||
{
|
||||
return IsMatch(vg.ToString(), ig.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Type mismatch (after regex/guid checks)
|
||||
if (value.ValueKind != inputElement.ValueKind)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (value.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
{
|
||||
var nestedValues = value.EnumerateObject().ToArray();
|
||||
if (nestedValues.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return nestedValues.All(pair =>
|
||||
{
|
||||
var selected = SelectElement(inputElement, pair.Name);
|
||||
return selected != null && IsMatch(pair.Value, selected.Value);
|
||||
});
|
||||
}
|
||||
|
||||
case JsonValueKind.Array:
|
||||
{
|
||||
var valuesArray = value.EnumerateArray().ToArray();
|
||||
if (valuesArray.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var tokenArray = inputElement.EnumerateArray().ToArray();
|
||||
if (tokenArray.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken)));
|
||||
}
|
||||
|
||||
default:
|
||||
return IsMatch(GetStringValue(value), GetStringValue(inputElement));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two strings are a match (matching can be done exact or wildcard).
|
||||
/// </summary>
|
||||
protected abstract bool IsMatch(string value, string input);
|
||||
|
||||
/// <summary>
|
||||
/// Selects a <see cref="JsonElement"/> from an object using a key that may be a plain property name,
|
||||
/// a dotted path (e.g. "a.b.c") or bracket notation (e.g. "['name.with.dot']"),
|
||||
/// mirroring Newtonsoft's <c>SelectToken</c> + direct indexer fallback.
|
||||
/// </summary>
|
||||
private static JsonElement? SelectElement(JsonElement input, string key)
|
||||
{
|
||||
if (input.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Direct property access (also handles keys containing colons or dots that are literal property names)
|
||||
if (input.TryGetProperty(key, out var direct))
|
||||
{
|
||||
return direct;
|
||||
}
|
||||
|
||||
// Bracket notation: ['property.name.with.dots']
|
||||
if (key.StartsWith("['") && key.EndsWith("']"))
|
||||
{
|
||||
var propertyName = key.Substring(2, key.Length - 4);
|
||||
return input.TryGetProperty(propertyName, out var bracketValue) ? bracketValue : null;
|
||||
}
|
||||
|
||||
// Dotted path: a.b.c
|
||||
if (key.Contains('.'))
|
||||
{
|
||||
var parts = key.Split('.');
|
||||
var current = input;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (current.ValueKind != JsonValueKind.Object || !current.TryGetProperty(part, out var next))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetStringValue(JsonElement element)
|
||||
{
|
||||
return element.ValueKind == JsonValueKind.String
|
||||
? element.GetString()!
|
||||
: element.GetRawText();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using AnyOfTypes;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
@@ -13,9 +12,8 @@ namespace WireMock.Matchers;
|
||||
/// <summary>
|
||||
/// JsonPathMatcher
|
||||
/// </summary>
|
||||
/// <seealso cref="IStringMatcher" />
|
||||
/// <seealso cref="IObjectMatcher" />
|
||||
public class JsonPathMatcher : IStringMatcher, IObjectMatcher
|
||||
/// <seealso cref="IJsonPathMatcher" />
|
||||
public class JsonPathMatcher : IJsonPathMatcher
|
||||
{
|
||||
private readonly AnyOf<string, StringPattern>[] _patterns;
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Serialization;
|
||||
using WireMock.Util;
|
||||
using JsonUtils = WireMock.Util.JsonUtils;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
@@ -69,7 +70,7 @@ public class JsonMatcher : IJsonMatcher
|
||||
Regex = regex;
|
||||
|
||||
Value = value;
|
||||
_valueAsJToken = JsonUtils.ConvertValueToJToken(value);
|
||||
_valueAsJToken = ConvertValueToJToken(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -83,7 +84,7 @@ public class JsonMatcher : IJsonMatcher
|
||||
{
|
||||
try
|
||||
{
|
||||
var inputAsJToken = JsonUtils.ConvertValueToJToken(input);
|
||||
var inputAsJToken = ConvertValueToJToken(input);
|
||||
|
||||
var match = IsMatch(RenameJToken(_valueAsJToken), RenameJToken(inputAsJToken));
|
||||
score = MatchScores.ToScore(match);
|
||||
@@ -103,7 +104,7 @@ public class JsonMatcher : IJsonMatcher
|
||||
return $"new {Name}" +
|
||||
$"(" +
|
||||
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
|
||||
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
|
||||
$")";
|
||||
@@ -241,6 +242,18 @@ public class JsonMatcher : IJsonMatcher
|
||||
return new JObject(renamedProperties);
|
||||
}
|
||||
|
||||
private static JToken ConvertValueToJToken(object value)
|
||||
{
|
||||
// Check if JToken, string, IEnumerable or object
|
||||
return value switch
|
||||
{
|
||||
JToken tokenValue => tokenValue,
|
||||
string stringValue => JsonConvert.DeserializeObject<JToken>(stringValue, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!,
|
||||
IEnumerable enumerableValue => JArray.FromObject(enumerableValue),
|
||||
_ => JObject.FromObject(value),
|
||||
};
|
||||
}
|
||||
|
||||
private static string? ToUpper(string? input)
|
||||
{
|
||||
return input?.ToUpperInvariant();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Matchers.Helpers;
|
||||
using WireMock.Util;
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections;
|
||||
using System.Text.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// SystemTextJsonMatcher - behaves the same as <see cref="JsonMatcher"/> but uses System.Text.Json instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
public class SystemTextJsonMatcher : IJsonMatcher
|
||||
{
|
||||
private static readonly JsonSerializerOptions DefaultSerializerOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = false
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Name => nameof(SystemTextJsonMatcher);
|
||||
|
||||
/// <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 JsonElement _valueAsJsonElement;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> 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 SystemTextJsonMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> 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 SystemTextJsonMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> 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 SystemTextJsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
|
||||
{
|
||||
Guard.NotNull(value);
|
||||
|
||||
MatchBehaviour = matchBehaviour;
|
||||
IgnoreCase = ignoreCase;
|
||||
Regex = regex;
|
||||
|
||||
Value = value;
|
||||
_valueAsJsonElement = ConvertToJsonElement(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 inputAsJsonElement = ConvertToJsonElement(input);
|
||||
|
||||
var match = IsMatch(NormalizeElement(_valueAsJsonElement), NormalizeElement(inputAsJsonElement));
|
||||
score = MatchScores.ToScore(match);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return MatchResult.From(Name, 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>
|
||||
protected virtual bool IsMatch(JsonElement value, JsonElement? input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputElement = input.Value;
|
||||
|
||||
// If using Regex and the value is a string, use the MatchRegex method.
|
||||
if (Regex && value.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var valueAsString = value.GetString()!;
|
||||
var inputAsString = inputElement.ValueKind == JsonValueKind.String
|
||||
? inputElement.GetString()!
|
||||
: inputElement.GetRawText();
|
||||
|
||||
var (valid, result) = RegexUtils.MatchRegex(valueAsString, inputAsString);
|
||||
if (valid)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// If the value is a Guid (string) and input is a string, or vice versa, compare as strings.
|
||||
if (value.ValueKind == JsonValueKind.String && inputElement.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var valueStr = value.GetString()!;
|
||||
var inputStr = inputElement.GetString()!;
|
||||
|
||||
if (Guid.TryParse(valueStr, out var valueGuid) && Guid.TryParse(inputStr, out var inputGuid))
|
||||
{
|
||||
return valueGuid == inputGuid;
|
||||
}
|
||||
}
|
||||
|
||||
switch (value.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
{
|
||||
if (inputElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueProperties = value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
|
||||
var inputProperties = inputElement.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
|
||||
|
||||
if (valueProperties.Count != inputProperties.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var pair in valueProperties)
|
||||
{
|
||||
if (!inputProperties.TryGetValue(pair.Key, out var inputPropValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMatch(pair.Value, inputPropValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case JsonValueKind.Array:
|
||||
{
|
||||
if (inputElement.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueArray = value.EnumerateArray().ToArray();
|
||||
var inputArray = inputElement.EnumerateArray().ToArray();
|
||||
|
||||
if (valueArray.Length != inputArray.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any();
|
||||
}
|
||||
|
||||
default:
|
||||
return value.GetRawText() == inputElement.GetRawText();
|
||||
}
|
||||
}
|
||||
|
||||
private JsonElement NormalizeElement(JsonElement element)
|
||||
{
|
||||
if (!IgnoreCase)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
var normalized = NormalizeValue(element);
|
||||
return ConvertToJsonElement(normalized);
|
||||
}
|
||||
|
||||
private object NormalizeValue(JsonElement element)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
{
|
||||
var dict = new Dictionary<string, object?>();
|
||||
foreach (var prop in element.EnumerateObject())
|
||||
{
|
||||
var normalizedKey = prop.Name.ToUpperInvariant();
|
||||
dict[normalizedKey] = NormalizeValue(prop.Value);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
case JsonValueKind.Array:
|
||||
{
|
||||
if (Regex)
|
||||
{
|
||||
return element.EnumerateArray().Select(e => (object)e.GetRawText()).ToArray();
|
||||
}
|
||||
|
||||
return element.EnumerateArray().Select(NormalizeValue).ToArray();
|
||||
}
|
||||
|
||||
case JsonValueKind.String:
|
||||
{
|
||||
var str = element.GetString()!;
|
||||
return Regex ? str : str.ToUpperInvariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return element.GetRawText();
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonElement ConvertToJsonElement(object value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case JsonElement jsonElement:
|
||||
return jsonElement;
|
||||
|
||||
case JsonDocument jsonDocument:
|
||||
return jsonDocument.RootElement;
|
||||
|
||||
case string stringValue:
|
||||
return JsonDocument.Parse(stringValue).RootElement;
|
||||
|
||||
case IEnumerable enumerableValue when value is not string:
|
||||
return JsonSerializer.SerializeToElement(enumerableValue, DefaultSerializerOptions);
|
||||
|
||||
default:
|
||||
var json = JsonSerializer.Serialize(value, DefaultSerializerOptions);
|
||||
return JsonDocument.Parse(json).RootElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// SystemTextJsonPartialMatcher - uses System.Text.Json instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
public class SystemTextJsonPartialMatcher : AbstractSystemTextJsonPartialMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => nameof(SystemTextJsonPartialMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialMatcher(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>
|
||||
/// SystemTextJsonPartialWildcardMatcher - uses System.Text.Json instead of Newtonsoft.Json.
|
||||
/// </summary>
|
||||
public class SystemTextJsonPartialWildcardMatcher : AbstractSystemTextJsonPartialMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => nameof(SystemTextJsonPartialWildcardMatcher);
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool regex = false)
|
||||
: base(value, ignoreCase, regex)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemTextJsonPartialWildcardMatcher(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)}" +
|
||||
$")";
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,8 @@ internal class OwinRequestMapper : IOwinRequestMapper
|
||||
ContentType = request.ContentType,
|
||||
DeserializeJson = !options.DisableJsonBodyParsing.GetValueOrDefault(false),
|
||||
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
|
||||
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false)
|
||||
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false),
|
||||
DefaultJsonConverter = options.DefaultJsonSerializer
|
||||
};
|
||||
|
||||
body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Owin.ActivityTracing;
|
||||
using WireMock.Serialization;
|
||||
@@ -42,7 +41,7 @@ internal class WireMockMiddlewareLogger(
|
||||
if (_options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true })
|
||||
{
|
||||
var filename = $"{logEntry.Guid}.LogEntry.json";
|
||||
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(logEntry));
|
||||
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, _options.DefaultJsonSerializer.Serialize(logEntry));
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
||||
@@ -7,4 +7,4 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
|
||||
// Needed for Moq in the UnitTest project
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
@@ -48,7 +48,8 @@ internal class ProxyHelper(WireMockServerSettings settings)
|
||||
originalUri,
|
||||
deserializeJson,
|
||||
decompressGzipAndDeflate,
|
||||
deserializeFormUrlEncoded
|
||||
deserializeFormUrlEncoded,
|
||||
_settings.DefaultJsonSerializer
|
||||
).ConfigureAwait(false);
|
||||
|
||||
IMapping? newMapping = null;
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
using System.Text;
|
||||
using JsonConverter.Abstractions;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Models;
|
||||
using WireMock.Serialization;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
@@ -119,11 +121,13 @@ public partial class Response
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
|
||||
public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null, IJsonConverter? jsonConverter = null, JsonConverterOptions? options = null)
|
||||
{
|
||||
Guard.NotNull(body);
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
jsonConverter ??= new NewtonsoftJsonConverter();
|
||||
options ??= JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone;
|
||||
|
||||
ResponseMessage.BodyDestination = destination;
|
||||
ResponseMessage.BodyData = new BodyData
|
||||
@@ -140,7 +144,7 @@ public partial class Response
|
||||
|
||||
case BodyDestinationFormat.Json:
|
||||
ResponseMessage.BodyData.DetectedBodyType = BodyType.Json;
|
||||
ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body);
|
||||
ResponseMessage.BodyData.BodyAsJson = jsonConverter.Deserialize<object>(body, options);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -197,7 +197,7 @@ public partial class Response : IResponseBuilder
|
||||
|
||||
if (ProxyAndRecordSettings != null && _httpClientForProxy != null)
|
||||
{
|
||||
string RemoveFirstOccurrence(string source, string find)
|
||||
static string RemoveFirstOccurrence(string source, string find)
|
||||
{
|
||||
int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase);
|
||||
return place >= 0 ? source.Remove(place, find.Length) : source;
|
||||
@@ -265,7 +265,7 @@ public partial class Response : IResponseBuilder
|
||||
var decoded = await protoBufMatcher.DecodeAsync(requestMessage.BodyData?.BodyAsBytes).ConfigureAwait(false);
|
||||
if (decoded != null)
|
||||
{
|
||||
requestMessageImplementation.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded);
|
||||
requestMessageImplementation.BodyAsJson = settings.DefaultJsonSerializer.ToJsonToken(decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,31 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using JsonConverter.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
|
||||
using System.Text.Json;
|
||||
#endif
|
||||
using JsonConverter.Abstractions.Models;
|
||||
|
||||
namespace WireMock.Serialization;
|
||||
|
||||
internal class MappingSerializer(IJsonConverter jsonConverter)
|
||||
{
|
||||
private static readonly JsonConverterOptions JsonConverterOptions = new JsonConverterOptions
|
||||
{
|
||||
DateParseHandling = (int) DateParseHandling.None
|
||||
};
|
||||
|
||||
internal T[] DeserializeJsonToArray<T>(string value)
|
||||
{
|
||||
// DeserializeObject
|
||||
return DeserializeObjectToArray<T>(jsonConverter.Deserialize<object>(value, JsonConverterOptions)!);
|
||||
switch (JsonTypeHelper.GetJsonType(value))
|
||||
{
|
||||
case JsonType.Array:
|
||||
return jsonConverter.Deserialize<T[]>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone)!;
|
||||
|
||||
case JsonType.Object:
|
||||
var singleResult = jsonConverter.Deserialize<T>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
|
||||
return [singleResult!];
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static T[] DeserializeObjectToArray<T>(object value)
|
||||
internal T[] DeserializeObjectToArray<T>(object value)
|
||||
{
|
||||
if (value is JArray jArray)
|
||||
{
|
||||
return jArray.ToObject<T[]>()!;
|
||||
}
|
||||
|
||||
if (value is JObject jObject)
|
||||
{
|
||||
var singleResult = jObject.ToObject<T>();
|
||||
return [singleResult!];
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
|
||||
if (value is JsonElement jElement)
|
||||
{
|
||||
if (jElement.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
return jElement.Deserialize<T[]>()!;
|
||||
}
|
||||
|
||||
if (jElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
var singleResult = jElement.Deserialize<T>();
|
||||
return [singleResult!];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
|
||||
var json = jsonConverter.Serialize(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
|
||||
return DeserializeJsonToArray<T>(json);
|
||||
}
|
||||
}
|
||||
@@ -106,9 +106,29 @@ internal class MatcherMapper
|
||||
var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
|
||||
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex);
|
||||
|
||||
case nameof(SystemTextJsonMatcher):
|
||||
var valueForSystemTextJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
|
||||
return new SystemTextJsonMatcher(matchBehaviour, valueForSystemTextJsonMatcher!, ignoreCase, useRegex);
|
||||
|
||||
case nameof(SystemTextJsonPartialMatcher):
|
||||
var valueForSystemTextJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
|
||||
return new SystemTextJsonPartialMatcher(matchBehaviour, valueForSystemTextJsonPartialMatcher!, ignoreCase, useRegex);
|
||||
|
||||
case nameof(SystemTextJsonPartialWildcardMatcher):
|
||||
var valueForSystemTextJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
|
||||
return new SystemTextJsonPartialWildcardMatcher(matchBehaviour, valueForSystemTextJsonPartialWildcardMatcher!, ignoreCase, useRegex);
|
||||
|
||||
case nameof(JsonPathMatcher):
|
||||
return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns);
|
||||
|
||||
case "SystemTextJsonPathMatcher":
|
||||
if (TypeLoader.TryLoadNewInstance<ISystemTextJsonPathMatcher>(out var systemTextJsonPathMatcher, matchBehaviour, matchOperator, stringPatterns))
|
||||
{
|
||||
return systemTextJsonPathMatcher;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The 'SystemTextJsonPathMatcher' cannot be loaded. Please install the WireMock.Net.Matchers.SystemTextJsonPath package.");
|
||||
|
||||
case nameof(JmesPathMatcher):
|
||||
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
|
||||
|
||||
@@ -171,6 +191,10 @@ internal class MatcherMapper
|
||||
model.Regex = jsonMatcher.Regex;
|
||||
break;
|
||||
|
||||
case SystemTextJsonMatcher stjMatcher:
|
||||
model.Regex = stjMatcher.Regex;
|
||||
break;
|
||||
|
||||
case XPathMatcher xpathMatcher:
|
||||
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Pact.Models.V2;
|
||||
@@ -49,7 +51,7 @@ internal static class PactMapper
|
||||
pact.Interactions.Add(interaction);
|
||||
}
|
||||
|
||||
return (filename, JsonUtils.SerializeAsPactFile(pact));
|
||||
return (filename, SerializeAsPactFile(pact));
|
||||
}
|
||||
|
||||
private static PactRequest MapRequest(RequestModel request, string path)
|
||||
@@ -152,7 +154,7 @@ internal static class PactMapper
|
||||
/// </summary>
|
||||
private static object? TryDeserializeJsonStringAsObject(string? value)
|
||||
{
|
||||
return value != null ? JsonUtils.TryDeserializeObject<object?>(value) ?? value : null;
|
||||
return value != null ? TryDeserializeObject<object?>(value) ?? value : null;
|
||||
}
|
||||
|
||||
//private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
|
||||
@@ -164,4 +166,22 @@ internal static class PactMapper
|
||||
|
||||
// return defaultValue;
|
||||
//}
|
||||
|
||||
private static byte[] SerializeAsPactFile(object value)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsPact);
|
||||
return Encoding.UTF8.GetBytes(json);
|
||||
}
|
||||
|
||||
private static T? TryDeserializeObject<T>(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NJsonSchema;
|
||||
using NJsonSchema.Extensions;
|
||||
using NSwag;
|
||||
@@ -281,7 +282,7 @@ internal static class SwaggerMapper
|
||||
if (matcher is { Name: nameof(JsonMatcher) })
|
||||
{
|
||||
var pattern = GetPatternAsStringFromMatcher(matcher);
|
||||
if (JsonUtils.TryParseAsJObject(pattern, out var jObject))
|
||||
if (TryParseAsJObject(pattern, out var jObject))
|
||||
{
|
||||
return jObject;
|
||||
}
|
||||
@@ -292,6 +293,39 @@ internal static class SwaggerMapper
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsJson(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = value!.Trim();
|
||||
|
||||
return (value.StartsWith("{") && value.EndsWith("}")) || (value.StartsWith("[") && value.EndsWith("]"));
|
||||
}
|
||||
|
||||
private static bool TryParseAsJObject(string? strInput, [NotNullWhen(true)] out JObject? value)
|
||||
{
|
||||
value = null;
|
||||
|
||||
if (!IsJson(strInput))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Try to convert this string into a JObject
|
||||
value = JObject.Parse(strInput!);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetContentType(RequestModel request)
|
||||
{
|
||||
var contentType = request.Headers?.FirstOrDefault(h => h.Name == "Content-Type");
|
||||
|
||||
@@ -346,7 +346,7 @@ public partial class WireMockServer
|
||||
}
|
||||
|
||||
o.CorsPolicyOptions = corsPolicyOptions;
|
||||
o.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode) _settings.ClientCertificateMode;
|
||||
o.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode)_settings.ClientCertificateMode;
|
||||
o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
|
||||
});
|
||||
|
||||
@@ -883,6 +883,18 @@ public partial class WireMockServer
|
||||
};
|
||||
}
|
||||
|
||||
private T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
|
||||
{
|
||||
var bodyAsJson = requestMessage.BodyData.BodyAsJson!;
|
||||
|
||||
return _mappingSerializer.DeserializeObjectToArray<T>(bodyAsJson);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private static Encoding? ToEncoding(EncodingModel? encodingModel)
|
||||
{
|
||||
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
|
||||
@@ -902,28 +914,16 @@ public partial class WireMockServer
|
||||
};
|
||||
}
|
||||
|
||||
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
|
||||
{
|
||||
var bodyAsJson = requestMessage.BodyData.BodyAsJson!;
|
||||
|
||||
return MappingSerializer.DeserializeObjectToArray<T>(bodyAsJson);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private static T DeserializeObject<T>(IRequestMessage requestMessage)
|
||||
private T DeserializeObject<T>(IRequestMessage requestMessage)
|
||||
{
|
||||
switch (requestMessage.BodyData?.DetectedBodyType)
|
||||
{
|
||||
case BodyType.String:
|
||||
case BodyType.FormUrlEncoded:
|
||||
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString!);
|
||||
case BodyType.String when requestMessage.BodyData?.BodyAsString != null:
|
||||
case BodyType.FormUrlEncoded when requestMessage.BodyData?.BodyAsString != null:
|
||||
return _settings.DefaultJsonSerializer.Deserialize<T>(requestMessage.BodyData.BodyAsString)!;
|
||||
|
||||
case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null:
|
||||
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!;
|
||||
return _settings.DefaultJsonSerializer.ParseJsonToken<T>(requestMessage.BodyData.BodyAsJson)!;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Matchers;
|
||||
@@ -153,7 +152,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var clientIPModel = JsonUtils.ParseJTokenToObject<ClientIPModel>(requestModel.ClientIP);
|
||||
var clientIPModel = _settings.DefaultJsonSerializer.ParseJsonToken<ClientIPModel>(requestModel.ClientIP);
|
||||
if (clientIPModel.Matchers != null)
|
||||
{
|
||||
requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
|
||||
@@ -169,7 +168,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var pathModel = JsonUtils.ParseJTokenToObject<PathModel>(requestModel.Path);
|
||||
var pathModel = _settings.DefaultJsonSerializer.ParseJsonToken<PathModel>(requestModel.Path);
|
||||
if (pathModel.Matchers != null)
|
||||
{
|
||||
var matchOperator = StringUtils.ParseMatchOperator(pathModel.MatchOperator);
|
||||
@@ -185,7 +184,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var urlModel = JsonUtils.ParseJTokenToObject<UrlModel>(requestModel.Url);
|
||||
var urlModel = _settings.DefaultJsonSerializer.ParseJsonToken<UrlModel>(requestModel.Url);
|
||||
if (urlModel.Matchers != null)
|
||||
{
|
||||
var matchOperator = StringUtils.ParseMatchOperator(urlModel.MatchOperator);
|
||||
@@ -273,7 +272,7 @@ public partial class WireMockServer
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
|
||||
private IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
|
||||
{
|
||||
var responseBuilder = Response.Create();
|
||||
|
||||
@@ -338,7 +337,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
|
||||
var headers = _settings.DefaultJsonSerializer.ParseJsonToken<string[]>(entry.Value);
|
||||
responseBuilder.WithHeader(entry.Key, headers);
|
||||
}
|
||||
}
|
||||
@@ -364,7 +363,7 @@ public partial class WireMockServer
|
||||
}
|
||||
else
|
||||
{
|
||||
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
|
||||
var headers = _settings.DefaultJsonSerializer.ParseJsonToken<string[]>(entry.Value);
|
||||
responseBuilder.WithTrailingHeader(entry.Key, headers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
using JsonConverter.System.Text.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Constants;
|
||||
using WireMock.Logging;
|
||||
using WireMock.Models;
|
||||
using WireMock.Transformers;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
@@ -57,11 +59,9 @@ public static class WireMockServerSettingsParser
|
||||
DisableRequestBodyDecompressing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableRequestBodyDecompressing)),
|
||||
DisableDeserializeFormUrlEncoded = parser.GetBoolValue(nameof(WireMockServerSettings.DisableDeserializeFormUrlEncoded)),
|
||||
DoNotSaveDynamicResponseInLogEntry = parser.GetBoolValue(nameof(WireMockServerSettings.DoNotSaveDynamicResponseInLogEntry)),
|
||||
GraphQLSchemas = parser.GetObjectValueFromJson<Dictionary<string, GraphQLSchemaDetails>>(nameof(settings.GraphQLSchemas)),
|
||||
HandleRequestsSynchronously = parser.GetBoolValue(nameof(WireMockServerSettings.HandleRequestsSynchronously)),
|
||||
HostingScheme = parser.GetEnumValue<HostingScheme>(nameof(WireMockServerSettings.HostingScheme)),
|
||||
MaxRequestLogCount = parser.GetIntValue(nameof(WireMockServerSettings.MaxRequestLogCount)),
|
||||
ProtoDefinitions = parser.GetObjectValueFromJson<Dictionary<string, string[]>>(nameof(settings.ProtoDefinitions)),
|
||||
QueryParameterMultipleValueSupport = parser.GetEnumValue<QueryParameterMultipleValueSupport>(nameof(WireMockServerSettings.QueryParameterMultipleValueSupport)),
|
||||
ReadStaticMappings = parser.GetBoolValue(nameof(WireMockServerSettings.ReadStaticMappings)),
|
||||
RequestLogExpirationDuration = parser.GetIntValue(nameof(WireMockServerSettings.RequestLogExpirationDuration)),
|
||||
@@ -80,6 +80,7 @@ public static class WireMockServerSettingsParser
|
||||
settings.AcceptAnyClientCertificate = parser.GetBoolValue(nameof(WireMockServerSettings.AcceptAnyClientCertificate));
|
||||
#endif
|
||||
|
||||
ParseJsonSerializerSettings(settings, parser);
|
||||
ParseLoggerSettings(settings, logger, parser);
|
||||
ParsePortSettings(settings, parser);
|
||||
ParseProxyAndRecordSettings(settings, parser);
|
||||
@@ -88,6 +89,9 @@ public static class WireMockServerSettingsParser
|
||||
ParseActivityTracingSettings(settings, parser);
|
||||
ParseWebSocketSettings(settings, parser);
|
||||
|
||||
settings.GraphQLSchemas = parser.GetObjectValueFromJson<Dictionary<string, GraphQLSchemaDetails>>(nameof(settings.GraphQLSchemas), settings.DefaultJsonSerializer);
|
||||
settings.ProtoDefinitions = parser.GetObjectValueFromJson<Dictionary<string, string[]>>(nameof(settings.ProtoDefinitions), settings.DefaultJsonSerializer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -259,4 +263,21 @@ public static class WireMockServerSettingsParser
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseJsonSerializerSettings(WireMockServerSettings settings, SimpleSettingsParser parser)
|
||||
{
|
||||
var defaultJsonSerializer = parser.GetStringValue(nameof(WireMockServerSettings.DefaultJsonSerializer));
|
||||
settings.DefaultJsonSerializer = defaultJsonSerializer switch
|
||||
{
|
||||
nameof(SystemTextJsonConverter) => new SystemTextJsonConverter(),
|
||||
_ => new NewtonsoftJsonConverter(),
|
||||
};
|
||||
|
||||
var defaultJsonBodyTransformer = parser.GetStringValue(nameof(WireMockServerSettings.DefaultJsonBodyTransformer));
|
||||
settings.DefaultJsonBodyTransformer = defaultJsonBodyTransformer switch
|
||||
{
|
||||
nameof(SystemTextJsonBodyTransformer) => new SystemTextJsonBodyTransformer(settings),
|
||||
_ => new NewtonsoftJsonBodyTransformer(settings),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using HandlebarsDotNet;
|
||||
using WireMock.Transformers;
|
||||
|
||||
namespace WireMock.Transformers.Handlebars;
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using WireMock.Handlers;
|
||||
|
||||
namespace WireMock.Transformers;
|
||||
|
||||
internal interface ITransformerContext
|
||||
{
|
||||
IFileSystemHandler FileSystemHandler { get; }
|
||||
|
||||
string ParseAndRender(string text, object model);
|
||||
|
||||
object? ParseAndEvaluate(string text, object model);
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using HandlebarsDotNet.Helpers.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
using WireMock.Settings;
|
||||
using WireMock.Types;
|
||||
@@ -14,17 +9,13 @@ namespace WireMock.Transformers;
|
||||
|
||||
internal class Transformer : ITransformer
|
||||
{
|
||||
private readonly JsonSerializer _jsonSerializer;
|
||||
private readonly IJsonBodyTransformer _jsonBodyTransformer;
|
||||
private readonly ITransformerContextFactory _factory;
|
||||
|
||||
public Transformer(WireMockServerSettings settings, ITransformerContextFactory factory)
|
||||
{
|
||||
_factory = Guard.NotNull(factory);
|
||||
|
||||
_jsonSerializer = new JsonSerializer
|
||||
{
|
||||
Culture = Guard.NotNull(settings).Culture
|
||||
};
|
||||
_jsonBodyTransformer = Guard.NotNull(settings).DefaultJsonBodyTransformer;
|
||||
}
|
||||
|
||||
public IBodyData? TransformBody(
|
||||
@@ -121,13 +112,17 @@ internal class Transformer : ITransformer
|
||||
});
|
||||
}
|
||||
|
||||
private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||
private BodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||
{
|
||||
switch (original.DetectedBodyType)
|
||||
{
|
||||
case BodyType.Json:
|
||||
case BodyType.ProtoBuf:
|
||||
return TransformBodyAsJson(transformerContext, options, model, original);
|
||||
return _jsonBodyTransformer.TransformBodyAsJson(
|
||||
transformerContext,
|
||||
options,
|
||||
model,
|
||||
original);
|
||||
|
||||
case BodyType.File:
|
||||
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
|
||||
@@ -159,185 +154,7 @@ internal class Transformer : ITransformer
|
||||
return newHeaders;
|
||||
}
|
||||
|
||||
private IBodyData TransformBodyAsJson(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original)
|
||||
{
|
||||
JToken? jToken = null;
|
||||
switch (original.BodyAsJson)
|
||||
{
|
||||
case JObject bodyAsJObject:
|
||||
jToken = bodyAsJObject.DeepClone();
|
||||
WalkNode(transformerContext, options, jToken, model);
|
||||
break;
|
||||
|
||||
case JArray bodyAsJArray:
|
||||
jToken = bodyAsJArray.DeepClone();
|
||||
WalkNode(transformerContext, options, jToken, model);
|
||||
break;
|
||||
|
||||
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
|
||||
jToken = JArray.FromObject(bodyAsEnumerable, _jsonSerializer);
|
||||
WalkNode(transformerContext, options, jToken, model);
|
||||
break;
|
||||
|
||||
case string bodyAsString:
|
||||
jToken = ReplaceSingleNode(transformerContext, options, bodyAsString, model);
|
||||
break;
|
||||
|
||||
case not null:
|
||||
jToken = JObject.FromObject(original.BodyAsJson, _jsonSerializer);
|
||||
WalkNode(transformerContext, options, jToken, model);
|
||||
break;
|
||||
}
|
||||
|
||||
return new BodyData
|
||||
{
|
||||
Encoding = original.Encoding,
|
||||
DetectedBodyType = original.DetectedBodyType,
|
||||
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
|
||||
ProtoDefinition = original.ProtoDefinition,
|
||||
ProtoBufMessageType = original.ProtoBufMessageType,
|
||||
BodyAsJson = jToken
|
||||
};
|
||||
}
|
||||
|
||||
private JToken ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model)
|
||||
{
|
||||
var transformedString = transformerContext.ParseAndRender(stringValue, model);
|
||||
|
||||
if (!string.Equals(stringValue, transformedString))
|
||||
{
|
||||
const string property = "_";
|
||||
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
|
||||
if (dummy[property] == null)
|
||||
{
|
||||
// TODO: check if just returning null is fine
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
JToken node = dummy[property]!;
|
||||
|
||||
ReplaceNodeValue(options, node, transformedString);
|
||||
|
||||
return dummy[property]!;
|
||||
}
|
||||
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
private void WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JToken node, object model)
|
||||
{
|
||||
switch (node.Type)
|
||||
{
|
||||
case JTokenType.Object:
|
||||
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
|
||||
foreach (var child in node.Children<JProperty>().ToArray())
|
||||
{
|
||||
WalkNode(transformerContext, options, child.Value, model);
|
||||
}
|
||||
break;
|
||||
|
||||
case JTokenType.Array:
|
||||
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
|
||||
foreach (var child in node.Children().ToArray())
|
||||
{
|
||||
WalkNode(transformerContext, options, child, model);
|
||||
}
|
||||
break;
|
||||
|
||||
case JTokenType.String:
|
||||
// In case of string, try to transform the value.
|
||||
var stringValue = node.Value<string>();
|
||||
if (string.IsNullOrEmpty(stringValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
|
||||
if (!Equals(stringValue, transformed))
|
||||
{
|
||||
ReplaceNodeValue(options, node, transformed);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, object? transformedValue)
|
||||
{
|
||||
switch (transformedValue)
|
||||
{
|
||||
case JValue jValue:
|
||||
node.Replace(jValue);
|
||||
return;
|
||||
|
||||
case string transformedString:
|
||||
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
|
||||
if (isConvertedFromString)
|
||||
{
|
||||
node.Replace(JToken.FromObject(convertedValueFromString, _jsonSerializer));
|
||||
}
|
||||
else
|
||||
{
|
||||
node.Replace(ParseAsJObject(transformedString));
|
||||
}
|
||||
break;
|
||||
|
||||
case WireMockList<string> strings:
|
||||
switch (strings.Count)
|
||||
{
|
||||
case 1:
|
||||
node.Replace(ParseAsJObject(strings[0]));
|
||||
return;
|
||||
|
||||
case > 1:
|
||||
node.Replace(JToken.FromObject(strings.ToArray(), _jsonSerializer));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case { }:
|
||||
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
|
||||
if (isConverted)
|
||||
{
|
||||
node.Replace(JToken.FromObject(convertedValue, _jsonSerializer));
|
||||
}
|
||||
return;
|
||||
|
||||
default: // It's null, skip it. Maybe remove it ?
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static JToken ParseAsJObject(string stringValue)
|
||||
{
|
||||
return JsonUtils.TryParseAsJObject(stringValue, out var parsedAsjObject) ? parsedAsjObject : stringValue;
|
||||
}
|
||||
|
||||
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
|
||||
{
|
||||
var valueAsString = value as string;
|
||||
|
||||
if (options == ReplaceNodeOptions.Evaluate)
|
||||
{
|
||||
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
|
||||
{
|
||||
return (true, decoded);
|
||||
}
|
||||
|
||||
return (false, value);
|
||||
}
|
||||
|
||||
if (valueAsString != null)
|
||||
{
|
||||
return WrappedString.TryDecode(valueAsString, out var decoded) ?
|
||||
(true, decoded) :
|
||||
StringUtils.TryConvertToKnownType(valueAsString);
|
||||
}
|
||||
|
||||
return (false, value);
|
||||
}
|
||||
|
||||
private static IBodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original)
|
||||
private static BodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original)
|
||||
{
|
||||
return new BodyData
|
||||
{
|
||||
@@ -348,7 +165,7 @@ internal class Transformer : ITransformer
|
||||
};
|
||||
}
|
||||
|
||||
private static IBodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||
private static BodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||
{
|
||||
var transformedBodyAsFilename = transformerContext.ParseAndRender(original.BodyAsFile!, model);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>Minimal version from the lightweight Http Mocking Server for .NET</Description>
|
||||
<AssemblyTitle>WireMock.Net.Minimal</AssemblyTitle>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>Some extensions for NUnit</Description>
|
||||
<AssemblyTitle>WireMock.Net.NUnit</AssemblyTitle>
|
||||
@@ -25,7 +25,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.9.0" />
|
||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
|
||||
<PackageReference Include="NUnit" Version="4.4.0" />
|
||||
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
+41
-12
@@ -32,15 +32,15 @@ public partial class WireMockAdminApiAssertions
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAdminApiAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAdminApiAssertions> WithBodyAsJson(object body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
public AndConstraint<WireMockAdminApiAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
|
||||
public AndConstraint<WireMockAdminApiAssertions> WithBodyAsJson(string body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
|
||||
{
|
||||
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
|
||||
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
|
||||
}
|
||||
|
||||
[CustomAssertion]
|
||||
@@ -127,15 +127,44 @@ public partial class WireMockAdminApiAssertions
|
||||
|
||||
private static string? FormatBody(object? body)
|
||||
{
|
||||
return body switch
|
||||
if (body == null)
|
||||
{
|
||||
null => null,
|
||||
string str => str,
|
||||
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
|
||||
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
|
||||
JToken jToken => jToken.ToString(Formatting.None),
|
||||
_ => JToken.FromObject(body).ToString(Formatting.None)
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
if (body is string str)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
if (body is AnyOf<string, StringPattern>[] stringPatterns)
|
||||
{
|
||||
return FormatBodies(stringPatterns.Select(p => p.GetPattern()));
|
||||
}
|
||||
|
||||
if (body is byte[] bytes)
|
||||
{
|
||||
return $"byte[{bytes.Length}] {{...}}";
|
||||
}
|
||||
|
||||
if (body is JToken jToken)
|
||||
{
|
||||
return jToken.ToString(Formatting.None);
|
||||
}
|
||||
|
||||
// System.IO.FileNotFoundException : Could not load file or assembly 'System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
|
||||
var typeName = body.GetType().FullName;
|
||||
if (typeName == "System.Text.Json.JsonElement")
|
||||
{
|
||||
return ((dynamic)body).GetRawText();
|
||||
}
|
||||
|
||||
if (typeName == "System.Text.Json.JsonDocument")
|
||||
{
|
||||
return ((dynamic)body).RootElement.GetRawText();
|
||||
}
|
||||
|
||||
return JToken.FromObject(body).ToString(Formatting.None);
|
||||
}
|
||||
|
||||
private static string? FormatBodies(IEnumerable<object?> bodies)
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.9.0" />
|
||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
|
||||
<PackageReference Include="RestEase" Version="1.6.4" />
|
||||
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// IJsonPathMatcher
|
||||
/// <seealso cref="IStringMatcher"/> and <seealso cref="IObjectMatcher"/>.
|
||||
/// </summary>
|
||||
public interface IJsonPathMatcher : IStringMatcher, IObjectMatcher
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
namespace WireMock.Matchers;
|
||||
|
||||
/// <summary>
|
||||
/// ISystemTextJsonPathMatcher
|
||||
/// <seealso cref="IJsonPathMatcher"/>.
|
||||
/// </summary>
|
||||
public interface ISystemTextJsonPathMatcher : IJsonPathMatcher
|
||||
{
|
||||
}
|
||||
@@ -7,9 +7,10 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.GraphQL, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.ProtoBuf, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Matchers.SystemTextJsonPath, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.OpenTelemetry, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
// [assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
|
||||
|
||||
// Needed for Moq in the UnitTest project
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using JsonConverter.Abstractions;
|
||||
using WireMock.Models;
|
||||
|
||||
@@ -19,8 +17,10 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
|
||||
/// <param name="body">The body.</param>
|
||||
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
|
||||
/// <param name="encoding">The body encoding.</param>
|
||||
/// <param name="jsonConverter">The JSON converter.</param>
|
||||
/// <param name="options">The JSON converter options.</param>
|
||||
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
|
||||
IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
|
||||
IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null, IJsonConverter? jsonConverter = null, JsonConverterOptions? options = null);
|
||||
|
||||
/// <summary>
|
||||
/// WithBody : Create a ... response based on a callback function.
|
||||
|
||||
@@ -14,16 +14,16 @@ internal static class JsonSerializationConstants
|
||||
IgnoreNullValues = true
|
||||
};
|
||||
|
||||
//internal static readonly JsonSerializerSettings JsonSerializerSettingsDefault = new()
|
||||
//{
|
||||
// Formatting = Formatting.Indented,
|
||||
// NullValueHandling = NullValueHandling.Ignore
|
||||
//};
|
||||
|
||||
internal static readonly JsonSerializerSettings JsonSerializerSettingsIncludeNullValues = new()
|
||||
internal static readonly JsonConverterOptions JsonConverterOptionsIncludeNullValues = new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
NullValueHandling = NullValueHandling.Include
|
||||
WriteIndented = true,
|
||||
IgnoreNullValues = false
|
||||
};
|
||||
|
||||
internal static readonly JsonConverterOptions JsonConverterOptionsWithDateParsingNone = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
DateParseHandling = 0
|
||||
};
|
||||
|
||||
internal static readonly JsonSerializerSettings JsonDeserializerSettingsWithDateParsingNone = new()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections;
|
||||
using JsonConverter.Abstractions;
|
||||
using WireMock.Extensions;
|
||||
using WireMock.Util;
|
||||
|
||||
@@ -191,9 +192,9 @@ internal class SimpleSettingsParser
|
||||
return GetValue(name, values => values.FirstOrDefault());
|
||||
}
|
||||
|
||||
public T? GetObjectValueFromJson<T>(string name)
|
||||
public T? GetObjectValueFromJson<T>(string name, IJsonConverter jsonConverter)
|
||||
{
|
||||
var value = GetValue(name, values => values.FirstOrDefault());
|
||||
return string.IsNullOrWhiteSpace(value) ? default : JsonUtils.DeserializeObject<T>(value!);
|
||||
return string.IsNullOrWhiteSpace(value) ? default : jsonConverter.Deserialize<T>(value!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using WireMock.Logging;
|
||||
using WireMock.Matchers;
|
||||
using WireMock.Models;
|
||||
using WireMock.RegularExpressions;
|
||||
using WireMock.Transformers;
|
||||
using WireMock.Types;
|
||||
|
||||
namespace WireMock.Settings;
|
||||
@@ -362,11 +363,31 @@ public class WireMockServerSettings
|
||||
/// Default is <see cref="NewtonsoftJsonConverter"/>.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
|
||||
public IJsonConverter DefaultJsonSerializer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default JSON body transformer used for template-based JSON body transformations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Set this property to provide a custom implementation for transforming JSON and ProtoBuf body content.
|
||||
/// Default is <see cref="NewtonsoftJsonBodyTransformer"/>.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
[JsonIgnore]
|
||||
public IJsonBodyTransformer DefaultJsonBodyTransformer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket settings.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public WebSocketSettings? WebSocketSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WireMockServerSettings"/> class.
|
||||
/// </summary>
|
||||
public WireMockServerSettings()
|
||||
{
|
||||
DefaultJsonSerializer = new NewtonsoftJsonConverter();
|
||||
DefaultJsonBodyTransformer = new NewtonsoftJsonBodyTransformer(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Transformers;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the contract for transforming JSON-like body data using a transformer context.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public interface IJsonBodyTransformer
|
||||
{
|
||||
/// <summary>
|
||||
/// Transforms the JSON body using the provided transformer context and model.
|
||||
/// </summary>
|
||||
/// <param name="transformerContext">The transformer context used to render and evaluate template values.</param>
|
||||
/// <param name="options">The JSON node replacement behavior to apply during transformation.</param>
|
||||
/// <param name="model">The model used when rendering or evaluating template values.</param>
|
||||
/// <param name="original">The original body data to transform.</param>
|
||||
/// <returns>The transformed JSON body data.</returns>
|
||||
BodyData TransformBodyAsJson(
|
||||
ITransformerContext transformerContext,
|
||||
ReplaceNodeOptions options,
|
||||
object model,
|
||||
IBodyData original);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using WireMock.Handlers;
|
||||
|
||||
namespace WireMock.Transformers;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the transformer context used to render and evaluate templates during response transformation.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public interface ITransformerContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the file system handler used by the current transformer context.
|
||||
/// </summary>
|
||||
IFileSystemHandler FileSystemHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Renders the specified template text using the supplied model.
|
||||
/// </summary>
|
||||
/// <param name="text">The template text to render.</param>
|
||||
/// <param name="model">The model used during rendering.</param>
|
||||
/// <returns>The rendered text.</returns>
|
||||
string ParseAndRender(string text, object model);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the specified template text using the supplied model.
|
||||
/// </summary>
|
||||
/// <param name="text">The template text to evaluate.</param>
|
||||
/// <param name="model">The model used during evaluation.</param>
|
||||
/// <returns>The evaluated value.</returns>
|
||||
object? ParseAndEvaluate(string text, object model);
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections;
|
||||
using HandlebarsDotNet.Helpers.Models;
|
||||
using JetBrains.Annotations;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WireMock.Settings;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Transformers;
|
||||
|
||||
/// <summary>
|
||||
/// Default JSON body transformer implementation based on Newtonsoft.Json.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="NewtonsoftJsonBodyTransformer"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="settings">The server settings used to configure JSON transformation behavior.</param>
|
||||
[PublicAPI]
|
||||
public class NewtonsoftJsonBodyTransformer(WireMockServerSettings settings) : IJsonBodyTransformer
|
||||
{
|
||||
private static readonly NewtonsoftJsonConverter _jsonConverter = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public BodyData TransformBodyAsJson(
|
||||
ITransformerContext transformerContext,
|
||||
ReplaceNodeOptions options,
|
||||
object model,
|
||||
IBodyData original)
|
||||
{
|
||||
var jsonSerializer = new JsonSerializer
|
||||
{
|
||||
Culture = settings.Culture
|
||||
};
|
||||
|
||||
JToken? jToken = null;
|
||||
switch (original.BodyAsJson)
|
||||
{
|
||||
case JObject bodyAsJObject:
|
||||
jToken = bodyAsJObject.DeepClone();
|
||||
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
|
||||
break;
|
||||
|
||||
case JArray bodyAsJArray:
|
||||
jToken = bodyAsJArray.DeepClone();
|
||||
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
|
||||
break;
|
||||
|
||||
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
|
||||
jToken = JArray.FromObject(bodyAsEnumerable, jsonSerializer);
|
||||
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
|
||||
break;
|
||||
|
||||
case string bodyAsString:
|
||||
jToken = ReplaceSingleNode(transformerContext, jsonSerializer, options, bodyAsString, model);
|
||||
break;
|
||||
|
||||
case not null:
|
||||
jToken = JObject.FromObject(original.BodyAsJson, jsonSerializer);
|
||||
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
|
||||
break;
|
||||
}
|
||||
|
||||
return new BodyData
|
||||
{
|
||||
Encoding = original.Encoding,
|
||||
DetectedBodyType = original.DetectedBodyType,
|
||||
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
|
||||
ProtoDefinition = original.ProtoDefinition,
|
||||
ProtoBufMessageType = original.ProtoBufMessageType,
|
||||
BodyAsJson = jToken
|
||||
};
|
||||
}
|
||||
|
||||
private JToken ParseAsJObject(string stringValue)
|
||||
{
|
||||
if (_jsonConverter.IsValidJson(stringValue))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to convert this string into a JObject
|
||||
return JObject.Parse(stringValue!);
|
||||
}
|
||||
catch
|
||||
{
|
||||
settings.Logger.Warn("Failed to parse string ''{0}'' as JSON. Returning the original string value.", stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
private JToken ReplaceSingleNode(ITransformerContext transformerContext, JsonSerializer jsonSerializer, ReplaceNodeOptions options, string stringValue, object model)
|
||||
{
|
||||
var transformedString = transformerContext.ParseAndRender(stringValue, model);
|
||||
|
||||
if (!string.Equals(stringValue, transformedString))
|
||||
{
|
||||
const string property = "_";
|
||||
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
|
||||
if (dummy[property] == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
JToken node = dummy[property]!;
|
||||
|
||||
ReplaceNodeValue(jsonSerializer, options, node, transformedString);
|
||||
|
||||
return dummy[property]!;
|
||||
}
|
||||
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
private void WalkNode(ITransformerContext transformerContext, JsonSerializer jsonSerializer, ReplaceNodeOptions options, JToken node, object model)
|
||||
{
|
||||
switch (node.Type)
|
||||
{
|
||||
case JTokenType.Object:
|
||||
foreach (var child in node.Children<JProperty>().ToArray())
|
||||
{
|
||||
WalkNode(transformerContext, jsonSerializer, options, child.Value, model);
|
||||
}
|
||||
break;
|
||||
|
||||
case JTokenType.Array:
|
||||
foreach (var child in node.Children().ToArray())
|
||||
{
|
||||
WalkNode(transformerContext, jsonSerializer, options, child, model);
|
||||
}
|
||||
break;
|
||||
|
||||
case JTokenType.String:
|
||||
var stringValue = node.Value<string>();
|
||||
if (string.IsNullOrEmpty(stringValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
|
||||
if (!Equals(stringValue, transformed))
|
||||
{
|
||||
ReplaceNodeValue(jsonSerializer, options, node, transformed);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReplaceNodeValue(JsonSerializer jsonSerializer, ReplaceNodeOptions options, JToken node, object? transformedValue)
|
||||
{
|
||||
switch (transformedValue)
|
||||
{
|
||||
case JValue jValue:
|
||||
node.Replace(jValue);
|
||||
return;
|
||||
|
||||
case string transformedString:
|
||||
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
|
||||
if (isConvertedFromString)
|
||||
{
|
||||
node.Replace(JToken.FromObject(convertedValueFromString, jsonSerializer));
|
||||
}
|
||||
else
|
||||
{
|
||||
node.Replace(ParseAsJObject(transformedString));
|
||||
}
|
||||
break;
|
||||
|
||||
case WireMockList<string> strings:
|
||||
switch (strings.Count)
|
||||
{
|
||||
case 1:
|
||||
node.Replace(ParseAsJObject(strings[0]));
|
||||
return;
|
||||
|
||||
case > 1:
|
||||
node.Replace(JToken.FromObject(strings.ToArray(), jsonSerializer));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case { }:
|
||||
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
|
||||
if (isConverted)
|
||||
{
|
||||
node.Replace(JToken.FromObject(convertedValue, jsonSerializer));
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
|
||||
{
|
||||
var valueAsString = value as string;
|
||||
|
||||
if (options == ReplaceNodeOptions.Evaluate)
|
||||
{
|
||||
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
|
||||
{
|
||||
return (true, decoded);
|
||||
}
|
||||
|
||||
return (false, value);
|
||||
}
|
||||
|
||||
if (valueAsString != null)
|
||||
{
|
||||
return WrappedString.TryDecode(valueAsString, out var decoded)
|
||||
? (true, decoded)
|
||||
: TryConvertToKnownType(valueAsString);
|
||||
}
|
||||
|
||||
return (false, value);
|
||||
}
|
||||
|
||||
internal static (bool IsConverted, object ConvertedValue) TryConvertToKnownType(string value)
|
||||
{
|
||||
if (bool.TryParse(value, out var boolResult))
|
||||
{
|
||||
return (true, boolResult);
|
||||
}
|
||||
|
||||
if (int.TryParse(value, out var intResult))
|
||||
{
|
||||
return (true, intResult);
|
||||
}
|
||||
|
||||
if (long.TryParse(value, out var longResult))
|
||||
{
|
||||
return (true, longResult);
|
||||
}
|
||||
|
||||
if (double.TryParse(value, out var doubleResult))
|
||||
{
|
||||
return (true, doubleResult);
|
||||
}
|
||||
|
||||
if (Guid.TryParseExact(value, "D", out var guidResult))
|
||||
{
|
||||
return (true, guidResult);
|
||||
}
|
||||
|
||||
if (TimeSpan.TryParse(value, out var timeSpanResult))
|
||||
{
|
||||
return (true, timeSpanResult);
|
||||
}
|
||||
|
||||
if (DateTime.TryParse(value, out var dateTimeResult))
|
||||
{
|
||||
return (true, dateTimeResult);
|
||||
}
|
||||
|
||||
if ((value.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) ||
|
||||
value.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase) ||
|
||||
value.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) &&
|
||||
Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var uriResult))
|
||||
{
|
||||
return (true, uriResult);
|
||||
}
|
||||
|
||||
return (false, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using HandlebarsDotNet.Helpers.Models;
|
||||
using JetBrains.Annotations;
|
||||
using JsonConverter.System.Text.Json;
|
||||
using WireMock.Settings;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock.Transformers;
|
||||
|
||||
/// <summary>
|
||||
/// JSON body transformer implementation based on System.Text.Json.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class SystemTextJsonBodyTransformer(WireMockServerSettings settings) : IJsonBodyTransformer
|
||||
{
|
||||
private static readonly SystemTextJsonConverter _jsonConverter = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public BodyData TransformBodyAsJson(
|
||||
ITransformerContext transformerContext,
|
||||
ReplaceNodeOptions options,
|
||||
object model,
|
||||
IBodyData original)
|
||||
{
|
||||
JsonNode? jsonNode = null;
|
||||
switch (original.BodyAsJson)
|
||||
{
|
||||
case JsonObject bodyAsJsonObject:
|
||||
jsonNode = CloneNode(bodyAsJsonObject);
|
||||
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
|
||||
break;
|
||||
|
||||
case JsonArray bodyAsJsonArray:
|
||||
jsonNode = CloneNode(bodyAsJsonArray);
|
||||
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
|
||||
break;
|
||||
|
||||
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
|
||||
jsonNode = JsonSerializer.SerializeToNode(bodyAsEnumerable);
|
||||
if (jsonNode != null)
|
||||
{
|
||||
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
|
||||
}
|
||||
break;
|
||||
|
||||
case string bodyAsString:
|
||||
jsonNode = ReplaceSingleNode(transformerContext, options, bodyAsString, model);
|
||||
break;
|
||||
|
||||
case not null:
|
||||
jsonNode = JsonSerializer.SerializeToNode(original.BodyAsJson);
|
||||
if (jsonNode != null)
|
||||
{
|
||||
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return new BodyData
|
||||
{
|
||||
Encoding = original.Encoding,
|
||||
DetectedBodyType = original.DetectedBodyType,
|
||||
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
|
||||
ProtoDefinition = original.ProtoDefinition,
|
||||
ProtoBufMessageType = original.ProtoBufMessageType,
|
||||
BodyAsJson = jsonNode
|
||||
};
|
||||
}
|
||||
|
||||
private JsonNode ParseAsJsonObject(string stringValue)
|
||||
{
|
||||
if (_jsonConverter.IsValidJson(stringValue))
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsed = JsonNode.Parse(stringValue);
|
||||
if (parsed is JsonObject)
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
settings.Logger.Warn("Failed to parse string ''{0}'' as JSON. Returning the original string value.", stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
return JsonValue.Create(stringValue)!;
|
||||
}
|
||||
|
||||
private JsonNode? ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model)
|
||||
{
|
||||
var transformedString = transformerContext.ParseAndRender(stringValue, model);
|
||||
|
||||
if (!string.Equals(stringValue, transformedString))
|
||||
{
|
||||
return ReplaceNodeValue(options, transformedString);
|
||||
}
|
||||
|
||||
return JsonValue.Create(stringValue);
|
||||
}
|
||||
|
||||
private JsonNode? WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JsonNode? node, object model)
|
||||
{
|
||||
switch (node)
|
||||
{
|
||||
case JsonObject jsonObject:
|
||||
foreach (var property in jsonObject.ToArray())
|
||||
{
|
||||
jsonObject[property.Key] = WalkNode(transformerContext, options, property.Value, model);
|
||||
}
|
||||
return jsonObject;
|
||||
|
||||
case JsonArray jsonArray:
|
||||
for (var i = 0; i < jsonArray.Count; i++)
|
||||
{
|
||||
jsonArray[i] = WalkNode(transformerContext, options, jsonArray[i], model);
|
||||
}
|
||||
return jsonArray;
|
||||
|
||||
case JsonValue jsonValue when jsonValue.TryGetValue<string>(out var stringValue):
|
||||
if (string.IsNullOrEmpty(stringValue))
|
||||
{
|
||||
return jsonValue;
|
||||
}
|
||||
|
||||
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
|
||||
return !Equals(stringValue, transformed) ? ReplaceNodeValue(options, transformed) ?? jsonValue : jsonValue;
|
||||
|
||||
default:
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode? ReplaceNodeValue(ReplaceNodeOptions options, object? transformedValue)
|
||||
{
|
||||
switch (transformedValue)
|
||||
{
|
||||
case JsonNode jsonNode:
|
||||
return CloneNode(jsonNode);
|
||||
|
||||
case string transformedString:
|
||||
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
|
||||
return isConvertedFromString
|
||||
? JsonSerializer.SerializeToNode(convertedValueFromString)
|
||||
: ParseAsJsonObject(transformedString);
|
||||
|
||||
case WireMockList<string> strings:
|
||||
switch (strings.Count)
|
||||
{
|
||||
case 1:
|
||||
return ParseAsJsonObject(strings[0]);
|
||||
|
||||
case > 1:
|
||||
return JsonSerializer.SerializeToNode(strings.ToArray());
|
||||
}
|
||||
break;
|
||||
|
||||
case { }:
|
||||
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
|
||||
if (isConverted)
|
||||
{
|
||||
return JsonSerializer.SerializeToNode(convertedValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JsonNode? CloneNode(JsonNode? node)
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
return node?.DeepClone();
|
||||
#else
|
||||
return node == null ? null : JsonNode.Parse(node.ToJsonString());
|
||||
#endif
|
||||
}
|
||||
|
||||
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
|
||||
{
|
||||
var valueAsString = value as string;
|
||||
|
||||
if (options == ReplaceNodeOptions.Evaluate)
|
||||
{
|
||||
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
|
||||
{
|
||||
return (true, decoded);
|
||||
}
|
||||
|
||||
return (false, value);
|
||||
}
|
||||
|
||||
if (valueAsString != null)
|
||||
{
|
||||
return WrappedString.TryDecode(valueAsString, out var decoded)
|
||||
? (true, decoded)
|
||||
: NewtonsoftJsonBodyTransformer.TryConvertToKnownType(valueAsString);
|
||||
}
|
||||
|
||||
return (false, value);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using Stef.Validation;
|
||||
@@ -129,11 +128,11 @@ internal static class BodyParser
|
||||
{
|
||||
Guard.NotNull(settings);
|
||||
|
||||
var bodyWithContentEncoding = await ReadBytesAsync(settings.Stream, settings.ContentEncoding, settings.DecompressGZipAndDeflate).ConfigureAwait(false);
|
||||
var (ContentType, Bytes) = await ReadBytesAsync(settings.Stream, settings.ContentEncoding, settings.DecompressGZipAndDeflate).ConfigureAwait(false);
|
||||
var data = new BodyData
|
||||
{
|
||||
BodyAsBytes = bodyWithContentEncoding.Bytes,
|
||||
DetectedCompression = bodyWithContentEncoding.ContentType,
|
||||
BodyAsBytes = Bytes,
|
||||
DetectedCompression = ContentType,
|
||||
DetectedBodyType = BodyType.Bytes,
|
||||
DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(settings.ContentType)
|
||||
};
|
||||
@@ -169,17 +168,17 @@ internal static class BodyParser
|
||||
data.DetectedBodyType = BodyType.FormUrlEncoded;
|
||||
}
|
||||
|
||||
// If string is not null or empty, try to deserialize the string to a JObject
|
||||
if (settings.DeserializeJson && JsonUtils.IsJson(data.BodyAsString))
|
||||
// If string is not null or empty, try to deserialize the string
|
||||
if (settings.DeserializeJson && settings.DefaultJsonConverter.IsValidJson(data.BodyAsString))
|
||||
{
|
||||
try
|
||||
{
|
||||
data.BodyAsJson = JsonUtils.DeserializeObject(data.BodyAsString);
|
||||
data.BodyAsJson = settings.DefaultJsonConverter.Deserialize<object>(data.BodyAsString);
|
||||
data.DetectedBodyType = BodyType.Json;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// JsonConvert failed, just ignore.
|
||||
// JsonConverter failed, just ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,7 +201,7 @@ internal static class BodyParser
|
||||
return (null, data);
|
||||
}
|
||||
|
||||
public static bool IsProbablyText(byte[] data)
|
||||
private static bool IsProbablyText(byte[] data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.IO;
|
||||
using JsonConverter.Abstractions;
|
||||
using JsonConverter.Newtonsoft.Json;
|
||||
|
||||
namespace WireMock.Util;
|
||||
|
||||
@@ -35,4 +36,13 @@ internal class BodyParserSettings
|
||||
/// Try to deserialize the body as FormUrlEncoded.
|
||||
/// </summary>
|
||||
public bool DeserializeFormUrlEncoded { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default JSON converter used for deserialization.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Set this property to customize how objects are serialized to and deserialized from JSON during mapping.
|
||||
/// Default is <see cref="NewtonsoftJsonConverter"/>.
|
||||
/// </remarks>
|
||||
public IJsonConverter DefaultJsonConverter { get; set; } = new NewtonsoftJsonConverter();
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WireMock.Serialization;
|
||||
|
||||
namespace WireMock.Util;
|
||||
|
||||
internal static class JsonUtils
|
||||
{
|
||||
public static bool IsJson(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = value!.Trim();
|
||||
|
||||
return (value.StartsWith("{") && value.EndsWith("}")) || (value.StartsWith("[") && value.EndsWith("]"));
|
||||
}
|
||||
|
||||
public static bool TryParseAsJObject(string? strInput, [NotNullWhen(true)] out JObject? value)
|
||||
{
|
||||
value = null;
|
||||
|
||||
if (!IsJson(strInput))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Try to convert this string into a JToken
|
||||
value = JObject.Parse(strInput!);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string Serialize(object value)
|
||||
{
|
||||
return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues);
|
||||
}
|
||||
|
||||
public static byte[] SerializeAsPactFile(object value)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsPact);
|
||||
return Encoding.UTF8.GetBytes(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a Newtonsoft.Json.Linq.JObject from a string that contains JSON.
|
||||
/// Using : DateParseHandling = DateParseHandling.None
|
||||
/// </summary>
|
||||
/// <param name="json">A System.String that contains JSON.</param>
|
||||
/// <returns>A Newtonsoft.Json.Linq.JToken populated from the string that contains JSON.</returns>
|
||||
public static JToken Parse(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<JToken>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the JSON to a .NET object.
|
||||
/// Using : DateParseHandling = DateParseHandling.None
|
||||
/// </summary>
|
||||
/// <param name="json">A System.String that contains JSON.</param>
|
||||
/// <returns>The deserialized object from the JSON string.</returns>
|
||||
public static object DeserializeObject(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the JSON to the specified .NET type.
|
||||
/// Using : DateParseHandling = DateParseHandling.None
|
||||
/// </summary>
|
||||
/// <param name="json">A System.String that contains JSON.</param>
|
||||
/// <returns>The deserialized object from the JSON string.</returns>
|
||||
public static T DeserializeObject<T>(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!;
|
||||
}
|
||||
|
||||
public static T? TryDeserializeObject<T>(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static T ParseJTokenToObject<T>(object? value)
|
||||
{
|
||||
if (value != null && value.GetType() == typeof(T))
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
return value switch
|
||||
{
|
||||
JToken tokenValue => tokenValue.ToObject<T>()!,
|
||||
|
||||
_ => throw new NotSupportedException($"Unable to convert value to {typeof(T)}.")
|
||||
};
|
||||
}
|
||||
|
||||
public static JToken ConvertValueToJToken(object value)
|
||||
{
|
||||
// Check if JToken, string, IEnumerable or object
|
||||
switch (value)
|
||||
{
|
||||
case JToken tokenValue:
|
||||
return tokenValue;
|
||||
|
||||
case string stringValue:
|
||||
return Parse(stringValue);
|
||||
|
||||
case IEnumerable enumerableValue:
|
||||
return JArray.FromObject(enumerableValue);
|
||||
|
||||
default:
|
||||
return JObject.FromObject(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Stef.Validation;
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
<PackageReference Include="Stef.Validation" Version="0.2.0" />
|
||||
<PackageReference Include="AnyOf" Version="0.5.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.9.0" />
|
||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
|
||||
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -34,5 +34,6 @@
|
||||
<ProjectReference Include="../WireMock.Net.GraphQL/WireMock.Net.GraphQL.csproj" />
|
||||
<ProjectReference Include="../WireMock.Net.ProtoBuf/WireMock.Net.ProtoBuf.csproj" />
|
||||
<ProjectReference Include="../WireMock.Net.OpenTelemetry/WireMock.Net.OpenTelemetry.csproj" />
|
||||
<ProjectReference Include="../WireMock.Net.Matchers.SystemTextJsonPath/WireMock.Net.Matchers.SystemTextJsonPath.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user