Compare commits

...

32 Commits

Author SHA1 Message Date
Stef Heyenrath
9dea577da1 1.5.21 2023-03-22 16:18:19 +01:00
Stef Heyenrath
7ca4294de6 Fixed QueryStringParser for UrlEncoded values (#911) 2023-03-22 16:15:50 +01:00
Stef Heyenrath
66245409f9 RequestBuilder : add WithBodyAsJson and WithBody (with IJsonConverter) (#908)
* RequestBuilder : add WithBodyAsJson and WithBody (with IJsonConverter)

* tests
2023-03-22 15:47:58 +01:00
Stef Heyenrath
da6cb9fe0a 1.5.20 2023-03-19 10:23:24 +01:00
Stef Heyenrath
b30e4faab6 Fix issue with application/x-www-form-urlencoded and ExactMatcher (#907) 2023-03-19 10:21:47 +01:00
Stef Heyenrath
1221d52c69 Add DeserializeFormUrl Encoded to the settings (#905)
* Add DeserializeFormUrlEncoded to Settings

* EmptyArray<>.Value

* .
2023-03-19 09:19:53 +01:00
Stef Heyenrath
52d2109c7e packagereleasenotes 2023-03-17 17:16:35 +01:00
Stef Heyenrath
30064b922b 1.5.19 2023-03-17 17:15:19 +01:00
Stef Heyenrath
78b94d2ebc Add WithBody with IDictionary (form-urlencoded values) (#903)
* .

* x

* fx

* fix

* f

* tests

* fix tests

* add tst
2023-03-17 17:08:45 +01:00
Stef Heyenrath
19701f5260 Update Handlebars.Net.Helpers to 2.3.15 (#904) 2023-03-15 18:30:34 +01:00
Stef Heyenrath
1269fb178f 1.5.18 2023-03-09 19:49:29 +01:00
Stef Heyenrath
7426bf76ee Add 'Data' to response which can be used during transforming the response (#893)
* Add 'Data' to response which can be used during transforming the response

* md

* hb

* fix

* Linq

* fix test

* v4

* 14

* .

* x

* remove

* s
2023-03-09 17:20:34 +01:00
dependabot[bot]
36c9d95abb Bump Microsoft.Owin in /examples/WireMock.Net.Service (#896)
Bumps [Microsoft.Owin](https://github.com/aspnet/AspNetKatana) from 2.0.2 to 4.2.2.
- [Release notes](https://github.com/aspnet/AspNetKatana/releases)
- [Commits](https://github.com/aspnet/AspNetKatana/compare/v2.0.2...v4.2.2)

---
updated-dependencies:
- dependency-name: Microsoft.Owin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-09 17:20:15 +01:00
Stef Heyenrath
674fa89c3e ProxySettings : Add logic to not save some requests depending on HttpMethods (#900)
* Add ExcludedHttpMethods to ProxySettings

* tst

* fix

* SaveMappingSettings

* .
2023-03-09 15:28:52 +01:00
Stef Heyenrath
61cdc13fae Add extra test for WithCallback 2023-03-01 12:10:03 +01:00
Stef Heyenrath
c344b73f45 Cleanup some code from JsonUtils.cs 2023-02-27 21:23:43 +01:00
Stef Heyenrath
2ac9ca207a 1.5.17 2023-02-25 12:53:41 +01:00
Stef Heyenrath
f099f3a288 AdminApiMappingBuilder (#890)
* AdminApiMappingBuilder

* .

* IWireMockAdminApi

* add methods

* .
2023-02-25 12:47:06 +01:00
Stef Heyenrath
02b607cc95 Slow test (#882) 2023-02-18 18:09:21 +01:00
Stef Heyenrath
7ac89e85b7 Add WithBodyAsJson builder method with accepts a Func (#881)
* Add WithBodyAsJson builder method with accepts a Func

* ut
2023-02-06 20:50:11 +01:00
Stef Heyenrath
cc4cf27101 1.5.16 2023-02-01 20:47:32 +01:00
Stef Heyenrath
6839b11d35 Add WithProxy(string proxyUrl, X509Certificate2 certificate) (#880) 2023-02-01 10:42:35 +01:00
Stef Heyenrath
1000f4409f 1.5.15 2023-01-29 10:27:53 +01:00
Stef Heyenrath
7fe2c8af78 Update REST Admin interface to support "Get Mapping(s) as C# Code" (#878)
* Add /__admin/mappings/code endpoint

* api

* fix

* .

* fix

* .

* .

* .
2023-01-29 10:24:58 +01:00
Stef Heyenrath
0fc664b404 1.5.14 2023-01-24 16:50:58 +01:00
Stef Heyenrath
770a670e53 Generate C# code from Mapping (#842)
* 1

* .

* v

* .

* .

* -

* b

* res b

* Fix UT

* .

* Verify

* v

* ...

* .

* .

* dir

* m
2023-01-24 16:45:47 +01:00
eseneckiy
b4c8779d68 Fix Self referencing loop detected for property 'Parent' with type 'System.Globalization.CultureInfo' (#875)
Co-authored-by: evgeniy.s <evgeniy.s@uklon.com.ua>
2023-01-23 20:07:06 +01:00
Stef Heyenrath
c85eaf1072 Add unit test example for Transformer Handlebars String.Append String.Join (#877)
* Response_ProvideResponse_Transformer_WithBodyAsJson_Handlebars_StringAppend

* fix
2023-01-20 08:21:59 +01:00
Stef Heyenrath
b2a8178161 Fix unsubscribe from LogEntriesChanged event handler (#872)
* Fix unsubscribe from LogEntriesChanged event handler

* .

* f
2023-01-19 14:23:38 +01:00
Stef Heyenrath
20eb37b0c8 Add MappingBuilder to build mappings in code and export to Models or JSON (#869)
* MappingBuilder

* .

* ...

* sc

* t

* .
2023-01-06 19:11:56 +01:00
Gerhard Gradnig
742f1d1f0a Add UseWebhooksFireAndForget to Server ConvertMapping (#871)
Co-authored-by: Gerhard.Gradnig <gerhard.gradnig@admiral.at>
2023-01-05 18:33:08 +01:00
Stef Heyenrath
d8927b88c8 Fix example projects 2022-12-24 17:00:41 +01:00
190 changed files with 7505 additions and 4519 deletions

2
.gitignore vendored
View File

@@ -255,3 +255,5 @@ paket-files/
/test/WireMock.Net.Tests/coverage.opencover.xml
/test/WireMock.Net.Tests/coverage.netcoreapp3.1.opencover.xml
/test/WireMock.Net.Tests/coverage.net5.0.opencover.xml
*.received.*

View File

@@ -1,3 +1,47 @@
# 1.5.21 (22 March 2023)
- [#908](https://github.com/WireMock-Net/WireMock.Net/pull/908) - RequestBuilder : add WithBodyAsJson and WithBody (with IJsonConverter) [feature] contributed by [StefH](https://github.com/StefH)
- [#911](https://github.com/WireMock-Net/WireMock.Net/pull/911) - Fixed QueryStringParser for UrlEncoded values [bug] contributed by [StefH](https://github.com/StefH)
- [#901](https://github.com/WireMock-Net/WireMock.Net/issues/901) - Matching one form-urlencoded value [feature]
# 1.5.20 (19 March 2023)
- [#905](https://github.com/WireMock-Net/WireMock.Net/pull/905) - Add DeserializeFormUrl Encoded to the settings [feature] contributed by [StefH](https://github.com/StefH)
- [#907](https://github.com/WireMock-Net/WireMock.Net/pull/907) - Fix issue with application/x-www-form-urlencoded and ExactMatcher [bug] contributed by [StefH](https://github.com/StefH)
- [#906](https://github.com/WireMock-Net/WireMock.Net/issues/906) - Upgrade to 1.5.19 breaks a form data test [bug]
# 1.5.19 (17 March 2023)
- [#903](https://github.com/WireMock-Net/WireMock.Net/pull/903) - Add WithBody with IDictionary (form-urlencoded values) [feature] contributed by [StefH](https://github.com/StefH)
- [#904](https://github.com/WireMock-Net/WireMock.Net/pull/904) - Update Handlebars.Net.Helpers to 2.3.15 [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.18 (09 March 2023)
- [#893](https://github.com/WireMock-Net/WireMock.Net/pull/893) - Add 'Data' to response which can be used during transforming the response [feature] contributed by [StefH](https://github.com/StefH)
- [#896](https://github.com/WireMock-Net/WireMock.Net/pull/896) - Bump Microsoft.Owin from 2.0.2 to 4.2.2 in /examples/WireMock.Net.Service [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#900](https://github.com/WireMock-Net/WireMock.Net/pull/900) - ProxySettings : Add logic to not save some requests depending on HttpMethods [feature] contributed by [StefH](https://github.com/StefH)
- [#897](https://github.com/WireMock-Net/WireMock.Net/issues/897) - WebHostBuilder.ConfigureServices method not found when using nunit3testadapter 4.4.0 [bug]
- [#899](https://github.com/WireMock-Net/WireMock.Net/issues/899) - Ignore OPTIONS request when using proxyandrecord [feature]
# 1.5.17 (25 February 2023)
- [#881](https://github.com/WireMock-Net/WireMock.Net/pull/881) - Add WithBodyAsJson builder method with accepts a Func [feature] contributed by [StefH](https://github.com/StefH)
- [#882](https://github.com/WireMock-Net/WireMock.Net/pull/882) - Add example code to test HTTP Status 400 and 500 [test] contributed by [StefH](https://github.com/StefH)
- [#890](https://github.com/WireMock-Net/WireMock.Net/pull/890) - AdminApiMappingBuilder [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.16 (01 February 2023)
- [#880](https://github.com/WireMock-Net/WireMock.Net/pull/880) - Add `WithProxy(string proxyUrl, X509Certificate2 certificate)` [feature] contributed by [StefH](https://github.com/StefH)
- [#879](https://github.com/WireMock-Net/WireMock.Net/issues/879) - Possibility to pass a X509Certificate2 to WithProxy() or specifiy certificate loading options [feature]
# 1.5.15 (29 January 2023)
- [#878](https://github.com/WireMock-Net/WireMock.Net/pull/878) - Update REST Admin interface to support &quot;Get Mapping(s) as C# Code&quot; [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.14 (24 January 2023)
- [#842](https://github.com/WireMock-Net/WireMock.Net/pull/842) - Generate C# code from Mapping [feature] contributed by [StefH](https://github.com/StefH)
- [#869](https://github.com/WireMock-Net/WireMock.Net/pull/869) - Add MappingBuilder to build mappings in code and export to Models or JSON [feature] contributed by [StefH](https://github.com/StefH)
- [#871](https://github.com/WireMock-Net/WireMock.Net/pull/871) - Add UseWebhooksFireAndForget to Server ConvertMapping [bug] contributed by [ggradnig](https://github.com/ggradnig)
- [#872](https://github.com/WireMock-Net/WireMock.Net/pull/872) - Fix unsubscribe from LogEntriesChanged event handler [bug] contributed by [StefH](https://github.com/StefH)
- [#875](https://github.com/WireMock-Net/WireMock.Net/pull/875) - Fix Self referencing loop detected for property [bug] contributed by [eseneckiy](https://github.com/eseneckiy)
- [#877](https://github.com/WireMock-Net/WireMock.Net/pull/877) - Add unit test example for Transformer Handlebars String.Append String.Join [test] contributed by [StefH](https://github.com/StefH)
- [#701](https://github.com/WireMock-Net/WireMock.Net/issues/701) - Allow to create MappingModel from c# to be able to configure local and remote mocks similarly [feature]
- [#867](https://github.com/WireMock-Net/WireMock.Net/issues/867) - Can I build mappings with code and save them to JSON-file without starting server [feature]
- [#870](https://github.com/WireMock-Net/WireMock.Net/issues/870) - Can not unsubscribe from LogEntriesChanged event. [bug]
# 1.5.13 (11 December 2022)
- [#858](https://github.com/WireMock-Net/WireMock.Net/pull/858) - Update Transformer functionality to return value instead of string [feature] contributed by [StefH](https://github.com/StefH)
- [#859](https://github.com/WireMock-Net/WireMock.Net/pull/859) - Add UpdatedAt property to Mapping [feature] contributed by [StefH](https://github.com/StefH)
@@ -633,8 +677,8 @@
- [#263](https://github.com/WireMock-Net/WireMock.Net/issues/263) - Content-Type multipart/form-data is not serialized in proxy and recording mode [bug]
# 1.0.11.0 (30 March 2019)
- [#261](https://github.com/WireMock-Net/WireMock.Net/pull/261) - Fix BodyAsJson transform bug in ResponseMessageTransformer contributed by [psypilat](https://github.com/psypilat)
- [#262](https://github.com/WireMock-Net/WireMock.Net/pull/262) - Add ProvideResponse_WithJsonBodyAndTransform test contributed by [psypilat](https://github.com/psypilat)
- [#261](https://github.com/WireMock-Net/WireMock.Net/pull/261) - Fix BodyAsJson transform bug in ResponseMessageTransformer contributed by [ghost](https://github.com/ghost)
- [#262](https://github.com/WireMock-Net/WireMock.Net/pull/262) - Add ProvideResponse_WithJsonBodyAndTransform test contributed by [ghost](https://github.com/ghost)
# 1.0.10.0 (27 March 2019)
- [#260](https://github.com/WireMock-Net/WireMock.Net/pull/260) - Fix Response.Delay property serialization [bug] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>1.5.13</VersionPrefix>
<VersionPrefix>1.5.21</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.13
SET version=1.5.21
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN%

View File

@@ -1,9 +1,6 @@
# 1.5.13 (11 December 2022)
- #858 Update Transformer functionality to return value instead of string [feature]
- #859 Add UpdatedAt property to Mapping [feature]
- #861 Add extra functionality for issue 55
- #862 Add client certificate support [feature]
- #863 Update WireMockServer.CreateClient/CreateClients to include handlers [feature]
- #856 Inconsistent result with overlapping (duplicate) request [bug]
# 1.5.21 (22 March 2023)
- #908 RequestBuilder : add WithBodyAsJson and WithBody (with IJsonConverter) [feature]
- #911 Fixed QueryStringParser for UrlEncoded values [bug]
- #901 Matching one form-urlencoded value [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,78 +1,125 @@
using Newtonsoft.Json;
using RestEase;
using System;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RestEase;
using WireMock.Admin.Settings;
using WireMock.Client;
using WireMock.Client.Extensions;
namespace WireMock.Net.Client
namespace WireMock.Net.Client;
class Program
{
class Program
static async Task Main(string[] args)
{
static async Task Main(string[] args)
// Create an implementation of the IWireMockAdminApi and pass in the base URL for the API.
var api = RestClient.For<IWireMockAdminApi>("http://localhost:9091");
// await api.ResetMappingsAsync().ConfigureAwait(false);
var mappingBuilder = api.GetMappingBuilder();
mappingBuilder.Given(m => m
.WithTitle("This is my title 1")
.WithRequest(req => req
.UsingGet()
.WithPath("/bla1")
)
.WithResponse(rsp => rsp
.WithBody("x1")
.WithHeaders(h => h.Add("h1", "v1"))
)
);
mappingBuilder.Given(m => m
.WithTitle("This is my title 2")
.WithRequest(req => req
.UsingGet()
.WithPath("/bla2")
)
.WithResponse(rsp => rsp
.WithBody("x2")
.WithHeaders(h => h.Add("h2", "v2"))
)
);
mappingBuilder.Given(m => m
.WithTitle("This is my title 3")
.WithRequest(req => req
.UsingGet()
.WithPath("/bla3")
)
.WithResponse(rsp => rsp
.WithBodyAsJson(new
{
x = "test"
}, true)
)
);
var result = await mappingBuilder.BuildAndPostAsync().ConfigureAwait(false);
Console.WriteLine($"result = {JsonConvert.SerializeObject(result)}");
var mappings = await api.GetMappingsAsync();
Console.WriteLine($"mappings = {JsonConvert.SerializeObject(mappings)}");
// Set BASIC Auth
var value = Convert.ToBase64String(Encoding.ASCII.GetBytes("a:b"));
api.Authorization = new AuthenticationHeaderValue("Basic", value);
var settings1 = await api.GetSettingsAsync();
Console.WriteLine($"settings1 = {JsonConvert.SerializeObject(settings1)}");
var settingsViaBuilder = new SettingsModelBuilder()
.WithGlobalProcessingDelay(1077)
.WithoutGlobalProcessingDelay()
.Build();
settings1.GlobalProcessingDelay = 1077;
api.PostSettingsAsync(settings1).Wait();
var settings2 = await api.GetSettingsAsync();
Console.WriteLine($"settings2 = {JsonConvert.SerializeObject(settings2)}");
mappings = await api.GetMappingsAsync();
Console.WriteLine($"mappings = {JsonConvert.SerializeObject(mappings)}");
try
{
// Create an implementation of the IWireMockAdminApi and pass in the base URL for the API.
var api = RestClient.For<IWireMockAdminApi>("http://localhost:9091");
// Set BASIC Auth
var value = Convert.ToBase64String(Encoding.ASCII.GetBytes("a:b"));
api.Authorization = new AuthenticationHeaderValue("Basic", value);
var settings1 = await api.GetSettingsAsync();
Console.WriteLine($"settings1 = {JsonConvert.SerializeObject(settings1)}");
var settingsViaBuilder = new SettingsModelBuilder()
.WithGlobalProcessingDelay(1077)
.WithoutGlobalProcessingDelay()
.Build();
settings1.GlobalProcessingDelay = 1077;
api.PostSettingsAsync(settings1).Wait();
var settings2 = await api.GetSettingsAsync();
Console.WriteLine($"settings2 = {JsonConvert.SerializeObject(settings2)}");
var mappings = await api.GetMappingsAsync();
Console.WriteLine($"mappings = {JsonConvert.SerializeObject(mappings)}");
try
{
var guid = Guid.Parse("11111110-a633-40e8-a244-5cb80bc0ab66");
var mapping = await api.GetMappingAsync(guid);
Console.WriteLine($"mapping = {JsonConvert.SerializeObject(mapping)}");
}
catch (Exception e)
{
}
var request = await api.GetRequestsAsync();
Console.WriteLine($"request = {JsonConvert.SerializeObject(request)}");
//var deleteRequestsAsync = api.DeleteRequestsAsync().Result;
//Console.WriteLine($"DeleteRequestsAsync = {deleteRequestsAsync.Status}");
//var resetRequestsAsync = api.ResetRequestsAsync().Result;
//Console.WriteLine($"ResetRequestsAsync = {resetRequestsAsync.Status}");
var scenarioStates = await api.GetScenariosAsync();
Console.WriteLine($"GetScenariosAsync = {JsonConvert.SerializeObject(scenarioStates)}");
var postFileResult = await api.PostFileAsync("1.cs", "C# Hello");
Console.WriteLine($"postFileResult = {JsonConvert.SerializeObject(postFileResult)}");
var getFileResult = await api.GetFileAsync("1.cs");
Console.WriteLine($"getFileResult = {getFileResult}");
var resetMappingsAsync = await api.ResetMappingsAsync();
Console.WriteLine($"resetMappingsAsync = {resetMappingsAsync.Status}");
var resetMappingsAndReloadStaticMappingsAsync = await api.ResetMappingsAsync(true);
Console.WriteLine($"resetMappingsAndReloadStaticMappingsAsync = {resetMappingsAndReloadStaticMappingsAsync.Status}");
Console.WriteLine("Press any key to quit");
Console.ReadKey();
var guid = Guid.Parse("11111110-a633-40e8-a244-5cb80bc0ab66");
var mapping = await api.GetMappingAsync(guid);
Console.WriteLine($"mapping = {JsonConvert.SerializeObject(mapping)}");
}
catch (Exception e)
{
}
var request = await api.GetRequestsAsync();
Console.WriteLine($"request = {JsonConvert.SerializeObject(request)}");
//var deleteRequestsAsync = api.DeleteRequestsAsync().Result;
//Console.WriteLine($"DeleteRequestsAsync = {deleteRequestsAsync.Status}");
//var resetRequestsAsync = api.ResetRequestsAsync().Result;
//Console.WriteLine($"ResetRequestsAsync = {resetRequestsAsync.Status}");
var scenarioStates = await api.GetScenariosAsync();
Console.WriteLine($"GetScenariosAsync = {JsonConvert.SerializeObject(scenarioStates)}");
var postFileResult = await api.PostFileAsync("1.cs", "C# Hello");
Console.WriteLine($"postFileResult = {JsonConvert.SerializeObject(postFileResult)}");
var getFileResult = await api.GetFileAsync("1.cs");
Console.WriteLine($"getFileResult = {getFileResult}");
var resetMappingsAsync = await api.ResetMappingsAsync();
Console.WriteLine($"resetMappingsAsync = {resetMappingsAsync.Status}");
var resetMappingsAndReloadStaticMappingsAsync = await api.ResetMappingsAsync(true);
Console.WriteLine($"resetMappingsAndReloadStaticMappingsAsync = {resetMappingsAndReloadStaticMappingsAsync.Status}");
Console.WriteLine("Press any key to quit");
Console.ReadKey();
}
}

View File

@@ -1,19 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="RestEase" Version="1.5.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net.RestClient\WireMock.Net.RestClient.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.RestClient\WireMock.Net.RestClient.csproj" />
</ItemGroup>
</Project>

View File

@@ -27,7 +27,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />
<!--<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />-->
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -21,10 +21,10 @@
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.*" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -33,10 +33,10 @@
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.*" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -30,10 +30,10 @@
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Handlebars.Net.Helpers" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.DynamicLinq" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Json" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.XPath" Version="2.*" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.10" />
<PackageReference Include="Handlebars.Net.Helpers.Xeger" Version="2.3.*" />
<PackageReference Include="Handlebars.Net.Helpers.Random" Version="2.3.*" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
@@ -33,12 +35,54 @@ namespace WireMock.Net.ConsoleApplication
}
}
public class Todo
{
public int Id { get; set; }
}
public static class MainApp
{
public static void Run()
{
var s = WireMockServer.Start();
s.Stop();
var mappingBuilder = new MappingBuilder();
mappingBuilder
.Given(Request
.Create()
.WithPath(new WildcardMatcher("/param2", true))
.WithParam("key", "test")
.UsingGet())
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/json")
.WithBodyAsJson(new { result = "param2" }));
var json = mappingBuilder.ToJson();
System.Console.WriteLine("mappingBuilder : Json = {0}", json);
var todos = new Dictionary<int, Todo>();
var server = WireMockServer.Start();
server
.Given(Request.Create()
.WithPath("todos")
.UsingGet()
)
.RespondWith(Response.Create()
.WithBodyAsJson(todos.Values)
);
server
.Given(Request.Create()
.UsingGet()
.WithPath("todos")
.WithParam("id")
)
.RespondWith(Response.Create()
.WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())])
);
var httpClient = server.CreateClient();
//server.Stop();
var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
{
@@ -57,7 +101,7 @@ namespace WireMock.Net.ConsoleApplication
string url2 = "http://localhost:9092/";
string url3 = "https://localhost:9443/";
var server = WireMockServer.Start(new WireMockServerSettings
server = WireMockServer.Start(new WireMockServerSettings
{
AllowCSharpCodeMatcher = true,
Urls = new[] { url1, url2, url3 },
@@ -93,7 +137,43 @@ namespace WireMock.Net.ConsoleApplication
// server.AllowPartialMapping();
// 400 ms
server
.Given(Request.Create()
.WithPath("/slow/400")
.UsingPost())
.RespondWith(
Response.Create()
.WithStatusCode(400)
.WithBody("return 400")
.WithHeader("Content-Type", "text/plain")
);
// 4 sec
server
.Given(Request.Create()
.WithPath("/slow/500")
.UsingPost())
.RespondWith(
Response.Create()
.WithStatusCode(500)
.WithBody("return 500")
.WithHeader("Content-Type", "text/plain")
);
server
.Given(Request.Create()
.UsingMethod("GET")
.WithPath("/foo1")
.WithParam("p1", "xyz")
)
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.RespondWith(Response.Create()
.WithBody("Hello World")
);
server.Given(Request.Create().WithPath(MatchOperator.Or, "/mypath", "/mypath1", "/mypath2").UsingPost())
.WithGuid("86984b0e-2516-4935-a2ef-b45bf4820d7d")
.RespondWith(Response.Create()
.WithHeader("Content-Type", "application/json")
.WithBodyAsJson("{{JsonPath.SelectToken request.body \"..name\"}}")
@@ -366,14 +446,14 @@ namespace WireMock.Net.ConsoleApplication
// http://localhost:9091/trans?start=1000&stop=1&stop=2
server
.Given(Request.Create().WithPath("/trans").UsingGet())
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f05")
.WithGuid("90356dba-b36c-469a-a17e-669cd84f1f06")
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}")
.WithHeader("xyz_{{request.headers.Postman-Token}}", "token is {{request.headers.Postman-Token}}")
.WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, add={{Math.Add request.query.start.[0] 42}} bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }")
.WithTransformer(TransformerType.Handlebars, true, ReplaceNodeOptions.None)
.WithTransformer(TransformerType.Handlebars, true, ReplaceNodeOptions.EvaluateAndTryToConvert)
.WithDelay(TimeSpan.FromMilliseconds(100))
);
@@ -563,6 +643,7 @@ namespace WireMock.Net.ConsoleApplication
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBodyAsJson(new { Id = "5bdf076c-5654-4b3e-842c-7caf1fabf8c9" }));
server
.Given(Request.Create().WithPath("/random200or505").UsingGet())
.RespondWith(Response.Create().WithCallback(request =>
@@ -570,7 +651,11 @@ namespace WireMock.Net.ConsoleApplication
int code = new Random().Next(1, 2) == 1 ? 505 : 200;
return new ResponseMessage
{
BodyData = new BodyData { BodyAsString = "random200or505:" + code, DetectedBodyType = Types.BodyType.String },
BodyData = new BodyData
{
BodyAsString = "random200or505:" + code + ", HeadersFromRequest = " + string.Join(",", request.Headers),
DetectedBodyType = Types.BodyType.String,
},
StatusCode = code
};
}));

View File

@@ -42,11 +42,11 @@
<Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net452\Handlebars.dll</HintPath>
</Reference>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.10\lib\net452\Handlebars.Net.Helpers.dll</HintPath>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.12\lib\net452\Handlebars.Net.Helpers.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Core.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.12\lib\net452\HandlebarsDotNet.Helpers.Core.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>

View File

@@ -2,8 +2,8 @@
<packages>
<package id="AnyOf" version="0.3.0" targetFramework="net452" />
<package id="Handlebars.Net" version="2.1.2" targetFramework="net452" />
<package id="Handlebars.Net.Helpers" version="2.3.10" targetFramework="net452" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.10" targetFramework="net452" />
<package id="Handlebars.Net.Helpers" version="2.3.12" targetFramework="net452" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.12" targetFramework="net452" />
<package id="log4net" version="2.0.15" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="3.1.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" />

View File

@@ -41,11 +41,11 @@
<Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net46\Handlebars.dll</HintPath>
</Reference>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.10\lib\net452\Handlebars.Net.Helpers.dll</HintPath>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.12\lib\net46\Handlebars.Net.Helpers.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Core.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Core.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=2.0.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\..\packages\log4net.2.0.15\lib\net45\log4net.dll</HintPath>

View File

@@ -2,8 +2,8 @@
<packages>
<package id="AnyOf" version="0.3.0" targetFramework="net461" />
<package id="Handlebars.Net" version="2.1.2" targetFramework="net461" />
<package id="Handlebars.Net.Helpers" version="2.3.10" targetFramework="net461" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.10" targetFramework="net461" />
<package id="Handlebars.Net.Helpers" version="2.3.12" targetFramework="net461" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.12" targetFramework="net461" />
<package id="log4net" version="2.0.15" targetFramework="net461" />
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="2.2.0" targetFramework="net461" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net461" />

View File

@@ -49,29 +49,29 @@
<Reference Include="Handlebars, Version=2.1.2.0, Culture=neutral, PublicKeyToken=22225d0bf33cd661, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.2.1.2\lib\net46\Handlebars.dll</HintPath>
</Reference>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.10\lib\net452\Handlebars.Net.Helpers.dll</HintPath>
<Reference Include="Handlebars.Net.Helpers, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.2.3.12\lib\net46\Handlebars.Net.Helpers.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Core.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.Core, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Core.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Core.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.DynamicLinq, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.DynamicLinq.2.3.10\lib\net452\HandlebarsDotNet.Helpers.DynamicLinq.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.DynamicLinq, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.DynamicLinq.2.3.12\lib\net46\HandlebarsDotNet.Helpers.DynamicLinq.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.Humanizer, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Humanizer.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Humanizer.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.Humanizer, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Humanizer.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Humanizer.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.Json, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Json.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Json.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.Json, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Json.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Json.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.Random, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Random.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Random.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.Random, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Random.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Random.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.Xeger, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Xeger.2.3.10\lib\net452\HandlebarsDotNet.Helpers.Xeger.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.Xeger, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.Xeger.2.3.12\lib\net46\HandlebarsDotNet.Helpers.Xeger.dll</HintPath>
</Reference>
<Reference Include="HandlebarsDotNet.Helpers.XPath, Version=2.3.10.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.XPath.2.3.10\lib\net452\HandlebarsDotNet.Helpers.XPath.dll</HintPath>
<Reference Include="HandlebarsDotNet.Helpers.XPath, Version=2.3.12.0, Culture=neutral, PublicKeyToken=00d131fae0c250bc, processorArchitecture=MSIL">
<HintPath>..\..\packages\Handlebars.Net.Helpers.XPath.2.3.12\lib\net46\HandlebarsDotNet.Helpers.XPath.dll</HintPath>
</Reference>
<Reference Include="Humanizer, Version=2.14.0.0, Culture=neutral, PublicKeyToken=979442b78dfc278e, processorArchitecture=MSIL">
<HintPath>..\..\packages\Humanizer.Core.2.14.1\lib\netstandard2.0\Humanizer.dll</HintPath>

View File

@@ -3,14 +3,14 @@
<package id="AnyOf" version="0.3.0" targetFramework="net472" />
<package id="Fare" version="2.2.1" targetFramework="net472" />
<package id="Handlebars.Net" version="2.1.2" targetFramework="net472" />
<package id="Handlebars.Net.Helpers" version="2.3.10" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.10" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.DynamicLinq" version="2.3.10" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Humanizer" version="2.3.10" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Json" version="2.3.10" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Random" version="2.3.10" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Xeger" version="2.3.10" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.XPath" version="2.3.10" targetFramework="net472" />
<package id="Handlebars.Net.Helpers" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Core" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.DynamicLinq" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Humanizer" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Json" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Random" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.Xeger" version="2.3.12" targetFramework="net472" />
<package id="Handlebars.Net.Helpers.XPath" version="2.3.12" targetFramework="net472" />
<package id="Humanizer" version="2.14.1" targetFramework="net472" />
<package id="Humanizer.Core" version="2.14.1" targetFramework="net472" />
<package id="Humanizer.Core.af" version="2.14.1" targetFramework="net472" />

View File

@@ -1,6 +1,6 @@
using System;
using System;
using System.Collections.Specialized;
using System.Net.Http;
using Newtonsoft.Json;
using WireMock.Server;
using WireMock.Settings;
@@ -26,10 +26,8 @@ namespace WireMock.Net.Console.Proxy.Net452
}
});
server.LogEntriesChanged += (sender, eventRecordArgs) =>
{
System.Console.WriteLine(JsonConvert.SerializeObject(eventRecordArgs.NewItems, Formatting.Indented));
};
System.Console.WriteLine("Subscribing to LogEntriesChanged");
server.LogEntriesChanged += Server_LogEntriesChanged;
var uri = new Uri(urls[0]);
var form = new MultipartFormDataContent
@@ -38,9 +36,23 @@ namespace WireMock.Net.Console.Proxy.Net452
};
new HttpClient().PostAsync(uri, form).GetAwaiter().GetResult();
System.Console.WriteLine("Unsubscribing to LogEntriesChanged");
server.LogEntriesChanged -= Server_LogEntriesChanged;
form = new MultipartFormDataContent
{
{ new StringContent("data2"), "test2", "test2.txt" }
};
new HttpClient().PostAsync(uri, form).GetAwaiter().GetResult();
System.Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey();
server.Stop();
}
private static void Server_LogEntriesChanged(object sender, NotifyCollectionChangedEventArgs eventRecordArgs)
{
System.Console.WriteLine("Server_LogEntriesChanged : {0}", eventRecordArgs.NewItems.Count);
}
}
}

View File

@@ -5,7 +5,7 @@
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.Owin" version="2.0.2" targetFramework="net452" />
<package id="Microsoft.Owin" version="4.2.2" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="2.0.2" targetFramework="net452" />
<package id="Microsoft.Owin.Hosting" version="2.0.2" targetFramework="net452" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" />

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using WireMock.Models;
namespace WireMock.Admin.Mappings;
@@ -84,4 +85,13 @@ public class MappingModel
/// Fire and forget for webhooks.
/// </summary>
public bool? UseWebhooksFireAndForget { get; set; }
/// <summary>
/// Data Object which can be used when WithTransformer is used.
/// e.g. lookup an path in this object using
/// <example>
/// lookup data "1"
/// </example>
/// </summary>
public object? Data { get; set; }
}

View File

@@ -1,121 +1,120 @@
using System.Collections.Generic;
namespace WireMock.Admin.Mappings
namespace WireMock.Admin.Mappings;
/// <summary>
/// ResponseModel
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ResponseModel
{
/// <summary>
/// ResponseModel
/// Gets or sets the HTTP status.
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ResponseModel
{
/// <summary>
/// Gets or sets the HTTP status.
/// </summary>
public object? StatusCode { get; set; }
public object? StatusCode { get; set; }
/// <summary>
/// Gets or sets the body destination (SameAsSource, String or Bytes).
/// </summary>
public string? BodyDestination { get; set; }
/// <summary>
/// Gets or sets the body destination (SameAsSource, String or Bytes).
/// </summary>
public string? BodyDestination { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
public string? Body { 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>
/// Gets or sets the body (as JSON object).
/// </summary>
public object? BodyAsJson { get; set; }
/// <summary>
/// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.
/// </summary>
public bool? BodyAsJsonIndented { get; set; }
/// <summary>
/// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.
/// </summary>
public bool? BodyAsJsonIndented { get; set; }
/// <summary>
/// Gets or sets the body (as bytearray).
/// </summary>
public byte[]? BodyAsBytes { get; set; }
/// <summary>
/// Gets or sets the body (as bytearray).
/// </summary>
public byte[]? BodyAsBytes { get; set; }
/// <summary>
/// Gets or sets the body as a file.
/// </summary>
public string? BodyAsFile { get; set; }
/// <summary>
/// Gets or sets the body as a file.
/// </summary>
public string? BodyAsFile { get; set; }
/// <summary>
/// Is the body as file cached?
/// </summary>
public bool? BodyAsFileIsCached { get; set; }
/// <summary>
/// Is the body as file cached?
/// </summary>
public bool? BodyAsFileIsCached { get; set; }
/// <summary>
/// Gets or sets the body encoding.
/// </summary>
public EncodingModel? BodyEncoding { get; set; }
/// <summary>
/// Gets or sets the body encoding.
/// </summary>
public EncodingModel? BodyEncoding { get; set; }
/// <summary>
/// Use ResponseMessage Transformer.
/// </summary>
public bool? UseTransformer { 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; }
/// <summary>
/// Gets the type of the transformer.
/// </summary>
public string? TransformerType { get; set; }
/// <summary>
/// Use the Handlebars transformer for the content from the referenced BodyAsFile.
/// </summary>
public bool? UseTransformerForBodyAsFile { get; set; }
/// <summary>
/// Use the Handlebars transformer for the content from the referenced BodyAsFile.
/// </summary>
public bool? UseTransformerForBodyAsFile { get; set; }
/// <summary>
/// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary>
public string? TransformerReplaceNodeOptions { get; set; }
/// <summary>
/// The ReplaceNodeOptions to use when transforming a JSON node.
/// </summary>
public string? TransformerReplaceNodeOptions { get; set; }
/// <summary>
/// Gets or sets the headers.
/// </summary>
public IDictionary<string, object>? Headers { get; set; }
/// <summary>
/// Gets or sets the headers.
/// </summary>
public IDictionary<string, object>? Headers { get; set; }
/// <summary>
/// Gets or sets the Headers (Raw).
/// </summary>
public string? HeadersRaw { get; set; }
/// <summary>
/// Gets or sets the Headers (Raw).
/// </summary>
public string? HeadersRaw { get; set; }
/// <summary>
/// Gets or sets the delay in milliseconds.
/// </summary>
public int? Delay { get; set; }
/// <summary>
/// Gets or sets the delay in milliseconds.
/// </summary>
public int? Delay { get; set; }
/// <summary>
/// Gets or sets the minimum random delay in milliseconds.
/// </summary>
public int? MinimumRandomDelay { get; set; }
/// <summary>
/// Gets or sets the minimum random delay in milliseconds.
/// </summary>
public int? MinimumRandomDelay { get; set; }
/// <summary>
/// Gets or sets the maximum random delay in milliseconds.
/// </summary>
public int? MaximumRandomDelay { get; set; }
/// <summary>
/// Gets or sets the maximum random delay in milliseconds.
/// </summary>
public int? MaximumRandomDelay { get; set; }
/// <summary>
/// Gets or sets the Proxy URL.
/// </summary>
public string? ProxyUrl { get; set; }
/// <summary>
/// Gets or sets the Proxy URL.
/// </summary>
public string? ProxyUrl { get; set; }
/// <summary>
/// The client X509Certificate2 Thumbprint or SubjectName to use.
/// </summary>
public string? X509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary>
/// The client X509Certificate2 Thumbprint or SubjectName to use.
/// </summary>
public string? X509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary>
/// Gets or sets the fault.
/// </summary>
public FaultModel? Fault { get; set; }
/// <summary>
/// Gets or sets the fault.
/// </summary>
public FaultModel? Fault { get; set; }
/// <summary>
/// Gets or sets the WebProxy settings.
/// </summary>
public WebProxyModel? WebProxy { get; set; }
}
/// <summary>
/// Gets or sets the WebProxy settings.
/// </summary>
public WebProxyModel? WebProxy { get; set; }
}

View File

@@ -31,27 +31,49 @@ public class SettingsModel
public int? MaxRequestLogCount { get; set; }
/// <summary>
/// Allow a Body for all HTTP Methods. (default set to false).
/// Allow a Body for all HTTP Methods. (default set to <c>false</c>).
/// </summary>
public bool? AllowBodyForAllHttpMethods { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to false).
/// Allow only a HttpStatus Code in the response which is defined. (default set to <c>false</c>).
/// - false : also null, 0, empty or invalid HttpStatus codes are allowed.
/// - true : only codes defined in <see cref="System.Net.HttpStatusCode"/> are allowed.
/// </summary>
public bool? AllowOnlyDefinedHttpStatusCodeInResponse { get; set; }
/// <summary>
/// Set to true to disable Json deserialization when processing requests. (default set to <c>false</c>).
/// </summary>
public bool? DisableJsonBodyParsing { get; set; }
/// <summary>
/// Disable support for GZip and Deflate request body decompression. (default set to <c>false</c>).
/// </summary>
public bool? DisableRequestBodyDecompressing { get; set; }
/// <summary>
/// Set to true to disable FormUrlEncoded deserializing when processing requests. (default set to <c>false</c>).
/// </summary>
public bool? DisableDeserializeFormUrlEncoded { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to <c>false</c>).
/// </summary>
public bool? HandleRequestsSynchronously { get; set; }
/// <summary>
/// Throw an exception when the Matcher fails because of invalid input. (default set to false).
/// Throw an exception when the Matcher fails because of invalid input. (default set to <c>false</c>).
/// </summary>
public bool? ThrowExceptionWhenMatcherFails { get; set; }
/// <summary>
/// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to true).
/// Use the RegexExtended instead of the default <see cref="Regex"/>. (default set to <c>true</c>).
/// </summary>
public bool? UseRegexExtended { get; set; }
/// <summary>
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to false).
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/>. (default set to <c>false</c>).
/// </summary>
public bool? SaveUnmatchedRequests { get; set; }
@@ -86,7 +108,7 @@ public class SettingsModel
public HostingScheme? HostingScheme { get; set; }
/// <summary>
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to false).
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to <c>false</c>).
/// </summary>
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }

View File

@@ -0,0 +1,139 @@
using System;
// ReSharper disable once CheckNamespace
namespace WireMock.Admin.Mappings;
/// <summary>
/// RequestModelBuilder
/// </summary>
public partial class RequestModelBuilder
{
/// <summary>
/// UsingConnect: add HTTP Method matching on `CONNECT`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingConnect() => WithMethods("CONNECT");
/// <summary>
/// UsingDelete: add HTTP Method matching on `DELETE`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingDelete() => WithMethods("DELETE");
/// <summary>
/// UsingGet: add HTTP Method matching on `GET`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingGet() => WithMethods("GET");
/// <summary>
/// UsingHead: Add HTTP Method matching on `HEAD`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingHead() => WithMethods("HEAD");
/// <summary>
/// UsingPost: add HTTP Method matching on `POST`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingPost() => WithMethods("POST");
/// <summary>
/// UsingPatch: add HTTP Method matching on `PATCH`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingPatch() => WithMethods("PATCH");
/// <summary>
/// UsingPut: add HTTP Method matching on `OPTIONS`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingOptions() => WithMethods("OPTIONS");
/// <summary>
/// UsingPut: add HTTP Method matching on `PUT`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingPut() => WithMethods("PUT");
/// <summary>
/// UsingTrace: add HTTP Method matching on `TRACE`.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingTrace() => WithMethods("TRACE");
/// <summary>
/// UsingAnyMethod: add HTTP Method matching on any method.
/// </summary>
/// <returns>The <see cref="RequestModelBuilder"/>.</returns>
public RequestModelBuilder UsingAnyMethod() => this;
/// <summary>
/// Set the ClientIP.
/// </summary>
public RequestModelBuilder WithClientIP(string value) => WithClientIP(() => value);
/// <summary>
/// Set the ClientIP.
/// </summary>
public RequestModelBuilder WithClientIP(ClientIPModel value) => WithClientIP(() => value);
/// <summary>
/// Set the ClientIP.
/// </summary>
public RequestModelBuilder WithClientIP(Action<ClientIPModelBuilder> action)
{
return WithClientIP(() =>
{
var builder = new ClientIPModelBuilder();
action(builder);
return builder.Build();
});
}
/// <summary>
/// Set the Path.
/// </summary>
public RequestModelBuilder WithPath(string value) => WithPath(() => value);
/// <summary>
/// Set the Path.
/// </summary>
public RequestModelBuilder WithPath(PathModel value) => WithPath(() => value);
/// <summary>
/// Set the Path.
/// </summary>
public RequestModelBuilder WithPath(Action<PathModelBuilder> action)
{
return WithPath(() =>
{
var builder = new PathModelBuilder();
action(builder);
return builder.Build();
});
}
/// <summary>
/// Set the Url.
/// </summary>
public RequestModelBuilder WithUrl(string value) => WithUrl(() => value);
/// <summary>
/// Set the Url.
/// </summary>
public RequestModelBuilder WithUrl(UrlModel value) => WithUrl(() => value);
/// <summary>
/// Set the Url.
/// </summary>
public RequestModelBuilder WithUrl(Action<UrlModelBuilder> action)
{
return WithUrl(() =>
{
var builder = new UrlModelBuilder();
action(builder);
return builder.Build();
});
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Net;
// ReSharper disable once CheckNamespace
namespace WireMock.Admin.Mappings;
/// <summary>
/// ResponseModelBuilder
/// </summary>
public partial class ResponseModelBuilder
{
/// <summary>
/// Set the StatusCode.
/// </summary>
public ResponseModelBuilder WithStatusCode(int value) => WithStatusCode(() => value);
/// <summary>
/// Set the StatusCode.
/// </summary>
public ResponseModelBuilder WithStatusCode(HttpStatusCode value) => WithStatusCode(() => value);
/// <summary>
/// Set the Delay.
/// </summary>
public ResponseModelBuilder WithDelay(TimeSpan value) => WithDelay((int) value.TotalMilliseconds);
/// <summary>
/// Set the MinimumRandomDelay.
/// </summary>
public ResponseModelBuilder WithMinimumRandomDelay(TimeSpan value) => WithMinimumRandomDelay((int)value.TotalMilliseconds);
/// <summary>
/// Set the MaximumRandomDelay.
/// </summary>
public ResponseModelBuilder WithMaximumRandomDelay(TimeSpan value) => WithMaximumRandomDelay((int)value.TotalMilliseconds);
}

View File

@@ -78,6 +78,11 @@ public interface IRequestMessage
/// </summary>
IDictionary<string, WireMockList<string>>? Query { get; }
/// <summary>
/// Gets the query.
/// </summary>
IDictionary<string, WireMockList<string>>? QueryIgnoreCase { get; }
/// <summary>
/// Gets the raw query.
/// </summary>

View File

@@ -44,7 +44,7 @@ public interface IResponseMessage
/// Gets or sets the status code.
/// </summary>
object? StatusCode { get; }
/// <summary>
/// Adds the header.
/// </summary>

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Text;
using WireMock.Types;
@@ -38,6 +39,11 @@ public interface IBodyData
/// </summary>
string? BodyAsString { get; set; }
/// <summary>
/// The body as Form UrlEncoded dictionary.
/// </summary>
IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
/// <summary>
/// The detected body type (detection based on body content).
/// </summary>

View File

@@ -3,212 +3,227 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using WireMock.Admin.Mappings;
using WireMock.Logging;
using WireMock.Types;
namespace WireMock.Server
namespace WireMock.Server;
/// <summary>
/// The fluent mock server interface.
/// </summary>
public interface IWireMockServer : IDisposable
{
/// <summary>
/// The fluent mock server interface.
/// Gets a value indicating whether this server is started.
/// </summary>
public interface IWireMockServer : IDisposable
{
/// <summary>
/// Gets a value indicating whether this server is started.
/// </summary>
bool IsStarted { get; }
bool IsStarted { get; }
/// <summary>
/// Gets the request logs.
/// </summary>
IEnumerable<ILogEntry> LogEntries { get; }
/// <summary>
/// Gets the request logs.
/// </summary>
IEnumerable<ILogEntry> LogEntries { get; }
/// <summary>
/// Gets the mappings as MappingModels.
/// </summary>
IEnumerable<MappingModel> MappingModels { get; }
/// <summary>
/// Gets the mappings as MappingModels.
/// </summary>
IEnumerable<MappingModel> MappingModels { get; }
// <summary>
// Gets the mappings.
// </summary>
//[PublicAPI]
//IEnumerable<IMapping> Mappings { get; }
// <summary>
// Gets the mappings.
// </summary>
//[PublicAPI]
//IEnumerable<IMapping> Mappings { get; }
/// <summary>
/// Gets the ports.
/// </summary>
List<int> Ports { get; }
/// <summary>
/// Gets the ports.
/// </summary>
List<int> Ports { get; }
/// <summary>
/// Gets the first port.
/// </summary>
int Port { get; }
/// <summary>
/// Gets the first port.
/// </summary>
int Port { get; }
/// <summary>
/// Gets the urls.
/// </summary>
string[] Urls { get; }
/// <summary>
/// Gets the urls.
/// </summary>
string[] Urls { get; }
/// <summary>
/// Gets the first url.
/// </summary>
string? Url { get; }
/// <summary>
/// Gets the first url.
/// </summary>
string? Url { get; }
/// <summary>
/// Gets the consumer.
/// </summary>
string? Consumer { get; }
/// <summary>
/// Gets the consumer.
/// </summary>
string? Consumer { get; }
/// <summary>
/// Gets the provider.
/// </summary>
string? Provider { get; }
/// <summary>
/// Gets the provider.
/// </summary>
string? Provider { get; }
//ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
//ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
/// <summary>
/// Occurs when [log entries changed].
/// </summary>
event NotifyCollectionChangedEventHandler LogEntriesChanged;
/// <summary>
/// Occurs when [log entries changed].
/// </summary>
event NotifyCollectionChangedEventHandler LogEntriesChanged;
/// <summary>
/// Adds a 'catch all mapping'
///
/// - matches all Paths and any Methods
/// - priority is set to 1000
/// - responds with a 404 "No matching mapping found"
/// </summary>
void AddCatchAllMapping();
/// <summary>
/// Adds a 'catch all mapping'
///
/// - matches all Paths and any Methods
/// - priority is set to 1000
/// - responds with a 404 "No matching mapping found"
/// </summary>
void AddCatchAllMapping();
/// <summary>
/// The add request processing delay.
/// </summary>
/// <param name="delay">The delay.</param>
void AddGlobalProcessingDelay(TimeSpan delay);
/// <summary>
/// The add request processing delay.
/// </summary>
/// <param name="delay">The delay.</param>
void AddGlobalProcessingDelay(TimeSpan delay);
/// <summary>
/// Allows the partial mapping.
/// </summary>
void AllowPartialMapping(bool allow = true);
/// <summary>
/// Allows the partial mapping.
/// </summary>
void AllowPartialMapping(bool allow = true);
/// <summary>
/// Deletes a LogEntry.
/// </summary>
/// <param name="guid">The unique identifier.</param>
bool DeleteLogEntry(Guid guid);
/// <summary>
/// Deletes a LogEntry.
/// </summary>
/// <param name="guid">The unique identifier.</param>
bool DeleteLogEntry(Guid guid);
/// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
bool DeleteMapping(Guid guid);
/// <summary>
/// Deletes the mapping.
/// </summary>
/// <param name="guid">The unique identifier.</param>
bool DeleteMapping(Guid guid);
//IEnumerable<LogEntry> FindLogEntries([NotNull] params IRequestMatcher[] matchers);
//IEnumerable<LogEntry> FindLogEntries([NotNull] params IRequestMatcher[] matchers);
// IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
// IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
/// <summary>
/// Reads a static mapping file and adds or updates a single mapping.
///
/// Calling this method manually forces WireMock.Net to read and apply the specified static mapping file.
/// </summary>
/// <param name="path">The path to the static mapping file.</param>
bool ReadStaticMappingAndAddOrUpdate(string path);
/// <summary>
/// Reads a static mapping file and adds or updates a single mapping.
///
/// Calling this method manually forces WireMock.Net to read and apply the specified static mapping file.
/// </summary>
/// <param name="path">The path to the static mapping file.</param>
bool ReadStaticMappingAndAddOrUpdate(string path);
/// <summary>
/// Reads the static mappings from a folder.
/// (This method is also used when WireMockServerSettings.ReadStaticMappings is set to true.
///
/// Calling this method manually forces WireMock.Net to read and apply all static mapping files in the specified folder.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void ReadStaticMappings(string? folder = null);
/// <summary>
/// Reads the static mappings from a folder.
/// (This method is also used when WireMockServerSettings.ReadStaticMappings is set to true.
///
/// Calling this method manually forces WireMock.Net to read and apply all static mapping files in the specified folder.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void ReadStaticMappings(string? folder = null);
/// <summary>
/// Removes the authentication.
/// </summary>
void RemoveAuthentication();
/// <summary>
/// Removes the authentication.
/// </summary>
void RemoveAuthentication();
/// <summary>
/// Resets LogEntries and Mappings.
/// </summary>
void Reset();
/// <summary>
/// Resets LogEntries and Mappings.
/// </summary>
void Reset();
/// <summary>
/// Resets the Mappings.
/// </summary>
void ResetMappings();
/// <summary>
/// Resets the Mappings.
/// </summary>
void ResetMappings();
/// <summary>
/// Resets all Scenarios.
/// </summary>
void ResetScenarios();
/// <summary>
/// Resets all Scenarios.
/// </summary>
void ResetScenarios();
/// <summary>
/// Resets a specific Scenario by the name.
/// </summary>
bool ResetScenario(string name);
/// <summary>
/// Resets a specific Scenario by the name.
/// </summary>
bool ResetScenario(string name);
/// <summary>
/// Resets the LogEntries.
/// </summary>
void ResetLogEntries();
/// <summary>
/// Resets the LogEntries.
/// </summary>
void ResetLogEntries();
/// <summary>
/// Saves the static mappings.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void SaveStaticMappings(string? folder = null);
/// <summary>
/// Saves the static mappings.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void SaveStaticMappings(string? folder = null);
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="tenant">The Tenant.</param>
/// <param name="audience">The Audience or Resource.</param>
void SetAzureADAuthentication(string tenant, string audience);
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="tenant">The Tenant.</param>
/// <param name="audience">The Audience or Resource.</param>
void SetAzureADAuthentication(string tenant, string audience);
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
void SetBasicAuthentication(string username, string password);
/// <summary>
/// Sets the basic authentication.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
void SetBasicAuthentication(string username, string password);
/// <summary>
/// Sets the maximum RequestLog count.
/// </summary>
/// <param name="maxRequestLogCount">The maximum RequestLog count.</param>
void SetMaxRequestLogCount(int? maxRequestLogCount);
/// <summary>
/// Sets the maximum RequestLog count.
/// </summary>
/// <param name="maxRequestLogCount">The maximum RequestLog count.</param>
void SetMaxRequestLogCount(int? maxRequestLogCount);
/// <summary>
/// Sets RequestLog expiration in hours.
/// </summary>
/// <param name="requestLogExpirationDuration">The RequestLog expiration in hours.</param>
void SetRequestLogExpirationDuration(int? requestLogExpirationDuration);
/// <summary>
/// Sets RequestLog expiration in hours.
/// </summary>
/// <param name="requestLogExpirationDuration">The RequestLog expiration in hours.</param>
void SetRequestLogExpirationDuration(int? requestLogExpirationDuration);
/// <summary>
/// Stop this server.
/// </summary>
void Stop();
/// <summary>
/// Stop this server.
/// </summary>
void Stop();
/// <summary>
/// Watches the static mappings for changes.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void WatchStaticMappings(string? folder = null);
/// <summary>
/// Watches the static mappings for changes.
/// </summary>
/// <param name="folder">The optional folder. If not defined, use {CurrentFolder}/__admin/mappings</param>
void WatchStaticMappings(string? folder = null);
/// <summary>
/// Register the mappings (via <see cref="MappingModel"/>).
///
/// This can be used if you have 1 or more <see cref="MappingModel"/> defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// </summary>
/// <param name="mappings">The MappingModels</param>
IWireMockServer WithMapping(params MappingModel[] mappings);
/// <summary>
/// Register the mappings (via <see cref="MappingModel"/>).
///
/// This can be used if you have 1 or more <see cref="MappingModel"/> defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// </summary>
/// <param name="mappings">The MappingModels</param>
IWireMockServer WithMapping(params MappingModel[] mappings);
/// <summary>
/// Register the mappings (via json string).
///
/// This can be used if you the mappings as json string defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// </summary>
/// <param name="mappings">The mapping(s) as json string.</param>
IWireMockServer WithMapping(string mappings);
}
/// <summary>
/// Register the mappings (via json string).
///
/// This can be used if you the mappings as json string defined and want to register these in WireMock.Net directly instead of using the fluent syntax.
/// </summary>
/// <param name="mappings">The mapping(s) as json string.</param>
IWireMockServer WithMapping(string mappings);
/// <summary>
/// Get the C# code for a mapping.
/// </summary>
/// <param name="guid">The Mapping Guid.</param>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code (null in case the mapping is not found)</returns>
string? MappingToCSharpCode(Guid guid, MappingConverterType converterType);
/// <summary>
/// Get the C# code for all mappings.
/// </summary>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code</returns>
public string MappingsToCSharpCode(MappingConverterType converterType);
}

View File

@@ -1,38 +1,42 @@
namespace WireMock.Types
namespace WireMock.Types;
/// <summary>
/// The BodyType
/// </summary>
public enum BodyType
{
/// <summary>
/// The BodyType
/// No body present
/// </summary>
public enum BodyType
{
/// <summary>
/// No body present
/// </summary>
None,
None,
/// <summary>
/// Body is a String
/// </summary>
String,
/// <summary>
/// Body is a String
/// </summary>
String,
/// <summary>
/// Body is a Json object
/// </summary>
Json,
/// <summary>
/// Body is a Json object
/// </summary>
Json,
/// <summary>
/// Body is a Byte array
/// </summary>
Bytes,
/// <summary>
/// Body is a Byte array
/// </summary>
Bytes,
/// <summary>
/// Body is a File
/// </summary>
File,
/// <summary>
/// Body is a File
/// </summary>
File,
/// <summary>
/// Body is a MultiPart
/// </summary>
MultiPart
}
/// <summary>
/// Body is a MultiPart
/// </summary>
MultiPart,
/// <summary>
/// Body is a String which is x-www-form-urlencoded.
/// </summary>
FormUrlEncoded
}

View File

@@ -0,0 +1,11 @@
namespace WireMock.Types;
/// <summary>
///
/// </summary>
public enum MappingConverterType
{
Server,
Builder
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Types;
@@ -63,7 +64,8 @@ public class WireMockList<T> : List<T>
return this[0]?.ToString();
default:
return base.ToString();
var strings = this.Select(x => x as string ?? x?.ToString());
return string.Join(", ", strings);
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Admin.Mappings;
namespace WireMock.Client.Builders;
/// <summary>
/// AdminApiMappingBuilder
/// </summary>
public class AdminApiMappingBuilder
{
private readonly List<Action<MappingModelBuilder>> _mappingModelBuilderActions = new();
private readonly IWireMockAdminApi _api;
/// <summary>
/// AdminApiMappingBuilder
/// </summary>
/// <param name="api">The <see cref="IWireMockAdminApi"/>.</param>
public AdminApiMappingBuilder(IWireMockAdminApi api)
{
_api = Guard.NotNull(api);
}
/// <summary>
/// The Given
/// </summary>
/// <param name="mappingModelBuilderAction">The action.</param>
public void Given(Action<MappingModelBuilder> mappingModelBuilderAction)
{
_mappingModelBuilderActions.Add(Guard.NotNull(mappingModelBuilderAction));
}
/// <summary>
/// Build the mappings and post these using the <see cref="IWireMockAdminApi"/> to the WireMock.Net server.
/// </summary>
/// <param name="cancellationToken">The optional CancellationToken.</param>
/// <returns><see cref="StatusModel"/></returns>
public Task<StatusModel> BuildAndPostAsync(CancellationToken cancellationToken = default)
{
var modelMappings = new List<MappingModel>();
foreach (var mappingModelBuilderAction in _mappingModelBuilderActions)
{
cancellationToken.ThrowIfCancellationRequested();
var mappingModelBuilder = new MappingModelBuilder();
mappingModelBuilderAction(mappingModelBuilder);
modelMappings.Add(mappingModelBuilder.Build());
}
return _api.PostMappingsAsync(modelMappings, cancellationToken);
}
}

View File

@@ -0,0 +1,46 @@
using System.Text;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using WireMock.Admin.Mappings;
namespace WireMock.Client.Extensions;
/// <summary>
/// ResponseModelBuilder
/// </summary>
public static class ResponseModelBuilderExtensions
{
private static readonly Encoding Utf8NoBom = new UTF8Encoding(false);
private static readonly IJsonConverter JsonConverter = new NewtonsoftJsonConverter();
/// <summary>
/// WithBodyAsJson
/// </summary>
/// <param name="builder">The ResponseModelBuilder.</param>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param>
/// <param name="indented">Define whether child objects to be indented.</param>
public static ResponseModelBuilder WithBodyAsJson(this ResponseModelBuilder builder, object body, Encoding? encoding = null, bool? indented = null)
{
return builder.WithBodyAsBytes(() =>
{
var options = new JsonConverterOptions
{
WriteIndented = indented == true
};
var jsonBody = JsonConverter.Serialize(body, options);
return (encoding ?? Utf8NoBom).GetBytes(jsonBody);
});
}
/// <summary>
/// WithBodyAsJson
/// </summary>
/// <param name="builder">The ResponseModelBuilder.</param>
/// <param name="body">The body.</param>
/// <param name="indented">Define whether child objects to be indented.</param>
public static ResponseModelBuilder WithBodyAsJson(this ResponseModelBuilder builder, object body, bool indented)
{
return builder.WithBodyAsJson(body, null, indented);
}
}

View File

@@ -0,0 +1,19 @@
using WireMock.Client.Builders;
namespace WireMock.Client.Extensions;
/// <summary>
/// Some extensions for <see cref="IWireMockAdminApi"/>.
/// </summary>
public static class WireMockAdminApiExtensions
{
/// <summary>
/// Get a new <see cref="AdminApiMappingBuilder"/> for the <see cref="IWireMockAdminApi"/>.
/// </summary>
/// <param name="api">See <see cref="IWireMockAdminApi"/>.</param>
/// <returns></returns>
public static AdminApiMappingBuilder GetMappingBuilder(this IWireMockAdminApi api)
{
return new AdminApiMappingBuilder(api);
}
}

View File

@@ -1,233 +1,283 @@
using RestEase;
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using RestEase;
using WireMock.Admin.Mappings;
using WireMock.Admin.Requests;
using WireMock.Admin.Scenarios;
using WireMock.Admin.Settings;
using WireMock.Types;
namespace WireMock.Client
namespace WireMock.Client;
/// <summary>
/// The RestEase interface which defines all admin commands.
/// </summary>
[BasePath("__admin")]
public interface IWireMockAdminApi
{
/// <summary>
/// The RestEase interface which defines all admin commands.
/// Authentication header
/// </summary>
[BasePath("__admin")]
public interface IWireMockAdminApi
{
/// <summary>
/// Authentication header
/// </summary>
[Header("Authorization")]
AuthenticationHeaderValue Authorization { get; set; }
[Header("Authorization")]
AuthenticationHeaderValue Authorization { get; set; }
/// <summary>
/// Get the settings.
/// </summary>
/// <returns>SettingsModel</returns>
[Get("settings")]
Task<SettingsModel> GetSettingsAsync();
/// <summary>
/// Get the settings.
/// </summary>
/// <returns>SettingsModel</returns>
[Get("settings")]
Task<SettingsModel> GetSettingsAsync();
/// <summary>
/// Update the settings.
/// </summary>
/// <param name="settings">SettingsModel</param>
[Put("settings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PutSettingsAsync([Body] SettingsModel settings);
/// <summary>
/// Update the settings.
/// </summary>
/// <param name="settings">SettingsModel</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Put("settings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PutSettingsAsync([Body] SettingsModel settings, CancellationToken cancellationToken = default);
/// <summary>
/// Update the settings
/// </summary>
/// <param name="settings">SettingsModel</param>
[Post("settings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PostSettingsAsync([Body] SettingsModel settings);
/// <summary>
/// Update the settings
/// </summary>
/// <param name="settings">SettingsModel</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("settings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PostSettingsAsync([Body] SettingsModel settings, CancellationToken cancellationToken = default);
/// <summary>
/// Get the mappings.
/// </summary>
/// <returns>MappingModels</returns>
[Get("mappings")]
Task<IList<MappingModel>> GetMappingsAsync();
/// <summary>
/// Get the mappings.
/// </summary>
/// <returns>MappingModels</returns>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("mappings")]
Task<IList<MappingModel>> GetMappingsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Add a new mapping.
/// </summary>
/// <param name="mapping">MappingModel</param>
[Post("mappings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PostMappingAsync([Body] MappingModel mapping);
/// <summary>
/// Get the C# code from all mappings
/// </summary>
/// <returns>C# code</returns>
/// <param name="mappingConverterType">The <see cref="MappingConverterType"/>, default is Server.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("mappings/code")]
Task<string> GetMappingsCodeAsync([Query] MappingConverterType mappingConverterType = MappingConverterType.Server, CancellationToken cancellationToken = default);
/// <summary>
/// Add new mappings.
/// </summary>
/// <param name="mappings">MappingModels</param>
[Post("mappings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PostMappingsAsync([Body] IList<MappingModel> mappings);
/// <summary>
/// Add a new mapping.
/// </summary>
/// <param name="mapping">MappingModel</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("mappings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PostMappingAsync([Body] MappingModel mapping, CancellationToken cancellationToken = default);
/// <summary>
/// Delete all mappings.
/// </summary>
[Delete("mappings")]
Task<StatusModel> DeleteMappingsAsync();
/// <summary>
/// Add new mappings.
/// </summary>
/// <param name="mappings">MappingModels</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("mappings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PostMappingsAsync([Body] IList<MappingModel> mappings, CancellationToken cancellationToken = default);
/// <summary>
/// Delete mappings according to GUIDs
/// </summary>
/// <param name="mappings">MappingModels</param>
[Delete("mappings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> DeleteMappingsAsync([Body] IList<MappingModel> mappings);
/// <summary>
/// Delete all mappings.
/// </summary>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("mappings")]
Task<StatusModel> DeleteMappingsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Delete (reset) all mappings.
/// </summary>
/// <param name="reloadStaticMappings">A value indicating whether to reload the static mappings after the reset.</param>
[Post("mappings/reset")]
Task<StatusModel> ResetMappingsAsync(bool? reloadStaticMappings = false);
/// <summary>
/// Delete mappings according to GUIDs
/// </summary>
/// <param name="mappings">MappingModels</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("mappings")]
[Header("Content-Type", "application/json")]
Task<StatusModel> DeleteMappingsAsync([Body] IList<MappingModel> mappings, CancellationToken cancellationToken = default);
/// <summary>
/// Get a mapping based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <returns>MappingModel</returns>
[Get("mappings/{guid}")]
Task<MappingModel> GetMappingAsync([Path] Guid guid);
/// <summary>
/// Delete (reset) all mappings.
/// </summary>
/// <param name="reloadStaticMappings">A value indicating whether to reload the static mappings after the reset.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("mappings/reset")]
Task<StatusModel> ResetMappingsAsync(bool? reloadStaticMappings = false, CancellationToken cancellationToken = default);
/// <summary>
/// Update a mapping based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <param name="mapping">MappingModel</param>
[Put("mappings/{guid}")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PutMappingAsync([Path] Guid guid, [Body] MappingModel mapping);
/// <summary>
/// Get a mapping based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <returns>MappingModel</returns>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("mappings/{guid}")]
Task<MappingModel> GetMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary>
/// Delete a mapping based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
[Delete("mappings/{guid}")]
Task<StatusModel> DeleteMappingAsync([Path] Guid guid);
/// <summary>
/// Get the C# code from a mapping based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <param name="mappingConverterType">The optional mappingConverterType (can be Server or Builder)</param>
/// <returns>C# code</returns>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("mappings/code/{guid}")]
Task<string> GetMappingCodeAsync([Path] Guid guid, [Query] MappingConverterType mappingConverterType = MappingConverterType.Server, CancellationToken cancellationToken = default);
/// <summary>
/// Save the mappings
/// </summary>
[Post("mappings/save")]
Task<StatusModel> SaveMappingAsync();
/// <summary>
/// Update a mapping based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <param name="mapping">MappingModel</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Put("mappings/{guid}")]
[Header("Content-Type", "application/json")]
Task<StatusModel> PutMappingAsync([Path] Guid guid, [Body] MappingModel mapping, CancellationToken cancellationToken = default);
/// <summary>
/// Get the requests.
/// </summary>
/// <returns>LogRequestModels</returns>
[Get("requests")]
Task<IList<LogEntryModel>> GetRequestsAsync();
/// <summary>
/// Delete a mapping based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("mappings/{guid}")]
Task<StatusModel> DeleteMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary>
/// Delete all requests.
/// </summary>
[Delete("requests")]
Task<StatusModel> DeleteRequestsAsync();
/// <summary>
/// Save the mappings
/// </summary>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("mappings/save")]
Task<StatusModel> SaveMappingAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Delete (reset) all requests.
/// </summary>
[Post("requests/reset")]
Task<StatusModel> ResetRequestsAsync();
/// <summary>
/// Get the requests.
/// </summary>
/// <returns>LogRequestModels</returns>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("requests")]
Task<IList<LogEntryModel>> GetRequestsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Get a request based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <returns>MappingModel</returns>
[Get("requests/{guid}")]
Task<LogEntryModel> GetRequestAsync([Path] Guid guid);
/// <summary>
/// Delete all requests.
/// </summary>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("requests")]
Task<StatusModel> DeleteRequestsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Delete a request based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
[Delete("requests/{guid}")]
Task<StatusModel> DeleteRequestAsync([Path] Guid guid);
/// <summary>
/// Delete (reset) all requests.
/// </summary>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("requests/reset")]
Task<StatusModel> ResetRequestsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Find a request based on the criteria
/// </summary>
/// <param name="model">The RequestModel</param>
[Post("requests/find")]
[Header("Content-Type", "application/json")]
Task<IList<LogEntryModel>> FindRequestsAsync([Body] RequestModel model);
/// <summary>
/// Get a request based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <returns>MappingModel</returns>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("requests/{guid}")]
Task<LogEntryModel> GetRequestAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary>
/// Get all scenarios
/// </summary>
[Get("scenarios")]
Task<IList<ScenarioStateModel>> GetScenariosAsync();
/// <summary>
/// Delete a request based on the guid
/// </summary>
/// <param name="guid">The Guid</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("requests/{guid}")]
Task<StatusModel> DeleteRequestAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary>
/// Delete (reset) all scenarios
/// </summary>
[Delete("scenarios")]
Task<StatusModel> DeleteScenariosAsync();
/// <summary>
/// Find a request based on the criteria
/// </summary>
/// <param name="model">The RequestModel</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("requests/find")]
[Header("Content-Type", "application/json")]
Task<IList<LogEntryModel>> FindRequestsAsync([Body] RequestModel model, CancellationToken cancellationToken = default);
/// <summary>
/// Delete (reset) all scenarios
/// </summary>
[Post("scenarios")]
Task<StatusModel> ResetScenariosAsync();
/// <summary>
/// Get all scenarios
/// </summary>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("scenarios")]
Task<IList<ScenarioStateModel>> GetScenariosAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Delete (reset) a specific scenario
/// </summary>
[Delete("scenarios/{name}")]
[AllowAnyStatusCode]
Task<StatusModel> DeleteScenarioAsync([Path] string name);
/// <summary>
/// Delete (reset) all scenarios
/// </summary>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("scenarios")]
Task<StatusModel> DeleteScenariosAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Delete (reset) all scenarios
/// </summary>
[Post("scenarios/{name}/reset")]
[AllowAnyStatusCode]
Task<StatusModel> ResetScenarioAsync([Path] string name);
/// <summary>
/// Delete (reset) all scenarios
/// </summary>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("scenarios")]
Task<StatusModel> ResetScenariosAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Create a new File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="body">The body</param>
[Post("files/{filename}")]
Task<StatusModel> PostFileAsync([Path] string filename, [Body] string body);
/// <summary>
/// Delete (reset) a specific scenario
/// </summary>
/// <param name="name">Scenario name.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("scenarios/{name}")]
[AllowAnyStatusCode]
Task<StatusModel> DeleteScenarioAsync([Path] string name, CancellationToken cancellationToken = default);
/// <summary>
/// Update an existing File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="body">The body</param>
[Put("files/{filename}")]
Task<StatusModel> PutFileAsync([Path] string filename, [Body] string body);
/// <summary>
/// Delete (reset) all scenarios
/// </summary>
/// <param name="name">Scenario name.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("scenarios/{name}/reset")]
[AllowAnyStatusCode]
Task<StatusModel> ResetScenarioAsync([Path] string name, CancellationToken cancellationToken = default);
/// <summary>
/// Get the content of an existing File
/// </summary>
/// <param name="filename">The filename</param>
[Get("files/{filename}")]
Task<string> GetFileAsync([Path] string filename);
/// <summary>
/// Create a new File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="body">The body</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("files/{filename}")]
Task<StatusModel> PostFileAsync([Path] string filename, [Body] string body, CancellationToken cancellationToken = default);
/// <summary>
/// Delete an existing File
/// </summary>
/// <param name="filename">The filename</param>
[Delete("files/{filename}")]
Task<StatusModel> DeleteFileAsync([Path] string filename);
/// <summary>
/// Update an existing File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="body">The body</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Put("files/{filename}")]
Task<StatusModel> PutFileAsync([Path] string filename, [Body] string body, CancellationToken cancellationToken = default);
/// <summary>
/// Check if a file exists
/// </summary>
/// <param name="filename">The filename</param>
[Head("files/{filename}")]
Task FileExistsAsync([Path] string filename);
}
/// <summary>
/// Get the content of an existing File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Get("files/{filename}")]
Task<string> GetFileAsync([Path] string filename, CancellationToken cancellationToken = default);
/// <summary>
/// Delete an existing File
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Delete("files/{filename}")]
Task<StatusModel> DeleteFileAsync([Path] string filename, CancellationToken cancellationToken = default);
/// <summary>
/// Check if a file exists
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Head("files/{filename}")]
Task FileExistsAsync([Path] string filename, CancellationToken cancellationToken = default);
}

View File

@@ -30,8 +30,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.3.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="RestEase" Version="1.5.7" />
<PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -7,4 +7,5 @@ internal static class WireMockConstants
public const int ProxyPriority = -2_000_000;
public const string ContentTypeJson = "application/json";
public const string ContentTypeTextPlain = "text/plain";
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Reflection;
namespace WireMock.Extensions;
internal static class EnumExtensions
{
public static string GetFullyQualifiedEnumValue<T>(this T enumValue)
where T : struct, IConvertible
{
var type = typeof(T);
if (!type.GetTypeInfo().IsEnum)
{
throw new ArgumentException("T must be an enum");
}
return $"{type.Namespace}.{type.Name}.{enumValue}";
}
}

View File

@@ -35,9 +35,14 @@ internal static class HttpClientBuilder
{
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
var x509Certificate2 = CertificateLoader.LoadCertificate(settings.ClientX509Certificate2ThumbprintOrSubjectName);
var x509Certificate2 = CertificateLoader.LoadCertificate(settings.ClientX509Certificate2ThumbprintOrSubjectName!);
handler.ClientCertificates.Add(x509Certificate2);
}
else if (settings.Certificate != null)
{
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(settings.Certificate);
}
handler.AllowAutoRedirect = settings.AllowAutoRedirect == true;

View File

@@ -28,7 +28,7 @@ internal static class HttpRequestMessageHelper
switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.Bytes:
httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes, contentType);
httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes!, contentType);
break;
case BodyType.Json:
@@ -36,7 +36,8 @@ internal static class HttpRequestMessageHelper
break;
case BodyType.String:
httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString, contentType);
case BodyType.FormUrlEncoded:
httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType);
break;
}

View File

@@ -15,7 +15,8 @@ internal static class HttpResponseMessageHelper
Uri requiredUri,
Uri originalUri,
bool deserializeJson,
bool decompressGzipAndDeflate)
bool decompressGzipAndDeflate,
bool deserializeFormUrlEncoded)
{
var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode };
@@ -44,7 +45,8 @@ internal static class HttpResponseMessageHelper
ContentType = contentTypeHeader?.FirstOrDefault(),
DeserializeJson = deserializeJson,
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = decompressGzipAndDeflate
DecompressGZipAndDeflate = decompressGzipAndDeflate,
DeserializeFormUrlEncoded = deserializeFormUrlEncoded
};
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
}
@@ -55,7 +57,7 @@ internal static class HttpResponseMessageHelper
// If Location header contains absolute redirect URL, and base URL is one that we proxy to,
// we need to replace it to original one.
if (string.Equals(header.Key, HttpKnownHeaderNames.Location, StringComparison.OrdinalIgnoreCase)
&& Uri.TryCreate(header.Value.First(), UriKind.Absolute, out Uri absoluteLocationUri)
&& Uri.TryCreate(header.Value.First(), UriKind.Absolute, out var absoluteLocationUri)
&& string.Equals(absoluteLocationUri.Host, requiredUri.Host, StringComparison.OrdinalIgnoreCase))
{
var replacedLocationUri = new Uri(originalUri, absoluteLocationUri.PathAndQuery);

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using WireMock.Matchers.Request;
using WireMock.Models;
@@ -120,7 +121,16 @@ public interface IMapping
/// <summary>
/// Use Fire and Forget for the defined webhook(s). [Optional]
/// </summary>
public bool? UseWebhooksFireAndForget { get; set; }
bool? UseWebhooksFireAndForget { get; set; }
/// <summary>
/// Data Object which can be used when WithTransformer is used.
/// e.g. lookup an path in this object using
/// <example>
/// lookup data "1"
/// </example>
/// </summary>
object? Data { get; set; }
/// <summary>
/// ProvideResponseAsync

View File

@@ -0,0 +1,60 @@
using System;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Server;
using WireMock.Types;
namespace WireMock;
/// <summary>
/// IMappingBuilder
/// </summary>
public interface IMappingBuilder
{
/// <summary>
/// The given.
/// </summary>
/// <param name="requestMatcher">The request matcher.</param>
/// <param name="saveToFile">Optional boolean to indicate if this mapping should be saved as static mapping file.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
/// <summary>
/// Gets all the mappings as a list.
/// </summary>
/// <returns>A list from <see cref="MappingModel"/>s.</returns>
MappingModel[] GetMappings();
/// <summary>
/// Convert all mappings to JSON.
/// </summary>
/// <returns>JSON</returns>
string ToJson();
/// <summary>
/// Save all mappings as a single JSON to a file.
/// </summary>
/// <param name="path">The file to write to.</param>
void SaveMappingsToFile(string path);
/// <summary>
/// Save all mappings as multiple JSON files (each file is 1 mapping).
/// </summary>
/// <param name="folder">The folder to write the files to.</param>
void SaveMappingsToFolder(string folder);
/// <summary>
/// Get the C# code for a mapping.
/// </summary>
/// <param name="guid">The Mapping Guid.</param>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code (null in case the mapping is not found)</returns>
string? ToCSharpCode(Guid guid, MappingConverterType converterType);
/// <summary>
/// Get the C# code for all mappings.
/// </summary>
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
/// <returns>C# code</returns>
public string ToCSharpCode(MappingConverterType converterType);
}

View File

@@ -0,0 +1,10 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
namespace WireMock.Json;
internal class DynamicJsonClassOptions
{
public IntegerBehavior IntegerConvertBehavior { get; set; } = IntegerBehavior.UseLong;
public FloatBehavior FloatConvertBehavior { get; set; } = FloatBehavior.UseDouble;
}

View File

@@ -0,0 +1,15 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
using System.Linq.Dynamic.Core;
namespace WireMock.Json;
public class DynamicPropertyWithValue : DynamicProperty
{
public object? Value { get; }
public DynamicPropertyWithValue(string name, object? value) : base(name, value?.GetType() ?? typeof(object))
{
Value = value;
}
}

View File

@@ -0,0 +1,24 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
namespace WireMock.Json;
/// <summary>
/// Enum to define how to convert an Float in the Json Object.
/// </summary>
internal enum FloatBehavior
{
/// <summary>
/// Convert all Float types in the Json Object to a double. (default)
/// </summary>
UseDouble = 0,
/// <summary>
/// Convert all Float types in the Json Object to a float (unless overflow).
/// </summary>
UseFloat = 1,
/// <summary>
/// Convert all Float types in the Json Object to a decimal (unless overflow).
/// </summary>
UseDecimal = 2
}

View File

@@ -0,0 +1,20 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
namespace WireMock.Json;
/// <summary>
/// Enum to define how to convert an Integer in the Json Object.
/// </summary>
internal enum IntegerBehavior
{
/// <summary>
/// Convert all Integer types in the Json Object to a int (unless overflow).
/// (default)
/// </summary>
UseInt = 0,
/// <summary>
/// Convert all Integer types in the Json Object to a long.
/// </summary>
UseLong = 1
}

View File

@@ -0,0 +1,202 @@
// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Reflection;
using Newtonsoft.Json.Linq;
namespace WireMock.Json;
internal static class JObjectExtensions
{
private class JTokenResolvers : Dictionary<JTokenType, Func<JToken, DynamicJsonClassOptions?, object?>>
{
}
private static readonly JTokenResolvers Resolvers = new()
{
{ JTokenType.Array, ConvertJTokenArray },
{ JTokenType.Boolean, (jToken, _) => jToken.Value<bool>() },
{ JTokenType.Bytes, (jToken, _) => jToken.Value<byte[]>() },
{ JTokenType.Date, (jToken, _) => jToken.Value<DateTime>() },
{ JTokenType.Float, ConvertJTokenFloat },
{ JTokenType.Guid, (jToken, _) => jToken.Value<Guid>() },
{ JTokenType.Integer, ConvertJTokenInteger },
{ JTokenType.None, (_, _) => null },
{ JTokenType.Null, (_, _) => null },
{ JTokenType.Object, ConvertJObject },
{ JTokenType.Property, ConvertJTokenProperty },
{ JTokenType.String, (jToken, _) => jToken.Value<string>() },
{ JTokenType.TimeSpan, (jToken, _) => jToken.Value<TimeSpan>() },
{ JTokenType.Undefined, (_, _) => null },
{ JTokenType.Uri, (o, _) => o.Value<Uri>() },
};
internal static DynamicClass? ToDynamicJsonClass(this JObject? src, DynamicJsonClassOptions? options = null)
{
if (src == null)
{
return null;
}
var dynamicPropertyWithValues = new List<DynamicPropertyWithValue>();
foreach (var prop in src.Properties())
{
var value = Resolvers[prop.Type](prop.Value, options);
if (value != null)
{
dynamicPropertyWithValues.Add(new DynamicPropertyWithValue(prop.Name, value));
}
}
return CreateInstance(dynamicPropertyWithValues);
}
internal static IEnumerable ToDynamicClassArray(this JArray? src, DynamicJsonClassOptions? options = null)
{
if (src == null)
{
return EmptyArray<object?>.Value;
}
return ConvertJTokenArray(src, options);
}
private static object? ConvertJObject(JToken arg, DynamicJsonClassOptions? options = null)
{
if (arg is JObject asJObject)
{
return asJObject.ToDynamicJsonClass(options);
}
return GetResolverFor(arg)(arg, options);
}
private static object PassThrough(JToken arg, DynamicJsonClassOptions? options)
{
return arg;
}
private static Func<JToken, DynamicJsonClassOptions?, object?> GetResolverFor(JToken arg)
{
return Resolvers.TryGetValue(arg.Type, out var result) ? result : PassThrough;
}
private static object ConvertJTokenFloat(JToken arg, DynamicJsonClassOptions? options = null)
{
if (arg.Type != JTokenType.Float)
{
throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to double or float.");
}
if (options?.FloatConvertBehavior == FloatBehavior.UseFloat)
{
try
{
return arg.Value<float>();
}
catch
{
return arg.Value<double>();
}
}
if (options?.FloatConvertBehavior == FloatBehavior.UseDecimal)
{
try
{
return arg.Value<decimal>();
}
catch
{
return arg.Value<double>();
}
}
return arg.Value<double>();
}
private static object ConvertJTokenInteger(JToken arg, DynamicJsonClassOptions? options = null)
{
if (arg.Type != JTokenType.Integer)
{
throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to long or int.");
}
var longValue = arg.Value<long>();
if (options is null || options.IntegerConvertBehavior == IntegerBehavior.UseInt)
{
if (longValue is >= int.MinValue and <= int.MaxValue)
{
return Convert.ToInt32(longValue);
}
}
return longValue;
}
private static object? ConvertJTokenProperty(JToken arg, DynamicJsonClassOptions? options = null)
{
var resolver = GetResolverFor(arg);
if (resolver is null)
{
throw new InvalidOperationException($"Unable to handle {nameof(JToken)} of type: {arg.Type}.");
}
return resolver(arg, options);
}
private static IEnumerable ConvertJTokenArray(JToken arg, DynamicJsonClassOptions? options = null)
{
if (arg is not JArray array)
{
throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to {nameof(JArray)}.");
}
var result = new List<object?>();
foreach (var item in array)
{
result.Add(ConvertJObject(item));
}
var distinctType = FindSameTypeOf(result);
return distinctType == null ? result.ToArray() : ConvertToTypedArray(result, distinctType);
}
private static Type? FindSameTypeOf(IEnumerable<object?> src)
{
var types = src.Select(o => o?.GetType()).Distinct().OfType<Type>().ToArray();
return types.Length == 1 ? types[0] : null;
}
private static IEnumerable ConvertToTypedArray(IEnumerable<object?> src, Type newType)
{
var method = ConvertToTypedArrayGenericMethod.MakeGenericMethod(newType);
return (IEnumerable)method.Invoke(null, new object[] { src })!;
}
private static readonly MethodInfo ConvertToTypedArrayGenericMethod = typeof(JObjectExtensions).GetMethod(nameof(ConvertToTypedArrayGeneric), BindingFlags.NonPublic | BindingFlags.Static)!;
private static T[] ConvertToTypedArrayGeneric<T>(IEnumerable<object> src)
{
return src.Cast<T>().ToArray();
}
public static DynamicClass CreateInstance(IList<DynamicPropertyWithValue> dynamicPropertiesWithValue, bool createParameterCtor = true)
{
var type = DynamicClassFactory.CreateType(dynamicPropertiesWithValue.Cast<DynamicProperty>().ToArray(), createParameterCtor);
var dynamicClass = (DynamicClass)Activator.CreateInstance(type);
foreach (var dynamicPropertyWithValue in dynamicPropertiesWithValue.Where(p => p.Value != null))
{
dynamicClass.SetDynamicPropertyValue(dynamicPropertyWithValue.Name, dynamicPropertyWithValue.Value!);
}
return dynamicClass;
}
}

View File

@@ -72,6 +72,9 @@ public class Mapping : IMapping
/// <inheritdoc />
public ITimeSettings? TimeSettings { get; }
/// <inheritdoc />
public object? Data { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary>
@@ -91,6 +94,7 @@ public class Mapping : IMapping
/// <param name="webhooks">The Webhooks. [Optional]</param>
/// <param name="useWebhooksFireAndForget">Use Fire and Forget for the defined webhook(s). [Optional]</param>
/// <param name="timeSettings">The TimeSettings. [Optional]</param>
/// <param name="data">The data object. [Optional]</param>
public Mapping(
Guid guid,
DateTime updatedAt,
@@ -107,7 +111,8 @@ public class Mapping : IMapping
int? stateTimes,
IWebhook[]? webhooks,
bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings)
ITimeSettings? timeSettings,
object? data)
{
Guid = guid;
UpdatedAt = updatedAt;
@@ -125,6 +130,7 @@ public class Mapping : IMapping
Webhooks = webhooks;
UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings;
Data = data;
}
/// <inheritdoc cref="IMapping.ProvideResponseAsync" />

View File

@@ -0,0 +1,160 @@
using System;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.Owin;
using WireMock.Serialization;
using WireMock.Server;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock;
/// <summary>
/// MappingBuilder
/// </summary>
public class MappingBuilder : IMappingBuilder
{
private readonly WireMockServerSettings _settings;
private readonly IWireMockMiddlewareOptions _options;
private readonly MappingConverter _mappingConverter;
private readonly MappingToFileSaver _mappingToFileSaver;
private readonly IGuidUtils _guidUtils;
private readonly IDateTimeUtils _dateTimeUtils;
/// <summary>
/// Create a MappingBuilder
/// </summary>
/// <param name="settings">The optional <see cref="WireMockServerSettings"/>.</param>
public MappingBuilder(WireMockServerSettings? settings = null)
{
_settings = settings ?? new WireMockServerSettings();
_options = WireMockMiddlewareOptionsHelper.InitFromSettings(_settings);
var matcherMapper = new MatcherMapper(_settings);
_mappingConverter = new MappingConverter(matcherMapper);
_mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter);
_guidUtils = new GuidUtils();
_dateTimeUtils = new DateTimeUtils();
}
internal MappingBuilder(
WireMockServerSettings settings,
IWireMockMiddlewareOptions options,
MappingConverter mappingConverter,
MappingToFileSaver mappingToFileSaver,
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils
)
{
_settings = Guard.NotNull(settings);
_options = Guard.NotNull(options);
_mappingConverter = Guard.NotNull(mappingConverter);
_mappingToFileSaver = Guard.NotNull(mappingToFileSaver);
_guidUtils = Guard.NotNull(guidUtils);
_dateTimeUtils = Guard.NotNull(dateTimeUtils);
}
/// <inheritdoc />
public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false)
{
return new RespondWithAProvider(RegisterMapping, Guard.NotNull(requestMatcher), _settings, _guidUtils, _dateTimeUtils, saveToFile);
}
/// <inheritdoc />
public MappingModel[] GetMappings()
{
return GetMappingsInternal().Select(_mappingConverter.ToMappingModel).ToArray();
}
internal IMapping[] GetMappingsInternal()
{
return _options.Mappings.Values.ToArray().Where(m => !m.IsAdminInterface).ToArray();
}
/// <inheritdoc />
public string ToJson()
{
return ToJson(GetMappings());
}
/// <inheritdoc />
public string? ToCSharpCode(Guid guid, MappingConverterType converterType)
{
var mapping = GetMappingsInternal().FirstOrDefault(m => m.Guid == guid);
if (mapping is null)
{
return null;
}
var settings = new MappingConverterSettings { AddStart = true, ConverterType = converterType };
return _mappingConverter.ToCSharpCode(mapping, settings);
}
/// <inheritdoc />
public string ToCSharpCode(MappingConverterType converterType)
{
var sb = new StringBuilder();
bool addStart = true;
foreach (var mapping in GetMappingsInternal())
{
sb.AppendLine(_mappingConverter.ToCSharpCode(mapping, new MappingConverterSettings { AddStart = addStart, ConverterType = converterType }));
if (addStart)
{
addStart = false;
}
}
return sb.ToString();
}
/// <inheritdoc />
public void SaveMappingsToFile(string path)
{
_mappingToFileSaver.SaveMappingsToFile(GetNonAdminMappings(), path);
}
/// <inheritdoc />
public void SaveMappingsToFolder(string? folder)
{
foreach (var mapping in GetNonAdminMappings().Where(m => !m.IsAdminInterface))
{
_mappingToFileSaver.SaveMappingToFile(mapping, folder);
}
}
private IMapping[] GetNonAdminMappings()
{
return _options.Mappings.Values.ToArray();
}
private void RegisterMapping(IMapping mapping, bool saveToFile)
{
// Check a mapping exists with the same Guid. If so, update the datetime and replace it.
if (_options.Mappings.ContainsKey(mapping.Guid))
{
mapping.UpdatedAt = _dateTimeUtils.UtcNow;
_options.Mappings[mapping.Guid] = mapping;
}
else
{
_options.Mappings.TryAdd(mapping.Guid, mapping);
}
if (saveToFile)
{
_mappingToFileSaver.SaveMappingToFile(mapping);
}
}
private static string ToJson(object value)
{
return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsDefault);
}
}

View File

@@ -5,8 +5,8 @@ using AnyOfTypes;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Json;
using WireMock.Models;
using WireMock.Util;
namespace WireMock.Matchers;
@@ -100,38 +100,55 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
{
double match = MatchScores.Mismatch;
JObject value;
switch (input)
JArray jArray;
try
{
case JObject valueAsJObject:
value = valueAsJObject;
break;
case { } valueAsObject:
value = JObject.FromObject(valueAsObject);
break;
default:
return MatchScores.Mismatch;
jArray = new JArray { input };
}
catch
{
jArray = new JArray { JToken.FromObject(input) };
}
//enumerable = jArray.ToDynamicClassArray();
//JObject value;
//switch (input)
//{
// case JObject valueAsJObject:
// value = valueAsJObject;
// break;
// case { } valueAsObject:
// value = JObject.FromObject(valueAsObject);
// break;
// default:
// return MatchScores.Mismatch;
//}
// Convert a single object to a Queryable JObject-list with 1 entry.
var queryable1 = new[] { value }.AsQueryable();
//var queryable1 = new[] { value }.AsQueryable();
var queryable = jArray.ToDynamicClassArray().AsQueryable();
try
{
// Generate the DynamicLinq select statement.
string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value);
//string dynamicSelect = JsonUtils.GenerateDynamicLinqStatement(value);
// Execute DynamicLinq Select statement.
var queryable2 = queryable1.Select(dynamicSelect);
//var queryable2 = queryable1.Select(dynamicSelect);
// Use the Any(...) method to check if the result matches.
match = MatchScores.ToScore(_patterns.Select(pattern => queryable2.Any(pattern)).ToArray(), MatchOperator);
var patternsAsStringArray = _patterns.Select(p => p.GetPattern()).ToArray();
var scores = patternsAsStringArray.Select(p => queryable.Any(p)).ToArray();
match = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern())).ToArray(), MatchOperator);
return MatchBehaviourHelper.Convert(MatchBehaviour, match);
}
catch
catch (Exception e)
{
if (ThrowException)
{

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace WireMock.Matchers;

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using AnyOfTypes;
using WireMock.Models;
@@ -62,7 +63,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc cref="IStringMatcher.GetPatterns"/>
public AnyOf<string, StringPattern>[] GetPatterns()
{
return new AnyOf<string, StringPattern>[0];
return EmptyArray<AnyOf<string, StringPattern>>.Value;
}
/// <inheritdoc />

View File

@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using Stef.Validation;
using WireMock.Models;
using WireMock.Types;
using WireMock.Util;
@@ -33,6 +32,11 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// </summary>
public Func<IBodyData?, bool>? BodyDataFunc { get; }
/// <summary>
/// The body data function for FormUrlEncoded
/// </summary>
public Func<IDictionary<string, string>?, bool>? FormUrlEncodedFunc { get; }
/// <summary>
/// The matchers.
/// </summary>
@@ -109,6 +113,15 @@ public class RequestMessageBodyMatcher : IRequestMatcher
BodyDataFunc = Guard.NotNull(func);
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
{
FormUrlEncodedFunc = Guard.NotNull(func);
}
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
@@ -144,6 +157,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
{
case BodyType.Json:
case BodyType.String:
case BodyType.FormUrlEncoded:
return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
case BodyType.Bytes:
@@ -158,7 +172,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
{
// If the body is a byte array, try to match.
var detectedBodyType = requestMessage.BodyData?.DetectedBodyType;
if (detectedBodyType is BodyType.Bytes or BodyType.String)
if (detectedBodyType is BodyType.Bytes or BodyType.String or BodyType.FormUrlEncoded)
{
return exactObjectMatcher.IsMatch(requestMessage.BodyData?.BodyAsBytes);
}
@@ -184,7 +198,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
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)
if (requestMessage?.BodyData?.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded)
{
return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString);
}
@@ -206,6 +220,11 @@ public class RequestMessageBodyMatcher : IRequestMatcher
return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString));
}
if (FormUrlEncodedFunc != null)
{
return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded));
}
if (JsonFunc != null)
{
return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson));

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Text;
using WireMock.Types;
@@ -14,6 +15,9 @@ public class BodyData : IBodyData
/// <inheritdoc />
public string? BodyAsString { get; set; }
/// <inheritdoc />
public IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
/// <inheritdoc cref="IBodyData.BodyAsJson" />
public object? BodyAsJson { get; set; }

View File

@@ -130,6 +130,7 @@ namespace WireMock.Owin.Mappers
switch (responseMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!);
case BodyType.Json:

View File

@@ -0,0 +1,35 @@
using Stef.Validation;
using WireMock.Settings;
namespace WireMock.Owin;
internal static class WireMockMiddlewareOptionsHelper
{
public static IWireMockMiddlewareOptions InitFromSettings(WireMockServerSettings settings, IWireMockMiddlewareOptions? options = null)
{
Guard.NotNull(settings);
options ??= new WireMockMiddlewareOptions();
options.FileSystemHandler = settings.FileSystemHandler;
options.PreWireMockMiddlewareInit = settings.PreWireMockMiddlewareInit;
options.PostWireMockMiddlewareInit = settings.PostWireMockMiddlewareInit;
options.Logger = settings.Logger;
options.DisableJsonBodyParsing = settings.DisableJsonBodyParsing;
options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
options.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
options.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
if (settings.CustomCertificateDefined)
{
options.X509StoreName = settings.CertificateSettings!.X509StoreName;
options.X509StoreLocation = settings.CertificateSettings.X509StoreLocation;
options.X509ThumbprintOrSubjectName = settings.CertificateSettings.X509StoreThumbprintOrSubjectName;
options.X509CertificateFilePath = settings.CertificateSettings.X509CertificateFilePath;
options.X509CertificatePassword = settings.CertificateSettings.X509CertificatePassword;
}
return options;
}
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Stef.Validation;
using WireMock.Http;
using WireMock.Matchers;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Util;
@@ -43,16 +45,45 @@ internal class ProxyHelper
// Create ResponseMessage
bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false);
bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.GetValueOrDefault(false);
bool deserializeFormUrlEncoded = !_settings.DisableDeserializeFormUrlEncoded.GetValueOrDefault(false);
var responseMessage = await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri, deserializeJson, decompressGzipAndDeflate).ConfigureAwait(false);
var responseMessage = await HttpResponseMessageHelper.CreateAsync(
httpResponseMessage,
requiredUri,
originalUri,
deserializeJson,
decompressGzipAndDeflate,
deserializeFormUrlEncoded
).ConfigureAwait(false);
IMapping? newMapping = null;
if (HttpStatusRangeParser.IsMatch(proxyAndRecordSettings.SaveMappingForStatusCodePattern, responseMessage.StatusCode) &&
(proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile))
var saveMappingSettings = proxyAndRecordSettings.SaveMappingSettings;
bool save = true;
if (saveMappingSettings != null)
{
save &= Check(saveMappingSettings.StatusCodePattern,
() => saveMappingSettings.StatusCodePattern != null && HttpStatusRangeParser.IsMatch(saveMappingSettings.StatusCodePattern, responseMessage.StatusCode)
);
save &= Check(saveMappingSettings.HttpMethods,
() => saveMappingSettings.HttpMethods != null && saveMappingSettings.HttpMethods.Value.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase)
);
}
if (save && (proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile))
{
newMapping = _proxyMappingConverter.ToMapping(mapping, proxyAndRecordSettings, requestMessage, responseMessage);
}
return (responseMessage, newMapping);
}
private static bool Check<T>(ProxySaveMappingSetting<T>? saveMappingSetting, Func<bool> action)
{
var isMatch = saveMappingSetting is null || action();
var matchBehaviour = saveMappingSetting?.MatchBehaviour ?? MatchBehaviour.AcceptOnMatch;
return isMatch == (matchBehaviour == MatchBehaviour.AcceptOnMatch);
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using JsonConverter.Abstractions;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Util;
@@ -45,35 +47,60 @@ public interface IBodyRequestBuilder : IRequestMatcher
/// WithBody: Body as object
/// </summary>
/// <param name="body">The body.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="matchBehaviour">The match behaviour [default is AcceptOnMatch].</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBody : Body as a string response based on a object (which will be converted to a JSON string using NewtonSoft.Json).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="matchBehaviour">The match behaviour [default is AcceptOnMatch].</param>
/// <returns>A <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBody : Body as a string response based on a object (which will be converted to a JSON string using the <see cref="IJsonConverter"/>).
/// </summary>
/// <param name="body">The body.</param>
/// <param name="converter">The JsonConverter.</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <param name="matchBehaviour">The match behaviour [default is AcceptOnMatch].</param>
/// <returns>A <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsJson(object body, IJsonConverter converter, JsonConverterOptions? options = null, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithBody: func (string)
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<string, bool> func);
IRequestBuilder WithBody(Func<string?, bool> func);
/// <summary>
/// WithBody: func (byte[])
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<byte[], bool> func);
IRequestBuilder WithBody(Func<byte[]?, bool> func);
/// <summary>
/// WithBody: func (json object)
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<object, bool> func);
IRequestBuilder WithBody(Func<object?, bool> func);
/// <summary>
/// WithBody: func (BodyData object)
/// </summary>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<IBodyData, bool> func);
IRequestBuilder WithBody(Func<IBodyData?, bool> func);
/// <summary>
/// WithBody: Body as form-urlencoded values.
/// </summary>
/// <param name="func">The form-urlencoded values.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func);
}

View File

@@ -1,85 +1,83 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using WireMock.Matchers;
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
/// <summary>
/// The CookieRequestBuilder interface.
/// </summary>
public interface ICookiesRequestBuilder : IParamsRequestBuilder
{
/// <summary>
/// The CookieRequestBuilder interface.
/// WithCookie: matching based on name, pattern and matchBehaviour.
/// </summary>
public interface ICookiesRequestBuilder : IParamsRequestBuilder
{
/// <summary>
/// WithCookie: matching based on name, pattern and matchBehaviour.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, string pattern, MatchBehaviour matchBehaviour);
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie(string name, string pattern, MatchBehaviour matchBehaviour);
/// <summary>
/// WithCookie: matching based on name, pattern, ignoreCase and matchBehaviour.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithCookie: matching based on name, pattern, ignoreCase and matchBehaviour.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie(string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithCookie: matching based on name, patterns and matchBehaviour.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, string[] patterns, MatchBehaviour matchBehaviour);
/// <summary>
/// WithCookie: matching based on name, patterns and matchBehaviour.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie(string name, string[] patterns, MatchBehaviour matchBehaviour);
/// <summary>
/// WithCookie: matching based on name, patterns, ignoreCase and matchBehaviour.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithCookie: matching based on name, patterns, ignoreCase and matchBehaviour.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="patterns">The patterns.</param>
/// <param name="ignoreCase">Ignore the case from the pattern.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie(string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch);
/// <summary>
/// WithCookie: matching based on name and IStringMatcher[].
/// </summary>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, [NotNull] params IStringMatcher[] matchers);
/// <summary>
/// WithCookie: matching based on name and IStringMatcher[].
/// </summary>
/// <param name="name">The name.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie(string name, params IStringMatcher[] matchers);
/// <summary>
/// WithCookie: matching based on name, ignoreCase and IStringMatcher[].
/// </summary>
/// <param name="name">The name.</param>
/// <param name="ignoreCase">Ignore the case from the cookie-keys.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers);
/// <summary>
/// WithCookie: matching based on name, ignoreCase and IStringMatcher[].
/// </summary>
/// <param name="name">The name.</param>
/// <param name="ignoreCase">Ignore the case from the cookie-keys.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie(string name, bool ignoreCase, params IStringMatcher[] matchers);
/// <summary>
/// WithCookie: matching based on name, ignoreCase, matchBehaviour and IStringMatcher[].
/// </summary>
/// <param name="name">The name.</param>
/// <param name="ignoreCase">Ignore the case from the cookie-keys.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] string name, bool ignoreCase, MatchBehaviour matchBehaviour, [NotNull] params IStringMatcher[] matchers);
/// <summary>
/// WithCookie: matching based on name, ignoreCase, matchBehaviour and IStringMatcher[].
/// </summary>
/// <param name="name">The name.</param>
/// <param name="ignoreCase">Ignore the case from the cookie-keys.</param>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie(string name, bool ignoreCase, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers);
/// <summary>
/// WithCookie: matching based on functions.
/// </summary>
/// <param name="funcs">The cookies funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie([NotNull] params Func<IDictionary<string, string>, bool>[] funcs);
}
/// <summary>
/// WithCookie: matching based on functions.
/// </summary>
/// <param name="funcs">The cookies funcs.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithCookie(params Func<IDictionary<string, string>, bool>[] funcs);
}

View File

@@ -1,36 +1,57 @@
// 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 JsonConverter.Abstractions;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Util;
using Stef.Validation;
namespace WireMock.RequestBuilders;
public partial class Request
{
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(string, MatchBehaviour)"/>
/// <inheritdoc />
public IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(byte[], MatchBehaviour)"/>
/// <inheritdoc />
public IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(object, MatchBehaviour)"/>
/// <inheritdoc />
public IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
var bodyAsJsonString = JsonConvert.SerializeObject(body);
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, bodyAsJsonString));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsJson(object body, IJsonConverter converter, JsonConverterOptions? options = null, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(converter);
var bodyAsJsonString = converter.Serialize(body, options);
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, bodyAsJsonString));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBody(IMatcher matcher)
{
@@ -46,39 +67,46 @@ public partial class Request
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{string, bool})"/>
public IRequestBuilder WithBody(Func<string, bool> func)
/// <inheritdoc />
public IRequestBuilder WithBody(Func<string?, bool> func)
{
Guard.NotNull(func, nameof(func));
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{byte[], bool})"/>
public IRequestBuilder WithBody(Func<byte[], bool> func)
/// <inheritdoc />
public IRequestBuilder WithBody(Func<byte[]?, bool> func)
{
Guard.NotNull(func, nameof(func));
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{object, bool})"/>
public IRequestBuilder WithBody(Func<object, bool> func)
/// <inheritdoc />
public IRequestBuilder WithBody(Func<object?, bool> func)
{
Guard.NotNull(func, nameof(func));
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{IBodyData, bool})"/>
public IRequestBuilder WithBody(Func<IBodyData, bool> func)
/// <inheritdoc />
public IRequestBuilder WithBody(Func<IBodyData?, bool> func)
{
Guard.NotNull(func, nameof(func));
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
}
}

View File

@@ -56,9 +56,12 @@ public class RequestMessage : IRequestMessage
/// <inheritdoc cref="IRequestMessage.Cookies" />
public IDictionary<string, string>? Cookies { get; }
/// <inheritdoc cref="IRequestMessage.Query" />
/// <inheritdoc />
public IDictionary<string, WireMockList<string>>? Query { get; }
/// <inheritdoc />
public IDictionary<string, WireMockList<string>>? QueryIgnoreCase { get; }
/// <inheritdoc cref="IRequestMessage.RawQuery" />
public string RawQuery { get; }
@@ -171,6 +174,7 @@ public class RequestMessage : IRequestMessage
Cookies = cookies;
RawQuery = urlDetails.Url.Query;
Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport);
QueryIgnoreCase = new Dictionary<string, WireMockList<string>>(Query, StringComparer.OrdinalIgnoreCase);
#if USE_ASPNETCORE
ClientCertificate = clientCertificate;
#endif

View File

@@ -29,7 +29,7 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
IResponseBuilder WithBody(Func<IRequestMessage, string> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null);
/// <summary>
/// WithBody : Create a ... response based on a callback function.
/// WithBody : Create a ... response based on a async 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>
@@ -51,7 +51,7 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// </summary>
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding.</param>
/// <param name="indented">Use JSON indented.</param>
/// <param name="indented">Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null);
@@ -63,6 +63,22 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(object body, bool indented);
/// <summary>
/// WithBodyAsJson : Create a ... response based on a callback function.
/// </summary>
/// <param name="bodyFactory">The delegate to build the body.</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(Func<IRequestMessage, object> bodyFactory, Encoding? encoding = null);
/// <summary>
/// WithBodyAsJson : Create a ... response based on a async callback function.
/// </summary>
/// <param name="bodyFactory">The async delegate to build the body.</param>
/// <param name="encoding">The body encoding.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBodyAsJson(Func<IRequestMessage, Task<object>> bodyFactory, Encoding? encoding = null);
/// <summary>
/// WithBodyFromFile : Create a ... response based on a File.
/// </summary>
@@ -76,7 +92,7 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// </summary>
/// <param name="body">The body.</param>
/// <param name="converter">The JsonConverter.</param>
/// <param name="options">The IJsonConverterOption [optional].</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(object body, IJsonConverter converter, JsonConverterOptions? options = null);
@@ -86,7 +102,7 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// <param name="body">The body.</param>
/// <param name="encoding">The body encoding, can be <c>null</c>.</param>
/// <param name="converter">The JsonConverter.</param>
/// <param name="options">The IJsonConverterOption [optional].</param>
/// <param name="options">The <see cref="JsonConverterOptions"/> [optional].</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter converter, JsonConverterOptions? options = null);
}

View File

@@ -1,4 +1,4 @@
using JetBrains.Annotations;
using System.Security.Cryptography.X509Certificates;
using WireMock.Settings;
namespace WireMock.ResponseBuilders;
@@ -17,9 +17,17 @@ public interface IProxyResponseBuilder : IStatusCodeResponseBuilder
IResponseBuilder WithProxy(string proxyUrl, string? clientX509Certificate2ThumbprintOrSubjectName = null);
/// <summary>
/// WithProxy using IProxyAndRecordSettings.
/// WithProxy using <see cref="ProxyAndRecordSettings"/>.
/// </summary>
/// <param name="settings">The IProxyAndRecordSettings.</param>
/// <param name="settings">The ProxyAndRecordSettings.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy([NotNull] ProxyAndRecordSettings settings);
IResponseBuilder WithProxy(ProxyAndRecordSettings settings);
/// <summary>
/// WithProxy using <see cref="X509Certificate2"/>.
/// </summary>
/// <param name="proxyUrl">The proxy url.</param>
/// <param name="certificate"">The X509Certificate2.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy(string proxyUrl, X509Certificate2 certificate);
}

View File

@@ -13,7 +13,7 @@ public partial class Response
/// <inheritdoc />
public IResponseBuilder WithBody(Func<IRequestMessage, string> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory, nameof(bodyFactory));
Guard.NotNull(bodyFactory);
return WithCallbackInternal(true, req => new ResponseMessage
{
@@ -30,7 +30,7 @@ public partial class Response
/// <inheritdoc />
public IResponseBuilder WithBody(Func<IRequestMessage, Task<string>> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory, nameof(bodyFactory));
Guard.NotNull(bodyFactory);
return WithCallbackInternal(true, async req => new ResponseMessage
{
@@ -70,7 +70,7 @@ public partial class Response
return this;
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyFromFile"/>
/// <inheritdoc />
public IResponseBuilder WithBodyFromFile(string filename, bool cache = true)
{
Guard.NotNull(filename);
@@ -127,7 +127,7 @@ public partial class Response
return this;
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyAsJson(object, Encoding, bool?)"/>
/// <inheritdoc />
public IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null)
{
Guard.NotNull(body);
@@ -144,12 +144,46 @@ public partial class Response
return this;
}
/// <inheritdoc cref="IBodyResponseBuilder.WithBodyAsJson(object, bool)"/>
/// <inheritdoc />
public IResponseBuilder WithBodyAsJson(object body, bool indented)
{
return WithBodyAsJson(body, null, indented);
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsJson(Func<IRequestMessage, object> bodyFactory, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory);
return WithCallbackInternal(true, req => new ResponseMessage
{
BodyData = new BodyData
{
Encoding = encoding ?? Encoding.UTF8,
DetectedBodyType = BodyType.Json,
BodyAsJson = bodyFactory(req),
IsFuncUsed = "Func<IRequestMessage, object>"
}
});
}
/// <inheritdoc />
public IResponseBuilder WithBodyAsJson(Func<IRequestMessage, Task<object>> bodyFactory, Encoding? encoding = null)
{
Guard.NotNull(bodyFactory);
return WithCallbackInternal(true, async req => new ResponseMessage
{
BodyData = new BodyData
{
Encoding = encoding ?? Encoding.UTF8,
DetectedBodyType = BodyType.Json,
BodyAsJson = await bodyFactory(req).ConfigureAwait(false),
IsFuncUsed = "Func<IRequestMessage, Task<object>>"
}
});
}
/// <inheritdoc />
public IResponseBuilder WithBody(object body, IJsonConverter converter, JsonConverterOptions? options = null)
{

View File

@@ -2,6 +2,7 @@ using System.Net.Http;
using WireMock.Http;
using WireMock.Settings;
using Stef.Validation;
using System.Security.Cryptography.X509Certificates;
namespace WireMock.ResponseBuilders;
@@ -38,4 +39,19 @@ public partial class Response
_httpClientForProxy = HttpClientBuilder.Build(settings);
return this;
}
/// <inheritdoc />
public IResponseBuilder WithProxy(string proxyUrl, X509Certificate2 certificate)
{
Guard.NotNullOrEmpty(proxyUrl);
Guard.NotNull(certificate);
var settings = new ProxyAndRecordSettings
{
Url = proxyUrl,
Certificate = certificate
};
return WithProxy(settings);
}
}

View File

@@ -107,14 +107,12 @@ public partial class Response : IResponseBuilder
/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class.
/// </summary>
/// <param name="responseMessage">
/// The response.
/// </param>
/// <param name="responseMessage">The response.</param>
private Response(ResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
}
/// <inheritdoc cref="IStatusCodeResponseBuilder.WithStatusCode(int)"/>
[PublicAPI]
public IResponseBuilder WithStatusCode(int code)

View File

@@ -44,6 +44,7 @@ internal class LogEntryMapper
switch (logEntry.RequestMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString;
break;
@@ -120,6 +121,7 @@ internal class LogEntryMapper
switch (logEntry.ResponseMessage.BodyData!.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
if (!string.IsNullOrEmpty(logEntry.ResponseMessage.BodyData.IsFuncUsed) && _options.DoNotSaveDynamicResponseInLogEntry == true)
{
logResponseModel.Body = logEntry.ResponseMessage.BodyData.IsFuncUsed;
@@ -142,6 +144,9 @@ internal class LogEntryMapper
logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile;
logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached;
break;
default:
break;
}
}

View File

@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Constants;
using WireMock.Extensions;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Models;
@@ -16,6 +19,8 @@ namespace WireMock.Serialization;
internal class MappingConverter
{
private static readonly string AcceptOnMatch = MatchBehaviour.AcceptOnMatch.GetFullyQualifiedEnumValue();
private readonly MatcherMapper _mapper;
public MappingConverter(MatcherMapper mapper)
@@ -23,6 +28,134 @@ internal class MappingConverter
_mapper = Guard.NotNull(mapper);
}
public string ToCSharpCode(IMapping mapping, MappingConverterSettings? settings = null)
{
settings ??= new MappingConverterSettings();
var request = (Request)mapping.RequestMatcher;
var response = (Response) mapping.Provider;
var clientIPMatcher = request.GetRequestMessageMatcher<RequestMessageClientIPMatcher>();
var pathMatcher = request.GetRequestMessageMatcher<RequestMessagePathMatcher>();
var urlMatcher = request.GetRequestMessageMatcher<RequestMessageUrlMatcher>();
var headerMatchers = request.GetRequestMessageMatchers<RequestMessageHeaderMatcher>();
var cookieMatchers = request.GetRequestMessageMatchers<RequestMessageCookieMatcher>();
var paramsMatchers = request.GetRequestMessageMatchers<RequestMessageParamMatcher>();
var methodMatcher = request.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
var bodyMatcher = request.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
var sb = new StringBuilder();
if (settings.ConverterType == MappingConverterType.Server)
{
if (settings.AddStart)
{
sb.AppendLine("var server = WireMockServer.Start();");
}
sb.AppendLine("server");
}
else
{
if (settings.AddStart)
{
sb.AppendLine("var builder = new MappingBuilder();");
}
sb.AppendLine("builder");
}
// Request
sb.AppendLine(" .Given(Request.Create()");
sb.AppendLine($" .UsingMethod({To1Or2Or3Arguments(methodMatcher?.MatchBehaviour, methodMatcher?.MatchOperator, methodMatcher?.Methods, HttpRequestMethod.GET)})");
if (pathMatcher is { Matchers: { } })
{
sb.AppendLine($" .WithPath({To1Or2Arguments(pathMatcher.MatchOperator, pathMatcher.Matchers)})");
}
else if (urlMatcher is { Matchers: { } })
{
sb.AppendLine($" .WithUrl({To1Or2Arguments(urlMatcher.MatchOperator, urlMatcher.Matchers)})");
}
foreach (var paramsMatcher in paramsMatchers)
{
sb.AppendLine($" .WithParam({To1Or2Or3Arguments(paramsMatcher.Key, paramsMatcher.MatchBehaviour, paramsMatcher.Matchers!)})");
}
if (clientIPMatcher is { Matchers: { } })
{
sb.AppendLine($" .WithClientIP({ToValueArguments(GetStringArray(clientIPMatcher.Matchers))})");
}
foreach (var headerMatcher in headerMatchers.Where(h => h.Matchers is { }))
{
var headerBuilder = new StringBuilder($"\"{headerMatcher.Name}\", {ToValueArguments(GetStringArray(headerMatcher.Matchers!))}, true");
if (headerMatcher.MatchOperator != MatchOperator.Or)
{
headerBuilder.Append($"{AcceptOnMatch}, {headerMatcher.MatchOperator.GetFullyQualifiedEnumValue()}");
}
sb.AppendLine($" .WithHeader({headerBuilder})");
}
foreach (var cookieMatcher in cookieMatchers.Where(h => h.Matchers is { }))
{
sb.AppendLine($" .WithCookie(\"{cookieMatcher.Name}\", {ToValueArguments(GetStringArray(cookieMatcher.Matchers!))}, true)");
}
if (bodyMatcher is { Matchers: { } })
{
var wildcardMatcher = bodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault();
if (wildcardMatcher is { } && wildcardMatcher.GetPatterns().Any())
{
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
}
}
sb.AppendLine(@" )");
// Guid
sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")");
// Response
sb.AppendLine(" .RespondWith(Response.Create()");
if (response.ResponseMessage.Headers is { })
{
foreach (var header in response.ResponseMessage.Headers)
{
sb.AppendLine($" .WithHeader(\"{header.Key})\", {ToValueArguments(header.Value.ToArray())})");
}
}
if (response.ResponseMessage.BodyData is { })
{
switch (response.ResponseMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
sb.AppendLine($" .WithBody(\"{response.ResponseMessage.BodyData.BodyAsString}\")");
break;
}
}
if (response.Delay is { })
{
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
}
else if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
{
sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
}
if (response.UseTransformer)
{
var transformerArgs = response.TransformerType != TransformerType.Handlebars ? response.TransformerType.GetFullyQualifiedEnumValue() : string.Empty;
sb.AppendLine($" .WithTransformer({transformerArgs})");
}
sb.AppendLine(@" );");
return sb.ToString();
}
public MappingModel ToMappingModel(IMapping mapping)
{
var request = (Request)mapping.RequestMatcher;
@@ -49,6 +182,7 @@ internal class MappingConverter
Scenario = mapping.Scenario,
WhenStateIs = mapping.ExecutionConditionState,
SetStateTo = mapping.NextState,
Data = mapping.Data,
Request = new RequestModel
{
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
@@ -192,6 +326,7 @@ internal class MappingConverter
switch (response.ResponseMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString;
break;
@@ -237,6 +372,64 @@ internal class MappingConverter
return mappingModel;
}
private static string GetString(IStringMatcher stringMatcher)
{
return stringMatcher.GetPatterns().Select(p => $"\"{p.GetPattern()}\"").First();
}
private static string[] GetStringArray(IReadOnlyList<IStringMatcher> stringMatchers)
{
return stringMatchers.SelectMany(m => m.GetPatterns()).Select(p => p.GetPattern()).ToArray();
}
private static string To1Or2Or3Arguments(string key, MatchBehaviour? matchBehaviour, IReadOnlyList<IStringMatcher> matchers)
{
var sb = new StringBuilder($"\"{key}\", ");
if (matchBehaviour.HasValue && matchBehaviour != MatchBehaviour.AcceptOnMatch)
{
sb.AppendFormat("{0}, ", matchBehaviour.Value.GetFullyQualifiedEnumValue());
}
sb.AppendFormat("{0}", ToValueArguments(GetStringArray(matchers), string.Empty));
return sb.ToString();
}
private static string To1Or2Or3Arguments(MatchBehaviour? matchBehaviour, MatchOperator? matchOperator, string[]? values, string defaultValue)
{
var sb = new StringBuilder();
if (matchBehaviour.HasValue && matchBehaviour != MatchBehaviour.AcceptOnMatch)
{
sb.AppendFormat("{0}, ", matchBehaviour.Value.GetFullyQualifiedEnumValue());
}
return To1Or2Arguments(matchOperator, values, defaultValue);
}
private static string To1Or2Arguments(MatchOperator? matchOperator, IReadOnlyList<IStringMatcher> matchers)
{
return To1Or2Arguments(matchOperator, GetStringArray(matchers), string.Empty);
}
private static string To1Or2Arguments(MatchOperator? matchOperator, string[]? values, string defaultValue)
{
var sb = new StringBuilder();
if (matchOperator.HasValue && matchOperator != MatchOperator.Or)
{
sb.AppendFormat("{0}, ", matchOperator.Value.GetFullyQualifiedEnumValue());
}
return sb.Append(ToValueArguments(values, defaultValue)).ToString();
}
private static string ToValueArguments(string[]? values, string defaultValue = "")
{
return values is { } ? string.Join(", ", values.Select(v => $"\"{v}\"")) : $"\"{defaultValue}\"";
}
private static WebProxyModel? MapWebProxy(WebProxySettings? settings)
{
return settings != null ? new WebProxyModel

View File

@@ -0,0 +1,25 @@
using WireMock.Types;
namespace WireMock.Serialization;
/// <summary>
/// MappingConverterSettings
/// </summary>
public class MappingConverterSettings
{
/// <summary>
/// Use 'Server' or 'Builder'.
///
/// Default is Server
/// </summary>
public MappingConverterType ConverterType { get; set; }
/// <summary>
/// Add "var server = WireMockServer.Start();"
/// or
/// Add "var builder = new MappingBuilder();"
///
/// Default it's false.
/// </summary>
public bool AddStart { get; set; }
}

View File

@@ -17,6 +17,20 @@ internal class MappingToFileSaver
_mappingConverter = Guard.NotNull(mappingConverter);
}
public void SaveMappingsToFile(IMapping[] mappings, string? folder = null)
{
folder ??= _settings.FileSystemHandler.GetMappingFolder();
if (!_settings.FileSystemHandler.FolderExists(folder))
{
_settings.FileSystemHandler.CreateFolder(folder);
}
var models = mappings.Select(_mappingConverter.ToMappingModel).ToArray();
Save(models, folder);
}
public void SaveMappingToFile(IMapping mapping, string? folder = null)
{
folder ??= _settings.FileSystemHandler.GetMappingFolder();
@@ -31,9 +45,14 @@ internal class MappingToFileSaver
var filename = BuildSanitizedFileName(mapping);
var path = Path.Combine(folder, filename);
Save(model, path);
}
private void Save(object value, string path)
{
_settings.Logger.Info("Saving Mapping file {0}", path);
_settings.FileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(model, JsonSerializationConstants.JsonSerializerSettingsDefault));
_settings.FileSystemHandler.WriteMappingFile(path, JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsDefault));
}
private string BuildSanitizedFileName(IMapping mapping, char replaceChar = '_')

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using SimMetrics.Net;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Extensions;
using WireMock.Matchers;
@@ -19,7 +20,7 @@ internal class MatcherMapper
public MatcherMapper(WireMockServerSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_settings = Guard.NotNull(settings);
}
public IMatcher[]? Map(IEnumerable<MatcherModel>? matchers)

View File

@@ -26,8 +26,12 @@ internal class ProxyMappingConverter
_dateTimeUtils = Guard.NotNull(dateTimeUtils);
}
public IMapping ToMapping(IMapping? mapping, ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage)
public IMapping? ToMapping(IMapping? mapping, ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage)
{
var useDefinedRequestMatchers = proxyAndRecordSettings.UseDefinedRequestMatchers;
var excludedHeaders = new List<string>(proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }) { "Cookie" };
var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[0];
var request = (Request?)mapping?.RequestMatcher;
var clientIPMatcher = request?.GetRequestMessageMatcher<RequestMessageClientIPMatcher>();
var pathMatcher = request?.GetRequestMessageMatcher<RequestMessagePathMatcher>();
@@ -37,11 +41,6 @@ internal class ProxyMappingConverter
var methodMatcher = request?.GetRequestMessageMatcher<RequestMessageMethodMatcher>();
var bodyMatcher = request?.GetRequestMessageMatcher<RequestMessageBodyMatcher>();
var useDefinedRequestMatchers = proxyAndRecordSettings.UseDefinedRequestMatchers;
var excludedHeaders = new List<string>(proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }) { "Cookie" };
var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[] { };
var newRequest = Request.Create();
// ClientIP
@@ -142,6 +141,7 @@ internal class ProxyMappingConverter
break;
case BodyType.String:
case BodyType.FormUrlEncoded:
newRequest.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, true, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString!));
break;
@@ -178,7 +178,8 @@ internal class ProxyMappingConverter
stateTimes: null,
webhooks: null,
useWebhooksFireAndForget: null,
timeSettings: null
timeSettings: null,
data: mapping?.Data
);
}
}

View File

@@ -97,6 +97,7 @@ internal static class WebhookMapper
switch (webhook.Request.BodyData.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
model.Request.Body = webhook.Request.BodyData.BodyAsString;
break;

View File

@@ -166,4 +166,14 @@ public interface IRespondWithAProvider
bool useTransformer = true,
TransformerType transformerType = TransformerType.Handlebars
);
/// <summary>
/// Data Object which can be used when WithTransformer is used.
/// e.g. lookup an path in this object using
/// <param name="data">The data dictionary object.</param>
/// <example>
/// lookup data "1"
/// </example>
/// </summary>
IRespondWithAProvider WithData(object data);
}

View File

@@ -2,6 +2,7 @@
// 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 Stef.Validation;
using WireMock.Matchers.Request;
using WireMock.Models;
@@ -28,11 +29,10 @@ internal class RespondWithAProvider : IRespondWithAProvider
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly WireMockServerSettings _settings;
private readonly IDateTimeUtils _dateTimeUtils;
private readonly bool _saveToFile;
private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
private bool _useWebhookFireAndForget;
private bool? _useWebhookFireAndForget;
public Guid Guid { get; private set; }
@@ -40,26 +40,33 @@ internal class RespondWithAProvider : IRespondWithAProvider
public ITimeSettings? TimeSettings { get; private set; }
public object? Data { 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="guidUtils">GuidUtils to make unit testing possible.</param>
/// <param name="dateTimeUtils">DateTimeUtils to make unit testing possible.</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,
WireMockServerSettings settings,
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils,
bool saveToFile = false
)
{
_registrationCallback = Guard.NotNull(registrationCallback);
_requestMatcher = Guard.NotNull(requestMatcher);
_settings = Guard.NotNull(settings);
_saveToFile = Guard.NotNull(saveToFile);
_dateTimeUtils = Guard.NotNull(dateTimeUtils);
_saveToFile = saveToFile;
Guid = _guidUtils.NewGuid();
Guid = guidUtils.NewGuid();
}
/// <summary>
@@ -84,10 +91,20 @@ internal class RespondWithAProvider : IRespondWithAProvider
_timesInSameState,
Webhooks,
_useWebhookFireAndForget,
TimeSettings);
TimeSettings,
Data);
_registrationCallback(mapping, _saveToFile);
}
/// <inheritdoc />
[PublicAPI]
public IRespondWithAProvider WithData(object data)
{
Data = data;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithGuid(string guid)
{

View File

@@ -32,6 +32,7 @@ public partial class WireMockServer
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
private const string AdminFiles = "/__admin/files";
private const string AdminMappings = "/__admin/mappings";
private const string AdminMappingsCode = "/__admin/mappings/code";
private const string AdminMappingsWireMockOrg = "/__admin/mappings/wiremock.org";
private const string AdminRequests = "/__admin/requests";
private const string AdminSettings = "/__admin/settings";
@@ -41,6 +42,7 @@ public partial class WireMockServer
private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
private static readonly RegexMatcher AdminMappingsGuidPathMatcher = new(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
private static readonly RegexMatcher AdminMappingsCodeGuidPathMatcher = new(@"^\/__admin\/mappings\/code\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
private static readonly RegexMatcher AdminRequestsGuidPathMatcher = new(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
private static readonly RegexMatcher AdminScenariosNameMatcher = new(@"^\/__admin\/scenarios\/.+$");
private static readonly RegexMatcher AdminScenariosNameWithResetMatcher = new(@"^\/__admin\/scenarios\/.+\/reset$");
@@ -57,9 +59,14 @@ public partial class WireMockServer
// __admin/mappings
Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet));
Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost));
Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg));
Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete));
// __admin/mappings/code
Given(Request.Create().WithPath(AdminMappingsCode).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsCodeGet));
// __admin/mappings/wiremock.org
Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg));
// __admin/mappings/reset
Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset));
@@ -68,6 +75,9 @@ public partial class WireMockServer
Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
// __admin/mappings/code/{guid}
Given(Request.Create().WithPath(AdminMappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet));
// __admin/mappings/save
Given(Request.Create().WithPath($"{AdminMappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave));
@@ -111,10 +121,7 @@ public partial class WireMockServer
[PublicAPI]
public void SaveStaticMappings(string? folder = null)
{
foreach (var mapping in Mappings.Where(m => !m.IsAdminInterface))
{
_mappingToFileSaver.SaveMappingToFile(mapping, folder);
}
_mappingBuilder.SaveMappingsToFolder(folder);
}
/// <inheritdoc cref="IWireMockServer.ReadStaticMappings" />
@@ -210,10 +217,17 @@ public partial class WireMockServer
var model = new SettingsModel
{
AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods,
AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse,
AllowPartialMapping = _settings.AllowPartialMapping,
DisableDeserializeFormUrlEncoded = _settings.DisableDeserializeFormUrlEncoded,
DisableJsonBodyParsing = _settings.DisableJsonBodyParsing,
DisableRequestBodyDecompressing = _settings.DisableRequestBodyDecompressing,
DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry,
GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds,
HandleRequestsSynchronously = _settings.HandleRequestsSynchronously,
HostingScheme = _settings.HostingScheme,
MaxRequestLogCount = _settings.MaxRequestLogCount,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
ReadStaticMappings = _settings.ReadStaticMappings,
RequestLogExpirationDuration = _settings.RequestLogExpirationDuration,
SaveUnmatchedRequests = _settings.SaveUnmatchedRequests,
@@ -221,14 +235,11 @@ public partial class WireMockServer
UseRegexExtended = _settings.UseRegexExtended,
WatchStaticMappings = _settings.WatchStaticMappings,
WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories,
HostingScheme = _settings.HostingScheme,
DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
#if USE_ASPNETCORE
CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString(),
AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate,
ClientCertificateMode = _settings.ClientCertificateMode,
AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate
CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString()
#endif
};
@@ -243,10 +254,16 @@ public partial class WireMockServer
// _settings
_settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods;
_settings.AllowOnlyDefinedHttpStatusCodeInResponse = settings.AllowOnlyDefinedHttpStatusCodeInResponse;
_settings.AllowPartialMapping = settings.AllowPartialMapping;
_settings.DisableDeserializeFormUrlEncoded = settings.DisableDeserializeFormUrlEncoded;
_settings.DisableJsonBodyParsing = settings.DisableJsonBodyParsing;
_settings.DisableRequestBodyDecompressing = settings.DisableRequestBodyDecompressing;
_settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_settings.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_settings.MaxRequestLogCount = settings.MaxRequestLogCount;
_settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings);
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
_settings.ReadStaticMappings = settings.ReadStaticMappings;
_settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
_settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
@@ -254,8 +271,6 @@ public partial class WireMockServer
_settings.UseRegexExtended = settings.UseRegexExtended;
_settings.WatchStaticMappings = settings.WatchStaticMappings;
_settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories;
_settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
InitSettings(_settings);
@@ -289,13 +304,11 @@ public partial class WireMockServer
#region Mapping/{guid}
private IResponseMessage MappingGet(IRequestMessage requestMessage)
{
Guid guid = ParseGuidFromRequestMessage(requestMessage);
var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid);
var mapping = FindMappingByGuid(requestMessage);
if (mapping == null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create("Mapping not found", 404);
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
}
var model = _mappingConverter.ToMappingModel(mapping);
@@ -303,31 +316,71 @@ public partial class WireMockServer
return ToJson(model);
}
private IResponseMessage MappingCodeGet(IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
var code = _mappingBuilder.ToCSharpCode(guid, GetMappingConverterType(requestMessage));
if (code is null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
}
return ToResponseMessage(code);
}
_settings.Logger.Warn("HttpStatusCode set to 400");
return ResponseMessageBuilder.Create("GUID is missing", HttpStatusCode.BadRequest);
}
private static MappingConverterType GetMappingConverterType(IRequestMessage requestMessage)
{
var mappingConverterType = MappingConverterType.Server;
if (requestMessage.QueryIgnoreCase?.TryGetValue(nameof(MappingConverterType), out var values) == true &&
Enum.TryParse(values.FirstOrDefault(), true, out MappingConverterType parsed))
{
mappingConverterType = parsed;
}
return mappingConverterType;
}
private IMapping? FindMappingByGuid(IRequestMessage requestMessage)
{
return TryParseGuidFromRequestMessage(requestMessage, out var guid) ? Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid) : null;
}
private IResponseMessage MappingPut(IRequestMessage requestMessage)
{
Guid guid = ParseGuidFromRequestMessage(requestMessage);
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
var mappingModel = DeserializeObject<MappingModel>(requestMessage);
var guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
var mappingModel = DeserializeObject<MappingModel>(requestMessage);
Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
return ResponseMessageBuilder.Create("Mapping added or updated", HttpStatusCode.OK, guidFromPut);
}
return ResponseMessageBuilder.Create("Mapping added or updated", HttpStatusCode.OK, guidFromPut);
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
}
private IResponseMessage MappingDelete(IRequestMessage requestMessage)
{
Guid guid = ParseGuidFromRequestMessage(requestMessage);
if (DeleteMapping(guid))
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid))
{
return ResponseMessageBuilder.Create("Mapping removed", HttpStatusCode.OK, guid);
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
}
private static Guid ParseGuidFromRequestMessage(IRequestMessage requestMessage)
private static bool TryParseGuidFromRequestMessage(IRequestMessage requestMessage, out Guid guid)
{
return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1));
var lastPart = requestMessage.Path.Split('/').LastOrDefault();
return Guid.TryParse(lastPart, out guid);
}
#endregion Mapping/{guid}
@@ -353,9 +406,9 @@ public partial class WireMockServer
return ResponseMessageBuilder.Create("Mappings saved to disk");
}
private IEnumerable<MappingModel> ToMappingModels()
private MappingModel[] ToMappingModels()
{
return Mappings.Where(m => !m.IsAdminInterface).Select(_mappingConverter.ToMappingModel);
return _mappingBuilder.GetMappings();
}
private IResponseMessage MappingsGet(IRequestMessage requestMessage)
@@ -363,6 +416,15 @@ public partial class WireMockServer
return ToJson(ToMappingModels());
}
private IResponseMessage MappingsCodeGet(IRequestMessage requestMessage)
{
var converterType = GetMappingConverterType(requestMessage);
var code = _mappingBuilder.ToCSharpCode(converterType);
return ToResponseMessage(code);
}
private IResponseMessage MappingsPost(IRequestMessage requestMessage)
{
try
@@ -418,18 +480,15 @@ public partial class WireMockServer
try
{
var mappingModels = DeserializeRequestMessageToArray<MappingModel>(requestMessage);
foreach (var mappingModel in mappingModels)
foreach (var guid in mappingModels.Where(mm => mm.Guid.HasValue).Select(mm => mm.Guid!.Value))
{
if (mappingModel.Guid.HasValue)
if (DeleteMapping(guid))
{
if (DeleteMapping(mappingModel.Guid.Value))
{
deletedGuids.Add(mappingModel.Guid.Value);
}
else
{
_settings.Logger.Debug($"Did not find/delete mapping with GUID: {mappingModel.Guid.Value}.");
}
deletedGuids.Add(guid);
}
else
{
_settings.Logger.Debug($"Did not find/delete mapping with GUID: {guid}.");
}
}
}
@@ -470,30 +529,29 @@ public partial class WireMockServer
#region Request/{guid}
private IResponseMessage RequestGet(IRequestMessage requestMessage)
{
Guid guid = ParseGuidFromRequestMessage(requestMessage);
var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
if (entry == null)
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create("Request not found", 404);
var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
if (entry is { })
{
var model = new LogEntryMapper(_options).Map(entry);
return ToJson(model);
}
}
var model = new LogEntryMapper(_options).Map(entry);
return ToJson(model);
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create("Request not found", HttpStatusCode.NotFound);
}
private IResponseMessage RequestDelete(IRequestMessage requestMessage)
{
Guid guid = ParseGuidFromRequestMessage(requestMessage);
if (DeleteLogEntry(guid))
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid))
{
return ResponseMessageBuilder.Create("Request removed");
}
return ResponseMessageBuilder.Create("Request not found", 404);
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create("Request not found", HttpStatusCode.NotFound);
}
#endregion Request/{guid}
@@ -570,7 +628,7 @@ public partial class WireMockServer
return ResetScenario(name) ?
ResponseMessageBuilder.Create("Scenario reset") :
ResponseMessageBuilder.Create($"No scenario found by name '{name}'.", 404);
ResponseMessageBuilder.Create($"No scenario found by name '{name}'.", HttpStatusCode.NotFound);
}
#endregion
@@ -693,16 +751,34 @@ public partial class WireMockServer
};
}
private static ResponseMessage ToResponseMessage(string text)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = text
},
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeTextPlain) } }
};
}
private static T DeserializeObject<T>(IRequestMessage requestMessage) where T : new()
{
return requestMessage.BodyData?.DetectedBodyType switch
switch (requestMessage.BodyData?.DetectedBodyType)
{
BodyType.String => JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString),
case BodyType.String:
case BodyType.FormUrlEncoded:
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString!);
BodyType.Json when requestMessage.BodyData?.BodyAsJson != null => ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!,
case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null:
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!;
_ => throw new NotSupportedException()
};
default:
throw new NotSupportedException();
}
}
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
@@ -726,7 +802,7 @@ public partial class WireMockServer
{
if (value is JArray jArray)
{
return jArray.ToObject<T[]>();
return jArray.ToObject<T[]>()!;
}
var singleResult = ((JObject)value).ToObject<T>();

View File

@@ -65,7 +65,7 @@ namespace WireMock.Server
}
};
if (BytesEncodingUtils.TryGetEncoding(bytes, out Encoding encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any())
if (BytesEncodingUtils.TryGetEncoding(bytes, out var encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any())
{
response.BodyData.DetectedBodyType = BodyType.String;
response.BodyData.BodyAsString = encoding.GetString(bytes);

View File

@@ -46,7 +46,7 @@ public partial class WireMockServer
}
var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true);
if (guid != null)
{
respondProvider = respondProvider.WithGuid(guid.Value);
@@ -56,6 +56,11 @@ public partial class WireMockServer
respondProvider = respondProvider.WithGuid(mappingModel.Guid.Value);
}
if (mappingModel.Data != null)
{
respondProvider = respondProvider.WithData(mappingModel.Data);
}
var timeSettings = TimeSettingsMapper.Map(mappingModel.TimeSettings);
if (timeSettings != null)
{
@@ -102,6 +107,8 @@ public partial class WireMockServer
respondProvider = respondProvider.WithWebhook(webhooks);
}
respondProvider.WithWebhookFireAndForget(mappingModel.UseWebhooksFireAndForget ?? false);
var responseBuilder = InitResponseBuilder(mappingModel.Response);
respondProvider.RespondWith(responseBuilder);

View File

@@ -14,26 +14,12 @@ namespace WireMock.Server;
public partial class WireMockServer
{
/// <inheritdoc cref="IWireMockServer.LogEntriesChanged" />
/// <inheritdoc />
[PublicAPI]
public event NotifyCollectionChangedEventHandler LogEntriesChanged
{
add
{
_options.LogEntries.CollectionChanged += (sender, eventRecordArgs) =>
{
try
{
value(sender, eventRecordArgs);
}
catch (Exception exception)
{
_options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message);
}
};
}
remove => _options.LogEntries.CollectionChanged -= value;
add => _logEntriesChanged += value;
remove => _logEntriesChanged -= value;
}
/// <inheritdoc cref="IWireMockServer.LogEntries" />
@@ -90,4 +76,24 @@ public partial class WireMockServer
return false;
}
private NotifyCollectionChangedEventHandler? _logEntriesChanged;
private void LogEntries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_logEntriesChanged is { })
{
foreach (var handler in _logEntriesChanged.GetInvocationList())
{
try
{
handler.DynamicInvoke(this, e);
}
catch (Exception exception)
{
_options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message);
}
}
}
}
}

View File

@@ -22,6 +22,7 @@ using WireMock.ResponseProviders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Server;
@@ -38,6 +39,9 @@ public partial class WireMockServer : IWireMockServer
private readonly MappingConverter _mappingConverter;
private readonly MatcherMapper _matcherMapper;
private readonly MappingToFileSaver _mappingToFileSaver;
private readonly MappingBuilder _mappingBuilder;
private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
/// <inheritdoc cref="IWireMockServer.IsStarted" />
[PublicAPI]
@@ -89,6 +93,8 @@ public partial class WireMockServer : IWireMockServer
/// </summary>
public void Dispose()
{
_options.LogEntries.CollectionChanged -= LogEntries_CollectionChanged;
Dispose(true);
GC.SuppressFinalize(this);
}
@@ -246,7 +252,7 @@ public partial class WireMockServer : IWireMockServer
[PublicAPI]
public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls)
{
Guard.NotNullOrEmpty(urls, nameof(urls));
Guard.NotNullOrEmpty(urls);
return new WireMockServer(new WireMockServerSettings
{
@@ -268,7 +274,7 @@ public partial class WireMockServer : IWireMockServer
/// <exception cref="TimeoutException">Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}</exception>
protected WireMockServer(WireMockServerSettings settings)
{
_settings = settings;
_settings = Guard.NotNull(settings);
// Set default values if not provided
_settings.Logger = settings.Logger ?? new WireMockNullLogger();
@@ -305,28 +311,21 @@ public partial class WireMockServer : IWireMockServer
}
}
_options.FileSystemHandler = _settings.FileSystemHandler;
_options.PreWireMockMiddlewareInit = _settings.PreWireMockMiddlewareInit;
_options.PostWireMockMiddlewareInit = _settings.PostWireMockMiddlewareInit;
_options.Logger = _settings.Logger;
_options.DisableJsonBodyParsing = _settings.DisableJsonBodyParsing;
_options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
_options.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_options.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
WireMockMiddlewareOptionsHelper.InitFromSettings(settings, _options);
if (settings.CustomCertificateDefined)
{
_options.X509StoreName = settings.CertificateSettings!.X509StoreName;
_options.X509StoreLocation = settings.CertificateSettings.X509StoreLocation;
_options.X509ThumbprintOrSubjectName = settings.CertificateSettings.X509StoreThumbprintOrSubjectName;
_options.X509CertificateFilePath = settings.CertificateSettings.X509CertificateFilePath;
_options.X509CertificatePassword = settings.CertificateSettings.X509CertificatePassword;
}
_options.LogEntries.CollectionChanged += LogEntries_CollectionChanged;
_matcherMapper = new MatcherMapper(_settings);
_mappingConverter = new MappingConverter(_matcherMapper);
_mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter);
_mappingBuilder = new MappingBuilder(
settings,
_options,
_mappingConverter,
_mappingToFileSaver,
_guidUtils,
_dateTimeUtils
);
#if USE_ASPNETCORE
_options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration;
@@ -538,26 +537,21 @@ public partial class WireMockServer : IWireMockServer
[PublicAPI]
public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false)
{
return new RespondWithAProvider(RegisterMapping, requestMatcher, _settings, saveToFile);
return _mappingBuilder.Given(requestMatcher, saveToFile);
}
private void RegisterMapping(IMapping mapping, bool saveToFile)
/// <inheritdoc />
[PublicAPI]
public string? MappingToCSharpCode(Guid guid, MappingConverterType converterType)
{
// Check a mapping exists with the same Guid. If so, update the datetime and replace it.
if (_options.Mappings.ContainsKey(mapping.Guid))
{
mapping.UpdatedAt = DateTime.UtcNow;
_options.Mappings[mapping.Guid] = mapping;
}
else
{
_options.Mappings.TryAdd(mapping.Guid, mapping);
}
return _mappingBuilder.ToCSharpCode(guid, converterType);
}
if (saveToFile)
{
_mappingToFileSaver.SaveMappingToFile(mapping);
}
/// <inheritdoc />
[PublicAPI]
public string MappingsToCSharpCode(MappingConverterType converterType)
{
return _mappingBuilder.ToCSharpCode(converterType);
}
private void InitSettings(WireMockServerSettings settings)

View File

@@ -1,9 +1,11 @@
using System.Security.Cryptography.X509Certificates;
namespace WireMock.Settings;
/// <summary>
/// HttpClientSettings
/// </summary>
public class HttpClientSettings
public abstract class HttpClientSettings
{
/// <summary>
/// The clientCertificate thumbprint or subject name fragment to use.
@@ -20,4 +22,9 @@ public class HttpClientSettings
/// Proxy requests should follow redirection (30x).
/// </summary>
public bool? AllowAutoRedirect { get; set; }
/// <summary>
/// The <see cref="X509Certificate2"/> to use.
/// </summary>
public X509Certificate2? Certificate { get; set; }
}

View File

@@ -28,9 +28,28 @@ public class ProxyAndRecordSettings : HttpClientSettings
/// <summary>
/// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.)
/// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported.
///
/// Deprecated : use SaveMappingSettings.
/// </summary>
[PublicAPI]
public string SaveMappingForStatusCodePattern { get; set; } = "*";
public string SaveMappingForStatusCodePattern
{
set
{
if (SaveMappingSettings is null)
{
SaveMappingSettings = new ProxySaveMappingSettings();
}
SaveMappingSettings.StatusCodePattern = value;
}
}
/// <summary>
/// Additional SaveMappingSettings.
/// </summary>
[PublicAPI]
public ProxySaveMappingSettings? SaveMappingSettings { get; set; }
/// <summary>
/// Defines a list from headers which will be excluded from the saved mappings.

View File

@@ -0,0 +1,20 @@
using WireMock.Matchers;
namespace WireMock.Settings;
public class ProxySaveMappingSetting<T>
{
public MatchBehaviour MatchBehaviour { get; } = MatchBehaviour.AcceptOnMatch;
public T Value { get; }
public ProxySaveMappingSetting(T value, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Value = value;
MatchBehaviour = matchBehaviour;
}
public static implicit operator ProxySaveMappingSetting<T>(T value) => new(value);
public static implicit operator T(ProxySaveMappingSetting<T> @this) => @this.Value;
}

View File

@@ -0,0 +1,22 @@
using JetBrains.Annotations;
namespace WireMock.Settings;
/// <summary>
/// ProxySaveMappingSettings
/// </summary>
public class ProxySaveMappingSettings
{
/// <summary>
/// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.)
/// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported.
/// </summary>
[PublicAPI]
public ProxySaveMappingSetting<string>? StatusCodePattern { get; set; } = "*";
/// <summary>
/// Only save these Http Methods. (Note that SaveMapping must also be set to true.)
/// </summary>
[PublicAPI]
public ProxySaveMappingSetting<string[]>? HttpMethods { get; set; }
}

View File

@@ -32,7 +32,7 @@ internal class SimpleCommandLineParser
}
else if (string.IsNullOrEmpty(currentName))
{
Arguments[arg] = new string[0];
Arguments[arg] = EmptyArray<string>.Value;
}
else
{

View File

@@ -1,54 +1,53 @@
using JetBrains.Annotations;
namespace WireMock.Settings
namespace WireMock.Settings;
/// <summary>
/// If https is used, these settings can be used to configure the CertificateSettings in case a custom certificate instead the default .NET certificate should be used.
///
/// X509StoreName and X509StoreLocation should be defined
/// OR
/// X509CertificateFilePath and X509CertificatePassword should be defined
/// </summary>
public class WireMockCertificateSettings
{
/// <summary>
/// If https is used, these settings can be used to configure the CertificateSettings in case a custom certificate instead the default .NET certificate should be used.
///
/// X509 StoreName (AddressBook, AuthRoot, CertificateAuthority, My, Root, TrustedPeople or TrustedPublisher)
/// </summary>
[PublicAPI]
public string? X509StoreName { get; set; }
/// <summary>
/// X509 StoreLocation (CurrentUser or LocalMachine)
/// </summary>
[PublicAPI]
public string? X509StoreLocation { get; set; }
/// <summary>
/// X509 Thumbprint or SubjectName (if not defined, the 'host' is used)
/// </summary>
[PublicAPI]
public string? X509StoreThumbprintOrSubjectName { get; set; }
/// <summary>
/// X509Certificate FilePath
/// </summary>
[PublicAPI]
public string? X509CertificateFilePath { get; set; }
/// <summary>
/// X509Certificate Password
/// </summary>
[PublicAPI]
public string? X509CertificatePassword { get; set; }
/// <summary>
/// X509StoreName and X509StoreLocation should be defined
/// OR
/// X509CertificateFilePath and X509CertificatePassword should be defined
/// </summary>
public class WireMockCertificateSettings
{
/// <summary>
/// X509 StoreName (AddressBook, AuthRoot, CertificateAuthority, My, Root, TrustedPeople or TrustedPublisher)
/// </summary>
[PublicAPI]
public string? X509StoreName { get; set; }
/// <summary>
/// X509 StoreLocation (CurrentUser or LocalMachine)
/// </summary>
[PublicAPI]
public string? X509StoreLocation { get; set; }
/// <summary>
/// X509 Thumbprint or SubjectName (if not defined, the 'host' is used)
/// </summary>
[PublicAPI]
public string? X509StoreThumbprintOrSubjectName { get; set; }
/// <summary>
/// X509Certificate FilePath
/// </summary>
[PublicAPI]
public string? X509CertificateFilePath { get; set; }
/// <summary>
/// X509Certificate Password
/// </summary>
[PublicAPI]
public string? X509CertificatePassword { get; set; }
/// <summary>
/// X509StoreName and X509StoreLocation should be defined
/// OR
/// X509CertificateFilePath and X509CertificatePassword should be defined
/// </summary>
[PublicAPI]
public bool IsDefined =>
!string.IsNullOrEmpty(X509StoreName) && !string.IsNullOrEmpty(X509StoreLocation) ||
!string.IsNullOrEmpty(X509CertificateFilePath);
}
[PublicAPI]
public bool IsDefined =>
!string.IsNullOrEmpty(X509StoreName) && !string.IsNullOrEmpty(X509StoreLocation) ||
!string.IsNullOrEmpty(X509CertificateFilePath);
}

View File

@@ -182,13 +182,13 @@ public class WireMockServerSettings
public bool? AllowCSharpCodeMatcher { get; set; }
/// <summary>
/// Allow a Body for all HTTP Methods. (default set to false).
/// Allow a Body for all HTTP Methods. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? AllowBodyForAllHttpMethods { get; set; }
/// <summary>
/// Allow only a HttpStatus Code in the response which is defined. (default set to false).
/// Allow only a HttpStatus Code in the response which is defined. (default set to <c>false</c>).
/// - false : also null, 0, empty or invalid HttpStatus codes are allowed.
/// - true : only codes defined in <see cref="System.Net.HttpStatusCode"/> are allowed.
/// </summary>
@@ -196,25 +196,31 @@ public class WireMockServerSettings
public bool? AllowOnlyDefinedHttpStatusCodeInResponse { get; set; }
/// <summary>
/// Set to true to disable Json deserialization when processing requests. (default set to false).
/// Set to true to disable Json deserialization when processing requests. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DisableJsonBodyParsing { get; set; }
/// <summary>
/// Disable support for GZip and Deflate request body decompression. (default set to false).
/// Disable support for GZip and Deflate request body decompression. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DisableRequestBodyDecompressing { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to false).
/// Set to true to disable FormUrlEncoded deserializing when processing requests. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DisableDeserializeFormUrlEncoded { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? HandleRequestsSynchronously { get; set; }
/// <summary>
/// Throw an exception when the <see cref="IMatcher"/> fails because of invalid input. (default set to false).
/// Throw an exception when the <see cref="IMatcher"/> fails because of invalid input. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? ThrowExceptionWhenMatcherFails { get; set; }
@@ -255,19 +261,19 @@ public class WireMockServerSettings
public WebhookSettings? WebhookSettings { get; set; }
/// <summary>
/// Use the <see cref="RegexExtended"/> instead of the default <see cref="Regex"/> (default set to true).
/// Use the <see cref="RegexExtended"/> instead of the default <see cref="Regex"/> (default set to <c>true</c>).
/// </summary>
[PublicAPI]
public bool? UseRegexExtended { get; set; } = true;
/// <summary>
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/> (default set to false).
/// Save unmatched requests to a file using the <see cref="IFileSystemHandler"/> (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? SaveUnmatchedRequests { get; set; }
/// <summary>
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to false).
/// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }
@@ -297,5 +303,6 @@ public class WireMockServerSettings
/// Currently used for:
/// - Handlebars Transformer
/// </summary>
[JsonIgnore]
public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture;
}

View File

@@ -46,6 +46,8 @@ public static class WireMockServerSettingsParser
AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"),
AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"),
DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"),
DisableRequestBodyDecompressing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableRequestBodyDecompressing)),
DisableDeserializeFormUrlEncoded = parser.GetBoolValue(nameof(WireMockServerSettings.DisableDeserializeFormUrlEncoded)),
HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"),
MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"),
ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"),
@@ -111,7 +113,12 @@ public static class WireMockServerSettingsParser
SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"),
UseDefinedRequestMatchers = parser.GetBoolValue(nameof(ProxyAndRecordSettings.UseDefinedRequestMatchers)),
AppendGuidToSavedMappingFile = parser.GetBoolValue(nameof(ProxyAndRecordSettings.AppendGuidToSavedMappingFile)),
Url = proxyUrl!
Url = proxyUrl!,
SaveMappingSettings = new ProxySaveMappingSettings
{
StatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern", "*"),
// HttpMethods = new ProxySaveMappingSetting<string[]>(parser.GetValues("DoNotSaveMappingForHttpMethods", new string[0]), MatchBehaviour.RejectOnMatch)
}
};
string? proxyAddress = parser.GetStringValue("WebProxyAddress");

View File

@@ -10,4 +10,6 @@ internal struct TransformModel
public IRequestMessage request { get; set; }
public IResponseMessage? response { get; set; }
public object data { get; set; }
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
@@ -84,7 +85,7 @@ internal class Transformer : ITransformer
{
responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile);
if (original.BodyData.DetectedBodyType == BodyType.String)
if (original.BodyData.DetectedBodyType is BodyType.String or BodyType.FormUrlEncoded)
{
responseMessage.BodyOriginal = original.BodyData.BodyAsString;
}
@@ -115,19 +116,28 @@ internal class Transformer : ITransformer
{
mapping = mapping,
request = request,
response = response
response = response,
data = mapping.Data ?? new { }
});
}
private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
{
return original.DetectedBodyType switch
switch (original.DetectedBodyType)
{
BodyType.Json => TransformBodyAsJson(transformerContext, options, model, original),
BodyType.File => TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile),
BodyType.String => TransformBodyAsString(transformerContext, model, original),
_ => null
};
case BodyType.Json:
return TransformBodyAsJson(transformerContext, options, model, original);
case BodyType.File:
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
case BodyType.String:
case BodyType.FormUrlEncoded:
return TransformBodyAsString(transformerContext, model, original);
default:
return null;
}
}
private static IDictionary<string, WireMockList<string>> TransformHeaders(ITransformerContext transformerContext, TransformModel model, IDictionary<string, WireMockList<string>>? original)

View File

@@ -49,12 +49,14 @@ internal static class BodyParser
new WildcardMatcher("application/vnd.*+json", true)
};
private static readonly IStringMatcher FormUrlEncodedMatcher = new WildcardMatcher("application/x-www-form-urlencoded", true);
private static readonly IStringMatcher[] TextContentTypeMatchers =
{
new WildcardMatcher("text/*", true),
new RegexMatcher("^application\\/(java|type)script$", true),
new WildcardMatcher("application/*xml", true),
new WildcardMatcher("application/x-www-form-urlencoded", true)
FormUrlEncodedMatcher
};
public static bool ShouldParseBody(string? httpMethod, bool allowBodyForAllHttpMethods)
@@ -69,7 +71,7 @@ internal static class BodyParser
return true;
}
if (BodyAllowedForMethods.TryGetValue(httpMethod!.ToUpper(), out bool allowed))
if (BodyAllowedForMethods.TryGetValue(httpMethod!.ToUpper(), out var allowed))
{
return allowed;
}
@@ -88,6 +90,11 @@ internal static class BodyParser
return BodyType.Bytes;
}
if (MatchScores.IsPerfect(FormUrlEncodedMatcher.IsMatch(contentType.MediaType)))
{
return BodyType.FormUrlEncoded;
}
if (TextContentTypeMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MediaType))))
{
return BodyType.String;
@@ -133,13 +140,30 @@ internal static class BodyParser
return data;
}
// Try to get the body as String or Json
// Try to get the body as String, FormUrlEncoded or Json
try
{
data.BodyAsString = DefaultEncoding.GetString(data.BodyAsBytes);
data.Encoding = DefaultEncoding;
data.DetectedBodyType = BodyType.String;
// If string is not null or empty, try to deserialize the string to a IDictionary<string, string>
if (settings.DeserializeFormUrlEncoded &&
data.DetectedBodyTypeFromContentType == BodyType.FormUrlEncoded &&
QueryStringParser.TryParse(data.BodyAsString, false, out var nameValueCollection)
)
{
try
{
data.BodyAsFormUrlEncoded = nameValueCollection;
data.DetectedBodyType = BodyType.FormUrlEncoded;
}
catch
{
// Deserialize FormUrlEncoded failed, just ignore.
}
}
// If string is not null or empty, try to deserialize the string to a JObject
if (settings.DeserializeJson && !string.IsNullOrEmpty(data.BodyAsString))
{

View File

@@ -13,4 +13,6 @@ internal class BodyParserSettings
public bool DecompressGZipAndDeflate { get; set; } = true;
public bool DeserializeJson { get; set; } = true;
public bool DeserializeFormUrlEncoded { get; set; } = true;
}

View File

@@ -16,18 +16,14 @@ internal static class HttpStatusRangeParser
/// <param name="pattern">The pattern. (Can be null, in that case it's allowed.)</param>
/// <param name="httpStatusCode">The value.</param>
/// <exception cref="ArgumentException"><paramref name="pattern"/> is invalid.</exception>
public static bool IsMatch(string pattern, object? httpStatusCode)
public static bool IsMatch(string? pattern, object? httpStatusCode)
{
switch (httpStatusCode)
return httpStatusCode switch
{
case int statusCodeAsInteger:
return IsMatch(pattern, statusCodeAsInteger);
case string statusCodeAsString:
return IsMatch(pattern, int.Parse(statusCodeAsString));
}
return false;
int statusCodeAsInteger => IsMatch(pattern, statusCodeAsInteger),
string statusCodeAsString => IsMatch(pattern, int.Parse(statusCodeAsString)),
_ => false
};
}
/// <summary>

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -11,38 +9,6 @@ namespace WireMock.Util;
internal static class JsonUtils
{
public static Type CreateTypeFromJObject(JObject instance, string? fullName = null)
{
static Type ConvertType(JToken value, string? propertyName = null)
{
var type = value.Type;
return type switch
{
JTokenType.Array => value.HasValues ? ConvertType(value.First!, propertyName).MakeArrayType() : typeof(object).MakeArrayType(),
JTokenType.Boolean => typeof(bool),
JTokenType.Bytes => typeof(byte[]),
JTokenType.Date => typeof(DateTime),
JTokenType.Guid => typeof(Guid),
JTokenType.Float => typeof(float),
JTokenType.Integer => typeof(long),
JTokenType.Null => typeof(object),
JTokenType.Object => CreateTypeFromJObject((JObject)value, propertyName),
JTokenType.String => typeof(string),
JTokenType.TimeSpan => typeof(TimeSpan),
JTokenType.Uri => typeof(string),
_ => typeof(object)
};
}
var properties = new Dictionary<string, Type>();
foreach (var item in instance.Properties())
{
properties.Add(item.Name, ConvertType(item.Value, item.Name));
}
return TypeBuilderUtils.BuildType(properties, fullName) ?? throw new InvalidOperationException();
}
public static bool TryParseAsJObject(string? strInput, [NotNullWhen(true)] out JObject? value)
{
value = null;
@@ -140,100 +106,4 @@ internal static class JsonUtils
_ => throw new NotSupportedException($"Unable to convert value to {typeof(T)}.")
};
}
public static string GenerateDynamicLinqStatement(JToken jsonObject)
{
var lines = new List<string>();
WalkNode(jsonObject, null, null, lines);
return lines.First();
}
private static void WalkNode(JToken node, string? path, string? propertyName, List<string> lines)
{
switch (node.Type)
{
case JTokenType.Object:
ProcessObject(node, propertyName, lines);
break;
case JTokenType.Array:
ProcessArray(node, propertyName, lines);
break;
default:
ProcessItem(node, path ?? "it", propertyName, lines);
break;
}
}
private static void ProcessObject(JToken node, string? propertyName, List<string> lines)
{
var items = new List<string>();
var text = new StringBuilder("new (");
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (var child in node.Children<JProperty>().ToArray())
{
WalkNode(child.Value, child.Path, child.Name, items);
}
text.Append(string.Join(", ", items));
text.Append(")");
if (!string.IsNullOrEmpty(propertyName))
{
text.AppendFormat(" as {0}", propertyName);
}
lines.Add(text.ToString());
}
private static void ProcessArray(JToken node, string? propertyName, List<string> lines)
{
var items = new List<string>();
var text = new StringBuilder("(new [] { ");
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
var idx = 0;
foreach (var child in node.Children().ToArray())
{
WalkNode(child, $"{node.Path}[{idx}]", null, items);
idx++;
}
text.Append(string.Join(", ", items));
text.Append("})");
if (!string.IsNullOrEmpty(propertyName))
{
text.AppendFormat(" as {0}", propertyName);
}
lines.Add(text.ToString());
}
private static void ProcessItem(JToken node, string path, string? propertyName, List<string> lines)
{
var castText = node.Type switch
{
JTokenType.Boolean => $"bool({path})",
JTokenType.Date => $"DateTime({path})",
JTokenType.Float => $"double({path})",
JTokenType.Guid => $"Guid({path})",
JTokenType.Integer => $"long({path})",
JTokenType.Null => "null",
JTokenType.String => $"string({path})",
JTokenType.TimeSpan => $"TimeSpan({path})",
JTokenType.Uri => $"Uri({path})",
_ => throw new NotSupportedException($"JTokenType '{node.Type}' cannot be converted to a Dynamic Linq cast operator.")
};
if (!string.IsNullOrEmpty(propertyName))
{
castText += $" as {propertyName}";
}
lines.Add(castText);
}
}

View File

@@ -1,7 +1,8 @@
using System;
using System.Net;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using WireMock.Types;
namespace WireMock.Util;
@@ -13,6 +14,31 @@ internal static class QueryStringParser
{
private static readonly Dictionary<string, WireMockList<string>> Empty = new();
public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary<string, string>? nameValueCollection)
{
if (queryString is null)
{
nameValueCollection = default;
return false;
}
var parts = queryString!
.Split(new[] { "&" }, StringSplitOptions.RemoveEmptyEntries)
.Select(parameter => parameter.Split('='))
.Distinct();
nameValueCollection = caseIgnore ? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, string>();
foreach (var part in parts)
{
if (part.Length == 2)
{
nameValueCollection.Add(part[0], WebUtility.UrlDecode(part[1]));
}
}
return true;
}
public static IDictionary<string, WireMockList<string>> Parse(string? queryString, QueryParameterMultipleValueSupport? support = null)
{
if (string.IsNullOrEmpty(queryString))

View File

@@ -36,14 +36,12 @@ internal static class RegexUtils
{
if (useRegexExtended)
{
var r = new RegexExtended(pattern, RegexOptions.None, RegexTimeOut);
return (true, r.IsMatch(input));
}
else
{
var r = new Regex(pattern, RegexOptions.None, RegexTimeOut);
return (true, r.IsMatch(input));
var regexExtended = new RegexExtended(pattern, RegexOptions.None, RegexTimeOut);
return (true, regexExtended.IsMatch(input));
}
var regex = new Regex(pattern, RegexOptions.None, RegexTimeOut);
return (true, regex.IsMatch(input));
}
catch
{

View File

@@ -1,119 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace WireMock.Util;
/// <summary>
/// Code based on https://stackoverflow.com/questions/40507909/convert-jobject-to-anonymous-object
/// </summary>
internal static class TypeBuilderUtils
{
private static readonly ConcurrentDictionary<IDictionary<string, Type>, Type> Types = new();
private static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder
.DefineDynamicAssembly(new AssemblyName("WireMock.Net.Reflection"), AssemblyBuilderAccess.Run)
.DefineDynamicModule("WireMock.Net.Reflection.Module");
public static Type BuildType(IDictionary<string, Type> properties, string? name = null)
{
var keyExists = Types.Keys.FirstOrDefault(k => Compare(k, properties));
if (keyExists != null)
{
return Types[keyExists];
}
var typeBuilder = GetTypeBuilder(name ?? Guid.NewGuid().ToString());
foreach (var property in properties)
{
CreateGetSetMethods(typeBuilder, property.Key, property.Value);
}
var type = typeBuilder.CreateTypeInfo()!.AsType();
Types.TryAdd(properties, type);
return type;
}
/// <summary>
/// https://stackoverflow.com/questions/3804367/testing-for-equality-between-dictionaries-in-c-sharp
/// </summary>
private static bool Compare<TKey, TValue>(IDictionary<TKey, TValue> dict1, IDictionary<TKey, TValue> dict2)
where TKey : notnull
{
if (dict1 == dict2)
{
return true;
}
if (dict1.Count != dict2.Count)
{
return false;
}
var valueComparer = EqualityComparer<TValue>.Default;
foreach (var kvp in dict1)
{
if (!dict2.TryGetValue(kvp.Key, out var value2))
{
return false;
}
if (!valueComparer.Equals(kvp.Value, value2))
{
return false;
}
}
return true;
}
private static TypeBuilder GetTypeBuilder(string name)
{
return ModuleBuilder.DefineType(name,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
}
private static void CreateGetSetMethods(TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
var getPropertyMethodBuilder = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
var getIl = getPropertyMethodBuilder.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
var setPropertyMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { propertyType });
var setIl = setPropertyMethodBuilder.GetILGenerator();
var modifyProperty = setIl.DefineLabel();
var exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropertyMethodBuilder);
propertyBuilder.SetSetMethod(setPropertyMethodBuilder);
}
}

View File

@@ -1,11 +0,0 @@
using System;
namespace WireMock.Validation
{
/// <summary>
/// To fix 'xxx' is null on at least one execution path. See also https://rules.sonarsource.com/csharp/RSPEC-3900.
/// </summary>
internal class ValidatedNotNullAttribute : Attribute
{
}
}

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