Compare commits

..

47 Commits

Author SHA1 Message Date
Stef Heyenrath
dabe3a2a10 Merge branch 'master' into stef-IgnoreOpenApiErrors 2023-06-03 17:01:15 +02:00
Stef Heyenrath
1f1bc05f00 1.5.27 2023-06-03 17:00:18 +02:00
Stef Heyenrath
c107e38e3b Fix WireMock.Net.FluentAssertions for net47 2023-06-02 12:03:12 +02:00
Stef Heyenrath
b609191095 . 2023-06-01 21:14:13 +02:00
Stef Heyenrath
b1ae9aaf46 Merge branch 'master' into stef-IgnoreOpenApiErrors 2023-06-01 21:04:21 +02:00
Stef Heyenrath
a77c4fe1ac Add ".NET Framework 4.7" to WireMock.Net.FluentAssertions (#949) 2023-06-01 18:06:28 +02:00
Stef Heyenrath
ad3ef83c5e . 2023-05-30 21:41:30 +02:00
Stef Heyenrath
35ffbbc7d9 RegexExampleValueGenerator 2023-05-28 23:10:09 +02:00
Stef Heyenrath
c1e71707c5 Add warning logging when sending a request to a Webhook does not return status 200 (#946) 2023-05-28 10:53:54 +02:00
Oleg Nenashev
69499afe43 Update Slack link (#944)
Better to use https://slack.wiremock.org/ to have proper Slack signup
2023-05-26 10:01:16 +02:00
Stef Heyenrath
aadac78577 add Slack 2023-05-25 23:49:12 +02:00
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
144 changed files with 5190 additions and 1759 deletions

View File

@@ -1,3 +1,50 @@
# 1.5.27 (03 June 2023)
- [#946](https://github.com/WireMock-Net/WireMock.Net/pull/946) - Add warning logging when sending a request to a Webhook does not return status 200 [feature] contributed by [StefH](https://github.com/StefH)
- [#949](https://github.com/WireMock-Net/WireMock.Net/pull/949) - Add &quot;.NET Framework 4.7&quot; to WireMock.Net.FluentAssertions [feature] contributed by [StefH](https://github.com/StefH)
- [#928](https://github.com/WireMock-Net/WireMock.Net/issues/928) - TypeLoadException when using WithHeader method. [bug]
- [#945](https://github.com/WireMock-Net/WireMock.Net/issues/945) - Webhook logging [feature]
# 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) # 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) - [#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) - [#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)

View File

@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>1.5.18</VersionPrefix> <VersionPrefix>1.5.27</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon> <PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/WireMock-Net/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@@ -12,6 +12,7 @@
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl> <RepositoryUrl>https://github.com/WireMock-Net/WireMock.Net</RepositoryUrl>
<ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon> <ApplicationIcon>../../resources/WireMock.Net-Logo.ico</ApplicationIcon>
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
<LangVersion>Latest</LangVersion> <LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@@ -29,7 +30,7 @@
<ItemGroup> <ItemGroup>
<None Include="../../resources/WireMock.Net-Logo.png" Pack="true" PackagePath="" /> <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> </ItemGroup>
<Choose> <Choose>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.5.18 SET version=1.5.27
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels question invalid doc duplicate --version %version% --token %GH_TOKEN% 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,8 +1,7 @@
# 1.5.18 (09 March 2023) # 1.5.27 (03 June 2023)
- #893 Add 'Data' to response which can be used during transforming the response [feature] - #946 Add warning logging when sending a request to a Webhook does not return status 200 [feature]
- #896 Bump Microsoft.Owin from 2.0.2 to 4.2.2 in /examples/WireMock.Net.Service [dependencies] - #949 Add &quot;.NET Framework 4.7&quot; to WireMock.Net.FluentAssertions [feature]
- #900 ProxySettings : Add logic to not save some requests depending on HttpMethods [feature] - #928 TypeLoadException when using WithHeader method. [bug]
- #897 WebHostBuilder.ConfigureServices method not found when using nunit3testadapter 4.4.0 [bug] - #945 Webhook logging [feature]
- #899 Ignore OPTIONS request when using proxyandrecord [feature]
The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md The full release notes can be found here: https://github.com/WireMock-Net/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,24 +1,28 @@
# WireMock.Net # 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). 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 * HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
* Library can be used in unit tests and integration tests * Library can be used in unit tests and integration tests
* Runs as a standalone process, as windows service, as Azure/IIS or as docker * 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) * Record/playback of stubs (proxying)
* Per-request conditional proxying * Per-request conditional proxying
* Stateful behaviour simulation * Stateful behaviour simulation
* Response templating / transformation using Handlebars and extensions * Response templating / transformation using Handlebars and extensions
* Can be used locally or in CI/CD scenarios * 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; | | ***Project*** | &nbsp; |
| &nbsp;&nbsp;**Chat** | [![Gitter](https://img.shields.io/gitter/room/wiremock_dotnet/Lobby.svg)](https://gitter.im/wiremock_dotnet/Lobby) | | &nbsp;&nbsp;**Chat** | [![Slack](https://badgen.net/badge/icon/slack?icon=slack&label)](https://slack.wiremock.org/) [![Gitter](https://img.shields.io/gitter/room/wiremock_dotnet/Lobby.svg)](https://gitter.im/wiremock_dotnet/Lobby) |
| &nbsp;&nbsp;**Issues** | [![GitHub issues](https://img.shields.io/github/issues/WireMock-Net/WireMock.Net.svg)](https://github.com/WireMock-Net/WireMock.Net/issues) | | &nbsp;&nbsp;**Issues** | [![GitHub issues](https://img.shields.io/github/issues/WireMock-Net/WireMock.Net.svg)](https://github.com/WireMock-Net/WireMock.Net/issues) |
| | | | | |
| ***Quality*** | &nbsp; | | ***Quality*** | &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;**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)| | &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) | | | 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) | &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. 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. 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). 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). 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). 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). 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: WireMock.Net can be used in several ways:
### UnitTesting ### UnitTesting

View File

@@ -28,6 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\FUNDING.yml = .github\FUNDING.yml .github\FUNDING.yml = .github\FUNDING.yml
Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd Generate-ReleaseNotes.cmd = Generate-ReleaseNotes.cmd
nuget.config = nuget.config nuget.config = nuget.config
PackageReadme.md = PackageReadme.md
PackageReleaseNotes.template = PackageReleaseNotes.template PackageReleaseNotes.template = PackageReleaseNotes.template
PackageReleaseNotes.txt = PackageReleaseNotes.txt PackageReleaseNotes.txt = PackageReleaseNotes.txt
README.md = README.md 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/=Guids/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@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/=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/=Pacticipant/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Raml/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean>

View File

@@ -11,7 +11,7 @@ public class DynamicDataGeneration : WireMockOpenApiParserDynamicExampleValues
get get
{ {
// Since you have your Schema, you can get if max-length is set. You can generate accurate examples with this settings // 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 return RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex
{ {

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using WireMock.Net.OpenApiParser.Types;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
@@ -17,8 +18,8 @@ class Program
private static void RunMockServerWithDynamicExampleGeneration() 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); var serverCustomer_V2_json = Run.RunServer(Path.Combine(Folder, "Swagger_Customer_V2.0.json"), "http://localhost:8090/", true, new DynamicDataGeneration(), ExampleValueType.Value, ExampleValueType.Value);
Console.WriteLine("Press any key to stop the servers"); Console.WriteLine("Press any key to stop the servers");
Console.ReadKey(); Console.ReadKey();
@@ -27,15 +28,15 @@ class Program
private static void RunOthersOpenApiParserExample() private static void RunOthersOpenApiParserExample()
{ {
var serverOpenAPIExamples = Run.RunServer(Path.Combine(Folder, "openAPIExamples.yaml"), "https://localhost:9091/"); 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"), "https://localhost:9092/"); 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"), "https://localhost:9093/"); 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"), "https://localhost:9094/"); 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"), "https://localhost:9095/"); 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"), "https://localhost:9096/"); var testopenapifile_json = Run.RunServer(Path.Combine(Folder, "testopenapifile.json"), "http://localhost:9096/");
var file_errorYaml = Run.RunServer(Path.Combine(Folder, "file_error.yaml"), "https://localhost:9097/"); var file_errorYaml = Run.RunServer(Path.Combine(Folder, "file_error.yaml"), "http://localhost:9097/");
var file_petJson = Run.RunServer(Path.Combine(Folder, "pet.json"), "https://localhost:9098/"); var file_petJson = Run.RunServer(Path.Combine(Folder, "pet.json"), "http://localhost:9098/");
var refsYaml = Run.RunServer(Path.Combine(Folder, "refs.yaml"), "https://localhost:9099/"); var refsYaml = Run.RunServer(Path.Combine(Folder, "refs.yaml"), "http://localhost:9099/");
testopenapifile_json testopenapifile_json
.Given(Request.Create().WithPath("/x").UsingGet()) .Given(Request.Create().WithPath("/x").UsingGet())

View File

@@ -9,11 +9,18 @@ using WireMock.Net.OpenApiParser.Types;
using WireMock.Server; using WireMock.Server;
using WireMock.Settings; 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
{ {
@@ -69,4 +76,3 @@ namespace WireMock.Net.OpenApiParser.ConsoleApp
server.Stop(); server.Stop();
} }
} }
}

View File

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

View File

@@ -1,11 +1,11 @@
using System; using System;
using log4net; using log4net;
using Newtonsoft.Json; using Newtonsoft.Json;
using WireMock.Admin.Requests; using WireMock.Admin.Requests;
using WireMock.Logging; 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)); private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
@@ -41,4 +41,3 @@ namespace WireMock.Net.StandAlone.NETCoreApp
Log.DebugFormat("Admin[{0}] {1}", isAdminRequest, message); Log.DebugFormat("Admin[{0}] {1}", isAdminRequest, message);
} }
} }
}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using WireMock.Models; using WireMock.Models;
namespace WireMock.Admin.Mappings; namespace WireMock.Admin.Mappings;
@@ -94,4 +93,9 @@ public class MappingModel
/// </example> /// </example>
/// </summary> /// </summary>
public object? Data { get; set; } 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

@@ -68,4 +68,9 @@ public class ProxyAndRecordSettingsModel
/// Append an unique GUID to the filename from the saved mapping file. /// Append an unique GUID to the filename from the saved mapping file.
/// </summary> /// </summary>
public bool AppendGuidToSavedMappingFile { get; set; } 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; } public int? MaxRequestLogCount { get; set; }
/// <summary> /// <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> /// </summary>
public bool? AllowBodyForAllHttpMethods { get; set; } public bool? AllowBodyForAllHttpMethods { get; set; }
/// <summary> /// <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> /// </summary>
public bool? HandleRequestsSynchronously { get; set; } public bool? HandleRequestsSynchronously { get; set; }
/// <summary> /// <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> /// </summary>
public bool? ThrowExceptionWhenMatcherFails { get; set; } public bool? ThrowExceptionWhenMatcherFails { get; set; }
/// <summary> /// <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> /// </summary>
public bool? UseRegexExtended { get; set; } public bool? UseRegexExtended { get; set; }
/// <summary> /// <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> /// </summary>
public bool? SaveUnmatchedRequests { get; set; } public bool? SaveUnmatchedRequests { get; set; }
@@ -86,7 +108,7 @@ public class SettingsModel
public HostingScheme? HostingScheme { get; set; } public HostingScheme? HostingScheme { get; set; }
/// <summary> /// <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> /// </summary>
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; } public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }

View File

@@ -143,6 +143,14 @@ public interface IRequestMessage
/// </summary> /// </summary>
string Origin { get; } 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 #if NETSTANDARD1_3_OR_GREATER || NET461
/// <summary> /// <summary>
/// Gets the connection's client certificate /// Gets the connection's client certificate

View File

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

View File

@@ -1,5 +1,6 @@
namespace WireMock.ResponseBuilders // ReSharper disable InconsistentNaming
{ namespace WireMock.ResponseBuilders;
/// <summary> /// <summary>
/// The FaultType enumeration /// The FaultType enumeration
/// </summary> /// </summary>
@@ -20,4 +21,3 @@
/// </summary> /// </summary>
MALFORMED_RESPONSE_CHUNK MALFORMED_RESPONSE_CHUNK
} }
}

View File

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

View File

@@ -1,5 +1,5 @@
namespace WireMock.Types namespace WireMock.Types;
{
/// <summary> /// <summary>
/// The BodyType /// The BodyType
/// </summary> /// </summary>
@@ -33,6 +33,10 @@
/// <summary> /// <summary>
/// Body is a MultiPart /// Body is a MultiPart
/// </summary> /// </summary>
MultiPart 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" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<!-- See also https://mstack.nl/blog/20210801-source-generators --> <!-- 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> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -4,7 +4,7 @@
<Description>FluentAssertions extensions for WireMock.Net</Description> <Description>FluentAssertions extensions for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.FluentAssertions</AssemblyTitle> <AssemblyTitle>WireMock.Net.FluentAssertions</AssemblyTitle>
<Authors>Mahmoud Ali;Stef Heyenrath</Authors> <Authors>Mahmoud Ali;Stef Heyenrath</Authors>
<TargetFrameworks>net451;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks> <TargetFrameworks>net451;net47;netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.FluentAssertions</AssemblyName> <AssemblyName>WireMock.Net.FluentAssertions</AssemblyName>
<PackageId>WireMock.Net.FluentAssertions</PackageId> <PackageId>WireMock.Net.FluentAssertions</PackageId>

View File

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

View File

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

@@ -23,27 +23,28 @@ internal class OpenApiPathsMapper
private const string HeaderContentType = "Content-Type"; private const string HeaderContentType = "Content-Type";
private readonly WireMockOpenApiParserSettings _settings; private readonly WireMockOpenApiParserSettings _settings;
private readonly ExampleValueGenerator _exampleValueGenerator; private readonly IExampleValueGenerator _exampleValueGenerator;
private readonly IExampleValueGenerator _regexExampleValueGenerator;
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings) public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{ {
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
_exampleValueGenerator = new ExampleValueGenerator(settings); _exampleValueGenerator = new ExampleValueGenerator(settings);
_regexExampleValueGenerator = new RegexExampleValueGenerator(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> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
{ {
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray();
}
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
{
return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers));
} }
private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers) private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers)
@@ -123,7 +124,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; openApiMediaType = null;
contentType = null; contentType = null;
@@ -270,54 +271,19 @@ internal class OpenApiPathsMapper
return newPath; return newPath;
} }
private string MapBasePath(IList<OpenApiServer>? servers) private IDictionary<string, object>? MapHeaders(string? responseContentType, IDictionary<string, OpenApiHeader>? headers)
{ {
if (servers == null || servers.Count == 0) var mappedHeaders = headers?.ToDictionary(
{
return string.Empty;
}
OpenApiServer server = servers.First();
if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult))
{
return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString();
}
return string.Empty;
}
private JToken? MapOpenApiAnyToJToken(IOpenApiAny? any)
{
if (any == null)
{
return null;
}
using var outputString = new StringWriter();
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
if (any.AnyType == AnyType.Array)
{
return JArray.Parse(outputString.ToString());
}
return JObject.Parse(outputString.ToString());
}
private IDictionary<string, object?>? MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
{
var mappedHeaders = headers.ToDictionary(
item => item.Key, item => item.Key,
_ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!
); );
if (!string.IsNullOrEmpty(responseContentType)) 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) private IList<ParamModel>? MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
@@ -356,19 +322,38 @@ internal class OpenApiPathsMapper
return list.Any() ? list : null; return list.Any() ? list : null;
} }
private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType type) private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType exampleValueType)
{ {
return type switch return exampleValueType switch
{ {
ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues }, ExampleValueType.Value => new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetExampleValueAsStringForSchemaType(schema, exampleValueType),
IgnoreCase = _settings.IgnoreCaseExampleValues
},
_ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" } ExampleValueType.Regex => new MatcherModel
{
Name = "RegexMatcher",
Pattern = GetExampleValueAsStringForSchemaType(schema, exampleValueType),
IgnoreCase = _settings.IgnoreCaseExampleValues
},
_ => new MatcherModel
{
Name = "WildcardMatcher",
Pattern = "*",
IgnoreCase = _settings.IgnoreCaseExampleValues
}
}; };
} }
private string GetExampleValueAsStringForSchemaType(OpenApiSchema? schema) private string GetExampleValueAsStringForSchemaType(OpenApiSchema? schema, ExampleValueType exampleValueType)
{ {
var value = _exampleValueGenerator.GetExampleValue(schema); var value = exampleValueType == ExampleValueType.Regex ?
_regexExampleValueGenerator.GetExampleValue(schema) :
_exampleValueGenerator.GetExampleValue(schema);
return value switch return value switch
{ {
@@ -377,4 +362,39 @@ internal class OpenApiPathsMapper
_ => value.ToString(), _ => value.ToString(),
}; };
} }
private static string MapBasePath(IList<OpenApiServer>? servers)
{
if (servers == null || servers.Count == 0)
{
return string.Empty;
}
var server = servers.First();
if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult))
{
return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString();
}
return string.Empty;
}
private static JToken? MapOpenApiAnyToJToken(IOpenApiAny? any)
{
if (any == null)
{
return null;
}
using var outputString = new StringWriter();
var writer = new OpenApiJsonWriter(outputString);
any.Write(writer, OpenApiSpecVersion.OpenApi3_0);
if (any.AnyType == AnyType.Array)
{
return JArray.Parse(outputString.ToString());
}
return JObject.Parse(outputString.ToString());
}
} }

View File

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

View File

@@ -1,7 +1,7 @@
namespace WireMock.Net.OpenApiParser.Types; namespace WireMock.Net.OpenApiParser.Types;
/// <summary> /// <summary>
/// The example value to use /// The (example) value pattern to use.
/// </summary> /// </summary>
public enum ExampleValueType public enum ExampleValueType
{ {
@@ -12,6 +12,11 @@ public enum ExampleValueType
/// </summary> /// </summary>
Value, Value,
/// <summary>
/// Build a Regex based on the SchemaType.
/// </summary>
Regex,
/// <summary> /// <summary>
/// Just use a Wildcard (*) character. /// Just use a Wildcard (*) character.
/// </summary> /// </summary>

View File

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

@@ -0,0 +1,8 @@
using Microsoft.OpenApi.Models;
namespace WireMock.Net.OpenApiParser.Utils;
internal interface IExampleValueGenerator
{
object GetExampleValue(OpenApiSchema? schema);
}

View File

@@ -0,0 +1,40 @@
using Microsoft.OpenApi.Models;
using Stef.Validation;
using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Utils;
internal class RegexExampleValueGenerator : IExampleValueGenerator
{
public RegexExampleValueGenerator(WireMockOpenApiParserSettings settings)
{
Guard.NotNull(settings);
}
public object GetExampleValue(OpenApiSchema? schema)
{
switch (schema?.GetSchemaType())
{
case SchemaType.Boolean:
return @"(true|false)";
case SchemaType.Integer:
return @"-?\d+";
case SchemaType.Number:
return @"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?";
default:
return schema?.GetSchemaFormat() switch
{
SchemaFormat.Date => @"(\d{4})-([01]\d)-([0-3]\d)",
SchemaFormat.DateTime => @"(\d{4})-([01]\d)-([0-3]\d)T([0-2]\d):([0-5]\d):([0-5]\d)(\.\d+)?(Z|[+-][0-2]\d:[0-5]\d)",
SchemaFormat.Byte => @"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",
SchemaFormat.Binary => @"[a-zA-Z0-9\+/]*={0,3}",
_ => ".*"
};
}
}
}

View File

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

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Readers;
@@ -8,6 +9,7 @@ using RamlToOpenApiConverter;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Mappers; using WireMock.Net.OpenApiParser.Mappers;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser; namespace WireMock.Net.OpenApiParser;
@@ -16,18 +18,25 @@ namespace WireMock.Net.OpenApiParser;
/// </summary> /// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser public class WireMockOpenApiParser : IWireMockOpenApiParser
{ {
private readonly OpenApiStreamReader _reader = new OpenApiStreamReader(); private readonly WireMockOpenApiParserSettings _wireMockOpenApiParserSettings = new WireMockOpenApiParserSettings
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, out OpenApiDiagnostic)" />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{ {
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic); HeaderPatternToUse = ExampleValueType.Regex,
QueryParameterPatternToUse = ExampleValueType.Regex,
PathPatternToUse = ExampleValueType.Regex
};
private readonly OpenApiStreamReader _reader = new();
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{
return FromFile(path, _wireMockOpenApiParserSettings, out diagnostic);
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" /> /// <inheritdoc />
[PublicAPI] [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; OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase)) if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
@@ -44,24 +53,38 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
return FromDocument(document, settings); return FromDocument(document, settings);
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, out OpenApiDiagnostic)" /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic) public IReadOnlyList<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null)
{
return new OpenApiPathsMapper(settings ?? _wireMockOpenApiParserSettings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{ {
return FromDocument(_reader.Read(stream, out diagnostic)); return FromDocument(_reader.Read(stream, out diagnostic));
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" /> /// <inheritdoc />
[PublicAPI] [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); return FromDocument(_reader.Read(stream, out diagnostic), settings);
} }
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument(OpenApiDocument, WireMockOpenApiParserSettings)" /> /// <inheritdoc />
[PublicAPI] [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

@@ -280,4 +280,20 @@ public interface IWireMockAdminApi
/// <param name="cancellationToken">The optional cancellationToken.</param> /// <param name="cancellationToken">The optional cancellationToken.</param>
[Head("files/{filename}")] [Head("files/{filename}")]
Task FileExistsAsync([Path] string filename, CancellationToken cancellationToken = default); 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

@@ -0,0 +1,17 @@
{
"help": "https://go.microsoft.com/fwlink/?linkid=866610",
"root": true,
"dependentFileProviders": {
"add": {
"addedExtension": {},
"pathSegment": {
"add": {
".*": [
".cs"
]
}
}
}
}
}

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,7 +1,7 @@
using System; using System;
namespace WireMock.Exceptions;
namespace WireMock.Exceptions
{
/// <summary> /// <summary>
/// WireMockException /// WireMockException
/// </summary> /// </summary>
@@ -32,4 +32,3 @@ namespace WireMock.Exceptions
{ {
} }
} }
}

View File

@@ -3,8 +3,8 @@ using System.IO;
using WireMock.Util; using WireMock.Util;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Handlers namespace WireMock.Handlers;
{
/// <summary> /// <summary>
/// Default implementation for a handler to interact with the local file system to read and write static mapping files. /// Default implementation for a handler to interact with the local file system to read and write static mapping files.
/// </summary> /// </summary>
@@ -34,7 +34,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.FolderExists"/> /// <inheritdoc cref="IFileSystemHandler.FolderExists"/>
public virtual bool FolderExists(string path) public virtual bool FolderExists(string path)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
return Directory.Exists(path); return Directory.Exists(path);
} }
@@ -42,7 +42,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.CreateFolder"/> /// <inheritdoc cref="IFileSystemHandler.CreateFolder"/>
public virtual void CreateFolder(string path) public virtual void CreateFolder(string path)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
} }
@@ -50,7 +50,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/> /// <inheritdoc cref="IFileSystemHandler.EnumerateFiles"/>
public virtual IEnumerable<string> EnumerateFiles(string path, bool includeSubdirectories) public virtual IEnumerable<string> EnumerateFiles(string path, bool includeSubdirectories)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
return includeSubdirectories ? Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(path); return includeSubdirectories ? Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(path);
} }
@@ -64,7 +64,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/> /// <inheritdoc cref="IFileSystemHandler.ReadMappingFile"/>
public virtual string ReadMappingFile(string path) public virtual string ReadMappingFile(string path)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
return File.ReadAllText(path); return File.ReadAllText(path);
} }
@@ -81,7 +81,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsFile"/> /// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsFile"/>
public virtual byte[] ReadResponseBodyAsFile(string path) public virtual byte[] ReadResponseBodyAsFile(string path)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
path = PathUtils.CleanPath(path); path = PathUtils.CleanPath(path);
// If the file exists at the given path relative to the MappingsFolder, then return that. // If the file exists at the given path relative to the MappingsFolder, then return that.
// Else the path will just be as-is. // Else the path will just be as-is.
@@ -91,7 +91,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsString"/> /// <inheritdoc cref="IFileSystemHandler.ReadResponseBodyAsString"/>
public virtual string ReadResponseBodyAsString(string path) public virtual string ReadResponseBodyAsString(string path)
{ {
Guard.NotNullOrEmpty(path, nameof(path)); Guard.NotNullOrEmpty(path);
path = PathUtils.CleanPath(path); path = PathUtils.CleanPath(path);
// In case the path is a filename, the path will be adjusted to the MappingFolder. // In case the path is a filename, the path will be adjusted to the MappingFolder.
// Else the path will just be as-is. // Else the path will just be as-is.
@@ -101,7 +101,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.FileExists"/> /// <inheritdoc cref="IFileSystemHandler.FileExists"/>
public virtual bool FileExists(string filename) public virtual bool FileExists(string filename)
{ {
Guard.NotNullOrEmpty(filename, nameof(filename)); Guard.NotNullOrEmpty(filename);
return File.Exists(AdjustPathForMappingFolder(filename)); return File.Exists(AdjustPathForMappingFolder(filename));
} }
@@ -109,8 +109,8 @@ namespace WireMock.Handlers
/// <inheritdoc /> /// <inheritdoc />
public virtual void WriteFile(string filename, byte[] bytes) public virtual void WriteFile(string filename, byte[] bytes)
{ {
Guard.NotNullOrEmpty(filename, nameof(filename)); Guard.NotNullOrEmpty(filename);
Guard.NotNull(bytes, nameof(bytes)); Guard.NotNull(bytes);
File.WriteAllBytes(AdjustPathForMappingFolder(filename), bytes); File.WriteAllBytes(AdjustPathForMappingFolder(filename), bytes);
} }
@@ -128,7 +128,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.DeleteFile"/> /// <inheritdoc cref="IFileSystemHandler.DeleteFile"/>
public virtual void DeleteFile(string filename) public virtual void DeleteFile(string filename)
{ {
Guard.NotNullOrEmpty(filename, nameof(filename)); Guard.NotNullOrEmpty(filename);
File.Delete(AdjustPathForMappingFolder(filename)); File.Delete(AdjustPathForMappingFolder(filename));
} }
@@ -136,7 +136,7 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.ReadFile"/> /// <inheritdoc cref="IFileSystemHandler.ReadFile"/>
public virtual byte[] ReadFile(string filename) public virtual byte[] ReadFile(string filename)
{ {
Guard.NotNullOrEmpty(filename, nameof(filename)); Guard.NotNullOrEmpty(filename);
return File.ReadAllBytes(AdjustPathForMappingFolder(filename)); return File.ReadAllBytes(AdjustPathForMappingFolder(filename));
} }
@@ -156,8 +156,8 @@ namespace WireMock.Handlers
/// <inheritdoc cref="IFileSystemHandler.WriteUnmatchedRequest"/> /// <inheritdoc cref="IFileSystemHandler.WriteUnmatchedRequest"/>
public virtual void WriteUnmatchedRequest(string filename, string text) public virtual void WriteUnmatchedRequest(string filename, string text)
{ {
Guard.NotNullOrEmpty(filename, nameof(filename)); Guard.NotNullOrEmpty(filename);
Guard.NotNull(text, nameof(text)); Guard.NotNull(text);
var folder = GetUnmatchedRequestsFolder(); var folder = GetUnmatchedRequestsFolder();
if (!FolderExists(folder)) if (!FolderExists(folder))
@@ -178,4 +178,3 @@ namespace WireMock.Handlers
return Path.Combine(GetMappingFolder(), filename); return Path.Combine(GetMappingFolder(), filename);
} }
} }
}

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Models; using WireMock.Models;
@@ -121,7 +120,7 @@ public interface IMapping
/// <summary> /// <summary>
/// Use Fire and Forget for the defined webhook(s). [Optional] /// Use Fire and Forget for the defined webhook(s). [Optional]
/// </summary> /// </summary>
bool? UseWebhooksFireAndForget { get; set; } bool? UseWebhooksFireAndForget { get; }
/// <summary> /// <summary>
/// Data Object which can be used when WithTransformer is used. /// Data Object which can be used when WithTransformer is used.
@@ -130,7 +129,12 @@ public interface IMapping
/// lookup data "1" /// lookup data "1"
/// </example> /// </example>
/// </summary> /// </summary>
object? Data { get; set; } object? Data { get; }
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
double? Probability { get; }
/// <summary> /// <summary>
/// ProvideResponseAsync /// ProvideResponseAsync

View File

@@ -4,7 +4,7 @@ using System.Linq.Dynamic.Core;
namespace WireMock.Json; namespace WireMock.Json;
public class DynamicPropertyWithValue : DynamicProperty internal class DynamicPropertyWithValue : DynamicProperty
{ {
public object? Value { get; } public object? Value { get; }

View File

@@ -60,7 +60,7 @@ internal static class JObjectExtensions
{ {
if (src == null) if (src == null)
{ {
return new object?[0]; return EmptyArray<object?>.Value;
} }
return ConvertJTokenArray(src, options); return ConvertJTokenArray(src, options);

View File

@@ -67,13 +67,16 @@ public class Mapping : IMapping
public IWebhook[]? Webhooks { get; } public IWebhook[]? Webhooks { get; }
/// <inheritdoc /> /// <inheritdoc />
public bool? UseWebhooksFireAndForget { get; set; } public bool? UseWebhooksFireAndForget { get; }
/// <inheritdoc /> /// <inheritdoc />
public ITimeSettings? TimeSettings { get; } public ITimeSettings? TimeSettings { get; }
/// <inheritdoc /> /// <inheritdoc />
public object? Data { get; set; } public object? Data { get; }
/// <inheritdoc />
public double? Probability { get; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class. /// Initializes a new instance of the <see cref="Mapping"/> class.
@@ -95,6 +98,7 @@ public class Mapping : IMapping
/// <param name="useWebhooksFireAndForget">Use Fire and Forget for the defined webhook(s). [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="timeSettings">The TimeSettings. [Optional]</param>
/// <param name="data">The data object. [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( public Mapping(
Guid guid, Guid guid,
DateTime updatedAt, DateTime updatedAt,
@@ -112,7 +116,8 @@ public class Mapping : IMapping
IWebhook[]? webhooks, IWebhook[]? webhooks,
bool? useWebhooksFireAndForget, bool? useWebhooksFireAndForget,
ITimeSettings? timeSettings, ITimeSettings? timeSettings,
object? data) object? data,
double? probability)
{ {
Guid = guid; Guid = guid;
UpdatedAt = updatedAt; UpdatedAt = updatedAt;
@@ -131,6 +136,7 @@ public class Mapping : IMapping
UseWebhooksFireAndForget = useWebhooksFireAndForget; UseWebhooksFireAndForget = useWebhooksFireAndForget;
TimeSettings = timeSettings; TimeSettings = timeSettings;
Data = data; Data = data;
Probability = probability;
} }
/// <inheritdoc cref="IMapping.ProvideResponseAsync" /> /// <inheritdoc cref="IMapping.ProvideResponseAsync" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
using System; using System;
namespace WireMock.Models namespace WireMock.Models;
{
/// <summary> /// <summary>
/// TimeSettingsModel: Start, End and TTL /// TimeSettingsModel: Start, End and TTL
/// </summary> /// </summary>
@@ -16,4 +16,3 @@ namespace WireMock.Models
/// <inheritdoc /> /// <inheritdoc />
public int? TTL { get; set; } public int? TTL { get; set; }
} }
}

View File

@@ -1,8 +1,8 @@
using System; using System;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Models namespace WireMock.Models;
{
/// <summary> /// <summary>
/// UrlDetails /// UrlDetails
/// </summary> /// </summary>
@@ -41,11 +41,7 @@ namespace WireMock.Models
/// <param name="url">The URL (relative).</param> /// <param name="url">The URL (relative).</param>
public UrlDetails(Uri absoluteUrl, Uri url) public UrlDetails(Uri absoluteUrl, Uri url)
{ {
Guard.NotNull(absoluteUrl, nameof(absoluteUrl)); AbsoluteUrl = Guard.NotNull(absoluteUrl);
Guard.NotNull(url, nameof(url)); Url = Guard.NotNull(url);
AbsoluteUrl = absoluteUrl;
Url = url;
}
} }
} }

View File

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

View File

@@ -62,11 +62,11 @@ namespace WireMock.Owin.Mappers
switch (responseMessage.FaultType) switch (responseMessage.FaultType)
{ {
case FaultType.EMPTY_RESPONSE: case FaultType.EMPTY_RESPONSE:
bytes = IsFault(responseMessage) ? new byte[0] : GetNormalBody(responseMessage); bytes = IsFault(responseMessage) ? EmptyArray<byte>.Value : GetNormalBody(responseMessage);
break; break;
case FaultType.MALFORMED_RESPONSE_CHUNK: case FaultType.MALFORMED_RESPONSE_CHUNK:
bytes = GetNormalBody(responseMessage) ?? new byte[0]; bytes = GetNormalBody(responseMessage) ?? EmptyArray<byte>.Value;
if (IsFault(responseMessage)) if (IsFault(responseMessage))
{ {
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray(); bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
@@ -87,7 +87,7 @@ namespace WireMock.Owin.Mappers
case { } typeAsString when typeAsString == typeof(string): case { } typeAsString when typeAsString == typeof(string):
// Note: this case will also match on null // 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); response.StatusCode = MapStatusCode(result);
break; break;
@@ -130,6 +130,7 @@ namespace WireMock.Owin.Mappers
switch (responseMessage.BodyData?.DetectedBodyType) switch (responseMessage.BodyData?.DetectedBodyType)
{ {
case BodyType.String: case BodyType.String:
case BodyType.FormUrlEncoded:
return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!); return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!);
case BodyType.Json: case BodyType.Json:

View File

@@ -1,18 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using WireMock.Extensions;
using Stef.Validation; using Stef.Validation;
using WireMock.Extensions;
using WireMock.Services;
namespace WireMock.Owin; namespace WireMock.Owin;
internal class MappingMatcher : IMappingMatcher internal class MappingMatcher : IMappingMatcher
{ {
private readonly IWireMockMiddlewareOptions _options; private readonly IWireMockMiddlewareOptions _options;
private readonly IRandomizerDoubleBetween0And1 _randomizerDoubleBetween0And1;
public MappingMatcher(IWireMockMiddlewareOptions options) public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetween0And1 randomizerDoubleBetween0And1)
{ {
_options = Guard.NotNull(options); _options = Guard.NotNull(options);
_randomizerDoubleBetween0And1 = Guard.NotNull(randomizerDoubleBetween0And1);
} }
public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request) public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request)
@@ -21,7 +24,12 @@ internal class MappingMatcher : IMappingMatcher
var possibleMappings = new List<MappingMatcherResult>(); 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 try
{ {

View File

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

View File

@@ -60,7 +60,7 @@ namespace WireMock.Owin
public Task Invoke(IContext ctx) public Task Invoke(IContext ctx)
#endif #endif
{ {
if (_options.HandleRequestsSynchronously.GetValueOrDefault(true)) if (_options.HandleRequestsSynchronously.GetValueOrDefault(false))
{ {
lock (_lock) lock (_lock)
{ {
@@ -216,7 +216,12 @@ namespace WireMock.Owin
{ {
try try
{ {
await webhookSender.SendAsync(httpClientForWebhook, mapping, webhookRequest, request, response).ConfigureAwait(false); var result = await webhookSender.SendAsync(httpClientForWebhook, mapping, webhookRequest, request, response).ConfigureAwait(false);
if (!result.IsSuccessStatusCode)
{
var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
_options.Logger.Warn($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. HttpStatusCode: {result.StatusCode} Content: {content}");
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -37,7 +37,19 @@ internal class ProxyHelper
var requiredUri = new Uri(url); var requiredUri = new Uri(url);
// Create HttpRequestMessage // 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 // Call the URL
var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false); var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false);
@@ -45,8 +57,16 @@ internal class ProxyHelper
// Create ResponseMessage // Create ResponseMessage
bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false); bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false);
bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.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; IMapping? newMapping = null;
@@ -56,11 +76,11 @@ internal class ProxyHelper
if (saveMappingSettings != null) if (saveMappingSettings != null)
{ {
save &= Check(saveMappingSettings.StatusCodePattern, save &= Check(saveMappingSettings.StatusCodePattern,
() => HttpStatusRangeParser.IsMatch(saveMappingSettings.StatusCodePattern, responseMessage.StatusCode) () => saveMappingSettings.StatusCodePattern != null && HttpStatusRangeParser.IsMatch(saveMappingSettings.StatusCodePattern, responseMessage.StatusCode)
); );
save &= Check(saveMappingSettings.HttpMethods, save &= Check(saveMappingSettings.HttpMethods,
() => saveMappingSettings.HttpMethods.Value.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase) () => saveMappingSettings.HttpMethods != null && saveMappingSettings.HttpMethods.Value.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase)
); );
} }

View File

@@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using JsonConverter.Abstractions;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Util; using WireMock.Util;
@@ -45,35 +47,60 @@ public interface IBodyRequestBuilder : IRequestMatcher
/// WithBody: Body as object /// WithBody: Body as object
/// </summary> /// </summary>
/// <param name="body">The body.</param> /// <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> /// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); 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> /// <summary>
/// WithBody: func (string) /// WithBody: func (string)
/// </summary> /// </summary>
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns> /// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<string, bool> func); IRequestBuilder WithBody(Func<string?, bool> func);
/// <summary> /// <summary>
/// WithBody: func (byte[]) /// WithBody: func (byte[])
/// </summary> /// </summary>
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns> /// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<byte[], bool> func); IRequestBuilder WithBody(Func<byte[]?, bool> func);
/// <summary> /// <summary>
/// WithBody: func (json object) /// WithBody: func (json object)
/// </summary> /// </summary>
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns> /// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<object, bool> func); IRequestBuilder WithBody(Func<object?, bool> func);
/// <summary> /// <summary>
/// WithBody: func (BodyData object) /// WithBody: func (BodyData object)
/// </summary> /// </summary>
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns> /// <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> /// <summary>
/// IRequestBuilder /// IRequestBuilder
/// </summary> /// </summary>
public interface IRequestBuilder : IClientIPRequestBuilder 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. // 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. // For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root.
using System; using System;
using System.Collections.Generic;
using JsonConverter.Abstractions;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Util; using WireMock.Util;
using Stef.Validation;
namespace WireMock.RequestBuilders; namespace WireMock.RequestBuilders;
public partial class Request public partial class Request
{ {
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(string, MatchBehaviour)"/> /// <inheritdoc />
public IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
return this; return this;
} }
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(byte[], MatchBehaviour)"/> /// <inheritdoc />
public IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
return this; return this;
} }
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(object, MatchBehaviour)"/> /// <inheritdoc />
public IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
_requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body));
return this; 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 /> /// <inheritdoc />
public IRequestBuilder WithBody(IMatcher matcher) public IRequestBuilder WithBody(IMatcher matcher)
{ {
@@ -46,39 +67,46 @@ public partial class Request
return this; return this;
} }
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{string, bool})"/> /// <inheritdoc />
public IRequestBuilder WithBody(Func<string, bool> func) public IRequestBuilder WithBody(Func<string?, bool> func)
{ {
Guard.NotNull(func, nameof(func)); Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func)); _requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this; return this;
} }
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{byte[], bool})"/> /// <inheritdoc />
public IRequestBuilder WithBody(Func<byte[], bool> func) public IRequestBuilder WithBody(Func<byte[]?, bool> func)
{ {
Guard.NotNull(func, nameof(func)); Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func)); _requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this; return this;
} }
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{object, bool})"/> /// <inheritdoc />
public IRequestBuilder WithBody(Func<object, bool> func) public IRequestBuilder WithBody(Func<object?, bool> func)
{ {
Guard.NotNull(func, nameof(func)); Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func)); _requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this; return this;
} }
/// <inheritdoc cref="IBodyRequestBuilder.WithBody(Func{IBodyData, bool})"/> /// <inheritdoc />
public IRequestBuilder WithBody(Func<IBodyData, bool> func) public IRequestBuilder WithBody(Func<IBodyData?, bool> func)
{ {
Guard.NotNull(func, nameof(func)); Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher(func)); _requestMatchers.Add(new RequestMessageBodyMatcher(func));
return this; return this;
} }
/// <inheritdoc />
public IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
}
} }

View File

@@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using Stef.Validation; using Stef.Validation;
namespace WireMock.RequestBuilders namespace WireMock.RequestBuilders;
{
public partial class Request public partial class Request
{ {
/// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, MatchBehaviour)"/> /// <inheritdoc cref="ICookiesRequestBuilder.WithCookie(string, string, MatchBehaviour)"/>
@@ -79,4 +79,3 @@ namespace WireMock.RequestBuilders
return this; return this;
} }
} }
}

View File

@@ -7,8 +7,8 @@ using WireMock.Matchers.Request;
using WireMock.Types; using WireMock.Types;
using Stef.Validation; using Stef.Validation;
namespace WireMock.RequestBuilders namespace WireMock.RequestBuilders;
{
public partial class Request public partial class Request
{ {
/// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour)"/> /// <inheritdoc cref="IParamsRequestBuilder.WithParam(string, MatchBehaviour)"/>
@@ -89,4 +89,3 @@ namespace WireMock.RequestBuilders
return this; return this;
} }
} }
}

View File

@@ -3,8 +3,8 @@ using Stef.Validation;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
namespace WireMock.RequestBuilders namespace WireMock.RequestBuilders;
{
public partial class Request public partial class Request
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -46,4 +46,3 @@ namespace WireMock.RequestBuilders
return this; return this;
} }
} }
}

View File

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

View File

@@ -180,12 +180,7 @@ public class RequestMessage : IRequestMessage
#endif #endif
} }
/// <summary> /// <inheritdoc />
/// 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>
public WireMockList<string>? GetParameter(string key, bool ignoreCase = false) public WireMockList<string>? GetParameter(string key, bool ignoreCase = false)
{ {
if (Query == null) if (Query == null)

View File

@@ -92,7 +92,7 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// </summary> /// </summary>
/// <param name="body">The body.</param> /// <param name="body">The body.</param>
/// <param name="converter">The JsonConverter.</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> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(object body, IJsonConverter converter, JsonConverterOptions? options = null); IResponseBuilder WithBody(object body, IJsonConverter converter, JsonConverterOptions? options = null);
@@ -102,7 +102,7 @@ public interface IBodyResponseBuilder : IFaultResponseBuilder
/// <param name="body">The body.</param> /// <param name="body">The body.</param>
/// <param name="encoding">The body encoding, can be <c>null</c>.</param> /// <param name="encoding">The body encoding, can be <c>null</c>.</param>
/// <param name="converter">The JsonConverter.</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> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter converter, JsonConverterOptions? options = null); IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter converter, JsonConverterOptions? options = null);
} }

View File

@@ -27,7 +27,7 @@ public interface IProxyResponseBuilder : IStatusCodeResponseBuilder
/// WithProxy using <see cref="X509Certificate2"/>. /// WithProxy using <see cref="X509Certificate2"/>.
/// </summary> /// </summary>
/// <param name="proxyUrl">The proxy url.</param> /// <param name="proxyUrl">The proxy url.</param>
/// <param name="certificate"">The X509Certificate2.</param> /// <param name="certificate">The X509Certificate2.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns> /// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy(string proxyUrl, X509Certificate2 certificate); IResponseBuilder WithProxy(string proxyUrl, X509Certificate2 certificate);
} }

View File

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

View File

@@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Newtonsoft.Json;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Constants; using WireMock.Constants;
@@ -14,7 +16,9 @@ using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.Settings; using WireMock.Settings;
using WireMock.Types; using WireMock.Types;
using WireMock.Util;
using static WireMock.Util.CSharpFormatter;
namespace WireMock.Serialization; namespace WireMock.Serialization;
internal class MappingConverter internal class MappingConverter
@@ -103,11 +107,28 @@ internal class MappingConverter
if (bodyMatcher is { Matchers: { } }) if (bodyMatcher is { Matchers: { } })
{ {
var wildcardMatcher = bodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault(); if (bodyMatcher.Matchers.OfType<WildcardMatcher>().FirstOrDefault() is { } wildcardMatcher && wildcardMatcher.GetPatterns().Any())
if (wildcardMatcher is { } && wildcardMatcher.GetPatterns().Any())
{ {
sb.AppendLine($" .WithBody({GetString(wildcardMatcher)})"); 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(@" )"); sb.AppendLine(@" )");
@@ -115,23 +136,50 @@ internal class MappingConverter
// Guid // Guid
sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")"); sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")");
if (mapping.Probability != null)
{
sb.AppendLine($" .WithProbability({mapping.Probability.Value.ToString(CultureInfoUtils.CultureInfoEnUS)})");
}
// Response // Response
sb.AppendLine(" .RespondWith(Response.Create()"); 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 { }) if (response.ResponseMessage.Headers is { })
{ {
foreach (var header in response.ResponseMessage.Headers) 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) switch (response.ResponseMessage.BodyData.DetectedBodyType)
{ {
case BodyType.String: 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; break;
} }
} }
@@ -140,7 +188,7 @@ internal class MappingConverter
{ {
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})"); 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})"); sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
} }
@@ -182,6 +230,7 @@ internal class MappingConverter
WhenStateIs = mapping.ExecutionConditionState, WhenStateIs = mapping.ExecutionConditionState,
SetStateTo = mapping.NextState, SetStateTo = mapping.NextState,
Data = mapping.Data, Data = mapping.Data,
Probability = mapping.Probability,
Request = new RequestModel Request = new RequestModel
{ {
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
@@ -325,6 +374,7 @@ internal class MappingConverter
switch (response.ResponseMessage.BodyData?.DetectedBodyType) switch (response.ResponseMessage.BodyData?.DetectedBodyType)
{ {
case BodyType.String: case BodyType.String:
case BodyType.FormUrlEncoded:
mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString; mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString;
break; break;
@@ -372,7 +422,7 @@ internal class MappingConverter
private static string GetString(IStringMatcher stringMatcher) 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) private static string[] GetStringArray(IReadOnlyList<IStringMatcher> stringMatchers)
@@ -425,7 +475,7 @@ internal class MappingConverter
private static string ToValueArguments(string[]? values, string defaultValue = "") 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) private static WebProxyModel? MapWebProxy(WebProxySettings? settings)
@@ -456,4 +506,6 @@ internal class MappingConverter
return newDictionary; return newDictionary;
} }
} }

View File

@@ -31,6 +31,7 @@ internal class ProxyMappingConverter
var useDefinedRequestMatchers = proxyAndRecordSettings.UseDefinedRequestMatchers; var useDefinedRequestMatchers = proxyAndRecordSettings.UseDefinedRequestMatchers;
var excludedHeaders = new List<string>(proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }) { "Cookie" }; var excludedHeaders = new List<string>(proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }) { "Cookie" };
var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[0]; var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? new string[0];
var excludedParams = proxyAndRecordSettings.ExcludedParams ?? new string[0];
var request = (Request?)mapping?.RequestMatcher; var request = (Request?)mapping?.RequestMatcher;
var clientIPMatcher = request?.GetRequestMessageMatcher<RequestMessageClientIPMatcher>(); var clientIPMatcher = request?.GetRequestMessageMatcher<RequestMessageClientIPMatcher>();
@@ -73,13 +74,22 @@ internal class ProxyMappingConverter
if (useDefinedRequestMatchers && paramMatchers is not null) if (useDefinedRequestMatchers && paramMatchers is not null)
{ {
foreach (var paramMatcher in paramMatchers) foreach (var paramMatcher in paramMatchers)
{
if (!excludedParams.Contains(paramMatcher.Key, StringComparer.OrdinalIgnoreCase))
{ {
newRequest.WithParam(paramMatcher.Key, paramMatcher.MatchBehaviour, paramMatcher.Matchers!.ToArray()); newRequest.WithParam(paramMatcher.Key, paramMatcher.MatchBehaviour, paramMatcher.Matchers!.ToArray());
} }
} }
}
else 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 // Cookies
@@ -141,6 +151,7 @@ internal class ProxyMappingConverter
break; break;
case BodyType.String: case BodyType.String:
case BodyType.FormUrlEncoded:
newRequest.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, true, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString!)); newRequest.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, true, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString!));
break; break;
@@ -178,7 +189,8 @@ internal class ProxyMappingConverter
webhooks: null, webhooks: null,
useWebhooksFireAndForget: null, useWebhooksFireAndForget: null,
timeSettings: null, timeSettings: null,
data: mapping?.Data data: mapping?.Data,
probability: null
); );
} }
} }

View File

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

View File

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

View File

@@ -175,5 +175,13 @@ public interface IRespondWithAProvider
/// lookup data "1" /// lookup data "1"
/// </example> /// </example>
/// </summary> /// </summary>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithData(object data); 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

@@ -18,6 +18,12 @@ namespace WireMock.Server;
/// </summary> /// </summary>
internal class RespondWithAProvider : IRespondWithAProvider 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 int _priority;
private string? _title; private string? _title;
private string? _description; private string? _description;
@@ -26,13 +32,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
private string? _nextState; private string? _nextState;
private string? _scenario; private string? _scenario;
private int _timesInSameState = 1; 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 bool? _useWebhookFireAndForget;
private double? _probability;
public Guid Guid { get; private set; } public Guid Guid { get; private set; }
@@ -92,7 +93,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
Webhooks, Webhooks,
_useWebhookFireAndForget, _useWebhookFireAndForget,
TimeSettings, TimeSettings,
Data); Data,
_probability);
_registrationCallback(mapping, _saveToFile); _registrationCallback(mapping, _saveToFile);
} }
@@ -285,6 +287,13 @@ internal class RespondWithAProvider : IRespondWithAProvider
return this; return this;
} }
public IRespondWithAProvider WithProbability(double probability)
{
_probability = Guard.Condition(probability, p => p is >= 0 and <= 1.0);
return this;
}
private static IWebhook InitWebhook( private static IWebhook InitWebhook(
string url, string url,
string method, string method,

View File

@@ -37,6 +37,8 @@ public partial class WireMockServer
private const string AdminRequests = "/__admin/requests"; private const string AdminRequests = "/__admin/requests";
private const string AdminSettings = "/__admin/settings"; private const string AdminSettings = "/__admin/settings";
private const string AdminScenarios = "/__admin/scenarios"; private const string AdminScenarios = "/__admin/scenarios";
private const string AdminOpenApi = "/__admin/openapi";
private const string QueryParamReloadStaticMappings = "reloadStaticMappings"; private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567"); 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).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).UsingHead()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileHead));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete)); 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 #endregion
@@ -217,10 +223,17 @@ public partial class WireMockServer
var model = new SettingsModel var model = new SettingsModel
{ {
AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods, AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods,
AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse,
AllowPartialMapping = _settings.AllowPartialMapping, AllowPartialMapping = _settings.AllowPartialMapping,
DisableDeserializeFormUrlEncoded = _settings.DisableDeserializeFormUrlEncoded,
DisableJsonBodyParsing = _settings.DisableJsonBodyParsing,
DisableRequestBodyDecompressing = _settings.DisableRequestBodyDecompressing,
DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry,
GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds, GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds,
HandleRequestsSynchronously = _settings.HandleRequestsSynchronously, HandleRequestsSynchronously = _settings.HandleRequestsSynchronously,
HostingScheme = _settings.HostingScheme,
MaxRequestLogCount = _settings.MaxRequestLogCount, MaxRequestLogCount = _settings.MaxRequestLogCount,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
ReadStaticMappings = _settings.ReadStaticMappings, ReadStaticMappings = _settings.ReadStaticMappings,
RequestLogExpirationDuration = _settings.RequestLogExpirationDuration, RequestLogExpirationDuration = _settings.RequestLogExpirationDuration,
SaveUnmatchedRequests = _settings.SaveUnmatchedRequests, SaveUnmatchedRequests = _settings.SaveUnmatchedRequests,
@@ -228,14 +241,11 @@ public partial class WireMockServer
UseRegexExtended = _settings.UseRegexExtended, UseRegexExtended = _settings.UseRegexExtended,
WatchStaticMappings = _settings.WatchStaticMappings, WatchStaticMappings = _settings.WatchStaticMappings,
WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories, WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories,
HostingScheme = _settings.HostingScheme,
DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
#if USE_ASPNETCORE #if USE_ASPNETCORE
CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString(), AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate,
ClientCertificateMode = _settings.ClientCertificateMode, ClientCertificateMode = _settings.ClientCertificateMode,
AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString()
#endif #endif
}; };
@@ -250,10 +260,16 @@ public partial class WireMockServer
// _settings // _settings
_settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods; _settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods;
_settings.AllowOnlyDefinedHttpStatusCodeInResponse = settings.AllowOnlyDefinedHttpStatusCodeInResponse;
_settings.AllowPartialMapping = settings.AllowPartialMapping; _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.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_settings.MaxRequestLogCount = settings.MaxRequestLogCount; _settings.MaxRequestLogCount = settings.MaxRequestLogCount;
_settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings); _settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings);
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
_settings.ReadStaticMappings = settings.ReadStaticMappings; _settings.ReadStaticMappings = settings.ReadStaticMappings;
_settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; _settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
_settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; _settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
@@ -261,8 +277,6 @@ public partial class WireMockServer
_settings.UseRegexExtended = settings.UseRegexExtended; _settings.UseRegexExtended = settings.UseRegexExtended;
_settings.WatchStaticMappings = settings.WatchStaticMappings; _settings.WatchStaticMappings = settings.WatchStaticMappings;
_settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories; _settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories;
_settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
InitSettings(_settings); InitSettings(_settings);
@@ -729,7 +743,7 @@ public partial class WireMockServer
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; 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 return new ResponseMessage
{ {
@@ -738,7 +752,7 @@ public partial class WireMockServer
DetectedBodyType = BodyType.String, DetectedBodyType = BodyType.String,
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault) 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) } } 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() 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) 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.DetectedBodyType = BodyType.String;
response.BodyData.BodyAsString = encoding.GetString(bytes); response.BodyData.BodyAsString = encoding.GetString(bytes);

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
@@ -14,7 +15,7 @@ namespace WireMock.Server;
public partial class WireMockServer public partial class WireMockServer
{ {
private void ConvertMappingsAndRegisterAsRespondProvider(MappingModel[] mappingModels, string? path = null) private void ConvertMappingsAndRegisterAsRespondProvider(IReadOnlyList<MappingModel> mappingModels, string? path = null)
{ {
var duplicateGuids = mappingModels var duplicateGuids = mappingModels
.Where(m => m.Guid != null) .Where(m => m.Guid != null)
@@ -107,7 +108,15 @@ public partial class WireMockServer
respondProvider = respondProvider.WithWebhook(webhooks); 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); var responseBuilder = InitResponseBuilder(mappingModel.Response);
respondProvider.RespondWith(responseBuilder); respondProvider.RespondWith(responseBuilder);

View File

@@ -0,0 +1,74 @@
#if OPENAPIPARSER
using System;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
using WireMock.Net.OpenApiParser;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types;
using WireMock.Serialization;
#endif
namespace WireMock.Server;
public partial class WireMockServer
{
#if OPENAPIPARSER
private readonly WireMockOpenApiParserSettings _openApiParserSettings = new WireMockOpenApiParserSettings
{
PathPatternToUse = ExampleValueType.Regex,
HeaderPatternToUse = ExampleValueType.Regex,
QueryParameterPatternToUse = ExampleValueType.Regex
};
#endif
private IResponseMessage OpenApiConvertToMappings(IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body, out var diagnostic);
if (diagnostic.Errors.Any())
{
var diagnosticAsJson = JsonConvert.SerializeObject(diagnostic, JsonSerializationConstants.JsonSerializerSettingsDefault);
_settings.Logger.Warn("OpenApiError(s) while converting OpenAPI specification to MappingModel(s) : {0}", diagnosticAsJson);
}
return 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())
{
var diagnosticAsJson = JsonConvert.SerializeObject(diagnostic, JsonSerializationConstants.JsonSerializerSettingsDefault);
_settings.Logger.Warn("OpenApiError(s) while converting OpenAPI specification to MappingModel(s) : {0}", diagnosticAsJson);
}
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
}
}

View File

@@ -43,10 +43,14 @@ public partial class WireMockServer : IWireMockServer
private readonly IGuidUtils _guidUtils = new GuidUtils(); private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils(); private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
/// <inheritdoc cref="IWireMockServer.IsStarted" /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public bool IsStarted => _httpServer is { IsStarted: true }; public bool IsStarted => _httpServer is { IsStarted: true };
/// <inheritdoc />
[PublicAPI]
public bool IsStartedWithAdminInterface => IsStarted && _settings.StartAdminInterface.GetValueOrDefault();
/// <inheritdoc /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public List<int> Ports { get; } public List<int> Ports { get; }

View File

@@ -0,0 +1,6 @@
namespace WireMock.Services;
internal interface IRandomizerDoubleBetween0And1
{
double Generate();
}

View File

@@ -0,0 +1,14 @@
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
namespace WireMock.Services;
internal class RandomizerDoubleBetween0And1 : IRandomizerDoubleBetween0And1
{
private readonly IRandomizerNumber<double> _randomizerDoubleBetween0And1 = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 });
public double Generate()
{
return _randomizerDoubleBetween0And1.Generate() ?? 0;
}
}

View File

@@ -57,12 +57,24 @@ public class ProxyAndRecordSettings : HttpClientSettings
[PublicAPI] [PublicAPI]
public string[]? ExcludedHeaders { get; set; } public string[]? ExcludedHeaders { get; set; }
/// <summary>
/// Defines a list of params which will be excluded from the saved mappings.
/// </summary>
[PublicAPI]
public string[]? ExcludedParams { get; set; }
/// <summary> /// <summary>
/// Defines a list of cookies which will be excluded from the saved mappings. /// Defines a list of cookies which will be excluded from the saved mappings.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public string[]? ExcludedCookies { get; set; } public string[]? ExcludedCookies { get; set; }
/// <summary>
/// Replace Settings
/// </summary>
[PublicAPI]
public ProxyUrlReplaceSettings? ReplaceSettings { get; set; }
/// <summary> /// <summary>
/// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>). /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to <c>true</c>).
/// </summary> /// </summary>

View File

@@ -6,7 +6,7 @@ public class ProxySaveMappingSetting<T>
{ {
public MatchBehaviour MatchBehaviour { get; } = MatchBehaviour.AcceptOnMatch; public MatchBehaviour MatchBehaviour { get; } = MatchBehaviour.AcceptOnMatch;
public T Value { get; private set; } public T Value { get; }
public ProxySaveMappingSetting(T value, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) public ProxySaveMappingSetting(T value, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{ {
@@ -14,7 +14,7 @@ public class ProxySaveMappingSetting<T>
MatchBehaviour = matchBehaviour; MatchBehaviour = matchBehaviour;
} }
public static implicit operator ProxySaveMappingSetting<T>(T value) => new ProxySaveMappingSetting<T>(value); public static implicit operator ProxySaveMappingSetting<T>(T value) => new(value);
public static implicit operator T(ProxySaveMappingSetting<T> @this) => @this.Value; public static implicit operator T(ProxySaveMappingSetting<T> @this) => @this.Value;
} }

View File

@@ -0,0 +1,22 @@
namespace WireMock.Settings;
/// <summary>
/// Defines an old path param and a new path param to be replaced when proxying.
/// </summary>
public class ProxyUrlReplaceSettings
{
/// <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

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

View File

@@ -182,13 +182,13 @@ public class WireMockServerSettings
public bool? AllowCSharpCodeMatcher { get; set; } public bool? AllowCSharpCodeMatcher { get; set; }
/// <summary> /// <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> /// </summary>
[PublicAPI] [PublicAPI]
public bool? AllowBodyForAllHttpMethods { get; set; } public bool? AllowBodyForAllHttpMethods { get; set; }
/// <summary> /// <summary>
/// Allow only a HttpStatus Code in the response which is defined. (default set to false). /// Allow only a HttpStatus Code in the response which is defined. (default set to <c>false</c>).
/// - false : also null, 0, empty or invalid HttpStatus codes are allowed. /// - false : also null, 0, empty or invalid HttpStatus codes are allowed.
/// - true : only codes defined in <see cref="System.Net.HttpStatusCode"/> are allowed. /// - true : only codes defined in <see cref="System.Net.HttpStatusCode"/> are allowed.
/// </summary> /// </summary>
@@ -196,25 +196,31 @@ public class WireMockServerSettings
public bool? AllowOnlyDefinedHttpStatusCodeInResponse { get; set; } public bool? AllowOnlyDefinedHttpStatusCodeInResponse { get; set; }
/// <summary> /// <summary>
/// Set to true to disable Json deserialization when processing requests. (default set to false). /// Set to true to disable Json deserialization when processing requests. (default set to <c>false</c>).
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public bool? DisableJsonBodyParsing { get; set; } public bool? DisableJsonBodyParsing { get; set; }
/// <summary> /// <summary>
/// Disable support for GZip and Deflate request body decompression. (default set to false). /// Disable support for GZip and Deflate request body decompression. (default set to <c>false</c>).
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public bool? DisableRequestBodyDecompressing { get; set; } public bool? DisableRequestBodyDecompressing { get; set; }
/// <summary> /// <summary>
/// Handle all requests synchronously. (default set to false). /// Set to true to disable FormUrlEncoded deserializing when processing requests. (default set to <c>false</c>).
/// </summary>
[PublicAPI]
public bool? DisableDeserializeFormUrlEncoded { get; set; }
/// <summary>
/// Handle all requests synchronously. (default set to <c>false</c>).
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public bool? HandleRequestsSynchronously { get; set; } public bool? HandleRequestsSynchronously { get; set; }
/// <summary> /// <summary>
/// Throw an exception when the <see cref="IMatcher"/> fails because of invalid input. (default set to false). /// Throw an exception when the <see cref="IMatcher"/> fails because of invalid input. (default set to <c>false</c>).
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public bool? ThrowExceptionWhenMatcherFails { get; set; } public bool? ThrowExceptionWhenMatcherFails { get; set; }
@@ -255,19 +261,19 @@ public class WireMockServerSettings
public WebhookSettings? WebhookSettings { get; set; } public WebhookSettings? WebhookSettings { get; set; }
/// <summary> /// <summary>
/// Use the <see cref="RegexExtended"/> instead of the default <see cref="Regex"/> (default set to true). /// Use the <see cref="RegexExtended"/> instead of the default <see cref="Regex"/> (default set to <c>true</c>).
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public bool? UseRegexExtended { get; set; } = true; public bool? UseRegexExtended { get; set; } = true;
/// <summary> /// <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> /// </summary>
[PublicAPI] [PublicAPI]
public bool? SaveUnmatchedRequests { get; set; } public bool? SaveUnmatchedRequests { get; set; }
/// <summary> /// <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> /// </summary>
[PublicAPI] [PublicAPI]
public bool? DoNotSaveDynamicResponseInLogEntry { get; set; } public bool? DoNotSaveDynamicResponseInLogEntry { get; set; }

View File

@@ -4,7 +4,6 @@ using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
@@ -22,7 +21,8 @@ public static class WireMockServerSettingsParser
/// <param name="logger">The logger (optional, can be null)</param> /// <param name="logger">The logger (optional, can be null)</param>
/// <param name="settings">The parsed settings</param> /// <param name="settings">The parsed settings</param>
[PublicAPI] [PublicAPI]
public static bool TryParseArguments(string[] args, [NotNullWhen(true)] out WireMockServerSettings? settings, IWireMockLogger? logger = null) public static bool TryParseArguments(string[] args, [NotNullWhen(true)] out WireMockServerSettings? settings,
IWireMockLogger? logger = null)
{ {
Guard.HasNoNulls(args); Guard.HasNoNulls(args);
@@ -47,6 +47,8 @@ public static class WireMockServerSettingsParser
AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"), AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"),
AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"), AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"),
DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"), DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"),
DisableRequestBodyDecompressing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableRequestBodyDecompressing)),
DisableDeserializeFormUrlEncoded = parser.GetBoolValue(nameof(WireMockServerSettings.DisableDeserializeFormUrlEncoded)),
HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"), HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"),
MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"), MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"),
ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"), ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"),
@@ -69,6 +71,17 @@ public static class WireMockServerSettingsParser
settings.AcceptAnyClientCertificate = parser.GetBoolValue(nameof(WireMockServerSettings.AcceptAnyClientCertificate)); settings.AcceptAnyClientCertificate = parser.GetBoolValue(nameof(WireMockServerSettings.AcceptAnyClientCertificate));
#endif #endif
ParseLoggerSettings(settings, logger, parser);
ParsePortSettings(settings, parser);
ParseProxyAndRecordSettings(settings, parser);
ParseCertificateSettings(settings, parser);
return true;
}
private static void ParseLoggerSettings(WireMockServerSettings settings, IWireMockLogger? logger,
SimpleCommandLineParser parser)
{
var loggerType = parser.GetStringValue("WireMockLogger"); var loggerType = parser.GetStringValue("WireMockLogger");
switch (loggerType) switch (loggerType)
{ {
@@ -85,22 +98,17 @@ public static class WireMockServerSettingsParser
{ {
settings.Logger = logger; settings.Logger = logger;
} }
break; break;
} }
if (parser.Contains(nameof(WireMockServerSettings.Port)))
{
settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port));
}
else if (settings.HostingScheme is null)
{
settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" });
} }
private static void ParseProxyAndRecordSettings(WireMockServerSettings settings, SimpleCommandLineParser parser)
{
var proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl"); var proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl");
if (!string.IsNullOrEmpty(proxyUrl)) if (!string.IsNullOrEmpty(proxyUrl))
{ {
settings.ProxyAndRecordSettings = new ProxyAndRecordSettings var proxyAndRecordSettings = new ProxyAndRecordSettings
{ {
AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect"), AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect"),
ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue("ClientX509Certificate2ThumbprintOrSubjectName"), ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue("ClientX509Certificate2ThumbprintOrSubjectName"),
@@ -120,18 +128,27 @@ public static class WireMockServerSettingsParser
} }
}; };
string? proxyAddress = parser.GetStringValue("WebProxyAddress"); ParseWebProxyAddressSettings(proxyAndRecordSettings, parser);
if (!string.IsNullOrEmpty(proxyAddress)) ParseProxyUrlReplaceSettings(proxyAndRecordSettings, parser);
{
settings.ProxyAndRecordSettings.WebProxySettings = new WebProxySettings settings.ProxyAndRecordSettings = proxyAndRecordSettings;
{
Address = proxyAddress!,
UserName = parser.GetStringValue("WebProxyUserName"),
Password = parser.GetStringValue("WebProxyPassword")
};
} }
} }
private static void ParsePortSettings(WireMockServerSettings settings, SimpleCommandLineParser parser)
{
if (parser.Contains(nameof(WireMockServerSettings.Port)))
{
settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port));
}
else if (settings.HostingScheme is null)
{
settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" });
}
}
private static void ParseCertificateSettings(WireMockServerSettings settings, SimpleCommandLineParser parser)
{
var certificateSettings = new WireMockCertificateSettings var certificateSettings = new WireMockCertificateSettings
{ {
X509StoreName = parser.GetStringValue("X509StoreName"), X509StoreName = parser.GetStringValue("X509StoreName"),
@@ -144,7 +161,33 @@ public static class WireMockServerSettingsParser
{ {
settings.CertificateSettings = certificateSettings; settings.CertificateSettings = certificateSettings;
} }
}
return true; private static void ParseWebProxyAddressSettings(ProxyAndRecordSettings settings, SimpleCommandLineParser parser)
{
string? proxyAddress = parser.GetStringValue("WebProxyAddress");
if (!string.IsNullOrEmpty(proxyAddress))
{
settings.WebProxySettings = new WebProxySettings
{
Address = proxyAddress!,
UserName = parser.GetStringValue("WebProxyUserName"),
Password = parser.GetStringValue("WebProxyPassword")
};
}
}
private static void ParseProxyUrlReplaceSettings(ProxyAndRecordSettings settings, SimpleCommandLineParser parser)
{
var proxyUrlReplaceOldValue = parser.GetStringValue("ProxyUrlReplaceOldValue");
var proxyUrlReplaceNewValue = parser.GetStringValue("ProxyUrlReplaceNewValue");
if (!string.IsNullOrEmpty(proxyUrlReplaceOldValue) && proxyUrlReplaceNewValue != null)
{
settings.ReplaceSettings = new ProxyUrlReplaceSettings
{
OldValue = proxyUrlReplaceOldValue!,
NewValue = proxyUrlReplaceNewValue!
};
}
} }
} }

View File

@@ -85,7 +85,7 @@ internal class Transformer : ITransformer
{ {
responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile); responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile);
if (original.BodyData.DetectedBodyType == BodyType.String) if (original.BodyData.DetectedBodyType is BodyType.String or BodyType.FormUrlEncoded)
{ {
responseMessage.BodyOriginal = original.BodyData.BodyAsString; responseMessage.BodyOriginal = original.BodyData.BodyAsString;
} }
@@ -123,13 +123,21 @@ internal class Transformer : ITransformer
private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile) private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
{ {
return original.DetectedBodyType switch switch (original.DetectedBodyType)
{ {
BodyType.Json => TransformBodyAsJson(transformerContext, options, model, original), case BodyType.Json:
BodyType.File => TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile), return TransformBodyAsJson(transformerContext, options, model, original);
BodyType.String => TransformBodyAsString(transformerContext, model, original),
_ => null case BodyType.File:
}; return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
case BodyType.String:
case BodyType.FormUrlEncoded:
return TransformBodyAsString(transformerContext, model, original);
default:
return null;
}
} }
private static IDictionary<string, WireMockList<string>> TransformHeaders(ITransformerContext transformerContext, TransformModel model, IDictionary<string, WireMockList<string>>? original) private static IDictionary<string, WireMockList<string>> TransformHeaders(ITransformerContext transformerContext, TransformModel model, IDictionary<string, WireMockList<string>>? original)

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ namespace WireMock.Util;
/// http://www.unicode.org/versions/corrigendum1.html /// http://www.unicode.org/versions/corrigendum1.html
/// http://www.ietf.org/rfc/rfc2279.txt /// http://www.ietf.org/rfc/rfc2279.txt
/// </summary> /// </summary>
public static class BytesEncodingUtils internal static class BytesEncodingUtils
{ {
/// <summary> /// <summary>
/// Tries the get the Encoding from an array of bytes. /// Tries the get the Encoding from an array of bytes.
@@ -108,7 +108,7 @@ public static class BytesEncodingUtils
return true; return true;
} }
if (ch >= 0xc2 && ch <= 0xdf) if (ch is >= 0xc2 and <= 0xdf)
{ {
if (position >= length - 2) if (position >= length - 2)
{ {
@@ -145,7 +145,7 @@ public static class BytesEncodingUtils
return true; return true;
} }
if (ch >= 0xe1 && ch <= 0xef) if (ch is >= 0xe1 and <= 0xef)
{ {
if (position >= length - 3) if (position >= length - 3)
{ {
@@ -204,7 +204,7 @@ public static class BytesEncodingUtils
return true; return true;
} }
if (ch >= 0xf1 && ch <= 0xf3) if (ch is >= 0xf1 and <= 0xf3)
{ {
if (position >= length - 4) if (position >= length - 4)
{ {

View File

@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace WireMock.Util;
internal static class CSharpFormatter
{
#region Reserved Keywords
private static readonly HashSet<string> CSharpReservedKeywords = new(new[]
{
"abstract",
"as",
"base",
"bool",
"break",
"byte",
"case",
"catch",
"char",
"checked",
"class",
"const",
"continue",
"decimal",
"default",
"delegate",
"do",
"double",
"else",
"enum",
"event",
"explicit",
"extern",
"false",
"finally",
"fixed",
"float",
"for",
"foreach",
"goto",
"if",
"implicit",
"in",
"int",
"interface",
"internal",
"is",
"lock",
"long",
"namespace",
"new",
"null",
"object",
"operator",
"out",
"override",
"params",
"private",
"protected",
"public",
"readonly",
"ref",
"return",
"sbyte",
"sealed",
"short",
"sizeof",
"stackalloc",
"static",
"string",
"struct",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"uint",
"ulong",
"unchecked",
"unsafe",
"ushort",
"using",
"virtual",
"void",
"volatile",
"while"
});
#endregion
private const string Null = "null";
public static object ConvertToAnonymousObjectDefinition(object jsonBody)
{
var serializedBody = JsonConvert.SerializeObject(jsonBody);
using var jsonReader = new JsonTextReader(new StringReader(serializedBody));
jsonReader.DateParseHandling = DateParseHandling.None;
var deserializedBody = JObject.Load(jsonReader);
return ConvertJsonToAnonymousObjectDefinition(deserializedBody, 2);
}
public static string ConvertJsonToAnonymousObjectDefinition(JToken token, int ind = 0)
{
return token switch
{
JArray jArray => FormatArray(jArray, ind),
JObject jObject => FormatObject(jObject, ind),
JProperty jProperty => $"{FormatPropertyName(jProperty.Name)} = {ConvertJsonToAnonymousObjectDefinition(jProperty.Value, ind)}",
JValue jValue => jValue.Type switch
{
JTokenType.None => Null,
JTokenType.Integer => jValue.Value != null ? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value) : Null,
JTokenType.Float => jValue.Value != null ? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value) : Null,
JTokenType.String => ToCSharpStringLiteral(jValue.Value?.ToString()),
JTokenType.Boolean => jValue.Value != null ? string.Format(CultureInfo.InvariantCulture, "{0}", jValue.Value).ToLower() : Null,
JTokenType.Null => Null,
JTokenType.Undefined => Null,
JTokenType.Date when jValue.Value is DateTime dateValue =>
$"DateTime.Parse({ToCSharpStringLiteral(dateValue.ToString("s"))})",
_ => $"UNHANDLED_CASE: {jValue.Type}"
},
_ => $"UNHANDLED_CASE: {token}"
};
}
public static string ToCSharpBooleanLiteral(bool value) => value ? "true" : "false";
public static string ToCSharpStringLiteral(string? value)
{
if (string.IsNullOrEmpty(value))
{
return "\"\"";
}
if (value.Contains("\n"))
{
var escapedValue = value?.Replace("\"", "\"\"") ?? string.Empty;
return $"@\"{escapedValue}\"";
}
else
{
var escapedValue = value?.Replace("\"", "\\\"") ?? string.Empty;
return $"\"{escapedValue}\"";
}
}
public static string FormatPropertyName(string propertyName)
{
return CSharpReservedKeywords.Contains(propertyName) ? "@" + propertyName : propertyName;
}
private static string FormatObject(JObject jObject, int ind)
{
var indStr = new string(' ', 4 * ind);
var indStrSub = new string(' ', 4 * (ind + 1));
var items = jObject.Properties().Select(x => ConvertJsonToAnonymousObjectDefinition(x, ind + 1));
return $"new\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}";
}
private static string FormatArray(JArray jArray, int ind)
{
var hasComplexItems = jArray.FirstOrDefault() is JObject or JArray;
var items = jArray.Select(x => ConvertJsonToAnonymousObjectDefinition(x, hasComplexItems ? ind + 1 : ind));
if (hasComplexItems)
{
var indStr = new string(' ', 4 * ind);
var indStrSub = new string(' ', 4 * (ind + 1));
return $"new []\r\n{indStr}{{\r\n{indStrSub}{string.Join($",\r\n{indStrSub}", items)}\r\n{indStr}}}";
}
return $"new [] {{ {string.Join(", ", items)} }}";
}
}

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
namespace WireMock.Util namespace WireMock.Util;
{
/// <summary> /// <summary>
/// A special Collection that overrides methods of <see cref="ObservableCollection{T}"/> to make them thread safe. /// A special Collection that overrides methods of <see cref="ObservableCollection{T}"/> to make them thread safe.
/// </summary> /// </summary>
@@ -83,4 +83,3 @@ namespace WireMock.Util
} }
} }
} }
}

View File

@@ -5,6 +5,8 @@ namespace WireMock.Util;
internal static class CultureInfoUtils internal static class CultureInfoUtils
{ {
public static readonly CultureInfo CultureInfoEnUS = new("en-US");
public static CultureInfo Parse(string? value) public static CultureInfo Parse(string? value)
{ {
if (value is null) if (value is null)

View File

@@ -4,8 +4,8 @@ using System.IO;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
namespace WireMock.Util namespace WireMock.Util;
{
/// <summary> /// <summary>
/// An EnhancedFileSystemWatcher, which can be used to suppress duplicate events that fire on a single change to the file. /// An EnhancedFileSystemWatcher, which can be used to suppress duplicate events that fire on a single change to the file.
/// </summary> /// </summary>
@@ -251,4 +251,3 @@ namespace WireMock.Util
#endregion #endregion
#endregion #endregion
} }
}

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