mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-04-21 16:01:18 +02:00
Update Transformer functionality to return value instead of string (#858)
This commit is contained in:
@@ -1,27 +1,26 @@
|
||||
using System;
|
||||
using HandlebarsDotNet;
|
||||
using HandlebarsDotNet.Helpers.Attributes;
|
||||
using HandlebarsDotNet.Helpers.Enums;
|
||||
using HandlebarsDotNet.Helpers.Helpers;
|
||||
using Stef.Validation;
|
||||
using WireMock.Handlers;
|
||||
|
||||
namespace WireMock.Transformers.Handlebars
|
||||
namespace WireMock.Transformers.Handlebars;
|
||||
|
||||
internal class FileHelpers : BaseHelpers, IHelpers
|
||||
{
|
||||
internal class FileHelpers : BaseHelpers, IHelpers
|
||||
private readonly IFileSystemHandler _fileSystemHandler;
|
||||
|
||||
public FileHelpers(IHandlebars context, IFileSystemHandler fileSystemHandler) : base(context)
|
||||
{
|
||||
private readonly IFileSystemHandler _fileSystemHandler;
|
||||
_fileSystemHandler = Guard.NotNull(fileSystemHandler);
|
||||
}
|
||||
|
||||
public FileHelpers(IHandlebars context, IFileSystemHandler fileSystemHandler) : base(context)
|
||||
{
|
||||
_fileSystemHandler = fileSystemHandler ?? throw new ArgumentNullException(nameof(fileSystemHandler));
|
||||
}
|
||||
|
||||
[HandlebarsWriter(WriterType.String, usage: HelperUsage.Both, passContext: true, name: "File")]
|
||||
public string Read(Context context, string path)
|
||||
{
|
||||
var templateFunc = Context.Compile(path);
|
||||
string transformed = templateFunc(context.Value);
|
||||
return _fileSystemHandler.ReadResponseBodyAsString(transformed);
|
||||
}
|
||||
[HandlebarsWriter(WriterType.String, usage: HelperUsage.Both, passContext: true, name: "File")]
|
||||
public string Read(Context context, string path)
|
||||
{
|
||||
var templateFunc = Context.Compile(path);
|
||||
string transformed = templateFunc(context.Value);
|
||||
return _fileSystemHandler.ReadResponseBodyAsString(transformed);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,35 @@
|
||||
using HandlebarsDotNet;
|
||||
using HandlebarsDotNet.Helpers.Extensions;
|
||||
using Stef.Validation;
|
||||
using WireMock.Handlers;
|
||||
|
||||
namespace WireMock.Transformers.Handlebars
|
||||
namespace WireMock.Transformers.Handlebars;
|
||||
|
||||
internal class HandlebarsContext : IHandlebarsContext
|
||||
{
|
||||
internal class HandlebarsContext : IHandlebarsContext
|
||||
public IHandlebars Handlebars { get; }
|
||||
|
||||
public IFileSystemHandler FileSystemHandler { get; }
|
||||
|
||||
public HandlebarsContext(IHandlebars handlebars, IFileSystemHandler fileSystemHandler)
|
||||
{
|
||||
public IHandlebars Handlebars { get; set; }
|
||||
Handlebars = Guard.NotNull(handlebars);
|
||||
FileSystemHandler = Guard.NotNull(fileSystemHandler);
|
||||
}
|
||||
|
||||
public IFileSystemHandler FileSystemHandler { get; set; }
|
||||
public string ParseAndRender(string text, object model)
|
||||
{
|
||||
var template = Handlebars.Compile(text);
|
||||
return template(model);
|
||||
}
|
||||
|
||||
public string ParseAndRender(string text, object model)
|
||||
public object? ParseAndEvaluate(string text, object model)
|
||||
{
|
||||
if (Handlebars.TryEvaluate(text, model, out var result) && result is not UndefinedBindingResult)
|
||||
{
|
||||
var template = Handlebars.Compile(text);
|
||||
return template(model);
|
||||
return result;
|
||||
}
|
||||
|
||||
return ParseAndRender(text, model);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,30 @@
|
||||
using System;
|
||||
using HandlebarsDotNet;
|
||||
using Stef.Validation;
|
||||
using WireMock.Handlers;
|
||||
using WireMock.Settings;
|
||||
|
||||
namespace WireMock.Transformers.Handlebars;
|
||||
|
||||
internal class HandlebarsContextFactory : ITransformerContextFactory
|
||||
{
|
||||
private readonly IFileSystemHandler _fileSystemHandler;
|
||||
private readonly Action<IHandlebars, IFileSystemHandler>? _action;
|
||||
private readonly WireMockServerSettings _settings;
|
||||
|
||||
public HandlebarsContextFactory(IFileSystemHandler fileSystemHandler, Action<IHandlebars, IFileSystemHandler>? action)
|
||||
public HandlebarsContextFactory(WireMockServerSettings settings)
|
||||
{
|
||||
_fileSystemHandler = Guard.NotNull(fileSystemHandler);
|
||||
_action = action;
|
||||
_settings = Guard.NotNull(settings);
|
||||
}
|
||||
|
||||
public ITransformerContext Create()
|
||||
{
|
||||
var handlebars = HandlebarsDotNet.Handlebars.Create();
|
||||
|
||||
WireMockHandlebarsHelpers.Register(handlebars, _fileSystemHandler);
|
||||
|
||||
_action?.Invoke(handlebars, _fileSystemHandler);
|
||||
|
||||
return new HandlebarsContext
|
||||
var config = new HandlebarsConfiguration
|
||||
{
|
||||
Handlebars = handlebars,
|
||||
FileSystemHandler = _fileSystemHandler
|
||||
FormatProvider = _settings.Culture
|
||||
};
|
||||
var handlebars = HandlebarsDotNet.Handlebars.Create(config);
|
||||
|
||||
WireMockHandlebarsHelpers.Register(handlebars, _settings.FileSystemHandler);
|
||||
|
||||
_settings.HandlebarsRegistrationCallback?.Invoke(handlebars, _settings.FileSystemHandler);
|
||||
|
||||
return new HandlebarsContext(handlebars, _settings.FileSystemHandler);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using HandlebarsDotNet;
|
||||
using HandlebarsDotNet;
|
||||
|
||||
namespace WireMock.Transformers.Handlebars
|
||||
namespace WireMock.Transformers.Handlebars;
|
||||
|
||||
interface IHandlebarsContext : ITransformerContext
|
||||
{
|
||||
interface IHandlebarsContext : ITransformerContext
|
||||
{
|
||||
IHandlebars Handlebars { get; set; }
|
||||
}
|
||||
IHandlebars Handlebars { get; }
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using WireMock.Handlers;
|
||||
using WireMock.Handlers;
|
||||
|
||||
namespace WireMock.Transformers
|
||||
namespace WireMock.Transformers;
|
||||
|
||||
internal interface ITransformerContext
|
||||
{
|
||||
interface ITransformerContext
|
||||
{
|
||||
IFileSystemHandler FileSystemHandler { get; set; }
|
||||
IFileSystemHandler FileSystemHandler { get; }
|
||||
|
||||
string ParseAndRender(string text, object model);
|
||||
}
|
||||
string ParseAndRender(string text, object model);
|
||||
|
||||
object? ParseAndEvaluate(string text, object model);
|
||||
}
|
||||
@@ -3,25 +3,30 @@ using Stef.Validation;
|
||||
using WireMock.Handlers;
|
||||
using WireMock.Types;
|
||||
|
||||
namespace WireMock.Transformers.Scriban
|
||||
namespace WireMock.Transformers.Scriban;
|
||||
|
||||
internal class ScribanContext : ITransformerContext
|
||||
{
|
||||
internal class ScribanContext : ITransformerContext
|
||||
private readonly TransformerType _transformerType;
|
||||
|
||||
public IFileSystemHandler FileSystemHandler { get; }
|
||||
|
||||
public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType)
|
||||
{
|
||||
private readonly TransformerType _transformerType;
|
||||
FileSystemHandler = Guard.NotNull(fileSystemHandler);
|
||||
_transformerType = transformerType;
|
||||
}
|
||||
|
||||
public IFileSystemHandler FileSystemHandler { get; set; }
|
||||
public string ParseAndRender(string text, object model)
|
||||
{
|
||||
var template = _transformerType == TransformerType.ScribanDotLiquid ? Template.ParseLiquid(text) : Template.Parse(text);
|
||||
|
||||
public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType)
|
||||
{
|
||||
FileSystemHandler = Guard.NotNull(fileSystemHandler);
|
||||
_transformerType = transformerType;
|
||||
}
|
||||
return template.Render(model, member => member.Name);
|
||||
}
|
||||
|
||||
public string ParseAndRender(string text, object model)
|
||||
{
|
||||
var template = _transformerType == TransformerType.ScribanDotLiquid ? Template.ParseLiquid(text) : Template.Parse(text);
|
||||
|
||||
return template.Render(model, member => member.Name);
|
||||
}
|
||||
public object? ParseAndEvaluate(string text, object model)
|
||||
{
|
||||
// In case of Scriban, call ParseAndRender.
|
||||
return ParseAndRender(text, model);
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,21 @@ using WireMock.Handlers;
|
||||
using WireMock.Types;
|
||||
using Stef.Validation;
|
||||
|
||||
namespace WireMock.Transformers.Scriban
|
||||
namespace WireMock.Transformers.Scriban;
|
||||
|
||||
internal class ScribanContextFactory : ITransformerContextFactory
|
||||
{
|
||||
internal class ScribanContextFactory : ITransformerContextFactory
|
||||
private readonly IFileSystemHandler _fileSystemHandler;
|
||||
private readonly TransformerType _transformerType;
|
||||
|
||||
public ScribanContextFactory(IFileSystemHandler fileSystemHandler, TransformerType transformerType)
|
||||
{
|
||||
private readonly IFileSystemHandler _fileSystemHandler;
|
||||
private readonly TransformerType _transformerType;
|
||||
_fileSystemHandler = Guard.NotNull(fileSystemHandler);
|
||||
_transformerType = Guard.Condition(transformerType, t => t is TransformerType.Scriban or TransformerType.ScribanDotLiquid);
|
||||
}
|
||||
|
||||
public ScribanContextFactory(IFileSystemHandler fileSystemHandler, TransformerType transformerType)
|
||||
{
|
||||
_fileSystemHandler = Guard.NotNull(fileSystemHandler);
|
||||
_transformerType = Guard.Condition(transformerType, t => t == TransformerType.Scriban || t == TransformerType.ScribanDotLiquid);
|
||||
}
|
||||
|
||||
public ITransformerContext Create()
|
||||
{
|
||||
return new ScribanContext(_fileSystemHandler, _transformerType);
|
||||
}
|
||||
public ITransformerContext Create()
|
||||
{
|
||||
return new ScribanContext(_fileSystemHandler, _transformerType);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stef.Validation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using WireMock.Settings;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
@@ -11,11 +13,19 @@ namespace WireMock.Transformers;
|
||||
|
||||
internal class Transformer : ITransformer
|
||||
{
|
||||
private static readonly Type[] SupportedTypes = { typeof(bool), typeof(long), typeof(int), typeof(double), typeof(Guid), typeof(DateTime), typeof(TimeSpan), typeof(Uri) };
|
||||
|
||||
private readonly JsonSerializer _jsonSerializer;
|
||||
private readonly ITransformerContextFactory _factory;
|
||||
|
||||
public Transformer(ITransformerContextFactory factory)
|
||||
public Transformer(WireMockServerSettings settings, ITransformerContextFactory factory)
|
||||
{
|
||||
_factory = Guard.NotNull(factory);
|
||||
|
||||
_jsonSerializer = new JsonSerializer
|
||||
{
|
||||
Culture = Guard.NotNull(settings).Culture
|
||||
};
|
||||
}
|
||||
|
||||
public IBodyData? TransformBody(
|
||||
@@ -109,7 +119,7 @@ internal class Transformer : ITransformer
|
||||
});
|
||||
}
|
||||
|
||||
private static IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||
private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||
{
|
||||
return original.DetectedBodyType switch
|
||||
{
|
||||
@@ -139,33 +149,33 @@ internal class Transformer : ITransformer
|
||||
return newHeaders;
|
||||
}
|
||||
|
||||
private static IBodyData TransformBodyAsJson(ITransformerContext handlebarsContext, ReplaceNodeOptions options, object model, IBodyData original)
|
||||
private IBodyData TransformBodyAsJson(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original)
|
||||
{
|
||||
JToken? jToken = null;
|
||||
switch (original.BodyAsJson)
|
||||
{
|
||||
case JObject bodyAsJObject:
|
||||
jToken = bodyAsJObject.DeepClone();
|
||||
WalkNode(handlebarsContext, options, jToken, model);
|
||||
WalkNode(transformerContext, options, jToken, model);
|
||||
break;
|
||||
|
||||
case JArray bodyAsJArray:
|
||||
jToken = bodyAsJArray.DeepClone();
|
||||
WalkNode(handlebarsContext, options, jToken, model);
|
||||
WalkNode(transformerContext, options, jToken, model);
|
||||
break;
|
||||
|
||||
case Array bodyAsArray:
|
||||
jToken = JArray.FromObject(bodyAsArray);
|
||||
WalkNode(handlebarsContext, options, jToken, model);
|
||||
jToken = JArray.FromObject(bodyAsArray, _jsonSerializer);
|
||||
WalkNode(transformerContext, options, jToken, model);
|
||||
break;
|
||||
|
||||
case string bodyAsString:
|
||||
jToken = ReplaceSingleNode(handlebarsContext, options, bodyAsString, model);
|
||||
jToken = ReplaceSingleNode(transformerContext, options, bodyAsString, model);
|
||||
break;
|
||||
|
||||
case not null:
|
||||
jToken = JObject.FromObject(original.BodyAsJson);
|
||||
WalkNode(handlebarsContext, options, jToken, model);
|
||||
jToken = JObject.FromObject(original.BodyAsJson, _jsonSerializer);
|
||||
WalkNode(transformerContext, options, jToken, model);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -178,9 +188,9 @@ internal class Transformer : ITransformer
|
||||
};
|
||||
}
|
||||
|
||||
private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, string stringValue, object model)
|
||||
private JToken ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model)
|
||||
{
|
||||
string transformedString = handlebarsContext.ParseAndRender(stringValue, model);
|
||||
string transformedString = transformerContext.ParseAndRender(stringValue, model);
|
||||
|
||||
if (!string.Equals(stringValue, transformedString))
|
||||
{
|
||||
@@ -202,7 +212,7 @@ internal class Transformer : ITransformer
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
private static void WalkNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, JToken node, object model)
|
||||
private void WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JToken node, object model)
|
||||
{
|
||||
switch (node.Type)
|
||||
{
|
||||
@@ -210,7 +220,7 @@ internal class Transformer : ITransformer
|
||||
// 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(handlebarsContext, options, child.Value, model);
|
||||
WalkNode(transformerContext, options, child.Value, model);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -218,7 +228,7 @@ internal class Transformer : ITransformer
|
||||
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
|
||||
foreach (var child in node.Children().ToArray())
|
||||
{
|
||||
WalkNode(handlebarsContext, options, child, model);
|
||||
WalkNode(transformerContext, options, child, model);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -230,8 +240,8 @@ internal class Transformer : ITransformer
|
||||
return;
|
||||
}
|
||||
|
||||
string transformed = handlebarsContext.ParseAndRender(stringValue!, model);
|
||||
if (!string.Equals(stringValue, transformed))
|
||||
var transformed = transformerContext.ParseAndEvaluate(stringValue, model);
|
||||
if (!Equals(stringValue, transformed))
|
||||
{
|
||||
ReplaceNodeValue(options, node, transformed);
|
||||
}
|
||||
@@ -240,44 +250,88 @@ internal class Transformer : ITransformer
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private static void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, string transformedString)
|
||||
private void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, object? transformedValue)
|
||||
{
|
||||
StringUtils.TryParseQuotedString(transformedString, out var result, out _);
|
||||
if (bool.TryParse(result, out var valueAsBoolean) || bool.TryParse(transformedString, out valueAsBoolean))
|
||||
switch (transformedValue)
|
||||
{
|
||||
node.Replace(valueAsBoolean);
|
||||
return;
|
||||
}
|
||||
case JValue jValue:
|
||||
node.Replace(jValue);
|
||||
return;
|
||||
|
||||
JToken value;
|
||||
try
|
||||
{
|
||||
// Try to convert this string into a JsonObject
|
||||
value = JToken.Parse(transformedString);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// Ignore JsonException and just keep string value and convert to JToken
|
||||
value = transformedString;
|
||||
}
|
||||
case string transformedString:
|
||||
if (TryConvert(transformedString, out var convertedFromStringValue))
|
||||
{
|
||||
node.Replace(JToken.FromObject(convertedFromStringValue, _jsonSerializer));
|
||||
}
|
||||
else
|
||||
{
|
||||
node.Replace(ParseAsJObject(transformedString));
|
||||
}
|
||||
break;
|
||||
|
||||
node.Replace(value);
|
||||
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 { }:
|
||||
if (TryConvert(transformedValue, out var convertedValue))
|
||||
{
|
||||
node.Replace(JToken.FromObject(convertedValue, _jsonSerializer));
|
||||
}
|
||||
return;
|
||||
|
||||
default: // It's null, skip it. Maybe remove it ?
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static IBodyData TransformBodyAsString(ITransformerContext handlebarsContext, object model, IBodyData original)
|
||||
private static JToken ParseAsJObject(string stringValue)
|
||||
{
|
||||
return JsonUtils.TryParseAsJObject(stringValue, out var parsedAsjObject) ? parsedAsjObject : stringValue;
|
||||
}
|
||||
|
||||
private static bool TryConvert(object? transformedValue, [NotNullWhen(true)] out object? convertedValue)
|
||||
{
|
||||
foreach (var supportedType in SupportedTypes)
|
||||
{
|
||||
try
|
||||
{
|
||||
convertedValue = Convert.ChangeType(transformedValue, supportedType);
|
||||
return convertedValue is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
convertedValue = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IBodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original)
|
||||
{
|
||||
return new BodyData
|
||||
{
|
||||
Encoding = original.Encoding,
|
||||
DetectedBodyType = original.DetectedBodyType,
|
||||
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
|
||||
BodyAsString = handlebarsContext.ParseAndRender(original.BodyAsString!, model)
|
||||
BodyAsString = transformerContext.ParseAndRender(original.BodyAsString!, model)
|
||||
};
|
||||
}
|
||||
|
||||
private static IBodyData TransformBodyAsFile(ITransformerContext handlebarsContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||
private static IBodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||
{
|
||||
string transformedBodyAsFilename = handlebarsContext.ParseAndRender(original.BodyAsFile!, model);
|
||||
string transformedBodyAsFilename = transformerContext.ParseAndRender(original.BodyAsFile!, model);
|
||||
|
||||
if (!useTransformerForBodyAsFile)
|
||||
{
|
||||
@@ -289,12 +343,12 @@ internal class Transformer : ITransformer
|
||||
};
|
||||
}
|
||||
|
||||
string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename);
|
||||
string text = transformerContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename);
|
||||
return new BodyData
|
||||
{
|
||||
DetectedBodyType = BodyType.String,
|
||||
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
|
||||
BodyAsString = handlebarsContext.ParseAndRender(text, model),
|
||||
BodyAsString = transformerContext.ParseAndRender(text, model),
|
||||
BodyAsFile = transformedBodyAsFilename
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user