Compare commits

...

43 Commits

Author SHA1 Message Date
Stef Heyenrath
799ea2d219 1.4.19 2021-08-04 14:38:41 +00:00
Stef Heyenrath
0f99e06acc Add NotNullOrEmptyMatcher (#625) 2021-08-04 16:22:22 +02:00
Stef Heyenrath
9d0682bff6 Add FluentBuilder for client models (#622) 2021-08-04 15:40:35 +02:00
Stef Heyenrath
cb1d2a5294 1.4.18 2021-07-10 11:48:54 +02:00
Stef Heyenrath
4b435faf0b Update Handlebars.Net.Helpers.XPath to fix issue with 'xml version' (#619)
Update Handlebars.Net.Helpers.XPath to fix issue with 'xml version'
2021-07-10 11:43:59 +02:00
Stef Heyenrath
5e7cb44525 Update FUNDING.yml 2021-07-07 20:07:40 +02:00
Stef Heyenrath
444298c28c 1.4.17 2021-07-07 18:31:07 +02:00
Stef Heyenrath
6f42aa99bc Handlebars.Net.Helpers.Humanizer (#617) 2021-07-07 17:02:15 +02:00
Stef Heyenrath
8a9ea1b843 Update FUNDING.yml 2021-07-06 12:13:13 +02:00
Stef Heyenrath
dade24de37 Update FUNDING.yml 2021-07-06 12:12:48 +02:00
Stef Heyenrath
6beaa631f4 Update FUNDING.yml 2021-07-03 09:44:33 +02:00
Stef Heyenrath
282281aa7f 1.4.16 2021-06-05 11:00:51 +02:00
Stef Heyenrath
4052a0ef3b Upgrade Handlebars.Net.Helpers to 2.19 (#616)
* Upgrade Handlebars.Net.Helpers to 2.19

* fix ut
2021-06-05 10:59:09 +02:00
Stef Heyenrath
2ca394b7f6 1.4.15 2021-05-19 12:01:19 +00:00
Stef Heyenrath
b04000bfdd Add support for multiple webhooks (#615) 2021-05-19 13:58:48 +02:00
Stef Heyenrath
93ab4e1853 1.4.14 2021-05-11 05:45:07 +00:00
starkpl
80b5eaac6e Add AdditionalServiceRegistration action for custom ASP.NET Core DI setup (#611) 2021-05-11 07:37:20 +02:00
Stef Heyenrath
a1dc2ba646 condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests (#612) 2021-05-10 22:06:42 +02:00
Stef Heyenrath
934c444902 Fix some SonarCloud issues in UnitTests (#610) 2021-05-07 18:23:23 +02:00
Stef Heyenrath
83d178bdb5 1.4.13 2021-04-26 14:53:15 +00:00
Stef Heyenrath
d91b5d5831 Add possibility to use settings to generate MappingModel models with wildcard path parameters. (#609)
* Add optional settings for WithMappingFromOpenApi

* .

* .

* cleanup
2021-04-26 14:44:45 +02:00
Stef Heyenrath
43b96ce340 Add OpenApiParser NuGet to readme 2021-04-23 17:51:45 +02:00
dependabot[bot]
4d8cf43357 Bump System.Text.Encodings.Web (#607)
Bumps [System.Text.Encodings.Web](https://github.com/dotnet/corefx) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/dotnet/corefx/releases)
- [Commits](https://github.com/dotnet/corefx/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-22 18:11:30 +02:00
Stef Heyenrath
328e9090b1 1.4.12 2021-04-22 06:46:24 +00:00
dependabot[bot]
a22b3bfbc5 Bump System.Net.Http in /examples/WireMock.Net.Service (#606)
Bumps System.Net.Http from 4.3.3 to 4.3.4.

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-22 07:49:32 +02:00
dependabot[bot]
29974c7ad4 Bump System.Net.Http from 4.3.3 to 4.3.4 in /src/WireMock.Net (#605)
Bumps System.Net.Http from 4.3.3 to 4.3.4.

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-22 07:48:39 +02:00
Stef Heyenrath
660a09e656 1.4.11 2021-04-18 21:32:42 +02:00
Stef Heyenrath
fbecd3b119 Fix match logic (#604) 2021-04-18 21:30:03 +02:00
Stef Heyenrath
b17840cea9 1.4.10 2021-04-15 19:46:41 +02:00
Stef Heyenrath
969b0da8e2 Fix callback with Headers (#603) 2021-04-15 19:41:45 +02:00
Stef Heyenrath
dc078b57ea 1.4.9 2021-03-31 18:23:49 +02:00
Stef Heyenrath
8140b37095 WithProxy() should save the new mapping (#600)
* WithProxy should save the new mapping

* fix ut

* .
2021-03-31 18:17:27 +02:00
Stef Heyenrath
2ad060bbd4 1.4.8 2021-03-24 17:20:31 +00:00
Stef Heyenrath
d758301e4f Webhook (#591)
Webhook
2021-03-24 18:15:31 +01:00
Stef Heyenrath
cee73023c7 1.4.7 2021-03-21 09:22:18 +01:00
Ben Arnold
6f7d2c83f5 Remove an approximate two second delay in response to the first request from a new socket connection, only occuring on some Windows 10 machines. (#597)
A side-effect of this fix is that is also allows connections to IPv6 addresses.
2021-03-21 09:12:19 +01:00
Stef Heyenrath
2fb0f92a2d Use Handlebars.Net.Helpers Version="2.1.2" (#595) 2021-03-19 15:17:39 +01:00
Stef Heyenrath
ddf2b49240 wip (#594) 2021-03-18 14:29:13 +01:00
Stef Heyenrath
3617e95db6 1.4.6 2021-02-26 12:37:54 +00:00
Stef Heyenrath
aa8510fab3 Fix WithCallback logic when using other fluent builder statements (#587) 2021-02-26 13:10:25 +01:00
Stef Heyenrath
e23249c144 1.4.5 2021-02-11 11:03:09 +00:00
Roman Vovk
7a8f4c3630 Fix response date header (#585) 2021-02-11 11:49:24 +01:00
Stef Heyenrath
ddcf2b29a3 coverlet 2021-02-09 20:43:24 +01:00
133 changed files with 5598 additions and 3720 deletions

6
.editorconfig Normal file
View File

@@ -0,0 +1,6 @@
[*]
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = true

4
.github/FUNDING.yml vendored
View File

@@ -1,8 +1,8 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: [StefH]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
open_collective: # wiremocknet
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.4.3</VersionPrefix>
<VersionPrefix>1.4.19</VersionPrefix>
<PackageReleaseNotes>See CHANGELOG.md</PackageReleaseNotes>
<PackageIconUrl>https://raw.githubusercontent.com/WireMock-Net/WireMock.Net/master/WireMock.Net-Logo.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>

View File

@@ -1,3 +1,3 @@
https://github.com/StefH/GitHubReleaseNotes
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc --version 1.4.3
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version 1.4.19

View File

@@ -36,6 +36,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Net.FluentAssertions** | [![NuGet Badge WireMock.Net.FluentAssertions](https://buildstats.info/nuget/WireMock.Net.FluentAssertions)](https://www.nuget.org/packages/WireMock.Net.FluentAssertions) | [![MyGet Badge WireMock.Net.FluentAssertions](https://buildstats.info/myget/wiremock-net/WireMock.Net.FluentAssertions?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.FluentAssertions)
| &nbsp;&nbsp;**WireMock.Net.RestClient** | [![NuGet Badge WireMock.Net.RestClient](https://buildstats.info/nuget/WireMock.Net.RestClient)](https://www.nuget.org/packages/WireMock.Net.RestClient) | [![MyGet Badge WireMock.Net.RestClient](https://buildstats.info/myget/wiremock-net/WireMock.Net.RestClient?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
| &nbsp;&nbsp;**WireMock.Net.Matchers.CSharpCode** | [![NuGet Badge WireMock.Net.Matchers.CSharpCode](https://buildstats.info/nuget/WireMock.Net.Matchers.CSharpCode)](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [![MyGet Badge WireMock.Net.Matchers.CSharpCode](https://buildstats.info/myget/wiremock-net/WireMock.Net.Matchers.CSharpCode?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)
| &nbsp;&nbsp;**WireMock.Net.OpenApiParser** | [![NuGet Badge WireMock.Net.OpenApiParser](https://buildstats.info/nuget/WireMock.Net.OpenApiParser)](https://www.nuget.org/packages/WireMock.Net.OpenApiParser) | [![MyGet Badge WireMock.Net.OpenApiParser](https://buildstats.info/myget/wiremock-net/WireMock.Net.OpenApiParser?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenApiParser)
## Development
For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page.

View File

@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
# Visual Studio Version 17
VisualStudioVersion = 17.0.31521.260
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}"
EndProject
@@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{98
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7EFB2C5B-1BB2-4AAF-BC9F-216ED80C594D}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
azure-pipelines-ci-linux.yml = azure-pipelines-ci-linux.yml
azure-pipelines-ci.yml = azure-pipelines-ci.yml
@@ -82,8 +83,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Matchers.CShar
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.Net472.Classic", "examples\WireMock.Net.Console.Net472.Classic\WireMock.Net.Console.Net472.Classic.csproj", "{6580580B-1EFD-4922-B0EC-FF290DB279EE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.XamarinUI.Tests", "test\WireMock.Net.XamarinUI.Tests\WireMock.Net.XamarinUI.Tests.csproj", "{0DE14F1B-A51E-4B59-A87C-C6012DCD2844}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -206,14 +205,6 @@ Global
{6580580B-1EFD-4922-B0EC-FF290DB279EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6580580B-1EFD-4922-B0EC-FF290DB279EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6580580B-1EFD-4922-B0EC-FF290DB279EE}.Release|Any CPU.Build.0 = Release|Any CPU
{0DE14F1B-A51E-4B59-A87C-C6012DCD2844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DE14F1B-A51E-4B59-A87C-C6012DCD2844}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DE14F1B-A51E-4B59-A87C-C6012DCD2844}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DE14F1B-A51E-4B59-A87C-C6012DCD2844}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -247,9 +238,7 @@ Global
{40BF24B5-12E6-4610-9489-138798632E28} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{3F8CF0AE-5F24-4A54-89E7-A3EE829DB5F8} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{B6269AAC-170A-4346-8B9A-444DED3D9A44} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{B6269AAC-170A-4346-8B9A-444DED3D9A44} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{6580580B-1EFD-4922-B0EC-FF290DB279EE} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{0DE14F1B-A51E-4B59-A87C-C6012DCD2844} = {0BB8B634-407A-4610-A91F-11586990767A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -13,6 +13,9 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=funcs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jmes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Stef/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Webhook/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Webhooks/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

View File

@@ -28,6 +28,7 @@ jobs:
- task: SonarCloudPrepare@1
displayName: 'Prepare analysis on SonarCloud'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
inputs:
SonarCloud: SonarCloud
organization: wiremock-net
@@ -52,9 +53,11 @@ jobs:
- task: SonarCloudAnalyze@1
displayName: 'SonarCloud: Run Code Analysis'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
- task: SonarCloudPublish@1
displayName: 'SonarCloud: Publish Quality Gate Result'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) # Do not run for PullRequests
- task: whitesource.ws-bolt.bolt.wss.WhiteSource Bolt@19
displayName: 'WhiteSource Bolt'

View File

@@ -22,6 +22,11 @@ namespace WireMock.Net.Client
var settings1 = await api.GetSettingsAsync();
Console.WriteLine($"settings1 = {JsonConvert.SerializeObject(settings1)}");
var settingsViaBuilder = new FluentBuilder.SettingsModelBuilder()
.WithGlobalProcessingDelay(1077)
.WithoutGlobalProcessingDelay()
.Build();
settings1.GlobalProcessingDelay = 1077;
api.PostSettingsAsync(settings1).Wait();

View File

@@ -59,6 +59,7 @@ namespace WireMock.Net.ConsoleApplication
//},
PreWireMockMiddlewareInit = app => { System.Console.WriteLine($"PreWireMockMiddlewareInit : {app.GetType()}"); },
PostWireMockMiddlewareInit = app => { System.Console.WriteLine($"PostWireMockMiddlewareInit : {app.GetType()}"); },
AdditionalServiceRegistration = services => { System.Console.WriteLine($"AdditionalServiceRegistration : {services.GetType()}"); },
Logger = new WireMockConsoleLogger(),
HandlebarsRegistrationCallback = (handlebarsContext, fileSystemHandler) =>

View File

@@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using WireMock.Admin.Mappings;
using WireMock.Logging;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
using WireMock.Server;
using WireMock.Settings;
using WireMock.Net.OpenApiParser.Extensions;
namespace WireMock.Net.OpenApiParser.ConsoleApp
{
@@ -23,13 +25,18 @@ namespace WireMock.Net.OpenApiParser.ConsoleApp
ReadStaticMappings = false,
WatchStaticMappings = false,
WatchStaticMappingsInSubdirectories = false,
Logger = new WireMockConsoleLogger(),
Logger = new WireMockConsoleLogger()
});
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
server.SetBasicAuthentication("a", "b");
server.WithMappingFromOpenApiFile(path, out var diag);
var settings = new WireMockOpenApiParserSettings
{
PathPatternToUse = ExampleValueType.Wildcard
};
server.WithMappingFromOpenApiFile(path, settings, out var diag);
Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey();

View File

@@ -32,11 +32,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Handlebars, Version=1.9.5.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.1.9.5\lib\net452\Handlebars.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath>
<Reference Include="log4net, Version=2.0.12.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.12\lib\net45\log4net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.2.0.2\lib\net45\Microsoft.Owin.dll</HintPath>
@@ -57,6 +54,7 @@
<HintPath>..\..\packages\SimMetrics.Net.1.0.5\lib\net45\SimMetrics.Net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Handlebars.Net" version="1.9.5" targetFramework="net452" />
<package id="log4net" version="2.0.10" targetFramework="net452" />
<package id="log4net" version="2.0.12" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
@@ -12,6 +11,6 @@
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net452" />
<package id="Owin" version="1.0" targetFramework="net452" />
<package id="SimMetrics.Net" version="1.0.5" targetFramework="net452" />
<package id="System.Net.Http" version="4.3.3" targetFramework="net452" />
<package id="System.Net.Http" version="4.3.4" targetFramework="net452" />
<package id="XPath2" version="1.1.0" targetFramework="net452" />
</packages>

View File

@@ -10,6 +10,7 @@ using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;
using WireMock.Util;
namespace WireMock.Net.StandAlone.NETCoreApp
{
@@ -34,6 +35,14 @@ namespace WireMock.Net.StandAlone.NETCoreApp
_server = WireMockServer.Start(settings);
_server.Given(Request.Create().WithPath("/api/sap")
.UsingPost()
.WithBody((IBodyData xmlData) => {
//xmlData is always null
return true;
}))
.RespondWith(Response.Create().WithStatusCode(System.Net.HttpStatusCode.OK));
_server
.Given(Request.Create()
.UsingAnyMethod())

View File

@@ -2,7 +2,7 @@
"profiles": {
"WireMock.Net.StandAlone.NETCoreApp": {
"commandName": "Project",
"commandLineArgs": "--Urls https://localhost:10080 --WireMockLogger WireMockConsoleLogger"
"commandLineArgs": "--Urls http://localhost:9091 --WireMockLogger WireMockConsoleLogger"
}
}
}

View File

@@ -39,8 +39,8 @@
<StartupObject>WireMock.Net.StandAlone.Net452.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath>
<Reference Include="log4net, Version=2.0.12.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.12\lib\net45\log4net.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Host.HttpListener.4.0.0\lib\net451\Microsoft.Owin.Host.HttpListener.dll</HintPath>
@@ -49,12 +49,14 @@
<HintPath>..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.6.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.6\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.10" targetFramework="net452" />
<package id="log4net" version="2.0.12" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.6" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="4.0.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net452" />

View File

@@ -76,7 +76,7 @@
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net461" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net461" />
<package id="System.Security.Principal.Windows" version="4.5.1" targetFramework="net461" />
<package id="System.Text.Encodings.Web" version="4.5.0" targetFramework="net461" />
<package id="System.Text.Encodings.Web" version="4.5.1" targetFramework="net461" />
<package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net461" />
<package id="XPath2" version="1.1.0" targetFramework="net461" />
</packages>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// Body Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class BodyModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// ClientIPModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ClientIPModel
{
/// <summary>

View File

@@ -5,6 +5,7 @@ namespace WireMock.Admin.Mappings
/// <summary>
/// Cookie Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class CookieModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// EncodingModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class EncodingModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// Fault Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class FaultModel
{
/// <summary>

View File

@@ -5,6 +5,7 @@ namespace WireMock.Admin.Mappings
/// <summary>
/// Header Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class HeaderModel
{
/// <summary>

View File

@@ -5,6 +5,7 @@ namespace WireMock.Admin.Mappings
/// <summary>
/// MappingModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class MappingModel
{
/// <summary>
@@ -52,5 +53,15 @@ namespace WireMock.Admin.Mappings
/// Saves this mapping as a static mapping file.
/// </summary>
public bool? SaveToFile { get; set; }
/// <summary>
/// The Webhook.
/// </summary>
public WebhookModel Webhook { get; set; }
/// <summary>
/// The Webhooks.
/// </summary>
public WebhookModel[] Webhooks { get; set; }
}
}

View File

@@ -3,6 +3,7 @@
/// <summary>
/// MatcherModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class MatcherModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// Param Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ParamModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// PathModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class PathModel
{
/// <summary>

View File

@@ -5,6 +5,7 @@ namespace WireMock.Admin.Mappings
/// <summary>
/// RequestModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class RequestModel
{
/// <summary>

View File

@@ -1,11 +1,11 @@
using System.Collections.Generic;
using WireMock.Types;
namespace WireMock.Admin.Mappings
{
/// <summary>
/// ResponseModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ResponseModel
{
/// <summary>

View File

@@ -5,6 +5,7 @@ namespace WireMock.Admin.Mappings
/// <summary>
/// Status
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class StatusModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// UrlModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class UrlModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// WebProxy settings
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class WebProxyModel
{
/// <summary>

View File

@@ -0,0 +1,14 @@
namespace WireMock.Admin.Mappings
{
/// <summary>
/// The Webhook
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class WebhookModel
{
/// <summary>
/// The Webhook Request.
/// </summary>
public WebhookRequestModel Request { get; set; }
}
}

View File

@@ -0,0 +1,46 @@
using System.Collections.Generic;
namespace WireMock.Admin.Mappings
{
/// <summary>
/// RequestModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class WebhookRequestModel
{
/// <summary>
/// Gets or sets the Url.
/// </summary>
public string Url { get; set; }
/// <summary>
/// The methods
/// </summary>
public string Method { get; set; }
/// <summary>
/// Gets or sets the headers.
/// </summary>
public IDictionary<string, string> Headers { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
public string Body { get; set; }
/// <summary>
/// Gets or sets the body (as JSON object).
/// </summary>
public object BodyAsJson { get; set; }
/// <summary>
/// Use ResponseMessage Transformer.
/// </summary>
public bool? UseTransformer { get; set; }
/// <summary>
/// Gets the type of the transformer.
/// </summary>
public string TransformerType { get; set; }
}
}

View File

@@ -5,6 +5,7 @@ namespace WireMock.Admin.Requests
/// <summary>
/// Request Log Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class LogEntryModel
{
/// <summary>

View File

@@ -5,6 +5,7 @@ namespace WireMock.Admin.Requests
/// <summary>
/// LogRequestMatchModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class LogRequestMatchModel
{
/// <summary>

View File

@@ -8,6 +8,7 @@ namespace WireMock.Admin.Requests
/// <summary>
/// RequestMessage Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class LogRequestModel
{
/// <summary>

View File

@@ -7,6 +7,7 @@ namespace WireMock.Admin.Requests
/// <summary>
/// Response MessageModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class LogResponseModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// ScenarioStateModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ScenarioStateModel
{
/// <summary>

View File

@@ -3,6 +3,7 @@
/// <summary>
/// Settings
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class SettingsModel
{
/// <summary>

View File

@@ -0,0 +1,13 @@
namespace WireMock.Models
{
/// <summary>
/// IWebhook
/// </summary>
public interface IWebhook
{
/// <summary>
/// Request
/// </summary>
IWebhookRequest Request { get; set; }
}
}

View File

@@ -0,0 +1,42 @@
using System.Collections.Generic;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Models
{
/// <summary>
/// IWebhookRequest
/// </summary>
public interface IWebhookRequest
{
/// <summary>
/// The Webhook Url.
/// </summary>
string Url { get; set; }
/// <summary>
/// The method to use.
/// </summary>
string Method { get; set; }
/// <summary>
/// The Headers to send.
/// </summary>
IDictionary<string, WireMockList<string>> Headers { get; }
/// <summary>
/// The body to send.
/// </summary>
IBodyData BodyData { get; set; }
/// <summary>
/// Use Transformer.
/// </summary>
bool? UseTransformer { get; set; }
/// <summary>
/// The transformer type.
/// </summary>
TransformerType TransformerType { get; set; }
}
}

View File

@@ -32,6 +32,7 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="FluentBuilder" Version="0.0.4" />
</ItemGroup>
</Project>

View File

@@ -1,12 +1,19 @@
using System;
using System.IO;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using SharpYaml.Model;
using Stef.Validation;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Server;
namespace WireMock.Net.OpenApiParser.Extensions
{
/// <summary>
/// Some extension methods for <see cref="IWireMockServer"/>.
/// </summary>
public static class WireMockServerExtensions
{
/// <summary>
@@ -15,18 +22,26 @@ namespace WireMock.Net.OpenApiParser.Extensions
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic)
{
if (server == null)
{
throw new ArgumentNullException(nameof(server));
}
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}
return WithMappingFromOpenApiFile(server, path, null, out diagnostic);
}
var mappings = new WireMockOpenApiParser().FromFile(path, out diagnostic);
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 file.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
/// <param name="settings">Additional settings</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
Guard.NotNull(server, nameof(server));
Guard.NotNullOrEmpty(path, nameof(path));
var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic);
return server.WithMapping(mappings.ToArray());
}
@@ -37,9 +52,27 @@ namespace WireMock.Net.OpenApiParser.Extensions
/// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic)
{
var mappings = new WireMockOpenApiParser().FromStream(stream, out diagnostic);
return WithMappingFromOpenApiStream(server, stream, null, out diagnostic);
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2 or V3 stream.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
Guard.NotNull(server, nameof(server));
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(settings, nameof(settings));
var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic);
return server.WithMapping(mappings.ToArray());
}
@@ -49,9 +82,14 @@ namespace WireMock.Net.OpenApiParser.Extensions
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="document">The OpenAPI document to use as mappings.</param>
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document)
/// <param name="settings">Additional settings [optional]</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings = null)
{
var mappings = new WireMockOpenApiParser().FromDocument(document);
Guard.NotNull(server, nameof(server));
Guard.NotNull(document, nameof(document));
var mappings = new WireMockOpenApiParser().FromDocument(document, settings);
return server.WithMapping(mappings.ToArray());
}

View File

@@ -1,37 +1,57 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using WireMock.Admin.Mappings;
namespace WireMock.Net.OpenApiParser
{
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// </summary>
public interface IWireMockOpenApiParser
{
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>.
/// </summary>
/// <param name="document">The source OpenApiDocument</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromDocument(OpenApiDocument document);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic);
using System.Collections.Generic;
using System.IO;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser
{
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// </summary>
public interface IWireMockOpenApiParser
{
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a file-path.
/// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3 or Raml file.</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic);
}
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a file-path.
/// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3 or Raml file.</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>.
/// </summary>
/// <param name="document">The source OpenApiDocument</param>
/// <param name="settings">Additional settings [optional]</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings settings = null);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IEnumerable{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
}
}

View File

@@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Writers;
using Newtonsoft.Json.Linq;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
using WireMock.Net.OpenApiParser.Utils;
namespace WireMock.Net.OpenApiParser.Mappers
{
internal class OpenApiPathsMapper
{
private readonly WireMockOpenApiParserSettings _settings;
private readonly ExampleValueGenerator _exampleValueGenerator;
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_exampleValueGenerator = new ExampleValueGenerator(settings);
}
public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths)
{
return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x);
}
private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths)
{
return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x);
}
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value));
}
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation)
{
var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query);
var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path);
var response = operation.Responses.FirstOrDefault();
TryGetContent(response.Value?.Content, out OpenApiMediaType responseContent, out string responseContentType);
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example;
var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) : MapSchemaToObject(responseSchema);
if (int.TryParse(response.Key, out var httpStatusCode))
{
httpStatusCode = 200;
}
return new MappingModel
{
Guid = Guid.NewGuid(),
Request = new RequestModel
{
Methods = new[] { httpMethod },
Path = MapPathWithParameters(path, pathParameters),
Params = MapQueryParameters(queryParameters)
},
Response = new ResponseModel
{
StatusCode = httpStatusCode,
Headers = MapHeaders(responseContentType, response.Value?.Headers),
BodyAsJson = body
}
};
}
private bool TryGetContent(IDictionary<string, OpenApiMediaType> contents, out OpenApiMediaType openApiMediaType, out string contentType)
{
openApiMediaType = null;
contentType = null;
if (contents == null || contents.Values.Count == 0)
{
return false;
}
if (contents.TryGetValue("application/json", out var content))
{
openApiMediaType = content;
contentType = "application/json";
}
else
{
var first = contents.FirstOrDefault();
openApiMediaType = first.Value;
contentType = first.Key;
}
return true;
}
private object MapSchemaToObject(OpenApiSchema schema, string name = null)
{
if (schema == null)
{
return null;
}
switch (schema.GetSchemaType())
{
case SchemaType.Array:
var jArray = new JArray();
for (int i = 0; i < _settings.NumberOfArrayItems; i++)
{
if (schema.Items.Properties.Count > 0)
{
var arrayItem = new JObject();
foreach (var property in schema.Items.Properties)
{
var objectValue = MapSchemaToObject(property.Value, property.Key);
if (objectValue is JProperty jp)
{
arrayItem.Add(jp);
}
else
{
arrayItem.Add(new JProperty(property.Key, objectValue));
}
}
jArray.Add(arrayItem);
}
else
{
jArray.Add(MapSchemaToObject(schema.Items, name));
}
}
return jArray;
case SchemaType.Boolean:
case SchemaType.Integer:
case SchemaType.Number:
case SchemaType.String:
return _exampleValueGenerator.GetExampleValue(schema);
case SchemaType.Object:
var propertyAsJObject = new JObject();
foreach (var schemaProperty in schema.Properties)
{
string propertyName = schemaProperty.Key;
var openApiSchema = schemaProperty.Value;
if (openApiSchema.GetSchemaType() == SchemaType.Object)
{
var mapped = MapSchemaToObject(schemaProperty.Value, schemaProperty.Key);
if (mapped is JProperty jp)
{
propertyAsJObject.Add(jp);
}
}
else
{
bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x);
propertyAsJObject.Add(new JProperty(propertyName, _exampleValueGenerator.GetExampleValue(openApiSchema)));
}
}
return name != null ? new JProperty(name, propertyAsJObject) : (JToken)propertyAsJObject;
default:
return null;
}
}
private string MapPathWithParameters(string path, IEnumerable<OpenApiParameter> parameters)
{
if (parameters == null)
{
return path;
}
string newPath = path;
foreach (var parameter in parameters)
{
newPath = newPath.Replace($"{{{parameter.Name}}}", GetExampleValue(parameter.Schema, _settings.PathPatternToUse));
}
return newPath;
}
private JToken MapOpenApiAnyToJToken(IOpenApiAny any)
{
if (any == null)
{
return null;
}
using var outputString = new StringWriter();
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
return JObject.Parse(outputString.ToString());
}
private IDictionary<string, object> MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
{
var mappedHeaders = headers.ToDictionary(
item => item.Key,
item => GetExampleValue(null, _settings.HeaderPatternToUse) as object
);
if (!string.IsNullOrEmpty(responseContentType))
{
if (!mappedHeaders.ContainsKey("Content-Type"))
{
mappedHeaders.Add("Content-Type", responseContentType);
}
else
{
mappedHeaders["Content-Type"] = responseContentType;
}
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
}
private IList<ParamModel> MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
{
var list = queryParameters
.Select(qp => new ParamModel
{
Name = qp.Name,
Matchers = new[]
{
new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetDefaultValueAsStringForSchemaType(qp.Schema)
}
}
})
.ToList();
return list.Any() ? list : null;
}
private string GetDefaultValueAsStringForSchemaType(OpenApiSchema schema)
{
var value = _exampleValueGenerator.GetExampleValue(schema);
switch (value)
{
case string valueAsString:
return valueAsString;
default:
return value.ToString();
}
}
private string GetExampleValue(OpenApiSchema schema, ExampleValueType type)
{
switch (type)
{
case ExampleValueType.Value:
return GetDefaultValueAsStringForSchemaType(schema);
default:
return "*";
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace WireMock.Net.OpenApiParser.Settings
{
/// <summary>
/// A class defining the example values to use for the different types.
/// </summary>
public class WireMockOpenApiParserExampleValues
{
#pragma warning disable 1591
public bool Boolean { get; set; } = true;
public int Integer { get; set; } = 42;
public float Float { get; set; } = 4.2f;
public double Double { get; set; } = 4.2d;
public Func<DateTime> Date { get; set; } = () => System.DateTime.UtcNow.Date;
public Func<DateTime> DateTime { get; set; } = () => System.DateTime.UtcNow;
public byte[] Bytes { get; set; } = { 48, 49, 50 };
public object Object { get; set; } = "example-object";
public string String { get; set; } = "example-string";
#pragma warning restore 1591
}
}

View File

@@ -0,0 +1,30 @@
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Settings
{
/// <summary>
/// The WireMockOpenApiParser Settings
/// </summary>
public class WireMockOpenApiParserSettings
{
/// <summary>
/// The number of array items to generate (default is 3).
/// </summary>
public int NumberOfArrayItems { get; set; } = 3;
/// <summary>
/// The example value type to use when generating a Path
/// </summary>
public ExampleValueType PathPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary>
/// The example value type to use when generating a Header
/// </summary>
public ExampleValueType HeaderPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary>
/// The example values to use
/// </summary>
public WireMockOpenApiParserExampleValues ExampleValues { get; } = new WireMockOpenApiParserExampleValues();
}
}

View File

@@ -0,0 +1,18 @@
namespace WireMock.Net.OpenApiParser.Types
{
/// <summary>
/// The example value to use
/// </summary>
public enum ExampleValueType
{
/// <summary>
/// Use a generated example value based on the SchemaType (default).
/// </summary>
Value,
/// <summary>
/// Just use a Wildcard (*) character.
/// </summary>
Wildcard
}
}

View File

@@ -1,49 +1,57 @@
using System;
using Microsoft.OpenApi.Models;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Utils
{
internal static class ExampleValueGenerator
internal class ExampleValueGenerator
{
public static object GetExampleValue(OpenApiSchema schema)
private readonly WireMockOpenApiParserSettings _settings;
public ExampleValueGenerator(WireMockOpenApiParserSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public object GetExampleValue(OpenApiSchema schema)
{
switch (schema?.GetSchemaType())
{
case SchemaType.Boolean:
return true;
return _settings.ExampleValues.Boolean;
case SchemaType.Integer:
return 42;
return _settings.ExampleValues.Integer;
case SchemaType.Number:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Float:
return 4.2f;
return _settings.ExampleValues.Float;
default:
return 4.2d;
return _settings.ExampleValues.Double;
}
default:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Date:
return DateTimeUtils.ToRfc3339Date(DateTime.UtcNow);
return DateTimeUtils.ToRfc3339Date(_settings.ExampleValues.Date());
case SchemaFormat.DateTime:
return DateTimeUtils.ToRfc3339DateTime(DateTime.UtcNow);
return DateTimeUtils.ToRfc3339DateTime(_settings.ExampleValues.DateTime());
case SchemaFormat.Byte:
return new byte[] { 48, 49, 50 };
return _settings.ExampleValues.Bytes;
case SchemaFormat.Binary:
return "example-object";
return _settings.ExampleValues.Object;
default:
return "example-string";
return _settings.ExampleValues.String;
}
}
}

View File

@@ -12,6 +12,7 @@
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
@@ -24,14 +25,11 @@
<PackageReference Include="RamlToOpenApiConverter" Version="0.1.1" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Stef.Validation" Version="0.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Options\" />
</ItemGroup>
</Project>

View File

@@ -1,34 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using Microsoft.OpenApi.Writers;
using Newtonsoft.Json.Linq;
using RamlToOpenApiConverter;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Types;
using WireMock.Net.OpenApiParser.Utils;
using WireMock.Net.OpenApiParser.Mappers;
using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser
{
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels.
/// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser
{
private const int ArrayItems = 3;
private readonly OpenApiStreamReader _reader = new OpenApiStreamReader();
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile" />
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
@@ -42,248 +41,28 @@ namespace WireMock.Net.OpenApiParser
document = reader.Read(File.OpenRead(path), out diagnostic);
}
return FromDocument(document);
return FromDocument(document, settings);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream" />
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{
return FromDocument(_reader.Read(stream, out diagnostic));
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument" />
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument)
public IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
return MapPaths(openApiDocument.Paths);
return FromDocument(_reader.Read(stream, out diagnostic), settings);
}
private static IEnumerable<MappingModel> MapPaths(OpenApiPaths paths)
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument(OpenApiDocument, WireMockOpenApiParserSettings)" />
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings settings = null)
{
return paths.Select(p => MapPath(p.Key, p.Value)).SelectMany(x => x);
}
private static IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value));
}
private static MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation)
{
var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query);
var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path);
var response = operation.Responses.FirstOrDefault();
TryGetContent(response.Value?.Content, out OpenApiMediaType responseContent, out string responseContentType);
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example;
var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) : MapSchemaToObject(responseSchema);
if (int.TryParse(response.Key, out var httpStatusCode))
{
httpStatusCode = 200;
}
return new MappingModel
{
Guid = Guid.NewGuid(),
Request = new RequestModel
{
Methods = new[] { httpMethod },
Path = MapPathWithParameters(path, pathParameters),
Params = MapQueryParameters(queryParameters)
},
Response = new ResponseModel
{
StatusCode = httpStatusCode,
Headers = MapHeaders(responseContentType, response.Value?.Headers),
BodyAsJson = body
}
};
}
private static bool TryGetContent(IDictionary<string, OpenApiMediaType> contents, out OpenApiMediaType openApiMediaType, out string contentType)
{
openApiMediaType = null;
contentType = null;
if (contents == null || contents.Values.Count == 0)
{
return false;
}
if (contents.TryGetValue("application/json", out var content))
{
openApiMediaType = content;
contentType = "application/json";
}
else
{
var first = contents.FirstOrDefault();
openApiMediaType = first.Value;
contentType = first.Key;
}
return true;
}
private static object MapSchemaToObject(OpenApiSchema schema, string name = null)
{
if (schema == null)
{
return null;
}
switch (schema.GetSchemaType())
{
case SchemaType.Array:
var jArray = new JArray();
for (int i = 0; i < ArrayItems; i++)
{
if (schema.Items.Properties.Count > 0)
{
var arrayItem = new JObject();
foreach (var property in schema.Items.Properties)
{
var objectValue = MapSchemaToObject(property.Value, property.Key);
if (objectValue is JProperty jp)
{
arrayItem.Add(jp);
}
else
{
arrayItem.Add(new JProperty(property.Key, objectValue));
}
}
jArray.Add(arrayItem);
}
else
{
jArray.Add(MapSchemaToObject(schema.Items, name));
}
}
return jArray;
case SchemaType.Boolean:
case SchemaType.Integer:
case SchemaType.Number:
case SchemaType.String:
return ExampleValueGenerator.GetExampleValue(schema);
case SchemaType.Object:
var propertyAsJObject = new JObject();
foreach (var schemaProperty in schema.Properties)
{
string propertyName = schemaProperty.Key;
var openApiSchema = schemaProperty.Value;
if (openApiSchema.GetSchemaType() == SchemaType.Object)
{
var mapped = MapSchemaToObject(schemaProperty.Value, schemaProperty.Key);
if (mapped is JProperty jp)
{
propertyAsJObject.Add(jp);
}
}
else
{
bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x);
propertyAsJObject.Add(new JProperty(propertyName, ExampleValueGenerator.GetExampleValue(openApiSchema)));
}
}
return name != null ? new JProperty(name, propertyAsJObject) : (JToken)propertyAsJObject;
default:
return null;
}
}
private static string MapPathWithParameters(string path, IEnumerable<OpenApiParameter> parameters)
{
if (parameters == null)
{
return path;
}
string newPath = path;
foreach (var parameter in parameters)
{
newPath = newPath.Replace($"{{{parameter.Name}}}", ExampleValueGenerator.GetExampleValue(parameter.Schema).ToString());
}
return newPath;
}
private static JToken MapOpenApiAnyToJToken(IOpenApiAny any)
{
if (any == null)
{
return null;
}
using (var outputString = new StringWriter())
{
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
return JObject.Parse(outputString.ToString());
}
}
private static IDictionary<string, object> MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
{
var mappedHeaders = headers.ToDictionary(item => item.Key, item => ExampleValueGenerator.GetExampleValue(null));
if (!string.IsNullOrEmpty(responseContentType))
{
if (!mappedHeaders.ContainsKey("Content-Type"))
{
mappedHeaders.Add("Content-Type", responseContentType);
}
else
{
mappedHeaders["Content-Type"] = responseContentType;
}
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
}
private static IList<ParamModel> MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
{
var list = queryParameters
.Select(qp => new ParamModel
{
Name = qp.Name,
Matchers = new[]
{
new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetDefaultValueAsStringForSchemaType(qp.Schema)
}
}
})
.ToList();
return list.Any() ? list : null;
}
private static string GetDefaultValueAsStringForSchemaType(OpenApiSchema schema)
{
var value = ExampleValueGenerator.GetExampleValue(schema);
switch (value)
{
case string valueAsString:
return valueAsString;
default:
return value.ToString();
}
return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths);
}
}
}

View File

@@ -7,7 +7,7 @@ namespace WireMock.Http
{
internal static class HttpClientBuilder
{
public static HttpClient Build(IProxyAndRecordSettings settings)
public static HttpClient Build(IHttpClientSettings settings)
{
#if NETSTANDARD || NETCOREAPP3_1 || NET5_0
var handler = new HttpClientHandler

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Models;
using WireMock.Settings;
using WireMock.Transformers;
using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban;
using WireMock.Types;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Http
{
internal class WebhookSender
{
private const string ClientIp = "::1";
private readonly IWireMockServerSettings _settings;
public WebhookSender(IWireMockServerSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public Task<HttpResponseMessage> SendAsync([NotNull] HttpClient client, [NotNull] IWebhookRequest request, [NotNull] RequestMessage originalRequestMessage, [NotNull] ResponseMessage originalResponseMessage)
{
Check.NotNull(client, nameof(client));
Check.NotNull(request, nameof(request));
Check.NotNull(originalRequestMessage, nameof(originalRequestMessage));
Check.NotNull(originalResponseMessage, nameof(originalResponseMessage));
IBodyData bodyData;
IDictionary<string, WireMockList<string>> headers;
if (request.UseTransformer == true)
{
ITransformer responseMessageTransformer;
switch (request.TransformerType)
{
case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback);
responseMessageTransformer = new Transformer(factoryHandlebars);
break;
case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, request.TransformerType);
responseMessageTransformer = new Transformer(factoryDotLiquid);
break;
default:
throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported.");
}
(bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers);
}
else
{
bodyData = request.BodyData;
headers = request.Headers;
}
// Create RequestMessage
var requestMessage = new RequestMessage(
new UrlDetails(request.Url),
request.Method,
ClientIp,
bodyData,
headers?.ToDictionary(x => x.Key, x => x.Value.ToArray())
)
{
DateTime = DateTime.UtcNow
};
// Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, request.Url);
// Call the URL
return client.SendAsync(httpRequestMessage);
}
}
}

View File

@@ -2,6 +2,7 @@
using System;
using System.Threading.Tasks;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseProviders;
using WireMock.Settings;
@@ -93,12 +94,17 @@ namespace WireMock
/// </value>
bool LogMapping { get; }
/// <summary>
/// The Webhooks.
/// </summary>
IWebhook[] Webhooks { get; }
/// <summary>
/// ProvideResponseAsync
/// </summary>
/// <param name="requestMessage">The request message.</param>
/// <returns>The <see cref="ResponseMessage"/>.</returns>
Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage);
/// <returns>The <see cref="ResponseMessage"/> including a new (optional) <see cref="IMapping"/>.</returns>
Task<(ResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(RequestMessage requestMessage);
/// <summary>
/// Gets the RequestMatchResult based on the RequestMessage.

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseProviders;
using WireMock.Settings;
@@ -54,6 +55,9 @@ namespace WireMock
/// <inheritdoc cref="IMapping.LogMapping" />
public bool LogMapping => !(Provider is DynamicResponseProvider || Provider is DynamicAsyncResponseProvider);
/// <inheritdoc cref="IMapping.Webhooks" />
public IWebhook[] Webhooks { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary>
@@ -68,6 +72,7 @@ namespace WireMock
/// <param name="executionConditionState">State in which the current mapping can occur. [Optional]</param>
/// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param>
/// <param name="stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]</param>
/// <param name="webhooks">The Webhooks. [Optional]</param>
public Mapping(
Guid guid,
[CanBeNull] string title,
@@ -79,7 +84,8 @@ namespace WireMock
[CanBeNull] string scenario,
[CanBeNull] string executionConditionState,
[CanBeNull] string nextState,
[CanBeNull] int? stateTimes)
[CanBeNull] int? stateTimes,
[CanBeNull] IWebhook[] webhooks)
{
Guid = guid;
Title = title;
@@ -92,10 +98,11 @@ namespace WireMock
ExecutionConditionState = executionConditionState;
NextState = nextState;
StateTimes = stateTimes;
Webhooks = webhooks;
}
/// <inheritdoc cref="IMapping.ProvideResponseAsync" />
public async Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage)
public async Task<(ResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(RequestMessage requestMessage)
{
return await Provider.ProvideResponseAsync(requestMessage, Settings);
}

View File

@@ -1,15 +1,15 @@
namespace WireMock.Matchers
{
/// <summary>
/// IValueMatcher
/// </summary>
/// <seealso cref="IObjectMatcher" />
public interface IValueMatcher: IObjectMatcher
{
/// <summary>
/// Gets the value (can be a string or an object).
/// </summary>
/// <returns>Value</returns>
object Value { get; }
}
namespace WireMock.Matchers
{
/// <summary>
/// IValueMatcher
/// </summary>
/// <seealso cref="IObjectMatcher" />
public interface IValueMatcher : IObjectMatcher
{
/// <summary>
/// Gets the value (can be a string or an object).
/// </summary>
/// <returns>Value</returns>
object Value { get; }
}
}

View File

@@ -0,0 +1,52 @@
using System.Linq;
namespace WireMock.Matchers
{
/// <summary>
/// NotNullOrEmptyMatcher
/// </summary>
/// <seealso cref="IObjectMatcher" />
public class NotNullOrEmptyMatcher : IObjectMatcher
{
/// <inheritdoc cref="IMatcher.Name"/>
public string Name => "NotNullOrEmptyMatcher";
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="NotNullOrEmptyMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
public NotNullOrEmptyMatcher(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object input)
{
bool match;
switch (input)
{
case string @string:
match = !string.IsNullOrEmpty(@string);
break;
case byte[] bytes:
match = bytes != null && bytes.Any();
break;
default:
match = input != null;
break;
}
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match));
}
}
}

View File

@@ -1,113 +1,113 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// Regular Expression Matcher
/// </summary>
/// <inheritdoc cref="IStringMatcher"/>
/// <inheritdoc cref="IIgnoreCaseMatcher"/>
public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
{
private readonly string[] _patterns;
private readonly Regex[] _expressions;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RegexMatcher([NotNull, RegexPattern] string pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] string pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RegexMatcher([NotNull, RegexPattern] string[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] string[] patterns, bool ignoreCase = false, bool throwException = false)
{
Check.NotNull(patterns, nameof(patterns));
_patterns = patterns;
IgnoreCase = ignoreCase;
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline;
if (ignoreCase)
{
options |= RegexOptions.IgnoreCase;
}
_expressions = patterns.Select(p => new Regex(p, options)).ToArray();
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public virtual double IsMatch(string input)
{
double match = MatchScores.Mismatch;
if (input != null)
{
try
{
match = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)));
}
catch (Exception)
{
if (ThrowException)
{
throw;
}
}
using System;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using WireMock.Validation;
namespace WireMock.Matchers
{
/// <summary>
/// Regular Expression Matcher
/// </summary>
/// <inheritdoc cref="IStringMatcher"/>
/// <inheritdoc cref="IIgnoreCaseMatcher"/>
public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
{
private readonly string[] _patterns;
private readonly Regex[] _expressions;
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IMatcher.ThrowException"/>
public bool ThrowException { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RegexMatcher([NotNull, RegexPattern] string pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] string pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
public RegexMatcher([NotNull, RegexPattern] string[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RegexMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="throwException">Throw an exception when the internal matching fails because of invalid input.</param>
public RegexMatcher(MatchBehaviour matchBehaviour, [NotNull, RegexPattern] string[] patterns, bool ignoreCase = false, bool throwException = false)
{
Check.NotNull(patterns, nameof(patterns));
_patterns = patterns;
IgnoreCase = ignoreCase;
MatchBehaviour = matchBehaviour;
ThrowException = throwException;
RegexOptions options = RegexOptions.Compiled | RegexOptions.Multiline;
if (ignoreCase)
{
options |= RegexOptions.IgnoreCase;
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public virtual string[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc cref="IMatcher.Name"/>
public virtual string Name => "RegexMatcher";
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
public bool IgnoreCase { get; }
}
_expressions = patterns.Select(p => new Regex(p, options)).ToArray();
}
/// <inheritdoc cref="IStringMatcher.IsMatch"/>
public virtual double IsMatch(string input)
{
double match = MatchScores.Mismatch;
if (input != null)
{
try
{
match = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)));
}
catch (Exception)
{
if (ThrowException)
{
throw;
}
}
}
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public virtual string[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc cref="IMatcher.Name"/>
public virtual string Name => "RegexMatcher";
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
public bool IgnoreCase { get; }
}
}

View File

@@ -1,164 +1,211 @@
using JetBrains.Annotations;
using System;
using System.Linq;
using WireMock.Types;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request body matcher.
/// </summary>
public class RequestMessageBodyMatcher : IRequestMatcher
{
/// <summary>
/// The body function
/// </summary>
public Func<string, bool> Func { get; }
/// <summary>
/// The body data function for byte[]
/// </summary>
public Func<byte[], bool> DataFunc { get; }
/// <summary>
/// The body data function for json
/// </summary>
public Func<object, bool> JsonFunc { get; }
/// <summary>
/// The matchers.
/// </summary>
public IMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="body">The body.</param>
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] string body) : this(new[] { new WildcardMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="body">The body.</param>
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] byte[] body) : this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="body">The body.</param>
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] object body) : this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher([NotNull] Func<string, bool> func)
{
Check.NotNull(func, nameof(func));
Func = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher([NotNull] Func<byte[], bool> func)
{
Check.NotNull(func, nameof(func));
DataFunc = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher([NotNull] Func<object, bool> func)
{
Check.NotNull(func, nameof(func));
JsonFunc = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matchers">The matchers.</param>
public RequestMessageBodyMatcher([NotNull] params IMatcher[] matchers)
{
Check.NotNull(matchers, nameof(matchers));
Matchers = matchers;
}
/// <see cref="IRequestMatcher.GetMatchingScore"/>
public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = CalculateMatchScore(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
}
private double CalculateMatchScore(IRequestMessage requestMessage, IMatcher matcher)
{
// Check if the matcher is a IObjectMatcher
if (matcher is IObjectMatcher objectMatcher)
{
// If the body is a JSON object, try to match.
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json)
{
return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsJson);
}
// If the body is a byte array, try to match.
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Bytes)
{
return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes);
}
}
// Check if the matcher is a IStringMatcher
if (matcher is IStringMatcher stringMatcher)
{
// If the body is a Json or a String, use the BodyAsString to match on.
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json || requestMessage?.BodyData?.DetectedBodyType == BodyType.String)
{
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
}
}
return MatchScores.Mismatch;
}
private double CalculateMatchScore(IRequestMessage requestMessage)
{
if (Matchers != null && Matchers.Any())
{
return Matchers.Max(matcher => CalculateMatchScore(requestMessage, matcher));
}
if (Func != null)
{
return MatchScores.ToScore(Func(requestMessage?.BodyData?.BodyAsString));
}
if (JsonFunc != null)
{
return MatchScores.ToScore(JsonFunc(requestMessage?.BodyData?.BodyAsJson));
}
if (DataFunc != null)
{
return MatchScores.ToScore(DataFunc(requestMessage?.BodyData?.BodyAsBytes));
}
return MatchScores.Mismatch;
}
}
using JetBrains.Annotations;
using System;
using System.Linq;
using WireMock.Types;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Matchers.Request
{
/// <summary>
/// The request body matcher.
/// </summary>
public class RequestMessageBodyMatcher : IRequestMatcher
{
/// <summary>
/// The body function
/// </summary>
public Func<string, bool> Func { get; }
/// <summary>
/// The body data function for byte[]
/// </summary>
public Func<byte[], bool> DataFunc { get; }
/// <summary>
/// The body data function for json
/// </summary>
public Func<object, bool> JsonFunc { get; }
/// <summary>
/// The body data function for BodyData
/// </summary>
public Func<IBodyData, bool> BodyDataFunc { get; }
/// <summary>
/// The matchers.
/// </summary>
public IMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="body">The body.</param>
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] string body) : this(new[] { new WildcardMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="body">The body.</param>
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] byte[] body) : this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="body">The body.</param>
public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, [NotNull] object body) : this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast<IMatcher>().ToArray())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher([NotNull] Func<string, bool> func)
{
Check.NotNull(func, nameof(func));
Func = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher([NotNull] Func<byte[], bool> func)
{
Check.NotNull(func, nameof(func));
DataFunc = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher([NotNull] Func<object, bool> func)
{
Check.NotNull(func, nameof(func));
JsonFunc = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher([NotNull] Func<IBodyData, bool> func)
{
Check.NotNull(func, nameof(func));
BodyDataFunc = func;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="matchers">The matchers.</param>
public RequestMessageBodyMatcher([NotNull] params IMatcher[] matchers)
{
Check.NotNull(matchers, nameof(matchers));
Matchers = matchers;
}
/// <see cref="IRequestMatcher.GetMatchingScore"/>
public double GetMatchingScore(IRequestMessage requestMessage, RequestMatchResult requestMatchResult)
{
double score = CalculateMatchScore(requestMessage);
return requestMatchResult.AddScore(GetType(), score);
}
private double CalculateMatchScore(IRequestMessage requestMessage, IMatcher matcher)
{
if (matcher is NotNullOrEmptyMatcher notNullOrEmptyMatcher)
{
switch (requestMessage?.BodyData?.DetectedBodyType)
{
case BodyType.Json:
case BodyType.String:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
case BodyType.Bytes:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes);
default:
return MatchScores.Mismatch;
}
}
if (matcher is ExactObjectMatcher exactObjectMatcher)
{
// If the body is a byte array, try to match.
var detectedBodyType = requestMessage?.BodyData?.DetectedBodyType;
if (detectedBodyType == BodyType.Bytes || detectedBodyType == BodyType.String)
{
return exactObjectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes);
}
}
// Check if the matcher is a IObjectMatcher
if (matcher is IObjectMatcher objectMatcher)
{
// If the body is a JSON object, try to match.
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json)
{
return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsJson);
}
// If the body is a byte array, try to match.
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Bytes)
{
return objectMatcher.IsMatch(requestMessage.BodyData.BodyAsBytes);
}
}
// Check if the matcher is a IStringMatcher
if (matcher is IStringMatcher stringMatcher)
{
// If the body is a Json or a String, use the BodyAsString to match on.
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json || requestMessage?.BodyData?.DetectedBodyType == BodyType.String)
{
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
}
}
return MatchScores.Mismatch;
}
private double CalculateMatchScore(IRequestMessage requestMessage)
{
if (Matchers != null && Matchers.Any())
{
return Matchers.Max(matcher => CalculateMatchScore(requestMessage, matcher));
}
if (Func != null)
{
return MatchScores.ToScore(Func(requestMessage?.BodyData?.BodyAsString));
}
if (JsonFunc != null)
{
return MatchScores.ToScore(JsonFunc(requestMessage?.BodyData?.BodyAsJson));
}
if (DataFunc != null)
{
return MatchScores.ToScore(DataFunc(requestMessage?.BodyData?.BodyAsBytes));
}
if (BodyDataFunc != null)
{
return MatchScores.ToScore(BodyDataFunc(requestMessage?.BodyData));
}
return MatchScores.Mismatch;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace WireMock.Models
{
/// <summary>
/// Webhook
/// </summary>
public class Webhook : IWebhook
{
/// <inheritdoc cref="IWebhook.Request"/>
public IWebhookRequest Request { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Models
{
/// <summary>
/// WebhookRequest
/// </summary>
public class WebhookRequest : IWebhookRequest
{
/// <inheritdoc cref="IWebhookRequest.Url"/>
public string Url { get; set; }
/// <inheritdoc cref="IWebhookRequest.Method"/>
public string Method { get; set; }
/// <inheritdoc cref="IWebhookRequest.Headers"/>
public IDictionary<string, WireMockList<string>> Headers { get; set; }
/// <inheritdoc cref="IWebhookRequest.BodyData"/>
public IBodyData BodyData { get; set; }
/// <inheritdoc cref="IWebhookRequest.UseTransformer"/>
public bool? UseTransformer { get; set; }
/// <inheritdoc cref="IWebhookRequest.TransformerType"/>
public TransformerType TransformerType { get; set; }
}
}

View File

@@ -24,7 +24,7 @@ namespace WireMock.Owin
{
if (urlDetail.IsHttps)
{
kestrelOptions.Listen(System.Net.IPAddress.Any, urlDetail.Port, listenOptions =>
kestrelOptions.ListenAnyIP(urlDetail.Port, listenOptions =>
{
if (wireMockMiddlewareOptions.CustomCertificateDefined)
{
@@ -45,7 +45,7 @@ namespace WireMock.Owin
}
else
{
kestrelOptions.Listen(System.Net.IPAddress.Any, urlDetail.Port);
kestrelOptions.ListenAnyIP(urlDetail.Port);
}
}
}

View File

@@ -66,6 +66,8 @@ namespace WireMock.Owin
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
_wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services);
})
.Configure(appBuilder =>
{
@@ -106,7 +108,7 @@ namespace WireMock.Owin
foreach (string address in addresses)
{
Urls.Add(address.Replace("0.0.0.0", "localhost"));
Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost"));
PortUtils.TryExtract(address, out bool isHttps, out string protocol, out string host, out int port);
Ports.Add(port);

View File

@@ -8,6 +8,7 @@ using WireMock.Util;
using Owin;
#else
using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder;
using Microsoft.Extensions.DependencyInjection;
#endif
namespace WireMock.Owin
@@ -36,6 +37,10 @@ namespace WireMock.Owin
Action<IAppBuilder> PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
Action<IServiceCollection> AdditionalServiceRegistration { get; set; }
#endif
IFileSystemHandler FileSystemHandler { get; set; }
bool? AllowBodyForAllHttpMethods { get; set; }

View File

@@ -53,7 +53,7 @@ namespace WireMock.Owin.Mappers
}
}
BodyData body = null;
IBodyData body = null;
if (request.Body != null && BodyParser.ShouldParseBody(method, options.AllowBodyForAllHttpMethods == true))
{
var bodyParserSettings = new BodyParserSettings

View File

@@ -154,7 +154,13 @@ namespace WireMock.Owin.Mappers
private static void SetResponseHeaders(ResponseMessage responseMessage, IResponse response)
{
// Force setting the Date header (#577)
AppendResponseHeader(response, HttpKnownHeaderNames.Date, new[] { string.Format(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, DateTime.Now) });
AppendResponseHeader(
response,
HttpKnownHeaderNames.Date,
new[]
{
DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)
});
// Set other headers
foreach (var item in responseMessage.Headers)

View File

@@ -9,6 +9,7 @@ using WireMock.Serialization;
using WireMock.Types;
using WireMock.Validation;
using WireMock.ResponseBuilders;
using WireMock.Settings;
#if !USE_ASPNETCORE
using Microsoft.Owin;
using IContext = Microsoft.Owin.IOwinContext;
@@ -128,15 +129,16 @@ namespace WireMock.Owin
await Task.Delay(_options.RequestProcessingDelay.Value);
}
response = await targetMapping.ProvideResponseAsync(request);
var (theResponse, theOptionalNewMapping) = await targetMapping.ProvideResponseAsync(request);
response = theResponse;
var responseBuilder = targetMapping.Provider as Response;
if (!targetMapping.IsAdminInterface)
if (!targetMapping.IsAdminInterface && theOptionalNewMapping != null)
{
if (responseBuilder?.ProxyAndRecordSettings?.SaveMapping == true || targetMapping?.Settings?.ProxyAndRecordSettings?.SaveMapping == true)
{
_options.Mappings.TryAdd(targetMapping.Guid, targetMapping);
_options.Mappings.TryAdd(theOptionalNewMapping.Guid, theOptionalNewMapping);
}
if (responseBuilder?.ProxyAndRecordSettings?.SaveMappingToFile == true || targetMapping?.Settings?.ProxyAndRecordSettings?.SaveMappingToFile == true)
@@ -145,7 +147,7 @@ namespace WireMock.Owin
var mappingConverter = new MappingConverter(matcherMapper);
var mappingToFileSaver = new MappingToFileSaver(targetMapping.Settings, mappingConverter);
mappingToFileSaver.SaveMappingToFile(targetMapping);
mappingToFileSaver.SaveMappingToFile(theOptionalNewMapping);
}
}
@@ -153,6 +155,11 @@ namespace WireMock.Owin
{
UpdateScenarioState(targetMapping);
}
if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0)
{
await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false);
}
}
catch (Exception ex)
{
@@ -184,6 +191,24 @@ namespace WireMock.Owin
await CompletedTask;
}
private async Task SendToWebhooksAsync(IMapping mapping, RequestMessage request, ResponseMessage response)
{
for (int index = 0; index < mapping.Webhooks.Length; index++)
{
var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings());
var webhookSender = new WebhookSender(mapping.Settings);
try
{
await webhookSender.SendAsync(httpClientForWebhook, mapping.Webhooks[index].Request, request, response).ConfigureAwait(false);
}
catch (Exception ex)
{
_options.Logger.Error($"Sending message to Webhook [{index}] from Mapping '{mapping.Guid}' failed. Exception: {ex}");
}
}
}
private void UpdateScenarioState(IMapping mapping)
{
var scenario = _options.Scenarios[mapping.Scenario];

View File

@@ -8,6 +8,7 @@ using WireMock.Util;
using Owin;
#else
using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder;
using Microsoft.Extensions.DependencyInjection;
#endif
namespace WireMock.Owin
@@ -36,6 +37,10 @@ namespace WireMock.Owin
public Action<IAppBuilder> PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
public Action<IServiceCollection> AdditionalServiceRegistration { get; set; }
#endif
/// <inheritdoc cref="IWireMockMiddlewareOptions.FileSystemHandler"/>
public IFileSystemHandler FileSystemHandler { get; set; }

View File

@@ -1,112 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using HandlebarsDotNet.Helpers.Validation;
using JetBrains.Annotations;
using WireMock.Http;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using JetBrains.Annotations;
using WireMock.Http;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Proxy
{
internal class ProxyHelper
{
private readonly IWireMockServerSettings _settings;
public ProxyHelper([NotNull] IWireMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
_settings = settings;
}
public async Task<(ResponseMessage ResponseMessage, IMapping Mapping)> SendAsync(
[NotNull] IProxyAndRecordSettings proxyAndRecordSettings,
[NotNull] HttpClient client,
[NotNull] RequestMessage requestMessage,
[NotNull] string url)
{
Check.NotNull(client, nameof(client));
Check.NotNull(requestMessage, nameof(requestMessage));
Check.NotNull(url, nameof(url));
var originalUri = new Uri(requestMessage.Url);
var requiredUri = new Uri(url);
// Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, url);
// Call the URL
var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead);
// Create ResponseMessage
bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false);
bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.GetValueOrDefault(false);
var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate);
IMapping mapping = null;
if (HttpStatusRangeParser.IsMatch(proxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) &&
(proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile))
{
mapping = ToMapping(proxyAndRecordSettings, requestMessage, responseMessage);
}
return (responseMessage, mapping);
}
private IMapping ToMapping(IProxyAndRecordSettings proxyAndRecordSettings, RequestMessage requestMessage, ResponseMessage responseMessage)
{
string[] excludedHeaders = proxyAndRecordSettings.ExcludedHeaders ?? new string[] { };
string[] excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[] { };
var request = Request.Create();
request.WithPath(requestMessage.Path);
request.UsingMethod(requestMessage.Method);
requestMessage.Query.Loop((key, value) => request.WithParam(key, false, value.ToArray()));
requestMessage.Cookies.Loop((key, value) =>
{
if (!excludedCookies.Contains(key, StringComparer.OrdinalIgnoreCase))
{
request.WithCookie(key, value);
}
});
var allExcludedHeaders = new List<string>(excludedHeaders) { "Cookie" };
requestMessage.Headers.Loop((key, value) =>
{
if (!allExcludedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase))
{
request.WithHeader(key, value.ToArray());
}
});
bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true;
switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.Json:
request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson, true, throwExceptionWhenMatcherFails));
break;
case BodyType.String:
request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, throwExceptionWhenMatcherFails, requestMessage.BodyData.BodyAsString));
break;
case BodyType.Bytes:
request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes, throwExceptionWhenMatcherFails));
break;
}
var response = Response.Create(responseMessage);
return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null, null);
}
}
namespace WireMock.Proxy
{
internal class ProxyHelper
{
private readonly IWireMockServerSettings _settings;
public ProxyHelper([NotNull] IWireMockServerSettings settings)
{
Check.NotNull(settings, nameof(settings));
_settings = settings;
}
public async Task<(ResponseMessage Message, IMapping Mapping)> SendAsync(
[NotNull] IProxyAndRecordSettings proxyAndRecordSettings,
[NotNull] HttpClient client,
[NotNull] RequestMessage requestMessage,
[NotNull] string url)
{
Check.NotNull(client, nameof(client));
Check.NotNull(requestMessage, nameof(requestMessage));
Check.NotNull(url, nameof(url));
var originalUri = new Uri(requestMessage.Url);
var requiredUri = new Uri(url);
// Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, url);
// Call the URL
var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead);
// Create ResponseMessage
bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false);
bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.GetValueOrDefault(false);
var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate);
IMapping mapping = null;
if (HttpStatusRangeParser.IsMatch(proxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) &&
(proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile))
{
mapping = ToMapping(proxyAndRecordSettings, requestMessage, responseMessage);
}
return (responseMessage, mapping);
}
private IMapping ToMapping(IProxyAndRecordSettings proxyAndRecordSettings, RequestMessage requestMessage, ResponseMessage responseMessage)
{
string[] excludedHeaders = proxyAndRecordSettings.ExcludedHeaders ?? new string[] { };
string[] excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[] { };
var request = Request.Create();
request.WithPath(requestMessage.Path);
request.UsingMethod(requestMessage.Method);
requestMessage.Query.Loop((key, value) => request.WithParam(key, false, value.ToArray()));
requestMessage.Cookies.Loop((key, value) =>
{
if (!excludedCookies.Contains(key, StringComparer.OrdinalIgnoreCase))
{
request.WithCookie(key, value);
}
});
var allExcludedHeaders = new List<string>(excludedHeaders) { "Cookie" };
requestMessage.Headers.Loop((key, value) =>
{
if (!allExcludedHeaders.Contains(key, StringComparer.OrdinalIgnoreCase))
{
request.WithHeader(key, value.ToArray());
}
});
bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true;
switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.Json:
request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson, true, throwExceptionWhenMatcherFails));
break;
case BodyType.String:
request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, throwExceptionWhenMatcherFails, requestMessage.BodyData.BodyAsString));
break;
case BodyType.Bytes:
request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes, throwExceptionWhenMatcherFails));
break;
}
var response = Response.Create(responseMessage);
return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null, null, null);
}
}
}

View File

@@ -2,7 +2,8 @@
using System;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Util;
namespace WireMock.RequestBuilders
{
/// <summary>
@@ -63,10 +64,17 @@ namespace WireMock.RequestBuilders
IRequestBuilder WithBody([NotNull] Func<byte[], bool> func);
/// <summary>
/// WithBody: func (object)
/// WithBody: func (json object)
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody([NotNull] Func<object, bool> func);
/// <summary>
/// WithBody: func (BodyData object)
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody([NotNull] Func<IBodyData, bool> func);
}
}

View File

@@ -3,6 +3,7 @@
using System;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.RequestBuilders
@@ -71,5 +72,14 @@ namespace WireMock.RequestBuilders
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{IBodyData, bool})"/>
public IRequestBuilder WithBody(Func<IBodyData, bool> func)
{
Check.NotNull(func, nameof(func));
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
}
}

View File

@@ -101,7 +101,7 @@ namespace WireMock
/// <param name="bodyData">The BodyData.</param>
/// <param name="headers">The headers.</param>
/// <param name="cookies">The cookies.</param>
public RequestMessage([NotNull] UrlDetails urlDetails, [NotNull] string method, [NotNull] string clientIP, [CanBeNull] BodyData bodyData = null, [CanBeNull] IDictionary<string, string[]> headers = null, [CanBeNull] IDictionary<string, string> cookies = null)
public RequestMessage([NotNull] UrlDetails urlDetails, [NotNull] string method, [NotNull] string clientIP, [CanBeNull] IBodyData bodyData = null, [CanBeNull] IDictionary<string, string[]> headers = null, [CanBeNull] IDictionary<string, string> cookies = null)
{
Check.NotNull(urlDetails, nameof(urlDetails));
Check.NotNull(method, nameof(method));

View File

@@ -1,7 +1,8 @@
using JetBrains.Annotations;
using System;
using System.Text;
using System.Threading.Tasks;
namespace WireMock.ResponseBuilders
{
/// <summary>
@@ -27,6 +28,15 @@ namespace WireMock.ResponseBuilders
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody([NotNull] Func<RequestMessage, string> bodyFactory, [CanBeNull] string destination = BodyDestinationFormat.SameAsSource, [CanBeNull] Encoding encoding = null);
/// <summary>
/// WithBody : Create a ... response based on a callback function.
/// </summary>
/// <param name="bodyFactory">The async delegate to build the body.</param>
/// <param name="destination">The Body Destination format (SameAsSource, String or Bytes).</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody([NotNull] Func<RequestMessage, Task<string>> bodyFactory, [CanBeNull] string destination = BodyDestinationFormat.SameAsSource, [CanBeNull] Encoding encoding = null);
/// <summary>
/// WithBody : Create a ... response based on a bytearray.
/// </summary>

View File

@@ -167,7 +167,7 @@ namespace WireMock.ResponseBuilders
{
Check.NotNull(bodyFactory, nameof(bodyFactory));
return WithCallbackInternal(false, req => new ResponseMessage
return WithCallbackInternal(true, req => new ResponseMessage
{
BodyData = new BodyData
{
@@ -178,6 +178,22 @@ namespace WireMock.ResponseBuilders
});
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBody(Func{RequestMessage, Task{string}}, string, Encoding)"/>
public IResponseBuilder WithBody(Func<RequestMessage, Task<string>> bodyFactory, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null)
{
Check.NotNull(bodyFactory, nameof(bodyFactory));
return WithCallbackInternal(true, async req => new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = await bodyFactory(req),
Encoding = encoding ?? Encoding.UTF8
}
});
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBody(byte[], string, Encoding)"/>
public IResponseBuilder WithBody(byte[] body, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null)
{
@@ -319,7 +335,7 @@ namespace WireMock.ResponseBuilders
}
/// <inheritdoc cref="IResponseProvider.ProvideResponseAsync(RequestMessage, IWireMockServerSettings)"/>
public async Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings)
public async Task<(ResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings)
{
Check.NotNull(requestMessage, nameof(requestMessage));
Check.NotNull(settings, nameof(settings));
@@ -345,18 +361,16 @@ namespace WireMock.ResponseBuilders
var proxyHelper = new ProxyHelper(settings);
var (proxyResponseMessage, _) = await proxyHelper.SendAsync(
return await proxyHelper.SendAsync(
ProxyAndRecordSettings,
_httpClientForProxy,
requestMessage,
requestMessage.ProxyUrl
);
return proxyResponseMessage;
}
ResponseMessage responseMessage;
if (Callback == null && CallbackAsync == null)
if (!WithCallbackUsed)
{
responseMessage = ResponseMessage;
}
@@ -371,16 +385,16 @@ namespace WireMock.ResponseBuilders
responseMessage = await CallbackAsync(requestMessage);
}
if (!WithCallbackUsed)
// Copy StatusCode from ResponseMessage (if defined)
if (ResponseMessage.StatusCode != null)
{
// Copy StatusCode from ResponseMessage
responseMessage.StatusCode = ResponseMessage.StatusCode;
}
// Copy Headers from ResponseMessage (if defined)
if (ResponseMessage.Headers != null)
{
responseMessage.Headers = ResponseMessage.Headers;
}
// Copy Headers from ResponseMessage (if defined)
if (ResponseMessage.Headers?.Count > 0)
{
responseMessage.Headers = ResponseMessage.Headers;
}
}
@@ -404,7 +418,7 @@ namespace WireMock.ResponseBuilders
throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported.");
}
return responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile);
return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile), null);
}
if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true)
@@ -412,7 +426,7 @@ namespace WireMock.ResponseBuilders
ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile);
}
return responseMessage;
return (responseMessage, null);
}
}
}

View File

@@ -13,9 +13,9 @@ namespace WireMock.ResponseProviders
_responseMessageFunc = responseMessageFunc;
}
public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings)
public async Task<(ResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings)
{
return _responseMessageFunc(requestMessage);
return (await _responseMessageFunc(requestMessage), null);
}
}
}

View File

@@ -13,9 +13,10 @@ namespace WireMock.ResponseProviders
_responseMessageFunc = responseMessageFunc;
}
public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings)
public Task<(ResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings)
{
return Task.FromResult(_responseMessageFunc(requestMessage));
(ResponseMessage responseMessage, IMapping mapping) result = (_responseMessageFunc(requestMessage), null);
return Task.FromResult(result);
}
}
}

View File

@@ -16,7 +16,7 @@ namespace WireMock.ResponseProviders
/// </summary>
/// <param name="requestMessage">The request.</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <returns>The <see cref="ResponseMessage"/>.</returns>
Task<ResponseMessage> ProvideResponseAsync([NotNull] RequestMessage requestMessage, [NotNull] IWireMockServerSettings settings);
/// <returns>The <see cref="ResponseMessage"/> including a new (optional) <see cref="IMapping"/>.</returns>
Task<(ResponseMessage Message, IMapping Mapping)> ProvideResponseAsync([NotNull] RequestMessage requestMessage, [NotNull] IWireMockServerSettings settings);
}
}

View File

@@ -15,9 +15,9 @@ namespace WireMock.ResponseProviders
_settings = settings;
}
public Task<ResponseMessage> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings)
public async Task<(ResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(RequestMessage requestMessage, IWireMockServerSettings settings)
{
return _responseMessageFunc(requestMessage, _settings);
return (await _responseMessageFunc(requestMessage, _settings), null);
}
}
}

View File

@@ -87,6 +87,15 @@ namespace WireMock.Serialization
}
};
if (mapping.Webhooks?.Length == 1)
{
mappingModel.Webhook = WebhookMapper.Map(mapping.Webhooks[0]);
}
else if (mapping.Webhooks?.Length > 1)
{
mappingModel.Webhooks = mapping.Webhooks.Select(WebhookMapper.Map).ToArray();
}
if (bodyMatcher?.Matchers != null)
{
mappingModel.Request.Body = new BodyModel();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@@ -43,7 +43,10 @@ namespace WireMock.Serialization
bool throwExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails == true;
switch (matcherName)
{
{
case "NotNullOrEmptyMatcher":
return new NotNullOrEmptyMatcher(matchBehaviour);
case "CSharpCodeMatcher":
if (_settings.AllowCSharpCodeMatcher == true)
{

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Admin.Mappings;
using WireMock.Http;
using WireMock.Models;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Serialization
{
internal static class WebhookMapper
{
public static IWebhook Map(WebhookModel model)
{
var webhook = new Webhook
{
Request = new WebhookRequest
{
Url = model.Request.Url,
Method = model.Request.Method,
Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList<string>(x.Value)) ?? new Dictionary<string, WireMockList<string>>()
}
};
if (model.Request.UseTransformer == true)
{
webhook.Request.UseTransformer = true;
if (!Enum.TryParse<TransformerType>(model.Request.TransformerType, out var transformerType))
{
transformerType = TransformerType.Handlebars;
}
webhook.Request.TransformerType = transformerType;
}
IEnumerable<string> contentTypeHeader = null;
if (webhook.Request.Headers.Any(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)))
{
contentTypeHeader = webhook.Request.Headers.First(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)).Value;
}
if (model.Request.Body != null)
{
webhook.Request.BodyData = new BodyData
{
BodyAsString = model.Request.Body,
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault())
};
}
else if (model.Request.BodyAsJson != null)
{
webhook.Request.BodyData = new BodyData
{
BodyAsJson = model.Request.BodyAsJson,
DetectedBodyType = BodyType.Json,
DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault())
};
}
return webhook;
}
public static WebhookModel Map(IWebhook webhook)
{
if (webhook?.Request == null)
{
return null;
}
var model = new WebhookModel
{
Request = new WebhookRequestModel
{
Url = webhook.Request.Url,
Method = webhook.Request.Method,
Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()),
UseTransformer = webhook.Request.UseTransformer,
TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null
}
};
if (webhook.Request.BodyData != null)
{
switch (webhook.Request.BodyData.DetectedBodyType)
{
case BodyType.String:
model.Request.Body = webhook.Request.BodyData.BodyAsString;
break;
case BodyType.Json:
model.Request.BodyAsJson = webhook.Request.BodyData.BodyAsJson;
break;
default:
break;
}
}
return model;
}
}
}

View File

@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Models;
using WireMock.ResponseProviders;
using WireMock.Types;
namespace WireMock.Server
{
@@ -97,5 +101,50 @@ namespace WireMock.Server
/// <param name="times">The number of times this match should be matched before the state will be changed to the specified one. Default value is 1.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WillSetStateTo(int state, int? times = 1);
/// <summary>
/// Add (multiple) Webhook(s) to call after the response has been generated.
/// </summary>
/// <param name="webhooks">The Webhooks</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook(params IWebhook[] webhooks);
/// <summary>
/// Add a Webhook to call after the response has been generated.
/// </summary>
/// <param name="url">The Webhook Url</param>
/// <param name="method">The method to use. [optional]</param>
/// <param name="headers">The Headers to send. [optional]</param>
/// <param name="body">The body (as string) to send. [optional]</param>
/// <param name="useTransformer">Use Transformer. [optional]</param>
/// <param name="transformerType">The transformer type. [optional]</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook(
[NotNull] string url,
[CanBeNull] string method = "post",
[CanBeNull] IDictionary<string, WireMockList<string>> headers = null,
[CanBeNull] string body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars
);
/// <summary>
/// Add a Webhook to call after the response has been generated.
/// </summary>
/// <param name="url">The Webhook Url</param>
/// <param name="method">The method to use. [optional]</param>
/// <param name="headers">The Headers to send. [optional]</param>
/// <param name="body">The body (as json) to send. [optional]</param>
/// <param name="useTransformer">Use Transformer. [optional]</param>
/// <param name="transformerType">The transformer type. [optional]</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithWebhook(
[NotNull] string url,
[CanBeNull] string method = "post",
[CanBeNull] IDictionary<string, WireMockList<string>> headers = null,
[CanBeNull] object body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars
);
}
}

View File

@@ -1,144 +1,230 @@
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using WireMock.Matchers.Request;
using WireMock.ResponseProviders;
using WireMock.Settings;
namespace WireMock.Server
{
/// <summary>
/// The respond with a provider.
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
private int _priority;
private string _title;
private string _path;
private string _executionConditionState;
private string _nextState;
private string _scenario;
private int _timesInSameState = 1;
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly IWireMockServerSettings _settings;
private readonly bool _saveToFile;
public Guid Guid { get; private set; } = Guid.NewGuid();
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
/// <param name="registrationCallback">The registration callback.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher, IWireMockServerSettings settings, bool saveToFile = false)
{
_registrationCallback = registrationCallback;
_requestMatcher = requestMatcher;
_settings = settings;
_saveToFile = saveToFile;
}
/// <summary>
/// The respond with.
/// </summary>
/// <param name="provider">The provider.</param>
public void RespondWith(IResponseProvider provider)
{
_registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState), _saveToFile);
}
/// <see cref="IRespondWithAProvider.WithGuid(string)"/>
public IRespondWithAProvider WithGuid(string guid)
{
return WithGuid(Guid.Parse(guid));
}
/// <see cref="IRespondWithAProvider.WithGuid(Guid)"/>
public IRespondWithAProvider WithGuid(Guid guid)
{
Guid = guid;
return this;
}
/// <see cref="IRespondWithAProvider.WithTitle"/>
public IRespondWithAProvider WithTitle(string title)
{
_title = title;
return this;
}
/// <see cref="IRespondWithAProvider.WithPath"/>
public IRespondWithAProvider WithPath(string path)
{
_path = path;
return this;
}
/// <see cref="IRespondWithAProvider.AtPriority"/>
public IRespondWithAProvider AtPriority(int priority)
{
_priority = priority;
return this;
}
/// <see cref="IRespondWithAProvider.InScenario(string)"/>
public IRespondWithAProvider InScenario(string scenario)
{
_scenario = scenario;
return this;
}
/// <see cref="IRespondWithAProvider.InScenario(int)"/>
public IRespondWithAProvider InScenario(int scenario)
{
return InScenario(scenario.ToString());
}
/// <see cref="IRespondWithAProvider.WhenStateIs(string)"/>
public IRespondWithAProvider WhenStateIs(string state)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set state condition when no scenario is defined.");
}
_executionConditionState = state;
return this;
}
/// <see cref="IRespondWithAProvider.WhenStateIs(int)"/>
public IRespondWithAProvider WhenStateIs(int state)
{
return WhenStateIs(state.ToString());
}
/// <see cref="IRespondWithAProvider.WillSetStateTo(string, int?)"/>
public IRespondWithAProvider WillSetStateTo(string state, int? times = 1)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set next state when no scenario is defined.");
}
_nextState = state;
_timesInSameState = times ?? 1;
return this;
}
/// <see cref="IRespondWithAProvider.WillSetStateTo(int, int?)"/>
public IRespondWithAProvider WillSetStateTo(int state, int? times = 1)
{
return WillSetStateTo(state.ToString(), times);
}
}
// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License.
// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.ResponseProviders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Server
{
/// <summary>
/// The respond with a provider.
/// </summary>
internal class RespondWithAProvider : IRespondWithAProvider
{
private int _priority;
private string _title;
private string _path;
private string _executionConditionState;
private string _nextState;
private string _scenario;
private int _timesInSameState = 1;
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly IWireMockServerSettings _settings;
private readonly bool _saveToFile;
public Guid Guid { get; private set; } = Guid.NewGuid();
public IWebhook[] Webhooks { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RespondWithAProvider"/> class.
/// </summary>
/// <param name="registrationCallback">The registration callback.</param>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="settings">The WireMockServerSettings.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
public RespondWithAProvider(RegistrationCallback registrationCallback, IRequestMatcher requestMatcher, IWireMockServerSettings settings, bool saveToFile = false)
{
_registrationCallback = registrationCallback;
_requestMatcher = requestMatcher;
_settings = settings;
_saveToFile = saveToFile;
}
/// <summary>
/// The respond with.
/// </summary>
/// <param name="provider">The provider.</param>
public void RespondWith(IResponseProvider provider)
{
_registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState, Webhooks), _saveToFile);
}
/// <see cref="IRespondWithAProvider.WithGuid(string)"/>
public IRespondWithAProvider WithGuid(string guid)
{
return WithGuid(Guid.Parse(guid));
}
/// <see cref="IRespondWithAProvider.WithGuid(Guid)"/>
public IRespondWithAProvider WithGuid(Guid guid)
{
Guid = guid;
return this;
}
/// <see cref="IRespondWithAProvider.WithTitle"/>
public IRespondWithAProvider WithTitle(string title)
{
_title = title;
return this;
}
/// <see cref="IRespondWithAProvider.WithPath"/>
public IRespondWithAProvider WithPath(string path)
{
_path = path;
return this;
}
/// <see cref="IRespondWithAProvider.AtPriority"/>
public IRespondWithAProvider AtPriority(int priority)
{
_priority = priority;
return this;
}
/// <see cref="IRespondWithAProvider.InScenario(string)"/>
public IRespondWithAProvider InScenario(string scenario)
{
_scenario = scenario;
return this;
}
/// <see cref="IRespondWithAProvider.InScenario(int)"/>
public IRespondWithAProvider InScenario(int scenario)
{
return InScenario(scenario.ToString());
}
/// <see cref="IRespondWithAProvider.WhenStateIs(string)"/>
public IRespondWithAProvider WhenStateIs(string state)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set state condition when no scenario is defined.");
}
_executionConditionState = state;
return this;
}
/// <see cref="IRespondWithAProvider.WhenStateIs(int)"/>
public IRespondWithAProvider WhenStateIs(int state)
{
return WhenStateIs(state.ToString());
}
/// <see cref="IRespondWithAProvider.WillSetStateTo(string, int?)"/>
public IRespondWithAProvider WillSetStateTo(string state, int? times = 1)
{
if (string.IsNullOrEmpty(_scenario))
{
throw new NotSupportedException("Unable to set next state when no scenario is defined.");
}
_nextState = state;
_timesInSameState = times ?? 1;
return this;
}
/// <see cref="IRespondWithAProvider.WillSetStateTo(int, int?)"/>
public IRespondWithAProvider WillSetStateTo(int state, int? times = 1)
{
return WillSetStateTo(state.ToString(), times);
}
/// <see cref="IRespondWithAProvider.WithWebhook(IWebhook[])"/>
public IRespondWithAProvider WithWebhook(params IWebhook[] webhooks)
{
Check.HasNoNulls(webhooks, nameof(webhooks));
Webhooks = webhooks;
return this;
}
/// <see cref="IRespondWithAProvider.WithWebhook(string, string, IDictionary{string, WireMockList{string}}, string, bool, TransformerType)"/>
public IRespondWithAProvider WithWebhook(
[NotNull] string url,
[CanBeNull] string method = "post",
[CanBeNull] IDictionary<string, WireMockList<string>> headers = null,
[CanBeNull] string body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars)
{
Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) };
if (body != null)
{
Webhooks[0].Request.BodyData = new BodyData
{
BodyAsString = body,
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = BodyType.String
};
}
return this;
}
/// <see cref="IRespondWithAProvider.WithWebhook(string, string, IDictionary{string, WireMockList{string}}, object, bool, TransformerType)"/>
public IRespondWithAProvider WithWebhook(
[NotNull] string url,
[CanBeNull] string method = "post",
[CanBeNull] IDictionary<string, WireMockList<string>> headers = null,
[CanBeNull] object body = null,
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars)
{
Webhooks = new[] { InitWebhook(url, method, headers, useTransformer, transformerType) };
if (body != null)
{
Webhooks[0].Request.BodyData = new BodyData
{
BodyAsJson = body,
DetectedBodyType = BodyType.Json,
DetectedBodyTypeFromContentType = BodyType.Json
};
}
return this;
}
private IWebhook InitWebhook(
string url,
string method,
IDictionary<string, WireMockList<string>> headers,
bool useTransformer,
TransformerType transformerType)
{
return new Webhook
{
Request = new WebhookRequest
{
Url = url,
Method = method ?? "post",
Headers = headers,
UseTransformer = useTransformer,
TransformerType = transformerType
}
};
}
}
}

View File

@@ -470,6 +470,16 @@ namespace WireMock.Server
respondProvider = respondProvider.WillSetStateTo(mappingModel.SetStateTo);
}
if (mappingModel.Webhook != null)
{
respondProvider = respondProvider.WithWebhook(WebhookMapper.Map(mappingModel.Webhook));
}
else if (mappingModel.Webhooks?.Length > 1)
{
var webhooks = mappingModel.Webhooks.Select(WebhookMapper.Map).ToArray();
respondProvider = respondProvider.WithWebhook(webhooks);
}
respondProvider.RespondWith(responseBuilder);
return respondProvider.Guid;

View File

@@ -242,6 +242,7 @@ namespace WireMock.Server
_mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter);
#if USE_ASPNETCORE
_options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration;
_httpServer = new AspNetCoreSelfHost(_options, urlOptions);
#else
_httpServer = new OwinSelfHost(_options, urlOptions);

View File

@@ -0,0 +1,17 @@
namespace WireMock.Settings
{
/// <summary>
/// HttpClientSettings
/// </summary>
public class HttpClientSettings : IHttpClientSettings
{
/// <inheritdoc cref="IHttpClientSettings.ClientX509Certificate2ThumbprintOrSubjectName"/>
public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <inheritdoc cref="IHttpClientSettings.WebProxySettings"/>
public IWebProxySettings WebProxySettings { get; set; }
/// <inheritdoc cref="IHttpClientSettings.AllowAutoRedirect"/>
public bool? AllowAutoRedirect { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
namespace WireMock.Settings
{
/// <summary>
/// IHttpClientSettings
/// </summary>
public interface IHttpClientSettings
{
/// <summary>
/// The clientCertificate thumbprint or subject name fragment to use.
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary>
string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary>
/// Defines the WebProxySettings.
/// </summary>
IWebProxySettings WebProxySettings { get; set; }
/// <summary>
/// Proxy requests should follow redirection (30x).
/// </summary>
bool? AllowAutoRedirect { get; set; }
}
}

View File

@@ -5,7 +5,7 @@ namespace WireMock.Settings
/// <summary>
/// IProxyAndRecordSettings
/// </summary>
public interface IProxyAndRecordSettings
public interface IProxyAndRecordSettings : IHttpClientSettings
{
/// <summary>
/// The URL to proxy.
@@ -29,12 +29,6 @@ namespace WireMock.Settings
/// </summary>
bool SaveMappingToFile { get; set; }
/// <summary>
/// The clientCertificate thumbprint or subject name fragment to use.
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary>
string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary>
/// Defines a list from headers which will be excluded from the saved mappings.
/// </summary>
@@ -44,15 +38,5 @@ namespace WireMock.Settings
/// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary>
string[] ExcludedCookies { get; set; }
/// <summary>
/// Defines the WebProxySettings.
/// </summary>
IWebProxySettings WebProxySettings { get; set; }
/// <summary>
/// Proxy requests should follow redirection (30x).
/// </summary>
bool? AllowAutoRedirect { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace WireMock.Settings
{
/// <summary>
/// IWebhookSettings
/// </summary>
public interface IWebhookSettings : IHttpClientSettings
{
}
}

View File

@@ -4,6 +4,9 @@ using JetBrains.Annotations;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
#if USE_ASPNETCORE
using Microsoft.Extensions.DependencyInjection;
#endif
namespace WireMock.Settings
{
@@ -109,6 +112,14 @@ namespace WireMock.Settings
[PublicAPI]
Action<object> PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
/// <summary>
/// Action which is called with IServiceCollection when ASP.NET Core DI is being configured. [Optional]
/// </summary>
[PublicAPI]
Action<IServiceCollection> AdditionalServiceRegistration { get; set; }
#endif
/// <summary>
/// The IWireMockLogger which logs Debug, Info, Warning or Error
/// </summary>
@@ -186,5 +197,11 @@ namespace WireMock.Settings
/// </summary>
[PublicAPI]
bool CustomCertificateDefined { get; }
/// <summary>
/// Defines the global IWebhookSettingsto use
/// </summary>
[PublicAPI]
IWebhookSettings WebhookSettings { get; set; }
}
}

View File

@@ -5,7 +5,7 @@ namespace WireMock.Settings
/// <summary>
/// ProxyAndRecordSettings
/// </summary>
public class ProxyAndRecordSettings : IProxyAndRecordSettings
public class ProxyAndRecordSettings : HttpClientSettings, IProxyAndRecordSettings
{
/// <summary>
/// The URL to proxy.
@@ -32,13 +32,6 @@ namespace WireMock.Settings
[PublicAPI]
public string SaveMappingForStatusCodePattern { get; set; } = "*";
/// <summary>
/// The clientCertificate thumbprint or subject name fragment to use.
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary>
[PublicAPI]
public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <inheritdoc cref="IProxyAndRecordSettings.ExcludedHeaders"/>
[PublicAPI]
public string[] ExcludedHeaders { get; set; }
@@ -46,13 +39,5 @@ namespace WireMock.Settings
/// <inheritdoc cref="IProxyAndRecordSettings.ExcludedCookies"/>
[PublicAPI]
public string[] ExcludedCookies { get; set; }
/// <inheritdoc cref="IProxyAndRecordSettings.WebProxySettings"/>
[PublicAPI]
public IWebProxySettings WebProxySettings { get; set; }
/// <inheritdoc cref="IProxyAndRecordSettings.AllowAutoRedirect"/>
[PublicAPI]
public bool? AllowAutoRedirect { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace WireMock.Settings
{
/// <summary>
/// WebhookSettings
/// </summary>
public class WebhookSettings : HttpClientSettings, IWebhookSettings
{
}
}

View File

@@ -4,6 +4,9 @@ using JetBrains.Annotations;
using Newtonsoft.Json;
using WireMock.Handlers;
using WireMock.Logging;
#if USE_ASPNETCORE
using Microsoft.Extensions.DependencyInjection;
#endif
namespace WireMock.Settings
{
@@ -79,6 +82,13 @@ namespace WireMock.Settings
[JsonIgnore]
public Action<object> PostWireMockMiddlewareInit { get; set; }
#if USE_ASPNETCORE
/// <inheritdoc cref="IWireMockServerSettings.AdditionalServiceRegistration"/>
[PublicAPI]
[JsonIgnore]
public Action<IServiceCollection> AdditionalServiceRegistration { get; set; }
#endif
/// <inheritdoc cref="IWireMockServerSettings.Logger"/>
[PublicAPI]
[JsonIgnore]
@@ -129,5 +139,9 @@ namespace WireMock.Settings
/// <inheritdoc cref="IWireMockServerSettings.CustomCertificateDefined"/>
[PublicAPI]
public bool CustomCertificateDefined => CertificateSettings?.IsDefined == true;
/// <inheritdoc cref="IWireMockServerSettings.WebhookSettings"/>
[PublicAPI]
public IWebhookSettings WebhookSettings { get; set; }
}
}

View File

@@ -1,7 +1,13 @@
namespace WireMock.Transformers
using System.Collections.Generic;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Transformers
{
interface ITransformer
{
ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile);
(IBodyData BodyData, IDictionary<string, WireMockList<string>> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary<string, WireMockList<string>> headers);
}
}

View File

@@ -1,227 +1,265 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Types;
using WireMock.Util;
using WireMock.Validation;
namespace WireMock.Transformers.Handlebars
{
internal class Transformer : ITransformer
{
private readonly ITransformerContextFactory _factory;
public Transformer([NotNull] ITransformerContextFactory factory)
{
Check.NotNull(factory, nameof(factory));
_factory = factory;
}
public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile)
{
var handlebarsContext = _factory.Create();
var responseMessage = new ResponseMessage();
var model = new { request = requestMessage };
switch (original.BodyData?.DetectedBodyType)
{
case BodyType.Json:
TransformBodyAsJson(handlebarsContext, model, original, responseMessage);
break;
case BodyType.File:
TransformBodyAsFile(handlebarsContext, model, original, responseMessage, useTransformerForBodyAsFile);
break;
case BodyType.String:
responseMessage.BodyOriginal = original.BodyData.BodyAsString;
TransformBodyAsString(handlebarsContext, model, original, responseMessage);
break;
}
responseMessage.FaultType = original.FaultType;
responseMessage.FaultPercentage = original.FaultPercentage;
// Headers
var newHeaders = new Dictionary<string, WireMockList<string>>();
foreach (var header in original.Headers)
{
var headerKey = handlebarsContext.ParseAndRender(header.Key, model);
var templateHeaderValues = header.Value
.Select(text => handlebarsContext.ParseAndRender(text, model))
.ToArray();
newHeaders.Add(headerKey, new WireMockList<string>(templateHeaderValues));
}
responseMessage.Headers = newHeaders;
switch (original.StatusCode)
{
case int statusCodeAsInteger:
responseMessage.StatusCode = statusCodeAsInteger;
break;
case string statusCodeAsString:
responseMessage.StatusCode = handlebarsContext.ParseAndRender(statusCodeAsString, model);
break;
}
return responseMessage;
}
private static void TransformBodyAsJson(ITransformerContext handlebarsContext, object model, ResponseMessage original, ResponseMessage responseMessage)
{
JToken jToken;
switch (original.BodyData.BodyAsJson)
{
case JObject bodyAsJObject:
jToken = bodyAsJObject.DeepClone();
WalkNode(handlebarsContext, jToken, model);
break;
case Array bodyAsArray:
jToken = JArray.FromObject(bodyAsArray);
WalkNode(handlebarsContext, jToken, model);
break;
case string bodyAsString:
jToken = ReplaceSingleNode(handlebarsContext, bodyAsString, model);
break;
default:
jToken = JObject.FromObject(original.BodyData.BodyAsJson);
WalkNode(handlebarsContext, jToken, model);
break;
}
responseMessage.BodyData = new BodyData
{
Encoding = original.BodyData.Encoding,
DetectedBodyType = original.BodyData.DetectedBodyType,
DetectedBodyTypeFromContentType = original.BodyData.DetectedBodyTypeFromContentType,
BodyAsJson = jToken
};
}
private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, string stringValue, object model)
{
string transformedString = handlebarsContext.ParseAndRender(stringValue, model) as string;
if (!string.Equals(stringValue, transformedString))
{
const string property = "_";
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
JToken node = dummy[property];
ReplaceNodeValue(node, transformedString);
return dummy[property];
}
return stringValue;
}
private static void WalkNode(ITransformerContext handlebarsContext, JToken node, object model)
{
if (node.Type == JTokenType.Object)
{
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (JProperty child in node.Children<JProperty>().ToArray())
{
WalkNode(handlebarsContext, child.Value, model);
}
}
else if (node.Type == JTokenType.Array)
{
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (JToken child in node.Children().ToArray())
{
WalkNode(handlebarsContext, child, model);
}
}
else if (node.Type == JTokenType.String)
{
// In case of string, try to transform the value.
string stringValue = node.Value<string>();
if (string.IsNullOrEmpty(stringValue))
{
return;
}
string transformedString = handlebarsContext.ParseAndRender(stringValue, model);
if (!string.Equals(stringValue, transformedString))
{
ReplaceNodeValue(node, transformedString);
}
}
}
private static void ReplaceNodeValue(JToken node, string stringValue)
{
if (bool.TryParse(stringValue, out bool valueAsBoolean))
{
node.Replace(valueAsBoolean);
return;
}
JToken value;
try
{
// Try to convert this string into a JsonObject
value = JToken.Parse(stringValue);
}
catch (JsonException)
{
// Ignore JsonException and just keep string value and convert to JToken
value = stringValue;
}
node.Replace(value);
}
private static void TransformBodyAsString(ITransformerContext handlebarsContext, object model, ResponseMessage original, ResponseMessage responseMessage)
{
responseMessage.BodyData = new BodyData
{
Encoding = original.BodyData.Encoding,
DetectedBodyType = original.BodyData.DetectedBodyType,
DetectedBodyTypeFromContentType = original.BodyData.DetectedBodyTypeFromContentType,
BodyAsString = handlebarsContext.ParseAndRender(original.BodyData.BodyAsString, model)
};
}
private void TransformBodyAsFile(ITransformerContext handlebarsContext, object model, ResponseMessage original, ResponseMessage responseMessage, bool useTransformerForBodyAsFile)
{
string transformedBodyAsFilename = handlebarsContext.ParseAndRender(original.BodyData.BodyAsFile, model);
if (!useTransformerForBodyAsFile)
{
responseMessage.BodyData = new BodyData
{
DetectedBodyType = original.BodyData.DetectedBodyType,
DetectedBodyTypeFromContentType = original.BodyData.DetectedBodyTypeFromContentType,
BodyAsFile = transformedBodyAsFilename
};
}
else
{
string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename);
responseMessage.BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = original.BodyData.DetectedBodyTypeFromContentType,
BodyAsString = handlebarsContext.ParseAndRender(text, model),
BodyAsFile = transformedBodyAsFilename
};
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Transformers.Handlebars
{
internal class Transformer : ITransformer
{
private readonly ITransformerContextFactory _factory;
public Transformer([NotNull] ITransformerContextFactory factory)
{
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
public (IBodyData BodyData, IDictionary<string, WireMockList<string>> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary<string, WireMockList<string>> headers)
{
var transformerContext = _factory.Create();
var model = new
{
request = originalRequestMessage,
response = originalResponseMessage
};
IBodyData newBodyData = null;
if (bodyData?.DetectedBodyType != null)
{
newBodyData = TransformBodyData(transformerContext, model, bodyData, false);
}
return (newBodyData, TransformHeaders(transformerContext, model, headers));
}
public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile)
{
var transformerContext = _factory.Create();
var responseMessage = new ResponseMessage();
var model = new
{
request = requestMessage
};
if (original.BodyData?.DetectedBodyType != null)
{
responseMessage.BodyData = TransformBodyData(transformerContext, model, original.BodyData, useTransformerForBodyAsFile);
if (original.BodyData.DetectedBodyType == BodyType.String)
{
responseMessage.BodyOriginal = original.BodyData.BodyAsString;
}
}
responseMessage.FaultType = original.FaultType;
responseMessage.FaultPercentage = original.FaultPercentage;
responseMessage.Headers = TransformHeaders(transformerContext, model, original.Headers);
switch (original.StatusCode)
{
case int statusCodeAsInteger:
responseMessage.StatusCode = statusCodeAsInteger;
break;
case string statusCodeAsString:
responseMessage.StatusCode = transformerContext.ParseAndRender(statusCodeAsString, model);
break;
}
return responseMessage;
}
private static IBodyData TransformBodyData(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
{
switch (original?.DetectedBodyType)
{
case BodyType.Json:
return TransformBodyAsJson(transformerContext, model, original);
case BodyType.File:
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
case BodyType.String:
return TransformBodyAsString(transformerContext, model, original);
default:
return null;
}
}
private static IDictionary<string, WireMockList<string>> TransformHeaders(ITransformerContext transformerContext, object model, IDictionary<string, WireMockList<string>> original)
{
if (original == null)
{
return new Dictionary<string, WireMockList<string>>();
}
var newHeaders = new Dictionary<string, WireMockList<string>>();
foreach (var header in original)
{
var headerKey = transformerContext.ParseAndRender(header.Key, model);
var templateHeaderValues = header.Value.Select(text => transformerContext.ParseAndRender(text, model)).ToArray();
newHeaders.Add(headerKey, new WireMockList<string>(templateHeaderValues));
}
return newHeaders;
}
private static IBodyData TransformBodyAsJson(ITransformerContext handlebarsContext, object model, IBodyData original)
{
JToken jToken;
switch (original.BodyAsJson)
{
case JObject bodyAsJObject:
jToken = bodyAsJObject.DeepClone();
WalkNode(handlebarsContext, jToken, model);
break;
case Array bodyAsArray:
jToken = JArray.FromObject(bodyAsArray);
WalkNode(handlebarsContext, jToken, model);
break;
case string bodyAsString:
jToken = ReplaceSingleNode(handlebarsContext, bodyAsString, model);
break;
default:
jToken = JObject.FromObject(original.BodyAsJson);
WalkNode(handlebarsContext, jToken, model);
break;
}
return new BodyData
{
Encoding = original.Encoding,
DetectedBodyType = original.DetectedBodyType,
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
BodyAsJson = jToken
};
}
private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, string stringValue, object model)
{
string transformedString = handlebarsContext.ParseAndRender(stringValue, model);
if (!string.Equals(stringValue, transformedString))
{
const string property = "_";
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
JToken node = dummy[property];
ReplaceNodeValue(node, transformedString);
return dummy[property];
}
return stringValue;
}
private static void WalkNode(ITransformerContext handlebarsContext, JToken node, object model)
{
if (node.Type == JTokenType.Object)
{
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (JProperty child in node.Children<JProperty>().ToArray())
{
WalkNode(handlebarsContext, child.Value, model);
}
}
else if (node.Type == JTokenType.Array)
{
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (JToken child in node.Children().ToArray())
{
WalkNode(handlebarsContext, child, model);
}
}
else if (node.Type == JTokenType.String)
{
// In case of string, try to transform the value.
string stringValue = node.Value<string>();
if (string.IsNullOrEmpty(stringValue))
{
return;
}
string transformedString = handlebarsContext.ParseAndRender(stringValue, model);
if (!string.Equals(stringValue, transformedString))
{
ReplaceNodeValue(node, transformedString);
}
}
}
private static void ReplaceNodeValue(JToken node, string stringValue)
{
if (bool.TryParse(stringValue, out bool valueAsBoolean))
{
node.Replace(valueAsBoolean);
return;
}
JToken value;
try
{
// Try to convert this string into a JsonObject
value = JToken.Parse(stringValue);
}
catch (JsonException)
{
// Ignore JsonException and just keep string value and convert to JToken
value = stringValue;
}
node.Replace(value);
}
private static IBodyData TransformBodyAsString(ITransformerContext handlebarsContext, object model, IBodyData original)
{
return new BodyData
{
Encoding = original.Encoding,
DetectedBodyType = original.DetectedBodyType,
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
BodyAsString = handlebarsContext.ParseAndRender(original.BodyAsString, model)
};
}
private static IBodyData TransformBodyAsFile(ITransformerContext handlebarsContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
{
string transformedBodyAsFilename = handlebarsContext.ParseAndRender(original.BodyAsFile, model);
if (!useTransformerForBodyAsFile)
{
return new BodyData
{
DetectedBodyType = original.DetectedBodyType,
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
BodyAsFile = transformedBodyAsFilename
};
}
else
{
string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename);
return new BodyData
{
DetectedBodyType = BodyType.String,
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
BodyAsString = handlebarsContext.ParseAndRender(text, model),
BodyAsFile = transformedBodyAsFilename
};
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More