Compare commits

...

48 Commits

Author SHA1 Message Date
Stef Heyenrath
71393204cc 1.5.26 2023-05-25 21:36:01 +02:00
Cezary Piątek
e5cc6f570c Code generator improvements (#940)
* Fix quotation marks escaping in multiline string

* Add support for JsonPartialMatcher and JsonPartialWildcardMatcher in mapping code generator
2023-05-25 20:59:13 +02:00
Stef Heyenrath
7c3a0c815d Add GetParameter method to IRequestMessage (#942) 2023-05-25 15:14:02 +02:00
Stef Heyenrath
e61f08fe48 WireMockMiddleware should use HandleRequestsSynchronously correctly (#939) 2023-05-19 21:43:14 +02:00
Stef Heyenrath
11f4c47851 Add more unitests for CSharpFormatter utils (#938)
* Add unit-tests for CSharpFormatter

* .

* t
2023-05-19 16:14:26 +02:00
Stef Heyenrath
3956cd703b 1.5.25 (ReleaseNotes) 2023-05-13 11:17:03 +02:00
Stef Heyenrath
27682d0ce4 1.5.25 + Fixes in CSharpFormatter 2023-05-13 11:15:40 +02:00
Cezary Piątek
8444c8c506 Code generator improvements (#934)
* Handle new line escaping in C# mapping code generator

* Prevent date conversion when value persisted as string

* Handle object properties named as csharp keywords

* Refactor: Extract logic responsible for generating anonymous object definition to a separate class
2023-05-13 09:33:25 +02:00
Stef Heyenrath
6ef116a295 1.5.24 2023-05-07 14:48:20 +02:00
Stef Heyenrath
59195eaed8 Refactor some code (MappingConverter) 2023-05-07 14:42:00 +02:00
Cezary Piątek
7d9e450814 C# code generator improvements (#933)
* Escape quotes in generated C# strings

* Handle response with JSON body in C# code generator
2023-05-07 14:37:48 +02:00
Stef Heyenrath
7019a5a78c Add Package Readme (#932)
* Add Package Readme

* C# .NET API

* <PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
2023-05-07 09:24:48 +02:00
Stef Heyenrath
d29f3e81f3 Add property 'IsStartedWithAdminInterface' to 'IWireMockServer' (#931)
* Add property 'IsStartedWithAdminInterface' to 'IWireMockServer'

* update tests

* .
2023-05-06 13:12:00 +02:00
Stef Heyenrath
ccd8026884 Update C# mapping code generator for WithStatusCode (#930) 2023-05-06 10:24:53 +02:00
Cezary Piątek
1214ba5108 Enrich generated code with status code (#927) 2023-05-06 09:40:41 +02:00
Cezary Piątek
427715a38a Fix csharp mapping code generator (#926) 2023-05-02 16:48:32 +02:00
Stef Heyenrath
d949dfb64c 1.5.23 2023-04-23 12:06:38 +02:00
Stef Heyenrath
0a2763c06e Add IgnoreCase option to ProxyUrlReplaceSettings (#925)
* Add IgnoreCase option to ProxyUrlReplaceSettings

* fix
2023-04-23 11:56:08 +02:00
nudejustin
9ef8bd0b7b Allow removal of prefix when proxying to another server (#630) (#924)
* #630 Allow removal of prefix when proxying to another server

* #630 Rename replace to replace settings and ensure properties used in place of fields

* #630 Update replace settings type name to ProxyUrlReplaceSettings

* #630 Add admin model and update settings parser to parse new values

* Fix formatting issues

* #630 Ensure json mapping between admin model and internal model takes place

* #630 Refactor parsing and structure of extracting new proxy url

* Reduce function complexity

* #630 Fix line length issues and remove try prefix from parser methods
2023-04-23 09:31:38 +02:00
Stef Heyenrath
090e0eb437 Add WithProbability (#922)
* WithProbability

* fix

* x

* ,

* .

* .

* .
2023-04-12 20:48:53 +02:00
Stef Heyenrath
f3d52adbb2 1.5.22 2023-04-08 21:37:18 +02:00
Stef Heyenrath
a8775c3b77 Include WireMockOpenApiParser project (#916)
* Fix some nullability warnings for WireMockOpenApiParser

* .

* .

* .

* opt

* FromText

* ab

* .

* private const string AdminOpenApi = "/__admin/openapi";

* fix test

* rnd

* .

* urldetails

* 0

* ,

* .

* tests

* .

* CompressionUtilsTests

* ut

* .
2023-04-08 21:25:17 +02:00
Stef Heyenrath
3e24e3452b Add Blogs 2023-04-01 11:21:16 +02:00
Walid Haidari
95bf8e31aa #912 add excluded params to proxy mapping (#914) 2023-03-24 16:35:09 +01:00
Stef Heyenrath
090989ea7f Update comments for models (#913) 2023-03-24 09:20:23 +01:00
Stef Heyenrath
651486f718 Make some classes internal + chnage some files to file-scoped namespaces 2023-03-22 21:57:23 +01:00
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
170 changed files with 6491 additions and 2287 deletions

View File

@@ -1,3 +1,60 @@
# 1.5.26 (25 May 2023)
- [#938](https://github.com/WireMock-Net/WireMock.Net/pull/938) - Add more unitests for CSharpFormatter utils [test] contributed by [StefH](https://github.com/StefH)
- [#939](https://github.com/WireMock-Net/WireMock.Net/pull/939) - WireMockMiddleware should use HandleRequestsSynchronously correctly [bug] contributed by [StefH](https://github.com/StefH)
- [#940](https://github.com/WireMock-Net/WireMock.Net/pull/940) - Code generator improvements contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#942](https://github.com/WireMock-Net/WireMock.Net/pull/942) - Add GetParameter method to IRequestMessage [feature] contributed by [StefH](https://github.com/StefH)
- [#941](https://github.com/WireMock-Net/WireMock.Net/issues/941) - RequestMessage.GetParameter method missing from IRequestMessage interface [feature]
# 1.5.25 (13 May 2023)
- [#934](https://github.com/WireMock-Net/WireMock.Net/pull/934) - Code generator improvements [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
# 1.5.24 (07 May 2023)
- [#926](https://github.com/WireMock-Net/WireMock.Net/pull/926) - Fix C# mapping code generator for header names [bug] contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#927](https://github.com/WireMock-Net/WireMock.Net/pull/927) - Enrich generated code with status code [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
- [#930](https://github.com/WireMock-Net/WireMock.Net/pull/930) - Update C# mapping code generator for WithStatusCode [feature] contributed by [StefH](https://github.com/StefH)
- [#931](https://github.com/WireMock-Net/WireMock.Net/pull/931) - Add property 'IsStartedWithAdminInterface' to 'IWireMockServer' [feature] contributed by [StefH](https://github.com/StefH)
- [#933](https://github.com/WireMock-Net/WireMock.Net/pull/933) - C# code generator improvements [feature] contributed by [cezarypiatek](https://github.com/cezarypiatek)
# 1.5.23 (23 April 2023)
- [#922](https://github.com/WireMock-Net/WireMock.Net/pull/922) - Add WithProbability [feature] contributed by [StefH](https://github.com/StefH)
- [#924](https://github.com/WireMock-Net/WireMock.Net/pull/924) - Allow removal of prefix when proxying to another server (#630) [feature] contributed by [nudejustin](https://github.com/nudejustin)
- [#925](https://github.com/WireMock-Net/WireMock.Net/pull/925) - Add IgnoreCase option to ProxyUrlReplaceSettings [feature] contributed by [StefH](https://github.com/StefH)
# 1.5.22 (08 April 2023)
- [#914](https://github.com/WireMock-Net/WireMock.Net/pull/914) - #912 add excluded params to proxy mapping [feature] contributed by [walidhaidarii](https://github.com/walidhaidarii)
- [#916](https://github.com/WireMock-Net/WireMock.Net/pull/916) - Include WireMockOpenApiParser project [feature] contributed by [StefH](https://github.com/StefH)
- [#912](https://github.com/WireMock-Net/WireMock.Net/issues/912) - Feature: adding excluded params to proxy and records settings [feature]
# 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)
@@ -647,8 +704,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.15</VersionPrefix>
<VersionPrefix>1.5.26</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@@ -12,6 +12,7 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -29,7 +30,7 @@
<ItemGroup>
<None Include="../../resources/WireMock.Net-Logo.png" Pack="true" PackagePath="" />
<!--<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>-->
<None Include="../../PackageReadme.md" Pack="true" PackagePath=""/>
</ItemGroup>
<Choose>

View File

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

55
PackageReadme.md Normal file
View File

@@ -0,0 +1,55 @@
## WireMock.Net
Lightweight Http Mocking Server for .NET, inspired by [WireMock(http://WireMock.org) from the Java landscape.
### :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent C# .NET API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying)
* Per-request conditional proxying
* Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios
### :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing).
### :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching).
### :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating).
### :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
### :star: Using
WireMock.Net can be used in several ways:
#### UnitTesting
You can use your favorite test framework and use WireMock within your tests, see
[Wiki : UnitTesting](https://github.com/StefH/WireMock.Net/wiki/Using-WireMock-in-UnitTests).
#### As a dotnet tool
It's simple to install WireMock.Net as (global) dotnet tool, see [Wiki : dotnet tool](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-dotnet-tool).
#### As standalone process / console application
This is quite straight forward to launch a mock server within a console application, see [Wiki : Standalone Process](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
#### As a Windows Service
You can also run WireMock.Net as a Windows Service, follow this [WireMock-as-a-Windows-Service](https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-as-a-Windows-Service).
#### As a Web Job in Azure or application in IIS
See this link [WireMock-as-a-(Azure)-Web-App](https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-as-a-(Azure)-Web-App)
#### In a docker container
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
For more details see also [Docker](https://github.com/WireMock-Net/WireMock.Net-docker).
#### HTTPS / SSL
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/WireMock-Net/WireMock.Net/wiki/Using-HTTPS-(SSL))
## :books: Documentation
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).

View File

@@ -1,4 +1,8 @@
# 1.5.15 (29 January 2023)
- #878 Update REST Admin interface to support &quot;Get Mapping(s) as C# Code&quot; [feature]
# 1.5.26 (25 May 2023)
- #938 Add more unitests for CSharpFormatter utils [test]
- #939 WireMockMiddleware should use HandleRequestsSynchronously correctly [bug]
- #940 Code generator improvements
- #942 Add GetParameter method to IRequestMessage [feature]
- #941 RequestMessage.GetParameter method missing from IRequestMessage interface [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,20 +1,24 @@
# WireMock.Net
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock.org](http://WireMock.org).
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock](http://WireMock.org).
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/WireMock-Net/WireMock.Net/wiki/What-Is-WireMock.Net).
## Key Features
## :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker
* Configurable via a fluent DotNet API, JSON files and JSON over HTTP
* Configurable via a fluent C# .NET API, JSON files and JSON over HTTP
* Record/playback of stubs (proxying)
* Per-request conditional proxying
* Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios
## Info
## :memo: Blogs
- [mStack.nl : Generate C# Code from Mapping(s)](https://mstack.nl/blog/20230201-wiremock.net-tocode/)
## :computer: Project Info
| | |
| --- | --- |
| ***Project*** | &nbsp; |
@@ -27,7 +31,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/WireMock-Net/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/WireMock-Net/WireMock.Net)|
### NuGet packages
### :package: NuGet packages
| | Official | Preview [:information_source:](https://github.com/WireMock-Net/WireMock.Net/wiki/MyGet-preview-versions) |
| - | - | - |
@@ -41,23 +45,23 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://buildstats.info/nuget/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://buildstats.info/myget/wiremock-net/WireMock.Org.RestClient?includePreReleases=true)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
## Development
## :memo: Development
For the supported frameworks and build information, see [this](https://github.com/WireMock-Net/WireMock.Net/wiki/Development-Information) page.
## Stubbing
## :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing).
## Request Matching
## :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/WireMock-Net/WireMock.Net/wiki/Request-Matching).
## Response Templating
## :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/WireMock-Net/WireMock.Net/wiki/Response-Templating).
## Admin API Reference
## :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
## Using
## :star: Using
WireMock.Net can be used in several ways:
### UnitTesting

View File

@@ -28,6 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\FUNDING.yml = .github\FUNDING.yml
Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd
nuget.config = nuget.config
PackageReadme.md = PackageReadme.md
PackageReleaseNotes.template = PackageReleaseNotes.template
PackageReleaseNotes.txt = PackageReleaseNotes.txt
README.md = README.md

View File

@@ -27,6 +27,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Guids/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jmes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=openapi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pacticipant/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean>

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,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,6 +35,11 @@ namespace WireMock.Net.ConsoleApplication
}
}
public class Todo
{
public int Id { get; set; }
}
public static class MainApp
{
public static void Run()
@@ -51,8 +58,31 @@ namespace WireMock.Net.ConsoleApplication
var json = mappingBuilder.ToJson();
System.Console.WriteLine("mappingBuilder : Json = {0}", json);
var s = WireMockServer.Start();
s.Stop();
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
{
@@ -71,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 },
@@ -106,6 +136,31 @@ namespace WireMock.Net.ConsoleApplication
//server.SetAzureADAuthentication("6c2a4722-f3b9-4970-b8fc-fac41e29stef", "8587fde1-7824-42c7-8592-faf92b04stef");
// 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")
@@ -588,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 =>
@@ -595,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

@@ -11,7 +11,7 @@ public class DynamicDataGeneration : WireMockOpenApiParserDynamicExampleValues
get
{
// Since you have your Schema, you can get if max-length is set. You can generate accurate examples with this settings
var maxLength = Schema.MaxLength ?? 9;
var maxLength = Schema?.MaxLength ?? 9;
return RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex
{

View File

@@ -17,7 +17,7 @@ class Program
private static void RunMockServerWithDynamicExampleGeneration()
{
//Run your mocking framework specifieing youur Example Values generator class.
// Run your mocking framework specifying your Example Values generator class.
var serverCustomer_V2_json = Run.RunServer(Path.Combine(Folder, "Swagger_Customer_V2.0.json"), "http://localhost:8090/", true, new DynamicDataGeneration(), Types.ExampleValueType.Value, Types.ExampleValueType.Value);
Console.WriteLine("Press any key to stop the servers");
@@ -27,15 +27,15 @@ class Program
private static void RunOthersOpenApiParserExample()
{
var serverOpenAPIExamples = Run.RunServer(Path.Combine(Folder, "openAPIExamples.yaml"), "https://localhost:9091/");
var serverPetstore_V2_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V2.0.json"), "https://localhost:9092/");
var serverPetstore_V2_yaml = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V2.0.yaml"), "https://localhost:9093/");
var serverPetstore_V300_yaml = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.0.yaml"), "https://localhost:9094/");
var serverPetstore_V302_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.2.json"), "https://localhost:9095/");
var testopenapifile_json = Run.RunServer(Path.Combine(Folder, "testopenapifile.json"), "https://localhost:9096/");
var file_errorYaml = Run.RunServer(Path.Combine(Folder, "file_error.yaml"), "https://localhost:9097/");
var file_petJson = Run.RunServer(Path.Combine(Folder, "pet.json"), "https://localhost:9098/");
var refsYaml = Run.RunServer(Path.Combine(Folder, "refs.yaml"), "https://localhost:9099/");
var serverOpenAPIExamples = Run.RunServer(Path.Combine(Folder, "openAPIExamples.yaml"), "http://localhost:9091/");
var serverPetstore_V2_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V2.0.json"), "http://localhost:9092/");
var serverPetstore_V2_yaml = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V2.0.yaml"), "http://localhost:9093/");
var serverPetstore_V300_yaml = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.0.yaml"), "http://localhost:9094/");
var serverPetstore_V302_json = Run.RunServer(Path.Combine(Folder, "Swagger_Petstore_V3.0.2.json"), "http://localhost:9095/");
var testopenapifile_json = Run.RunServer(Path.Combine(Folder, "testopenapifile.json"), "http://localhost:9096/");
var file_errorYaml = Run.RunServer(Path.Combine(Folder, "file_error.yaml"), "http://localhost:9097/");
var file_petJson = Run.RunServer(Path.Combine(Folder, "pet.json"), "http://localhost:9098/");
var refsYaml = Run.RunServer(Path.Combine(Folder, "refs.yaml"), "http://localhost:9099/");
testopenapifile_json
.Given(Request.Create().WithPath("/x").UsingGet())

View File

@@ -9,64 +9,70 @@ using WireMock.Net.OpenApiParser.Types;
using WireMock.Server;
using WireMock.Settings;
namespace WireMock.Net.OpenApiParser.ConsoleApp
namespace WireMock.Net.OpenApiParser.ConsoleApp;
public static class Run
{
public static class Run
public static WireMockServer RunServer(
string path,
string url,
bool dynamicExamples = true,
IWireMockOpenApiParserExampleValues? examplesValuesGenerator = null,
ExampleValueType pathPatternToUse = ExampleValueType.Wildcard,
ExampleValueType headerPatternToUse = ExampleValueType.Wildcard
)
{
public static WireMockServer RunServer(string path, string url, bool dynamicExamples = true, IWireMockOpenApiParserExampleValues examplesValuesGenerator = null, ExampleValueType pathPatternToUse = ExampleValueType.Wildcard, ExampleValueType headerPatternToUse = ExampleValueType.Wildcard)
var server = WireMockServer.Start(new WireMockServerSettings
{
var server = WireMockServer.Start(new WireMockServerSettings
{
AllowCSharpCodeMatcher = true,
Urls = new[] { url },
StartAdminInterface = true,
ReadStaticMappings = true,
WatchStaticMappings = false,
WatchStaticMappingsInSubdirectories = false,
Logger = new WireMockConsoleLogger(),
SaveUnmatchedRequests = true
});
AllowCSharpCodeMatcher = true,
Urls = new[] { url },
StartAdminInterface = true,
ReadStaticMappings = true,
WatchStaticMappings = false,
WatchStaticMappingsInSubdirectories = false,
Logger = new WireMockConsoleLogger(),
SaveUnmatchedRequests = true
});
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
//server.SetBasicAuthentication("a", "b");
//server.SetBasicAuthentication("a", "b");
var settings = new WireMockOpenApiParserSettings
{
DynamicExamples = dynamicExamples,
ExampleValues = examplesValuesGenerator,
PathPatternToUse = pathPatternToUse,
HeaderPatternToUse = headerPatternToUse,
};
server.WithMappingFromOpenApiFile(path, settings, out var diag);
return server;
}
public static void RunServer(IEnumerable<MappingModel> mappings)
var settings = new WireMockOpenApiParserSettings
{
string url1 = "http://localhost:9091/";
DynamicExamples = dynamicExamples,
ExampleValues = examplesValuesGenerator,
PathPatternToUse = pathPatternToUse,
HeaderPatternToUse = headerPatternToUse,
};
var server = WireMockServer.Start(new WireMockServerSettings
{
AllowCSharpCodeMatcher = true,
Urls = new[] { url1 },
StartAdminInterface = true,
ReadStaticMappings = false,
WatchStaticMappings = false,
WatchStaticMappingsInSubdirectories = false,
Logger = new WireMockConsoleLogger(),
});
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
server.WithMappingFromOpenApiFile(path, settings, out var diag);
server.SetBasicAuthentication("a", "b");
return server;
}
server.WithMapping(mappings.ToArray());
public static void RunServer(IEnumerable<MappingModel> mappings)
{
string url1 = "http://localhost:9091/";
Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey();
server.Stop();
}
var server = WireMockServer.Start(new WireMockServerSettings
{
AllowCSharpCodeMatcher = true,
Urls = new[] { url1 },
StartAdminInterface = true,
ReadStaticMappings = false,
WatchStaticMappings = false,
WatchStaticMappingsInSubdirectories = false,
Logger = new WireMockConsoleLogger(),
});
Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
server.SetBasicAuthentication("a", "b");
server.WithMapping(mappings.ToArray());
Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey();
server.Stop();
}
}

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

@@ -24,8 +24,8 @@ static class Program
static async Task Main(string[] args)
{
await TestAsync().ConfigureAwait(false);
return;
//await TestAsync().ConfigureAwait(false);
//return;
XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config"));

View File

@@ -1,44 +1,43 @@
using System;
using System;
using log4net;
using Newtonsoft.Json;
using WireMock.Admin.Requests;
using WireMock.Logging;
namespace WireMock.Net.StandAlone.NETCoreApp
namespace WireMock.Net.StandAlone.NETCoreApp;
internal class WireMockLog4NetLogger : IWireMockLogger
{
internal class WireMockLog4NetLogger : IWireMockLogger
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
public void Debug(string formatString, params object[] args)
{
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
Log.DebugFormat(formatString, args);
}
public void Debug(string formatString, params object[] args)
{
Log.DebugFormat(formatString, args);
}
public void Info(string formatString, params object[] args)
{
Log.InfoFormat(formatString, args);
}
public void Info(string formatString, params object[] args)
{
Log.InfoFormat(formatString, args);
}
public void Warn(string formatString, params object[] args)
{
Log.WarnFormat(formatString, args);
}
public void Warn(string formatString, params object[] args)
{
Log.WarnFormat(formatString, args);
}
public void Error(string formatString, params object[] args)
{
Log.ErrorFormat(formatString, args);
}
public void Error(string formatString, params object[] args)
{
Log.ErrorFormat(formatString, args);
}
public void Error(string message, Exception exception)
{
Log.Error(message, exception);
}
public void Error(string message, Exception exception)
{
Log.Error(message, exception);
}
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
Log.DebugFormat("Admin[{0}] {1}", isAdminRequest, message);
}
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
Log.DebugFormat("Admin[{0}] {1}", isAdminRequest, message);
}
}

View File

@@ -19,12 +19,12 @@ public class CookieModel
public IList<MatcherModel>? Matchers { get; set; }
/// <summary>
/// Gets or sets the ignore case.
/// Gets or sets the ignore case for the Cookie Name.
/// </summary>
public bool? IgnoreCase { get; set; }
/// <summary>
/// Reject on match.
/// Gets or sets the Reject on match for the Cookie Name.
/// </summary>
public bool? RejectOnMatch { get; set; }

View File

@@ -1,19 +1,18 @@
namespace WireMock.Admin.Mappings
namespace WireMock.Admin.Mappings;
/// <summary>
/// Fault Model
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class FaultModel
{
/// <summary>
/// Fault Model
/// Gets or sets the fault. Can be null, "", NONE, EMPTY_RESPONSE or MALFORMED_RESPONSE_CHUNK.
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class FaultModel
{
/// <summary>
/// Gets or sets the fault. Can be null, "", NONE, EMPTY_RESPONSE, MALFORMED_RESPONSE_CHUNK or RANDOM_DATA_THEN_CLOSE.
/// </summary>
public string Type { get; set; }
public string? Type { get; set; }
/// <summary>
/// Gets or sets the fault percentage.
/// </summary>
public double? Percentage { get; set; }
}
/// <summary>
/// Gets or sets the fault percentage.
/// </summary>
public double? Percentage { get; set; }
}

View File

@@ -9,7 +9,7 @@ namespace WireMock.Admin.Mappings;
public class HeaderModel
{
/// <summary>
/// Gets or sets the name.
/// Gets or sets the name (key).
/// </summary>
public string Name { get; set; } = null!;
@@ -19,12 +19,12 @@ public class HeaderModel
public IList<MatcherModel>? Matchers { get; set; }
/// <summary>
/// Gets or sets the ignore case.
/// Gets or sets the ignore case for the Header Key.
/// </summary>
public bool? IgnoreCase { get; set; }
/// <summary>
/// Reject on match.
/// Gets or sets the Reject on match for the Header Key.
/// </summary>
public bool? RejectOnMatch { get; set; }

View File

@@ -84,4 +84,18 @@ 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; }
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
public double? Probability { 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

@@ -1,71 +1,76 @@
namespace WireMock.Admin.Settings;
[FluentBuilder.AutoGenerateBuilder]
public class ProxyAndRecordSettingsModel
{
/// <summary>
/// The clientCertificate thumbprint or subject name fragment to use.
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary>
public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary>
/// Defines the WebProxySettings.
/// </summary>
public WebProxySettingsModel WebProxySettings { get; set; }
/// <summary>
/// Proxy requests should follow redirection (30x).
/// </summary>
public bool? AllowAutoRedirect { get; set; }
/// <summary>
/// The URL to proxy.
/// </summary>
public string Url { get; set; }
/// <summary>
/// Save the mapping for each request/response to the internal Mappings.
/// </summary>
public bool SaveMapping { get; set; }
/// <summary>
/// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.)
/// </summary>
public bool SaveMappingToFile { get; set; }
/// <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>
public string SaveMappingForStatusCodePattern { get; set; } = "*";
/// <summary>
/// Defines a list from headers which will be excluded from the saved mappings.
/// </summary>
public string[] ExcludedHeaders { get; set; }
/// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary>
public string[] ExcludedCookies { get; set; }
/// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary>
// public bool PreferProxyMapping { get; set; }
/// <summary>
/// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping.
/// - <c>false</c>, the default matchers will be used.
/// - <c>true</c>, the defined mappings in the request wil be used for the new mapping.
///
/// Default value is false.
/// </summary>
public bool UseDefinedRequestMatchers { get; set; }
/// <summary>
/// Append an unique GUID to the filename from the saved mapping file.
/// </summary>
public bool AppendGuidToSavedMappingFile { get; set; }
namespace WireMock.Admin.Settings;
[FluentBuilder.AutoGenerateBuilder]
public class ProxyAndRecordSettingsModel
{
/// <summary>
/// The clientCertificate thumbprint or subject name fragment to use.
/// Example thumbprint : "D2DBF135A8D06ACCD0E1FAD9BFB28678DF7A9818". Example subject name: "www.google.com""
/// </summary>
public string ClientX509Certificate2ThumbprintOrSubjectName { get; set; }
/// <summary>
/// Defines the WebProxySettings.
/// </summary>
public WebProxySettingsModel WebProxySettings { get; set; }
/// <summary>
/// Proxy requests should follow redirection (30x).
/// </summary>
public bool? AllowAutoRedirect { get; set; }
/// <summary>
/// The URL to proxy.
/// </summary>
public string Url { get; set; }
/// <summary>
/// Save the mapping for each request/response to the internal Mappings.
/// </summary>
public bool SaveMapping { get; set; }
/// <summary>
/// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.)
/// </summary>
public bool SaveMappingToFile { get; set; }
/// <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>
public string SaveMappingForStatusCodePattern { get; set; } = "*";
/// <summary>
/// Defines a list from headers which will be excluded from the saved mappings.
/// </summary>
public string[] ExcludedHeaders { get; set; }
/// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary>
public string[] ExcludedCookies { get; set; }
/// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary>
// public bool PreferProxyMapping { get; set; }
/// <summary>
/// When SaveMapping is set to <c>true</c>, this setting can be used to control the behavior of the generated request matchers for the new mapping.
/// - <c>false</c>, the default matchers will be used.
/// - <c>true</c>, the defined mappings in the request wil be used for the new mapping.
///
/// Default value is false.
/// </summary>
public bool UseDefinedRequestMatchers { get; set; }
/// <summary>
/// Append an unique GUID to the filename from the saved mapping file.
/// </summary>
public bool AppendGuidToSavedMappingFile { get; set; }
/// <summary>
/// Defines the Replace Settings
/// </summary>
public ProxyUrlReplaceSettingsModel? ReplaceSettings { get; set; }
}

View File

@@ -0,0 +1,23 @@
namespace WireMock.Admin.Settings;
/// <summary>
/// Defines an old path param and a new path param to be replaced when proxying.
/// </summary>
[FluentBuilder.AutoGenerateBuilder]
public class ProxyUrlReplaceSettingsModel
{
/// <summary>
/// The old path value to be replaced by the new path value
/// </summary>
public string OldValue { get; set; } = null!;
/// <summary>
/// The new path value to replace the old value with
/// </summary>
public string NewValue { get; set; } = null!;
/// <summary>
/// Defines if the case should be ignore when replacing.
/// </summary>
public bool IgnoreCase { 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

@@ -143,6 +143,14 @@ public interface IRequestMessage
/// </summary>
string Origin { get; }
/// <summary>
/// Get a query parameter.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <returns>The query parameter value as WireMockList or null when not found.</returns>
WireMockList<string>? GetParameter(string key, bool ignoreCase = false);
#if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary>
/// Gets the connection's client certificate

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

@@ -1,23 +1,23 @@
namespace WireMock.ResponseBuilders
// ReSharper disable InconsistentNaming
namespace WireMock.ResponseBuilders;
/// <summary>
/// The FaultType enumeration
/// </summary>
public enum FaultType
{
/// <summary>
/// The FaultType enumeration
/// No Fault
/// </summary>
public enum FaultType
{
/// <summary>
/// No Fault
/// </summary>
NONE,
NONE,
/// <summary>
/// Return a completely empty response.
/// </summary>
EMPTY_RESPONSE,
/// <summary>
/// Return a completely empty response.
/// </summary>
EMPTY_RESPONSE,
/// <summary>
/// Send a defined status header, then garbage, then close the connection.
/// </summary>
MALFORMED_RESPONSE_CHUNK
}
/// <summary>
/// Send a defined status header, then garbage, then close the connection.
/// </summary>
MALFORMED_RESPONSE_CHUNK
}

View File

@@ -17,6 +17,11 @@ public interface IWireMockServer : IDisposable
/// </summary>
bool IsStarted { get; }
/// <summary>
/// Gets a value indicating whether this server is started with the admin interface enabled.
/// </summary>
bool IsStartedWithAdminInterface { get; }
/// <summary>
/// Gets the request logs.
/// </summary>

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

@@ -35,7 +35,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<!-- See also https://mstack.nl/blog/20210801-source-generators -->
<PackageReference Include="FluentBuilder" Version="0.7.0">
<PackageReference Include="FluentBuilder" Version="0.9.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -31,13 +31,13 @@ public static class WireMockServerExtensions
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
Guard.NotNull(server, nameof(server));
Guard.NotNullOrEmpty(path, nameof(path));
Guard.NotNull(server);
Guard.NotNullOrEmpty(path);
var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic);
@@ -80,9 +80,9 @@ public static class WireMockServerExtensions
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="document">The OpenAPI document to use as mappings.</param>
/// <param name="settings">Additional settings [optional]</param>
/// <param name="settings">Additional settings [optional].</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings)
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings? settings = null)
{
Guard.NotNull(server);
Guard.NotNull(document);

View File

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

View File

@@ -31,19 +31,29 @@ internal class OpenApiPathsMapper
_exampleValueGenerator = new ExampleValueGenerator(settings);
}
public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths, IList<OpenApiServer> servers)
public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers)
{
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x);
return paths?
.OrderBy(p => p.Key)
.Select(p => MapPath(p.Key, p.Value, servers))
.SelectMany(x => x)
.ToArray() ??
Array.Empty<MappingModel>();
}
private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths, IList<OpenApiServer> servers)
private IReadOnlyList<MappingModel> MapPaths(OpenApiPaths? paths, IList<OpenApiServer> servers)
{
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x);
return paths?
.OrderBy(p => p.Key)
.Select(p => MapPath(p.Key, p.Value, servers))
.SelectMany(x => x)
.ToArray() ??
Array.Empty<MappingModel>();
}
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
private IReadOnlyList<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers));
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray();
}
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers)
@@ -123,7 +133,7 @@ internal class OpenApiPathsMapper
};
}
private bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
private static bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
{
openApiMediaType = null;
contentType = null;
@@ -305,19 +315,19 @@ internal class OpenApiPathsMapper
return JObject.Parse(outputString.ToString());
}
private IDictionary<string, object?>? MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
private IDictionary<string, object>? MapHeaders(string? responseContentType, IDictionary<string, OpenApiHeader>? headers)
{
var mappedHeaders = headers.ToDictionary(
var mappedHeaders = headers?.ToDictionary(
item => item.Key,
_ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern
_ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!
);
if (!string.IsNullOrEmpty(responseContentType))
{
mappedHeaders.TryAdd(HeaderContentType, responseContentType);
mappedHeaders.TryAdd(HeaderContentType, responseContentType!);
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
return mappedHeaders?.Keys.Any() == true ? mappedHeaders : null;
}
private IList<ParamModel>? MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
@@ -360,9 +370,18 @@ internal class OpenApiPathsMapper
{
return type switch
{
ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues },
ExampleValueType.Value => new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetExampleValueAsStringForSchemaType(schema),
IgnoreCase = _settings.IgnoreCaseExampleValues
},
_ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" }
_ => new MatcherModel
{
Name = "WildcardMatcher",
Pattern = "*"
}
};
}

View File

@@ -36,5 +36,5 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV
public virtual string String { get; set; } = "example-string";
/// <inheritdoc />
public virtual OpenApiSchema? Schema { get; set; } = new OpenApiSchema();
public virtual OpenApiSchema? Schema { get; set; } = new();
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Stef.Validation;
@@ -11,116 +10,108 @@ namespace WireMock.Net.OpenApiParser.Utils;
internal class ExampleValueGenerator
{
private readonly WireMockOpenApiParserSettings _settings;
private readonly IWireMockOpenApiParserExampleValues _exampleValues;
public ExampleValueGenerator(WireMockOpenApiParserSettings settings)
{
_settings = Guard.NotNull(settings);
Guard.NotNull(settings);
// Check if user provided an own implementation
if (settings.ExampleValues is null)
{
if (_settings.DynamicExamples)
if (settings.DynamicExamples)
{
_settings.ExampleValues = new WireMockOpenApiParserDynamicExampleValues();
_exampleValues = new WireMockOpenApiParserDynamicExampleValues();
}
else
{
_settings.ExampleValues = new WireMockOpenApiParserExampleValues();
_exampleValues = new WireMockOpenApiParserExampleValues();
}
}
else
{
_exampleValues = settings.ExampleValues;
}
}
public object GetExampleValue(OpenApiSchema? schema)
{
var schemaExample = schema?.Example;
var schemaEnum = GetRandomEnumValue(schema?.Enum);
var schemaEnum = schema?.Enum?.FirstOrDefault();
_settings.ExampleValues.Schema = schema;
_exampleValues.Schema = schema;
switch (schema?.GetSchemaType())
{
case SchemaType.Boolean:
var exampleBoolean = schemaExample as OpenApiBoolean;
return exampleBoolean is null ? _settings.ExampleValues.Boolean : exampleBoolean.Value;
return exampleBoolean?.Value ?? _exampleValues.Boolean;
case SchemaType.Integer:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Int64:
var exampleLong = (OpenApiLong)schemaExample;
var enumLong = (OpenApiLong)schemaEnum;
var valueLongEnumOrExample = enumLong is null ? exampleLong?.Value : enumLong?.Value;
return valueLongEnumOrExample ?? _settings.ExampleValues.Integer;
var exampleLong = schemaExample as OpenApiLong;
var enumLong = schemaEnum as OpenApiLong;
var valueLongEnumOrExample = enumLong?.Value ?? exampleLong?.Value;
return valueLongEnumOrExample ?? _exampleValues.Integer;
default:
var exampleInteger = (OpenApiInteger)schemaExample;
var enumInteger = (OpenApiInteger)schemaEnum;
var valueIntegerEnumOrExample = enumInteger is null ? exampleInteger?.Value : enumInteger?.Value;
return valueIntegerEnumOrExample ?? _settings.ExampleValues.Integer;
var exampleInteger = schemaExample as OpenApiInteger;
var enumInteger = schemaEnum as OpenApiInteger;
var valueIntegerEnumOrExample = enumInteger?.Value ?? exampleInteger?.Value;
return valueIntegerEnumOrExample ?? _exampleValues.Integer;
}
case SchemaType.Number:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Float:
var exampleFloat = (OpenApiFloat)schemaExample;
var enumFloat = (OpenApiFloat)schemaEnum;
var valueFloatEnumOrExample = enumFloat is null ? exampleFloat?.Value : enumFloat?.Value;
return valueFloatEnumOrExample ?? _settings.ExampleValues.Float;
var exampleFloat = schemaExample as OpenApiFloat;
var enumFloat = schemaEnum as OpenApiFloat;
var valueFloatEnumOrExample = enumFloat?.Value ?? exampleFloat?.Value;
return valueFloatEnumOrExample ?? _exampleValues.Float;
default:
var exampleDouble = (OpenApiDouble)schemaExample;
var enumDouble = (OpenApiDouble)schemaEnum;
var valueDoubleEnumOrExample = enumDouble is null ? exampleDouble?.Value : enumDouble?.Value;
return valueDoubleEnumOrExample ?? _settings.ExampleValues.Double;
var exampleDouble = schemaExample as OpenApiDouble;
var enumDouble = schemaEnum as OpenApiDouble;
var valueDoubleEnumOrExample = enumDouble?.Value ?? exampleDouble?.Value;
return valueDoubleEnumOrExample ?? _exampleValues.Double;
}
default:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Date:
var exampleDate = (OpenApiDate)schemaExample;
var enumDate = (OpenApiDate)schemaEnum;
var valueDateEnumOrExample = enumDate is null ? exampleDate?.Value : enumDate?.Value;
return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _settings.ExampleValues.Date());
var exampleDate = schemaExample as OpenApiDate;
var enumDate = schemaEnum as OpenApiDate;
var valueDateEnumOrExample = enumDate?.Value ?? exampleDate?.Value;
return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _exampleValues.Date());
case SchemaFormat.DateTime:
var exampleDateTime = (OpenApiDateTime)schemaExample;
var enumDateTime = (OpenApiDateTime)schemaEnum;
var valueDateTimeEnumOrExample = enumDateTime is null ? exampleDateTime?.Value : enumDateTime?.Value;
return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _settings.ExampleValues.DateTime());
var exampleDateTime = schemaExample as OpenApiDateTime;
var enumDateTime = schemaEnum as OpenApiDateTime;
var valueDateTimeEnumOrExample = enumDateTime?.Value ?? exampleDateTime?.Value;
return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _exampleValues.DateTime());
case SchemaFormat.Byte:
var exampleByte = (OpenApiByte)schemaExample;
var enumByte = (OpenApiByte)schemaEnum;
var valueByteEnumOrExample = enumByte is null ? exampleByte?.Value : enumByte?.Value;
return valueByteEnumOrExample ?? _settings.ExampleValues.Bytes;
var exampleByte = schemaExample as OpenApiByte;
var enumByte = schemaEnum as OpenApiByte;
var valueByteEnumOrExample = enumByte?.Value ?? exampleByte?.Value;
return valueByteEnumOrExample ?? _exampleValues.Bytes;
case SchemaFormat.Binary:
var exampleBinary = (OpenApiBinary)schemaExample;
var enumBinary = (OpenApiBinary)schemaEnum;
var valueBinaryEnumOrExample = enumBinary is null ? exampleBinary?.Value : enumBinary?.Value;
return valueBinaryEnumOrExample ?? _settings.ExampleValues.Object;
var exampleBinary = schemaExample as OpenApiBinary;
var enumBinary = schemaEnum as OpenApiBinary;
var valueBinaryEnumOrExample = enumBinary?.Value ?? exampleBinary?.Value;
return valueBinaryEnumOrExample ?? _exampleValues.Object;
default:
var exampleString = (OpenApiString)schemaExample;
var enumString = (OpenApiString)schemaEnum;
var valueStringEnumOrExample = enumString is null ? exampleString?.Value : enumString?.Value;
return valueStringEnumOrExample ?? _settings.ExampleValues.String;
var exampleString = schemaExample as OpenApiString;
var enumString = schemaEnum as OpenApiString;
var valueStringEnumOrExample = enumString?.Value ?? exampleString?.Value;
return valueStringEnumOrExample ?? _exampleValues.String;
}
}
}
private static IOpenApiAny? GetRandomEnumValue(IList<IOpenApiAny>? schemaEnum)
{
if (schemaEnum?.Count > 0)
{
int maxValue = schemaEnum.Count - 1;
int randomEnum = new Random().Next(0, maxValue);
return schemaEnum[randomEnum];
}
return null;
}
}

View File

@@ -4,7 +4,7 @@
<Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description>
<TargetFrameworks>net46;netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;openapi;OAS;converter;parser;openapiparser</PackageTags>
<PackageTags>wiremock;openapi;OAS;raml;converter;parser;openapiparser</PackageTags>
<ProjectGuid>{D3804228-91F4-4502-9595-39584E5AADAD}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
@@ -16,18 +17,18 @@ namespace WireMock.Net.OpenApiParser;
/// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser
{
private readonly OpenApiStreamReader _reader = new OpenApiStreamReader();
private readonly OpenApiStreamReader _reader = new();
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, out OpenApiDiagnostic)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
@@ -44,24 +45,38 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
return FromDocument(document, settings);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, out OpenApiDiagnostic)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null)
{
return new OpenApiPathsMapper(settings ?? new WireMockOpenApiParserSettings()).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{
return FromDocument(_reader.Read(stream, out diagnostic));
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
return FromDocument(_reader.Read(stream, out diagnostic), settings);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument(OpenApiDocument, WireMockOpenApiParserSettings)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null)
public IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic)
{
return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), out diagnostic);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), settings, out diagnostic);
}
}

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,250 +1,299 @@
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>
/// Get the C# code from all mappings
/// </summary>
/// <returns>C# code</returns>
[Get("mappings/code")]
Task<string> GetMappingsCodeAsync([Query] MappingConverterType mappingConverterType = MappingConverterType.Server);
/// <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>
/// 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>
[Get("mappings/code/{guid}")]
Task<string> GetMappingCodeAsync([Path] Guid guid, [Query] MappingConverterType mappingConverterType = MappingConverterType.Server);
/// <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);
/// <summary>
/// Convert an OpenApi / RAML document to mappings.
/// </summary>
/// <param name="text">The OpenApi or RAML document as text.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("openapi/convert")]
Task<IReadOnlyList<MappingModel>> OpenApiConvertAsync([Body] string text, CancellationToken cancellationToken = default);
/// <summary>
/// Convert an OpenApi / RAML document to mappings and save these.
/// </summary>
/// <param name="text">The OpenApi or RAML document as text.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("openapi/save")]
Task<StatusModel> OpenApiSaveAsync([Body] string text, 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

@@ -0,0 +1,15 @@
#if NET451 || NET452 || NET46 || NET451 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
using System.Text.RegularExpressions;
// ReSharper disable once CheckNamespace
namespace System;
internal static class StringExtensions
{
public static string Replace(this string text, string oldValue, string newValue, StringComparison stringComparison)
{
var options = stringComparison == StringComparison.OrdinalIgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
return Regex.Replace(text, oldValue, newValue, options);
}
}
#endif

View File

@@ -1,35 +1,34 @@
using System;
using System;
namespace WireMock.Exceptions
namespace WireMock.Exceptions;
/// <summary>
/// WireMockException
/// </summary>
/// <seealso cref="Exception" />
public class WireMockException : Exception
{
/// <summary>
/// WireMockException
/// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary>
/// <seealso cref="Exception" />
public class WireMockException : Exception
public WireMockException()
{
/// <summary>
/// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary>
public WireMockException()
{
}
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public WireMockException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public WireMockException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="inner">The inner.</param>
public WireMockException(string message, Exception inner) : base(message, inner)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WireMockException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="inner">The inner.</param>
public WireMockException(string message, Exception inner) : base(message, inner)
{
}
}

View File

@@ -3,179 +3,178 @@ using System.IO;
using WireMock.Util;
using Stef.Validation;
namespace WireMock.Handlers
namespace WireMock.Handlers;
/// <summary>
/// Default implementation for a handler to interact with the local file system to read and write static mapping files.
/// </summary>
public class LocalFileSystemHandler : IFileSystemHandler
{
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
private static readonly string UnmatchedRequestsFolder = Path.Combine("requests", "unmatched");
private readonly string _rootFolder;
/// <summary>
/// Default implementation for a handler to interact with the local file system to read and write static mapping files.
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class.
/// </summary>
public class LocalFileSystemHandler : IFileSystemHandler
public LocalFileSystemHandler() : this(Directory.GetCurrentDirectory())
{
private static readonly string AdminMappingsFolder = Path.Combine("__admin", "mappings");
private static readonly string UnmatchedRequestsFolder = Path.Combine("requests", "unmatched");
}
private readonly string _rootFolder;
/// <summary>
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class.
/// </summary>
/// <param name="rootFolder">The root folder.</param>
public LocalFileSystemHandler(string rootFolder)
{
_rootFolder = rootFolder;
}
/// <summary>
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class.
/// </summary>
public LocalFileSystemHandler() : this(Directory.GetCurrentDirectory())
/// <inheritdoc cref="IFileSystemHandler.FolderExists"/>
public virtual bool FolderExists(string path)
{
Guard.NotNullOrEmpty(path);
return Directory.Exists(path);
}
/// <inheritdoc cref="IFileSystemHandler.CreateFolder"/>
public virtual void CreateFolder(string path)
{
Guard.NotNullOrEmpty(path);
Directory.CreateDirectory(path);
}
/// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/>
public virtual IEnumerable<string> EnumerateFiles(string path, bool includeSubdirectories)
{
Guard.NotNullOrEmpty(path);
return includeSubdirectories ? Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(path);
}
/// <inheritdoc cref="IFileSystemHandler.GetMappingFolder"/>
public virtual string GetMappingFolder()
{
return Path.Combine(_rootFolder, AdminMappingsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
public virtual string ReadMappingFile(string path)
{
Guard.NotNullOrEmpty(path);
return File.ReadAllText(path);
}
/// <inheritdoc cref="IFileSystemHandler.WriteMappingFile(string, string)"/>
public virtual void WriteMappingFile(string path, string text)
{
Guard.NotNullOrEmpty(path, nameof(path));
Guard.NotNull(text, nameof(text));
File.WriteAllText(path, text);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsFile"/>
public virtual byte[] ReadResponseBodyAsFile(string path)
{
Guard.NotNullOrEmpty(path);
path = PathUtils.CleanPath(path);
// If the file exists at the given path relative to the MappingsFolder, then return that.
// Else the path will just be as-is.
return File.ReadAllBytes(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsString"/>
public virtual string ReadResponseBodyAsString(string path)
{
Guard.NotNullOrEmpty(path);
path = PathUtils.CleanPath(path);
// In case the path is a filename, the path will be adjusted to the MappingFolder.
// Else the path will just be as-is.
return File.ReadAllText(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.FileExists"/>
public virtual bool FileExists(string filename)
{
Guard.NotNullOrEmpty(filename);
return File.Exists(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc />
public virtual void WriteFile(string filename, byte[] bytes)
{
Guard.NotNullOrEmpty(filename);
Guard.NotNull(bytes);
File.WriteAllBytes(AdjustPathForMappingFolder(filename), bytes);
}
/// <inheritdoc />
public virtual void WriteFile(string folder, string filename, byte[] bytes)
{
Guard.NotNullOrEmpty(folder);
Guard.NotNullOrEmpty(filename);
Guard.NotNull(bytes);
File.WriteAllBytes(PathUtils.Combine(folder, filename), bytes);
}
/// <inheritdoc cref="IFileSystemHandler.DeleteFile"/>
public virtual void DeleteFile(string filename)
{
Guard.NotNullOrEmpty(filename);
File.Delete(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFile"/>
public virtual byte[] ReadFile(string filename)
{
Guard.NotNullOrEmpty(filename);
return File.ReadAllBytes(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFileAsString"/>
public virtual string ReadFileAsString(string filename)
{
return File.ReadAllText(AdjustPathForMappingFolder(Guard.NotNullOrEmpty(filename, nameof(filename))));
}
/// <inheritdoc cref="IFileSystemHandler.GetUnmatchedRequestsFolder"/>
public virtual string GetUnmatchedRequestsFolder()
{
return Path.Combine(_rootFolder, UnmatchedRequestsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.WriteUnmatchedRequest"/>
public virtual void WriteUnmatchedRequest(string filename, string text)
{
Guard.NotNullOrEmpty(filename);
Guard.NotNull(text);
var folder = GetUnmatchedRequestsFolder();
if (!FolderExists(folder))
{
CreateFolder(folder);
}
/// <summary>
/// Initializes a new instance of the <see cref="LocalFileSystemHandler"/> class.
/// </summary>
/// <param name="rootFolder">The root folder.</param>
public LocalFileSystemHandler(string rootFolder)
{
_rootFolder = rootFolder;
}
File.WriteAllText(Path.Combine(folder, filename), text);
}
/// <inheritdoc cref="IFileSystemHandler.FolderExists"/>
public virtual bool FolderExists(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
return Directory.Exists(path);
}
/// <inheritdoc cref="IFileSystemHandler.CreateFolder"/>
public virtual void CreateFolder(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
Directory.CreateDirectory(path);
}
/// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/>
public virtual IEnumerable<string> EnumerateFiles(string path, bool includeSubdirectories)
{
Guard.NotNullOrEmpty(path, nameof(path));
return includeSubdirectories ? Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(path);
}
/// <inheritdoc cref="IFileSystemHandler.GetMappingFolder"/>
public virtual string GetMappingFolder()
{
return Path.Combine(_rootFolder, AdminMappingsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
public virtual string ReadMappingFile(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
return File.ReadAllText(path);
}
/// <inheritdoc cref="IFileSystemHandler.WriteMappingFile(string, string)"/>
public virtual void WriteMappingFile(string path, string text)
{
Guard.NotNullOrEmpty(path, nameof(path));
Guard.NotNull(text, nameof(text));
File.WriteAllText(path, text);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsFile"/>
public virtual byte[] ReadResponseBodyAsFile(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
path = PathUtils.CleanPath(path);
// If the file exists at the given path relative to the MappingsFolder, then return that.
// Else the path will just be as-is.
return File.ReadAllBytes(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsString"/>
public virtual string ReadResponseBodyAsString(string path)
{
Guard.NotNullOrEmpty(path, nameof(path));
path = PathUtils.CleanPath(path);
// In case the path is a filename, the path will be adjusted to the MappingFolder.
// Else the path will just be as-is.
return File.ReadAllText(File.Exists(PathUtils.Combine(GetMappingFolder(), path)) ? PathUtils.Combine(GetMappingFolder(), path) : path);
}
/// <inheritdoc cref="IFileSystemHandler.FileExists"/>
public virtual bool FileExists(string filename)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
return File.Exists(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc />
public virtual void WriteFile(string filename, byte[] bytes)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
Guard.NotNull(bytes, nameof(bytes));
File.WriteAllBytes(AdjustPathForMappingFolder(filename), bytes);
}
/// <inheritdoc />
public virtual void WriteFile(string folder, string filename, byte[] bytes)
{
Guard.NotNullOrEmpty(folder);
Guard.NotNullOrEmpty(filename);
Guard.NotNull(bytes);
File.WriteAllBytes(PathUtils.Combine(folder, filename), bytes);
}
/// <inheritdoc cref="IFileSystemHandler.DeleteFile"/>
public virtual void DeleteFile(string filename)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
File.Delete(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFile"/>
public virtual byte[] ReadFile(string filename)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
return File.ReadAllBytes(AdjustPathForMappingFolder(filename));
}
/// <inheritdoc cref="IFileSystemHandler.ReadFileAsString"/>
public virtual string ReadFileAsString(string filename)
{
return File.ReadAllText(AdjustPathForMappingFolder(Guard.NotNullOrEmpty(filename, nameof(filename))));
}
/// <inheritdoc cref="IFileSystemHandler.GetUnmatchedRequestsFolder"/>
public virtual string GetUnmatchedRequestsFolder()
{
return Path.Combine(_rootFolder, UnmatchedRequestsFolder);
}
/// <inheritdoc cref="IFileSystemHandler.WriteUnmatchedRequest"/>
public virtual void WriteUnmatchedRequest(string filename, string text)
{
Guard.NotNullOrEmpty(filename, nameof(filename));
Guard.NotNull(text, nameof(text));
var folder = GetUnmatchedRequestsFolder();
if (!FolderExists(folder))
{
CreateFolder(folder);
}
File.WriteAllText(Path.Combine(folder, filename), text);
}
/// <summary>
/// Adjusts the path to the MappingFolder.
/// </summary>
/// <param name="filename">The path.</param>
/// <returns>Adjusted path</returns>
private string AdjustPathForMappingFolder(string filename)
{
return Path.Combine(GetMappingFolder(), filename);
}
/// <summary>
/// Adjusts the path to the MappingFolder.
/// </summary>
/// <param name="filename">The path.</param>
/// <returns>Adjusted path</returns>
private string AdjustPathForMappingFolder(string filename)
{
return Path.Combine(GetMappingFolder(), filename);
}
}

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

@@ -120,7 +120,21 @@ public interface IMapping
/// <summary>
/// Use Fire and Forget for the defined webhook(s). [Optional]
/// </summary>
public bool? UseWebhooksFireAndForget { get; set; }
bool? UseWebhooksFireAndForget { get; }
/// <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; }
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
double? Probability { get; }
/// <summary>
/// ProvideResponseAsync

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;
internal 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

@@ -67,11 +67,17 @@ public class Mapping : IMapping
public IWebhook[]? Webhooks { get; }
/// <inheritdoc />
public bool? UseWebhooksFireAndForget { get; set; }
public bool? UseWebhooksFireAndForget { get; }
/// <inheritdoc />
public ITimeSettings? TimeSettings { get; }
/// <inheritdoc />
public object? Data { get; }
/// <inheritdoc />
public double? Probability { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.
/// </summary>
@@ -91,6 +97,8 @@ 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>
/// <param name="probability">Define the probability when this request should be matched. [Optional]</param>
public Mapping(
Guid guid,
DateTime updatedAt,
@@ -107,7 +115,9 @@ public class Mapping : IMapping
int? stateTimes,
IWebhook[]? webhooks,
bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings)
ITimeSettings? timeSettings,
object? data,
double? probability)
{
Guid = guid;
UpdatedAt = updatedAt;
@@ -125,6 +135,8 @@ public class Mapping : IMapping
Webhooks = webhooks;
UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings;
Data = data;
Probability = probability;
}
/// <inheritdoc cref="IMapping.ProvideResponseAsync" />

View File

@@ -69,12 +69,7 @@ public class MappingBuilder : IMappingBuilder
/// <inheritdoc />
public MappingModel[] GetMappings()
{
return GetMappingsInternal().Select(_mappingConverter.ToMappingModel).ToArray();
}
internal IMapping[] GetMappingsInternal()
{
return _options.Mappings.Values.ToArray().Where(m => !m.IsAdminInterface).ToArray();
return GetNonAdminMappings().Select(_mappingConverter.ToMappingModel).ToArray();
}
/// <inheritdoc />
@@ -86,7 +81,7 @@ public class MappingBuilder : IMappingBuilder
/// <inheritdoc />
public string? ToCSharpCode(Guid guid, MappingConverterType converterType)
{
var mapping = GetMappingsInternal().FirstOrDefault(m => m.Guid == guid);
var mapping = GetNonAdminMappings().FirstOrDefault(m => m.Guid == guid);
if (mapping is null)
{
return null;
@@ -101,7 +96,7 @@ public class MappingBuilder : IMappingBuilder
{
var sb = new StringBuilder();
bool addStart = true;
foreach (var mapping in GetMappingsInternal())
foreach (var mapping in GetNonAdminMappings())
{
sb.AppendLine(_mappingConverter.ToCSharpCode(mapping, new MappingConverterSettings { AddStart = addStart, ConverterType = converterType }));
@@ -123,7 +118,7 @@ public class MappingBuilder : IMappingBuilder
/// <inheritdoc />
public void SaveMappingsToFolder(string? folder)
{
foreach (var mapping in GetNonAdminMappings().Where(m => !m.IsAdminInterface))
foreach (var mapping in GetNonAdminMappings())
{
_mappingToFileSaver.SaveMappingToFile(mapping, folder);
}
@@ -131,7 +126,7 @@ public class MappingBuilder : IMappingBuilder
private IMapping[] GetNonAdminMappings()
{
return _options.Mappings.Values.ToArray();
return _options.Mappings.Values.Where(m => !m.IsAdminInterface).OrderBy(m => m.UpdatedAt).ToArray();
}
private void RegisterMapping(IMapping mapping, bool saveToFile)

View File

@@ -1,6 +1,5 @@
using System.Net.Http.Headers;
using AnyOfTypes;
using JetBrains.Annotations;
using WireMock.Models;
namespace WireMock.Matchers;

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,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Stef.Validation;
namespace WireMock.Matchers.Request;
@@ -25,9 +24,9 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
/// </summary>
/// <param name="requestMatchers">The request matchers.</param>
/// <param name="type">The CompositeMatcherType type (Defaults to 'And')</param>
protected RequestMessageCompositeMatcher([NotNull] IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
protected RequestMessageCompositeMatcher(IEnumerable<IRequestMatcher> requestMatchers, CompositeMatcherType type = CompositeMatcherType.And)
{
Guard.NotNull(requestMatchers, nameof(requestMatchers));
Guard.NotNull(requestMatchers);
_type = type;
RequestMatchers = requestMatchers;

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

@@ -1,19 +1,18 @@
using System;
namespace WireMock.Models
namespace WireMock.Models;
/// <summary>
/// TimeSettingsModel: Start, End and TTL
/// </summary>
public class TimeSettings : ITimeSettings
{
/// <summary>
/// TimeSettingsModel: Start, End and TTL
/// </summary>
public class TimeSettings : ITimeSettings
{
/// <inheritdoc />
public DateTime? Start { get; set; }
/// <inheritdoc />
public DateTime? Start { get; set; }
/// <inheritdoc />
public DateTime? End { get; set; }
/// <inheritdoc />
public DateTime? End { get; set; }
/// <inheritdoc />
public int? TTL { get; set; }
}
/// <inheritdoc />
public int? TTL { get; set; }
}

View File

@@ -1,51 +1,47 @@
using System;
using System;
using Stef.Validation;
namespace WireMock.Models
namespace WireMock.Models;
/// <summary>
/// UrlDetails
/// </summary>
public class UrlDetails
{
/// <summary>
/// UrlDetails
/// Gets the url (relative).
/// </summary>
public class UrlDetails
public Uri Url { get; }
/// <summary>
/// Gets the AbsoluteUrl.
/// </summary>
public Uri AbsoluteUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(string url) : this(new Uri(url))
{
/// <summary>
/// Gets the url (relative).
/// </summary>
public Uri Url { get; }
}
/// <summary>
/// Gets the AbsoluteUrl.
/// </summary>
public Uri AbsoluteUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(Uri url) : this(url, url)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(string url) : this(new Uri(url))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(Uri url) : this(url, url)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="absoluteUrl">The absolute URL.</param>
/// <param name="url">The URL (relative).</param>
public UrlDetails(Uri absoluteUrl, Uri url)
{
Guard.NotNull(absoluteUrl, nameof(absoluteUrl));
Guard.NotNull(url, nameof(url));
AbsoluteUrl = absoluteUrl;
Url = url;
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="absoluteUrl">The absolute URL.</param>
/// <param name="url">The URL (relative).</param>
public UrlDetails(Uri absoluteUrl, Uri url)
{
AbsoluteUrl = Guard.NotNull(absoluteUrl);
Url = Guard.NotNull(url);
}
}

View File

@@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Owin.Mappers;
using WireMock.Services;
using WireMock.Util;
namespace WireMock.Owin
@@ -66,6 +67,7 @@ namespace WireMock.Owin
{
services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();

View File

@@ -62,11 +62,11 @@ namespace WireMock.Owin.Mappers
switch (responseMessage.FaultType)
{
case FaultType.EMPTY_RESPONSE:
bytes = IsFault(responseMessage) ? new byte[0] : GetNormalBody(responseMessage);
bytes = IsFault(responseMessage) ? EmptyArray<byte>.Value : GetNormalBody(responseMessage);
break;
case FaultType.MALFORMED_RESPONSE_CHUNK:
bytes = GetNormalBody(responseMessage) ?? new byte[0];
bytes = GetNormalBody(responseMessage) ?? EmptyArray<byte>.Value;
if (IsFault(responseMessage))
{
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
@@ -87,7 +87,7 @@ namespace WireMock.Owin.Mappers
case { } typeAsString when typeAsString == typeof(string):
// Note: this case will also match on null
int.TryParse(responseMessage.StatusCode as string, out int result);
int.TryParse(responseMessage.StatusCode as string, out var result);
response.StatusCode = MapStatusCode(result);
break;
@@ -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

@@ -1,18 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Extensions;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Services;
namespace WireMock.Owin;
internal class MappingMatcher : IMappingMatcher
{
private readonly IWireMockMiddlewareOptions _options;
private readonly IRandomizerDoubleBetween0And1 _randomizerDoubleBetween0And1;
public MappingMatcher(IWireMockMiddlewareOptions options)
public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetween0And1 randomizerDoubleBetween0And1)
{
_options = Guard.NotNull(options);
_randomizerDoubleBetween0And1 = Guard.NotNull(randomizerDoubleBetween0And1);
}
public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request)
@@ -21,7 +24,12 @@ internal class MappingMatcher : IMappingMatcher
var possibleMappings = new List<MappingMatcherResult>();
foreach (var mapping in _options.Mappings.Values.Where(m => m.TimeSettings.IsValid()))
var mappings = _options.Mappings.Values
.Where(m => m.TimeSettings.IsValid())
.Where(m => m.Probability is null || m.Probability <= _randomizerDoubleBetween0And1.Generate())
.ToArray();
foreach (var mapping in mappings)
{
try
{

View File

@@ -9,6 +9,9 @@ using JetBrains.Annotations;
using WireMock.Logging;
using WireMock.Owin.Mappers;
using Stef.Validation;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
using WireMock.Services;
namespace WireMock.Owin;
@@ -70,7 +73,7 @@ internal class OwinSelfHost : IOwinSelfHost
{
var requestMapper = new OwinRequestMapper();
var responseMapper = new OwinResponseMapper(_options);
var matcher = new MappingMatcher(_options);
var matcher = new MappingMatcher(_options, new RandomizerDoubleBetween0And1());
Action<IAppBuilder> startup = app =>
{

View File

@@ -60,7 +60,7 @@ namespace WireMock.Owin
public Task Invoke(IContext ctx)
#endif
{
if (_options.HandleRequestsSynchronously.GetValueOrDefault(true))
if (_options.HandleRequestsSynchronously.GetValueOrDefault(false))
{
lock (_lock)
{

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;
@@ -35,7 +37,19 @@ internal class ProxyHelper
var requiredUri = new Uri(url);
// Create HttpRequestMessage
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, url);
var replaceSettings = proxyAndRecordSettings.ReplaceSettings;
string proxyUrl;
if (replaceSettings is not null)
{
var stringComparison = replaceSettings.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
proxyUrl = url.Replace(replaceSettings.OldValue, replaceSettings.NewValue, stringComparison);
}
else
{
proxyUrl = url;
}
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, proxyUrl);
// Call the URL
var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false);
@@ -43,16 +57,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,9 +1,8 @@
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
/// <summary>
/// IRequestBuilder
/// </summary>
public interface IRequestBuilder : IClientIPRequestBuilder
{
/// <summary>
/// IRequestBuilder
/// </summary>
public interface IRequestBuilder : IClientIPRequestBuilder
{
}
}

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

@@ -1,82 +1,81 @@
using System;
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using Stef.Validation;
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
public partial class Request
{
public partial class Request
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string pattern, MatchBehaviour matchBehaviour)
{
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string pattern, MatchBehaviour matchBehaviour)
{
return WithCookie(name, pattern, true, matchBehaviour);
}
return WithCookie(name, pattern, true, matchBehaviour);
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, bool, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(pattern, nameof(pattern));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, bool, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string pattern, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(pattern, nameof(pattern));
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, pattern, ignoreCase));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, pattern, ignoreCase));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string[], MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string[] patterns, MatchBehaviour matchBehaviour)
{
return WithCookie(name, patterns, true, matchBehaviour);
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string[], MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string[] patterns, MatchBehaviour matchBehaviour)
{
return WithCookie(name, patterns, true, matchBehaviour);
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string[], bool, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(patterns, nameof(patterns));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string[], bool, MatchBehaviour)"/>
public IRequestBuilder WithCookie(string name, string[] patterns, bool ignoreCase = true, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(name, nameof(name));
Guard.NotNull(patterns, nameof(patterns));
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, ignoreCase, patterns));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, ignoreCase, patterns));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageCookieMatcher(MatchBehaviour.AcceptOnMatch, name, false, matchers));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(MatchBehaviour.AcceptOnMatch, name, false, matchers));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, bool, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, bool ignoreCase, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, bool, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, bool ignoreCase, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageCookieMatcher(MatchBehaviour.AcceptOnMatch, name, ignoreCase, matchers));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(MatchBehaviour.AcceptOnMatch, name, ignoreCase, matchers));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, bool ignoreCase, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, IStringMatcher[])"/>
public IRequestBuilder WithCookie(string name, bool ignoreCase, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
Guard.NotNull(name, nameof(name));
Guard.NotNullOrEmpty(matchers, nameof(matchers));
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, ignoreCase, matchers));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(matchBehaviour, name, ignoreCase, matchers));
return this;
}
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(Func{IDictionary{string, string}, bool}[])"/>
public IRequestBuilder WithCookie(params Func<IDictionary<string, string>, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs, nameof(funcs));
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(Func{IDictionary{string, string}, bool}[])"/>
public IRequestBuilder WithCookie(params Func<IDictionary<string, string>, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessageCookieMatcher(funcs));
return this;
}
_requestMatchers.Add(new RequestMessageCookieMatcher(funcs));
return this;
}
}

View File

@@ -7,86 +7,85 @@ using WireMock.Matchers.Request;
using WireMock.Types;
using Stef.Validation;
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
public partial class Request
{
public partial class Request
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
return WithParam(key, false, matchBehaviour);
}
return WithParam(key, false, matchBehaviour);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(key);
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, MatchBehaviour)"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
Guard.NotNull(key);
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase));
return this;
}
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase));
return this;
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, string[])"/>
public IRequestBuilder WithParam(string key, params string[] values)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, false, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, string[])"/>
public IRequestBuilder WithParam(string key, params string[] values)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, false, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, string[])"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, params string[] values)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, string[])"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, params string[] values)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, params IStringMatcher[] matchers)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, false, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, params IStringMatcher[] matchers)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, false, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, params IStringMatcher[] matchers)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, bool, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, bool ignoreCase, params IStringMatcher[] matchers)
{
return WithParam(key, MatchBehaviour.AcceptOnMatch, ignoreCase, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, string[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params string[] values)
{
return WithParam(key, matchBehaviour, false, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, string[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params string[] values)
{
return WithParam(key, matchBehaviour, false, values);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, bool, string[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, params string[] values)
{
Guard.NotNull(key);
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, bool, string[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase = false, params string[] values)
{
Guard.NotNull(key);
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, values));
return this;
}
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, values));
return this;
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
return WithParam(key, matchBehaviour, false, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, params IStringMatcher[] matchers)
{
return WithParam(key, matchBehaviour, false, matchers);
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, bool, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase, params IStringMatcher[] matchers)
{
Guard.NotNull(key);
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour, bool, IStringMatcher[])"/>
public IRequestBuilder WithParam(string key, MatchBehaviour matchBehaviour, bool ignoreCase, params IStringMatcher[] matchers)
{
Guard.NotNull(key);
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, matchers));
return this;
}
_requestMatchers.Add(new RequestMessageParamMatcher(matchBehaviour, key, ignoreCase, matchers));
return this;
}
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(Func{IDictionary{string, WireMockList{string}}, bool}[])"/>
public IRequestBuilder WithParam(params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs, nameof(funcs));
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(Func{IDictionary{string, WireMockList{string}}, bool}[])"/>
public IRequestBuilder WithParam(params Func<IDictionary<string, WireMockList<string>>, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs, nameof(funcs));
_requestMatchers.Add(new RequestMessageParamMatcher(funcs));
return this;
}
_requestMatchers.Add(new RequestMessageParamMatcher(funcs));
return this;
}
}

View File

@@ -3,47 +3,46 @@ using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders
namespace WireMock.RequestBuilders;
public partial class Request
{
public partial class Request
/// <inheritdoc />
public IRequestBuilder WithUrl(params IStringMatcher[] matchers)
{
/// <inheritdoc />
public IRequestBuilder WithUrl(params IStringMatcher[] matchers)
{
return WithUrl(MatchOperator.Or, matchers);
}
/// <inheritdoc />
public IRequestBuilder WithUrl(MatchOperator matchOperator, params IStringMatcher[] matchers)
{
Guard.NotNullOrEmpty(matchers);
_requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithUrl(params string[] urls)
{
return WithUrl(MatchOperator.Or, urls);
}
/// <inheritdoc />
public IRequestBuilder WithUrl(MatchOperator matchOperator, params string[] urls)
{
Guard.NotNullOrEmpty(urls);
_requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, urls));
return this;
}
/// <inheritdoc cref="IUrlAndPathRequestBuilder.WithUrl(Func{string, bool}[])"/>
public IRequestBuilder WithUrl(params Func<string, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs);
_requestMatchers.Add(new RequestMessageUrlMatcher(funcs));
return this;
}
return WithUrl(MatchOperator.Or, matchers);
}
}
/// <inheritdoc />
public IRequestBuilder WithUrl(MatchOperator matchOperator, params IStringMatcher[] matchers)
{
Guard.NotNullOrEmpty(matchers);
_requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithUrl(params string[] urls)
{
return WithUrl(MatchOperator.Or, urls);
}
/// <inheritdoc />
public IRequestBuilder WithUrl(MatchOperator matchOperator, params string[] urls)
{
Guard.NotNullOrEmpty(urls);
_requestMatchers.Add(new RequestMessageUrlMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, urls));
return this;
}
/// <inheritdoc cref="IUrlAndPathRequestBuilder.WithUrl(Func{string, bool}[])"/>
public IRequestBuilder WithUrl(params Func<string, bool>[] funcs)
{
Guard.NotNullOrEmpty(funcs);
_requestMatchers.Add(new RequestMessageUrlMatcher(funcs));
return this;
}
}

View File

@@ -63,5 +63,4 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
{
return _requestMatchers.OfType<T>().FirstOrDefault(func);
}
}

View File

@@ -180,12 +180,7 @@ public class RequestMessage : IRequestMessage
#endif
}
/// <summary>
/// Get a query parameter.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <returns>The query parameter.</returns>
/// <inheritdoc />
public WireMockList<string>? GetParameter(string key, bool ignoreCase = false)
{
if (Query == null)

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,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Constants;
@@ -14,7 +16,9 @@ using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
using static WireMock.Util.CSharpFormatter;
namespace WireMock.Serialization;
internal class MappingConverter
@@ -33,7 +37,7 @@ internal class MappingConverter
settings ??= new MappingConverterSettings();
var request = (Request)mapping.RequestMatcher;
var response = (Response) mapping.Provider;
var response = (Response)mapping.Provider;
var clientIPMatcher = request.GetRequestMessageMatcher<RequestMessageClientIPMatcher>();
var pathMatcher = request.GetRequestMessageMatcher<RequestMessagePathMatcher>();
@@ -103,11 +107,28 @@ internal class MappingConverter
if (bodyMatcher is { Matchers: { } })
{
var wildcardMatcher = bodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault();
if (wildcardMatcher is { } && wildcardMatcher.GetPatterns().Any())
if (bodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any())
{
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})");
}
else if (bodyMatcher.Matchers.OfType<JsonPartialMatcher>().FirstOrDefault() is { Value: { } } jsonPartialMatcher)
{
sb.AppendLine(@$" .WithBody(new JsonPartialMatcher(
value: {ToCSharpStringLiteral(jsonPartialMatcher.Value.ToString())},
ignoreCase: {ToCSharpBooleanLiteral(jsonPartialMatcher.IgnoreCase)},
throwException: {ToCSharpBooleanLiteral(jsonPartialMatcher.ThrowException)},
regex: {ToCSharpBooleanLiteral(jsonPartialMatcher.Regex)}
))");
}
else if (bodyMatcher.Matchers.OfType<JsonPartialWildcardMatcher>().FirstOrDefault() is { Value: { } } jsonPartialWildcardMatcher)
{
sb.AppendLine(@$" .WithBody(new JsonPartialWildcardMatcher(
value: {ToCSharpStringLiteral(jsonPartialWildcardMatcher.Value.ToString())},
ignoreCase: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.IgnoreCase)},
throwException: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.ThrowException)},
regex: {ToCSharpBooleanLiteral(jsonPartialWildcardMatcher.Regex)}
))");
}
}
sb.AppendLine(@" )");
@@ -115,23 +136,50 @@ internal class MappingConverter
// Guid
sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")");
if (mapping.Probability != null)
{
sb.AppendLine($" .WithProbability({mapping.Probability.Value.ToString(CultureInfoUtils.CultureInfoEnUS)})");
}
// Response
sb.AppendLine(" .RespondWith(Response.Create()");
if (response.ResponseMessage.StatusCode is int or string)
{
sb.AppendLine($" .WithStatusCode({JsonConvert.SerializeObject(response.ResponseMessage.StatusCode)})");
}
else if (response.ResponseMessage.StatusCode is HttpStatusCode httpStatusCode)
{
sb.AppendLine($" .WithStatusCode({(int)httpStatusCode})");
}
if (response.ResponseMessage.Headers is { })
{
foreach (var header in response.ResponseMessage.Headers)
{
sb.AppendLine($" .WithHeader(\"{header.Key})\", {ToValueArguments(header.Value.ToArray())})");
sb.AppendLine($" .WithHeader(\"{header.Key}\", {ToValueArguments(header.Value.ToArray())})");
}
}
if (response.ResponseMessage.BodyData is { })
if (response.ResponseMessage.BodyData is { } bodyData)
{
switch (response.ResponseMessage.BodyData.DetectedBodyType)
{
case BodyType.String:
sb.AppendLine($" .WithBody(\"{response.ResponseMessage.BodyData.BodyAsString}\")");
case BodyType.FormUrlEncoded:
sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyData.BodyAsString)})");
break;
case BodyType.Json:
if (bodyData.BodyAsJson is string bodyStringValue)
{
sb.AppendLine($" .WithBody({ToCSharpStringLiteral(bodyStringValue)})");
}
else if(bodyData.BodyAsJson is {} jsonBody)
{
var anonymousObjectDefinition = ConvertToAnonymousObjectDefinition(jsonBody);
sb.AppendLine($" .WithBodyAsJson({anonymousObjectDefinition})");
}
break;
}
}
@@ -140,7 +188,7 @@ internal class MappingConverter
{
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
}
else if (response.MinimumDelayMilliseconds > 0 && response.MaximumDelayMilliseconds > 0)
else if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
{
sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
}
@@ -181,6 +229,8 @@ internal class MappingConverter
Scenario = mapping.Scenario,
WhenStateIs = mapping.ExecutionConditionState,
SetStateTo = mapping.NextState,
Data = mapping.Data,
Probability = mapping.Probability,
Request = new RequestModel
{
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
@@ -324,6 +374,7 @@ internal class MappingConverter
switch (response.ResponseMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString;
break;
@@ -371,7 +422,7 @@ internal class MappingConverter
private static string GetString(IStringMatcher stringMatcher)
{
return stringMatcher.GetPatterns().Select(p => $"\"{p.GetPattern()}\"").First();
return stringMatcher.GetPatterns().Select(p => ToCSharpStringLiteral(p.GetPattern())).First();
}
private static string[] GetStringArray(IReadOnlyList<IStringMatcher> stringMatchers)
@@ -424,7 +475,7 @@ internal class MappingConverter
private static string ToValueArguments(string[]? values, string defaultValue = "")
{
return values is { } ? string.Join(", ", values.Select(v => $"\"{v}\"")) : $"\"{defaultValue}\"";
return values is { } ? string.Join(", ", values.Select(ToCSharpStringLiteral)) : ToCSharpStringLiteral(defaultValue);
}
private static WebProxyModel? MapWebProxy(WebProxySettings? settings)
@@ -455,4 +506,6 @@ internal class MappingConverter
return newDictionary;
}
}

View File

@@ -26,8 +26,13 @@ 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 excludedParams = proxyAndRecordSettings.ExcludedParams ?? new string[0];
var request = (Request?)mapping?.RequestMatcher;
var clientIPMatcher = request?.GetRequestMessageMatcher<RequestMessageClientIPMatcher>();
var pathMatcher = request?.GetRequestMessageMatcher<RequestMessagePathMatcher>();
@@ -37,11 +42,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
@@ -75,12 +75,21 @@ internal class ProxyMappingConverter
{
foreach (var paramMatcher in paramMatchers)
{
newRequest.WithParam(paramMatcher.Key, paramMatcher.MatchBehaviour, paramMatcher.Matchers!.ToArray());
if (!excludedParams.Contains(paramMatcher.Key, StringComparer.OrdinalIgnoreCase))
{
newRequest.WithParam(paramMatcher.Key, paramMatcher.MatchBehaviour, paramMatcher.Matchers!.ToArray());
}
}
}
else
{
requestMessage.Query?.Loop((key, value) => newRequest.WithParam(key, false, value.ToArray()));
requestMessage.Query?.Loop((key, value) =>
{
if (!excludedParams.Contains(key, StringComparer.OrdinalIgnoreCase))
{
newRequest.WithParam(key, false, value.ToArray());
}
});
}
// Cookies
@@ -142,6 +151,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 +188,9 @@ internal class ProxyMappingConverter
stateTimes: null,
webhooks: null,
useWebhooksFireAndForget: null,
timeSettings: null
timeSettings: null,
data: mapping?.Data,
probability: null
);
}
}

View File

@@ -54,10 +54,12 @@ internal static class SwaggerMapper
{
operation.Parameters.Add(openApiParameter);
}
foreach (var openApiParameter in MapRequestHeaders(mapping.Request.Headers))
{
operation.Parameters.Add(openApiParameter);
}
foreach (var openApiParameter in MapRequestCookies(mapping.Request.Cookies))
{
operation.Parameters.Add(openApiParameter);
@@ -94,7 +96,7 @@ internal static class SwaggerMapper
return openApiDocument.ToJson(SchemaType.OpenApi3, Formatting.Indented);
}
private static IEnumerable<OpenApiParameter> MapRequestQueryParameters(IList<ParamModel>? queryParameters)
private static IReadOnlyList<OpenApiParameter> MapRequestQueryParameters(IList<ParamModel>? queryParameters)
{
if (queryParameters == null)
{
@@ -146,7 +148,7 @@ internal static class SwaggerMapper
.ToList();
}
private static IEnumerable<OpenApiParameter> MapRequestCookies(IList<CookieModel>? cookies)
private static IReadOnlyList<OpenApiParameter> MapRequestCookies(IList<CookieModel>? cookies)
{
if (cookies == null)
{

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,22 @@ 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>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithData(object data);
/// <summary>
/// Define the probability when this request should be matched. Value is between 0 and 1.
/// </summary>
/// <param name="probability">The probability when this request should be matched. Value is between 0 and 1.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithProbability(double probability);
}

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;
@@ -17,6 +18,12 @@ namespace WireMock.Server;
/// </summary>
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 int _priority;
private string? _title;
private string? _description;
@@ -25,13 +32,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
private string? _nextState;
private string? _scenario;
private int _timesInSameState = 1;
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly WireMockServerSettings _settings;
private readonly IDateTimeUtils _dateTimeUtils;
private readonly bool _saveToFile;
private bool? _useWebhookFireAndForget;
private double? _probability;
public Guid Guid { get; private set; }
@@ -39,6 +41,8 @@ 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>
@@ -88,10 +92,21 @@ internal class RespondWithAProvider : IRespondWithAProvider
_timesInSameState,
Webhooks,
_useWebhookFireAndForget,
TimeSettings);
TimeSettings,
Data,
_probability);
_registrationCallback(mapping, _saveToFile);
}
/// <inheritdoc />
[PublicAPI]
public IRespondWithAProvider WithData(object data)
{
Data = data;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithGuid(string guid)
{
@@ -272,6 +287,13 @@ internal class RespondWithAProvider : IRespondWithAProvider
return this;
}
public IRespondWithAProvider WithProbability(double probability)
{
_probability = Guard.Condition(probability, p => p is >= 0 and <= 1.0);
return this;
}
private static IWebhook InitWebhook(
string url,
string method,

View File

@@ -37,6 +37,8 @@ public partial class WireMockServer
private const string AdminRequests = "/__admin/requests";
private const string AdminSettings = "/__admin/settings";
private const string AdminScenarios = "/__admin/scenarios";
private const string AdminOpenApi = "/__admin/openapi";
private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
@@ -113,6 +115,10 @@ public partial class WireMockServer
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileGet));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileHead));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete));
// __admin/openapi
Given(Request.Create().WithPath($"{AdminOpenApi}/convert").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiConvertToMappings));
Given(Request.Create().WithPath($"{AdminOpenApi}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiSaveToMappings));
}
#endregion
@@ -217,10 +223,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,
@@ -228,14 +241,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
};
@@ -250,10 +260,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;
@@ -261,8 +277,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);
@@ -729,7 +743,7 @@ public partial class WireMockServer
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
}
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false)
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false, object? statusCode = null)
{
return new ResponseMessage
{
@@ -738,7 +752,7 @@ public partial class WireMockServer
DetectedBodyType = BodyType.String,
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault)
},
StatusCode = (int)HttpStatusCode.OK,
StatusCode = statusCode ?? (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
};
}
@@ -759,14 +773,18 @@ public partial class WireMockServer
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)

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

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings;
@@ -14,7 +15,7 @@ namespace WireMock.Server;
public partial class WireMockServer
{
private void ConvertMappingsAndRegisterAsRespondProvider(MappingModel[] mappingModels, string? path = null)
private void ConvertMappingsAndRegisterAsRespondProvider(IReadOnlyList<MappingModel> mappingModels, string? path = null)
{
var duplicateGuids = mappingModels
.Where(m => m.Guid != null)
@@ -56,6 +57,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,7 +108,15 @@ public partial class WireMockServer
respondProvider = respondProvider.WithWebhook(webhooks);
}
respondProvider.WithWebhookFireAndForget(mappingModel.UseWebhooksFireAndForget ?? false);
if (mappingModel.UseWebhooksFireAndForget == true)
{
respondProvider.WithWebhookFireAndForget(mappingModel.UseWebhooksFireAndForget.Value);
}
if (mappingModel.Probability != null)
{
respondProvider.WithProbability(mappingModel.Probability.Value);
}
var responseBuilder = InitResponseBuilder(mappingModel.Response);
respondProvider.RespondWith(responseBuilder);
@@ -200,11 +214,11 @@ public partial class WireMockServer
{
foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null))
{
requestBuilder = requestBuilder.WithCookie(
cookieModel.Name,
cookieModel.IgnoreCase == true,
cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch,
cookieModel.Matchers!.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
requestBuilder = requestBuilder.WithCookie(
cookieModel.Name,
cookieModel.IgnoreCase == true,
cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch,
cookieModel.Matchers!.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
}
}

View File

@@ -0,0 +1,54 @@
#if OPENAPIPARSER
using System;
using System.Linq;
using System.Net;
using WireMock.Net.OpenApiParser;
#endif
namespace WireMock.Server;
public partial class WireMockServer
{
private IResponseMessage OpenApiConvertToMappings(IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body, out var diagnostic);
return diagnostic.Errors.Any() ? ToJson(diagnostic, false, HttpStatusCode.BadRequest) : ToJson(mappingModels);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(e.Message, HttpStatusCode.BadRequest);
}
#else
return ResponseMessageBuilder.Create("Not supported for .NETStandard 1.3 and .NET 4.5.2 or lower.", 400);
#endif
}
private IResponseMessage OpenApiSaveToMappings(IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body, out var diagnostic);
if (diagnostic.Errors.Any())
{
return ToJson(diagnostic, false, HttpStatusCode.BadRequest);
}
ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create("OpenApi document converted to Mappings", HttpStatusCode.Created);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(e.Message, HttpStatusCode.BadRequest);
}
#else
return ResponseMessageBuilder.Create("Not supported for .NETStandard 1.3 and .NET 4.5.2 or lower.", 400);
#endif
}
}

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