mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-05-03 14:14:29 +02:00
...
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
// Copyright © WireMock.Net
|
||||
|
||||
using System.Collections;
|
||||
using HandlebarsDotNet.Helpers.Models;
|
||||
using JetBrains.Annotations;
|
||||
using JsonConverter.Abstractions;
|
||||
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 readonly IJsonConverter _jsonConverter = new NewtonsoftJsonConverter();
|
||||
|
||||
/// <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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user