Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CSharpCodeMatcher #324

Merged
merged 17 commits into from
Sep 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -21,6 +21,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
14 changes: 11 additions & 3 deletions src/WireMock.Net/Serialization/MatcherMapper.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using JetBrains.Annotations;
using SimMetrics.Net;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using SimMetrics.Net;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
using WireMock.Settings;
Expand Down 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 @@ -87,6 +96,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 @@ -95,6 +105,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 @@ -104,10 +115,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 @@ -118,7 +131,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