diff --git a/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs b/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs index f97d9232..9c39b79b 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 @@ internal class MimeKitUtils : IMimeKitUtils 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!), + case BodyType.String or BodyType.MultiPart: + bytes = Encoding.UTF8.GetBytes(requestMessage.BodyData.BodyAsString!); + break; - _ => throw new NotSupportedException() - }; + // 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 84a23ce7..31e2ff93 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 class RequestMessageMultiPartMatcher : IRequestMatcher 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 class RequestMessageMultiPartMatcher : IRequestMatcher 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 1bb412d4..ef291bff 100644 --- a/src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs @@ -178,9 +178,12 @@ namespace WireMock.Owin.Mappers 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 e7fd8a70..eccaf654 100644 --- a/src/WireMock.Net.Minimal/RequestMessage.cs +++ b/src/WireMock.Net.Minimal/RequestMessage.cs @@ -183,16 +183,9 @@ public class RequestMessage : IRequestMessage #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 429ac679..39ad8fa4 100644 --- a/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs @@ -55,7 +55,12 @@ internal class MatcherMapper 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 @@ internal class MatcherMapper 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 @@ internal class MatcherMapper 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 d345b057..d0f7622b 100644 --- a/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs @@ -366,10 +366,8 @@ public partial class WireMockServer } 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 613d58f8..313be8e5 100644 --- a/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/Request/RequestMessageGraphQLMatcher.cs @@ -100,7 +100,10 @@ public class RequestMessageGraphQLMatcher : IRequestMatcher 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 372bd906..42b10daa 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 2f631297..dabd8e9b 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; + } - return (TInterface)Activator.CreateInstance(pluginType, args)!; + if (TryGetPluginType(out var pluginType)) + { + instance = (TInterface)Activator.CreateInstance(pluginType, args)!; + return true; + } + + 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; + } - return (TInterface)Activator.CreateInstance(pluginType, args)!; + if (TryGetPluginTypeByFullName(implementationTypeFullName, out var pluginType)) + { + instance = (TInterface)Activator.CreateInstance(pluginType, args)!; + return true; + } + + 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; + } - return (TInterface)Instances.GetOrAdd(pluginType, key => Activator.CreateInstance(key, args)!); + if (TryGetPluginTypeByFullName(implementationTypeFullName, out var pluginType)) + { + staticInstance = (TInterface)Instances.GetOrAdd(pluginType, key => Activator.CreateInstance(key, args)!); + return true; + } + + 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 6b591f6e..e37a6bdb 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 class TypeLoaderTests } [Fact] - public void LoadNewInstance() + public void TryLoadNewInstance() { var current = Directory.GetCurrentDirectory(); try @@ -65,10 +64,11 @@ public class TypeLoaderTests // 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 class TypeLoaderTests } [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