Compare commits

...

37 Commits

Author SHA1 Message Date
Stef Heyenrath 5a1605751b Update JsonMatcher to support IgnoreArrayOrder 2026-05-29 20:42:52 +02:00
Stef Heyenrath 36b0a93a6c Add SystemTextJsonMatchers (#1447)
* SystemTextJsonMatcher

* ,

* .

* new projectx

* Update test/WireMock.Net.Tests/Pact/PactTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net/WireMock.Net.csproj

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/WireMock.Net.Minimal/Properties/AssemblyInfo.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* .

* more tests

* .

* .

* x

* ...

* .

* fix tests

* 0.11.0

* .

* delete jsonutils.cs

* s

* fix findings

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* 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>

* JsonConverter 0.12.0

* -- tools

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-05-29 20:19:51 +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 3aef0ad7a2 Update Scriban.Signed to latest (#1462) 2026-05-23 21:53:32 +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
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 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 4bb378bdce 2.5.0 2026-05-04 18:52:29 +02:00
Stef Heyenrath f9df0d0ee9 Fix name in RequestMessageHeaderMatcher 2026-05-03 09:41:25 +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 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 85d61a1877 2.3.0 2026-04-20 18:53:59 +02:00
Stef Heyenrath 885911203b Use DefaultJsonSerializer for BodyAsJson-Response (#1448) 2026-04-18 09:43:24 +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
Stef Heyenrath 3214c2ebc7 2.2.0 2026-03-30 19:51:09 +02:00
Stef Heyenrath 6c6a42979e Add comments for ScenarioStateStore related code (#1433) 2026-03-30 19:49:28 +02:00
Stef Heyenrath b4f5b9256c Upgrade Scriban.Signed (#1434) 2026-03-30 19:49:16 +02:00
Stef Heyenrath 070e4b6ab9 2.1.0 2026-03-29 13:16:23 +02:00
m4tchl0ck f919929cb7 Add injectable IScenarioStateStore for distributed scenario state (#1430)
* Move ScenarioState to Abstractions and add IScenarioStateStore interface

ScenarioState is moved to the Abstractions project so it can be referenced
by the new IScenarioStateStore interface. The interface defines the contract
for storing and retrieving scenario states, enabling distributed implementations.

* Add InMemoryScenarioStateStore default implementation

Wraps ConcurrentDictionary with OrdinalIgnoreCase comparer, preserving
exact current behavior. The Update method encapsulates read-modify-write
so distributed implementations can make it atomic.

* Wire IScenarioStateStore into middleware options, settings, and consumers

Replace direct ConcurrentDictionary<string, ScenarioState> usage with
IScenarioStateStore across all consumer files. The store is injectable
via WireMockServerSettings.ScenarioStateStore, defaulting to the
InMemoryScenarioStateStore for backward compatibility.

* Add FileBasedScenarioStateStore for persistent scenario state

In-memory ConcurrentDictionary backed by JSON file persistence in
__admin/scenarios/. Reads from cache, mutations write through to disk.
Constructor loads existing state from disk on startup.

* Make ScenarioStateStore non-nullable with default InMemoryScenarioStateStore

Move InMemoryScenarioStateStore from WireMock.Net.Minimal to
WireMock.Net.Shared so it lives alongside WireMockServerSettings.
This allows WireMockServerSettings.ScenarioStateStore to be
non-nullable with a default value, following the same pattern as
DefaultJsonSerializer. The null-coalescing fallback in
WireMockMiddlewareOptionsHelper is no longer needed.
2026-03-25 13:04:44 +01:00
Logan Dam cdd33695e5 Add helpers for query params fluent MappingModelBuilder (#1425)
* Add helpers for query params

* add example with query params

* add fluent helpers for WithHeaders and WithCookies

---------

Co-authored-by: Logan Dam <Logan.Dam@rabobank.com>
Co-authored-by: Stef Heyenrath <Stef.Heyenrath@gmail.com>
2026-03-14 10:39:30 +01:00
Stef Heyenrath c4caa25eb6 Fix WithWebSocketProxy_Should_Proxy_Binary_Messages 2026-03-14 10:39:04 +01:00
Stef Heyenrath ca788cb9b0 Add WireMockAspNetCoreLogger to log Kestrel warnings/errors (#1432)
* Add WireMockAspNetCoreLogger

* fix tests

* x

* .
2026-03-14 10:30:56 +01:00
Stef Heyenrath d08ce944b6 Fix WireMockLogger implementation in dotnet-WireMock (#1431) 2026-03-13 18:34:47 +01:00
Stef Heyenrath 0a9f37e857 readme 2026-03-11 18:14:09 +01:00
185 changed files with 7829 additions and 2477 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
# Copilot Instructions # Copilot Instructions
## Project Guidelines ## 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
+45 -2
View File
@@ -1,3 +1,46 @@
# 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)
# 2.1.0 (29 March 2026)
- [#1425](https://github.com/wiremock/WireMock.Net/pull/1425) - Add helpers for query params fluent MappingModelBuilder [feature] contributed by [biltongza](https://github.com/biltongza)
- [#1430](https://github.com/wiremock/WireMock.Net/pull/1430) - Add injectable IScenarioStateStore for distributed scenario state [feature] contributed by [m4tchl0ck](https://github.com/m4tchl0ck)
- [#1431](https://github.com/wiremock/WireMock.Net/pull/1431) - Fix WireMockLogger implementation in dotnet-WireMock [bug] contributed by [StefH](https://github.com/StefH)
- [#1432](https://github.com/wiremock/WireMock.Net/pull/1432) - Add WireMockAspNetCoreLogger to log Kestrel warnings/errors [feature] contributed by [StefH](https://github.com/StefH)
# 2.0.0 (11 March 2026) # 2.0.0 (11 March 2026)
- [#1359](https://github.com/wiremock/WireMock.Net/pull/1359) - Version 2.x contributed by [StefH](https://github.com/StefH) - [#1359](https://github.com/wiremock/WireMock.Net/pull/1359) - Version 2.x contributed by [StefH](https://github.com/StefH)
- [#1394](https://github.com/wiremock/WireMock.Net/pull/1394) - MappingSerializer (Newtonsoft or SystemText)-Json [feature] contributed by [StefH](https://github.com/StefH) - [#1394](https://github.com/wiremock/WireMock.Net/pull/1394) - MappingSerializer (Newtonsoft or SystemText)-Json [feature] contributed by [StefH](https://github.com/StefH)
@@ -1356,11 +1399,11 @@
- [#145](https://github.com/wiremock/WireMock.Net/pull/145) - Cancellation token not passed to server instance in .NET Core 2 [bug] contributed by [Bob11327](https://github.com/Bob11327) - [#145](https://github.com/wiremock/WireMock.Net/pull/145) - Cancellation token not passed to server instance in .NET Core 2 [bug] contributed by [Bob11327](https://github.com/Bob11327)
# 1.0.3.18 (25 May 2018) # 1.0.3.18 (25 May 2018)
- [#142](https://github.com/wiremock/WireMock.Net/pull/142) - Allow all headers to be set as Response headers contributed by [StefH](https://github.com/StefH) - [#142](https://github.com/wiremock/WireMock.Net/pull/142) - Allow all headers to be set as Response headers [bug] contributed by [StefH](https://github.com/StefH)
- [#122](https://github.com/wiremock/WireMock.Net/issues/122) - WireMock.Net not responding in unit tests - same works in console application - [#122](https://github.com/wiremock/WireMock.Net/issues/122) - WireMock.Net not responding in unit tests - same works in console application
- [#126](https://github.com/wiremock/WireMock.Net/issues/126) - Question: UsingHead always returns 0 for Content-Length header even when explicitly specified - [#126](https://github.com/wiremock/WireMock.Net/issues/126) - Question: UsingHead always returns 0 for Content-Length header even when explicitly specified
- [#132](https://github.com/wiremock/WireMock.Net/issues/132) - LogEntries not being recorded on subsequent tests - [#132](https://github.com/wiremock/WireMock.Net/issues/132) - LogEntries not being recorded on subsequent tests
- [#136](https://github.com/wiremock/WireMock.Net/issues/136) - Question: Does the WireMock send Content-Length response header - [#136](https://github.com/wiremock/WireMock.Net/issues/136) - Question: Does the WireMock send Content-Length response header [bug]
- [#137](https://github.com/wiremock/WireMock.Net/issues/137) - Question: How to specify Transfer-Encoding response header? - [#137](https://github.com/wiremock/WireMock.Net/issues/137) - Question: How to specify Transfer-Encoding response header?
- [#139](https://github.com/wiremock/WireMock.Net/issues/139) - Wiki link https://github.com/StefH/WireMock.Net/wiki/Record-(via-proxy)-and-Save is dead - [#139](https://github.com/wiremock/WireMock.Net/issues/139) - Wiki link https://github.com/StefH/WireMock.Net/wiki/Record-(via-proxy)-and-Save is dead
+1 -1
View File
@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>2.0.0</VersionPrefix> <VersionPrefix>2.7.0</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon> <PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
+2 -2
View File
@@ -1,7 +1,7 @@
rem https://github.com/StefH/GitHubReleaseNotes rem https://github.com/StefH/GitHubReleaseNotes
SET version=2.0.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% GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
GitHubReleaseNotes --output PackageReleaseNotes.txt --skip-empty-releases --exclude-labels test question invalid doc duplicate example environment --template PackageReleaseNotes.template --version %version% --token %GH_TOKEN% GitHubReleaseNotes --output PackageReleaseNotes.txt --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --template PackageReleaseNotes.template --version %version% --token %GH_TOKEN%
+6 -6
View File
@@ -1,8 +1,8 @@
# 2.0.0 (11 March 2026) # 2.7.0 (24 May 2026)
- #1359 Version 2.x - #1457 Update OpenTelemetry.Api from 1.14.0 to 1.15.3 in unit test project [dependencies]
- #1394 MappingSerializer (Newtonsoft or SystemText)-Json [feature] - #1459 chore: update Handlebars 2.5.2 to 2.5.5 [dependencies]
- #1341 Configurable JSON serialization support (Newtonsoft.Json vs System.Text.Json) [feature] - #1461 Update Testcontainers nuget package to 4.12.0 [dependencies]
- #1422 WireMock.Net seems to be incompatible with Microsoft.Owin.Security.Interop [bug] - #1462 Update Scriban.Signed to latest [dependencies]
- #1424 WireMock.Net.FluentAssertions is incompatible with WireMock.Net.Aspire [feature] - #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 The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
+3 -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.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.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.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.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) | &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)
@@ -71,11 +72,12 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| &nbsp;&nbsp;**WireMock.Net.OpenTelemetry** | [![NuGet Badge WireMock.Net.OpenTelemetry](https://img.shields.io/nuget/v/WireMock.Net.OpenTelemetry)](https://www.nuget.org/packages/WireMock.Net.ProtoBuf) | [![MyGet Badge WireMock.Net.OpenTelemetry](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.OpenTelemetry?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenTelemetry) | &nbsp;&nbsp;**WireMock.Net.OpenTelemetry** | [![NuGet Badge WireMock.Net.OpenTelemetry](https://img.shields.io/nuget/v/WireMock.Net.OpenTelemetry)](https://www.nuget.org/packages/WireMock.Net.ProtoBuf) | [![MyGet Badge WireMock.Net.OpenTelemetry](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.OpenTelemetry?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenTelemetry)
| | | | | | | |
| &nbsp;&nbsp;**WireMock.Net.RestClient** | [![NuGet Badge WireMock.Net.RestClient](https://img.shields.io/nuget/v/WireMock.Net.RestClient)](https://www.nuget.org/packages/WireMock.Net.RestClient) | [![MyGet Badge WireMock.Net.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient) | &nbsp;&nbsp;**WireMock.Net.RestClient** | [![NuGet Badge WireMock.Net.RestClient](https://img.shields.io/nuget/v/WireMock.Net.RestClient)](https://www.nuget.org/packages/WireMock.Net.RestClient) | [![MyGet Badge WireMock.Net.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
| &nbsp;&nbsp;**WireMock.Net.RestClient.AwesomeAssertions** | [![NuGet Badge WireMock.Net.RestClient.AwesomeAssertions](https://img.shields.io/nuget/v/WireMock.Net.RestClient.AwesomeAssertions)](https://www.nuget.org/packages/WireMock.Net.RestClient.AwesomeAssertions) | [![MyGet Badge WireMock.Net.RestClient.AwesomeAssertions](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.RestClient.AwesomeAssertions?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient.AwesomeAssertions)
| &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://img.shields.io/nuget/v/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Org.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient) | &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://img.shields.io/nuget/v/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Org.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
<br /> <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 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18 # Visual Studio Version 18
VisualStudioVersion = 18.0.11205.157 VisualStudioVersion = 18.0.11205.157
@@ -38,8 +37,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Client", "examples\WireMock.Net.Client\WireMock.Net.Client.csproj", "{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Client", "examples\WireMock.Net.Client\WireMock.Net.Client.csproj", "{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}"
EndProject 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}" 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 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}" 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 EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.TestcontainersExample", "examples\WireMock.Net.TestcontainersExample\WireMock.Net.TestcontainersExample.csproj", "{56A38798-C48B-4A4A-B805-071E05C02CE1}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.TestcontainersExample", "examples\WireMock.Net.TestcontainersExample\WireMock.Net.TestcontainersExample.csproj", "{56A38798-C48B-4A4A-B805-071E05C02CE1}"
EndProject 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}" 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 EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMockAzureQueueProxy", "examples\WireMockAzureQueueProxy\WireMockAzureQueueProxy.csproj", "{7FC0B409-2682-40EE-B3B9-3930D6769D01}" 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 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}" 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 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|x64.Build.0 = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x86.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -861,7 +858,6 @@ Global
{B6269AAC-170A-43D5-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {B6269AAC-170A-43D5-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E} = {0BB8B634-407A-4610-A91F-11586990767A} {31DC2EF8-C3FE-467D-84BE-FB5D956E612E} = {0BB8B634-407A-4610-A91F-11586990767A}
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {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} {A9D039B9-7509-4CF1-9EFD-87EB82998575} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{5C09FB93-1535-4F92-AF26-21E8A061EE4A} = {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} {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} {BAA9EC2A-874B-45CE-8E51-A73622DC7F3D} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {12B016A5-9D8B-4EFE-96C2-CA51BE43367D} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{56A38798-C48B-4A4A-B805-071E05C02CE1} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {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} {1EA72C0F-92E9-486B-8FFE-53F992BFC4AA} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{7FC0B409-2682-40EE-B3B9-3930D6769D01} = {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} {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} {2CE8E3A6-59CC-FE9C-9399-AD54E1FA862B} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{3B05CC76-C3CB-8667-6B65-3129DFB25681} = {0BB8B634-407A-4610-A91F-11586990767A} {3B05CC76-C3CB-8667-6B65-3129DFB25681} = {0BB8B634-407A-4610-A91F-11586990767A}
{F4B2B967-98D7-4D93-9A5C-5EF7B84B941A} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
@@ -12,11 +12,11 @@
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.4.0" /> <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.4.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.1" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.1" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" /> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" /> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+45
View File
@@ -82,6 +82,51 @@ class Program
) )
); );
mappingBuilder.Given(m => m
.WithRequest(req => req
.WithPath("/testRequestWithQueryParams")
.UsingGet()
.WithParams(p => p.WithParam("param1", pb => pb.WithExactMatcher("value1")))
).WithResponse(rsp => rsp
.WithHeaders(h => h.Add("Content-Type", "application/json"))
.WithStatusCode(200)
.WithBodyAsJson(new
{
status = "ok"
}, true)
)
);
mappingBuilder.Given(m => m
.WithRequest(req => req
.WithPath("/testRequestWithHeaders")
.UsingGet()
.WithHeaders(h => h.WithHeader("Accept", hb => hb.WithExactMatcher("application/json")))
).WithResponse(rsp => rsp
.WithHeaders(h => h.Add("Content-Type", "application/json"))
.WithStatusCode(200)
.WithBodyAsJson(new
{
status = "ok"
}, true)
)
);
mappingBuilder.Given(m => m
.WithRequest(req => req
.WithPath("/testRequestWithCookie")
.UsingGet()
.WithCookies(c => c.WithCookie("cookie1", cb => cb.WithExactMatcher("cookievalue1")))
).WithResponse(rsp => rsp
.WithHeaders(h => h.Add("Content-Type", "application/json"))
.WithStatusCode(200)
.WithBodyAsJson(new
{
status = "ok"
}, true)
)
);
var result = await mappingBuilder.BuildAndPostAsync().ConfigureAwait(false); var result = await mappingBuilder.BuildAndPostAsync().ConfigureAwait(false);
Console.WriteLine($"result = {JsonConvert.SerializeObject(result)}"); Console.WriteLine($"result = {JsonConvert.SerializeObject(result)}");
@@ -16,7 +16,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <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" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup> </ItemGroup>
+19 -1
View File
@@ -8,6 +8,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using SharpYaml.Model;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Models; using WireMock.Models;
@@ -288,7 +289,24 @@ namespace WireMock.Net.ConsoleApplication
var todos = new Dictionary<int, Todo>(); var todos = new Dictionary<int, Todo>();
var server = WireMockServer.Start(); var server = WireMockServer.Start(new WireMockServerSettings
{
Logger = new WireMockConsoleLogger(),
Port = 9091
});
server
.WhenRequest(r => r
.WithPath("/Content-Length")
.UsingAnyMethod()
)
.ThenRespondWith(r => r
.WithStatusCode(HttpStatusCode.OK)
.WithHeader("Content-Length", "42")
);
System.Console.ReadLine();
//server //server
// .Given(Request.Create() // .Given(Request.Create()
@@ -18,7 +18,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <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" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup> </ItemGroup>
@@ -16,7 +16,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="WireMock.Net" Version="1.25.0" /> <PackageReference Include="WireMock.Net" Version="2.6.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -8,9 +8,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.14.0" /> <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -32,6 +32,9 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<StartupObject>Wiremock.Net.Service.Program</StartupObject>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <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> <HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <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.Client" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Core" 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" /> <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."); throw new InvalidOperationException($"The {nameof(TestcontainersSettings.OS.DockerEndpointAuthConfig)} is null. Check if Docker is started.");
} }
using var dockerClientConfig = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(); var dockerClientBuilder = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientBuilder();
using var dockerClient = dockerClientConfig.CreateClient(); using var dockerClient = dockerClientBuilder.Build();
var version = await dockerClient.System.GetVersionAsync(); var version = await dockerClient.System.GetVersionAsync();
return version.Os.IndexOf("Windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux; return version.Os.IndexOf("Windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux;
@@ -1,29 +1,18 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;
namespace WireMock.Net.WebApplication; 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) public Task StartAsync(CancellationToken cancellationToken)
{ {
_service.Start(); service.Start();
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
_service.Stop(); service.Stop();
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
@@ -1,9 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.Net.WebApplication; namespace WireMock.Net.WebApplication;
@@ -15,7 +15,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup> </ItemGroup>
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using WireMock.Admin.Requests; using WireMock.Admin.Requests;
@@ -15,7 +15,7 @@
"WireMockServerSettings": { "WireMockServerSettings": {
"StartAdminInterface": true, "StartAdminInterface": true,
"Urls": [ "Urls": [
"http://localhost" "http://localhost:0"
] ]
} }
} }
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<!-- <!--
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380 Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
@@ -7,6 +7,11 @@
<handlers> <handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers> </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" /> <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
</system.webServer> </system.webServer>
</configuration> </configuration>
@@ -61,6 +61,11 @@ public class MappingModel
/// </summary> /// </summary>
public int? TimesInSameState { get; set; } 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> /// <summary>
/// The request model. /// The request model.
/// </summary> /// </summary>
@@ -1,5 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using WireMock.Matchers.Request;
namespace WireMock.Admin.Mappings; namespace WireMock.Admin.Mappings;
/// <summary> /// <summary>
@@ -66,4 +68,10 @@ public class RequestModel
/// Gets or sets the body. /// Gets or sets the body.
/// </summary> /// </summary>
public BodyModel? Body { get; set; } 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; }
} }
@@ -0,0 +1,39 @@
// Copyright © WireMock.Net
namespace WireMock.Admin.Mappings;
public partial class ArrayMatcherModelBuilder
{
public ArrayMatcherModelBuilder WithExactMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
{
return WithMatcher("ExactMatcher", pattern, rejectOnMatch, ignoreCase);
}
public ArrayMatcherModelBuilder WithWildcardMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
{
return WithMatcher("WildcardMatcher", pattern, rejectOnMatch, ignoreCase);
}
public ArrayMatcherModelBuilder WithRegexMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
{
return WithMatcher("RegexMatcher", pattern, rejectOnMatch, ignoreCase);
}
public ArrayMatcherModelBuilder WithNotNullOrEmptyMatcher(bool rejectOnMatch = false)
{
return Add(mb => mb
.WithName("NotNullOrEmptyMatcher")
.WithRejectOnMatch(rejectOnMatch)
);
}
private ArrayMatcherModelBuilder WithMatcher(string name, object pattern, bool rejectOnMatch, bool ignoreCase = false)
{
return Add(mb => mb
.WithName(name)
.WithPattern(pattern)
.WithRejectOnMatch(rejectOnMatch)
.WithIgnoreCase(ignoreCase)
);
}
}
@@ -0,0 +1,19 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Text;
namespace WireMock.Admin.Mappings;
public partial class IListCookieModelBuilder
{
public IListCookieModelBuilder WithCookie(string name, Action<IListMatcherModelBuilder> action, bool rejectOnMatch = false)
{
return Add(cookieBuilder => cookieBuilder
.WithName(name)
.WithRejectOnMatch(rejectOnMatch)
.WithMatchers(matchersBuilder => action(matchersBuilder))
);
}
}
@@ -0,0 +1,19 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Text;
namespace WireMock.Admin.Mappings;
public partial class IListHeaderModelBuilder
{
public IListHeaderModelBuilder WithHeader(string name, Action<IListMatcherModelBuilder> action, bool rejectOnMatch = false)
{
return Add(headerBuilder => headerBuilder
.WithName(name)
.WithRejectOnMatch(rejectOnMatch)
.WithMatchers(matchersBuilder => action(matchersBuilder))
);
}
}
@@ -0,0 +1,39 @@
// Copyright © WireMock.Net
namespace WireMock.Admin.Mappings;
public partial class IListMatcherModelBuilder
{
public IListMatcherModelBuilder WithExactMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
{
return WithMatcher("ExactMatcher", pattern, rejectOnMatch, ignoreCase);
}
public IListMatcherModelBuilder WithWildcardMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
{
return WithMatcher("WildcardMatcher", pattern, rejectOnMatch, ignoreCase);
}
public IListMatcherModelBuilder WithRegexMatcher(object pattern, bool rejectOnMatch = false, bool ignoreCase = false)
{
return WithMatcher("RegexMatcher", pattern, rejectOnMatch, ignoreCase);
}
public IListMatcherModelBuilder WithNotNullOrEmptyMatcher(bool rejectOnMatch = false)
{
return Add(mb => mb
.WithName("NotNullOrEmptyMatcher")
.WithRejectOnMatch(rejectOnMatch)
);
}
private IListMatcherModelBuilder WithMatcher(string name, object pattern, bool rejectOnMatch, bool ignoreCase = false)
{
return Add(mb => mb
.WithName(name)
.WithPattern(pattern)
.WithRejectOnMatch(rejectOnMatch)
.WithIgnoreCase(ignoreCase)
);
}
}
@@ -0,0 +1,17 @@
// Copyright © WireMock.Net
using System;
namespace WireMock.Admin.Mappings;
public partial class IListParamModelBuilder
{
public IListParamModelBuilder WithParam(string name, Action<ArrayMatcherModelBuilder> action, bool rejectOnMatch = false)
{
return Add(paramBuilder => paramBuilder
.WithName(name)
.WithRejectOnMatch(rejectOnMatch)
.WithMatchers(matchersBuilder => action(matchersBuilder))
);
}
}
@@ -0,0 +1,24 @@
// Copyright © WireMock.Net
using System.Diagnostics.CodeAnalysis;
namespace WireMock.Handlers;
public interface IScenarioStateStore
{
bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state);
IReadOnlyList<ScenarioState> GetAll();
bool ContainsKey(string name);
bool TryAdd(string name, ScenarioState scenarioState);
ScenarioState AddOrUpdate(string name, Func<string, ScenarioState> addFactory, Func<string, ScenarioState, ScenarioState> updateFactory);
ScenarioState? Update(string name, Action<ScenarioState> updateAction);
bool TryRemove(string name);
void Clear();
}
@@ -7,6 +7,11 @@ namespace WireMock.Matchers.Request;
/// </summary> /// </summary>
public interface IRequestMatcher public interface IRequestMatcher
{ {
/// <summary>
/// Gets the request matcher's type.
/// </summary>
public RequestMatcherType Type { get; }
/// <summary> /// <summary>
/// Determines whether the specified RequestMessage is match. /// Determines whether the specified RequestMessage is match.
/// </summary> /// </summary>
@@ -10,7 +10,7 @@ public class MatchDetail
/// <summary> /// <summary>
/// Gets or sets the type of the matcher. /// Gets or sets the type of the matcher.
/// </summary> /// </summary>
public required Type MatcherType { get; set; } public required string MatcherType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the type of the matcher. /// Gets or sets the type of the matcher.
@@ -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
}
@@ -33,12 +33,6 @@ public interface IWireMockServer : IDisposable
/// </summary> /// </summary>
IReadOnlyList<MappingModel> MappingModels { get; } IReadOnlyList<MappingModel> MappingModels { get; }
// <summary>
// Gets the mappings.
// </summary>
//[PublicAPI]
//IEnumerable<IMapping> Mappings { get; }
/// <summary> /// <summary>
/// Gets the ports. /// Gets the ports.
/// </summary> /// </summary>
@@ -69,8 +63,6 @@ public interface IWireMockServer : IDisposable
/// </summary> /// </summary>
string? Provider { get; } string? Provider { get; }
//ConcurrentDictionary<string, ScenarioState> Scenarios { get; }
/// <summary> /// <summary>
/// Occurs when [log entries changed]. /// Occurs when [log entries changed].
/// </summary> /// </summary>
@@ -115,8 +107,6 @@ public interface IWireMockServer : IDisposable
/// <returns>The <see cref="IReadOnlyList{ILogEntry}"/>.</returns> /// <returns>The <see cref="IReadOnlyList{ILogEntry}"/>.</returns>
IReadOnlyList<ILogEntry> FindLogEntries(params IRequestMatcher[] matchers); IReadOnlyList<ILogEntry> FindLogEntries(params IRequestMatcher[] matchers);
// IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false);
/// <summary> /// <summary>
/// Reads a static mapping file and adds or updates a single mapping. /// Reads a static mapping file and adds or updates a single mapping.
/// ///
@@ -31,19 +31,19 @@ public partial class WireMockAssertions
} }
[CustomAssertion] [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] [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] [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); var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
@@ -126,15 +126,44 @@ public partial class WireMockAssertions
private static string? FormatBody(object? body) private static string? FormatBody(object? body)
{ {
return body switch if (body == null)
{ {
null => null, return null;
string str => str, }
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
byte[] bytes => $"byte[{bytes.Length}] {{...}}", if (body is string str)
JToken jToken => jToken.ToString(Formatting.None), {
_ => JToken.FromObject(body).ToString(Formatting.None) 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) private static string? FormatBodies(IEnumerable<object?> bodies)
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description> <Description>WireMock.Net.Routing extends WireMock.Net with modern, minimal-API-style routing for .NET</Description>
<Authors>Gennadii Saltyshchak</Authors> <Authors>Gennadii Saltyshchak</Authors>
@@ -25,7 +25,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="JsonConverter.Abstractions" Version="0.8.0" /> <PackageReference Include="JsonConverter.Abstractions" Version="0.12.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -31,19 +31,19 @@ public partial class WireMockAssertions
} }
[CustomAssertion] [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] [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] [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); var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);
@@ -126,15 +126,44 @@ public partial class WireMockAssertions
private static string? FormatBody(object? body) private static string? FormatBody(object? body)
{ {
return body switch if (body == null)
{ {
null => null, return null;
string str => str, }
AnyOf<string, StringPattern>[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())),
byte[] bytes => $"byte[{bytes.Length}] {{...}}", if (body is string str)
JToken jToken => jToken.ToString(Formatting.None), {
_ => JToken.FromObject(body).ToString(Formatting.None) 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) 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 // Copyright © WireMock.Net
using System; using JsonConverter.Abstractions;
using System.Collections.Generic; using JsonConverter.Newtonsoft.Json;
using Newtonsoft.Json;
using WireMock.Matchers.Helpers; using WireMock.Matchers.Helpers;
using WireMock.Models.Mime; using WireMock.Models.Mime;
using WireMock.Util; using WireMock.Util;
@@ -15,6 +16,8 @@ public class MimePartMatcher : IMimePartMatcher
{ {
private readonly IList<(string Name, Func<IMimePartData, MatchResult> func)> _matcherFunctions; private readonly IList<(string Name, Func<IMimePartData, MatchResult> func)> _matcherFunctions;
private readonly IJsonConverter _jsonConverter;
/// <inheritdoc /> /// <inheritdoc />
public string Name => nameof(MimePartMatcher); public string Name => nameof(MimePartMatcher);
@@ -41,7 +44,8 @@ public class MimePartMatcher : IMimePartMatcher
IStringMatcher? contentTypeMatcher, IStringMatcher? contentTypeMatcher,
IStringMatcher? contentDispositionMatcher, IStringMatcher? contentDispositionMatcher,
IStringMatcher? contentTransferEncodingMatcher, IStringMatcher? contentTransferEncodingMatcher,
IMatcher? contentMatcher IMatcher? contentMatcher,
IJsonConverter? jsonConverter = null
) )
{ {
MatchBehaviour = matchBehaviour; MatchBehaviour = matchBehaviour;
@@ -49,6 +53,7 @@ public class MimePartMatcher : IMimePartMatcher
ContentDispositionMatcher = contentDispositionMatcher; ContentDispositionMatcher = contentDispositionMatcher;
ContentTransferEncodingMatcher = contentTransferEncodingMatcher; ContentTransferEncodingMatcher = contentTransferEncodingMatcher;
ContentMatcher = contentMatcher; ContentMatcher = contentMatcher;
_jsonConverter = jsonConverter ?? new NewtonsoftJsonConverter();
_matcherFunctions = []; _matcherFunctions = [];
if (ContentTypeMatcher != null) if (ContentTypeMatcher != null)
@@ -107,7 +112,8 @@ public class MimePartMatcher : IMimePartMatcher
ContentType = GetContentTypeAsString(mimePart.ContentType), ContentType = GetContentTypeAsString(mimePart.ContentType),
DeserializeJson = true, DeserializeJson = true,
ContentEncoding = null, // mimePart.ContentType?.CharsetEncoding.ToString(), ContentEncoding = null, // mimePart.ContentType?.CharsetEncoding.ToString(),
DecompressGZipAndDeflate = true DecompressGZipAndDeflate = true,
DefaultJsonConverter = _jsonConverter
}; };
var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult(); var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult();
@@ -0,0 +1,145 @@
// Copyright © WireMock.Net
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using Stef.Validation;
namespace WireMock.Handlers;
/// <summary>
/// Provides a file-based implementation of <see cref="IScenarioStateStore" /> that persists scenario states to disk and allows concurrent access.
/// </summary>
public class FileBasedScenarioStateStore : IScenarioStateStore
{
private readonly ConcurrentDictionary<string, ScenarioState> _scenarios = new(StringComparer.OrdinalIgnoreCase);
private readonly string _scenariosFolder;
private readonly object _lock = new();
/// <summary>
/// Initializes a new instance of the FileBasedScenarioStateStore class using the specified root folder as the base directory for scenario state storage.
/// </summary>
/// <param name="rootFolder">The root directory under which scenario state data will be stored. Must be a valid file system path.</param>
public FileBasedScenarioStateStore(string rootFolder)
{
Guard.NotNullOrEmpty(rootFolder);
_scenariosFolder = Path.Combine(rootFolder, "__admin", "scenarios");
Directory.CreateDirectory(_scenariosFolder);
LoadScenariosFromDisk();
}
/// <inheritdoc />
public bool TryGet(string name, [NotNullWhen(true)] out ScenarioState? state)
{
return _scenarios.TryGetValue(name, out state);
}
/// <inheritdoc />
public IReadOnlyList<ScenarioState> GetAll()
{
return _scenarios.Values.ToArray();
}
/// <inheritdoc />
public bool ContainsKey(string name)
{
return _scenarios.ContainsKey(name);
}
/// <inheritdoc />
public bool TryAdd(string name, ScenarioState scenarioState)
{
if (_scenarios.TryAdd(name, scenarioState))
{
WriteScenarioToFile(name, scenarioState);
return true;
}
return false;
}
/// <inheritdoc />
public ScenarioState AddOrUpdate(string name, Func<string, ScenarioState> addFactory, Func<string, ScenarioState, ScenarioState> updateFactory)
{
lock (_lock)
{
var result = _scenarios.AddOrUpdate(name, addFactory, updateFactory);
WriteScenarioToFile(name, result);
return result;
}
}
/// <inheritdoc />
public ScenarioState? Update(string name, Action<ScenarioState> updateAction)
{
lock (_lock)
{
if (_scenarios.TryGetValue(name, out var state))
{
updateAction(state);
WriteScenarioToFile(name, state);
return state;
}
return null;
}
}
/// <inheritdoc />
public bool TryRemove(string name)
{
if (_scenarios.TryRemove(name, out _))
{
DeleteScenarioFile(name);
return true;
}
return false;
}
/// <inheritdoc />
public void Clear()
{
_scenarios.Clear();
foreach (var file in Directory.GetFiles(_scenariosFolder, "*.json"))
{
File.Delete(file);
}
}
private string GetScenarioFilePath(string name)
{
var sanitized = string.Concat(name.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c));
return Path.Combine(_scenariosFolder, sanitized + ".json");
}
private void WriteScenarioToFile(string name, ScenarioState state)
{
var json = JsonConvert.SerializeObject(state, Formatting.Indented);
File.WriteAllText(GetScenarioFilePath(name), json);
}
private void DeleteScenarioFile(string name)
{
var path = GetScenarioFilePath(name);
if (File.Exists(path))
{
File.Delete(path);
}
}
private void LoadScenariosFromDisk()
{
foreach (var file in Directory.GetFiles(_scenariosFolder, "*.json"))
{
var json = File.ReadAllText(file);
var state = JsonConvert.DeserializeObject<ScenarioState>(json);
if (state != null)
{
_scenarios.TryAdd(state.Name, state);
}
}
}
}
@@ -1,8 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using JsonConverter.Abstractions;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Http; namespace WireMock.Http;
@@ -15,7 +14,8 @@ internal static class HttpResponseMessageHelper
Uri originalUri, Uri originalUri,
bool deserializeJson, bool deserializeJson,
bool decompressGzipAndDeflate, bool decompressGzipAndDeflate,
bool deserializeFormUrlEncoded) bool deserializeFormUrlEncoded,
IJsonConverter jsonConverter)
{ {
var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode }; var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode };
@@ -45,7 +45,8 @@ internal static class HttpResponseMessageHelper
DeserializeJson = deserializeJson, DeserializeJson = deserializeJson,
ContentEncoding = contentEncodingHeader?.FirstOrDefault(), ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = decompressGzipAndDeflate, DecompressGZipAndDeflate = decompressGzipAndDeflate,
DeserializeFormUrlEncoded = deserializeFormUrlEncoded DeserializeFormUrlEncoded = deserializeFormUrlEncoded,
DefaultJsonConverter = jsonConverter
}; };
responseMessage.BodyData = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false); 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);
}
@@ -0,0 +1,38 @@
// Copyright © WireMock.Net
using Microsoft.Extensions.Logging;
namespace WireMock.Logging;
internal sealed class WireMockAspNetCoreLogger(IWireMockLogger logger, string categoryName) : ILogger
{
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Warning;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
var message = formatter(state, exception);
if (exception != null)
{
message = $"{message} | Exception: {exception}";
}
switch (logLevel)
{
case LogLevel.Warning:
logger.Warn("[{0}] {1}", categoryName, message);
break;
default:
logger.Error("[{0}] {1}", categoryName, message);
break;
}
}
}
@@ -0,0 +1,19 @@
// Copyright © WireMock.Net
using Microsoft.Extensions.Logging;
namespace WireMock.Logging;
internal sealed class WireMockAspNetCoreLoggerProvider : ILoggerProvider
{
private readonly IWireMockLogger _logger;
public WireMockAspNetCoreLoggerProvider(IWireMockLogger logger)
{
_logger = logger;
}
public ILogger CreateLogger(string categoryName) => new WireMockAspNetCoreLogger(_logger, categoryName);
public void Dispose() { }
}
+3
View File
@@ -62,6 +62,9 @@ public class Mapping : IMapping
/// <inheritdoc /> /// <inheritdoc />
public bool IsProxy => Provider is ProxyAsyncResponseProvider; public bool IsProxy => Provider is ProxyAsyncResponseProvider;
/// <inheritdoc />
public bool IsDisabled { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public bool LogMapping => Provider is not (DynamicResponseProvider or DynamicAsyncResponseProvider); 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 MappingToFileSaver _mappingToFileSaver;
private readonly IGuidUtils _guidUtils; private readonly IGuidUtils _guidUtils;
private readonly IDateTimeUtils _dateTimeUtils; private readonly IDateTimeUtils _dateTimeUtils;
private readonly IResponseMessageBuilder _responseMessageBuilder;
/// <summary> /// <summary>
/// Create a MappingBuilder /// Create a MappingBuilder
@@ -43,6 +44,7 @@ public class MappingBuilder : IMappingBuilder
_guidUtils = new GuidUtils(); _guidUtils = new GuidUtils();
_dateTimeUtils = new DateTimeUtils(); _dateTimeUtils = new DateTimeUtils();
_responseMessageBuilder = new ResponseMessageBuilder(_dateTimeUtils);
} }
internal MappingBuilder( internal MappingBuilder(
@@ -51,7 +53,8 @@ public class MappingBuilder : IMappingBuilder
MappingConverter mappingConverter, MappingConverter mappingConverter,
MappingToFileSaver mappingToFileSaver, MappingToFileSaver mappingToFileSaver,
IGuidUtils guidUtils, IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils IDateTimeUtils dateTimeUtils,
IResponseMessageBuilder responseMessageBuilder
) )
{ {
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
@@ -60,12 +63,13 @@ public class MappingBuilder : IMappingBuilder
_mappingToFileSaver = Guard.NotNull(mappingToFileSaver); _mappingToFileSaver = Guard.NotNull(mappingToFileSaver);
_guidUtils = Guard.NotNull(guidUtils); _guidUtils = Guard.NotNull(guidUtils);
_dateTimeUtils = Guard.NotNull(dateTimeUtils); _dateTimeUtils = Guard.NotNull(dateTimeUtils);
_responseMessageBuilder = Guard.NotNull(responseMessageBuilder);
} }
/// <inheritdoc /> /// <inheritdoc />
public IRespondWithAProvider Given(IRequestMatcher requestMatcher, bool saveToFile = false) 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 /> /// <inheritdoc />
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using WireMock.Util; 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 // Copyright © WireMock.Net
using System.Linq;
using AnyOfTypes; using AnyOfTypes;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Stef.Validation; using Stef.Validation;
@@ -13,9 +12,8 @@ namespace WireMock.Matchers;
/// <summary> /// <summary>
/// JsonPathMatcher /// JsonPathMatcher
/// </summary> /// </summary>
/// <seealso cref="IStringMatcher" /> /// <seealso cref="IJsonPathMatcher" />
/// <seealso cref="IObjectMatcher" /> public class JsonPathMatcher : IJsonPathMatcher
public class JsonPathMatcher : IStringMatcher, IObjectMatcher
{ {
private readonly AnyOf<string, StringPattern>[] _patterns; private readonly AnyOf<string, StringPattern>[] _patterns;
@@ -1,11 +1,12 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq; using System.Collections;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Extensions; using WireMock.Extensions;
using WireMock.Serialization;
using WireMock.Util; using WireMock.Util;
using JsonUtils = WireMock.Util.JsonUtils;
namespace WireMock.Matchers; namespace WireMock.Matchers;
@@ -31,6 +32,11 @@ public class JsonMatcher : IJsonMatcher
/// </summary> /// </summary>
public bool Regex { get; } public bool Regex { get; }
/// <summary>
/// Ignore array order when comparing
/// </summary>
public bool IgnoreArrayOrder { get; }
private readonly JToken _valueAsJToken; private readonly JToken _valueAsJToken;
/// <summary> /// <summary>
@@ -39,7 +45,8 @@ public class JsonMatcher : IJsonMatcher
/// <param name="value">The string value to check for equality.</param> /// <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="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param> /// <param name="regex">Support Regex.</param>
public JsonMatcher(string value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) /// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
public JsonMatcher(string value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder)
{ {
} }
@@ -49,7 +56,8 @@ public class JsonMatcher : IJsonMatcher
/// <param name="value">The object value to check for equality.</param> /// <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="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param> /// <param name="regex">Support Regex.</param>
public JsonMatcher(object value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) /// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
public JsonMatcher(object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder)
{ {
} }
@@ -60,16 +68,18 @@ public class JsonMatcher : IJsonMatcher
/// <param name="value">The value to check for equality.</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="ignoreCase">Ignore the case from the PropertyName and PropertyValue (string only).</param>
/// <param name="regex">Support Regex.</param> /// <param name="regex">Support Regex.</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) /// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false)
{ {
Guard.NotNull(value); Guard.NotNull(value);
MatchBehaviour = matchBehaviour; MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase; IgnoreCase = ignoreCase;
Regex = regex; Regex = regex;
IgnoreArrayOrder = ignoreArrayOrder;
Value = value; Value = value;
_valueAsJToken = JsonUtils.ConvertValueToJToken(value); _valueAsJToken = ConvertValueToJToken(value);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -83,7 +93,7 @@ public class JsonMatcher : IJsonMatcher
{ {
try try
{ {
var inputAsJToken = JsonUtils.ConvertValueToJToken(input); var inputAsJToken = ConvertValueToJToken(input);
var match = IsMatch(RenameJToken(_valueAsJToken), RenameJToken(inputAsJToken)); var match = IsMatch(RenameJToken(_valueAsJToken), RenameJToken(inputAsJToken));
score = MatchScores.ToScore(match); score = MatchScores.ToScore(match);
@@ -105,7 +115,8 @@ public class JsonMatcher : IJsonMatcher
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" + $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreArrayOrder)}" +
$")"; $")";
} }
@@ -182,6 +193,13 @@ public class JsonMatcher : IJsonMatcher
return false; return false;
} }
if (IgnoreArrayOrder)
{
// Sort both arrays by their string representation and compare
valueArray = valueArray.OrderBy(t => t.ToString()).ToArray();
inputArray = inputArray.OrderBy(t => t.ToString()).ToArray();
}
return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any(); return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any();
default: default:
@@ -241,6 +259,18 @@ public class JsonMatcher : IJsonMatcher
return new JObject(renamedProperties); 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) private static string? ToUpper(string? input)
{ {
return input?.ToUpperInvariant(); return input?.ToUpperInvariant();
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
namespace WireMock.Matchers.Request; namespace WireMock.Matchers.Request;
/// <summary> /// <summary>
@@ -30,7 +28,7 @@ public class RequestMatchResult : IRequestMatchResult
return AddMatchDetail(new MatchDetail return AddMatchDetail(new MatchDetail
{ {
Name = matcherType.Name.Replace("RequestMessage", string.Empty), Name = matcherType.Name.Replace("RequestMessage", string.Empty),
MatcherType = matcherType, MatcherType = matcherType.Name,
Score = score, Score = score,
Exception = exception Exception = exception
}); });
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Matchers.Helpers; using WireMock.Matchers.Helpers;
using WireMock.Util; using WireMock.Util;
@@ -142,6 +141,9 @@ public class RequestMessageBodyMatcher : IRequestMatcher
MatchOperator = matchOperator; MatchOperator = matchOperator;
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Body;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -31,6 +31,9 @@ public class RequestMessageBodyMatcher<T> : IRequestMatcher
Func = Guard.NotNull(func); Func = Guard.NotNull(func);
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.BodyOfT;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -74,6 +74,9 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs); Funcs = Guard.NotNull(funcs);
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.ClientIP;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -20,6 +20,11 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
/// </value> /// </value>
private IEnumerable<IRequestMatcher> RequestMatchers { get; } 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> /// <summary>
/// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class. /// Initializes a new instance of the <see cref="RequestMessageCompositeMatcher"/> class.
/// </summary> /// </summary>
@@ -31,6 +36,9 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
_type = type; _type = type;
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Composite;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -39,6 +47,13 @@ public abstract class RequestMessageCompositeMatcher : IRequestMatcher
return MatchScores.Mismatch; 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) if (_type == CompositeMatcherType.And)
{ {
return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult)); return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult));
@@ -1,7 +1,6 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using Stef.Validation; using Stef.Validation;
using System.Linq;
namespace WireMock.Matchers.Request; 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. Name = string.Empty; // Not used when Func, but set to a non-null valid value.
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Cookie;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) 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 // Copyright © WireMock.Net
using System.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Types; using WireMock.Types;
@@ -12,7 +11,7 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/> /// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageHeaderMatcher : IRequestMatcher public class RequestMessageHeaderMatcher : IRequestMatcher
{ {
private const string _name = nameof(RequestMessageCookieMatcher); private const string _name = nameof(RequestMessageHeaderMatcher);
/// <summary> /// <summary>
/// MatchBehaviour /// MatchBehaviour
@@ -106,6 +105,9 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
Name = string.Empty; // Not used when Func, but set to a non-null valid value. Name = string.Empty; // Not used when Func, but set to a non-null valid value.
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Header;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -65,6 +65,9 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
MatcherOnStringFunc = Guard.NotNull(func); MatcherOnStringFunc = Guard.NotNull(func);
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.HttpVersion;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -46,6 +46,9 @@ internal class RequestMessageMethodMatcher : IRequestMatcher
MatchOperator = matchOperator; MatchOperator = matchOperator;
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Method;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -55,6 +55,9 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
MatchOperator = matchOperator; MatchOperator = matchOperator;
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.MultiPart;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -82,6 +82,9 @@ public class RequestMessageParamMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs); Funcs = Guard.NotNull(funcs);
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Param;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -72,6 +72,9 @@ public class RequestMessagePathMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs); Funcs = Guard.NotNull(funcs);
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Path;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -29,6 +29,9 @@ internal class RequestMessageScenarioAndStateMatcher : IRequestMatcher
_executionConditionState = executionConditionState; _executionConditionState = executionConditionState;
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.ScenarioAndState;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -72,6 +72,9 @@ public class RequestMessageUrlMatcher : IRequestMatcher
Funcs = Guard.NotNull(funcs); Funcs = Guard.NotNull(funcs);
} }
/// <inheritdoc />
public RequestMatcherType Type => RequestMatcherType.Url;
/// <inheritdoc /> /// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{ {
@@ -0,0 +1,299 @@
// 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; }
/// <summary>
/// Ignore array order when comparing
/// </summary>
public bool IgnoreArrayOrder { 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>
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
public SystemTextJsonMatcher(string value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false)
: this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder)
{
}
/// <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>
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
public SystemTextJsonMatcher(object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false)
: this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex, ignoreArrayOrder)
{
}
/// <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>
/// <param name="ignoreArrayOrder">Ignore array element order when comparing.</param>
public SystemTextJsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false, bool ignoreArrayOrder = false)
{
Guard.NotNull(value);
MatchBehaviour = matchBehaviour;
IgnoreCase = ignoreCase;
Regex = regex;
IgnoreArrayOrder = ignoreArrayOrder;
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)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreArrayOrder)}" +
$")";
}
/// <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;
}
if (IgnoreArrayOrder)
{
// Sort both arrays by their string representation and compare
valueArray = valueArray.OrderBy(e => e.GetRawText()).ToArray();
inputArray = inputArray.OrderBy(e => e.GetRawText()).ToArray();
}
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)}" +
$")";
}
}
@@ -1,9 +1,9 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Stef.Validation; using Stef.Validation;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Owin.Mappers; using WireMock.Owin.Mappers;
@@ -57,6 +57,12 @@ internal partial class AspNetCoreSelfHost
_host = builder _host = builder
.UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/ .UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/
.ConfigureAppConfigurationUsingEnvironmentVariables() .ConfigureAppConfigurationUsingEnvironmentVariables()
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddProvider(new WireMockAspNetCoreLoggerProvider(_logger));
logging.SetMinimumLevel(LogLevel.Warning);
})
.ConfigureServices(services => .ConfigureServices(services =>
{ {
services.AddSingleton(_wireMockMiddlewareOptions); services.AddSingleton(_wireMockMiddlewareOptions);
@@ -66,6 +72,7 @@ internal partial class AspNetCoreSelfHost
services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>(); services.AddSingleton<IOwinResponseMapper, OwinResponseMapper>();
services.AddSingleton<IGuidUtils, GuidUtils>(); services.AddSingleton<IGuidUtils, GuidUtils>();
services.AddSingleton<IDateTimeUtils, DateTimeUtils>(); services.AddSingleton<IDateTimeUtils, DateTimeUtils>();
services.AddSingleton<IResponseMessageBuilder, ResponseMessageBuilder>();
services.AddSingleton<LogEntryMapper>(); services.AddSingleton<LogEntryMapper>();
services.AddSingleton<IWireMockMiddlewareLogger, WireMockMiddlewareLogger>(); services.AddSingleton<IWireMockMiddlewareLogger, WireMockMiddlewareLogger>();
@@ -169,10 +176,10 @@ internal partial class AspNetCoreSelfHost
return _host.RunAsync(token); return _host.RunAsync(token);
} }
catch (Exception e) catch (Exception ex)
{ {
RunningException = e; RunningException = ex;
_logger.Error(e.ToString()); _logger.Error("Error while RunAsync", ex);
IsStarted = false; IsStarted = false;
@@ -11,12 +11,14 @@ internal class GlobalExceptionMiddleware
{ {
private readonly IWireMockMiddlewareOptions _options; private readonly IWireMockMiddlewareOptions _options;
private readonly IOwinResponseMapper _responseMapper; 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; Next = next;
_options = Guard.NotNull(options); _options = Guard.NotNull(options);
_responseMapper = Guard.NotNull(responseMapper); _responseMapper = Guard.NotNull(responseMapper);
_responseMessageBuilder = Guard.NotNull(responseMessageBuilder);
} }
public RequestDelegate Next { get; } public RequestDelegate Next { get; }
@@ -35,7 +37,7 @@ internal class GlobalExceptionMiddleware
catch (Exception ex) catch (Exception ex)
{ {
_options.Logger.Error("HttpStatusCode set to 500 {0}", 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.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using JsonConverter.Abstractions;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using WireMock.Handlers; using WireMock.Handlers;
@@ -27,7 +28,7 @@ internal interface IWireMockMiddlewareOptions
ConcurrentDictionary<Guid, IMapping> Mappings { get; } ConcurrentDictionary<Guid, IMapping> Mappings { get; }
ConcurrentDictionary<string, ScenarioState> Scenarios { get; } IScenarioStateStore ScenarioStateStore { get; set; }
ConcurrentObservableCollection<LogEntry> LogEntries { get; } ConcurrentObservableCollection<LogEntry> LogEntries { get; }
@@ -99,4 +100,12 @@ internal interface IWireMockMiddlewareOptions
/// WebSocket settings. /// WebSocket settings.
/// </summary> /// </summary>
WebSocketSettings? WebSocketSettings { get; set; } 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, ContentType = request.ContentType,
DeserializeJson = !options.DisableJsonBodyParsing.GetValueOrDefault(false), DeserializeJson = !options.DisableJsonBodyParsing.GetValueOrDefault(false),
ContentEncoding = contentEncodingHeader?.FirstOrDefault(), ContentEncoding = contentEncodingHeader?.FirstOrDefault(),
DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false) DecompressGZipAndDeflate = !options.DisableRequestBodyDecompressing.GetValueOrDefault(false),
DefaultJsonConverter = options.DefaultJsonSerializer
}; };
body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false); body = await BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false);
@@ -1,31 +1,27 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Net; using System.Net;
using System.Reflection;
using System.Text; using System.Text;
using JsonConverter.Abstractions;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using RandomDataGenerator.FieldOptions; using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers; using RandomDataGenerator.Randomizers;
using Stef.Validation;
using WireMock.Http; using WireMock.Http;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.ResponseProviders; using WireMock.ResponseProviders;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Owin.Mappers namespace WireMock.Owin.Mappers;
{
/// <summary> /// <summary>
/// OwinResponseMapper /// OwinResponseMapper
/// </summary> /// </summary>
internal class OwinResponseMapper : IOwinResponseMapper internal class OwinResponseMapper(IWireMockMiddlewareOptions options) : IOwinResponseMapper
{ {
private readonly IRandomizerNumber<double> _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 }); 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 IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 });
private readonly IWireMockMiddlewareOptions _options;
private readonly Encoding _utf8NoBom = new UTF8Encoding(false); private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx // https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
@@ -44,15 +40,6 @@ namespace WireMock.Owin.Mappers
} }
}; };
/// <summary>
/// Constructor
/// </summary>
/// <param name="options">The IWireMockMiddlewareOptions.</param>
public OwinResponseMapper(IWireMockMiddlewareOptions options)
{
_options = Guard.NotNull(options);
}
/// <inheritdoc /> /// <inheritdoc />
public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response) public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response)
{ {
@@ -88,20 +75,16 @@ namespace WireMock.Owin.Mappers
break; break;
} }
var statusCodeType = responseMessage.StatusCode?.GetType(); if (responseMessage.StatusCode is HttpStatusCode or int)
if (statusCodeType != null)
{ {
if (statusCodeType == typeof(int) || statusCodeType == typeof(int?) || statusCodeType.GetTypeInfo().IsEnum) response.StatusCode = MapStatusCode((int)responseMessage.StatusCode);
{
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!);
} }
else if (statusCodeType == typeof(string)) else if (responseMessage.StatusCode is string statusCodeAsString)
{ {
// Note: this case will also match on null // Note: this case will also match on null
int.TryParse(responseMessage.StatusCode as string, out var statusCodeTypeAsInt); _ = int.TryParse(statusCodeAsString, out var statusCodeTypeAsInt);
response.StatusCode = MapStatusCode(statusCodeTypeAsInt); response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
} }
}
SetResponseHeaders(responseMessage, bytes != null, response); SetResponseHeaders(responseMessage, bytes != null, response);
@@ -113,7 +96,7 @@ namespace WireMock.Owin.Mappers
} }
catch (Exception ex) catch (Exception ex)
{ {
_options.Logger.Warn("Error writing response body. Exception : {0}", ex); options.Logger.Warn("Error writing response body. Exception : {0}", ex);
} }
} }
@@ -142,7 +125,7 @@ namespace WireMock.Owin.Mappers
private int MapStatusCode(int code) private int MapStatusCode(int code)
{ {
if (_options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code)) if (options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code))
{ {
return (int)HttpStatusCode.OK; return (int)HttpStatusCode.OK;
} }
@@ -165,8 +148,13 @@ namespace WireMock.Owin.Mappers
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!); return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);
case BodyType.Json: case BodyType.Json:
var formatting = bodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None; var jsonConverterOptions = new JsonConverterOptions
var jsonBody = JsonConvert.SerializeObject(bodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore }); {
WriteIndented = bodyData.BodyAsJsonIndented == true,
IgnoreNullValues = true
};
var jsonBody = options.DefaultJsonSerializer.Serialize(bodyData.BodyAsJson!, jsonConverterOptions);
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody); return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
case BodyType.ProtoBuf: case BodyType.ProtoBuf:
@@ -181,10 +169,10 @@ namespace WireMock.Owin.Mappers
return bodyData.BodyAsBytes; return bodyData.BodyAsBytes;
case BodyType.File: case BodyType.File:
return _options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!); return options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!);
case BodyType.MultiPart: case BodyType.MultiPart:
_options.Logger.Warn("MultiPart body type is not handled!"); options.Logger.Warn("MultiPart body type is not handled!");
break; break;
case BodyType.None: case BodyType.None:
@@ -254,4 +242,3 @@ namespace WireMock.Owin.Mappers
response.Headers.Append(headerName, values); response.Headers.Append(headerName, values);
} }
} }
}
@@ -19,6 +19,7 @@ internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDou
var possibleMappings = new List<MappingMatcherResult>(); var possibleMappings = new List<MappingMatcherResult>();
var mappings = _options.Mappings.Values var mappings = _options.Mappings.Values
.Where(m => !m.IsDisabled)
.Where(m => m.TimeSettings.IsValid()) .Where(m => m.TimeSettings.IsValid())
.Where(m => m.Probability is null || _randomizerDoubleBetween0And1.Generate() <= m.Probability) .Where(m => m.Probability is null || _randomizerDoubleBetween0And1.Generate() <= m.Probability)
.ToArray(); .ToArray();
@@ -89,14 +90,13 @@ internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDou
private string? GetNextState(IMapping mapping) private string? GetNextState(IMapping mapping)
{ {
// If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping, // If the mapping does not have a scenario or the store does not contain this scenario,
// just return null to indicate that there is no next state. // just return null to indicate that there is no next state.
if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario)) if (mapping.Scenario == null)
{ {
return null; return null;
} }
// Else just return the next state return _options.ScenarioStateStore.TryGet(mapping.Scenario, out var state) ? state.NextState : null;
return _options.Scenarios[mapping.Scenario].NextState;
} }
} }
@@ -26,7 +26,8 @@ internal class WireMockMiddleware(
IMappingMatcher mappingMatcher, IMappingMatcher mappingMatcher,
IWireMockMiddlewareLogger logger, IWireMockMiddlewareLogger logger,
IGuidUtils guidUtils, IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils IDateTimeUtils dateTimeUtils,
IResponseMessageBuilder responseMessageBuilder
) )
{ {
private readonly object _lock = new(); private readonly object _lock = new();
@@ -81,9 +82,9 @@ internal class WireMockMiddleware(
} }
// Set scenario start // Set scenario start
if (!options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState) if (!options.ScenarioStateStore.ContainsKey(mapping.Scenario) && mapping.IsStartState)
{ {
options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState options.ScenarioStateStore.TryAdd(mapping.Scenario, new ScenarioState
{ {
Name = mapping.Scenario Name = mapping.Scenario
}); });
@@ -97,7 +98,7 @@ internal class WireMockMiddleware(
{ {
logRequest = true; logRequest = true;
options.Logger.Warn("HttpStatusCode set to 404 : No matching mapping found"); 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; return;
} }
@@ -109,7 +110,7 @@ internal class WireMockMiddleware(
if (!authorizationHeaderPresent) if (!authorizationHeaderPresent)
{ {
options.Logger.Error("HttpStatusCode set to 401, authorization header is missing."); options.Logger.Error("HttpStatusCode set to 401, authorization header is missing.");
response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null); response = responseMessageBuilder.Create(HttpStatusCode.Unauthorized, null);
return; return;
} }
@@ -117,11 +118,19 @@ internal class WireMockMiddleware(
if (!MatchScores.IsPerfect(authorizationHeaderMatchResult.Score)) if (!MatchScores.IsPerfect(authorizationHeaderMatchResult.Score))
{ {
options.Logger.Error("HttpStatusCode set to 401, authentication failed.", authorizationHeaderMatchResult.Exception ?? throw new WireMockException("Authentication failed")); 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; 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) if (!targetMapping.IsAdminInterface && options.RequestProcessingDelay > TimeSpan.Zero)
{ {
await Task.Delay(options.RequestProcessingDelay.Value).ConfigureAwait(false); 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) if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0)
{ {
await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false); 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}"); options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
WireMockActivitySource.RecordException(activity, ex); WireMockActivitySource.RecordException(activity, ex);
response = ResponseMessageBuilder.Create(500, ex.Message); response = responseMessageBuilder.Create(500, ex.Message);
} }
finally finally
{ {
@@ -176,7 +180,7 @@ internal class WireMockMiddleware(
{ {
options.Logger.Error("HttpStatusCode set to 404 : No matching mapping found", ex); 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); await responseMapper.MapAsync(notFoundResponse, ctx.Response).ConfigureAwait(false);
} }
} }
@@ -233,8 +237,8 @@ internal class WireMockMiddleware(
private void UpdateScenarioState(IMapping mapping) private void UpdateScenarioState(IMapping mapping)
{ {
var scenario = options.Scenarios[mapping.Scenario!]; options.ScenarioStateStore.Update(mapping.Scenario!, scenario =>
{
// Increase the number of times this state has been executed // Increase the number of times this state has been executed
scenario.Counter++; scenario.Counter++;
@@ -248,5 +252,6 @@ internal class WireMockMiddleware(
// Else just update Started and Finished // Else just update Started and Finished
scenario.Started = true; scenario.Started = true;
scenario.Finished = mapping.NextState == null; scenario.Finished = mapping.NextState == null;
});
} }
} }
@@ -1,7 +1,6 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Owin.ActivityTracing; using WireMock.Owin.ActivityTracing;
using WireMock.Serialization; using WireMock.Serialization;
@@ -42,7 +41,7 @@ internal class WireMockMiddlewareLogger(
if (_options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true }) if (_options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true })
{ {
var filename = $"{logEntry.Guid}.LogEntry.json"; var filename = $"{logEntry.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(logEntry)); _options.FileSystemHandler?.WriteUnmatchedRequest(filename, _options.DefaultJsonSerializer.Serialize(logEntry));
} }
} }
catch catch
@@ -2,6 +2,8 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using WireMock.Handlers; using WireMock.Handlers;
@@ -27,7 +29,7 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
public ConcurrentDictionary<Guid, IMapping> Mappings { get; } = new ConcurrentDictionary<Guid, IMapping>(); public ConcurrentDictionary<Guid, IMapping> Mappings { get; } = new ConcurrentDictionary<Guid, IMapping>();
public ConcurrentDictionary<string, ScenarioState> Scenarios { get; } = new(StringComparer.OrdinalIgnoreCase); public IScenarioStateStore ScenarioStateStore { get; set; } = new InMemoryScenarioStateStore();
public ConcurrentObservableCollection<LogEntry> LogEntries { get; } = new(); public ConcurrentObservableCollection<LogEntry> LogEntries { get; } = new();
@@ -108,5 +110,9 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc /> /// <inheritdoc />
public ConcurrentDictionary<Guid, WebSocketConnectionRegistry> WebSocketRegistries { get; } = new(); public ConcurrentDictionary<Guid, WebSocketConnectionRegistry> WebSocketRegistries { get; } = new();
/// <inheritdoc />
public WebSocketSettings? WebSocketSettings { get; set; } public WebSocketSettings? WebSocketSettings { get; set; }
/// <inheritdoc />
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
} }
@@ -27,6 +27,7 @@ internal static class WireMockMiddlewareOptionsHelper
options.FileSystemHandler = settings.FileSystemHandler; options.FileSystemHandler = settings.FileSystemHandler;
options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
options.Logger = settings.Logger; options.Logger = settings.Logger;
options.ScenarioStateStore = settings.ScenarioStateStore;
options.MaxRequestLogCount = settings.MaxRequestLogCount; options.MaxRequestLogCount = settings.MaxRequestLogCount;
options.PostWireMockMiddlewareInit = settings.PostWireMockMiddlewareInit; options.PostWireMockMiddlewareInit = settings.PostWireMockMiddlewareInit;
options.PreWireMockMiddlewareInit = settings.PreWireMockMiddlewareInit; options.PreWireMockMiddlewareInit = settings.PreWireMockMiddlewareInit;
@@ -48,7 +48,8 @@ internal class ProxyHelper(WireMockServerSettings settings)
originalUri, originalUri,
deserializeJson, deserializeJson,
decompressGzipAndDeflate, decompressGzipAndDeflate,
deserializeFormUrlEncoded deserializeFormUrlEncoded,
_settings.DefaultJsonSerializer
).ConfigureAwait(false); ).ConfigureAwait(false);
IMapping? newMapping = null; IMapping? newMapping = null;
@@ -34,7 +34,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
/// Initializes a new instance of the <see cref="Request"/> class. /// Initializes a new instance of the <see cref="Request"/> class.
/// </summary> /// </summary>
/// <param name="requestMatchers">The request matchers.</param> /// <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); _requestMatchers = Guard.NotNull(requestMatchers);
} }
@@ -81,6 +81,13 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
return this; return this;
} }
/// <inheritdoc />
public IRequestBuilder WithEarlyMismatch(RequestMatcherType? earlyMatcherType)
{
EarlyMatcherType = earlyMatcherType;
return this;
}
internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher) internal bool TryGetProtoBufMatcher([NotNullWhen(true)] out IProtoBufMatcher? protoBufMatcher)
{ {
protoBufMatcher = GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher; protoBufMatcher = GetRequestMessageMatcher<RequestMessageProtoBufMatcher>()?.Matcher;
@@ -2,8 +2,10 @@
using System.Text; using System.Text;
using JsonConverter.Abstractions; using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using Stef.Validation; using Stef.Validation;
using WireMock.Models; using WireMock.Models;
using WireMock.Serialization;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
@@ -119,11 +121,13 @@ public partial class Response
} }
/// <inheritdoc /> /// <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); Guard.NotNull(body);
encoding ??= Encoding.UTF8; encoding ??= Encoding.UTF8;
jsonConverter ??= new NewtonsoftJsonConverter();
options ??= JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone;
ResponseMessage.BodyDestination = destination; ResponseMessage.BodyDestination = destination;
ResponseMessage.BodyData = new BodyData ResponseMessage.BodyData = new BodyData
@@ -140,7 +144,7 @@ public partial class Response
case BodyDestinationFormat.Json: case BodyDestinationFormat.Json:
ResponseMessage.BodyData.DetectedBodyType = BodyType.Json; ResponseMessage.BodyData.DetectedBodyType = BodyType.Json;
ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body); ResponseMessage.BodyData.BodyAsJson = jsonConverter.Deserialize<object>(body, options);
break; break;
default: default:
@@ -197,7 +197,7 @@ public partial class Response : IResponseBuilder
if (ProxyAndRecordSettings != null && _httpClientForProxy != null) 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); int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase);
return place >= 0 ? source.Remove(place, find.Length) : source; 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); var decoded = await protoBufMatcher.DecodeAsync(requestMessage.BodyData?.BodyAsBytes).ConfigureAwait(false);
if (decoded != null) if (decoded != null)
{ {
requestMessageImplementation.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded); requestMessageImplementation.BodyAsJson = settings.DefaultJsonSerializer.ToJsonToken(decoded);
} }
} }
} }
@@ -9,29 +9,30 @@ using WireMock.Util;
namespace WireMock; 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>> private static readonly IDictionary<string, WireMockList<string>> ContentTypeJsonHeaders = new Dictionary<string, WireMockList<string>>
{ {
{ HttpKnownHeaderNames.ContentType, new WireMockList<string> { WireMockConstants.ContentTypeJson } } { 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); 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 var response = new ResponseMessage
{ {
StatusCode = statusCode, StatusCode = statusCode,
Headers = ContentTypeJsonHeaders Headers = ContentTypeJsonHeaders,
DateTime = dateTimeUtils.UtcNow
}; };
if (status != null || error != null) if (status != null || error != null)
@@ -51,7 +52,7 @@ internal static class ResponseMessageBuilder
return response; return response;
} }
internal static ResponseMessage Create(HttpStatusCode statusCode) public ResponseMessage Create(HttpStatusCode statusCode)
{ {
return new ResponseMessage return new ResponseMessage
{ {
@@ -16,7 +16,7 @@ using WireMock.WebSockets;
namespace WireMock.ResponseProviders; 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( public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(
IMapping mapping, IMapping mapping,
@@ -27,7 +27,7 @@ internal class WebSocketResponseProvider(WebSocketBuilder builder, IGuidUtils gu
// Check if this is a WebSocket upgrade request // Check if this is a WebSocket upgrade request
if (!context.WebSockets.IsWebSocketRequest) 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)) 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 we haven't upgraded yet, we can return HTTP error
if (!context.Response.HasStarted) 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 // Already upgraded - return marker
@@ -66,6 +66,12 @@ internal class MappingConverter(MatcherMapper mapper)
// Request // Request
sb.AppendLine(" .Given(Request.Create()"); 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)})"); sb.AppendLine($" .UsingMethod({To1Or2Or3Arguments(methodMatcher?.MatchBehaviour, methodMatcher?.MatchOperator, methodMatcher?.Methods, HttpRequestMethod.GET)})");
if (pathMatcher?.Matchers != null) if (pathMatcher?.Matchers != null)
@@ -275,6 +281,7 @@ internal class MappingConverter(MatcherMapper mapper)
TimesInSameState = !string.IsNullOrWhiteSpace(mapping.NextState) ? mapping.TimesInSameState : null, TimesInSameState = !string.IsNullOrWhiteSpace(mapping.NextState) ? mapping.TimesInSameState : null,
Data = mapping.Data, Data = mapping.Data,
Probability = mapping.Probability, Probability = mapping.Probability,
IsDisabled = mapping.IsDisabled ? true : null,
Request = new RequestModel Request = new RequestModel
{ {
Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel Headers = headerMatchers.Any() ? headerMatchers.Select(hm => new HeaderModel
@@ -299,7 +306,9 @@ internal class MappingConverter(MatcherMapper mapper)
IgnoreCase = pm.IgnoreCase ? true : null, IgnoreCase = pm.IgnoreCase ? true : null,
RejectOnMatch = pm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null, RejectOnMatch = pm.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null,
Matchers = _mapper.Map(pm.Matchers) Matchers = _mapper.Map(pm.Matchers)
}).ToList() : null }).ToList() : null,
EarlyMatcherType = request.EarlyMatcherType
}, },
Response = new ResponseModel() Response = new ResponseModel()
}; };
@@ -1,10 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using JsonConverter.Abstractions; using JsonConverter.Abstractions;
using Newtonsoft.Json.Linq; using JsonConverter.Abstractions.Models;
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461
using System.Text.Json;
#endif
namespace WireMock.Serialization; namespace WireMock.Serialization;
@@ -12,37 +9,23 @@ internal class MappingSerializer(IJsonConverter jsonConverter)
{ {
internal T[] DeserializeJsonToArray<T>(string value) 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)!;
internal static T[] DeserializeObjectToArray<T>(object value) case JsonType.Object:
{ var singleResult = jsonConverter.Deserialize<T>(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
if (value is JArray jArray)
{
return jArray.ToObject<T[]>()!;
}
if (value is JObject jObject)
{
var singleResult = jObject.ToObject<T>();
return [singleResult!]; return [singleResult!];
}
#if NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET6_0_OR_GREATER || NET461 default:
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."); throw new InvalidOperationException("Cannot deserialize the provided value to an array or object.");
} }
} }
internal T[] DeserializeObjectToArray<T>(object value)
{
var json = jsonConverter.Serialize(value, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
return DeserializeJsonToArray<T>(json);
}
}
@@ -106,9 +106,29 @@ internal class MatcherMapper
var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns; var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns;
return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex); 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): case nameof(JsonPathMatcher):
return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns); 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): case nameof(JmesPathMatcher):
return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns); return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns);
@@ -171,6 +191,10 @@ internal class MatcherMapper
model.Regex = jsonMatcher.Regex; model.Regex = jsonMatcher.Regex;
break; break;
case SystemTextJsonMatcher stjMatcher:
model.Regex = stjMatcher.Regex;
break;
case XPathMatcher xpathMatcher: case XPathMatcher xpathMatcher:
model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap; model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap;
break; break;
@@ -1,6 +1,8 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq; using System.Linq;
using System.Text;
using Newtonsoft.Json;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Extensions; using WireMock.Extensions;
using WireMock.Pact.Models.V2; using WireMock.Pact.Models.V2;
@@ -49,7 +51,7 @@ internal static class PactMapper
pact.Interactions.Add(interaction); pact.Interactions.Add(interaction);
} }
return (filename, JsonUtils.SerializeAsPactFile(pact)); return (filename, SerializeAsPactFile(pact));
} }
private static PactRequest MapRequest(RequestModel request, string path) private static PactRequest MapRequest(RequestModel request, string path)
@@ -152,7 +154,7 @@ internal static class PactMapper
/// </summary> /// </summary>
private static object? TryDeserializeJsonStringAsObject(string? value) 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) //private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
@@ -164,4 +166,22 @@ internal static class PactMapper
// return defaultValue; // 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 // Copyright © WireMock.Net
using System.Linq; using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NJsonSchema; using NJsonSchema;
using NJsonSchema.Extensions; using NJsonSchema.Extensions;
using NSwag; using NSwag;
@@ -281,7 +282,7 @@ internal static class SwaggerMapper
if (matcher is { Name: nameof(JsonMatcher) }) if (matcher is { Name: nameof(JsonMatcher) })
{ {
var pattern = GetPatternAsStringFromMatcher(matcher); var pattern = GetPatternAsStringFromMatcher(matcher);
if (JsonUtils.TryParseAsJObject(pattern, out var jObject)) if (TryParseAsJObject(pattern, out var jObject))
{ {
return jObject; return jObject;
} }
@@ -292,6 +293,39 @@ internal static class SwaggerMapper
return null; 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) private static string GetContentType(RequestModel request)
{ {
var contentType = request.Headers?.FirstOrDefault(h => h.Name == "Content-Type"); var contentType = request.Headers?.FirstOrDefault(h => h.Name == "Content-Type");
@@ -234,6 +234,13 @@ public interface IRespondWithAProvider
/// <returns>The <see cref="IRespondWithAProvider"/>.</returns> /// <returns>The <see cref="IRespondWithAProvider"/>.</returns>
IRespondWithAProvider WithProbability(double probability); 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> /// <summary>
/// Define a Grpc ProtoDefinition which is used for the request and the response. /// 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. /// 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 WireMockServerSettings _settings;
private readonly IDateTimeUtils _dateTimeUtils; private readonly IDateTimeUtils _dateTimeUtils;
private readonly IGuidUtils _guidUtils; private readonly IGuidUtils _guidUtils;
private readonly IResponseMessageBuilder _responseMessageBuilder;
private readonly bool _saveToFile; private readonly bool _saveToFile;
@@ -37,6 +38,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
private int _timesInSameState = 1; private int _timesInSameState = 1;
private bool? _useWebhookFireAndForget; private bool? _useWebhookFireAndForget;
private double? _probability; private double? _probability;
private bool _isDisabled = false;
private GraphQLSchemaDetails? _graphQLSchemaDetails; // Future Use. private GraphQLSchemaDetails? _graphQLSchemaDetails; // Future Use.
public Guid Guid { get; private set; } public Guid Guid { get; private set; }
@@ -55,6 +57,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
WireMockServerSettings settings, WireMockServerSettings settings,
IGuidUtils guidUtils, IGuidUtils guidUtils,
IDateTimeUtils dateTimeUtils, IDateTimeUtils dateTimeUtils,
IResponseMessageBuilder responseMessageBuilder,
bool saveToFile = false bool saveToFile = false
) )
{ {
@@ -63,6 +66,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
_settings = Guard.NotNull(settings); _settings = Guard.NotNull(settings);
_dateTimeUtils = Guard.NotNull(dateTimeUtils); _dateTimeUtils = Guard.NotNull(dateTimeUtils);
_guidUtils = Guard.NotNull(guidUtils); _guidUtils = Guard.NotNull(guidUtils);
_responseMessageBuilder = Guard.NotNull(responseMessageBuilder);
_saveToFile = saveToFile; _saveToFile = saveToFile;
@@ -78,7 +82,8 @@ internal class RespondWithAProvider : IRespondWithAProvider
provider = new WebSocketResponseProvider( provider = new WebSocketResponseProvider(
response.WebSocketBuilder, response.WebSocketBuilder,
_guidUtils, _guidUtils,
_dateTimeUtils _dateTimeUtils,
_responseMessageBuilder
); );
} }
@@ -108,6 +113,11 @@ internal class RespondWithAProvider : IRespondWithAProvider
mapping.WithProbability(_probability.Value); mapping.WithProbability(_probability.Value);
} }
if (_isDisabled)
{
mapping.IsDisabled = true;
}
if (ProtoDefinition != null) if (ProtoDefinition != null)
{ {
mapping.WithProtoDefinition(ProtoDefinition.Value); mapping.WithProtoDefinition(ProtoDefinition.Value);
@@ -354,6 +364,13 @@ internal class RespondWithAProvider : IRespondWithAProvider
return this; return this;
} }
/// <inheritdoc />
public IRespondWithAProvider WithIsDisabled(bool isDisabled)
{
_isDisabled = isDisabled;
return this;
}
/// <inheritdoc /> /// <inheritdoc />
public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId) public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId)
{ {
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -26,9 +25,6 @@ using WireMock.Util;
namespace WireMock.Server; namespace WireMock.Server;
/// <summary>
/// The fluent mock server.
/// </summary>
public partial class WireMockServer public partial class WireMockServer
{ {
private const int EnhancedFileSystemWatcherTimeoutMs = 1000; private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
@@ -61,6 +57,8 @@ public partial class WireMockServer
public string OpenApi => $"{_prefix}/openapi"; 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 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 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 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\\/.+$"); 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).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)); 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} // __admin/mappings/code/{guid}
Given(Request.Create().WithPath(_adminPaths.MappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet)); Given(Request.Create().WithPath(_adminPaths.MappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet));
@@ -346,7 +350,7 @@ public partial class WireMockServer
o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate; o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
}); });
return ResponseMessageBuilder.Create(200, "Settings updated"); return _responseMessageBuilder.Create(200, "Settings updated");
} }
#endregion Settings #endregion Settings
@@ -357,7 +361,7 @@ public partial class WireMockServer
if (mapping == null) if (mapping == null)
{ {
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); _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); var model = _mappingConverter.ToMappingModel(mapping);
@@ -373,14 +377,14 @@ public partial class WireMockServer
if (code is null) if (code is null)
{ {
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); _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); return ToResponseMessage(code);
} }
_settings.Logger.Warn("HttpStatusCode set to 400"); _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) private static TEnum GetEnumFromQuery<TEnum>(IRequestMessage requestMessage, TEnum defaultValue)
@@ -407,22 +411,22 @@ public partial class WireMockServer
var mappingModel = DeserializeObject<MappingModel>(requestMessage); var mappingModel = DeserializeObject<MappingModel>(requestMessage);
var guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); 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"); _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) private IResponseMessage MappingDelete(HttpContext _, IRequestMessage requestMessage)
{ {
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid)) 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"); _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) private static bool TryParseGuidFromRequestMessage(IRequestMessage requestMessage, out Guid guid)
@@ -430,6 +434,47 @@ public partial class WireMockServer
var lastPart = requestMessage.Path.Split('/').LastOrDefault(); var lastPart = requestMessage.Path.Split('/').LastOrDefault();
return Guid.TryParse(lastPart, out guid); 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} #endregion Mapping/{guid}
#region Mappings #region Mappings
@@ -451,7 +496,7 @@ public partial class WireMockServer
{ {
SaveStaticMappings(); SaveStaticMappings();
return ResponseMessageBuilder.Create(200, "Mappings saved to disk"); return _responseMessageBuilder.Create(200, "Mappings saved to disk");
} }
private MappingModel[] ToMappingModels() private MappingModel[] ToMappingModels()
@@ -481,22 +526,22 @@ public partial class WireMockServer
if (mappingModels.Length == 1) if (mappingModels.Length == 1)
{ {
var guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]); var guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]);
return ResponseMessageBuilder.Create(201, "Mapping added", guid); return _responseMessageBuilder.Create(201, "Mapping added", guid);
} }
ConvertMappingsAndRegisterAsRespondProvider(mappingModels); ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create(201, "Mappings added"); return _responseMessageBuilder.Create(201, "Mappings added");
} }
catch (ArgumentException a) catch (ArgumentException a)
{ {
_settings.Logger.Error("HttpStatusCode set to 400 {0}", 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) catch (Exception e)
{ {
_settings.Logger.Error("HttpStatusCode set to 500 {0}", 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); var deletedGuids = MappingsDeleteMappingFromBody(requestMessage);
if (deletedGuids != null) 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 bad request
return ResponseMessageBuilder.Create(400, "Poorly formed mapping JSON."); return _responseMessageBuilder.Create(400, "Poorly formed mapping JSON.");
} }
ResetMappings(); ResetMappings();
ResetScenarios(); ResetScenarios();
return ResponseMessageBuilder.Create(200, "Mappings deleted"); return _responseMessageBuilder.Create(200, "Mappings deleted");
} }
private IEnumerable<Guid>? MappingsDeleteMappingFromBody(IRequestMessage requestMessage) private IEnumerable<Guid>? MappingsDeleteMappingFromBody(IRequestMessage requestMessage)
@@ -570,14 +615,14 @@ public partial class WireMockServer
message += " and static mappings reloaded"; message += " and static mappings reloaded";
} }
return ResponseMessageBuilder.Create(200, message); return _responseMessageBuilder.Create(200, message);
} }
private IResponseMessage ReloadStaticMappings(HttpContext _, IRequestMessage __) private IResponseMessage ReloadStaticMappings(HttpContext _, IRequestMessage __)
{ {
ReadStaticMappings(); ReadStaticMappings();
return ResponseMessageBuilder.Create(200, "Static Mappings reloaded"); return _responseMessageBuilder.Create(200, "Static Mappings reloaded");
} }
#endregion Mappings #endregion Mappings
@@ -595,18 +640,18 @@ public partial class WireMockServer
} }
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); _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) private IResponseMessage RequestDelete(HttpContext _, IRequestMessage requestMessage)
{ {
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid)) 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"); _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} #endregion Request/{guid}
@@ -625,7 +670,7 @@ public partial class WireMockServer
{ {
ResetLogEntries(); ResetLogEntries();
return ResponseMessageBuilder.Create(200, "Requests deleted"); return _responseMessageBuilder.Create(200, "Requests deleted");
} }
#endregion Requests #endregion Requests
@@ -665,14 +710,14 @@ public partial class WireMockServer
return ToJson(result); return ToJson(result);
} }
return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest); return _responseMessageBuilder.Create(HttpStatusCode.BadRequest);
} }
#endregion Requests/find #endregion Requests/find
#region Scenarios #region Scenarios
private IResponseMessage ScenariosGet(HttpContext _, IRequestMessage requestMessage) private IResponseMessage ScenariosGet(HttpContext _, IRequestMessage requestMessage)
{ {
var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel var scenariosStates = Scenarios.Select(s => new ScenarioStateModel
{ {
Name = s.Name, Name = s.Name,
NextState = s.NextState, NextState = s.NextState,
@@ -688,7 +733,7 @@ public partial class WireMockServer
{ {
ResetScenarios(); ResetScenarios();
return ResponseMessageBuilder.Create(200, "Scenarios reset"); return _responseMessageBuilder.Create(200, "Scenarios reset");
} }
private IResponseMessage ScenarioReset(HttpContext _, IRequestMessage requestMessage) private IResponseMessage ScenarioReset(HttpContext _, IRequestMessage requestMessage)
@@ -698,23 +743,23 @@ public partial class WireMockServer
Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First(); Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First();
return ResetScenario(name) ? return ResetScenario(name) ?
ResponseMessageBuilder.Create(200, "Scenario reset") : _responseMessageBuilder.Create(200, "Scenario reset") :
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'."); _responseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
} }
private IResponseMessage ScenariosSetState(HttpContext _, IRequestMessage requestMessage) private IResponseMessage ScenariosSetState(HttpContext _, IRequestMessage requestMessage)
{ {
var name = Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First(); var name = Enumerable.Reverse(requestMessage.Path.Split('/')).Skip(1).First();
if (!_options.Scenarios.ContainsKey(name)) 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); var update = DeserializeObject<ScenarioStateUpdateModel>(requestMessage);
return SetScenarioState(name, update.State) ? return SetScenarioState(name, update.State) ?
ResponseMessageBuilder.Create(200, $"Scenario state set to '{update.State}'") : _responseMessageBuilder.Create(200, $"Scenario state set to '{update.State}'") :
ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'."); _responseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'.");
} }
#endregion #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) private static Encoding? ToEncoding(EncodingModel? encodingModel)
{ {
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
@@ -857,28 +914,16 @@ public partial class WireMockServer
}; };
} }
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage) private T DeserializeObject<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)
{ {
switch (requestMessage.BodyData?.DetectedBodyType) switch (requestMessage.BodyData?.DetectedBodyType)
{ {
case BodyType.String: case BodyType.String when requestMessage.BodyData?.BodyAsString != null:
case BodyType.FormUrlEncoded: case BodyType.FormUrlEncoded when requestMessage.BodyData?.BodyAsString != null:
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString!); return _settings.DefaultJsonSerializer.Deserialize<T>(requestMessage.BodyData.BodyAsString)!;
case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null: case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null:
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>()!; return _settings.DefaultJsonSerializer.ParseJsonToken<T>(requestMessage.BodyData.BodyAsJson)!;
default: default:
throw new NotSupportedException(); throw new NotSupportedException();
@@ -18,14 +18,14 @@ public partial class WireMockServer
{ {
if (requestMessage.Body is null) 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(); var id = requestMessage.Path.Split('/').Last();
AddProtoDefinition(id, requestMessage.Body); AddProtoDefinition(id, requestMessage.Body);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "ProtoDefinition added"); return _responseMessageBuilder.Create(HttpStatusCode.OK, "ProtoDefinition added");
} }
#endregion #endregion
@@ -34,7 +34,7 @@ public partial class WireMockServer
{ {
if (requestMessage.BodyAsBytes is null) 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); var filename = GetFileNameFromRequestMessage(requestMessage);
@@ -47,14 +47,14 @@ public partial class WireMockServer
_settings.FileSystemHandler.WriteFile(filename, requestMessage.BodyAsBytes); _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) private IResponseMessage FilePut(HttpContext _, IRequestMessage requestMessage)
{ {
if (requestMessage.BodyAsBytes is null) 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); var filename = GetFileNameFromRequestMessage(requestMessage);
@@ -62,12 +62,12 @@ public partial class WireMockServer
if (!_settings.FileSystemHandler.FileExists(filename)) if (!_settings.FileSystemHandler.FileExists(filename))
{ {
_settings.Logger.Info("The file '{0}' does not exist, updating file will be skipped.", 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); _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) private IResponseMessage FileGet(HttpContext _, IRequestMessage requestMessage)
@@ -77,7 +77,7 @@ public partial class WireMockServer
if (!_settings.FileSystemHandler.FileExists(filename)) if (!_settings.FileSystemHandler.FileExists(filename))
{ {
_settings.Logger.Info("The file '{0}' does not exist.", 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); var bytes = _settings.FileSystemHandler.ReadFile(filename);
@@ -112,10 +112,10 @@ public partial class WireMockServer
if (!_settings.FileSystemHandler.FileExists(filename)) if (!_settings.FileSystemHandler.FileExists(filename))
{ {
_settings.Logger.Info("The file '{0}' does not exist.", 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) private IResponseMessage FileDelete(HttpContext _, IRequestMessage requestMessage)
@@ -125,11 +125,11 @@ public partial class WireMockServer
if (!_settings.FileSystemHandler.FileExists(filename)) if (!_settings.FileSystemHandler.FileExists(filename))
{ {
_settings.Logger.Info("The file '{0}' does not exist.", 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); _settings.FileSystemHandler.DeleteFile(filename);
return ResponseMessageBuilder.Create(HttpStatusCode.OK, "File deleted."); return _responseMessageBuilder.Create(HttpStatusCode.OK, "File deleted.");
} }
private string GetFileNameFromRequestMessage(IRequestMessage requestMessage) private string GetFileNameFromRequestMessage(IRequestMessage requestMessage)
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Matchers; using WireMock.Matchers;
@@ -120,6 +119,11 @@ public partial class WireMockServer
respondProvider.WithProbability(mappingModel.Probability.Value); respondProvider.WithProbability(mappingModel.Probability.Value);
} }
if (mappingModel.IsDisabled == true)
{
respondProvider.WithIsDisabled(true);
}
// ProtoDefinition is defined at Mapping level // ProtoDefinition is defined at Mapping level
if (mappingModel.ProtoDefinition != null) if (mappingModel.ProtoDefinition != null)
{ {
@@ -148,7 +152,7 @@ public partial class WireMockServer
} }
else else
{ {
var clientIPModel = JsonUtils.ParseJTokenToObject<ClientIPModel>(requestModel.ClientIP); var clientIPModel = _settings.DefaultJsonSerializer.ParseJsonToken<ClientIPModel>(requestModel.ClientIP);
if (clientIPModel.Matchers != null) if (clientIPModel.Matchers != null)
{ {
requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray()); requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
@@ -164,7 +168,7 @@ public partial class WireMockServer
} }
else else
{ {
var pathModel = JsonUtils.ParseJTokenToObject<PathModel>(requestModel.Path); var pathModel = _settings.DefaultJsonSerializer.ParseJsonToken<PathModel>(requestModel.Path);
if (pathModel.Matchers != null) if (pathModel.Matchers != null)
{ {
var matchOperator = StringUtils.ParseMatchOperator(pathModel.MatchOperator); var matchOperator = StringUtils.ParseMatchOperator(pathModel.MatchOperator);
@@ -180,7 +184,7 @@ public partial class WireMockServer
} }
else else
{ {
var urlModel = JsonUtils.ParseJTokenToObject<UrlModel>(requestModel.Url); var urlModel = _settings.DefaultJsonSerializer.ParseJsonToken<UrlModel>(requestModel.Url);
if (urlModel.Matchers != null) if (urlModel.Matchers != null)
{ {
var matchOperator = StringUtils.ParseMatchOperator(urlModel.MatchOperator); var matchOperator = StringUtils.ParseMatchOperator(urlModel.MatchOperator);
@@ -263,10 +267,12 @@ public partial class WireMockServer
} }
} }
requestBuilder = requestBuilder.WithEarlyMismatch(requestModel.EarlyMatcherType);
return requestBuilder; return requestBuilder;
} }
private static IResponseBuilder InitResponseBuilder(ResponseModel responseModel) private IResponseBuilder InitResponseBuilder(ResponseModel responseModel)
{ {
var responseBuilder = Response.Create(); var responseBuilder = Response.Create();
@@ -331,7 +337,7 @@ public partial class WireMockServer
} }
else else
{ {
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value); var headers = _settings.DefaultJsonSerializer.ParseJsonToken<string[]>(entry.Value);
responseBuilder.WithHeader(entry.Key, headers); responseBuilder.WithHeader(entry.Key, headers);
} }
} }
@@ -357,7 +363,7 @@ public partial class WireMockServer
} }
else else
{ {
var headers = JsonUtils.ParseJTokenToObject<string[]>(entry.Value); var headers = _settings.DefaultJsonSerializer.ParseJsonToken<string[]>(entry.Value);
responseBuilder.WithTrailingHeader(entry.Key, headers); responseBuilder.WithTrailingHeader(entry.Key, headers);
} }
} }

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