Compare commits

..

61 Commits

Author SHA1 Message Date
Stef Heyenrath 7753ac5d45 Merge branch 'master' into SystemTextJsonMatcher 2026-05-25 09:34:04 +02:00
Stef Heyenrath fed1c87663 2.7.0 2026-05-24 10:25:30 +02:00
Stef Heyenrath e2e83abeb5 Trusted Publishing (#1465)
* Trusted Publishing

* push:

* "

* pwsh

* pack

* push:

* Trusted Publishing = OK
2026-05-24 10:21:02 +02:00
Stef Heyenrath 33e033cbb4 -- tools 2026-05-24 09:17:06 +02:00
Stef Heyenrath 3fb11386a6 JsonConverter 0.12.0 2026-05-23 21:56:08 +02:00
Stef Heyenrath 9f2fa9d64a Merge branch 'master' into SystemTextJsonMatcher 2026-05-23 21:53:48 +02:00
Stef Heyenrath 3aef0ad7a2 Update Scriban.Signed to latest (#1462) 2026-05-23 21:53:32 +02:00
Stef Heyenrath 54e88028c3 mm 2026-05-23 21:49:45 +02:00
Stef Heyenrath e68a73c3d5 Remove MultipartUploader project 2026-05-23 21:44:33 +02:00
Stef Heyenrath bd83a630ff WireMock.Net.Service 2026-05-23 21:42:34 +02:00
Michi 52ac7e37dc Update Testcontainers to 4.12.0 (#1461) 2026-05-23 20:46:52 +02:00
Michaël 0eccf43a8e chore: update Handlebars 2.5.2 to 2.5.5 #1458 (#1459) 2026-05-23 20:26:36 +02:00
Stef Heyenrath 30bf81bbc4 Merge branch 'master' into SystemTextJsonMatcher 2026-05-12 13:23:43 +02:00
dependabot[bot] fa423370b1 Update OpenTelemetry.Api from 1.14.0 to 1.15.3 in unit test project (#1457)
* Bump OpenTelemetry.Api from 1.14.0 to 1.15.3

---
updated-dependencies:
- dependency-name: OpenTelemetry.Api
  dependency-version: 1.15.3
  dependency-type: direct:production
...

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

* chore: bump additional OpenTelemetry packages in UsingNuGet test project

Agent-Logs-Url: https://github.com/wiremock/WireMock.Net/sessions/bc8104f3-74b0-4a40-ac16-e404cbc230df

Co-authored-by: StefH <249938+StefH@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: StefH <249938+StefH@users.noreply.github.com>
2026-05-12 13:23:21 +02:00
Stef Heyenrath f27102ee53 Merge branch 'master' into SystemTextJsonMatcher 2026-05-11 10:33:50 +02:00
Stef Heyenrath 1806ae39f8 2.6.0 2026-05-11 10:31:09 +02:00
Peter Benko 67acdcf1d3 Fix request storing when RequestLogExpirationDuration is set [bug] (#1455)
* Fix response timestamp

* Extracted new interface to own file
2026-05-11 10:28:38 +02:00
Stef Heyenrath ce35cef1ea Merge branch 'master' into SystemTextJsonMatcher 2026-05-04 18:53:22 +02:00
Stef Heyenrath 4bb378bdce 2.5.0 2026-05-04 18:52:29 +02:00
Stef Heyenrath a86b8fbeb2 Potential fix for pull request finding 'Missing Dispose call on local IDisposable'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-05-03 11:52:47 +02:00
Stef Heyenrath 9bfae15f32 Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-05-03 11:33:07 +02:00
Stef Heyenrath 32d8ecd399 fix findings 2026-05-03 09:51:34 +02:00
Stef Heyenrath 811a2cd79e Merge branch 'master' into SystemTextJsonMatcher 2026-05-03 09:41:48 +02:00
Stef Heyenrath f9df0d0ee9 Fix name in RequestMessageHeaderMatcher 2026-05-03 09:41:25 +02:00
Stef Heyenrath 4b3c58351e Merge branch 'master' into SystemTextJsonMatcher 2026-05-03 09:28:26 +02:00
Степан f8d3b51fbc Feature/early mismatch (#1451)
* feat(request matchers): Add support for early mismatch in mapping processing

* test(request matchers): Add unit test for early mismatch functionality

* test(grpc): Add test for grpc requests early mismatch and error logging (Issue #1442)

* feat(request matchers): RequestMatcherType

Add `RequestMatcherType` to request matchers for improved type
identification

Closes #1442

* refactor(request matchers): Request

Replace `EarlyMatcherSelector` with `EarlyMatcherType` for improved
clarity and consistency

Closes #1442

* feat(request): conversion

Add EarlyMatcherType support in request models and mapping conversion

Closes #1442

* test(mapping): new tests

add unit tests for EarlyMatcherType in mapping conversion and
serialization

Closes #1442

* refactor(request matchers): RequestMessageEarlyMatcher

Replaced inline `EarlyMatcherType` logic with the new
`RequestMessageEarlyMatcher` class to support cases when several
matchers of the same type are present. For instance - Header, Cookie,
Param

Closes #1442

* test(request matchers): Early Mismatch

add unit tests for early mismatch scenarios with several matchers of
same type. Currently, headers and parameters

Closes #1442

* refactor(mapping): RequestModel.EarlyMatcherType

use fully qualified enum for EarlyMatcherType in serialization

Closes #1442

* style(review): fixes

- removed unused method
- added missing curly brackets

Closes #1442
2026-05-03 09:27:19 +02:00
Copilot be9864461d Fix CVE-2026-40021: upgrade log4net to 3.3.0 in examples/WireMock.Net.Service/packages.config (#1453)
* Initial plan

* Fix CVE-2026-40021: update log4net to 3.3.0 in packages.config

Agent-Logs-Url: https://github.com/wiremock/WireMock.Net/sessions/a09a4576-1b17-41fe-9912-c1efdf922fed

Co-authored-by: StefH <249938+StefH@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: StefH <249938+StefH@users.noreply.github.com>
2026-05-02 19:46:13 +02:00
dependabot[bot] 4bb7e8af45 Bump log4net from 2.0.15 to 3.3.0 (#1452)
---
updated-dependencies:
- dependency-name: log4net
  dependency-version: 3.3.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-02 18:30:43 +02:00
Stef Heyenrath 0677a6f525 s 2026-05-01 08:58:06 +02:00
Stef Heyenrath 9277315ef9 delete jsonutils.cs 2026-04-30 22:51:47 +02:00
Stef Heyenrath d0f9136570 . 2026-04-30 20:33:45 +02:00
Stef Heyenrath 1e07f3f7f3 0.11.0 2026-04-26 18:59:33 +02:00
Stef Heyenrath 64d3e4cbf3 fix tests 2026-04-26 09:09:40 +02:00
Stef Heyenrath 47b0bf594f . 2026-04-26 08:52:56 +02:00
Stef Heyenrath 31636e5e40 ... 2026-04-25 09:39:44 +02:00
Stef Heyenrath 32f42105b1 Merge branch 'master' into SystemTextJsonMatcher 2026-04-24 16:49:02 +02:00
Stef Heyenrath 8bf42904ab 2.4.0 2026-04-24 16:48:28 +02:00
dependabot[bot] 0a48b40021 Bump OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.14.0 to 1.15.x (#1450)
* Bump OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.14.0 to 1.15.3

---
updated-dependencies:
- dependency-name: OpenTelemetry.Exporter.OpenTelemetryProtocol
  dependency-version: 1.15.3
  dependency-type: direct:production
...

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

* Upgrade all OpenTelemetry related NuGet packages to latest versions

Agent-Logs-Url: https://github.com/wiremock/WireMock.Net/sessions/5b2f4449-ec3f-49f1-afbe-654e19a97a33

Co-authored-by: StefH <249938+StefH@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: StefH <249938+StefH@users.noreply.github.com>
2026-04-24 08:34:00 +02:00
Jayaraman Venkatesan 1962437dcd Added feature to enable and disable mappings (#1437)
* feat/1421 added feature to enable and disable mappings

* feat/1421 updated test constants to reflect 2 new admin endpoints /enable and /disable

* feat/1421 updated tests to fix flakyness - removed delay before assertion that is causing upstream connection from proxy to teardown prematurely before test ends

* feat/1421 addressing PR comments - Updated logic to represent IsDisable insted of IsEnabled
2026-04-24 08:07:37 +02:00
Stef Heyenrath 7d1dcc6fb1 x 2026-04-20 19:37:09 +02:00
Stef Heyenrath 82277c7804 Merge branch 'master' into SystemTextJsonMatcher 2026-04-20 18:55:02 +02:00
Stef Heyenrath 85d61a1877 2.3.0 2026-04-20 18:53:59 +02:00
Stef Heyenrath b105fd3706 . 2026-04-19 19:20:00 +02:00
Stef Heyenrath 21cc70e77b . 2026-04-19 11:05:55 +02:00
Stef Heyenrath 65bf469906 more tests 2026-04-18 15:41:35 +02:00
Stef Heyenrath 3fcc530d30 Merge branch 'master' into SystemTextJsonMatcher 2026-04-18 09:43:59 +02:00
Stef Heyenrath 885911203b Use DefaultJsonSerializer for BodyAsJson-Response (#1448) 2026-04-18 09:43:24 +02:00
Stef Heyenrath ebbedda098 . 2026-04-18 08:31:29 +02:00
Stef Heyenrath b55842afae Update src/WireMock.Net.Minimal/Properties/AssemblyInfo.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-18 08:21:37 +02:00
Stef Heyenrath 8d5f98dc0e Update src/WireMock.Net/WireMock.Net.csproj
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-18 08:21:12 +02:00
Stef Heyenrath 18d6b4958c Update test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-18 08:20:58 +02:00
Stef Heyenrath fece70a9d2 Update test/WireMock.Net.Tests/Pact/PactTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-18 08:19:57 +02:00
Stef Heyenrath ce35fe14af new projectx 2026-04-17 18:07:46 +02:00
Stef Heyenrath 6cb41494c8 . 2026-04-17 15:27:43 +02:00
Stef Heyenrath 6e402acd80 , 2026-04-17 14:42:43 +02:00
Stef Heyenrath 9853f055f2 SystemTextJsonMatcher 2026-04-17 14:32:55 +02:00
Stef Heyenrath 1e591d5f8a Fix ExactMatcher and JsonMatcher not working for ISO dates as string (#1443) 2026-04-17 13:32:26 +02:00
Stef Heyenrath 02b7e3744e Update instructions.md (#1444) 2026-04-17 13:23:29 +02:00
dependabot[bot] 6e2a4d7e04 Bump log4net from 2.0.15 to 3.3.0 (#1440)
---
updated-dependencies:
- dependency-name: log4net
  dependency-version: 3.3.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 07:49:17 +02:00
Jayaraman Venkatesan 479bb0b8ec bug/wiremock-1268 moving Scenario state change before global response delay (#1436)
Co-authored-by: Stef Heyenrath <Stef.Heyenrath@gmail.com>
2026-04-03 10:51:24 +02:00
Stef Heyenrath a453e00fdb Fix WireMock.Net.WebApplication.IIS example (#1435) 2026-03-31 22:52:27 +02:00
158 changed files with 5718 additions and 1608 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
# Copilot Instructions
## Project Guidelines
- All new byte[xx] calls should use using var data = ArrayPool<byte>.Shared.Lease(xx); instead of directly allocating byte arrays
- When running tests in this workspace, do not run tests for the net48 target framework.
- When changing System.Text.Json code in this workspace, verify API availability for netstandard2.0 and netstandard2.1 instead of assuming newer APIs exist.
+15
View File
@@ -0,0 +1,15 @@
---
description: 'Guidelines for this solution'
applyTo: '**/*.csproj, **/*.cs'
---
# Multi .NET Framework Targeting
## Instructions
- The main project "WireMock.Net.Minimal" targets `netstandard2.0` and `net8.0`. Ensure that any new code or dependencies are compatible with these frameworks.
# C# Guidelines
## Instructions
- When a new ByteArray is needed, do not use `var data = new byte[bufferSize];`. Always use `var data = ArrayPool<byte>.Shared.Lease(bufferSize);`.
+40
View File
@@ -0,0 +1,40 @@
name: Publish to NuGet
on:
workflow_dispatch:
jobs:
publish:
name: Build, Pack, and Publish
runs-on: windows-2022
permissions:
id-token: write # enable GitHub OIDC token issuance for this job
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build projects
shell: pwsh
run: |
Get-ChildItem ./src -Recurse -Filter *.csproj |
ForEach-Object {
dotnet build $_.FullName -c Release
}
- name: Pack projects
shell: pwsh
run: |
Get-ChildItem ./src -Recurse -Filter *.csproj |
ForEach-Object {
dotnet pack $_.FullName -c Release --no-build -o ./packages
}
- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }}
- name: Push to NuGet
run: dotnet nuget push "**/packages/*.nupkg" --api-key ${{steps.login.outputs.NUGET_API_KEY}} --source "https://api.nuget.org/v3/index.json" --skip-duplicate
+33
View File
@@ -1,3 +1,36 @@
# 2.7.0 (24 May 2026)
- [#1457](https://github.com/wiremock/WireMock.Net/pull/1457) - Update OpenTelemetry.Api from 1.14.0 to 1.15.3 in unit test project [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#1459](https://github.com/wiremock/WireMock.Net/pull/1459) - chore: update Handlebars 2.5.2 to 2.5.5 [dependencies] contributed by [kamisoft-fr](https://github.com/kamisoft-fr)
- [#1461](https://github.com/wiremock/WireMock.Net/pull/1461) - Update Testcontainers nuget package to 4.12.0 [dependencies] contributed by [MD-V](https://github.com/MD-V)
- [#1462](https://github.com/wiremock/WireMock.Net/pull/1462) - Update Scriban.Signed to latest [dependencies] contributed by [StefH](https://github.com/StefH)
- [#1458](https://github.com/wiremock/WireMock.Net/issues/1458) - update Handlebars Humanizer package version [feature]
# 2.6.0 (11 May 2026)
- [#1455](https://github.com/wiremock/WireMock.Net/pull/1455) - Fix request storing when RequestLogExpirationDuration is set [bug] [bug] contributed by [pbenko-xitaso](https://github.com/pbenko-xitaso)
- [#1454](https://github.com/wiremock/WireMock.Net/issues/1454) - No requests stored in Standalone when RequestLogExpirationDuration is set [bug]
# 2.5.0 (04 May 2026)
- [#1451](https://github.com/wiremock/WireMock.Net/pull/1451) - Feature/early mismatch [feature] contributed by [Stepami](https://github.com/Stepami)
- [#1452](https://github.com/wiremock/WireMock.Net/pull/1452) - Bump log4net from 2.0.15 to 3.3.0 in example console app [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#1453](https://github.com/wiremock/WireMock.Net/pull/1453) - Fix CVE-2026-40021: upgrade log4net to 3.3.0 in examples/WireMock.Net.Service/packages.config [dependencies] contributed by [Copilot](https://github.com/apps/copilot-swe-agent)
- [#1442](https://github.com/wiremock/WireMock.Net/issues/1442) - Bug: [grpc] WithBodyAsProtoBuf exception on match [bug]
# 2.4.0 (24 April 2026)
- [#1437](https://github.com/wiremock/WireMock.Net/pull/1437) - Added feature to enable and disable mappings [feature] contributed by [jayaraman-venkatesan](https://github.com/jayaraman-venkatesan)
- [#1450](https://github.com/wiremock/WireMock.Net/pull/1450) - Bump OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.14.0 to 1.15.x [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#1421](https://github.com/wiremock/WireMock.Net/issues/1421) - Deactivate mapping without deleting it [feature]
# 2.3.0 (20 April 2026)
- [#1436](https://github.com/wiremock/WireMock.Net/pull/1436) - Moving Scenario state change before global response delay is set [feature] contributed by [jayaraman-venkatesan](https://github.com/jayaraman-venkatesan)
- [#1440](https://github.com/wiremock/WireMock.Net/pull/1440) - Bump log4net from 2.0.15 to 3.3.0 [dependencies] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#1443](https://github.com/wiremock/WireMock.Net/pull/1443) - Fix ExactMatcher and JsonMatcher not working for ISO dates as string [bug] contributed by [StefH](https://github.com/StefH)
- [#1444](https://github.com/wiremock/WireMock.Net/pull/1444) - Update instructions.md [refactor] contributed by [StefH](https://github.com/StefH)
- [#1448](https://github.com/wiremock/WireMock.Net/pull/1448) - Use DefaultJsonSerializer for BodyAsJson-Response [bug] contributed by [StefH](https://github.com/StefH)
- [#1205](https://github.com/wiremock/WireMock.Net/issues/1205) - System.Private.Uri 4.3.0 Blackduck security High vulnerability [bug]
- [#1260](https://github.com/wiremock/WireMock.Net/issues/1260) - Scenario state change before a response delay timeout ends [bug]
- [#1441](https://github.com/wiremock/WireMock.Net/issues/1441) - ExactMatcher and JsonMatcher not working for ISO dates in v2.2.0 [bug]
- [#1446](https://github.com/wiremock/WireMock.Net/issues/1446) - WithBodyAsJson does not return correctly formatted json when using SystemTextJsonConverter [bug]
# 2.2.0 (30 March 2026)
- [#1433](https://github.com/wiremock/WireMock.Net/pull/1433) - Add comments for ScenarioStateStore related code [refactor] contributed by [StefH](https://github.com/StefH)
- [#1434](https://github.com/wiremock/WireMock.Net/pull/1434) - Upgrade Scriban.Signed [security] contributed by [StefH](https://github.com/StefH)
+1 -1
View File
@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>2.2.0</VersionPrefix>
<VersionPrefix>2.7.0</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
+1 -1
View File
@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=2.2.0
SET version=2.7.0
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
+6 -3
View File
@@ -1,5 +1,8 @@
# 2.2.0 (30 March 2026)
- #1433 Add comments for ScenarioStateStore related code [refactor]
- #1434 Upgrade Scriban.Signed [security]
# 2.7.0 (24 May 2026)
- #1457 Update OpenTelemetry.Api from 1.14.0 to 1.15.3 in unit test project [dependencies]
- #1459 chore: update Handlebars 2.5.2 to 2.5.5 [dependencies]
- #1461 Update Testcontainers nuget package to 4.12.0 [dependencies]
- #1462 Update Scriban.Signed to latest [dependencies]
- #1458 update Handlebars Humanizer package version [feature]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
+2 -1
View File
@@ -64,6 +64,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| | | |
| &nbsp;&nbsp;**WireMock.Net.Extensions.Routing** | [![NuGet Badge WireMock.Net.Extensions.Routing](https://img.shields.io/nuget/v/WireMock.Net.Extensions.Routing)](https://www.nuget.org/packages/WireMock.Net.Extensions.Routing) | [![MyGet Badge WireMock.Net.Extensions.Routing](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Extensions.Routing?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Extensions.Routing)
| &nbsp;&nbsp;**WireMock.Net.Matchers.CSharpCode** | [![NuGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/nuget/v/WireMock.Net.Matchers.CSharpCode)](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [![MyGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Matchers.CSharpCode?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)
| &nbsp;&nbsp;**WireMock.Net.Matchers.SystemTextJsonPath** | [![NuGet Badge WireMock.Net.Matchers.SystemTextJsonPath](https://img.shields.io/nuget/v/WireMock.Net.Matchers.SystemTextJsonPath)](https://www.nuget.org/packages/WireMock.Net.Matchers.SystemTextJsonPath) | [![MyGet Badge WireMock.Net.Matchers.SystemTextJsonPath](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Matchers.SystemTextJsonPath?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.SystemTextJsonPath)
| &nbsp;&nbsp;**WireMock.Net.OpenApiParser** | [![NuGet Badge WireMock.Net.OpenApiParser](https://img.shields.io/nuget/v/WireMock.Net.OpenApiParser)](https://www.nuget.org/packages/WireMock.Net.OpenApiParser) | [![MyGet Badge WireMock.Net.OpenApiParser](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.OpenApiParser?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenApiParser)
| &nbsp;&nbsp;**WireMock.Net.MimePart** | [![NuGet Badge WireMock.Net.MimePart](https://img.shields.io/nuget/v/WireMock.Net.MimePart)](https://www.nuget.org/packages/WireMock.Net.MimePart) | [![MyGet Badge WireMock.Net.MimePart](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.MimePart?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.MimePart)
| &nbsp;&nbsp;**WireMock.Net.GraphQL** | [![NuGet Badge WireMock.Net.GraphQL](https://img.shields.io/nuget/v/WireMock.Net.GraphQL)](https://www.nuget.org/packages/WireMock.Net.GraphQL) | [![MyGet Badge WireMock.Net.GraphQL](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.GraphQL?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.GraphQL)
@@ -76,7 +77,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
<br />
🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart*, *WireMock.Net.GraphQL*, *WireMock.Net.ProtoBuf* and *WireMock.Net.OpenTelemetry*.
🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart*, *WireMock.Net.GraphQL*, *WireMock.Net.ProtoBuf*, *WireMock.Net.OpenTelemetry* and *WireMock.Net.Matchers.SystemTextJsonPath*.
---
+30 -33
View File
@@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11205.157
@@ -38,8 +37,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Client", "examples\WireMock.Net.Client\WireMock.Net.Client.csproj", "{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Service", "examples\Wiremock.Net.Service\WireMock.Net.Service.csproj", "{7F0B2446-0363-4720-AF46-F47F83B557DC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.RequestLogTest", "examples\WireMock.Net.Console.RequestLogTest\WireMock.Net.Console.RequestLogTest.csproj", "{A9D039B9-7509-4CF1-9EFD-87EB82998575}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.OpenApiParser.ConsoleApp", "examples\WireMock.Net.OpenApiParser.ConsoleApp\WireMock.Net.OpenApiParser.ConsoleApp.csproj", "{5C09FB93-1535-4F92-AF26-21E8A061EE4A}"
@@ -76,10 +73,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Testcontainers
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.TestcontainersExample", "examples\WireMock.Net.TestcontainersExample\WireMock.Net.TestcontainersExample.csproj", "{56A38798-C48B-4A4A-B805-071E05C02CE1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0147029F-FA4A-44B3-B79A-3C3574054EE4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultipartUploader", "tools\MultipartUploader\MultipartUploader.csproj", "{07C30227-ADEC-4BDE-8CDC-849D85A690BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.NET8", "examples\WireMock.Net.Console.NET8\WireMock.Net.Console.NET8.csproj", "{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueProxy", "examples\WireMockAzureQueueProxy\WireMockAzureQueueProxy.csproj", "{7FC0B409-2682-40EE-B3B9-3930D6769D01}"
@@ -156,6 +149,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.TestWebApplica
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.RestClient.AwesomeAssertions", "src\WireMock.Net.RestClient.AwesomeAssertions\WireMock.Net.RestClient.AwesomeAssertions.csproj", "{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Matchers.SystemTextJsonPath", "src\WireMock.Net.Matchers.SystemTextJsonPath\WireMock.Net.Matchers.SystemTextJsonPath.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Service", "examples\WireMock.Net.Service\WireMock.Net.Service.csproj", "{7F0B2446-0363-4720-AF46-F47F83B557DC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -238,18 +235,6 @@ Global
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x64.Build.0 = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x86.ActiveCfg = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x86.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.Build.0 = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -394,18 +379,6 @@ Global
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x64.Build.0 = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x86.ActiveCfg = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x86.Build.0 = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x64.ActiveCfg = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x64.Build.0 = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x86.ActiveCfg = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x86.Build.0 = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.Build.0 = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x64.ActiveCfg = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x64.Build.0 = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x86.ActiveCfg = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x86.Build.0 = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -850,6 +823,30 @@ Global
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}.Release|x64.Build.0 = Release|Any CPU
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}.Release|x86.ActiveCfg = Release|Any CPU
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}.Release|x86.Build.0 = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -861,7 +858,6 @@ Global
{B6269AAC-170A-43D5-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E} = {0BB8B634-407A-4610-A91F-11586990767A}
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{7F0B2446-0363-4720-AF46-F47F83B557DC} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{A9D039B9-7509-4CF1-9EFD-87EB82998575} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{5C09FB93-1535-4F92-AF26-21E8A061EE4A} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{B6269AAC-170A-4346-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
@@ -876,7 +872,6 @@ Global
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{56A38798-C48B-4A4A-B805-071E05C02CE1} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{07C30227-ADEC-4BDE-8CDC-849D85A690BB} = {0147029F-FA4A-44B3-B79A-3C3574054EE4}
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{7FC0B409-2682-40EE-B3B9-3930D6769D01} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
@@ -914,6 +909,8 @@ Global
{2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{3B05CC76-C3CB-8667-6B65-3129DFB25681} = {0BB8B634-407A-4610-A91F-11586990767A}
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{7F0B2446-0363-4720-AF46-F47F83B557DC} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
@@ -12,11 +12,11 @@
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.4.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
</ItemGroup>
</Project>
@@ -16,7 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="log4net" Version="3.3.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup>
@@ -18,7 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="log4net" Version="3.3.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup>
@@ -16,7 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="WireMock.Net" Version="1.25.0" />
<PackageReference Include="WireMock.Net" Version="2.6.0" />
</ItemGroup>
</Project>
@@ -8,9 +8,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
</ItemGroup>
<ItemGroup>
@@ -32,6 +32,9 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject>Wiremock.Net.Service.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.15" targetFramework="net48" />
<package id="log4net" version="3.3.0" targetFramework="net48" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
@@ -246,8 +246,8 @@ internal class Program
throw new InvalidOperationException($"The {nameof(TestcontainersSettings.OS.DockerEndpointAuthConfig)} is null. Check if Docker is started.");
}
using var dockerClientConfig = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration();
using var dockerClient = dockerClientConfig.CreateClient();
var dockerClientBuilder = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientBuilder();
using var dockerClient = dockerClientBuilder.Build();
var version = await dockerClient.System.GetVersionAsync();
return version.Os.IndexOf("Windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux;
@@ -1,29 +1,18 @@
// Copyright © WireMock.Net
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;
namespace WireMock.Net.WebApplication;
public class App : IHostedService
public class App(IWireMockService service) : IHostedService
{
private readonly IWireMockService _service;
public App(IWireMockService service)
{
_service = service;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_service.Start();
service.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_service.Stop();
service.Stop();
return Task.CompletedTask;
}
}
@@ -1,9 +1,5 @@
// Copyright © WireMock.Net
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using WireMock.Settings;
namespace WireMock.Net.WebApplication;
@@ -15,7 +15,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using WireMock.Admin.Requests;
@@ -15,7 +15,7 @@
"WireMockServerSettings": {
"StartAdminInterface": true,
"Urls": [
"http://localhost"
"http://localhost:0"
]
}
}
@@ -1,12 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--
<!--
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
-->
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
</system.webServer>
-->
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
</system.webServer>
</configuration>
@@ -55,12 +55,17 @@ public class MappingModel
/// In case the value is null state will not be changed.
/// </summary>
public string? SetStateTo { get; set; }
/// <summary>
/// The number of times this match should be matched before the state will be changed to the specified one.
/// </summary>
public int? TimesInSameState { get; set; }
/// <summary>
/// Value to determine if the mapping is disabled. Defaults to <c>null</c> (not disabled).
/// </summary>
public bool? IsDisabled { get; set; }
/// <summary>
/// The request model.
/// </summary>
@@ -100,7 +105,7 @@ public class MappingModel
/// </summary>
public object? Data { get; set; }
/// <summary>
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
public double? Probability { get; set; }
@@ -1,5 +1,7 @@
// Copyright © WireMock.Net
using WireMock.Matchers.Request;
namespace WireMock.Admin.Mappings;
/// <summary>
@@ -61,9 +63,15 @@ public class RequestModel
/// Gets or sets the Params.
/// </summary>
public IList<ParamModel>? Params { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
public BodyModel? Body { get; set; }
/// <summary>
/// Type of the request matcher to return an immediate mismatch during mapping processing.
/// Optional.
/// </summary>
public RequestMatcherType? EarlyMatcherType { get; set; }
}
@@ -7,6 +7,11 @@ namespace WireMock.Matchers.Request;
/// </summary>
public interface IRequestMatcher
{
/// <summary>
/// Gets the request matcher's type.
/// </summary>
public RequestMatcherType Type { get; }
/// <summary>
/// Determines whether the specified RequestMessage is match.
/// </summary>
@@ -0,0 +1,84 @@
// Copyright © WireMock.Net
namespace WireMock.Matchers.Request;
/// <summary>
/// List of predefined request matcher types.
/// </summary>
public enum RequestMatcherType
{
/// <summary>
/// RequestMessageBodyMatcher
/// </summary>
Body = 0,
/// <summary>
/// RequestMessageBodyMatcher{T}
/// </summary>
BodyOfT = 1,
/// <summary>
/// RequestMessageClientIPMatcher
/// </summary>
ClientIP = 2,
/// <summary>
/// RequestMessageCookieMatcher
/// </summary>
Cookie = 3,
/// <summary>
/// RequestMessageGraphQLMatcher
/// </summary>
GraphQL = 4,
/// <summary>
/// RequestMessageHeaderMatcher
/// </summary>
Header = 5,
/// <summary>
/// RequestMessageHttpVersionMatcher
/// </summary>
HttpVersion = 6,
/// <summary>
/// RequestMessageMethodMatcher
/// </summary>
Method = 7,
/// <summary>
/// RequestMessageMultiPartMatcher
/// </summary>
MultiPart = 8,
/// <summary>
/// RequestMessageParamMatcher
/// </summary>
Param = 9,
/// <summary>
/// RequestMessagePathMatcher
/// </summary>
Path = 10,
/// <summary>
/// RequestMessageProtoBufMatcher
/// </summary>
ProtoBuf = 11,
/// <summary>
/// RequestMessageScenarioAndStateMatcher
/// </summary>
ScenarioAndState = 12,
/// <summary>
/// RequestMessageUrlMatcher
/// </summary>
Url = 13,
/// <summary>
/// RequestMessageCompositeMatcher
/// </summary>
Composite = 14
}
@@ -31,19 +31,19 @@ public partial class WireMockAssertions
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
{
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
{
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAssertions> WithBodyAsJson(IJsonMatcher matcher, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
@@ -126,15 +126,44 @@ public partial class WireMockAssertions
private static string? FormatBody(object? body)
{
return body switch
if (body == null)
{
null => null,
string str => str,
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
JToken jToken => jToken.ToString(Formatting.None),
_ => JToken.FromObject(body).ToString(Formatting.None)
};
return null;
}
if (body is string str)
{
return str;
}
if (body is AnyOf<string, StringPattern>[] stringPatterns)
{
return FormatBodies(stringPatterns.Select(p => p.GetPattern()));
}
if (body is byte[] bytes)
{
return $"byte[{bytes.Length}] {{...}}";
}
if (body is JToken jToken)
{
return jToken.ToString(Formatting.None);
}
// System.IO.FileNotFoundException : Could not load file or assembly 'System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
var typeName = body.GetType().FullName;
if (typeName == "System.Text.Json.JsonElement")
{
return ((dynamic)body).GetRawText();
}
if (typeName == "System.Text.Json.JsonDocument")
{
return ((dynamic)body).RootElement.GetRawText();
}
return JToken.FromObject(body).ToString(Formatting.None);
}
private static string? FormatBodies(IEnumerable<object?> bodies)
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description>
<Authors>Gennadii Saltyshchak</Authors>
@@ -25,7 +25,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Abstractions" Version="0.8.0" />
<PackageReference Include="JsonConverter.Abstractions" Version="0.12.0" />
</ItemGroup>
<ItemGroup>
@@ -31,19 +31,19 @@ public partial class WireMockAssertions
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAssertions> WithBodyAsJson(object body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
{
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAssertions> WithBodyAsJson(string body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
{
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAssertions> WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAssertions> WithBodyAsJson(IJsonMatcher matcher, string because = "", params object[] becauseArgs)
{
var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
@@ -126,15 +126,44 @@ public partial class WireMockAssertions
private static string? FormatBody(object? body)
{
return body switch
if (body == null)
{
null => null,
string str => str,
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
JToken jToken => jToken.ToString(Formatting.None),
_ => JToken.FromObject(body).ToString(Formatting.None)
};
return null;
}
if (body is string str)
{
return str;
}
if (body is AnyOf<string, StringPattern>[] stringPatterns)
{
return FormatBodies(stringPatterns.Select(p => p.GetPattern()));
}
if (body is byte[] bytes)
{
return $"byte[{bytes.Length}] {{...}}";
}
if (body is JToken jToken)
{
return jToken.ToString(Formatting.None);
}
// System.IO.FileNotFoundException : Could not load file or assembly 'System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
var typeName = body.GetType().FullName;
if (typeName == "System.Text.Json.JsonElement")
{
return ((dynamic)body).GetRawText();
}
if (typeName == "System.Text.Json.JsonDocument")
{
return ((dynamic)body).RootElement.GetRawText();
}
return JToken.FromObject(body).ToString(Formatting.None);
}
private static string? FormatBodies(IEnumerable<object?> bodies)
@@ -0,0 +1,184 @@
// Copyright © WireMock.Net
using System.Text.Json;
using System.Text.Json.Nodes;
using AnyOfTypes;
using Json.Path;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Models;
using WireMock.Util;
namespace WireMock.Matchers;
/// <summary>
/// SystemTextJsonPathMatcher - behaves the same as JsonPathMatcher but uses System.Text.Json and Json.Path instead of Newtonsoft.Json.
/// </summary>
/// <seealso cref="ISystemTextJsonPathMatcher" />
public class SystemTextJsonPathMatcher : ISystemTextJsonPathMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc />
public object Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public SystemTextJsonPathMatcher(params string[] patterns)
: this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns.ToAnyOfPatterns())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonPathMatcher"/> class.
/// </summary>
/// <param name="patterns">The patterns.</param>
public SystemTextJsonPathMatcher(params AnyOf<string, StringPattern>[] patterns)
: this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonPathMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="matchOperator">The <see cref="Matchers.MatchOperator"/> to use. (default = "Or")</param>
/// <param name="patterns">The patterns.</param>
public SystemTextJsonPathMatcher(
MatchBehaviour matchBehaviour,
MatchOperator matchOperator = MatchOperator.Or,
params AnyOf<string, StringPattern>[] patterns)
{
_patterns = Guard.NotNull(patterns);
MatchBehaviour = matchBehaviour;
MatchOperator = matchOperator;
Value = patterns;
}
/// <inheritdoc />
public MatchResult IsMatch(string? input)
{
var score = MatchScores.Mismatch;
Exception? exception = null;
if (!string.IsNullOrWhiteSpace(input))
{
try
{
var node = JsonNode.Parse(input!);
score = IsMatchInternal(node);
}
catch (Exception ex)
{
exception = ex;
}
}
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />
public MatchResult IsMatch(object? input)
{
var score = MatchScores.Mismatch;
Exception? exception = null;
// When input is null or byte[], return Mismatch.
if (input != null && input is not byte[])
{
try
{
JsonNode? node = input switch
{
JsonNode jsonNode => jsonNode,
string str => JsonNode.Parse(str),
_ => JsonNode.Parse(JsonSerializer.Serialize(input))
};
score = IsMatchInternal(node);
}
catch (Exception ex)
{
exception = ex;
}
}
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />
public AnyOf<string, StringPattern>[] GetPatterns()
{
return _patterns;
}
/// <inheritdoc />
public MatchOperator MatchOperator { get; }
/// <inheritdoc />
public string Name => nameof(SystemTextJsonPathMatcher);
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" +
$")";
}
private double IsMatchInternal(JsonNode? node)
{
// JsonPath.Net requires the node to be inside an object or array for filter expressions.
// Similar to JsonPathMatcher's ConvertJTokenToJArrayIfNeeded, wrap a plain object in an array
// when it's an object with a single non-array child property.
var evaluationNode = WrapIfNeeded(node);
var values = _patterns
.Select(pattern =>
{
var path = JsonPath.Parse(pattern.GetPattern());
var result = path.Evaluate(evaluationNode);
return result.Matches is { Count: > 0 };
})
.ToArray();
return MatchScores.ToScore(values, MatchOperator);
}
// Mirrors JsonPathMatcher.ConvertJTokenToJArrayIfNeeded:
// If the node is an object with exactly one property whose value is not already an array,
// wrap that value in an array so that filter expressions (e.g. [?(@.x == y)]) can match.
private static JsonNode? WrapIfNeeded(JsonNode? node)
{
if (node is not JsonObject obj)
{
return node;
}
var properties = obj.ToList();
if (properties.Count != 1)
{
return node;
}
var single = properties[0];
if (single.Value is JsonArray)
{
return node;
}
var clonedValue = JsonNode.Parse(single.Value?.ToJsonString() ?? "null");
return new JsonObject
{
[single.Key] = new JsonArray(clonedValue)
};
}
}
@@ -0,0 +1,5 @@
// Copyright © WireMock.Net
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>A SystemTextJsonPathMatcher which can be used to match WireMock.Net Requests using JsonPath.Net.</Description>
<AssemblyTitle>WireMock.Net.Matchers.SystemTextJsonPath</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;matchers;matcher;jsonpath;systemtextjson</PackageTags>
<RootNamespace>WireMock</RootNamespace>
<PackageId>WireMock.Net.Matchers.SystemTextJsonPath</PackageId>
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<!--<DelaySign>true</DelaySign>-->
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug - Sonar'">
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Shared\WireMock.Net.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JsonPath.Net" Version="3.0.2" />
</ItemGroup>
</Project>
@@ -1,7 +1,8 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using Newtonsoft.Json;
using WireMock.Matchers.Helpers;
using WireMock.Models.Mime;
using WireMock.Util;
@@ -15,6 +16,8 @@ public class MimePartMatcher : IMimePartMatcher
{
private readonly IList<(string Name, Func<IMimePartData, MatchResult> func)> _matcherFunctions;
private readonly IJsonConverter _jsonConverter;
/// <inheritdoc />
public string Name => nameof(MimePartMatcher);
@@ -41,7 +44,8 @@ public class MimePartMatcher : IMimePartMatcher
IStringMatcher? contentTypeMatcher,
IStringMatcher? contentDispositionMatcher,
IStringMatcher? contentTransferEncodingMatcher,
IMatcher? contentMatcher
IMatcher? contentMatcher,
IJsonConverter? jsonConverter = null
)
{
MatchBehaviour = matchBehaviour;
@@ -49,6 +53,7 @@ public class MimePartMatcher : IMimePartMatcher
ContentDispositionMatcher = contentDispositionMatcher;
ContentTransferEncodingMatcher = contentTransferEncodingMatcher;
ContentMatcher = contentMatcher;
_jsonConverter = jsonConverter ?? new NewtonsoftJsonConverter();
_matcherFunctions = [];
if (ContentTypeMatcher != null)
@@ -107,7 +112,8 @@ public class MimePartMatcher : IMimePartMatcher
ContentType = GetContentTypeAsString(mimePart.ContentType),
DeserializeJson = true,
ContentEncoding = null, // mimePart.ContentType?.CharsetEncoding.ToString(),
DecompressGZipAndDeflate = true
DecompressGZipAndDeflate = true,
DefaultJsonConverter = _jsonConverter
};
var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult();
@@ -1,8 +1,7 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Net;
using System.Net.Http;
using JsonConverter.Abstractions;
using WireMock.Util;
namespace WireMock.Http;
@@ -15,7 +14,8 @@ internal static class HttpResponseMessageHelper
Uri originalUri,
bool deserializeJson,
bool decompressGzipAndDeflate,
bool deserializeFormUrlEncoded)
bool deserializeFormUrlEncoded,
IJsonConverter jsonConverter)
{
var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode };
@@ -45,7 +45,8 @@ internal static class HttpResponseMessageHelper
DeserializeJson = deserializeJson,
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = decompressGzipAndDeflate,
DeserializeFormUrlEncoded = deserializeFormUrlEncoded
DeserializeFormUrlEncoded = deserializeFormUrlEncoded,
DefaultJsonConverter = jsonConverter
};
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
}
@@ -0,0 +1,16 @@
// Copyright © WireMock.Net
using System.Net;
namespace WireMock;
internal interface IResponseMessageBuilder
{
ResponseMessage Create(HttpStatusCode statusCode, string? status, Guid? guid = null);
ResponseMessage Create(int statusCode, string? status, Guid? guid = null);
ResponseMessage Create(int statusCode, string? status, string? error, Guid? guid = null);
ResponseMessage Create(HttpStatusCode statusCode);
}
+3
View File
@@ -62,6 +62,9 @@ public class Mapping : IMapping
/// <inheritdoc />
public bool IsProxy => Provider is ProxyAsyncResponseProvider;
/// <inheritdoc />
public bool IsDisabled { get; set; }
/// <inheritdoc />
public bool LogMapping => Provider is not (DynamicResponseProvider or DynamicAsyncResponseProvider);
+6 -2
View File
@@ -27,6 +27,7 @@ public class MappingBuilder : IMappingBuilder
private readonly MappingToFileSaver _mappingToFileSaver;
private readonly IGuidUtils _guidUtils;
private readonly IDateTimeUtils _dateTimeUtils;
private readonly IResponseMessageBuilder _responseMessageBuilder;
/// <summary>
/// Create a MappingBuilder
@@ -43,6 +44,7 @@ public class MappingBuilder : IMappingBuilder
_guidUtils = new GuidUtils();
_dateTimeUtils = new DateTimeUtils();
_responseMessageBuilder = new ResponseMessageBuilder(_dateTimeUtils);
}
internal MappingBuilder(
@@ -51,7 +53,8 @@ public class MappingBuilder : IMappingBuilder
MappingConverter mappingConverter,
MappingToFileSaver mappingToFileSaver,
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils
IDateTimeUtils dateTimeUtils,
IResponseMessageBuilder responseMessageBuilder
)
{
_settings = Guard.NotNull(settings);
@@ -60,12 +63,13 @@ public class MappingBuilder : IMappingBuilder
_mappingToFileSaver = Guard.NotNull(mappingToFileSaver);
_guidUtils = Guard.NotNull(guidUtils);
_dateTimeUtils = Guard.NotNull(dateTimeUtils);
_responseMessageBuilder = Guard.NotNull(responseMessageBuilder);
}
/// <inheritdoc />
public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false)
{
return new RespondWithAProvider(RegisterMapping, Guard.NotNull(requestMatcher), _settings, _guidUtils, _dateTimeUtils, saveToFile);
return new RespondWithAProvider(RegisterMapping, Guard.NotNull(requestMatcher), _settings, _guidUtils, _dateTimeUtils, _responseMessageBuilder, saveToFile);
}
/// <inheritdoc />
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using Newtonsoft.Json.Linq;
using WireMock.Util;
@@ -0,0 +1,173 @@
// Copyright © WireMock.Net
using System.Text.Json;
using WireMock.Util;
namespace WireMock.Matchers;
/// <summary>
/// Generic AbstractSystemTextJsonPartialMatcher - uses System.Text.Json instead of Newtonsoft.Json.
/// </summary>
public abstract class AbstractSystemTextJsonPartialMatcher : SystemTextJsonMatcher
{
/// <summary>
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
/// </summary>
protected AbstractSystemTextJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
/// </summary>
protected AbstractSystemTextJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AbstractSystemTextJsonPartialMatcher"/> class.
/// </summary>
protected AbstractSystemTextJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, regex)
{
}
/// <inheritdoc />
protected override bool IsMatch(JsonElement value, JsonElement? input)
{
if (input == null)
{
return false;
}
var inputElement = input.Value;
// Regex on a string value
if (Regex && value.ValueKind == JsonValueKind.String)
{
var valueAsString = value.GetString()!;
var inputAsString = GetStringValue(inputElement);
var (valid, result) = RegexUtils.MatchRegex(valueAsString, inputAsString);
if (valid)
{
return result;
}
}
// Guid comparison: both strings, both parseable as Guid
if (value.ValueKind == JsonValueKind.String && inputElement.ValueKind == JsonValueKind.String)
{
var valueStr = value.GetString()!;
var inputStr = inputElement.GetString()!;
if (Guid.TryParse(valueStr, out var vg) && Guid.TryParse(inputStr, out var ig))
{
return IsMatch(vg.ToString(), ig.ToString());
}
}
// Type mismatch (after regex/guid checks)
if (value.ValueKind != inputElement.ValueKind)
{
return false;
}
switch (value.ValueKind)
{
case JsonValueKind.Object:
{
var nestedValues = value.EnumerateObject().ToArray();
if (nestedValues.Length == 0)
{
return true;
}
return nestedValues.All(pair =>
{
var selected = SelectElement(inputElement, pair.Name);
return selected != null && IsMatch(pair.Value, selected.Value);
});
}
case JsonValueKind.Array:
{
var valuesArray = value.EnumerateArray().ToArray();
if (valuesArray.Length == 0)
{
return true;
}
var tokenArray = inputElement.EnumerateArray().ToArray();
if (tokenArray.Length == 0)
{
return false;
}
return valuesArray.All(subFilter => tokenArray.Any(subToken => IsMatch(subFilter, subToken)));
}
default:
return IsMatch(GetStringValue(value), GetStringValue(inputElement));
}
}
/// <summary>
/// Check if two strings are a match (matching can be done exact or wildcard).
/// </summary>
protected abstract bool IsMatch(string value, string input);
/// <summary>
/// Selects a <see cref="JsonElement"/> from an object using a key that may be a plain property name,
/// a dotted path (e.g. "a.b.c") or bracket notation (e.g. "['name.with.dot']"),
/// mirroring Newtonsoft's <c>SelectToken</c> + direct indexer fallback.
/// </summary>
private static JsonElement? SelectElement(JsonElement input, string key)
{
if (input.ValueKind != JsonValueKind.Object)
{
return null;
}
// Direct property access (also handles keys containing colons or dots that are literal property names)
if (input.TryGetProperty(key, out var direct))
{
return direct;
}
// Bracket notation: ['property.name.with.dots']
if (key.StartsWith("['") && key.EndsWith("']"))
{
var propertyName = key.Substring(2, key.Length - 4);
return input.TryGetProperty(propertyName, out var bracketValue) ? bracketValue : null;
}
// Dotted path: a.b.c
if (key.Contains('.'))
{
var parts = key.Split('.');
var current = input;
foreach (var part in parts)
{
if (current.ValueKind != JsonValueKind.Object || !current.TryGetProperty(part, out var next))
{
return null;
}
current = next;
}
return current;
}
return null;
}
private static string GetStringValue(JsonElement element)
{
return element.ValueKind == JsonValueKind.String
? element.GetString()!
: element.GetRawText();
}
}
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using AnyOfTypes;
using Newtonsoft.Json.Linq;
using Stef.Validation;
@@ -13,9 +12,8 @@ namespace WireMock.Matchers;
/// <summary>
/// JsonPathMatcher
/// </summary>
/// <seealso cref="IStringMatcher" />
/// <seealso cref="IObjectMatcher" />
public class JsonPathMatcher : IStringMatcher, IObjectMatcher
/// <seealso cref="IJsonPathMatcher" />
public class JsonPathMatcher : IJsonPathMatcher
{
private readonly AnyOf<string, StringPattern>[] _patterns;
@@ -1,11 +1,12 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Collections;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Serialization;
using WireMock.Util;
using JsonUtils = WireMock.Util.JsonUtils;
namespace WireMock.Matchers;
@@ -69,7 +70,7 @@ public class JsonMatcher : IJsonMatcher
Regex = regex;
Value = value;
_valueAsJToken = JsonUtils.ConvertValueToJToken(value);
_valueAsJToken = ConvertValueToJToken(value);
}
/// <inheritdoc />
@@ -83,7 +84,7 @@ public class JsonMatcher : IJsonMatcher
{
try
{
var inputAsJToken = JsonUtils.ConvertValueToJToken(input);
var inputAsJToken = ConvertValueToJToken(input);
var match = IsMatch(RenameJToken(_valueAsJToken), RenameJToken(inputAsJToken));
score = MatchScores.ToScore(match);
@@ -103,7 +104,7 @@ public class JsonMatcher : IJsonMatcher
return $"new {Name}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
$")";
@@ -241,6 +242,18 @@ public class JsonMatcher : IJsonMatcher
return new JObject(renamedProperties);
}
private static JToken ConvertValueToJToken(object value)
{
// Check if JToken, string, IEnumerable or object
return value switch
{
JToken tokenValue => tokenValue,
string stringValue => JsonConvert.DeserializeObject<JToken>(stringValue, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!,
IEnumerable enumerableValue => JArray.FromObject(enumerableValue),
_ => JObject.FromObject(value),
};
}
private static string? ToUpper(string? input)
{
return input?.ToUpperInvariant();
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using Stef.Validation;
using WireMock.Matchers.Helpers;
using WireMock.Util;
@@ -142,6 +141,9 @@ public class RequestMessageBodyMatcher : IRequestMatcher
MatchOperator = matchOperator;
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Body;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -31,6 +31,9 @@ public class RequestMessageBodyMatcher<T> : IRequestMatcher
Func = Guard.NotNull(func);
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.BodyOfT;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -74,6 +74,9 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs);
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.ClientIP;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -20,6 +20,11 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
/// </value>
private IEnumerable<IRequestMatcher> RequestMatchers { get; }
/// <summary>
/// Selected type to choose the matcher from <see cref="RequestMatchers"/> which will immediately return a mismatch.
/// </summary>
internal RequestMatcherType? EarlyMatcherType { get; private protected set; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class.
/// </summary>
@@ -31,6 +36,9 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
_type = type;
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Composite;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -39,6 +47,13 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
return MatchScores.Mismatch;
}
var earlyMatcher = new RequestMessageEarlyMatcher(EarlyMatcherType, RequestMatchers);
var earlyMatchResult = earlyMatcher.GetMatchingScore(requestMessage, requestMatchResult);
if (!MatchScores.IsPerfect(earlyMatchResult))
{
return MatchScores.Mismatch;
}
if (_type == CompositeMatcherType.And)
{
return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
@@ -1,7 +1,6 @@
// Copyright © WireMock.Net
using Stef.Validation;
using System.Linq;
namespace WireMock.Matchers.Request;
@@ -93,6 +92,9 @@ public class RequestMessageCookieMatcher : IRequestMatcher
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Cookie;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -0,0 +1,35 @@
// Copyright © WireMock.Net
namespace WireMock.Matchers.Request;
/// <summary>
/// Return the mismatch if the matching score of matchers is not perfect.
/// </summary>
internal sealed class RequestMessageEarlyMatcher(
RequestMatcherType? earlyMatcherType,
IEnumerable<IRequestMatcher> requestMatchers) : IRequestMatcher
{
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Composite;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
if (earlyMatcherType is null)
{
return MatchScores.Perfect;
}
var earlyMatchers = requestMatchers
.Where(m => m.Type == earlyMatcherType)
.ToList();
if (earlyMatchers.Count is 0)
{
return MatchScores.Perfect;
}
var compositeMatcher = new RequestBuilders.Request(earlyMatchers);
return compositeMatcher.GetMatchingScore(requestMessage, requestMatchResult);
}
}
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using Stef.Validation;
using WireMock.Types;
@@ -12,7 +11,7 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageHeaderMatcher : IRequestMatcher
{
private const string _name = nameof(RequestMessageCookieMatcher);
private const string _name = nameof(RequestMessageHeaderMatcher);
/// <summary>
/// MatchBehaviour
@@ -106,6 +105,9 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
Name = string.Empty; // Not used when Func, but set to a non-null valid value.
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Header;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -65,6 +65,9 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
MatcherOnStringFunc = Guard.NotNull(func);
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.HttpVersion;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -46,6 +46,9 @@ internal class RequestMessageMethodMatcher : IRequestMatcher
MatchOperator = matchOperator;
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Method;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -55,6 +55,9 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
MatchOperator = matchOperator;
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.MultiPart;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -82,6 +82,9 @@ public class RequestMessageParamMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs);
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Param;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -72,6 +72,9 @@ public class RequestMessagePathMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs);
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Path;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -29,6 +29,9 @@ internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher
_executionConditionState = executionConditionState;
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.ScenarioAndState;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -72,6 +72,9 @@ public class RequestMessageUrlMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs);
}
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Url;
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
@@ -0,0 +1,282 @@
// Copyright © WireMock.Net
using System.Collections;
using System.Text.Json;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Util;
namespace WireMock.Matchers;
/// <summary>
/// SystemTextJsonMatcher - behaves the same as <see cref="JsonMatcher"/> but uses System.Text.Json instead of Newtonsoft.Json.
/// </summary>
public class SystemTextJsonMatcher : IJsonMatcher
{
private static readonly JsonSerializerOptions DefaultSerializerOptions = new()
{
PropertyNameCaseInsensitive = false
};
/// <inheritdoc />
public virtual string Name => nameof(SystemTextJsonMatcher);
/// <inheritdoc />
public object Value { get; }
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <inheritdoc cref="IIgnoreCaseMatcher.IgnoreCase"/>
public bool IgnoreCase { get; }
/// <summary>
/// Support Regex
/// </summary>
public bool Regex { get; }
private readonly JsonElement _valueAsJsonElement;
/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> class.
/// </summary>
/// <param name="value">The string value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param>
public SystemTextJsonMatcher(string value, bool ignoreCase = false, bool regex = false)
: this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> class.
/// </summary>
/// <param name="value">The object value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param>
public SystemTextJsonMatcher(object value, bool ignoreCase = false, bool regex = false)
: this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonMatcher"/> class.
/// </summary>
/// <param name="matchBehaviour">The match behaviour.</param>
/// <param name="value">The value to check for equality.</param>
/// <param name="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param>
public SystemTextJsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
{
Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
Regex = regex;
Value = value;
_valueAsJsonElement = ConvertToJsonElement(value);
}
/// <inheritdoc />
public MatchResult IsMatch(object? input)
{
var score = MatchScores.Mismatch;
Exception? error = null;
// When input is null or byte[], return Mismatch.
if (input != null && input is not byte[])
{
try
{
var inputAsJsonElement = ConvertToJsonElement(input);
var match = IsMatch(NormalizeElement(_valueAsJsonElement), NormalizeElement(inputAsJsonElement));
score = MatchScores.ToScore(match);
}
catch (Exception ex)
{
error = ex;
}
}
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
}
/// <inheritdoc />
public virtual string GetCSharpCodeArguments()
{
return $"new {Name}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
$")";
}
/// <summary>
/// Compares the input against the matcher value
/// </summary>
protected virtual bool IsMatch(JsonElement value, JsonElement? input)
{
if (input == null)
{
return false;
}
var inputElement = input.Value;
// If using Regex and the value is a string, use the MatchRegex method.
if (Regex && value.ValueKind == JsonValueKind.String)
{
var valueAsString = value.GetString()!;
var inputAsString = inputElement.ValueKind == JsonValueKind.String
? inputElement.GetString()!
: inputElement.GetRawText();
var (valid, result) = RegexUtils.MatchRegex(valueAsString, inputAsString);
if (valid)
{
return result;
}
}
// If the value is a Guid (string) and input is a string, or vice versa, compare as strings.
if (value.ValueKind == JsonValueKind.String && inputElement.ValueKind == JsonValueKind.String)
{
var valueStr = value.GetString()!;
var inputStr = inputElement.GetString()!;
if (Guid.TryParse(valueStr, out var valueGuid) && Guid.TryParse(inputStr, out var inputGuid))
{
return valueGuid == inputGuid;
}
}
switch (value.ValueKind)
{
case JsonValueKind.Object:
{
if (inputElement.ValueKind != JsonValueKind.Object)
{
return false;
}
var valueProperties = value.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
var inputProperties = inputElement.EnumerateObject().ToDictionary(p => p.Name, p => p.Value);
if (valueProperties.Count != inputProperties.Count)
{
return false;
}
foreach (var pair in valueProperties)
{
if (!inputProperties.TryGetValue(pair.Key, out var inputPropValue))
{
return false;
}
if (!IsMatch(pair.Value, inputPropValue))
{
return false;
}
}
return true;
}
case JsonValueKind.Array:
{
if (inputElement.ValueKind != JsonValueKind.Array)
{
return false;
}
var valueArray = value.EnumerateArray().ToArray();
var inputArray = inputElement.EnumerateArray().ToArray();
if (valueArray.Length != inputArray.Length)
{
return false;
}
return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any();
}
default:
return value.GetRawText() == inputElement.GetRawText();
}
}
private JsonElement NormalizeElement(JsonElement element)
{
if (!IgnoreCase)
{
return element;
}
var normalized = NormalizeValue(element);
return ConvertToJsonElement(normalized);
}
private object NormalizeValue(JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
{
var dict = new Dictionary<string, object?>();
foreach (var prop in element.EnumerateObject())
{
var normalizedKey = prop.Name.ToUpperInvariant();
dict[normalizedKey] = NormalizeValue(prop.Value);
}
return dict;
}
case JsonValueKind.Array:
{
if (Regex)
{
return element.EnumerateArray().Select(e => (object)e.GetRawText()).ToArray();
}
return element.EnumerateArray().Select(NormalizeValue).ToArray();
}
case JsonValueKind.String:
{
var str = element.GetString()!;
return Regex ? str : str.ToUpperInvariant();
}
default:
return element.GetRawText();
}
}
private static JsonElement ConvertToJsonElement(object value)
{
switch (value)
{
case JsonElement jsonElement:
return jsonElement;
case JsonDocument jsonDocument:
return jsonDocument.RootElement;
case string stringValue:
return JsonDocument.Parse(stringValue).RootElement;
case IEnumerable enumerableValue when value is not string:
return JsonSerializer.SerializeToElement(enumerableValue, DefaultSerializerOptions);
default:
var json = JsonSerializer.Serialize(value, DefaultSerializerOptions);
return JsonDocument.Parse(json).RootElement;
}
}
}
@@ -0,0 +1,52 @@
// Copyright © WireMock.Net
using WireMock.Extensions;
using WireMock.Util;
namespace WireMock.Matchers;
/// <summary>
/// SystemTextJsonPartialMatcher - uses System.Text.Json instead of Newtonsoft.Json.
/// </summary>
public class SystemTextJsonPartialMatcher : AbstractSystemTextJsonPartialMatcher
{
/// <inheritdoc />
public override string Name => nameof(SystemTextJsonPartialMatcher);
/// <inheritdoc />
public SystemTextJsonPartialMatcher(string value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <inheritdoc />
public SystemTextJsonPartialMatcher(object value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <inheritdoc />
public SystemTextJsonPartialMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, regex)
{
}
/// <inheritdoc />
protected override bool IsMatch(string value, string input)
{
var exactStringMatcher = new ExactMatcher(MatchBehaviour.AcceptOnMatch, IgnoreCase, MatchOperator.Or, value);
return exactStringMatcher.IsMatch(input).IsPerfect();
}
/// <inheritdoc />
public override string GetCSharpCodeArguments()
{
return $"new {Name}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
$")";
}
}
@@ -0,0 +1,52 @@
// Copyright © WireMock.Net
using WireMock.Extensions;
using WireMock.Util;
namespace WireMock.Matchers;
/// <summary>
/// SystemTextJsonPartialWildcardMatcher - uses System.Text.Json instead of Newtonsoft.Json.
/// </summary>
public class SystemTextJsonPartialWildcardMatcher : AbstractSystemTextJsonPartialMatcher
{
/// <inheritdoc />
public override string Name => nameof(SystemTextJsonPartialWildcardMatcher);
/// <inheritdoc />
public SystemTextJsonPartialWildcardMatcher(string value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <inheritdoc />
public SystemTextJsonPartialWildcardMatcher(object value, bool ignoreCase = false, bool regex = false)
: base(value, ignoreCase, regex)
{
}
/// <inheritdoc />
public SystemTextJsonPartialWildcardMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false)
: base(matchBehaviour, value, ignoreCase, regex)
{
}
/// <inheritdoc />
protected override bool IsMatch(string value, string input)
{
var wildcardStringMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, IgnoreCase);
return wildcardStringMatcher.IsMatch(input).IsPerfect();
}
/// <inheritdoc />
public override string GetCSharpCodeArguments()
{
return $"new {Name}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
$")";
}
}
@@ -72,6 +72,7 @@ internal partial class AspNetCoreSelfHost
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>();
services.AddSingleton<IDateTimeUtils, DateTimeUtils>();
services.AddSingleton<IResponseMessageBuilder, ResponseMessageBuilder>();
services.AddSingleton<LogEntryMapper>();
services.AddSingleton<IWireMockMiddlewareLogger, WireMockMiddlewareLogger>();
@@ -11,12 +11,14 @@ internal class GlobalExceptionMiddleware
{
private readonly IWireMockMiddlewareOptions _options;
private readonly IOwinResponseMapper _responseMapper;
private readonly IResponseMessageBuilder _responseMessageBuilder;
public GlobalExceptionMiddleware(RequestDelegate next, IWireMockMiddlewareOptions options, IOwinResponseMapper responseMapper)
public GlobalExceptionMiddleware(RequestDelegate next, IWireMockMiddlewareOptions options, IOwinResponseMapper responseMapper, IResponseMessageBuilder responseMessageBuilder)
{
Next = next;
_options = Guard.NotNull(options);
_responseMapper = Guard.NotNull(responseMapper);
_responseMessageBuilder = Guard.NotNull(responseMessageBuilder);
}
public RequestDelegate Next { get; }
@@ -35,7 +37,7 @@ internal class GlobalExceptionMiddleware
catch (Exception ex)
{
_options.Logger.Error("HttpStatusCode set to 500 {0}", ex);
await _responseMapper.MapAsync(ResponseMessageBuilder.Create(500, JsonConvert.SerializeObject(ex)), ctx.Response).ConfigureAwait(false);
await _responseMapper.MapAsync(_responseMessageBuilder.Create(500, JsonConvert.SerializeObject(ex)), ctx.Response).ConfigureAwait(false);
}
}
}
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates;
using JsonConverter.Abstractions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using WireMock.Handlers;
@@ -99,4 +100,12 @@ internal interface IWireMockMiddlewareOptions
/// WebSocket settings.
/// </summary>
WebSocketSettings? WebSocketSettings { get; set; }
/// <summary>
/// Gets or sets the default JSON converter used for serialization.
/// </summary>
/// <remarks>
/// Set this property to customize how objects are serialized to and deserialized from JSON during mapping.
/// </remarks>
IJsonConverter DefaultJsonSerializer { get; set; }
}
@@ -53,7 +53,8 @@ internal class OwinRequestMapper : IOwinRequestMapper
ContentType = request.ContentType,
DeserializeJson = !options.DisableJsonBodyParsing.GetValueOrDefault(false),
ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false)
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false),
DefaultJsonConverter = options.DefaultJsonSerializer
};
body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
@@ -3,249 +3,242 @@
using System.Globalization;
using System.Net;
using System.Text;
using JsonConverter.Abstractions;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
using Stef.Validation;
using WireMock.Http;
using WireMock.ResponseBuilders;
using WireMock.ResponseProviders;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Owin.Mappers
namespace WireMock.Owin.Mappers;
/// <summary>
/// OwinResponseMapper
/// </summary>
internal class OwinResponseMapper(IWireMockMiddlewareOptions options) : IOwinResponseMapper
{
/// <summary>
/// OwinResponseMapper
/// </summary>
internal class OwinResponseMapper : IOwinResponseMapper
private readonly IRandomizerNumber<double> _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 });
private readonly IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 });
private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
private static readonly IDictionary<string, Action<HttpResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
new Dictionary<string, Action<HttpResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
{
{ HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() },
{ HttpKnownHeaderNames.ContentLength, (r, hasBody, v) =>
{
// Only set the Content-Length header if the response does not have a body
if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength))
{
r.ContentLength = contentLength;
}
}
}
};
/// <inheritdoc />
public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response)
{
private readonly IRandomizerNumber<double> _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 });
private readonly IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 });
private readonly IWireMockMiddlewareOptions _options;
private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
private static readonly IDictionary<string, Action<HttpResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
new Dictionary<string, Action<HttpResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
{
{ HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() },
{ HttpKnownHeaderNames.ContentLength, (r, hasBody, v) =>
{
// Only set the Content-Length header if the response does not have a body
if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength))
{
r.ContentLength = contentLength;
}
}
}
};
/// <summary>
/// Constructor
/// </summary>
/// <param name="options">The IWireMockMiddlewareOptions.</param>
public OwinResponseMapper(IWireMockMiddlewareOptions options)
if (responseMessage == null || responseMessage is WebSocketHandledResponse)
{
_options = Guard.NotNull(options);
return;
}
/// <inheritdoc />
public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response)
var bodyData = responseMessage.BodyData;
if (bodyData?.GetDetectedBodyType() == BodyType.SseString)
{
if (responseMessage == null || responseMessage is WebSocketHandledResponse)
{
return;
}
await HandleSseStringAsync(responseMessage, response, bodyData);
return;
}
var bodyData = responseMessage.BodyData;
if (bodyData?.GetDetectedBodyType() == BodyType.SseString)
{
await HandleSseStringAsync(responseMessage, response, bodyData);
return;
}
byte[]? bytes;
switch (responseMessage.FaultType)
{
case FaultType.EMPTY_RESPONSE:
bytes = IsFault(responseMessage) ? [] : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
byte[]? bytes;
switch (responseMessage.FaultType)
{
case FaultType.EMPTY_RESPONSE:
bytes = IsFault(responseMessage) ? [] : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
case FaultType.MALFORMED_RESPONSE_CHUNK:
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? [];
if (IsFault(responseMessage))
{
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
}
break;
default:
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
}
if (responseMessage.StatusCode is HttpStatusCode or int)
{
response.StatusCode = MapStatusCode((int) responseMessage.StatusCode);
}
else if (responseMessage.StatusCode is string statusCodeAsString)
{
// Note: this case will also match on null
_ = int.TryParse(statusCodeAsString, out var statusCodeTypeAsInt);
response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
}
SetResponseHeaders(responseMessage, bytes != null, response);
if (bytes != null)
{
try
case FaultType.MALFORMED_RESPONSE_CHUNK:
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? [];
if (IsFault(responseMessage))
{
await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
}
catch (Exception ex)
break;
default:
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
}
if (responseMessage.StatusCode is HttpStatusCode or int)
{
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode);
}
else if (responseMessage.StatusCode is string statusCodeAsString)
{
// Note: this case will also match on null
_ = int.TryParse(statusCodeAsString, out var statusCodeTypeAsInt);
response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
}
SetResponseHeaders(responseMessage, bytes != null, response);
if (bytes != null)
{
try
{
await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
}
catch (Exception ex)
{
options.Logger.Warn("Error writing response body. Exception : {0}", ex);
}
}
SetResponseTrailingHeaders(responseMessage, response);
}
private static async Task HandleSseStringAsync(IResponseMessage responseMessage, HttpResponse response, IBodyData bodyData)
{
if (bodyData.SseStringQueue == null)
{
return;
}
SetResponseHeaders(responseMessage, true, response);
string? text;
do
{
if (bodyData.SseStringQueue.TryRead(out text))
{
await response.WriteAsync(text);
await response.Body.FlushAsync();
}
} while (text != null);
}
private int MapStatusCode(int code)
{
if (options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code))
{
return (int)HttpStatusCode.OK;
}
return code;
}
private bool IsFault(IResponseMessage responseMessage)
{
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
}
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
{
var bodyData = responseMessage.BodyData;
switch (bodyData?.GetDetectedBodyType())
{
case BodyType.String:
case BodyType.FormUrlEncoded:
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);
case BodyType.Json:
var jsonConverterOptions = new JsonConverterOptions
{
_options.Logger.Warn("Error writing response body. Exception : {0}", ex);
}
}
WriteIndented = bodyData.BodyAsJsonIndented == true,
IgnoreNullValues = true
};
SetResponseTrailingHeaders(responseMessage, response);
}
var jsonBody = options.DefaultJsonSerializer.Serialize(bodyData.BodyAsJson!, jsonConverterOptions);
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
private static async Task HandleSseStringAsync(IResponseMessage responseMessage, HttpResponse response, IBodyData bodyData)
{
if (bodyData.SseStringQueue == null)
{
return;
}
SetResponseHeaders(responseMessage, true, response);
string? text;
do
{
if (bodyData.SseStringQueue.TryRead(out text))
case BodyType.ProtoBuf:
if (TypeLoader.TryLoadStaticInstance<IProtoBufUtils>(out var protoBufUtils))
{
await response.WriteAsync(text);
await response.Body.FlushAsync();
var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts;
return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false);
}
} while (text != null);
break;
case BodyType.Bytes:
return bodyData.BodyAsBytes;
case BodyType.File:
return options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!);
case BodyType.MultiPart:
options.Logger.Warn("MultiPart body type is not handled!");
break;
case BodyType.None:
break;
}
private int MapStatusCode(int code)
return null;
}
private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, HttpResponse response)
{
// Force setting the Date header (#577)
AppendResponseHeader(
response,
HttpKnownHeaderNames.Date,
[DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)]
);
// Set other headers
foreach (var item in responseMessage.Headers!)
{
if (_options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code))
var headerName = item.Key;
var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{
return (int)HttpStatusCode.OK;
action.Invoke(response, hasBody, value);
}
return code;
}
private bool IsFault(IResponseMessage responseMessage)
{
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
}
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
{
var bodyData = responseMessage.BodyData;
switch (bodyData?.GetDetectedBodyType())
else
{
case BodyType.String:
case BodyType.FormUrlEncoded:
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);
case BodyType.Json:
var formatting = bodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None;
var jsonBody = JsonConvert.SerializeObject(bodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
case BodyType.ProtoBuf:
if (TypeLoader.TryLoadStaticInstance<IProtoBufUtils>(out var protoBufUtils))
{
var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts;
return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false);
}
break;
case BodyType.Bytes:
return bodyData.BodyAsBytes;
case BodyType.File:
return _options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!);
case BodyType.MultiPart:
_options.Logger.Warn("MultiPart body type is not handled!");
break;
case BodyType.None:
break;
}
return null;
}
private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, HttpResponse response)
{
// Force setting the Date header (#577)
AppendResponseHeader(
response,
HttpKnownHeaderNames.Date,
[DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)]
);
// Set other headers
foreach (var item in responseMessage.Headers!)
{
var headerName = item.Key;
var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
// Check if this response header can be added (#148, #227 and #720)
if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
{
action.Invoke(response, hasBody, value);
}
else
{
// Check if this response header can be added (#148, #227 and #720)
if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
{
AppendResponseHeader(response, headerName, value.ToArray());
}
AppendResponseHeader(response, headerName, value.ToArray());
}
}
}
private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, HttpResponse response)
{
if (responseMessage.TrailingHeaders == null)
{
return;
}
#if TRAILINGHEADERS
foreach (var (headerName, value) in responseMessage.TrailingHeaders)
{
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{
action.Invoke(response, false, value);
}
else
{
// Check if this trailing header can be added to the response
if (response.SupportsTrailers() && !HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
{
response.AppendTrailer(headerName, new Microsoft.Extensions.Primitives.StringValues(value.ToArray()));
}
}
}
#endif
}
private static void AppendResponseHeader(HttpResponse response, string headerName, string[] values)
{
response.Headers.Append(headerName, values);
}
}
private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, HttpResponse response)
{
if (responseMessage.TrailingHeaders == null)
{
return;
}
#if TRAILINGHEADERS
foreach (var (headerName, value) in responseMessage.TrailingHeaders)
{
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{
action.Invoke(response, false, value);
}
else
{
// Check if this trailing header can be added to the response
if (response.SupportsTrailers() && !HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
{
response.AppendTrailer(headerName, new Microsoft.Extensions.Primitives.StringValues(value.ToArray()));
}
}
}
#endif
}
private static void AppendResponseHeader(HttpResponse response, string headerName, string[] values)
{
response.Headers.Append(headerName, values);
}
}
@@ -19,6 +19,7 @@ internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDou
var possibleMappings = new List<MappingMatcherResult>();
var mappings = _options.Mappings.Values
.Where(m => !m.IsDisabled)
.Where(m => m.TimeSettings.IsValid())
.Where(m => m.Probability is null || _randomizerDoubleBetween0And1.Generate() <= m.Probability)
.ToArray();
@@ -26,7 +26,8 @@ internal class WireMockMiddleware(
IMappingMatcher mappingMatcher,
IWireMockMiddlewareLogger logger,
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils
IDateTimeUtils dateTimeUtils,
IResponseMessageBuilder responseMessageBuilder
)
{
private readonly object _lock = new();
@@ -97,7 +98,7 @@ internal class WireMockMiddleware(
{
logRequest = true;
options.Logger.Warn("HttpStatusCode set to 404 : No matching mapping found");
response = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
response = responseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
return;
}
@@ -109,7 +110,7 @@ internal class WireMockMiddleware(
if (!authorizationHeaderPresent)
{
options.Logger.Error("HttpStatusCode set to 401, authorization header is missing.");
response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
response = responseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
return;
}
@@ -117,11 +118,19 @@ internal class WireMockMiddleware(
if (!MatchScores.IsPerfect(authorizationHeaderMatchResult.Score))
{
options.Logger.Error("HttpStatusCode set to 401, authentication failed.", authorizationHeaderMatchResult.Exception ?? throw new WireMockException("Authentication failed"));
response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
response = responseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
return;
}
}
// Transition scenario state immediately after matching, before any delay (global or
// per-mapping) so that concurrent retries arriving during a delay period see the
// updated state and match the correct next mapping instead of re-matching this one.
if (targetMapping.Scenario != null)
{
UpdateScenarioState(targetMapping);
}
if (!targetMapping.IsAdminInterface && options.RequestProcessingDelay > TimeSpan.Zero)
{
await Task.Delay(options.RequestProcessingDelay.Value).ConfigureAwait(false);
@@ -147,11 +156,6 @@ internal class WireMockMiddleware(
}
}
if (targetMapping.Scenario != null)
{
UpdateScenarioState(targetMapping);
}
if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0)
{
await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false);
@@ -162,7 +166,7 @@ internal class WireMockMiddleware(
options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
WireMockActivitySource.RecordException(activity, ex);
response = ResponseMessageBuilder.Create(500, ex.Message);
response = responseMessageBuilder.Create(500, ex.Message);
}
finally
{
@@ -176,7 +180,7 @@ internal class WireMockMiddleware(
{
options.Logger.Error("HttpStatusCode set to 404 : No matching mapping found", ex);
var notFoundResponse = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
var notFoundResponse = responseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound);
await responseMapper.MapAsync(notFoundResponse, ctx.Response).ConfigureAwait(false);
}
}
@@ -1,7 +1,6 @@
// Copyright © WireMock.Net
using System.Diagnostics;
using System.Linq;
using WireMock.Logging;
using WireMock.Owin.ActivityTracing;
using WireMock.Serialization;
@@ -42,7 +41,7 @@ internal class WireMockMiddlewareLogger(
if (_options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true })
{
var filename = $"{logEntry.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(logEntry));
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, _options.DefaultJsonSerializer.Serialize(logEntry));
}
}
catch
@@ -2,6 +2,8 @@
using System.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using WireMock.Handlers;
@@ -108,5 +110,9 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc />
public ConcurrentDictionary<Guid, WebSocketConnectionRegistry> WebSocketRegistries { get; } = new();
/// <inheritdoc />
public WebSocketSettings? WebSocketSettings { get; set; }
/// <inheritdoc />
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
}
@@ -7,4 +7,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
// Needed for Moq in the UnitTest project
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
@@ -48,7 +48,8 @@ internal class ProxyHelper(WireMockServerSettings settings)
originalUri,
deserializeJson,
decompressGzipAndDeflate,
deserializeFormUrlEncoded
deserializeFormUrlEncoded,
_settings.DefaultJsonSerializer
).ConfigureAwait(false);
IMapping? newMapping = null;
@@ -34,7 +34,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
/// Initializes a new instance of the <see cref="Request"/> class.
/// </summary>
/// <param name="requestMatchers">The request matchers.</param>
private Request(IList<IRequestMatcher> requestMatchers) : base(requestMatchers)
internal Request(IList<IRequestMatcher> requestMatchers) : base(requestMatchers)
{
_requestMatchers = Guard.NotNull(requestMatchers);
}
@@ -81,6 +81,13 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
return this;
}
/// <inheritdoc />
public IRequestBuilder WithEarlyMismatch(RequestMatcherType? earlyMatcherType)
{
EarlyMatcherType = earlyMatcherType;
return this;
}
internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher)
{
protoBufMatcher = GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
@@ -2,8 +2,10 @@
using System.Text;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using Stef.Validation;
using WireMock.Models;
using WireMock.Serialization;
using WireMock.Types;
using WireMock.Util;
@@ -119,11 +121,13 @@ public partial class Response
}
/// <inheritdoc />
public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null)
public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null, IJsonConverter? jsonConverter = null, JsonConverterOptions? options = null)
{
Guard.NotNull(body);
encoding ??= Encoding.UTF8;
jsonConverter ??= new NewtonsoftJsonConverter();
options ??= JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone;
ResponseMessage.BodyDestination = destination;
ResponseMessage.BodyData = new BodyData
@@ -140,7 +144,7 @@ public partial class Response
case BodyDestinationFormat.Json:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Json;
ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body);
ResponseMessage.BodyData.BodyAsJson = jsonConverter.Deserialize<object>(body, options);
break;
default:
@@ -197,7 +197,7 @@ public partial class Response : IResponseBuilder
if (ProxyAndRecordSettings != null && _httpClientForProxy != null)
{
string RemoveFirstOccurrence(string source, string find)
static string RemoveFirstOccurrence(string source, string find)
{
int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase);
return place >= 0 ? source.Remove(place, find.Length) : source;
@@ -265,7 +265,7 @@ public partial class Response : IResponseBuilder
var decoded = await protoBufMatcher.DecodeAsync(requestMessage.BodyData?.BodyAsBytes).ConfigureAwait(false);
if (decoded != null)
{
requestMessageImplementation.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded);
requestMessageImplementation.BodyAsJson = settings.DefaultJsonSerializer.ToJsonToken(decoded);
}
}
}
@@ -9,29 +9,30 @@ using WireMock.Util;
namespace WireMock;
internal static class ResponseMessageBuilder
internal class ResponseMessageBuilder(IDateTimeUtils dateTimeUtils) : IResponseMessageBuilder
{
private static readonly IDictionary<string, WireMockList<string>> ContentTypeJsonHeaders = new Dictionary<string, WireMockList<string>>
{
{ HttpKnownHeaderNames.ContentType, new WireMockList<string> { WireMockConstants.ContentTypeJson } }
};
internal static ResponseMessage Create(HttpStatusCode statusCode, string? status, Guid? guid = null)
public ResponseMessage Create(HttpStatusCode statusCode, string? status, Guid? guid = null)
{
return Create((int)statusCode, status, guid);
return Create((int)statusCode, status, null, guid);
}
internal static ResponseMessage Create(int statusCode, string? status, Guid? guid = null)
public ResponseMessage Create(int statusCode, string? status, Guid? guid = null)
{
return Create(statusCode, status, null, guid);
}
internal static ResponseMessage Create(int statusCode, string? status, string? error, Guid? guid = null)
public ResponseMessage Create(int statusCode, string? status, string? error, Guid? guid = null)
{
var response = new ResponseMessage
{
StatusCode = statusCode,
Headers = ContentTypeJsonHeaders
Headers = ContentTypeJsonHeaders,
DateTime = dateTimeUtils.UtcNow
};
if (status != null || error != null)
@@ -51,7 +52,7 @@ internal static class ResponseMessageBuilder
return response;
}
internal static ResponseMessage Create(HttpStatusCode statusCode)
public ResponseMessage Create(HttpStatusCode statusCode)
{
return new ResponseMessage
{
@@ -16,7 +16,7 @@ using WireMock.WebSockets;
namespace WireMock.ResponseProviders;
internal class WebSocketResponseProvider(WebSocketBuilder builder, IGuidUtils guidUtils, IDateTimeUtils dateTimeUtils) : IResponseProvider
internal class WebSocketResponseProvider(WebSocketBuilder builder, IGuidUtils guidUtils, IDateTimeUtils dateTimeUtils, IResponseMessageBuilder responseMessageBuilder) : IResponseProvider
{
public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(
IMapping mapping,
@@ -27,7 +27,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder, IGuidUtils gu
// Check if this is a WebSocket upgrade request
if (!context.WebSockets.IsWebSocketRequest)
{
return (ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Bad Request: Not a WebSocket upgrade request"), null);
return (responseMessageBuilder.Create(HttpStatusCode.BadRequest, "Bad Request: Not a WebSocket upgrade request"), null);
}
if (!context.Items.TryGetValue<IWireMockMiddlewareOptions>(nameof(IWireMockMiddlewareOptions), out var options))
@@ -110,7 +110,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder, IGuidUtils gu
// If we haven't upgraded yet, we can return HTTP error
if (!context.Response.HasStarted)
{
return (ResponseMessageBuilder.Create(HttpStatusCode.InternalServerError, $"WebSocket error: {ex.Message}"), null);
return (responseMessageBuilder.Create(HttpStatusCode.InternalServerError, $"WebSocket error: {ex.Message}"), null);
}
// Already upgraded - return marker
@@ -66,6 +66,12 @@ internal class MappingConverter(MatcherMapper mapper)
// Request
sb.AppendLine(" .Given(Request.Create()");
if (request.EarlyMatcherType != null)
{
sb.AppendLine($" .WithEarlyMismatch({request.EarlyMatcherType.Value.GetFullyQualifiedEnumValue()})");
}
sb.AppendLine($" .UsingMethod({To1Or2Or3Arguments(methodMatcher?.MatchBehaviour, methodMatcher?.MatchOperator, methodMatcher?.Methods, HttpRequestMethod.GET)})");
if (pathMatcher?.Matchers != null)
@@ -275,6 +281,7 @@ internal class MappingConverter(MatcherMapper mapper)
TimesInSameState = !string.IsNullOrWhiteSpace(mapping.NextState) ? mapping.TimesInSameState : null,
Data = mapping.Data,
Probability = mapping.Probability,
IsDisabled = mapping.IsDisabled ? true : null,
Request = new RequestModel
{
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
@@ -299,7 +306,9 @@ internal class MappingConverter(MatcherMapper mapper)
IgnoreCase = pm.IgnoreCase ? true : null,
RejectOnMatch = pm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(pm.Matchers)
}).ToList() : null
}).ToList() : null,
EarlyMatcherType = request.EarlyMatcherType
},
Response = new ResponseModel()
};
@@ -1,10 +1,7 @@
// Copyright © WireMock.Net
using JsonConverter.Abstractions;
using Newtonsoft.Json.Linq;
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
using System.Text.Json;
#endif
using JsonConverter.Abstractions.Models;
namespace WireMock.Serialization;
@@ -12,37 +9,23 @@ internal class MappingSerializer(IJsonConverter jsonConverter)
{
internal T[] DeserializeJsonToArray<T>(string value)
{
return DeserializeObjectToArray<T>(jsonConverter.Deserialize<object>(value)!);
switch (JsonTypeHelper.GetJsonType(value))
{
case JsonType.Array:
return jsonConverter.Deserialize<T[]>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone)!;
case JsonType.Object:
var singleResult = jsonConverter.Deserialize<T>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
return [singleResult!];
default:
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
}
}
internal static T[] DeserializeObjectToArray<T>(object value)
internal T[] DeserializeObjectToArray<T>(object value)
{
if (value is JArray jArray)
{
return jArray.ToObject<T[]>()!;
}
if (value is JObject jObject)
{
var singleResult = jObject.ToObject<T>();
return [singleResult!];
}
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
if (value is JsonElement jElement)
{
if (jElement.ValueKind == JsonValueKind.Array)
{
return jElement.Deserialize<T[]>()!;
}
if (jElement.ValueKind == JsonValueKind.Object)
{
var singleResult = jElement.Deserialize<T>();
return [singleResult!];
}
}
#endif
throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
var json = jsonConverter.Serialize(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
return DeserializeJsonToArray<T>(json);
}
}
@@ -106,9 +106,29 @@ internal class MatcherMapper
var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex);
case nameof(SystemTextJsonMatcher):
var valueForSystemTextJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new SystemTextJsonMatcher(matchBehaviour, valueForSystemTextJsonMatcher!, ignoreCase, useRegex);
case nameof(SystemTextJsonPartialMatcher):
var valueForSystemTextJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new SystemTextJsonPartialMatcher(matchBehaviour, valueForSystemTextJsonPartialMatcher!, ignoreCase, useRegex);
case nameof(SystemTextJsonPartialWildcardMatcher):
var valueForSystemTextJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new SystemTextJsonPartialWildcardMatcher(matchBehaviour, valueForSystemTextJsonPartialWildcardMatcher!, ignoreCase, useRegex);
case nameof(JsonPathMatcher):
return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns);
case "SystemTextJsonPathMatcher":
if (TypeLoader.TryLoadNewInstance<ISystemTextJsonPathMatcher>(out var systemTextJsonPathMatcher, matchBehaviour, matchOperator, stringPatterns))
{
return systemTextJsonPathMatcher;
}
throw new InvalidOperationException("The 'SystemTextJsonPathMatcher' cannot be loaded. Please install the WireMock.Net.Matchers.SystemTextJsonPath package.");
case nameof(JmesPathMatcher):
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
@@ -171,6 +191,10 @@ internal class MatcherMapper
model.Regex = jsonMatcher.Regex;
break;
case SystemTextJsonMatcher stjMatcher:
model.Regex = stjMatcher.Regex;
break;
case XPathMatcher xpathMatcher:
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
break;
@@ -1,6 +1,8 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using WireMock.Admin.Mappings;
using WireMock.Extensions;
using WireMock.Pact.Models.V2;
@@ -49,7 +51,7 @@ internal static class PactMapper
pact.Interactions.Add(interaction);
}
return (filename, JsonUtils.SerializeAsPactFile(pact));
return (filename, SerializeAsPactFile(pact));
}
private static PactRequest MapRequest(RequestModel request, string path)
@@ -152,7 +154,7 @@ internal static class PactMapper
/// </summary>
private static object? TryDeserializeJsonStringAsObject(string? value)
{
return value != null ? JsonUtils.TryDeserializeObject<object?>(value) ?? value : null;
return value != null ? TryDeserializeObject<object?>(value) ?? value : null;
}
//private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
@@ -164,4 +166,22 @@ internal static class PactMapper
// return defaultValue;
//}
private static byte[] SerializeAsPactFile(object value)
{
var json = JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsPact);
return Encoding.UTF8.GetBytes(json);
}
private static T? TryDeserializeObject<T>(string json)
{
try
{
return JsonConvert.DeserializeObject<T>(json);
}
catch
{
return default;
}
}
}
@@ -1,7 +1,8 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using NJsonSchema.Extensions;
using NSwag;
@@ -281,7 +282,7 @@ internal static class SwaggerMapper
if (matcher is { Name: nameof(JsonMatcher) })
{
var pattern = GetPatternAsStringFromMatcher(matcher);
if (JsonUtils.TryParseAsJObject(pattern, out var jObject))
if (TryParseAsJObject(pattern, out var jObject))
{
return jObject;
}
@@ -292,6 +293,39 @@ internal static class SwaggerMapper
return null;
}
private static bool IsJson(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
value = value!.Trim();
return (value.StartsWith("{") && value.EndsWith("}")) || (value.StartsWith("[") && value.EndsWith("]"));
}
private static bool TryParseAsJObject(string? strInput, [NotNullWhen(true)] out JObject? value)
{
value = null;
if (!IsJson(strInput))
{
return false;
}
try
{
// Try to convert this string into a JObject
value = JObject.Parse(strInput!);
return true;
}
catch
{
return false;
}
}
private static string GetContentType(RequestModel request)
{
var contentType = request.Headers?.FirstOrDefault(h => h.Name == "Content-Type");
@@ -234,6 +234,13 @@ public interface IRespondWithAProvider
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithProbability(double probability);
/// <summary>
/// Define whether this mapping is disabled. Defaults to <c>false</c>.
/// </summary>
/// <param name="isDisabled">Whether this mapping is disabled.</param>
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithIsDisabled(bool isDisabled);
/// <summary>
/// Define a Grpc ProtoDefinition which is used for the request and the response.
/// This can be a ProtoDefinition as a string, or an id when the ProtoDefinitions are defined at the WireMockServer.
@@ -24,6 +24,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
private readonly WireMockServerSettings _settings;
private readonly IDateTimeUtils _dateTimeUtils;
private readonly IGuidUtils _guidUtils;
private readonly IResponseMessageBuilder _responseMessageBuilder;
private readonly bool _saveToFile;
@@ -37,6 +38,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
private int _timesInSameState = 1;
private bool? _useWebhookFireAndForget;
private double? _probability;
private bool _isDisabled = false;
private GraphQLSchemaDetails? _graphQLSchemaDetails; // Future Use.
public Guid Guid { get; private set; }
@@ -55,6 +57,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
WireMockServerSettings settings,
IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils,
IResponseMessageBuilder responseMessageBuilder,
bool saveToFile = false
)
{
@@ -63,6 +66,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
_settings = Guard.NotNull(settings);
_dateTimeUtils = Guard.NotNull(dateTimeUtils);
_guidUtils = Guard.NotNull(guidUtils);
_responseMessageBuilder = Guard.NotNull(responseMessageBuilder);
_saveToFile = saveToFile;
@@ -78,7 +82,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
provider = new WebSocketResponseProvider(
response.WebSocketBuilder,
_guidUtils,
_dateTimeUtils
_dateTimeUtils,
_responseMessageBuilder
);
}
@@ -108,6 +113,11 @@ internal class RespondWithAProvider : IRespondWithAProvider
mapping.WithProbability(_probability.Value);
}
if (_isDisabled)
{
mapping.IsDisabled = true;
}
if (ProtoDefinition != null)
{
mapping.WithProtoDefinition(ProtoDefinition.Value);
@@ -354,6 +364,13 @@ internal class RespondWithAProvider : IRespondWithAProvider
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithIsDisabled(bool isDisabled)
{
_isDisabled = isDisabled;
return this;
}
/// <inheritdoc />
public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId)
{
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Net;
using System.Text;
using JetBrains.Annotations;
@@ -26,9 +25,6 @@ using WireMock.Util;
namespace WireMock.Server;
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class WireMockServer
{
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
@@ -61,6 +57,8 @@ public partial class WireMockServer
public string OpenApi => $"{_prefix}/openapi";
public RegexMatcher MappingsGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher MappingsGuidEnablePathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\\/enable$");
public RegexMatcher MappingsGuidDisablePathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\\/disable$");
public RegexMatcher MappingsCodeGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/code\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher RequestsGuidPathMatcher => new($"^{_prefixEscaped}\\/requests\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher ScenariosNameMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+$");
@@ -104,6 +102,12 @@ public partial class WireMockServer
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
// __admin/mappings/{guid}/enable
Given(Request.Create().WithPath(_adminPaths.MappingsGuidEnablePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingEnable));
// __admin/mappings/{guid}/disable
Given(Request.Create().WithPath(_adminPaths.MappingsGuidDisablePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDisable));
// __admin/mappings/code/{guid}
Given(Request.Create().WithPath(_adminPaths.MappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet));
@@ -342,11 +346,11 @@ public partial class WireMockServer
}
o.CorsPolicyOptions = corsPolicyOptions;
o.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode) _settings.ClientCertificateMode;
o.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode)_settings.ClientCertificateMode;
o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
});
return ResponseMessageBuilder.Create(200, "Settings updated");
return _responseMessageBuilder.Create(200, "Settings updated");
}
#endregion Settings
@@ -357,7 +361,7 @@ public partial class WireMockServer
if (mapping == null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
var model = _mappingConverter.ToMappingModel(mapping);
@@ -373,14 +377,14 @@ public partial class WireMockServer
if (code is null)
{
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
return ToResponseMessage(code);
}
_settings.Logger.Warn("HttpStatusCode set to 400");
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "GUID is missing");
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest, "GUID is missing");
}
private static TEnum GetEnumFromQuery<TEnum>(IRequestMessage requestMessage, TEnum defaultValue)
@@ -407,22 +411,22 @@ public partial class WireMockServer
var mappingModel = DeserializeObject<MappingModel>(requestMessage);
var guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping added or updated", guidFromPut);
return _responseMessageBuilder.Create(HttpStatusCode.OK, "Mapping added or updated", guidFromPut);
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
private IResponseMessage MappingDelete(HttpContext _, IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid))
{
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping removed", guid);
return _responseMessageBuilder.Create(HttpStatusCode.OK, "Mapping removed", guid);
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
private static bool TryParseGuidFromRequestMessage(IRequestMessage requestMessage, out Guid guid)
@@ -430,6 +434,47 @@ public partial class WireMockServer
var lastPart = requestMessage.Path.Split('/').LastOrDefault();
return Guid.TryParse(lastPart, out guid);
}
private static bool TryParseGuidFromSecondToLastSegment(IRequestMessage requestMessage, out Guid guid)
{
var parts = requestMessage.Path.Split('/');
if (parts.Length >= 2 && Guid.TryParse(parts[parts.Length - 2], out guid))
return true;
guid = Guid.Empty;
return false;
}
private IResponseMessage MappingEnable(HttpContext _, IRequestMessage requestMessage)
{
if (TryParseGuidFromSecondToLastSegment(requestMessage, out var guid))
{
var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid);
if (mapping != null)
{
mapping.IsDisabled = false;
return _responseMessageBuilder.Create(HttpStatusCode.OK, "Mapping enabled", guid);
}
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
private IResponseMessage MappingDisable(HttpContext _, IRequestMessage requestMessage)
{
if (TryParseGuidFromSecondToLastSegment(requestMessage, out var guid))
{
var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid);
if (mapping != null)
{
mapping.IsDisabled = true;
return _responseMessageBuilder.Create(HttpStatusCode.OK, "Mapping disabled", guid);
}
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found");
}
#endregion Mapping/{guid}
#region Mappings
@@ -451,7 +496,7 @@ public partial class WireMockServer
{
SaveStaticMappings();
return ResponseMessageBuilder.Create(200, "Mappings saved to disk");
return _responseMessageBuilder.Create(200, "Mappings saved to disk");
}
private MappingModel[] ToMappingModels()
@@ -481,22 +526,22 @@ public partial class WireMockServer
if (mappingModels.Length == 1)
{
var guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create(201, "Mapping added", guid);
return _responseMessageBuilder.Create(201, "Mapping added", guid);
}
ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create(201, "Mappings added");
return _responseMessageBuilder.Create(201, "Mappings added");
}
catch (ArgumentException a)
{
_settings.Logger.Error("HttpStatusCode set to 400 {0}", a);
return ResponseMessageBuilder.Create(400, a.Message);
return _responseMessageBuilder.Create(400, a.Message);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to 500 {0}", e);
return ResponseMessageBuilder.Create(500, e.ToString());
return _responseMessageBuilder.Create(500, e.ToString());
}
}
@@ -507,18 +552,18 @@ public partial class WireMockServer
var deletedGuids = MappingsDeleteMappingFromBody(requestMessage);
if (deletedGuids != null)
{
return ResponseMessageBuilder.Create(200, $"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]");
return _responseMessageBuilder.Create(200, $"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]");
}
// return bad request
return ResponseMessageBuilder.Create(400, "Poorly formed mapping JSON.");
return _responseMessageBuilder.Create(400, "Poorly formed mapping JSON.");
}
ResetMappings();
ResetScenarios();
return ResponseMessageBuilder.Create(200, "Mappings deleted");
return _responseMessageBuilder.Create(200, "Mappings deleted");
}
private IEnumerable<Guid>? MappingsDeleteMappingFromBody(IRequestMessage requestMessage)
@@ -570,14 +615,14 @@ public partial class WireMockServer
message += " and static mappings reloaded";
}
return ResponseMessageBuilder.Create(200, message);
return _responseMessageBuilder.Create(200, message);
}
private IResponseMessage ReloadStaticMappings(HttpContext _, IRequestMessage __)
{
ReadStaticMappings();
return ResponseMessageBuilder.Create(200, "Static Mappings reloaded");
return _responseMessageBuilder.Create(200, "Static Mappings reloaded");
}
#endregion Mappings
@@ -595,18 +640,18 @@ public partial class WireMockServer
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
}
private IResponseMessage RequestDelete(HttpContext _, IRequestMessage requestMessage)
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid))
{
return ResponseMessageBuilder.Create(200, "Request removed");
return _responseMessageBuilder.Create(200, "Request removed");
}
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found");
}
#endregion Request/{guid}
@@ -625,7 +670,7 @@ public partial class WireMockServer
{
ResetLogEntries();
return ResponseMessageBuilder.Create(200, "Requests deleted");
return _responseMessageBuilder.Create(200, "Requests deleted");
}
#endregion Requests
@@ -665,7 +710,7 @@ public partial class WireMockServer
return ToJson(result);
}
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest);
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest);
}
#endregion Requests/find
@@ -688,7 +733,7 @@ public partial class WireMockServer
{
ResetScenarios();
return ResponseMessageBuilder.Create(200, "Scenarios reset");
return _responseMessageBuilder.Create(200, "Scenarios reset");
}
private IResponseMessage ScenarioReset(HttpContext _, IRequestMessage requestMessage)
@@ -698,8 +743,8 @@ public partial class WireMockServer
Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First();
return ResetScenario(name) ?
ResponseMessageBuilder.Create(200, "Scenario reset") :
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
_responseMessageBuilder.Create(200, "Scenario reset") :
_responseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
}
private IResponseMessage ScenariosSetState(HttpContext _, IRequestMessage requestMessage)
@@ -707,14 +752,14 @@ public partial class WireMockServer
var name = Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First();
if (!_options.ScenarioStateStore.ContainsKey(name))
{
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
_responseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
}
var update = DeserializeObject<ScenarioStateUpdateModel>(requestMessage);
return SetScenarioState(name, update.State) ?
ResponseMessageBuilder.Create(200, $"Scenario state set to '{update.State}'") :
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
_responseMessageBuilder.Create(200, $"Scenario state set to '{update.State}'") :
_responseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
}
#endregion
@@ -838,6 +883,18 @@ public partial class WireMockServer
};
}
private T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
{
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
{
var bodyAsJson = requestMessage.BodyData.BodyAsJson!;
return _mappingSerializer.DeserializeObjectToArray<T>(bodyAsJson);
}
throw new NotSupportedException();
}
private static Encoding? ToEncoding(EncodingModel? encodingModel)
{
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
@@ -857,28 +914,16 @@ public partial class WireMockServer
};
}
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
{
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null)
{
var bodyAsJson = requestMessage.BodyData.BodyAsJson!;
return MappingSerializer.DeserializeObjectToArray<T>(bodyAsJson);
}
throw new NotSupportedException();
}
private static T DeserializeObject<T>(IRequestMessage requestMessage)
private T DeserializeObject<T>(IRequestMessage requestMessage)
{
switch (requestMessage.BodyData?.DetectedBodyType)
{
case BodyType.String:
case BodyType.FormUrlEncoded:
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString!);
case BodyType.String when requestMessage.BodyData?.BodyAsString != null:
case BodyType.FormUrlEncoded when requestMessage.BodyData?.BodyAsString != null:
return _settings.DefaultJsonSerializer.Deserialize<T>(requestMessage.BodyData.BodyAsString)!;
case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null:
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!;
return _settings.DefaultJsonSerializer.ParseJsonToken<T>(requestMessage.BodyData.BodyAsJson)!;
default:
throw new NotSupportedException();
@@ -18,14 +18,14 @@ public partial class WireMockServer
{
if (requestMessage.Body is null)
{
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
}
var id = requestMessage.Path.Split('/').Last();
AddProtoDefinition(id, requestMessage.Body);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "ProtoDefinition added");
return _responseMessageBuilder.Create(HttpStatusCode.OK, "ProtoDefinition added");
}
#endregion
@@ -34,7 +34,7 @@ public partial class WireMockServer
{
if (requestMessage.BodyAsBytes is null)
{
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
}
var filename = GetFileNameFromRequestMessage(requestMessage);
@@ -47,14 +47,14 @@ public partial class WireMockServer
_settings.FileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File created");
return _responseMessageBuilder.Create(HttpStatusCode.OK, "File created");
}
private IResponseMessage FilePut(HttpContext _, IRequestMessage requestMessage)
{
if (requestMessage.BodyAsBytes is null)
{
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest, "Body is null");
}
var filename = GetFileNameFromRequestMessage(requestMessage);
@@ -62,12 +62,12 @@ public partial class WireMockServer
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist, updating file will be skipped.", filename);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not found");
}
_settings.FileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File updated");
return _responseMessageBuilder.Create(HttpStatusCode.OK, "File updated");
}
private IResponseMessage FileGet(HttpContext _, IRequestMessage requestMessage)
@@ -77,7 +77,7 @@ public partial class WireMockServer
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not found");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not found");
}
var bytes = _settings.FileSystemHandler.ReadFile(filename);
@@ -112,10 +112,10 @@ public partial class WireMockServer
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound);
return _responseMessageBuilder.Create(HttpStatusCode.NotFound);
}
return ResponseMessageBuilder.Create(HttpStatusCode.NoContent);
return _responseMessageBuilder.Create(HttpStatusCode.NoContent);
}
private IResponseMessage FileDelete(HttpContext _, IRequestMessage requestMessage)
@@ -125,11 +125,11 @@ public partial class WireMockServer
if (!_settings.FileSystemHandler.FileExists(filename))
{
_settings.Logger.Info("The file '{0}' does not exist.", filename);
return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not deleted");
return _responseMessageBuilder.Create(HttpStatusCode.NotFound, "File is not deleted");
}
_settings.FileSystemHandler.DeleteFile(filename);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File deleted.");
return _responseMessageBuilder.Create(HttpStatusCode.OK, "File deleted.");
}
private string GetFileNameFromRequestMessage(IRequestMessage requestMessage)
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
@@ -120,6 +119,11 @@ public partial class WireMockServer
respondProvider.WithProbability(mappingModel.Probability.Value);
}
if (mappingModel.IsDisabled == true)
{
respondProvider.WithIsDisabled(true);
}
// ProtoDefinition is defined at Mapping level
if (mappingModel.ProtoDefinition != null)
{
@@ -148,7 +152,7 @@ public partial class WireMockServer
}
else
{
var clientIPModel = JsonUtils.ParseJTokenToObject<ClientIPModel>(requestModel.ClientIP);
var clientIPModel = _settings.DefaultJsonSerializer.ParseJsonToken<ClientIPModel>(requestModel.ClientIP);
if (clientIPModel.Matchers != null)
{
requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
@@ -164,7 +168,7 @@ public partial class WireMockServer
}
else
{
var pathModel = JsonUtils.ParseJTokenToObject<PathModel>(requestModel.Path);
var pathModel = _settings.DefaultJsonSerializer.ParseJsonToken<PathModel>(requestModel.Path);
if (pathModel.Matchers != null)
{
var matchOperator = StringUtils.ParseMatchOperator(pathModel.MatchOperator);
@@ -180,7 +184,7 @@ public partial class WireMockServer
}
else
{
var urlModel = JsonUtils.ParseJTokenToObject<UrlModel>(requestModel.Url);
var urlModel = _settings.DefaultJsonSerializer.ParseJsonToken<UrlModel>(requestModel.Url);
if (urlModel.Matchers != null)
{
var matchOperator = StringUtils.ParseMatchOperator(urlModel.MatchOperator);
@@ -263,10 +267,12 @@ public partial class WireMockServer
}
}
requestBuilder = requestBuilder.WithEarlyMismatch(requestModel.EarlyMatcherType);
return requestBuilder;
}
private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
private IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
{
var responseBuilder = Response.Create();
@@ -331,7 +337,7 @@ public partial class WireMockServer
}
else
{
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
var headers = _settings.DefaultJsonSerializer.ParseJsonToken<string[]>(entry.Value);
responseBuilder.WithHeader(entry.Key, headers);
}
}
@@ -357,7 +363,7 @@ public partial class WireMockServer
}
else
{
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value);
var headers = _settings.DefaultJsonSerializer.ParseJsonToken<string[]>(entry.Value);
responseBuilder.WithTrailingHeader(entry.Key, headers);
}
}
@@ -52,7 +52,7 @@ public partial class WireMockServer
if (mappingModels.Length == 1)
{
var guid = ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create(201, "Mapping added", guid);
return _responseMessageBuilder.Create(201, "Mapping added", guid);
}
foreach (var mappingModel in mappingModels)
@@ -60,17 +60,17 @@ public partial class WireMockServer
ConvertWireMockOrgMappingAndRegisterAsRespondProvider(mappingModel);
}
return ResponseMessageBuilder.Create(201, "Mappings added");
return _responseMessageBuilder.Create(201, "Mappings added");
}
catch (ArgumentException a)
{
_settings.Logger.Error("HttpStatusCode set to 400 {0}", a);
return ResponseMessageBuilder.Create(400, a.Message);
return _responseMessageBuilder.Create(400, a.Message);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to 500 {0}", e);
return ResponseMessageBuilder.Create(500, e.ToString());
return _responseMessageBuilder.Create(500, e.ToString());
}
}
@@ -19,7 +19,7 @@ public partial class WireMockServer
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
}
}
@@ -35,12 +35,12 @@ public partial class WireMockServer
ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create(HttpStatusCode.Created, "OpenApi document converted to Mappings");
return _responseMessageBuilder.Create(HttpStatusCode.Created, "OpenApi document converted to Mappings");
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
return _responseMessageBuilder.Create(HttpStatusCode.BadRequest, e.Message);
}
}
}
@@ -42,6 +42,7 @@ public partial class WireMockServer : IWireMockServer
private readonly MappingBuilder _mappingBuilder;
private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
private readonly IResponseMessageBuilder _responseMessageBuilder;
private readonly MappingSerializer _mappingSerializer;
/// <inheritdoc />
@@ -354,6 +355,8 @@ public partial class WireMockServer : IWireMockServer
{
_settings = Guard.NotNull(settings);
_responseMessageBuilder = new ResponseMessageBuilder(_dateTimeUtils);
_mappingSerializer = new MappingSerializer(settings.DefaultJsonSerializer ?? new NewtonsoftJsonConverter());
// Set default values if not provided
@@ -407,13 +410,15 @@ public partial class WireMockServer : IWireMockServer
_mappingConverter,
_mappingToFileSaver,
_guidUtils,
_dateTimeUtils
_dateTimeUtils,
_responseMessageBuilder
);
_options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration;
_options.CorsPolicyOptions = _settings.CorsPolicyOptions;
_options.ClientCertificateMode = (Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode)_settings.ClientCertificateMode;
_options.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
_options.DefaultJsonSerializer = _settings.DefaultJsonSerializer;
_httpServer = new AspNetCoreSelfHost(_options, urlOptions);
var startTask = _httpServer.StartAsync();
@@ -470,7 +475,7 @@ public partial class WireMockServer : IWireMockServer
Given(Request.Create().WithPath("/*").UsingAnyMethod())
.WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05"))
.AtPriority(1000)
.RespondWith(new DynamicResponseProvider((_, _) => ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound)));
.RespondWith(new DynamicResponseProvider((_, _) => _responseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound)));
}
/// <inheritdoc cref="IWireMockServer.Reset" />
@@ -3,12 +3,14 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using JetBrains.Annotations;
using JsonConverter.Newtonsoft.Json;
using JsonConverter.System.Text.Json;
using Stef.Validation;
using WireMock.Constants;
using WireMock.Logging;
using WireMock.Models;
using WireMock.Transformers;
using WireMock.Types;
using WireMock.Util;
@@ -57,11 +59,9 @@ public static class WireMockServerSettingsParser
DisableRequestBodyDecompressing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableRequestBodyDecompressing)),
DisableDeserializeFormUrlEncoded = parser.GetBoolValue(nameof(WireMockServerSettings.DisableDeserializeFormUrlEncoded)),
DoNotSaveDynamicResponseInLogEntry = parser.GetBoolValue(nameof(WireMockServerSettings.DoNotSaveDynamicResponseInLogEntry)),
GraphQLSchemas = parser.GetObjectValueFromJson<Dictionary<string, GraphQLSchemaDetails>>(nameof(settings.GraphQLSchemas)),
HandleRequestsSynchronously = parser.GetBoolValue(nameof(WireMockServerSettings.HandleRequestsSynchronously)),
HostingScheme = parser.GetEnumValue<HostingScheme>(nameof(WireMockServerSettings.HostingScheme)),
MaxRequestLogCount = parser.GetIntValue(nameof(WireMockServerSettings.MaxRequestLogCount)),
ProtoDefinitions = parser.GetObjectValueFromJson<Dictionary<string, string[]>>(nameof(settings.ProtoDefinitions)),
QueryParameterMultipleValueSupport = parser.GetEnumValue<QueryParameterMultipleValueSupport>(nameof(WireMockServerSettings.QueryParameterMultipleValueSupport)),
ReadStaticMappings = parser.GetBoolValue(nameof(WireMockServerSettings.ReadStaticMappings)),
RequestLogExpirationDuration = parser.GetIntValue(nameof(WireMockServerSettings.RequestLogExpirationDuration)),
@@ -80,6 +80,7 @@ public static class WireMockServerSettingsParser
settings.AcceptAnyClientCertificate = parser.GetBoolValue(nameof(WireMockServerSettings.AcceptAnyClientCertificate));
#endif
ParseJsonSerializerSettings(settings, parser);
ParseLoggerSettings(settings, logger, parser);
ParsePortSettings(settings, parser);
ParseProxyAndRecordSettings(settings, parser);
@@ -88,6 +89,9 @@ public static class WireMockServerSettingsParser
ParseActivityTracingSettings(settings, parser);
ParseWebSocketSettings(settings, parser);
settings.GraphQLSchemas = parser.GetObjectValueFromJson<Dictionary<string, GraphQLSchemaDetails>>(nameof(settings.GraphQLSchemas), settings.DefaultJsonSerializer);
settings.ProtoDefinitions = parser.GetObjectValueFromJson<Dictionary<string, string[]>>(nameof(settings.ProtoDefinitions), settings.DefaultJsonSerializer);
return true;
}
@@ -259,4 +263,21 @@ public static class WireMockServerSettingsParser
};
}
}
private static void ParseJsonSerializerSettings(WireMockServerSettings settings, SimpleSettingsParser parser)
{
var defaultJsonSerializer = parser.GetStringValue(nameof(WireMockServerSettings.DefaultJsonSerializer));
settings.DefaultJsonSerializer = defaultJsonSerializer switch
{
nameof(SystemTextJsonConverter) => new SystemTextJsonConverter(),
_ => new NewtonsoftJsonConverter(),
};
var defaultJsonBodyTransformer = parser.GetStringValue(nameof(WireMockServerSettings.DefaultJsonBodyTransformer));
settings.DefaultJsonBodyTransformer = defaultJsonBodyTransformer switch
{
nameof(SystemTextJsonBodyTransformer) => new SystemTextJsonBodyTransformer(settings),
_ => new NewtonsoftJsonBodyTransformer(settings),
};
}
}
@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using HandlebarsDotNet;
using WireMock.Transformers;
namespace WireMock.Transformers.Handlebars;
@@ -1,14 +0,0 @@
// Copyright © WireMock.Net
using WireMock.Handlers;
namespace WireMock.Transformers;
internal interface ITransformerContext
{
IFileSystemHandler FileSystemHandler { get; }
string ParseAndRender(string text, object model);
object? ParseAndEvaluate(string text, object model);
}
@@ -1,10 +1,5 @@
// Copyright © WireMock.Net
using System.Collections;
using System.Linq;
using HandlebarsDotNet.Helpers.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation;
using WireMock.Settings;
using WireMock.Types;
@@ -14,17 +9,13 @@ namespace WireMock.Transformers;
internal class Transformer : ITransformer
{
private readonly JsonSerializer _jsonSerializer;
private readonly IJsonBodyTransformer _jsonBodyTransformer;
private readonly ITransformerContextFactory _factory;
public Transformer(WireMockServerSettings settings, ITransformerContextFactory factory)
{
_factory = Guard.NotNull(factory);
_jsonSerializer = new JsonSerializer
{
Culture = Guard.NotNull(settings).Culture
};
_jsonBodyTransformer = Guard.NotNull(settings).DefaultJsonBodyTransformer;
}
public IBodyData? TransformBody(
@@ -121,13 +112,17 @@ internal class Transformer : ITransformer
});
}
private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
private BodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
{
switch (original.DetectedBodyType)
{
case BodyType.Json:
case BodyType.ProtoBuf:
return TransformBodyAsJson(transformerContext, options, model, original);
return _jsonBodyTransformer.TransformBodyAsJson(
transformerContext,
options,
model,
original);
case BodyType.File:
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
@@ -159,185 +154,7 @@ internal class Transformer : ITransformer
return newHeaders;
}
private IBodyData TransformBodyAsJson(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original)
{
JToken? jToken = null;
switch (original.BodyAsJson)
{
case JObject bodyAsJObject:
jToken = bodyAsJObject.DeepClone();
WalkNode(transformerContext, options, jToken, model);
break;
case JArray bodyAsJArray:
jToken = bodyAsJArray.DeepClone();
WalkNode(transformerContext, options, jToken, model);
break;
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
jToken = JArray.FromObject(bodyAsEnumerable, _jsonSerializer);
WalkNode(transformerContext, options, jToken, model);
break;
case string bodyAsString:
jToken = ReplaceSingleNode(transformerContext, options, bodyAsString, model);
break;
case not null:
jToken = JObject.FromObject(original.BodyAsJson, _jsonSerializer);
WalkNode(transformerContext, options, jToken, model);
break;
}
return new BodyData
{
Encoding = original.Encoding,
DetectedBodyType = original.DetectedBodyType,
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
ProtoDefinition = original.ProtoDefinition,
ProtoBufMessageType = original.ProtoBufMessageType,
BodyAsJson = jToken
};
}
private JToken ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model)
{
var transformedString = transformerContext.ParseAndRender(stringValue, model);
if (!string.Equals(stringValue, transformedString))
{
const string property = "_";
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
if (dummy[property] == null)
{
// TODO: check if just returning null is fine
return string.Empty;
}
JToken node = dummy[property]!;
ReplaceNodeValue(options, node, transformedString);
return dummy[property]!;
}
return stringValue;
}
private void WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JToken node, object model)
{
switch (node.Type)
{
case JTokenType.Object:
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (var child in node.Children<JProperty>().ToArray())
{
WalkNode(transformerContext, options, child.Value, model);
}
break;
case JTokenType.Array:
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
foreach (var child in node.Children().ToArray())
{
WalkNode(transformerContext, options, child, model);
}
break;
case JTokenType.String:
// In case of string, try to transform the value.
var stringValue = node.Value<string>();
if (string.IsNullOrEmpty(stringValue))
{
return;
}
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
if (!Equals(stringValue, transformed))
{
ReplaceNodeValue(options, node, transformed);
}
break;
}
}
// ReSharper disable once UnusedParameter.Local
private void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, object? transformedValue)
{
switch (transformedValue)
{
case JValue jValue:
node.Replace(jValue);
return;
case string transformedString:
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
if (isConvertedFromString)
{
node.Replace(JToken.FromObject(convertedValueFromString, _jsonSerializer));
}
else
{
node.Replace(ParseAsJObject(transformedString));
}
break;
case WireMockList<string> strings:
switch (strings.Count)
{
case 1:
node.Replace(ParseAsJObject(strings[0]));
return;
case > 1:
node.Replace(JToken.FromObject(strings.ToArray(), _jsonSerializer));
return;
}
break;
case { }:
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
if (isConverted)
{
node.Replace(JToken.FromObject(convertedValue, _jsonSerializer));
}
return;
default: // It's null, skip it. Maybe remove it ?
return;
}
}
private static JToken ParseAsJObject(string stringValue)
{
return JsonUtils.TryParseAsJObject(stringValue, out var parsedAsjObject) ? parsedAsjObject : stringValue;
}
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
{
var valueAsString = value as string;
if (options == ReplaceNodeOptions.Evaluate)
{
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
{
return (true, decoded);
}
return (false, value);
}
if (valueAsString != null)
{
return WrappedString.TryDecode(valueAsString, out var decoded) ?
(true, decoded) :
StringUtils.TryConvertToKnownType(valueAsString);
}
return (false, value);
}
private static IBodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original)
private static BodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original)
{
return new BodyData
{
@@ -348,7 +165,7 @@ internal class Transformer : ITransformer
};
}
private static IBodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
private static BodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
{
var transformedBodyAsFilename = transformerContext.ParseAndRender(original.BodyAsFile!, model);
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Minimal version from the lightweight Http Mocking Server for .NET</Description>
<AssemblyTitle>WireMock.Net.Minimal</AssemblyTitle>
@@ -43,7 +43,7 @@
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
<PackageReference Include="TinyMapper.Signed" Version="4.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.34.0" />
<PackageReference Include="Scriban.Signed" Version="7.0.6" />
<PackageReference Include="Scriban.Signed" Version="7.2.0" />
</ItemGroup>
<ItemGroup>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Some extensions for NUnit</Description>
<AssemblyTitle>WireMock.Net.NUnit</AssemblyTitle>
@@ -25,7 +25,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
</ItemGroup>
@@ -25,9 +25,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
</ItemGroup>
<ItemGroup>
@@ -32,15 +32,15 @@ public partial class WireMockAdminApiAssertions
}
[CustomAssertion]
public AndConstraint<WireMockAdminApiAssertions> WithBodyAsJson(object body, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAdminApiAssertions> WithBodyAsJson(object body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
{
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
}
[CustomAssertion]
public AndConstraint<WireMockAdminApiAssertions> WithBodyAsJson(string body, string because = "", params object[] becauseArgs)
public AndConstraint<WireMockAdminApiAssertions> WithBodyAsJson(string body, IJsonMatcher? jsonMatcher = null, string because = "", params object[] becauseArgs)
{
return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs);
return WithBodyAsJson(jsonMatcher ?? new JsonMatcher(body), because, becauseArgs);
}
[CustomAssertion]
@@ -127,15 +127,44 @@ public partial class WireMockAdminApiAssertions
private static string? FormatBody(object? body)
{
return body switch
if (body == null)
{
null => null,
string str => str,
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
byte[] bytes => $"byte[{bytes.Length}] {{...}}",
JToken jToken => jToken.ToString(Formatting.None),
_ => JToken.FromObject(body).ToString(Formatting.None)
};
return null;
}
if (body is string str)
{
return str;
}
if (body is AnyOf<string, StringPattern>[] stringPatterns)
{
return FormatBodies(stringPatterns.Select(p => p.GetPattern()));
}
if (body is byte[] bytes)
{
return $"byte[{bytes.Length}] {{...}}";
}
if (body is JToken jToken)
{
return jToken.ToString(Formatting.None);
}
// System.IO.FileNotFoundException : Could not load file or assembly 'System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.
var typeName = body.GetType().FullName;
if (typeName == "System.Text.Json.JsonElement")
{
return ((dynamic)body).GetRawText();
}
if (typeName == "System.Text.Json.JsonDocument")
{
return ((dynamic)body).RootElement.GetRawText();
}
return JToken.FromObject(body).ToString(Formatting.None);
}
private static string? FormatBodies(IEnumerable<object?> bodies)
@@ -163,6 +163,22 @@ public interface IWireMockAdminApi
[Header("Content-Type", "application/json")]
Task<StatusModel> PutMappingAsync([Path] Guid guid, [Body] MappingModel mapping, CancellationToken cancellationToken = default);
/// <summary>
/// Enable a mapping based on the guid.
/// </summary>
/// <param name="guid">The Guid.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Put("mappings/{guid}/enable")]
Task<StatusModel> EnableMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary>
/// Disable a mapping based on the guid.
/// </summary>
/// <param name="guid">The Guid.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Put("mappings/{guid}/disable")]
Task<StatusModel> DisableMappingAsync([Path] Guid guid, CancellationToken cancellationToken = default);
/// <summary>
/// Delete a mapping based on the guid
/// </summary>
@@ -33,7 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
<PackageReference Include="RestEase" Version="1.6.4" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
</ItemGroup>
+9 -1
View File
@@ -108,6 +108,14 @@ public interface IMapping
/// </value>
bool IsProxy { get; }
/// <summary>
/// Gets a value indicating whether this mapping is disabled.
/// </summary>
/// <value>
/// <c>true</c> if this mapping is disabled; otherwise, <c>false</c>.
/// </value>
bool IsDisabled { get; set; }
/// <summary>
/// Gets a value indicating whether this mapping to be logged.
/// </summary>
@@ -135,7 +143,7 @@ public interface IMapping
/// </summary>
object? Data { get; }
/// <summary>
/// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary>
double? Probability { get; }
@@ -0,0 +1,11 @@
// Copyright © WireMock.Net
namespace WireMock.Matchers;
/// <summary>
/// IJsonPathMatcher
/// <seealso cref="IStringMatcher"/> and <seealso cref="IObjectMatcher"/>.
/// </summary>
public interface IJsonPathMatcher : IStringMatcher, IObjectMatcher
{
}

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