From 371bfdc1608eb94dc344344ded74a825c7f6d81a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 31 Aug 2025 08:48:29 +0200 Subject: [PATCH 1/6] TypeLoader: implement Try methods (#1358) * TypeLoader: implement Try methods * fix --- .../Util/MimeKitUtils.cs | 22 ++-- .../Request/RequestMessageMultiPartMatcher.cs | 14 ++- .../Owin/Mappers/OwinResponseMapper.cs | 9 +- src/WireMock.Net.Minimal/RequestMessage.cs | 11 +- .../Serialization/MatcherMapper.cs | 38 +++++- .../Server/WireMockServer.ConvertMapping.cs | 4 +- .../Request/RequestMessageGraphQLMatcher.cs | 7 +- .../Request/RequestMessageProtoBufMatcher.cs | 5 +- src/WireMock.Net.Shared/Util/TypeLoader.cs | 118 +++++++++++++----- .../Util/TypeLoaderTests.cs | 45 +++---- 10 files changed, 191 insertions(+), 82 deletions(-) diff --git a/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs b/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs index f97d9232f..9c39b79b8 100644 --- a/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs +++ b/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs @@ -1,7 +1,6 @@ // Copyright © WireMock.Net using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -33,16 +32,25 @@ public bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true) StartsWithMultiPart(contentTypeHeader) ) { - var bytes = requestMessage.BodyData?.DetectedBodyType switch + byte[] bytes; + + switch (requestMessage.BodyData?.DetectedBodyType) { // If the body is bytes, use the BodyAsBytes to match on. - BodyType.Bytes => requestMessage.BodyData.BodyAsBytes!, + case BodyType.Bytes: + bytes = requestMessage.BodyData.BodyAsBytes!; + break; // If the body is a String or MultiPart, use the BodyAsString to match on. - BodyType.String or BodyType.MultiPart => Encoding.UTF8.GetBytes(requestMessage.BodyData.BodyAsString!), - - _ => throw new NotSupportedException() - }; + case BodyType.String or BodyType.MultiPart: + bytes = Encoding.UTF8.GetBytes(requestMessage.BodyData.BodyAsString!); + break; + + // Else return false. + default: + mimeMessageData = null; + return false; + } var fixedBytes = FixBytes(bytes, contentTypeHeader[0]); diff --git a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs index 84a23ce74..31e2ff93b 100644 --- a/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs @@ -12,7 +12,7 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageMultiPartMatcher : IRequestMatcher { - private static readonly IMimeKitUtils MimeKitUtils = TypeLoader.LoadStaticInstance(); + private readonly IMimeKitUtils _mimeKitUtils = LoadMimeKitUtils(); /// /// The matchers. @@ -62,7 +62,7 @@ public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResu return requestMatchResult.AddScore(GetType(), score, null); } - if (!MimeKitUtils.TryGetMimeMessage(requestMessage, out var message)) + if (!_mimeKitUtils.TryGetMimeMessage(requestMessage, out var message)) { return requestMatchResult.AddScore(GetType(), score, null); } @@ -96,4 +96,14 @@ public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResu return requestMatchResult.AddScore(GetType(), score, exception); } + + private static IMimeKitUtils LoadMimeKitUtils() + { + if (TypeLoader.TryLoadStaticInstance(out var mimeKitUtils)) + { + return mimeKitUtils; + } + + throw new InvalidOperationException("MimeKit is required for RequestMessageMultiPartMatcher. Please install the WireMock.Net.MimePart package."); + } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs b/src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs index 1bb412d4b..ef291bffb 100644 --- a/src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs @@ -178,9 +178,12 @@ private bool IsFault(IResponseMessage responseMessage) return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody); case BodyType.ProtoBuf: - var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts; - var protoBufUtils = TypeLoader.LoadStaticInstance(); - return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false); + if (TypeLoader.TryLoadStaticInstance(out var protoBufUtils)) + { + var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts; + return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false); + } + break; case BodyType.Bytes: return bodyData.BodyAsBytes; diff --git a/src/WireMock.Net.Minimal/RequestMessage.cs b/src/WireMock.Net.Minimal/RequestMessage.cs index e7fd8a706..eccaf654f 100644 --- a/src/WireMock.Net.Minimal/RequestMessage.cs +++ b/src/WireMock.Net.Minimal/RequestMessage.cs @@ -183,16 +183,9 @@ internal RequestMessage( #endif #if MIMEKIT - try + if (TypeLoader.TryLoadStaticInstance(out var mimeKitUtils) && mimeKitUtils.TryGetMimeMessage(this, out var mimeMessage)) { - if (TypeLoader.LoadStaticInstance().TryGetMimeMessage(this, out var mimeMessage)) - { - BodyAsMimeMessage = mimeMessage; - } - } - catch - { - // Ignore exception from MimeMessage.Load + BodyAsMimeMessage = mimeMessage; } #endif } diff --git a/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs index 429ac6795..39ad8fa4f 100644 --- a/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs @@ -55,7 +55,12 @@ public MatcherMapper(WireMockServerSettings settings) case "CSharpCodeMatcher": if (_settings.AllowCSharpCodeMatcher == true) { - return TypeLoader.LoadNewInstance(matchBehaviour, matchOperator, stringPatterns); + if (TypeLoader.TryLoadNewInstance(out var csharpCodeMatcher, matchBehaviour, matchOperator, stringPatterns)) + { + return csharpCodeMatcher; + } + + throw new InvalidOperationException("The 'CSharpCodeMatcher' cannot be loaded. Please install the WireMock.Net.Matchers.CSharpCode package."); } throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because WireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'."); @@ -75,7 +80,12 @@ public MatcherMapper(WireMockServerSettings settings) case "GraphQLMatcher": var patternAsString = stringPatterns[0].GetPattern(); var schema = new AnyOf(patternAsString); - return TypeLoader.LoadNewInstance(schema, matcherModel.CustomScalars, matchBehaviour, matchOperator); + if (TypeLoader.TryLoadNewInstance(out var graphQLMatcher, schema, matcherModel.CustomScalars, matchBehaviour, matchOperator)) + { + return graphQLMatcher; + } + + throw new InvalidOperationException("The 'GraphQLMatcher' cannot be loaded. Please install the WireMock.Net.GraphQL package."); case "MimePartMatcher": return CreateMimePartMatcher(matchBehaviour, matcherModel); @@ -282,18 +292,34 @@ private IMimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, Ma var contentTransferEncodingMatcher = Map(matcher.ContentTransferEncodingMatcher) as IStringMatcher; var contentMatcher = Map(matcher.ContentMatcher); - return TypeLoader.LoadNewInstance(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher); + if (TypeLoader.TryLoadNewInstance( + out var mimePartMatcher, + matchBehaviour, + contentTypeMatcher, + contentDispositionMatcher, + contentTransferEncodingMatcher, + contentMatcher)) + { + return mimePartMatcher; + } + + throw new InvalidOperationException("The 'MimePartMatcher' cannot be loaded. Please install the WireMock.Net.MimePart package."); } private IProtoBufMatcher CreateProtoBufMatcher(MatchBehaviour? matchBehaviour, IReadOnlyList protoDefinitions, MatcherModel matcher) { var objectMatcher = Map(matcher.ContentMatcher) as IObjectMatcher; - return TypeLoader.LoadNewInstance( + if (TypeLoader.TryLoadNewInstance( + out var protobufMatcher, () => ProtoDefinitionUtils.GetIdOrTexts(_settings, protoDefinitions.ToArray()), matcher.ProtoBufMessageType!, matchBehaviour ?? MatchBehaviour.AcceptOnMatch, - objectMatcher - ); + objectMatcher)) + { + return protobufMatcher; + } + + throw new InvalidOperationException("The 'ProtoBufMatcher' cannot be loaded. Please install the WireMock.Net.ProtoBuf package."); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs index d345b0577..d0f7622be 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs @@ -366,10 +366,8 @@ private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel) } else if (responseModel.BodyAsJson != null) { - if (responseModel.ProtoBufMessageType != null) + if (responseModel.ProtoBufMessageType != null && TypeLoader.TryLoadStaticInstance(out var protoBufUtils)) { - var protoBufUtils = TypeLoader.LoadStaticInstance(); - if (responseModel.ProtoDefinition != null) { responseBuilder = protoBufUtils.UpdateResponseBuilder(responseBuilder, responseModel.ProtoBufMessageType, responseModel.BodyAsJson, responseModel.ProtoDefinition); diff --git a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs index 613d58f82..313be8e5e 100644 --- a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs @@ -100,7 +100,10 @@ private static IMatcher[] CreateMatcherArray( IDictionary? customScalars ) { - var graphQLMatcher = TypeLoader.LoadNewInstance(schema, customScalars, matchBehaviour, MatchOperator.Or); - return [graphQLMatcher]; + if (TypeLoader.TryLoadNewInstance(out var graphQLMatcher, schema, customScalars, matchBehaviour, MatchOperator.Or)) + { + return [graphQLMatcher]; + } + return []; } } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageProtoBufMatcher.cs b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageProtoBufMatcher.cs index 372bd906b..42b10daa1 100644 --- a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageProtoBufMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageProtoBufMatcher.cs @@ -25,7 +25,10 @@ public class RequestMessageProtoBufMatcher : IRequestMatcher /// The optional matcher to use to match the ProtoBuf as (json) object. public RequestMessageProtoBufMatcher(MatchBehaviour matchBehaviour, Func protoDefinition, string messageType, IObjectMatcher? matcher = null) { - Matcher = TypeLoader.LoadNewInstance(protoDefinition, messageType, matchBehaviour, matcher); + if (TypeLoader.TryLoadNewInstance(out var protoBufMatcher, protoDefinition, messageType, matchBehaviour, matcher)) + { + Matcher = protoBufMatcher; + } } /// diff --git a/src/WireMock.Net.Shared/Util/TypeLoader.cs b/src/WireMock.Net.Shared/Util/TypeLoader.cs index 2f631297e..dabd8e9b9 100644 --- a/src/WireMock.Net.Shared/Util/TypeLoader.cs +++ b/src/WireMock.Net.Shared/Util/TypeLoader.cs @@ -14,68 +14,130 @@ internal static class TypeLoader { private static readonly ConcurrentDictionary Assemblies = new(); private static readonly ConcurrentDictionary Instances = new(); + private static readonly ConcurrentBag<(string FullName, Type Type)> InstancesWhichCannotBeFoundByFullName = []; + private static readonly ConcurrentBag<(string FullName, Type Type)> StaticInstancesWhichCannotBeFoundByFullName = []; + private static readonly ConcurrentBag InstancesWhichCannotBeFound = []; + private static readonly ConcurrentBag StaticInstancesWhichCannotBeFound = []; - public static TInterface LoadNewInstance(params object?[] args) where TInterface : class + public static bool TryLoadNewInstance([NotNullWhen(true)] out TInterface? instance, params object?[] args) where TInterface : class { - var pluginType = GetPluginType(); + var type = typeof(TInterface); + if (InstancesWhichCannotBeFound.Contains(type)) + { + instance = null; + return false; + } + + if (TryGetPluginType(out var pluginType)) + { + instance = (TInterface)Activator.CreateInstance(pluginType, args)!; + return true; + } - return (TInterface)Activator.CreateInstance(pluginType, args)!; + InstancesWhichCannotBeFound.Add(type); + instance = null; + return false; } - public static TInterface LoadStaticInstance(params object?[] args) where TInterface : class + public static bool TryLoadStaticInstance([NotNullWhen(true)] out TInterface? staticInstance, params object?[] args) where TInterface : class { - var pluginType = GetPluginType(); + var type = typeof(TInterface); + if (StaticInstancesWhichCannotBeFound.Contains(type)) + { + staticInstance = null; + return false; + } - return (TInterface)Instances.GetOrAdd(pluginType, key => Activator.CreateInstance(key, args)!); + if (TryGetPluginType(out var pluginType)) + { + staticInstance = (TInterface)Instances.GetOrAdd(pluginType, key => Activator.CreateInstance(key, args)!); + return true; + } + + StaticInstancesWhichCannotBeFound.Add(type); + staticInstance = null; + return false; } - public static TInterface LoadNewInstanceByFullName(string implementationTypeFullName, params object?[] args) where TInterface : class + public static bool TryLoadNewInstanceByFullName([NotNullWhen(true)] out TInterface? instance, string implementationTypeFullName, params object?[] args) where TInterface : class { Guard.NotNullOrEmpty(implementationTypeFullName); - var pluginType = GetPluginTypeByFullName(implementationTypeFullName); + var type = typeof(TInterface); + if (InstancesWhichCannotBeFoundByFullName.Contains((implementationTypeFullName, type))) + { + instance = null; + return false; + } + + if (TryGetPluginTypeByFullName(implementationTypeFullName, out var pluginType)) + { + instance = (TInterface)Activator.CreateInstance(pluginType, args)!; + return true; + } - return (TInterface)Activator.CreateInstance(pluginType, args)!; + InstancesWhichCannotBeFoundByFullName.Add((implementationTypeFullName, type)); + instance = null; + return false; } - public static TInterface LoadStaticInstanceByFullName(string implementationTypeFullName, params object?[] args) where TInterface : class + public static bool TryLoadStaticInstanceByFullName([NotNullWhen(true)] out TInterface? staticInstance, string implementationTypeFullName, params object?[] args) where TInterface : class { Guard.NotNullOrEmpty(implementationTypeFullName); - var pluginType = GetPluginTypeByFullName(implementationTypeFullName); + var type = typeof(TInterface); + if (StaticInstancesWhichCannotBeFoundByFullName.Contains((implementationTypeFullName, type))) + { + staticInstance = null; + return false; + } + + if (TryGetPluginTypeByFullName(implementationTypeFullName, out var pluginType)) + { + staticInstance = (TInterface)Instances.GetOrAdd(pluginType, key => Activator.CreateInstance(key, args)!); + return true; + } - return (TInterface)Instances.GetOrAdd(pluginType, key => Activator.CreateInstance(key, args)!); + StaticInstancesWhichCannotBeFoundByFullName.Add((implementationTypeFullName, type)); + staticInstance = null; + return false; } - private static Type GetPluginType() where TInterface : class + private static bool TryGetPluginType([NotNullWhen(true)] out Type? foundType) where TInterface : class { var key = typeof(TInterface).FullName!; - return Assemblies.GetOrAdd(key, _ => + if (Assemblies.TryGetValue(key, out foundType)) { - if (TryFindTypeInDlls(null, out var foundType)) - { - return foundType; - } + return true; + } - throw new DllNotFoundException($"No dll found which implements interface '{key}'."); - }); + if (TryFindTypeInDlls(null, out foundType)) + { + Assemblies.TryAdd(key, foundType); + return true; + } + + return false; } - private static Type GetPluginTypeByFullName(string implementationTypeFullName) where TInterface : class + private static bool TryGetPluginTypeByFullName(string implementationTypeFullName, [NotNullWhen(true)] out Type? foundType) where TInterface : class { var @interface = typeof(TInterface).FullName; var key = $"{@interface}_{implementationTypeFullName}"; - return Assemblies.GetOrAdd(key, _ => + if (Assemblies.TryGetValue(key, out foundType)) { - if (TryFindTypeInDlls(implementationTypeFullName, out var foundType)) - { - return foundType; - } + return true; + } - throw new DllNotFoundException($"No dll found which implements Interface '{@interface}' and has FullName '{implementationTypeFullName}'."); - }); + if (TryFindTypeInDlls(implementationTypeFullName, out foundType)) + { + Assemblies.TryAdd(key, foundType); + return true; + } + + return false; } private static bool TryFindTypeInDlls(string? implementationTypeFullName, [NotNullWhen(true)] out Type? pluginType) where TInterface : class diff --git a/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs b/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs index 6b591f6e4..e37a6bdb8 100644 --- a/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs +++ b/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using System.IO; using AnyOfTypes; using FluentAssertions; @@ -56,7 +55,7 @@ public void AddOne() } [Fact] - public void LoadNewInstance() + public void TryLoadNewInstance() { var current = Directory.GetCurrentDirectory(); try @@ -65,10 +64,11 @@ public void LoadNewInstance() // Act AnyOf pattern = "x"; - var result = TypeLoader.LoadNewInstance(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern); + var result = TypeLoader.TryLoadNewInstance(out var instance, MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern); // Assert - result.Should().NotBeNull(); + result.Should().BeTrue(); + instance.Should().BeOfType(); } finally { @@ -77,63 +77,66 @@ public void LoadNewInstance() } [Fact] - public void LoadNewInstanceByFullName() + public void TryLoadNewInstanceByFullName() { // Act - var result = TypeLoader.LoadNewInstanceByFullName(typeof(DummyClass).FullName!); + var result = TypeLoader.TryLoadNewInstanceByFullName(out var instance, typeof(DummyClass).FullName!); // Assert - result.Should().BeOfType(); + result.Should().BeTrue(); + instance.Should().BeOfType(); } [Fact] - public void LoadStaticInstance_ShouldOnlyCreateInstanceOnce() + public void TryLoadStaticInstance_ShouldOnlyCreateInstanceOnce() { // Arrange var counter = new Counter(); // Act - var result = TypeLoader.LoadStaticInstance(counter); - TypeLoader.LoadStaticInstance(counter); + var result = TypeLoader.TryLoadStaticInstance(out var staticInstance, counter); + TypeLoader.TryLoadStaticInstance(out staticInstance, counter); // Assert - result.Should().BeOfType(); + result.Should().BeTrue(); + staticInstance.Should().BeOfType(); counter.Value.Should().Be(1); } [Fact] - public void LoadStaticInstanceByFullName_ShouldOnlyCreateInstanceOnce() + public void TryLoadStaticInstanceByFullName_ShouldOnlyCreateInstanceOnce() { // Arrange var counter = new Counter(); var fullName = typeof(DummyClass2UsedForStaticTest).FullName!; // Act - var result = TypeLoader.LoadStaticInstanceByFullName(fullName, counter); - TypeLoader.LoadStaticInstanceByFullName(fullName, counter); + var result = TypeLoader.TryLoadStaticInstanceByFullName(out var staticInstance, fullName, counter); + TypeLoader.TryLoadStaticInstanceByFullName(out staticInstance, fullName, counter); // Assert - result.Should().BeOfType(); + result.Should().BeTrue(); + staticInstance.Should().BeOfType(); counter.Value.Should().Be(1); } [Fact] - public void LoadNewInstance_ButNoImplementationFoundForInterface_ThrowsException() + public void TryLoadNewInstance_ButNoImplementationFoundForInterface_ReturnsFalse() { // Act - Action a = () => TypeLoader.LoadNewInstance(); + var result = TypeLoader.TryLoadNewInstance(out _); // Assert - a.Should().Throw().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Util.TypeLoaderTests+IDummyInterfaceNoImplementation'."); + result.Should().BeFalse(); } [Fact] - public void LoadNewInstanceByFullName_ButNoImplementationFoundForInterface_ThrowsException() + public void TryLoadNewInstanceByFullName_ButNoImplementationFoundForInterface_ReturnsFalse() { // Act - Action a = () => TypeLoader.LoadNewInstanceByFullName("xyz"); + var result = TypeLoader.TryLoadNewInstanceByFullName(out _, "xyz"); // Assert - a.Should().Throw().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Util.TypeLoaderTests+IDummyInterfaceWithImplementation' and has FullName 'xyz'."); + result.Should().BeFalse(); } } \ No newline at end of file From 19e95325fa4efeb60a899b78be8ff7fe3fdedc63 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 28 Sep 2025 12:40:33 +0200 Subject: [PATCH 2/6] ProxyUrlTransformer (#1361) * ProxyUrlTransformer * tests * Update src/WireMock.Net.Shared/Settings/ProxyUrlReplaceSettings.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Http/WebhookSender.cs | 30 +----- src/WireMock.Net.Minimal/Proxy/ProxyHelper.cs | 25 +---- .../Proxy/ProxyUrlTransformer.cs | 21 ++++ .../ResponseBuilders/Response.cs | 21 +--- .../Transformers/ITransformer.cs | 4 +- .../ITransformerContextFactory.cs | 9 +- .../Transformers/Transformer.cs | 5 + .../Transformers/TransformerFactory.cs | 30 ++++++ .../Settings/ProxyUrlReplaceSettings.cs | 29 +++++- .../Proxy/ProxyUrlTransformerTests.cs | 98 +++++++++++++++++++ 10 files changed, 195 insertions(+), 77 deletions(-) create mode 100644 src/WireMock.Net.Minimal/Proxy/ProxyUrlTransformer.cs create mode 100644 src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs create mode 100644 test/WireMock.Net.Tests/Proxy/ProxyUrlTransformerTests.cs diff --git a/src/WireMock.Net.Minimal/Http/WebhookSender.cs b/src/WireMock.Net.Minimal/Http/WebhookSender.cs index 431d773fc..d6f367f88 100644 --- a/src/WireMock.Net.Minimal/Http/WebhookSender.cs +++ b/src/WireMock.Net.Minimal/Http/WebhookSender.cs @@ -11,24 +11,17 @@ using WireMock.Models; using WireMock.Settings; using WireMock.Transformers; -using WireMock.Transformers.Handlebars; -using WireMock.Transformers.Scriban; using WireMock.Types; using WireMock.Util; namespace WireMock.Http; -internal class WebhookSender +internal class WebhookSender(WireMockServerSettings settings) { private const string ClientIp = "::1"; private static readonly ThreadLocal Random = new(() => new Random(DateTime.UtcNow.Millisecond)); - private readonly WireMockServerSettings _settings; - - public WebhookSender(WireMockServerSettings settings) - { - _settings = Guard.NotNull(settings); - } + private readonly WireMockServerSettings _settings = Guard.NotNull(settings); public async Task SendAsync( HttpClient client, @@ -49,24 +42,7 @@ IResponseMessage originalResponseMessage string requestUrl; if (webhookRequest.UseTransformer == true) { - ITransformer transformer; - switch (webhookRequest.TransformerType) - { - case TransformerType.Handlebars: - var factoryHandlebars = new HandlebarsContextFactory(_settings); - transformer = new Transformer(_settings, factoryHandlebars); - break; - - case TransformerType.Scriban: - case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType); - transformer = new Transformer(_settings, factoryDotLiquid); - break; - - default: - throw new NotImplementedException($"TransformerType '{webhookRequest.TransformerType}' is not supported."); - } - + var transformer = TransformerFactory.Create(webhookRequest.TransformerType, _settings); bodyData = transformer.TransformBody(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.BodyData, webhookRequest.TransformerReplaceNodeOptions); headers = transformer.TransformHeaders(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Headers); requestUrl = transformer.TransformString(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Url); diff --git a/src/WireMock.Net.Minimal/Proxy/ProxyHelper.cs b/src/WireMock.Net.Minimal/Proxy/ProxyHelper.cs index 51dcadc5c..23f165f57 100644 --- a/src/WireMock.Net.Minimal/Proxy/ProxyHelper.cs +++ b/src/WireMock.Net.Minimal/Proxy/ProxyHelper.cs @@ -13,16 +13,10 @@ namespace WireMock.Proxy; -internal class ProxyHelper +internal class ProxyHelper(WireMockServerSettings settings) { - private readonly WireMockServerSettings _settings; - private readonly ProxyMappingConverter _proxyMappingConverter; - - public ProxyHelper(WireMockServerSettings settings) - { - _settings = Guard.NotNull(settings); - _proxyMappingConverter = new ProxyMappingConverter(settings, new GuidUtils(), new DateTimeUtils()); - } + private readonly WireMockServerSettings _settings = Guard.NotNull(settings); + private readonly ProxyMappingConverter _proxyMappingConverter = new(settings, new GuidUtils(), new DateTimeUtils()); public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync( IMapping? mapping, @@ -39,18 +33,7 @@ public ProxyHelper(WireMockServerSettings settings) var requiredUri = new Uri(url); // Create HttpRequestMessage - var replaceSettings = proxyAndRecordSettings.ReplaceSettings; - string proxyUrl; - if (replaceSettings is not null) - { - var stringComparison = replaceSettings.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - proxyUrl = url.Replace(replaceSettings.OldValue, replaceSettings.NewValue, stringComparison); - } - else - { - proxyUrl = url; - } - + var proxyUrl = proxyAndRecordSettings.ReplaceSettings != null ? ProxyUrlTransformer.Transform(_settings, proxyAndRecordSettings.ReplaceSettings, url) : url; var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, proxyUrl); // Call the URL diff --git a/src/WireMock.Net.Minimal/Proxy/ProxyUrlTransformer.cs b/src/WireMock.Net.Minimal/Proxy/ProxyUrlTransformer.cs new file mode 100644 index 000000000..ba6952de4 --- /dev/null +++ b/src/WireMock.Net.Minimal/Proxy/ProxyUrlTransformer.cs @@ -0,0 +1,21 @@ +// Copyright © WireMock.Net + +using System; +using WireMock.Settings; +using WireMock.Transformers; + +namespace WireMock.Proxy; + +internal static class ProxyUrlTransformer +{ + internal static string Transform(WireMockServerSettings settings, ProxyUrlReplaceSettings replaceSettings, string url) + { + if (!replaceSettings.UseTransformer) + { + return url.Replace(replaceSettings.OldValue, replaceSettings.NewValue, replaceSettings.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); + } + + var transformer = TransformerFactory.Create(replaceSettings.TransformerType, settings); + return transformer.Transform(replaceSettings.TransformTemplate, url); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs index bcf984917..13ba9afad 100644 --- a/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs +++ b/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs @@ -272,25 +272,8 @@ string RemoveFirstOccurrence(string source, string find) } } - ITransformer responseMessageTransformer; - switch (TransformerType) - { - case TransformerType.Handlebars: - var factoryHandlebars = new HandlebarsContextFactory(settings); - responseMessageTransformer = new Transformer(settings, factoryHandlebars); - break; - - case TransformerType.Scriban: - case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType); - responseMessageTransformer = new Transformer(settings, factoryDotLiquid); - break; - - default: - throw new NotSupportedException($"TransformerType '{TransformerType}' is not supported."); - } - - return (responseMessageTransformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null); + var transformer = TransformerFactory.Create(TransformerType, settings); + return (transformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null); } if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true && responseMessage.BodyData?.BodyAsFile is not null) diff --git a/src/WireMock.Net.Minimal/Transformers/ITransformer.cs b/src/WireMock.Net.Minimal/Transformers/ITransformer.cs index 65c0d2483..1b16483af 100644 --- a/src/WireMock.Net.Minimal/Transformers/ITransformer.cs +++ b/src/WireMock.Net.Minimal/Transformers/ITransformer.cs @@ -6,7 +6,7 @@ namespace WireMock.Transformers; -interface ITransformer +internal interface ITransformer { ResponseMessage Transform(IMapping mapping, IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options); @@ -15,4 +15,6 @@ interface ITransformer IDictionary> TransformHeaders(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IDictionary>? headers); string TransformString(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, string? value); + + string Transform(string template, object? model); } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Transformers/ITransformerContextFactory.cs b/src/WireMock.Net.Minimal/Transformers/ITransformerContextFactory.cs index 68c1772c9..8488f6bc6 100644 --- a/src/WireMock.Net.Minimal/Transformers/ITransformerContextFactory.cs +++ b/src/WireMock.Net.Minimal/Transformers/ITransformerContextFactory.cs @@ -1,9 +1,8 @@ // Copyright © WireMock.Net -namespace WireMock.Transformers +namespace WireMock.Transformers; + +internal interface ITransformerContextFactory { - interface ITransformerContextFactory - { - ITransformerContext Create(); - } + ITransformerContext Create(); } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Transformers/Transformer.cs b/src/WireMock.Net.Minimal/Transformers/Transformer.cs index 000eeece3..d70a089a3 100644 --- a/src/WireMock.Net.Minimal/Transformers/Transformer.cs +++ b/src/WireMock.Net.Minimal/Transformers/Transformer.cs @@ -75,6 +75,11 @@ public string TransformString( return transformerContext.ParseAndRender(value, model); } + public string Transform(string template, object? model) + { + return model is null ? string.Empty : _factory.Create().ParseAndRender(template, model); + } + public ResponseMessage Transform(IMapping mapping, IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options) { var responseMessage = new ResponseMessage(); diff --git a/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs b/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs new file mode 100644 index 000000000..9cb3ca0ad --- /dev/null +++ b/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs @@ -0,0 +1,30 @@ +// Copyright © WireMock.Net + +using System; +using WireMock.Settings; +using WireMock.Transformers.Handlebars; +using WireMock.Transformers.Scriban; +using WireMock.Types; + +namespace WireMock.Transformers; + +internal static class TransformerFactory +{ + internal static ITransformer Create(TransformerType transformerType, WireMockServerSettings settings) + { + switch (transformerType) + { + case TransformerType.Handlebars: + var factoryHandlebars = new HandlebarsContextFactory(settings); + return new Transformer(settings, factoryHandlebars); + + case TransformerType.Scriban: + case TransformerType.ScribanDotLiquid: + var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, transformerType); + return new Transformer(settings, factoryDotLiquid); + + default: + throw new NotSupportedException($"{nameof(TransformerType)} '{transformerType}' is not supported."); + } + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Settings/ProxyUrlReplaceSettings.cs b/src/WireMock.Net.Shared/Settings/ProxyUrlReplaceSettings.cs index bfffb64c6..716575466 100644 --- a/src/WireMock.Net.Shared/Settings/ProxyUrlReplaceSettings.cs +++ b/src/WireMock.Net.Shared/Settings/ProxyUrlReplaceSettings.cs @@ -1,5 +1,8 @@ // Copyright © WireMock.Net +using System.Diagnostics.CodeAnalysis; +using WireMock.Types; + namespace WireMock.Settings; /// @@ -8,17 +11,35 @@ namespace WireMock.Settings; public class ProxyUrlReplaceSettings { /// - /// The old path value to be replaced by the new path value + /// The old path value to be replaced by the new path value. /// - public string OldValue { get; set; } = null!; + public string? OldValue { get; set; } /// - /// The new path value to replace the old value with + /// The new path value to replace the old value with. /// - public string NewValue { get; set; } = null!; + public string? NewValue { get; set; } /// /// Defines if the case should be ignored when replacing. /// public bool IgnoreCase { get; set; } + + /// + /// Holds the transformation template used when is true. + /// + public string? TransformTemplate { get; set; } + + /// + /// Use Transformer. + /// + [MemberNotNullWhen(true, nameof(TransformTemplate))] + [MemberNotNullWhen(false, nameof(OldValue))] + [MemberNotNullWhen(false, nameof(NewValue))] + public bool UseTransformer => !string.IsNullOrEmpty(TransformTemplate); + + /// + /// The transformer type, in case is set to true. + /// + public TransformerType TransformerType { get; set; } = TransformerType.Handlebars; } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Proxy/ProxyUrlTransformerTests.cs b/test/WireMock.Net.Tests/Proxy/ProxyUrlTransformerTests.cs new file mode 100644 index 000000000..2921ee097 --- /dev/null +++ b/test/WireMock.Net.Tests/Proxy/ProxyUrlTransformerTests.cs @@ -0,0 +1,98 @@ +using System.Globalization; +using Moq; +using WireMock.Handlers; +using WireMock.Proxy; +using WireMock.Settings; +using WireMock.Types; +using Xunit; + +namespace WireMock.Net.Tests.Proxy; + +public class ProxyUrlTransformerTests +{ + private readonly Mock _fileSystemHandlerMock = new(); + + [Fact] + public void Transform_WithUseTransformerFalse_PerformsSimpleReplace_CaseSensitive() + { + // Arrange + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object, + Culture = CultureInfo.InvariantCulture + }; + + var replaceSettings = new ProxyUrlReplaceSettings + { + TransformTemplate = null, + OldValue = "/old", + NewValue = "/new", + IgnoreCase = false + }; + + var url = "http://example.com/old/path"; + var expected = "http://example.com/new/path"; + + // Act + var actual = ProxyUrlTransformer.Transform(settings, replaceSettings, url); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Transform_WithUseTransformerFalse_PerformsSimpleReplace_IgnoreCase() + { + // Arrange + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object, + Culture = CultureInfo.InvariantCulture + }; + + var replaceSettings = new ProxyUrlReplaceSettings + { + TransformTemplate = null, // UseTransformer == false + OldValue = "/OLD", + NewValue = "/new", + IgnoreCase = true + }; + + var url = "http://example.com/old/path"; // lowercase 'old' but OldValue is uppercase + var expected = "http://example.com/new/path"; + + // Act + var actual = ProxyUrlTransformer.Transform(settings, replaceSettings, url); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Transform_WithUseTransformerTrue_UsesTransformer_ToTransformUrl() + { + // Arrange + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object, + Culture = CultureInfo.InvariantCulture + }; + + // Handlebars is the default TransformerType; the TransformTemplate uses the model directly. + var replaceSettings = new ProxyUrlReplaceSettings + { + TransformTemplate = "{{this}}-transformed", + // TransformerType defaults to Handlebars but set explicitly for clarity. + TransformerType = TransformerType.Handlebars + }; + + var url = "http://example.com/path"; + var expected = "http://example.com/path-transformed"; + + // Act + var actual = ProxyUrlTransformer.Transform(settings, replaceSettings, url); + + // Assert + Assert.Equal(expected, actual); + } +} \ No newline at end of file From 45d4e7077d8bf9513c5bb701385ecc30504190a7 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 28 Sep 2025 12:44:14 +0200 Subject: [PATCH 3/6] 1.13.0 --- CHANGELOG.md | 5 +++++ Directory.Build.props | 2 +- Generate-ReleaseNotes.cmd | 2 +- PackageReleaseNotes.txt | 7 ++++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a15d244..b77b680e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.13.0 (28 September 2025) +- [#1358](https://github.com/wiremock/WireMock.Net/pull/1358) - TypeLoader: implement Try methods [feature] contributed by [StefH](https://github.com/StefH) +- [#1361](https://github.com/wiremock/WireMock.Net/pull/1361) - ProxyUrlTransformer [feature] contributed by [StefH](https://github.com/StefH) +- [#1360](https://github.com/wiremock/WireMock.Net/issues/1360) - ProxyURL path configuration [feature] + # 1.12.0 (30 August 2025) - [#1357](https://github.com/wiremock/WireMock.Net/pull/1357) - Upgrade Testcontainers to 4.7.0 [feature] contributed by [StefH](https://github.com/StefH) - [#1356](https://github.com/wiremock/WireMock.Net/issues/1356) - WireMock.Net 1.11.2 is not compatible with TestContainers 4.7.0 [bug] diff --git a/Directory.Build.props b/Directory.Build.props index e1c54b80c..2823ad1e9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.12.0 + 1.13.0 WireMock.Net-Logo.png https://github.com/wiremock/WireMock.Net Apache-2.0 diff --git a/Generate-ReleaseNotes.cmd b/Generate-ReleaseNotes.cmd index fc81b6b27..bac7b3302 100644 --- a/Generate-ReleaseNotes.cmd +++ b/Generate-ReleaseNotes.cmd @@ -1,6 +1,6 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=1.12.0 +SET version=1.13.0 GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN% diff --git a/PackageReleaseNotes.txt b/PackageReleaseNotes.txt index abe2b2499..8561a7ae8 100644 --- a/PackageReleaseNotes.txt +++ b/PackageReleaseNotes.txt @@ -1,5 +1,6 @@ -# 1.12.0 (30 August 2025) -- #1357 Upgrade Testcontainers to 4.7.0 [feature] -- #1356 WireMock.Net 1.11.2 is not compatible with TestContainers 4.7.0 [bug] +# 1.13.0 (28 September 2025) +- #1358 TypeLoader: implement Try methods [feature] +- #1361 ProxyUrlTransformer [feature] +- #1360 ProxyURL path configuration [feature] The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md \ No newline at end of file From b82dad2563fe3af9220d8fca525a9b7e09cfef27 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 5 Oct 2025 15:51:47 +0200 Subject: [PATCH 4/6] Add Tls13 (#1363) * Add Tls13 * fix --- src/WireMock.Net.Minimal/Http/HttpClientBuilder.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/WireMock.Net.Minimal/Http/HttpClientBuilder.cs b/src/WireMock.Net.Minimal/Http/HttpClientBuilder.cs index 275b7b576..5c465823c 100644 --- a/src/WireMock.Net.Minimal/Http/HttpClientBuilder.cs +++ b/src/WireMock.Net.Minimal/Http/HttpClientBuilder.cs @@ -15,8 +15,13 @@ public static HttpClient Build(HttpClientSettings settings) var handler = new HttpClientHandler { CheckCertificateRevocationList = false, +#if NET5_0_OR_GREATER + SslProtocols = System.Security.Authentication.SslProtocols.Tls13 | System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls, +#else SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls, +#endif ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; #elif NET46 @@ -62,7 +67,10 @@ public static HttpClient Build(HttpClientSettings settings) } } -#if !NETSTANDARD1_3 +#if NET5_0_OR_GREATER + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; + ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true; +#elif !NETSTANDARD1_3 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true; #endif From b9019a2f61760c2cf1f107ef2a4f43835e39042e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 6 Oct 2025 09:16:25 +0200 Subject: [PATCH 5/6] Update ProxyUrlReplaceSettingsModel with TransformTemplate property (#1362) * Update ProxyUrlReplaceSettingsModel with TransformTemplate property + parse settings correctly * oldValue nullable * 1.14.0-preview-01 --- Directory.Build.props | 2 +- .../Settings/ProxyUrlReplaceSettingsModel.cs | 18 +++++-- .../Settings/WireMockServerSettingsParser.cs | 21 ++++++-- ...ettings_And_TransformTemplate.verified.txt | 29 +++++++++++ .../AdminApi/WireMockAdminApiTests.cs | 49 +++++++++++++++++++ 5 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.IWireMockAdminApi_GetMappingAsync_WithProxy_And_ProxyUrlReplaceSettings_And_TransformTemplate.verified.txt diff --git a/Directory.Build.props b/Directory.Build.props index 2823ad1e9..bb5dd4bee 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.13.0 + 1.14.0-preview-01 WireMock.Net-Logo.png https://github.com/wiremock/WireMock.Net Apache-2.0 diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyUrlReplaceSettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyUrlReplaceSettingsModel.cs index cc9ce5a64..a3f54d90c 100644 --- a/src/WireMock.Net.Abstractions/Admin/Settings/ProxyUrlReplaceSettingsModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Settings/ProxyUrlReplaceSettingsModel.cs @@ -1,5 +1,7 @@ // Copyright © WireMock.Net +using WireMock.Types; + namespace WireMock.Admin.Settings; /// @@ -11,15 +13,25 @@ public class ProxyUrlReplaceSettingsModel /// /// The old path value to be replaced by the new path value /// - public string OldValue { get; set; } = null!; + public string? OldValue { get; set; } /// /// The new path value to replace the old value with /// - public string NewValue { get; set; } = null!; + public string? NewValue { get; set; } /// - /// Defines if the case should be ignore when replacing. + /// Defines if the case should be ignored when replacing. /// public bool IgnoreCase { get; set; } + + /// + /// Holds the transformation template. + /// + public string? TransformTemplate { get; set; } + + /// + /// The transformer type. + /// + public TransformerType TransformerType { get; set; } = TransformerType.Handlebars; } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsParser.cs index d65029b19..f9d4dd755 100644 --- a/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsParser.cs @@ -202,14 +202,27 @@ private static void ParseWebProxyAddressSettings(ProxyAndRecordSettings settings private static void ParseProxyUrlReplaceSettings(ProxyAndRecordSettings settings, SimpleSettingsParser parser) { - var proxyUrlReplaceOldValue = parser.GetStringValue("ProxyUrlReplaceOldValue"); - var proxyUrlReplaceNewValue = parser.GetStringValue("ProxyUrlReplaceNewValue"); + const string prefix = "ProxyUrlReplace"; + var proxyUrlReplaceOldValue = parser.GetStringValue($"{prefix}OldValue"); + var proxyUrlReplaceNewValue = parser.GetStringValue($"{prefix}NewValue"); if (!string.IsNullOrEmpty(proxyUrlReplaceOldValue) && proxyUrlReplaceNewValue != null) { settings.ReplaceSettings = new ProxyUrlReplaceSettings { - OldValue = proxyUrlReplaceOldValue!, - NewValue = proxyUrlReplaceNewValue + OldValue = proxyUrlReplaceOldValue, + NewValue = proxyUrlReplaceNewValue, + IgnoreCase = parser.GetBoolValue($"{prefix}IgnoreCase") + }; + return; + } + + var transformTemplate = parser.GetStringValue($"{prefix}TransformTemplate"); + if (!string.IsNullOrEmpty(transformTemplate)) + { + settings.ReplaceSettings = new ProxyUrlReplaceSettings + { + TransformTemplate = transformTemplate, + TransformerType = parser.GetEnumValue($"{prefix}TransformerType", TransformerType.Handlebars) }; } } diff --git a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.IWireMockAdminApi_GetMappingAsync_WithProxy_And_ProxyUrlReplaceSettings_And_TransformTemplate.verified.txt b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.IWireMockAdminApi_GetMappingAsync_WithProxy_And_ProxyUrlReplaceSettings_And_TransformTemplate.verified.txt new file mode 100644 index 000000000..61180ff86 --- /dev/null +++ b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.IWireMockAdminApi_GetMappingAsync_WithProxy_And_ProxyUrlReplaceSettings_And_TransformTemplate.verified.txt @@ -0,0 +1,29 @@ +{ + Guid: 90356dba-b36c-469a-a17e-669cd84f1f06, + UpdatedAt: DateTime_1, + Request: { + Path: { + Matchers: [ + { + Name: WildcardMatcher, + Pattern: /1, + IgnoreCase: false + } + ] + }, + Body: { + Matcher: { + Name: RegexMatcher, + Pattern: hello, + IgnoreCase: true + } + } + }, + Response: { + ProxyUrl: https://my-proxy.com, + ProxyUrlReplaceSettings: { + IgnoreCase: false, + TransformTemplate: x{{this}}y + } + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs index 20aaea0b2..257cca894 100644 --- a/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs +++ b/test/WireMock.Net.Tests/AdminApi/WireMockAdminApiTests.cs @@ -450,6 +450,55 @@ public async Task IWireMockAdminApi_GetMappingAsync_WithProxy_And_ProxyUrlReplac server.Stop(); } + [Fact] + public async Task IWireMockAdminApi_GetMappingAsync_WithProxy_And_ProxyUrlReplaceSettings_And_TransformTemplate() + { + // Arrange + var guid = Guid.Parse("90356dba-b36c-469a-a17e-669cd84f1f06"); + var server = WireMockServer.StartWithAdminInterface(); + var api = RestClient.For(server.Url); + + // Act + var model = new MappingModel + { + Guid = guid, + Request = new RequestModel + { + Path = "/1", + Body = new BodyModel + { + Matcher = new MatcherModel + { + Name = "RegexMatcher", + Pattern = "hello", + IgnoreCase = true + } + } + }, + Response = new ResponseModel + { + ProxyUrl = "https://my-proxy.com", + ProxyUrlReplaceSettings = new ProxyUrlReplaceSettingsModel + { + TransformTemplate = "x{{this}}y" + } + } + }; + var postMappingResult = await api.PostMappingAsync(model).ConfigureAwait(false); + + // Assert + postMappingResult.Should().NotBeNull(); + + var mapping = server.Mappings.FirstOrDefault(m => m.Guid == guid); + mapping.Should().NotBeNull(); + + var getMappingResult = await api.GetMappingAsync(guid).ConfigureAwait(false); + + await Verifier.Verify(getMappingResult, VerifySettings).DontScrubGuids(); + + server.Stop(); + } + [Fact] public async Task IWireMockAdminApi_GetRequestsAsync_Json() { From f6c5225fe042f58e76df3d3efba343033b7a69b0 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 6 Oct 2025 17:00:19 +0200 Subject: [PATCH 6/6] 1.14.0 --- CHANGELOG.md | 7 ++++++- Directory.Build.props | 2 +- Generate-ReleaseNotes.cmd | 2 +- PackageReleaseNotes.txt | 7 ++++--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b77b680e7..0f984dd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ +# 1.14.0 (06 October 2025) +- [#1362](https://github.com/wiremock/WireMock.Net/pull/1362) - Update ProxyUrlReplaceSettingsModel with TransformTemplate property [feature] contributed by [StefH](https://github.com/StefH) +- [#1363](https://github.com/wiremock/WireMock.Net/pull/1363) - Add Tls13 [bug] contributed by [StefH](https://github.com/StefH) +- [#1342](https://github.com/wiremock/WireMock.Net/issues/1342) - Facing SSL certificate validation error when using .NET 5 and above framework for some http services during recording [bug] +- [#1360](https://github.com/wiremock/WireMock.Net/issues/1360) - ProxyURL path configuration [feature] + # 1.13.0 (28 September 2025) - [#1358](https://github.com/wiremock/WireMock.Net/pull/1358) - TypeLoader: implement Try methods [feature] contributed by [StefH](https://github.com/StefH) - [#1361](https://github.com/wiremock/WireMock.Net/pull/1361) - ProxyUrlTransformer [feature] contributed by [StefH](https://github.com/StefH) -- [#1360](https://github.com/wiremock/WireMock.Net/issues/1360) - ProxyURL path configuration [feature] # 1.12.0 (30 August 2025) - [#1357](https://github.com/wiremock/WireMock.Net/pull/1357) - Upgrade Testcontainers to 4.7.0 [feature] contributed by [StefH](https://github.com/StefH) diff --git a/Directory.Build.props b/Directory.Build.props index bb5dd4bee..ae6342724 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.14.0-preview-01 + 1.14.0 WireMock.Net-Logo.png https://github.com/wiremock/WireMock.Net Apache-2.0 diff --git a/Generate-ReleaseNotes.cmd b/Generate-ReleaseNotes.cmd index bac7b3302..cfd2293c3 100644 --- a/Generate-ReleaseNotes.cmd +++ b/Generate-ReleaseNotes.cmd @@ -1,6 +1,6 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=1.13.0 +SET version=1.14.0 GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN% diff --git a/PackageReleaseNotes.txt b/PackageReleaseNotes.txt index 8561a7ae8..f0fd522da 100644 --- a/PackageReleaseNotes.txt +++ b/PackageReleaseNotes.txt @@ -1,6 +1,7 @@ -# 1.13.0 (28 September 2025) -- #1358 TypeLoader: implement Try methods [feature] -- #1361 ProxyUrlTransformer [feature] +# 1.14.0 (06 October 2025) +- #1362 Update ProxyUrlReplaceSettingsModel with TransformTemplate property [feature] +- #1363 Add Tls13 [bug] +- #1342 Facing SSL certificate validation error when using .NET 5 and above framework for some http services during recording [bug] - #1360 ProxyURL path configuration [feature] The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md \ No newline at end of file