Add ReplaceNodeOption flag (#710)

This commit is contained in:
Stef Heyenrath
2022-01-05 17:03:29 +01:00
committed by GitHub
parent e8e28c21a1
commit b153024de3
24 changed files with 563 additions and 174 deletions

View File

@@ -1,19 +1,17 @@
using Newtonsoft.Json;
using HandlebarsDotNet;
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Server; using WireMock.Server;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Util;
using System.Threading.Tasks;
using WireMock.Types; using WireMock.Types;
using WireMock.Util;
namespace WireMock.Net.ConsoleApplication namespace WireMock.Net.ConsoleApplication
{ {
@@ -359,7 +357,7 @@ namespace WireMock.Net.ConsoleApplication
.WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}") .WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}")
.WithHeader("xyz_{{request.headers.Postman-Token}}", "token is {{request.headers.Postman-Token}}") .WithHeader("xyz_{{request.headers.Postman-Token}}", "token is {{request.headers.Postman-Token}}")
.WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, add={{Math.Add request.query.start.[0] 42}} bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }") .WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, add={{Math.Add request.query.start.[0] 42}} bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }")
.WithTransformer(TransformerType.Handlebars) .WithTransformer(TransformerType.Handlebars, true, ReplaceNodeOptions.None)
.WithDelay(TimeSpan.FromMilliseconds(100)) .WithDelay(TimeSpan.FromMilliseconds(100))
); );

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace WireMock.Admin.Mappings namespace WireMock.Admin.Mappings
{ {
@@ -64,10 +64,15 @@ namespace WireMock.Admin.Mappings
public string TransformerType { get; set; } public string TransformerType { get; set; }
/// <summary> /// <summary>
/// Use the Handlerbars transformer for the content from the referenced BodyAsFile. /// Use the Handlebars transformer for the content from the referenced BodyAsFile.
/// </summary> /// </summary>
public bool? UseTransformerForBodyAsFile { get; set; } public bool? UseTransformerForBodyAsFile { get; set; }
/// <summary>
/// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary>
public string TransformerReplaceNodeOptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the headers. /// Gets or sets the headers.
/// </summary> /// </summary>

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Types;
namespace WireMock.Admin.Mappings namespace WireMock.Admin.Mappings
{ {
@@ -42,5 +43,10 @@ namespace WireMock.Admin.Mappings
/// Gets the type of the transformer. /// Gets the type of the transformer.
/// </summary> /// </summary>
public string TransformerType { get; set; } public string TransformerType { get; set; }
/// <summary>
/// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary>
public string TransformerReplaceNodeOptions { get; set; }
} }
} }

View File

@@ -1,13 +1,13 @@
namespace WireMock.Models namespace WireMock.Models
{ {
/// <summary> /// <summary>
/// IWebhook /// IWebhook
/// </summary> /// </summary>
public interface IWebhook public interface IWebhook
{ {
/// <summary> /// <summary>
/// Request /// Request
/// </summary> /// </summary>
IWebhookRequest Request { get; set; } IWebhookRequest Request { get; set; }
} }
} }

View File

@@ -1,42 +1,47 @@
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Models namespace WireMock.Models
{ {
/// <summary> /// <summary>
/// IWebhookRequest /// IWebhookRequest
/// </summary> /// </summary>
public interface IWebhookRequest public interface IWebhookRequest
{ {
/// <summary> /// <summary>
/// The Webhook Url. /// The Webhook Url.
/// </summary> /// </summary>
string Url { get; set; } string Url { get; set; }
/// <summary> /// <summary>
/// The method to use. /// The method to use.
/// </summary> /// </summary>
string Method { get; set; } string Method { get; set; }
/// <summary> /// <summary>
/// The Headers to send. /// The Headers to send.
/// </summary> /// </summary>
IDictionary<string, WireMockList<string>> Headers { get; } IDictionary<string, WireMockList<string>> Headers { get; }
/// <summary> /// <summary>
/// The body to send. /// The body to send.
/// </summary> /// </summary>
IBodyData BodyData { get; set; } IBodyData BodyData { get; set; }
/// <summary> /// <summary>
/// Use Transformer. /// Use Transformer.
/// </summary> /// </summary>
bool? UseTransformer { get; set; } bool? UseTransformer { get; set; }
/// <summary> /// <summary>
/// The transformer type. /// The transformer type.
/// </summary> /// </summary>
TransformerType TransformerType { get; set; } TransformerType TransformerType { get; set; }
}
/// <summary>
/// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary>
ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; }
}
} }

View File

@@ -0,0 +1,36 @@
using System;
namespace WireMock.Types
{
/// <summary>
/// Flags to use when replace a JSON node using the Transformer.
/// </summary>
[Flags]
public enum ReplaceNodeOptions
{
/// <summary>
/// Default
/// </summary>
None = 0
///// <summary>
///// Replace boolean string value to a real boolean value. (This is used by default to maintain backward compatibility.)
///// </summary>
//Bool = 0b00000001,
///// <summary>
///// Replace integer string value to a real integer value.
///// </summary>
//Integer = 0b00000010,
///// <summary>
///// Replace long string value to a real long value.
///// </summary>
//Long = 0b00000100,
///// <summary>
///// Replace all string values to a real values.
///// </summary>
//All = Bool | Integer | Long
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
@@ -55,7 +55,7 @@ namespace WireMock.Http
throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported."); throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported.");
} }
(bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers); (bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers, request.TransformerReplaceNodeOptions);
} }
else else
{ {

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
@@ -26,5 +26,8 @@ namespace WireMock.Models
/// <inheritdoc cref="IWebhookRequest.TransformerType"/> /// <inheritdoc cref="IWebhookRequest.TransformerType"/>
public TransformerType TransformerType { get; set; } public TransformerType TransformerType { get; set; }
/// <inheritdoc cref="IWebhookRequest.TransformerReplaceNodeOptions"/>
public ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; }
} }
} }

View File

@@ -1,13 +1,13 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using WireMock.Logging;
using System.Linq; using System.Linq;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Http; using WireMock.Http;
using WireMock.Owin.Mappers; using WireMock.Owin.Mappers;
using WireMock.Serialization; using WireMock.Serialization;
using WireMock.Types; using WireMock.Types;
using Stef.Validation;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Settings; using WireMock.Settings;
#if !USE_ASPNETCORE #if !USE_ASPNETCORE

View File

@@ -1,4 +1,4 @@
using WireMock.Types; using WireMock.Types;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders
{ {
@@ -13,7 +13,15 @@ namespace WireMock.ResponseBuilders
/// <returns> /// <returns>
/// The <see cref="IResponseBuilder"/>. /// The <see cref="IResponseBuilder"/>.
/// </returns> /// </returns>
IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile = false); IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile);
/// <summary>
/// Use the Handlebars.Net ResponseMessage transformer.
/// </summary>
/// <returns>
/// The <see cref="IResponseBuilder"/>.
/// </returns>
IResponseBuilder WithTransformer(ReplaceNodeOptions options);
/// <summary> /// <summary>
/// Use a specific ResponseMessage transformer. /// Use a specific ResponseMessage transformer.
@@ -21,6 +29,6 @@ namespace WireMock.ResponseBuilders
/// <returns> /// <returns>
/// The <see cref="IResponseBuilder"/>. /// The <see cref="IResponseBuilder"/>.
/// </returns> /// </returns>
IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false); IResponseBuilder WithTransformer(TransformerType transformerType = TransformerType.Handlebars, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None);
} }
} }

View File

@@ -8,6 +8,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Proxy; using WireMock.Proxy;
using WireMock.ResponseProviders; using WireMock.ResponseProviders;
using WireMock.Settings; using WireMock.Settings;
@@ -16,7 +17,6 @@ using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban; using WireMock.Transformers.Scriban;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
using Stef.Validation;
namespace WireMock.ResponseBuilders namespace WireMock.ResponseBuilders
{ {
@@ -68,10 +68,15 @@ namespace WireMock.ResponseBuilders
public TransformerType TransformerType { get; private set; } public TransformerType TransformerType { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether to use the Handlerbars transformer for the content from the referenced BodyAsFile. /// Gets a value indicating whether to use the Handlebars transformer for the content from the referenced BodyAsFile.
/// </summary> /// </summary>
public bool UseTransformerForBodyAsFile { get; private set; } public bool UseTransformerForBodyAsFile { get; private set; }
/// <summary>
/// Gets the ReplaceNodeOptions to use when transforming a JSON node.
/// </summary>
public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; }
/// <summary> /// <summary>
/// Gets the response message. /// Gets the response message.
/// </summary> /// </summary>
@@ -330,20 +335,26 @@ namespace WireMock.ResponseBuilders
} }
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(bool)"/> /// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(bool)"/>
public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile = false) public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile)
{ {
UseTransformer = true; return WithTransformer(TransformerType.Handlebars, transformContentFromBodyAsFile);
TransformerType = TransformerType.Handlebars;
UseTransformerForBodyAsFile = transformContentFromBodyAsFile;
return this;
} }
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(TransformerType, bool)"/> /// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(ReplaceNodeOptions)"/>
public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false) public IResponseBuilder WithTransformer(ReplaceNodeOptions options)
{
return WithTransformer(TransformerType.Handlebars, false, options);
}
#pragma warning disable CS1574
/// <inheritdoc cref="ITransformResponseBuilder.WithTransformer(TransformerType, bool, ReplaceNodeOptions)"/>
#pragma warning restore CS1574
public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None)
{ {
UseTransformer = true; UseTransformer = true;
TransformerType = transformerType; TransformerType = transformerType;
UseTransformerForBodyAsFile = transformContentFromBodyAsFile; UseTransformerForBodyAsFile = transformContentFromBodyAsFile;
TransformerReplaceNodeOptions = options;
return this; return this;
} }
@@ -458,7 +469,7 @@ namespace WireMock.ResponseBuilders
throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported."); throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported.");
} }
return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile), null); return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null);
} }
if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true) if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true)

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
@@ -16,7 +16,7 @@ namespace WireMock.Serialization
public MappingConverter(MatcherMapper mapper) public MappingConverter(MatcherMapper mapper)
{ {
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _mapper = Guard.NotNull(mapper, nameof(mapper));
} }
public MappingModel ToMappingModel(IMapping mapping) public MappingModel ToMappingModel(IMapping mapping)
@@ -130,6 +130,7 @@ namespace WireMock.Serialization
mappingModel.Response.UseTransformer = null; mappingModel.Response.UseTransformer = null;
mappingModel.Response.TransformerType = null; mappingModel.Response.TransformerType = null;
mappingModel.Response.UseTransformerForBodyAsFile = null; mappingModel.Response.UseTransformerForBodyAsFile = null;
mappingModel.Response.TransformerReplaceNodeOptions = null;
mappingModel.Response.BodyEncoding = null; mappingModel.Response.BodyEncoding = null;
mappingModel.Response.ProxyUrl = response.ProxyAndRecordSettings.Url; mappingModel.Response.ProxyUrl = response.ProxyAndRecordSettings.Url;
mappingModel.Response.Fault = null; mappingModel.Response.Fault = null;
@@ -150,6 +151,7 @@ namespace WireMock.Serialization
{ {
mappingModel.Response.UseTransformer = response.UseTransformer; mappingModel.Response.UseTransformer = response.UseTransformer;
mappingModel.Response.TransformerType = response.TransformerType.ToString(); mappingModel.Response.TransformerType = response.TransformerType.ToString();
mappingModel.Response.TransformerReplaceNodeOptions = response.TransformerReplaceNodeOptions.ToString();
} }
if (response.UseTransformerForBodyAsFile) if (response.UseTransformerForBodyAsFile)

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
@@ -31,6 +31,13 @@ namespace WireMock.Serialization
transformerType = TransformerType.Handlebars; transformerType = TransformerType.Handlebars;
} }
webhook.Request.TransformerType = transformerType; webhook.Request.TransformerType = transformerType;
if (!Enum.TryParse<ReplaceNodeOptions>(model.Request.TransformerReplaceNodeOptions, out var option))
{
option = ReplaceNodeOptions.None;
}
webhook.Request.TransformerReplaceNodeOptions = option;
} }
IEnumerable<string> contentTypeHeader = null; IEnumerable<string> contentTypeHeader = null;
@@ -76,7 +83,8 @@ namespace WireMock.Serialization
Method = webhook.Request.Method, Method = webhook.Request.Method,
Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()), Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()),
UseTransformer = webhook.Request.UseTransformer, UseTransformer = webhook.Request.UseTransformer,
TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null,
TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString()
} }
}; };
@@ -93,6 +101,7 @@ namespace WireMock.Serialization
break; break;
default: default:
// Empty
break; break;
} }
} }

View File

@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Admin.Scenarios; using WireMock.Admin.Scenarios;
using WireMock.Admin.Settings; using WireMock.Admin.Settings;
@@ -24,7 +25,6 @@ using WireMock.Serialization;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
using Stef.Validation;
namespace WireMock.Server namespace WireMock.Server
{ {
@@ -785,7 +785,15 @@ namespace WireMock.Server
{ {
transformerType = TransformerType.Handlebars; transformerType = TransformerType.Handlebars;
} }
responseBuilder = responseBuilder.WithTransformer(transformerType, responseModel.UseTransformerForBodyAsFile == true);
if (!Enum.TryParse<ReplaceNodeOptions>(responseModel.TransformerReplaceNodeOptions, out var option))
{
option = ReplaceNodeOptions.None;
}
responseBuilder = responseBuilder.WithTransformer(
transformerType,
responseModel.UseTransformerForBodyAsFile == true,
option);
} }
if (!string.IsNullOrEmpty(responseModel.ProxyUrl)) if (!string.IsNullOrEmpty(responseModel.ProxyUrl))

View File

@@ -1,18 +1,18 @@
using HandlebarsDotNet; using HandlebarsDotNet;
using WireMock.Handlers; using WireMock.Handlers;
namespace WireMock.Transformers.Handlebars namespace WireMock.Transformers.Handlebars
{ {
internal class HandlebarsContext : IHandlebarsContext internal class HandlebarsContext : IHandlebarsContext
{ {
public IHandlebars Handlebars { get; set; } public IHandlebars Handlebars { get; set; }
public IFileSystemHandler FileSystemHandler { get; set; } public IFileSystemHandler FileSystemHandler { get; set; }
public string ParseAndRender(string text, object model) public string ParseAndRender(string text, object model)
{ {
var template = Handlebars.Compile(text); var template = Handlebars.Compile(text);
return template(model); return template(model);
} }
} }
} }

View File

@@ -1,13 +1,14 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Transformers namespace WireMock.Transformers
{ {
interface ITransformer interface ITransformer
{ {
ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile); ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options);
(IBodyData BodyData, IDictionary<string, WireMockList<string>> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary<string, WireMockList<string>> headers); (IBodyData BodyData, IDictionary<string, WireMockList<string>> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary<string, WireMockList<string>> headers, ReplaceNodeOptions options);
} }
} }

View File

@@ -18,7 +18,7 @@ namespace WireMock.Transformers
_factory = factory ?? throw new ArgumentNullException(nameof(factory)); _factory = factory ?? throw new ArgumentNullException(nameof(factory));
} }
public (IBodyData BodyData, IDictionary<string, WireMockList<string>> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary<string, WireMockList<string>> headers) public (IBodyData BodyData, IDictionary<string, WireMockList<string>> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary<string, WireMockList<string>> headers, ReplaceNodeOptions options)
{ {
var transformerContext = _factory.Create(); var transformerContext = _factory.Create();
@@ -31,13 +31,13 @@ namespace WireMock.Transformers
IBodyData newBodyData = null; IBodyData newBodyData = null;
if (bodyData?.DetectedBodyType != null) if (bodyData?.DetectedBodyType != null)
{ {
newBodyData = TransformBodyData(transformerContext, model, bodyData, false); newBodyData = TransformBodyData(transformerContext, options, model, bodyData, false);
} }
return (newBodyData, TransformHeaders(transformerContext, model, headers)); return (newBodyData, TransformHeaders(transformerContext, model, headers));
} }
public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile) public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options)
{ {
var transformerContext = _factory.Create(); var transformerContext = _factory.Create();
@@ -50,7 +50,7 @@ namespace WireMock.Transformers
if (original.BodyData?.DetectedBodyType != null) if (original.BodyData?.DetectedBodyType != null)
{ {
responseMessage.BodyData = TransformBodyData(transformerContext, model, original.BodyData, useTransformerForBodyAsFile); responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile);
if (original.BodyData.DetectedBodyType == BodyType.String) if (original.BodyData.DetectedBodyType == BodyType.String)
{ {
@@ -77,12 +77,12 @@ namespace WireMock.Transformers
return responseMessage; return responseMessage;
} }
private static IBodyData TransformBodyData(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile) private static IBodyData TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original, bool useTransformerForBodyAsFile)
{ {
switch (original?.DetectedBodyType) switch (original?.DetectedBodyType)
{ {
case BodyType.Json: case BodyType.Json:
return TransformBodyAsJson(transformerContext, model, original); return TransformBodyAsJson(transformerContext, options, model, original);
case BodyType.File: case BodyType.File:
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile); return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
@@ -114,28 +114,28 @@ namespace WireMock.Transformers
return newHeaders; return newHeaders;
} }
private static IBodyData TransformBodyAsJson(ITransformerContext handlebarsContext, object model, IBodyData original) private static IBodyData TransformBodyAsJson(ITransformerContext handlebarsContext, ReplaceNodeOptions options, object model, IBodyData original)
{ {
JToken jToken; JToken jToken;
switch (original.BodyAsJson) switch (original.BodyAsJson)
{ {
case JObject bodyAsJObject: case JObject bodyAsJObject:
jToken = bodyAsJObject.DeepClone(); jToken = bodyAsJObject.DeepClone();
WalkNode(handlebarsContext, jToken, model); WalkNode(handlebarsContext, options, jToken, model);
break; break;
case Array bodyAsArray: case Array bodyAsArray:
jToken = JArray.FromObject(bodyAsArray); jToken = JArray.FromObject(bodyAsArray);
WalkNode(handlebarsContext, jToken, model); WalkNode(handlebarsContext, options, jToken, model);
break; break;
case string bodyAsString: case string bodyAsString:
jToken = ReplaceSingleNode(handlebarsContext, bodyAsString, model); jToken = ReplaceSingleNode(handlebarsContext, options, bodyAsString, model);
break; break;
default: default:
jToken = JObject.FromObject(original.BodyAsJson); jToken = JObject.FromObject(original.BodyAsJson);
WalkNode(handlebarsContext, jToken, model); WalkNode(handlebarsContext, options, jToken, model);
break; break;
} }
@@ -148,7 +148,7 @@ namespace WireMock.Transformers
}; };
} }
private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, string stringValue, object model) private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, string stringValue, object model)
{ {
string transformedString = handlebarsContext.ParseAndRender(stringValue, model); string transformedString = handlebarsContext.ParseAndRender(stringValue, model);
@@ -158,7 +158,7 @@ namespace WireMock.Transformers
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}"); JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
JToken node = dummy[property]; JToken node = dummy[property];
ReplaceNodeValue(node, transformedString); ReplaceNodeValue(options, node, transformedString);
return dummy[property]; return dummy[property];
} }
@@ -166,44 +166,47 @@ namespace WireMock.Transformers
return stringValue; return stringValue;
} }
private static void WalkNode(ITransformerContext handlebarsContext, JToken node, object model) private static void WalkNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, JToken node, object model)
{ {
if (node.Type == JTokenType.Object) switch (node.Type)
{ {
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions. case JTokenType.Object:
foreach (JProperty child in node.Children<JProperty>().ToArray()) // 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, child.Value, model); {
} WalkNode(handlebarsContext, options, child.Value, model);
} }
else if (node.Type == JTokenType.Array) break;
{
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (JToken child in node.Children().ToArray())
{
WalkNode(handlebarsContext, child, model);
}
}
else if (node.Type == JTokenType.String)
{
// In case of string, try to transform the value.
string stringValue = node.Value<string>();
if (string.IsNullOrEmpty(stringValue))
{
return;
}
string transformedString = handlebarsContext.ParseAndRender(stringValue, model); case JTokenType.Array:
if (!string.Equals(stringValue, transformedString)) // In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
{ foreach (var child in node.Children().ToArray())
ReplaceNodeValue(node, transformedString); {
} WalkNode(handlebarsContext, options, child, model);
}
break;
case JTokenType.String:
// In case of string, try to transform the value.
string stringValue = node.Value<string>();
if (string.IsNullOrEmpty(stringValue))
{
return;
}
string transformed = handlebarsContext.ParseAndRender(stringValue, model);
if (!string.Equals(stringValue, transformed))
{
ReplaceNodeValue(options, node, transformed);
}
break;
} }
} }
private static void ReplaceNodeValue(JToken node, string stringValue) private static void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, string transformedString)
{ {
if (bool.TryParse(stringValue, out bool valueAsBoolean)) StringUtils.TryParseQuotedString(transformedString, out var result, out _);
if (bool.TryParse(result, out var valueAsBoolean) || bool.TryParse(transformedString, out valueAsBoolean))
{ {
node.Replace(valueAsBoolean); node.Replace(valueAsBoolean);
return; return;
@@ -213,12 +216,12 @@ namespace WireMock.Transformers
try try
{ {
// Try to convert this string into a JsonObject // Try to convert this string into a JsonObject
value = JToken.Parse(stringValue); value = JToken.Parse(transformedString);
} }
catch (JsonException) catch (JsonException)
{ {
// Ignore JsonException and just keep string value and convert to JToken // Ignore JsonException and just keep string value and convert to JToken
value = stringValue; value = transformedString;
} }
node.Replace(value); node.Replace(value);
@@ -248,18 +251,15 @@ namespace WireMock.Transformers
BodyAsFile = transformedBodyAsFilename BodyAsFile = transformedBodyAsFilename
}; };
} }
else
{
string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename);
return new BodyData string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename);
{ return new BodyData
DetectedBodyType = BodyType.String, {
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, DetectedBodyType = BodyType.String,
BodyAsString = handlebarsContext.ParseAndRender(text, model), DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
BodyAsFile = transformedBodyAsFilename BodyAsString = handlebarsContext.ParseAndRender(text, model),
}; BodyAsFile = transformedBodyAsFilename
} };
} }
} }
} }

View File

@@ -10,6 +10,33 @@ namespace WireMock.Util
{ {
internal static class JsonUtils internal static class JsonUtils
{ {
public static bool TryParseAsComplexObject(string strInput, out JToken token)
{
token = null;
if (string.IsNullOrWhiteSpace(strInput))
{
return false;
}
strInput = strInput.Trim();
if ((!strInput.StartsWith("{") || !strInput.EndsWith("}")) && (!strInput.StartsWith("[") || !strInput.EndsWith("]")))
{
return false;
}
try
{
// Try to convert this string into a JToken
token = JToken.Parse(strInput);
return true;
}
catch
{
return false;
}
}
public static string Serialize<T>(T value) public static string Serialize<T>(T value)
{ {
return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues); return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues);

View File

@@ -0,0 +1,42 @@
using System.Linq;
using System.Text.RegularExpressions;
namespace WireMock.Util
{
internal static class StringUtils
{
public static bool TryParseQuotedString(string value, out string result, out char quote)
{
result = null;
quote = '\0';
if (value == null || value.Length < 2)
{
return false;
}
quote = value[0]; // This can be single or a double quote
if (quote != '"' && quote != '\'')
{
return false;
}
if (value.Last() != quote)
{
return false;
}
try
{
result = Regex.Unescape(value.Substring(1, value.Length - 2));
return true;
}
catch
{
// Ignore Exception, just continue and return false.
}
return false;
}
}
}

View File

@@ -83,7 +83,7 @@
<PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost" Version="5.2.6" /> <PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost" Version="5.2.6" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Scriban.Signed" Version="2.1.4" /> <PackageReference Include="Scriban.Signed" Version="2.1.4" />
<PackageReference Include="Mapster" Version="7.2.0" /> <!--<PackageReference Include="Mapster" Version="7.2.0" />-->
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">

View File

@@ -1,10 +1,8 @@
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Server; using WireMock.Server;

View File

@@ -7,6 +7,7 @@ using WireMock.Handlers;
using WireMock.Models; using WireMock.Models;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Types;
using Xunit; using Xunit;
namespace WireMock.Net.Tests.ResponseBuilders namespace WireMock.Net.Tests.ResponseBuilders
@@ -15,15 +16,14 @@ namespace WireMock.Net.Tests.ResponseBuilders
{ {
private const string ClientIp = "::1"; private const string ClientIp = "::1";
private readonly Mock<IFileSystemHandler> _filesystemHandlerMock;
private readonly WireMockServerSettings _settings = new WireMockServerSettings(); private readonly WireMockServerSettings _settings = new WireMockServerSettings();
public ResponseWithHandlebarsRandomTests() public ResponseWithHandlebarsRandomTests()
{ {
_filesystemHandlerMock = new Mock<IFileSystemHandler>(MockBehavior.Strict); var filesystemHandlerMock = new Mock<IFileSystemHandler>(MockBehavior.Strict);
_filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny<string>())).Returns("abc"); filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny<string>())).Returns("abc");
_settings.FileSystemHandler = _filesystemHandlerMock.Object; _settings.FileSystemHandler = filesystemHandlerMock.Object;
} }
[Fact] [Fact]
@@ -73,6 +73,31 @@ namespace WireMock.Net.Tests.ResponseBuilders
Check.That(j["Value"].Type).IsEqualTo(JTokenType.Boolean); Check.That(j["Value"].Type).IsEqualTo(JTokenType.Boolean);
} }
[Theory]
[InlineData(ReplaceNodeOptions.None, JTokenType.Integer)]
//[InlineData(ReplaceNodeOptions.Bool, JTokenType.String)]
//[InlineData(ReplaceNodeOptions.Integer, JTokenType.Integer)]
//[InlineData(ReplaceNodeOptions.Bool | ReplaceNodeOptions.Integer, JTokenType.Integer)]
public async Task Response_ProvideResponseAsync_Handlebars_Random1_Integer(ReplaceNodeOptions options, JTokenType expected)
{
// Assign
var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp);
var responseBuilder = Response.Create()
.WithBodyAsJson(new
{
Value = "{{Random Type=\"Integer\"}}"
})
.WithTransformer(options);
// Act
var response = await responseBuilder.ProvideResponseAsync(request, _settings).ConfigureAwait(false);
// Assert
JObject j = JObject.FromObject(response.Message.BodyData.BodyAsJson);
Check.That(j["Value"].Type).IsEqualTo(expected);
}
[Fact] [Fact]
public async Task Response_ProvideResponseAsync_Handlebars_Random1_Guid() public async Task Response_ProvideResponseAsync_Handlebars_Random1_Guid()
{ {

View File

@@ -23,17 +23,16 @@ namespace WireMock.Net.Tests.ResponseBuilders
{ {
public class ResponseWithTransformerTests public class ResponseWithTransformerTests
{ {
private readonly Mock<IFileSystemHandler> _filesystemHandlerMock;
private readonly WireMockServerSettings _settings = new WireMockServerSettings(); private readonly WireMockServerSettings _settings = new WireMockServerSettings();
private const string ClientIp = "::1"; private const string ClientIp = "::1";
public ResponseWithTransformerTests() public ResponseWithTransformerTests()
{ {
_filesystemHandlerMock = new Mock<IFileSystemHandler>(MockBehavior.Strict); var filesystemHandlerMock = new Mock<IFileSystemHandler>(MockBehavior.Strict);
_filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny<string>())).Returns("abc"); filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny<string>())).Returns("abc");
_settings.FileSystemHandler = _filesystemHandlerMock.Object; _settings.FileSystemHandler = filesystemHandlerMock.Object;
} }
[Theory] [Theory]
@@ -366,7 +365,7 @@ namespace WireMock.Net.Tests.ResponseBuilders
public async Task Response_ProvideResponse_Transformer_WithBodyAsJson_ResultAsObject(TransformerType transformerType) public async Task Response_ProvideResponse_Transformer_WithBodyAsJson_ResultAsObject(TransformerType transformerType)
{ {
// Assign // Assign
string jsonString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"Wiremock\" } ] }"; string jsonString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"WireMock\" } ] }";
var bodyData = new BodyData var bodyData = new BodyData
{ {
BodyAsJson = JsonConvert.DeserializeObject(jsonString), BodyAsJson = JsonConvert.DeserializeObject(jsonString),
@@ -386,6 +385,108 @@ namespace WireMock.Net.Tests.ResponseBuilders
Check.That(JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson)).Equals("{\"x\":\"test /foo_object\"}"); Check.That(JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson)).Equals("{\"x\":\"test /foo_object\"}");
} }
//[Theory]
//[InlineData(TransformerType.Handlebars, "a")]
//[InlineData(TransformerType.Handlebars, "42")]
//[InlineData(TransformerType.Handlebars, "{")]
//[InlineData(TransformerType.Handlebars, "]")]
//[InlineData(TransformerType.Handlebars, " ")]
//public async Task Response_ProvideResponse_Transformer_WithBodyAsJsonWithExtraQuotes_AndSpecialOption_MakesAString_ResultAsObject(TransformerType transformerType, string text)
//{
// string jsonString = $"{{ \"x\": \"{text}\" }}";
// var bodyData = new BodyData
// {
// BodyAsJson = JsonConvert.DeserializeObject(jsonString),
// DetectedBodyType = BodyType.Json,
// Encoding = Encoding.UTF8
// };
// var request = new RequestMessage(new UrlDetails("http://localhost/foo_object"), "POST", ClientIp, bodyData);
// var responseBuilder = Response.Create()
// .WithBodyAsJson(new { text = "\"{{request.bodyAsJson.x}}\"" })
// .WithTransformer(transformerType, false, ReplaceNodeOptions.Default);
// // Act
// var response = await responseBuilder.ProvideResponseAsync(request, _settings).ConfigureAwait(false);
// // Assert
// JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson).Should().Be($"{{\"text\":\"{text}\"}}");
//}
[Theory]
[InlineData(TransformerType.Handlebars, "\"\"", "\"\"")]
[InlineData(TransformerType.Handlebars, "\"a\"", "\"a\"")]
[InlineData(TransformerType.Handlebars, "\" \"", "\" \"")]
[InlineData(TransformerType.Handlebars, "\"'\"", "\"'\"")]
[InlineData(TransformerType.Handlebars, "\"false\"", "false")] // bool is special
[InlineData(TransformerType.Handlebars, "false", "false")]
[InlineData(TransformerType.Handlebars, "\"true\"", "true")] // bool is special
[InlineData(TransformerType.Handlebars, "true", "true")]
[InlineData(TransformerType.Handlebars, "\"-42\"", "-42")] // todo
[InlineData(TransformerType.Handlebars, "-42", "-42")]
[InlineData(TransformerType.Handlebars, "\"2147483647\"", "2147483647")] // todo
[InlineData(TransformerType.Handlebars, "2147483647", "2147483647")]
[InlineData(TransformerType.Handlebars, "\"9223372036854775807\"", "9223372036854775807")] // todo
[InlineData(TransformerType.Handlebars, "9223372036854775807", "9223372036854775807")]
public async Task Response_ProvideResponse_Transformer_WithBodyAsJson_And_ReplaceNodeOptionsKeep(TransformerType transformerType, string value, string expected)
{
string jsonString = $"{{ \"x\": {value} }}";
var bodyData = new BodyData
{
BodyAsJson = JsonConvert.DeserializeObject(jsonString),
DetectedBodyType = BodyType.Json,
Encoding = Encoding.UTF8
};
var request = new RequestMessage(new UrlDetails("http://localhost/foo_object"), "POST", ClientIp, bodyData);
var responseBuilder = Response.Create()
.WithBodyAsJson(new { text = "{{request.bodyAsJson.x}}" })
.WithTransformer(transformerType, false, ReplaceNodeOptions.None);
// Act
var response = await responseBuilder.ProvideResponseAsync(request, _settings).ConfigureAwait(false);
// Assert
JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson).Should().Be($"{{\"text\":{expected}}}");
}
[Theory]
[InlineData(TransformerType.Handlebars, "\"\"", "\"\"")]
[InlineData(TransformerType.Handlebars, "\"a\"", "\"a\"")]
[InlineData(TransformerType.Handlebars, "\" \"", "\" \"")]
[InlineData(TransformerType.Handlebars, "\"'\"", "\"'\"")]
[InlineData(TransformerType.Handlebars, "\"false\"", "false")] // bool is special
[InlineData(TransformerType.Handlebars, "false", "false")]
[InlineData(TransformerType.Handlebars, "\"true\"", "true")] // bool is special
[InlineData(TransformerType.Handlebars, "true", "true")]
[InlineData(TransformerType.Handlebars, "\"-42\"", "\"-42\"")]
[InlineData(TransformerType.Handlebars, "-42", "\"-42\"")]
[InlineData(TransformerType.Handlebars, "\"2147483647\"", "\"2147483647\"")]
[InlineData(TransformerType.Handlebars, "2147483647", "\"2147483647\"")]
[InlineData(TransformerType.Handlebars, "\"9223372036854775807\"", "\"9223372036854775807\"")]
[InlineData(TransformerType.Handlebars, "9223372036854775807", "\"9223372036854775807\"")]
public async Task Response_ProvideResponse_Transformer_WithBodyAsJsonWithExtraQuotes_AlwaysMakesString(TransformerType transformerType, string value, string expected)
{
string jsonString = $"{{ \"x\": {value} }}";
var bodyData = new BodyData
{
BodyAsJson = JsonConvert.DeserializeObject(jsonString),
DetectedBodyType = BodyType.Json,
Encoding = Encoding.UTF8
};
var request = new RequestMessage(new UrlDetails("http://localhost/foo_object"), "POST", ClientIp, bodyData);
var responseBuilder = Response.Create()
.WithBodyAsJson(new { text = "\"{{request.bodyAsJson.x}}\"" })
.WithTransformer(transformerType);
// Act
var response = await responseBuilder.ProvideResponseAsync(request, _settings).ConfigureAwait(false);
// Assert
JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson).Should().Be($"{{\"text\":{expected}}}");
}
[Theory] [Theory]
[InlineData(TransformerType.Handlebars)] [InlineData(TransformerType.Handlebars)]
//[InlineData(TransformerType.Scriban)] Scriban cannot access dynamic Json Objects //[InlineData(TransformerType.Scriban)] Scriban cannot access dynamic Json Objects

View File

@@ -0,0 +1,104 @@
using FluentAssertions;
using WireMock.Util;
using Xunit;
namespace WireMock.Net.Tests.Util
{
public class StringUtilsTests
{
[Theory]
[InlineData("'s")]
[InlineData("\"s")]
public void StringUtils_TryParseQuotedString_With_UnexpectedUnclosedString_Returns_False(string input)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
// Assert
valid.Should().BeFalse();
}
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData("x")]
public void StringUtils_TryParseQuotedString_With_InvalidStringLength_Returns_False(string input)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
// Assert
valid.Should().BeFalse();
}
[Theory]
[InlineData("xx")]
[InlineData(" ")]
public void StringUtils_TryParseQuotedString_With_InvalidStringQuoteCharacter_Returns_False(string input)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
// Assert
valid.Should().BeFalse();
}
[Fact]
public void StringUtils_TryParseQuotedString_With_UnexpectedUnrecognizedEscapeSequence_Returns_False()
{
// Arrange
string input = new string(new[] { '"', '\\', 'u', '?', '"' });
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
// Assert
valid.Should().BeFalse();
}
[Theory]
[InlineData("''", "")]
[InlineData("'s'", "s")]
[InlineData("'\\\\'", "\\")]
[InlineData("'\\n'", "\n")]
public void StringUtils_TryParseQuotedString_SingleQuotedString(string input, string expectedResult)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
// Assert
valid.Should().BeTrue();
result.Should().Be(expectedResult);
quote.Should().Be('\'');
}
[Theory]
[InlineData("\"\"", "")]
[InlineData("\"\\\\\"", "\\")]
[InlineData("\"\\n\"", "\n")]
[InlineData("\"\\\\n\"", "\\n")]
[InlineData("\"\\\\new\"", "\\new")]
[InlineData("\"[]\"", "[]")]
[InlineData("\"()\"", "()")]
[InlineData("\"(\\\"\\\")\"", "(\"\")")]
[InlineData("\"/\"", "/")]
[InlineData("\"a\"", "a")]
[InlineData("\"This \\\"is\\\" a test.\"", "This \"is\" a test.")]
[InlineData(@"""This \""is\"" b test.""", @"This ""is"" b test.")]
[InlineData("\"ab\\\"cd\"", "ab\"cd")]
[InlineData("\"\\\"\"", "\"")]
[InlineData("\"\\\"\\\"\"", "\"\"")]
[InlineData("\"AB YZ 19 \uD800\udc05 \u00e4\"", "AB YZ 19 \uD800\udc05 \u00e4")]
[InlineData("\"\\\\\\\\192.168.1.1\\\\audio\\\\new\"", "\\\\192.168.1.1\\audio\\new")]
public void StringUtils_TryParseQuotedString_DoubleQuotedString(string input, string expectedResult)
{
// Act
bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
// Assert
valid.Should().BeTrue();
result.Should().Be(expectedResult);
quote.Should().Be('"');
}
}
}