Skip to content

Commit

Permalink
Extend TypeLoader (#1069)
Browse files Browse the repository at this point in the history
* Extend PluginLoader

* rename
  • Loading branch information
StefH authored Feb 23, 2024
1 parent 2364866 commit ce833c1
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 95 deletions.
1 change: 1 addition & 0 deletions WireMock.Net Solution.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WWW/@EntryIndexedValue">WWW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XMS/@EntryIndexedValue">XMS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XUA/@EntryIndexedValue">XUA</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dlls/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Flurl/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=funcs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grpc/@EntryIndexedValue">True</s:Boolean>
Expand Down
56 changes: 0 additions & 56 deletions src/WireMock.Net/Plugin/PluginLoader.cs

This file was deleted.

3 changes: 1 addition & 2 deletions src/WireMock.Net/Serialization/MatcherMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using WireMock.Extensions;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.Plugin;
using WireMock.Settings;
using WireMock.Util;

Expand Down Expand Up @@ -53,7 +52,7 @@ public MatcherMapper(WireMockServerSettings settings)
case "CSharpCodeMatcher":
if (_settings.AllowCSharpCodeMatcher == true)
{
return PluginLoader.Load<ICSharpCodeMatcher>(matchBehaviour, matchOperator, stringPatterns);
return TypeLoader.Load<ICSharpCodeMatcher>(matchBehaviour, matchOperator, stringPatterns);
}

throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because WireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'.");
Expand Down
89 changes: 89 additions & 0 deletions src/WireMock.Net/Util/TypeLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using Stef.Validation;

namespace WireMock.Util;

internal static class TypeLoader
{
private static readonly ConcurrentDictionary<string, Type> Assemblies = new();

public static TInterface Load<TInterface>(params object[] args) where TInterface : class
{
var key = typeof(TInterface).FullName!;

var pluginType = Assemblies.GetOrAdd(key, _ =>
{
if (TryFindTypeInDlls<TInterface>(null, out var foundType))
{
return foundType;
}

throw new DllNotFoundException($"No dll found which implements Interface '{key}'.");
});

return (TInterface)Activator.CreateInstance(pluginType, args)!;
}

public static TInterface LoadByFullName<TInterface>(string implementationTypeFullName, params object[] args) where TInterface : class
{
Guard.NotNullOrEmpty(implementationTypeFullName);

var @interface = typeof(TInterface).FullName;
var key = $"{@interface}_{implementationTypeFullName}";

var pluginType = Assemblies.GetOrAdd(key, _ =>
{
if (TryFindTypeInDlls<TInterface>(implementationTypeFullName, out var foundType))
{
return foundType;
}

throw new DllNotFoundException($"No dll found which implements Interface '{@interface}' and has FullName '{implementationTypeFullName}'.");
});

return (TInterface)Activator.CreateInstance(pluginType, args)!;
}

private static bool TryFindTypeInDlls<TInterface>(string? implementationTypeFullName, [NotNullWhen(true)] out Type? pluginType) where TInterface : class
{
foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll"))
{
try
{
var assembly = Assembly.Load(new AssemblyName
{
Name = Path.GetFileNameWithoutExtension(file)
});

if (TryGetImplementationTypeByInterfaceAndOptionalFullName<TInterface>(assembly, implementationTypeFullName, out pluginType))
{
return true;
}
}
catch
{
// no-op: just try next .dll
}
}

pluginType = null;
return false;
}

private static bool TryGetImplementationTypeByInterfaceAndOptionalFullName<T>(Assembly assembly, string? implementationTypeFullName, [NotNullWhen(true)] out Type? type)
{
type = assembly
.GetTypes()
.FirstOrDefault(t =>
typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface &&
(implementationTypeFullName == null || string.Equals(t.FullName, implementationTypeFullName, StringComparison.OrdinalIgnoreCase))
);

return type != null;
}
}
37 changes: 0 additions & 37 deletions test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs

This file was deleted.

65 changes: 65 additions & 0 deletions test/WireMock.Net.Tests/Util/TypeLoaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using AnyOfTypes;
using FluentAssertions;
using WireMock.Matchers;
using WireMock.Models;
using WireMock.Util;
using Xunit;

namespace WireMock.Net.Tests.Util;

public class TypeLoaderTests
{
public interface IDummyInterfaceNoImplementation
{
}

public interface IDummyInterfaceWithImplementation
{
}

public class DummyClass : IDummyInterfaceWithImplementation
{
}

[Fact]
public void Load_ByInterface()
{
// Act
AnyOf<string, StringPattern> pattern = "x";
var result = TypeLoader.Load<ICSharpCodeMatcher>(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern);

// Assert
result.Should().NotBeNull();
}

[Fact]
public void Load_ByInterfaceAndFullName()
{
// Act
var result = TypeLoader.LoadByFullName<IDummyInterfaceWithImplementation>(typeof(DummyClass).FullName!);

// Assert
result.Should().BeOfType<DummyClass>();
}

[Fact]
public void Load_ByInterface_ButNoImplementationFoundForInterface_ThrowsException()
{
// Act
Action a = () => TypeLoader.Load<IDummyInterfaceNoImplementation>();

// Assert
a.Should().Throw<DllNotFoundException>().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Util.TypeLoaderTests+IDummyInterfaceNoImplementation'.");
}

[Fact]
public void Load_ByInterfaceAndFullName_ButNoImplementationFoundForInterface_ThrowsException()
{
// Act
Action a = () => TypeLoader.LoadByFullName<IDummyInterfaceWithImplementation>("xyz");

// Assert
a.Should().Throw<DllNotFoundException>().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Util.TypeLoaderTests+IDummyInterfaceWithImplementation' and has FullName 'xyz'.");
}
}

0 comments on commit ce833c1

Please sign in to comment.