Skip to content

Commit

Permalink
Add CSharpCodeMatcher (#324)
Browse files Browse the repository at this point in the history
* wip

* fix

* .

* windows-2019

* <Target Name="CheckIfShouldKillVBCSCompiler" />

* <!--aspnet/RoslynCodeDomProvider#51>

* AllowCSharpCodeMatcher

* CSharpCodeMatcher : IObjectMatcher

* TemplateForIsMatchWithDynamic

* RequestMessageBodyMatcher_GetMatchingScore_BodyAsJson_CSharpCodeMatcher

* fix

*  }

* Better Exception Handling
  • Loading branch information
StefH committed Sep 28, 2019
1 parent 4afef36 commit 0a9214e
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 9 deletions.
1 change: 1 addition & 0 deletions WireMock.Net Solution.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CS/@EntryIndexedValue">CS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD5</s:String>
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pool:
vmImage: 'vs2017-win2016'
vmImage: 'windows-2019'

variables:
Prerelease: 'ci'
Expand Down
1 change: 1 addition & 0 deletions examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static void Run()

var server = FluentMockServer.Start(new FluentMockServerSettings
{
AllowCSharpCodeMatcher = true,
Urls = new[] { url1, url2, url3 },
StartAdminInterface = true,
ReadStaticMappings = true,
Expand Down
5 changes: 3 additions & 2 deletions src/WireMock.Net.StandAlone/StandAloneApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ public static FluentMockServer Start([NotNull] string[] args, [CanBeNull] IWireM
StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true),
ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"),
WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"),
AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping", false),
AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"),
AdminUsername = parser.GetStringValue("AdminUsername"),
AdminPassword = parser.GetStringValue("AdminPassword"),
MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"),
RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration")
RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration"),
AllowCSharpCodeMatcher = parser.GetBoolValue("AllowCSharpCodeMatcher"),
};

if (logger != null)
Expand Down
197 changes: 197 additions & 0 deletions src/WireMock.Net/Matchers/CSharpCodeMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using WireMock.Exceptions;
using WireMock.Validation;

namespace WireMock.Matchers
{
/// <summary>
/// CSharpCode / CS-Script Matcher
/// </summary>
/// <inheritdoc cref="IObjectMatcher"/>
/// <inheritdoc cref="IStringMatcher"/>
internal class CSharpCodeMatcher : IObjectMatcher, IStringMatcher
{
private const string TemplateForIsMatchWithString = "{0} public class CodeHelper {{ public bool IsMatch(string it) {{ {1} }} }}";

private const string TemplateForIsMatchWithDynamic = "{0} public class CodeHelper {{ public bool IsMatch(dynamic it) {{ {1} }} }}";

private readonly string[] _usings =
{
"System",
"System.Linq",
"System.Collections.Generic",
"Microsoft.CSharp",
"Newtonsoft.Json.Linq"
};

public MatchBehaviour MatchBehaviour { get; }

private readonly string[] _patterns;

/// <summary>
/// Initializes a new instance of the <see cref="CSharpCodeMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public CSharpCodeMatcher([NotNull] params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, patterns)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CSharpCodeMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
public CSharpCodeMatcher(MatchBehaviour matchBehaviour, [NotNull] params string[] patterns)
{
Check.NotNull(patterns, nameof(patterns));

MatchBehaviour = matchBehaviour;
_patterns = patterns;
}

public double IsMatch(string input)
{
return IsMatchInternal(input);
}

public double IsMatch(object input)
{
return IsMatchInternal(input);
}

public double IsMatchInternal(object input)
{
double match = MatchScores.Mismatch;

if (input != null)
{
match = MatchScores.ToScore(_patterns.Select(pattern => IsMatch(input, pattern)));
}

return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}

private bool IsMatch(dynamic input, string pattern)
{
bool isMatchWithString = input is string;
var inputValue = isMatchWithString ? input : JObject.FromObject(input);
string source = GetSourceForIsMatchWithString(pattern, isMatchWithString);

object result = null;

#if NET451 || NET452
var compilerParams = new System.CodeDom.Compiler.CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false,
ReferencedAssemblies =
{
"System.dll",
"System.Core.dll",
"Microsoft.CSharp.dll",
"Newtonsoft.Json.dll"
}
};

using (var codeProvider = new Microsoft.CSharp.CSharpCodeProvider())
{
var compilerResults = codeProvider.CompileAssemblyFromSource(compilerParams, source);

if (compilerResults.Errors.Count != 0)
{
var errors = from System.CodeDom.Compiler.CompilerError er in compilerResults.Errors select er.ToString();
throw new WireMockException(string.Join(", ", errors));
}

object helper = compilerResults.CompiledAssembly.CreateInstance("CodeHelper");
if (helper == null)
{
throw new WireMockException("CSharpCodeMatcher: Unable to create instance from WireMock.CodeHelper");
}

var methodInfo = helper.GetType().GetMethod("IsMatch");
if (methodInfo == null)
{
throw new WireMockException("CSharpCodeMatcher: Unable to find method 'IsMatch' in WireMock.CodeHelper");
}

try
{
result = methodInfo.Invoke(helper, new[] { inputValue });
}
catch
{
throw new WireMockException("CSharpCodeMatcher: Unable to call method 'IsMatch' in WireMock.CodeHelper");
}
}
#elif NET46 || NET461
dynamic script;
try
{
script = CSScriptLibrary.CSScript.Evaluator.CompileCode(source).CreateObject("*");
}
catch
{
throw new WireMockException("CSharpCodeMatcher: Unable to create compiler for WireMock.CodeHelper");
}

try
{
result = script.IsMatch(inputValue);
}
catch
{
throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper");
}
#elif NETSTANDARD2_0
dynamic script;
try
{
var assembly = CSScriptLib.CSScript.Evaluator.CompileCode(source);
script = csscript.GenericExtensions.CreateObject(assembly, "*");
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Unable to compile code for WireMock.CodeHelper", ex);
}

try
{
result = script.IsMatch(inputValue);
}
catch (Exception ex)
{
throw new WireMockException("CSharpCodeMatcher: Problem calling method 'IsMatch' in WireMock.CodeHelper");
}
#else
throw new NotSupportedException("The 'CSharpCodeMatcher' cannot be used in netstandard 1.3");
#endif
try
{
return (bool)result;
}
catch
{
throw new WireMockException($"Unable to cast result '{result}' to bool");
}
}

private string GetSourceForIsMatchWithString(string pattern, bool isMatchWithString)
{
string template = isMatchWithString ? TemplateForIsMatchWithString : TemplateForIsMatchWithDynamic;
return string.Format(template, string.Join(Environment.NewLine, _usings.Select(u => $"using {u};")), pattern);
}

/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public string[] GetPatterns()
{
return _patterns;
}

/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "CSharpCodeMatcher";
}
}
4 changes: 2 additions & 2 deletions src/WireMock.Net/Matchers/LinqMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ namespace WireMock.Matchers
/// <summary>
/// System.Linq.Dynamic.Core Expression Matcher
/// </summary>
/// <inheritdoc cref="IObjectMatcher"/>
/// <inheritdoc cref="IStringMatcher"/>
public class LinqMatcher : IStringMatcher
public class LinqMatcher : IObjectMatcher, IStringMatcher
{
private readonly string[] _patterns;

Expand Down Expand Up @@ -117,7 +118,6 @@ public double IsMatch(object input)
}

return MatchBehaviourHelper.Convert(MatchBehaviour, match);

}

/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
Expand Down
8 changes: 8 additions & 0 deletions src/WireMock.Net/Serialization/MatcherMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ public IMatcher Map([CanBeNull] MatcherModel matcher)

switch (matcherName)
{
case "CSharpCodeMatcher":
if (_settings.AllowCSharpCodeMatcher == true)
{
return new CSharpCodeMatcher(matchBehaviour, stringPatterns);
}

throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because FluentMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'.");

case "LinqMatcher":
return new LinqMatcher(matchBehaviour, stringPatterns);

Expand Down
4 changes: 4 additions & 0 deletions src/WireMock.Net/Settings/FluentMockServerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,9 @@ public class FluentMockServerSettings : IFluentMockServerSettings
[PublicAPI]
[JsonIgnore]
public Action<IHandlebars, IFileSystemHandler> HandlebarsRegistrationCallback { get; set; }

/// <inheritdoc cref="IFluentMockServerSettings.AllowCSharpCodeMatcher"/>
[PublicAPI]
public bool? AllowCSharpCodeMatcher { get; set; }
}
}
11 changes: 8 additions & 3 deletions src/WireMock.Net/Settings/IFluentMockServerSettings.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using HandlebarsDotNet;
using System;
using HandlebarsDotNet;
using JetBrains.Annotations;
using System;
using WireMock.Handlers;
using WireMock.Logging;

Expand Down Expand Up @@ -115,9 +115,14 @@ public interface IFluentMockServerSettings
IFileSystemHandler FileSystemHandler { get; set; }

/// <summary>
/// Action which can be used to add additional is Handlebar registrations. [Optional]
/// Action which can be used to add additional Handlebars registrations. [Optional]
/// </summary>
[PublicAPI]
Action<IHandlebars, IFileSystemHandler> HandlebarsRegistrationCallback { get; set; }

/// <summary>
/// Allow the usage of CSharpCodeMatcher (default is not allowed).
/// </summary>
bool? AllowCSharpCodeMatcher { get; set; }
}
}
16 changes: 15 additions & 1 deletion src/WireMock.Net/WireMock.Net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<Description>Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape.</Description>
<AssemblyTitle>WireMock.Net</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<!--<TargetFrameworks>net451;net452;net46;net461;netstandard1.3;netstandard2.0;netcoreapp2.1</TargetFrameworks>-->
<TargetFrameworks>net451;net452;net46;net461;netstandard1.3;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net</AssemblyName>
Expand Down Expand Up @@ -31,6 +32,9 @@
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>

<!--https://github.com/aspnet/RoslynCodeDomProvider/issues/51-->
<Target Name="CheckIfShouldKillVBCSCompiler" />

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<PathMap>$(MSBuildProjectDirectory)=/</PathMap>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
Expand All @@ -40,6 +44,10 @@
<DefineConstants>NETSTANDARD;USE_ASPNETCORE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1' or '$(TargetFramework)' == 'netcoreapp2.2'">
<DefineConstants>USE_ASPNETCORE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net461'">
<DefineConstants>USE_ASPNETCORE;NET46</DefineConstants>
</PropertyGroup>
Expand All @@ -54,6 +62,7 @@
<PackageReference Include="JetBrains.Annotations" Version="2018.2.1">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<!--<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />-->
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
<PackageReference Include="RestEase" Version="1.4.7" />
Expand Down Expand Up @@ -86,6 +95,7 @@

<PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost" Version="5.2.6" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" Version="2.0.1" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
Expand All @@ -94,6 +104,7 @@

<PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost" Version="5.2.6" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" Version="2.0.1" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
Expand All @@ -103,10 +114,12 @@
<PackageReference Include="Microsoft.Owin.Hosting" Version="4.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.3" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="CS-Script" Version="3.29.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" />
<PackageReference Include="CS-Script" Version="3.29.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
Expand All @@ -117,7 +130,8 @@
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.4" />
<PackageReference Include="CS-Script.Core" Version="1.1.1" />
</ItemGroup>
</Project>
Loading

0 comments on commit 0a9214e

Please sign in to comment.