Compare commits

...
67 Commits
Author SHA1 Message Date
Stef Heyenrath dc4cdfae50 2.18.0 2026-09-22 22:21:50 +02:00
Stef Heyenrath f19d94acae 2.18.0 2026-09-22 22:18:47 +02:00
Stef Heyenrath 90819f031e WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly (#1510) 2026-09-22 22:16:13 +02:00
Stef Heyenrath 471745f704 2.17.0 2026-09-21 20:53:22 +02:00
Stef Heyenrath 7db5b562d8 Revert to RamlToOpenApiConverter.SourceOnly (#1509) 2026-09-21 20:50:49 +02:00
Stef Heyenrath e4389333a1 2.16.0 2026-09-19 07:54:10 +02:00
Stef HeyenrathandCopilot Autofix powered by AI f0969e9618 Upgrade dependency RamlToOpenApiConverter to version 0.40.0 (#1505)
* Upgrade dependency RamlToOpenApiConverter to version 0.40.0

* fix code comment

* .

* Potential fix for pull request finding 'Constant condition'

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

* Potential fix for pull request finding 'Constant condition'

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

* fix

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-09-10 20:28:43 +02:00
John Call 84f4109a7c Update SharpYaml package version to 2.1.5 (#1504)
Fix https://github.com/xoofx/SharpYaml/issues/130
2026-09-05 08:54:06 +02:00
ee0f890795 Fix FormUrlEncodedMatcher (+ refactor values / matchers logic) (#1503)
* Fix FormUrlEncodedMatcher

* tests

* Apply suggestions from code review

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

* fix comments

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-20 17:31:16 +02:00
Dmytro Nikitin 2b165eb6a1 Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode (#1502)
* Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode

* Fix matcher to code converters. Refactor changes.

* Adjust test artifacts.

* Return fully qualified class names for matchers
2026-08-19 19:58:47 +02:00
Stef Heyenrath 42353f035e 2.15.0 2026-08-15 09:51:50 +02:00
Stef Heyenrath 7e9718eab7 Update Testcontainers (#1499) 2026-08-14 16:55:57 +02:00
Dmytro Nikitin a0df0916f9 Fix #1496: WithParam RejectOnMatch inverting the match result (#1497) 2026-08-13 20:42:06 +02:00
Dmytro Nikitin 7a8582fef5 Fix #1494: WithClientIP(MatchOperator, IStringMatcher[]) ignores the operator (#1495) 2026-08-09 18:49:13 +02:00
Stef Heyenrath 05929bdea6 remove copilot-setup-steps.yml 2026-08-06 20:15:30 +02:00
Stef Heyenrath 84c32d5b2d 2.14.0 2026-08-06 19:32:33 +02:00
Dmytro Nikitin d39f7c771f Fix #1492: Serialize WithClientIP matcher to Request.ClientIP (#1493) 2026-08-06 19:28:40 +02:00
Stef Heyenrath 97c8e4a08a 2.13.0 2026-07-19 17:04:12 +02:00
Stef Heyenrath f5eb01c580 2.13.0 2026-07-19 09:59:27 +02:00
Stef Heyenrath 8260dba641 Add some more tests for WireMockList + move AdminPaths.cs file (#1491) 2026-07-18 09:48:41 +02:00
Stef Heyenrath 5e1c126e34 Pipeline parameter : runWindowsReleaseToMyGet 2026-07-18 09:48:23 +02:00
ca4eea808e Fix: Fully respect configured custom admin path (merge PR #1483) (#1490)
* Fix: Fully respect configured custom admin path

* Add tests for custom admin path behaviour

* Potential fix for pull request finding

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

---------

Co-authored-by: Peter Benko <peter.benko@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Stef Heyenrath <Stef.Heyenrath@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-18 09:01:04 +02:00
Stef Heyenrath 5692498ea3 Swagger (#1489)
* Add openapi swagger yml

* fix

* version

* reloadStaticMappings
2026-07-17 18:59:19 +02:00
СтепанandМинин Степан Александрович d270ad7f4f feat(xunit logging): TestOutputHelperWireMockLogger (#1488)
* feat(xunit logging): TestOutputHelperWireMockLogger

allow to provide current test output helper

* fix(ctor): TestOutputHelperWireMockLogger

made factory return nullable

---------

Co-authored-by: Минин Степан Александрович <minin_s@goldapple.ru>
2026-07-17 18:25:25 +02:00
Stef Heyenrath 345efe8cfc 2.12.0 2026-07-07 21:38:12 +02:00
Stef Heyenrath a74468e780 When Response.Create() is called without a pre-built ResponseMessage, use the DateTime.UtcNow (#1487) 2026-07-07 21:36:11 +02:00
John Call 041160d824 Update Scriban.Signed package version to 7.2.5 (#1484)
* Update Scriban.Signed package version to 7.2.6

* Downgrade Scriban.Signed package version to 7.2.5
2026-07-07 20:54:11 +02:00
Stef Heyenrath 4666757df9 Upgrade RamlToOpenApiConverter.SourceOnly to 0.21.0 (#1480) 2026-07-03 21:04:58 +02:00
Stef Heyenrath 49dcbda647 Upgrade Microsoft.OpenApi to 3.7.0 and YamlDotNet to 18.1.0 (#1479)
* Upgrade Microsoft.OpenApi to 3.7.0 and YamlDotNet to 18.1.0

* fix
2026-07-01 22:44:34 +02:00
Stef Heyenrath cbc28c235c 2.11.0 2026-06-12 20:09:27 +02:00
Stef Heyenrath dd1aa5139e Fix BodyParser (#1476) 2026-06-12 20:04:23 +02:00
Stef Heyenrath 89b3149732 Update NuGet packages (Aspire, MessagePack and more) (#1475) 2026-06-12 12:01:09 +02:00
Stef Heyenrath d2b794ec5a 2.10.0 2026-06-07 22:52:32 +02:00
Stef Heyenrath 921a3ebc63 Update JsonConverter to 0.13.0 (#1472) 2026-06-07 22:37:55 +02:00
Stef Heyenrath 428657a97e 2.9.0 2026-06-02 22:43:03 +02:00
Stef Heyenrath fe9745235a Fix WireMock.Net.Aspire project when using Aspire 13.4.0 (#1470) 2026-06-02 22:39:32 +02:00
Stef Heyenrath 1264df4a72 2.8.0 2026-05-31 09:24:23 +02:00
Stef Heyenrath 5b5d68be86 readme: Build GitHub 2026-05-30 09:13:18 +02:00
Stef Heyenrath e92041783c Update JsonMatcher to support IgnoreArrayOrder (#1468)
* Update JsonMatcher to support IgnoreArrayOrder

* more tests
2026-05-30 08:45:52 +02:00
dependabot[bot]andcopilot-swe-agent[bot] 6912d52faf Bump System.Text.RegularExpressions from 4.3.0 to 4.3.1 (#1466)
---
updated-dependencies:
- dependency-name: System.Text.RegularExpressions
  dependency-version: 4.3.1
  dependency-type: direct:production
...

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>
2026-05-29 20:53:08 +02:00
dependabot[bot] 3685fccfaf Bump System.Net.Http from 4.3.0 to 4.3.4 (#1467)
---
updated-dependencies:
- dependency-name: System.Net.Http
  dependency-version: 4.3.4
  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-29 20:40:44 +02:00
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
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
CopilotandStefH 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
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 VenkatesanandStef Heyenrath 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
257 changed files with 9897 additions and 2222 deletions

No files matched your search

+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
-36
View File
@@ -1,36 +0,0 @@
name: "Copilot Setup Steps"
# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest
# Set the permissions to the lowest permissions possible needed for your steps.
# Copilot will be given its own token for its operations.
permissions:
# If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete.
contents: read
# You can define any steps you want, and they will run before the agent starts.
# If you do not check out your code, Copilot will do this for you.
steps:
- name: Install .NET 10.x
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.x
dotnet-quality: preview
- name: dotnet --info
run: dotnet --info
+92
View File
@@ -1,3 +1,95 @@
# 2.18.0 (22 September 2026)
- [#1510](https://github.com/wiremock/WireMock.Net/pull/1510) - WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH)
- [#1508](https://github.com/wiremock/WireMock.Net/issues/1508) - Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug]
# 2.17.0 (21 September 2026)
- [#1509](https://github.com/wiremock/WireMock.Net/pull/1509) - Revert to RamlToOpenApiConverter.SourceOnly [bug] contributed by [StefH](https://github.com/StefH)
# 2.16.0 (19 September 2026)
- [#1502](https://github.com/wiremock/WireMock.Net/pull/1502) - Fix #1501: Generate WhenStateIs/WillSetStateTo correctly in ToCSharpCode contributed by [Bafyn](https://github.com/Bafyn)
- [#1503](https://github.com/wiremock/WireMock.Net/pull/1503) - Fix FormUrlEncodedMatcher (+ refactor values / matchers logic) contributed by [StefH](https://github.com/StefH)
- [#1504](https://github.com/wiremock/WireMock.Net/pull/1504) - Update SharpYaml package to version 2.5.1 [dependencies] contributed by [johnthcall](https://github.com/johnthcall)
- [#1505](https://github.com/wiremock/WireMock.Net/pull/1505) - Upgrade dependency RamlToOpenApiConverter to version 0.40.0 [dependencies] contributed by [StefH](https://github.com/StefH)
- [#1500](https://github.com/wiremock/WireMock.Net/issues/1500) - Sending a form-urlencoded body with a repeated field name returns HTTP 500 [bug]
- [#1501](https://github.com/wiremock/WireMock.Net/issues/1501) - Generated C# code for stateful (Scenario) mappings does not compile and drops the WhenStateIs condition [bug]
# 2.15.0 (15 August 2026)
- [#1495](https://github.com/wiremock/WireMock.Net/pull/1495) - Fix #1494: `WithClientIP(MatchOperator, params IStringMatcher[])` ignores the MatchOperator argument [bug] contributed by [Bafyn](https://github.com/Bafyn)
- [#1497](https://github.com/wiremock/WireMock.Net/pull/1497) - Fix WithParam RejectOnMatch inverting the match result #1496 [bug] contributed by [Bafyn](https://github.com/Bafyn)
- [#1499](https://github.com/wiremock/WireMock.Net/pull/1499) - Update Testcontainers [bug] contributed by [StefH](https://github.com/StefH)
- [#1494](https://github.com/wiremock/WireMock.Net/issues/1494) - `WithClientIP(MatchOperator, params IStringMatcher[])` ignores the `MatchOperator` argument [bug]
- [#1496](https://github.com/wiremock/WireMock.Net/issues/1496) - `WithParam(key, MatchBehaviour.RejectOnMatch, values)` inverts the match result [bug]
# 2.14.0 (06 August 2026)
- [#1493](https://github.com/wiremock/WireMock.Net/pull/1493) - Fix #1492: Serialize WithClientIP matcher to Request.ClientIP [bug] contributed by [Bafyn](https://github.com/Bafyn)
- [#1492](https://github.com/wiremock/WireMock.Net/issues/1492) - `WithClientIP` matcher is lost / corrupted when a mapping is serialized [bug]
# 2.13.0 (19 July 2026)
- [#1488](https://github.com/wiremock/WireMock.Net/pull/1488) - feat(xunit logging): TestOutputHelperWireMockLogger [feature] contributed by [Stepami](https://github.com/Stepami)
- [#1490](https://github.com/wiremock/WireMock.Net/pull/1490) - Fix: Fully respect configured custom admin path (merge PR #1483) [feature] contributed by [Copilot](https://github.com/apps/copilot-swe-agent)
- [#1482](https://github.com/wiremock/WireMock.Net/issues/1482) - WireMock.Minimal.Owin does not respect custom pathing for determining admin paths [bug]
# 2.12.0 (07 July 2026)
- [#1479](https://github.com/wiremock/WireMock.Net/pull/1479) - Upgrade Microsoft.OpenApi to 3.7.0 and YamlDotNet to 18.1.0 [security] contributed by [StefH](https://github.com/StefH)
- [#1480](https://github.com/wiremock/WireMock.Net/pull/1480) - Upgrade RamlToOpenApiConverter.SourceOnly to 0.21.0 [feature] contributed by [StefH](https://github.com/StefH)
- [#1484](https://github.com/wiremock/WireMock.Net/pull/1484) - Update Scriban.Signed package version to 7.2.5 [security] contributed by [johnthcall](https://github.com/johnthcall)
- [#1487](https://github.com/wiremock/WireMock.Net/pull/1487) - Response.Create() sets the DateTime to UtcNow [bug] contributed by [StefH](https://github.com/StefH)
- [#1486](https://github.com/wiremock/WireMock.Net/issues/1486) - ResponseMessage.DateTime defaults to DateTime.MinValue, causing premature log entry purging for *matched* requests [bug]
# 2.11.0 (12 June 2026)
- [#1475](https://github.com/wiremock/WireMock.Net/pull/1475) - Update NuGet packages (Aspire, MessagePack and more) [security] contributed by [StefH](https://github.com/StefH)
- [#1476](https://github.com/wiremock/WireMock.Net/pull/1476) - Fix BodyParser [bug] contributed by [StefH](https://github.com/StefH)
- [#1473](https://github.com/wiremock/WireMock.Net/issues/1473) - JsonPartialWildCardMatcher With DateTime Regex Stopped Matching (Regression?) [bug]
# 2.10.0 (07 June 2026)
- [#1472](https://github.com/wiremock/WireMock.Net/pull/1472) - Update JsonConverter to 0.13.0 [bug] contributed by [StefH](https://github.com/StefH)
# 2.9.0 (02 June 2026)
- [#1470](https://github.com/wiremock/WireMock.Net/pull/1470) - Fix WireMock.Net.Aspire project when using Aspire 13.4.0 [bug] contributed by [StefH](https://github.com/StefH)
- [#1469](https://github.com/wiremock/WireMock.Net/issues/1469) - WireMock.Net doesn't play well with Aspire 13.4.0 [bug]
# 2.8.0 (31 May 2026)
- [#1447](https://github.com/wiremock/WireMock.Net/pull/1447) - Add SystemTextJsonMatchers [feature] contributed by [StefH](https://github.com/StefH)
- [#1466](https://github.com/wiremock/WireMock.Net/pull/1466) - Bump System.Text.RegularExpressions from 4.3.0 to 4.3.1 in Aspire Example project [dependencies, .NET] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#1467](https://github.com/wiremock/WireMock.Net/pull/1467) - Bump System.Net.Http from 4.3.0 to 4.3.4 in Aspire Example project [dependencies, .NET] contributed by [dependabot[bot]](https://github.com/apps/dependabot)
- [#1468](https://github.com/wiremock/WireMock.Net/pull/1468) - Update JsonMatcher to support IgnoreArrayOrder [feature] contributed by [StefH](https://github.com/StefH)
- [#1439](https://github.com/wiremock/WireMock.Net/issues/1439) - JSON reference properties cannot be used when using System.Text.Json serialization [bug]
- [#1445](https://github.com/wiremock/WireMock.Net/issues/1445) - Configurable JSON DE-serialization support (Newtonsoft.Json vs System.Text.Json) [feature]
- [#1460](https://github.com/wiremock/WireMock.Net/issues/1460) - Option to ignore array order when comparing json body [feature]
# 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) # 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) - [#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) - [#1434](https://github.com/wiremock/WireMock.Net/pull/1434) - Upgrade Scriban.Signed [security] contributed by [StefH](https://github.com/StefH)
+3 -3
View File
@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<VersionPrefix>2.2.0</VersionPrefix> <VersionPrefix>2.18.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>
@@ -51,8 +51,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4" PrivateAssets="All" /> <PackageReference Include="JetBrains.Annotations" Version="2026.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.401" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
+1 -1
View File
@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes rem https://github.com/StefH/GitHubReleaseNotes
SET version=2.2.0 SET version=2.18.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%
+3 -3
View File
@@ -1,5 +1,5 @@
# 2.2.0 (30 March 2026) # 2.18.0 (22 September 2026)
- #1433 Add comments for ScenarioStateStore related code [refactor] - #1510 WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug]
- #1434 Upgrade Scriban.Signed [security] - #1508 Transitive Dependency Conflict with Microsoft.OpenApi in 2.16.0 [bug]
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
+4 -2
View File
@@ -39,8 +39,9 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| | | | | |
| ***Quality*** | &nbsp; | | ***Quality*** | &nbsp; |
| &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=61) | | &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=61) |
| &nbsp;&nbsp;**Build GitHub** | [![Run Tests](https://github.com/wiremock/WireMock.Net/actions/workflows/ci.yml/badge.svg)](https://github.com/wiremock/WireMock.Net/actions/workflows/ci.yml)
| &nbsp;&nbsp;**Quality** | [![Sonar Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=alert_status)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net) [![CodeFactor](https://www.codefactor.io/repository/github/wiremock/wiremock.net/badge)](https://www.codefactor.io/repository/github/wiremock/wiremock.net) | | &nbsp;&nbsp;**Quality** | [![Sonar Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=alert_status)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net) [![CodeFactor](https://www.codefactor.io/repository/github/wiremock/wiremock.net/badge)](https://www.codefactor.io/repository/github/wiremock/wiremock.net) |
| &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) | | &nbsp;&nbsp;**SonarQube** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/wiremock/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/wiremock/WireMock.Net)| | &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/wiremock/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/wiremock/WireMock.Net)|
| &nbsp;&nbsp;**TIOBE** | [TIOBE Quality Indicator](https://ticsdemo.tiobe.com/tiobeweb/DEMO/TqiDashboard.html#axes=Project(WireMock.Net),Sub()&metric=tqi) | &nbsp;&nbsp;**TIOBE** | [TIOBE Quality Indicator](https://ticsdemo.tiobe.com/tiobeweb/DEMO/TqiDashboard.html#axes=Project(WireMock.Net),Sub()&metric=tqi)
@@ -64,6 +65,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)
@@ -76,7 +78,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
<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}
+7
View File
@@ -1,3 +1,9 @@
parameters:
- name: runWindowsReleaseToMyGet
displayName: Run Windows Release to MyGet
type: boolean
default: false
variables: variables:
Prerelease: 'ci' Prerelease: 'ci'
buildId: "1$(Build.BuildId)" buildId: "1$(Build.BuildId)"
@@ -131,6 +137,7 @@ jobs:
- job: Windows_Release_to_MyGet - job: Windows_Release_to_MyGet
dependsOn: Windows_Build_Test dependsOn: Windows_Build_Test
condition: and(succeeded(), ${{ parameters.runWindowsReleaseToMyGet }})
pool: pool:
vmImage: 'windows-2025' vmImage: 'windows-2025'
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" /> <Sdk Name="Aspire.AppHost.Sdk" Version="13.4.0" />
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -18,7 +18,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" /> <PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" /> <Sdk Name="Aspire.AppHost.Sdk" Version="13.4.0" />
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -15,7 +15,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" /> <PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -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>
@@ -12,6 +12,8 @@
<PackageReference Include="Aspire.Hosting.Testing" Version="13.1.0" /> <PackageReference Include="Aspire.Hosting.Testing" Version="13.1.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0" /> <PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="xunit" Version="2.5.3" /> <PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup> </ItemGroup>
-2
View File
@@ -1,9 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using RestEase; using RestEase;
using WireMock.Admin.Settings; using WireMock.Admin.Settings;
@@ -9,9 +9,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.25.1" /> <PackageReference Include="Google.Protobuf" Version="3.33.5" />
<PackageReference Include="Grpc.Net.Client" Version="2.60.0" /> <PackageReference Include="Grpc.Net.Client" Version="2.76.0" />
<PackageReference Include="Grpc.Tools" Version="2.60.0"> <PackageReference Include="Grpc.Tools" Version="2.78.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -9,7 +9,7 @@ namespace WireMock.Net.Console.MimePart;
static class Program static class Program
{ {
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()!);
private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
static async Task Main(params string[] args) static async Task Main(params string[] args)
@@ -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>
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.IO;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Server; using WireMock.Server;
using WireMock.Settings; using WireMock.Settings;
@@ -1,14 +1,8 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq;
using System.Net; using System.Net;
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;
@@ -1115,7 +1109,7 @@ namespace WireMock.Net.ConsoleApplication
{ {
BodyData = new BodyData BodyData = new BodyData
{ {
BodyAsString = "random200or505:" + code + ", HeadersFromRequest = " + string.Join(",", request.Headers), BodyAsString = "random200or505:" + code + ", HeadersFromRequest = " + request.Headers != null ? string.Join(",", request.Headers!.Select(h => $"{h.Key}:{h.Value}")) : "none",
DetectedBodyType = BodyType.String, DetectedBodyType = BodyType.String,
}, },
StatusCode = code StatusCode = code
@@ -1,8 +1,6 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using log4net; using log4net;
using log4net.Config; using log4net.Config;
using log4net.Repository; using log4net.Repository;
@@ -12,8 +10,7 @@ namespace WireMock.Net.Console.NET8;
static class Program static class Program
{ {
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()!);
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
static async Task Main(params string[] args) static async Task Main(params string[] args)
{ {
@@ -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>
@@ -1,9 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Net.StandAlone; using WireMock.Net.StandAlone;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
@@ -16,7 +16,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="WireMock.Net" Version="1.25.0" /> <PackageReference Include="WireMock.Net" Version="2.15.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System;
using System.IO;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
@@ -6,18 +6,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" />
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'"> <ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="Microsoft.OpenApi" Version="2.3.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.OpenApi" Version="3.10.2" PrivateAssets="All" />
<!--<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" PrivateAssets="All" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="SharpYaml" Version="2.1.3" />-->
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -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,13 +32,10 @@
<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">
<HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="SimMetrics.Net, Version=1.0.5.0, Culture=neutral, PublicKeyToken=c58dc06d59f3391b, processorArchitecture=MSIL">
<HintPath>..\..\packages\SimMetrics.Net.1.0.5\lib\net45\SimMetrics.Net.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" /> <Reference Include="System.Configuration.Install" />
@@ -88,13 +85,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="log4net"> <PackageReference Include="log4net">
<Version>3.3.0</Version> <Version>3.3.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core"> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core">
<Version>2.3.9</Version> <Version>2.3.11</Version>
</PackageReference> </PackageReference>
<PackageReference Include="WireMock.Net"> <PackageReference Include="WireMock.Net">
<Version>1.25.0</Version> <Version>2.12.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
@@ -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;
@@ -11,11 +11,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
</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,12 +1,17 @@
<?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
--> -->
<system.webServer> <system.webServer>
<handlers> <handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers> </handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" /> <httpProtocol>
</system.webServer> <customHeaders>
<add name="X-Frame-Options" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
</system.webServer>
</configuration> </configuration>
@@ -55,12 +55,17 @@ public class MappingModel
/// In case the value is null state will not be changed. /// In case the value is null state will not be changed.
/// </summary> /// </summary>
public string? SetStateTo { get; set; } public string? SetStateTo { get; set; }
/// <summary> /// <summary>
/// The number of times this match should be matched before the state will be changed to the specified one. /// The number of times this match should be matched before the state will be changed to the specified one.
/// </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>
@@ -100,7 +105,7 @@ public class MappingModel
/// </summary> /// </summary>
public object? Data { get; set; } public object? Data { get; set; }
/// <summary> /// <summary>
/// The probability when this request should be matched. Value is between 0 and 1. [Optional] /// The probability when this request should be matched. Value is between 0 and 1. [Optional]
/// </summary> /// </summary>
public double? Probability { get; set; } public double? Probability { get; set; }
@@ -1,5 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using WireMock.Matchers.Request;
namespace WireMock.Admin.Mappings; namespace WireMock.Admin.Mappings;
/// <summary> /// <summary>
@@ -61,9 +63,15 @@ public class RequestModel
/// Gets or sets the Params. /// Gets or sets the Params.
/// </summary> /// </summary>
public IList<ParamModel>? Params { get; set; } public IList<ParamModel>? Params { get; set; }
/// <summary> /// <summary>
/// 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; }
} }
@@ -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>
@@ -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
}
@@ -46,7 +46,7 @@ public interface IBodyData
/// <summary> /// <summary>
/// The body as Form UrlEncoded dictionary. /// The body as Form UrlEncoded dictionary.
/// </summary> /// </summary>
IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; } IDictionary<string, WireMockList<string>>? BodyAsFormUrlEncoded { get; set; }
/// <summary> /// <summary>
/// The detected body type (detection based on body content). /// The detected body type (detection based on body content).
@@ -1,11 +1,11 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
namespace WireMock.Types; namespace WireMock.Types;
/// <summary> /// <summary>
/// A special List which overrides the ToString() to return first value. /// A special List which overrides the ToString() to return first value in case of a single element.
/// Else it will return a comma separated list of all values.
/// If null or empty, it will return an empty string.
/// </summary> /// </summary>
/// <typeparam name="T">The generic type</typeparam> /// <typeparam name="T">The generic type</typeparam>
/// <seealso cref="List{T}" /> /// <seealso cref="List{T}" />
@@ -46,6 +46,98 @@ public class WireMockList<T> : List<T>
/// <param name="values">The values to set.</param> /// <param name="values">The values to set.</param>
public static implicit operator WireMockList<T>(T[] values) => new(values); public static implicit operator WireMockList<T>(T[] values) => new(values);
/// <summary>
/// Operator for equality comparison from WireMockList to T
/// </summary>
public static bool operator ==(WireMockList<T>? left, T? right)
{
if (ReferenceEquals(left, right))
{
return true;
}
if (left?.Count == 1 && Equals(left[0], right))
{
return true;
}
return false;
}
/// <summary>
/// Operator for equality comparison from T to WireMockList
/// </summary>
public static bool operator ==(T? left, WireMockList<T>? right)
{
if (ReferenceEquals(left, right))
{
return true;
}
if (right?.Count == 1 && Equals(left, right[0]))
{
return true;
}
return false;
}
/// <summary>
/// Operator for inequality comparison from WireMockList to T
/// </summary>
public static bool operator !=(WireMockList<T>? left, T? right) => !(left == right);
/// <summary>
/// Operator for inequality comparison from T to WireMockList
/// </summary>
public static bool operator !=(T? left, WireMockList<T>? right) => !(left == right);
/// <summary>
/// Determines whether the specified object is equal to the current instance.
/// Two <see cref="WireMockList{T}"/> instances are equal if they contain the same elements in the same order.
/// </summary>
public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj is WireMockList<T> other)
{
if (Count != other.Count)
{
return false;
}
for (var i = 0; i < Count; i++)
{
if (!Equals(this[i], other[i]))
{
return false;
}
}
return true;
}
return false;
}
/// <summary>
/// Returns a hash code for this instance based on its elements.
/// </summary>
public override int GetHashCode()
{
var hashCode = 17;
foreach (var item in this)
{
hashCode = hashCode * 31 + (item?.GetHashCode() ?? 0);
}
return hashCode;
}
/// <summary> /// <summary>
/// Returns a <see cref="string" /> that represents this instance. /// Returns a <see cref="string" /> that represents this instance.
/// </summary> /// </summary>
@@ -62,10 +154,10 @@ public class WireMockList<T> : List<T>
return strValue; return strValue;
} }
return this[0]?.ToString(); return this[0]?.ToString() ?? string.Empty;
default: default:
var strings = this.Select(x => x as string ?? x?.ToString()); var strings = this.Select(x => x as string ?? x?.ToString() ?? string.Empty);
return string.Join(", ", strings); return string.Join(", ", strings);
} }
} }
@@ -39,7 +39,7 @@
</ItemGroup>--> </ItemGroup>-->
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentBuilder" Version="0.14.0"> <PackageReference Include="FluentBuilder" Version="0.15.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -45,7 +45,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="13.1.0" /> <PackageReference Include="Aspire.Hosting" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -21,11 +21,6 @@ public class WireMockHealthCheck(WireMockServerResource resource) : IHealthCheck
return HealthCheckResult.Unhealthy("WireMock.Net is not healthy"); return HealthCheckResult.Unhealthy("WireMock.Net is not healthy");
} }
if (resource.ApiMappingState == WireMockMappingState.NotSubmitted)
{
return HealthCheckResult.Unhealthy("WireMock.Net has not received mappings");
}
return HealthCheckResult.Healthy(); return HealthCheckResult.Healthy();
} }
@@ -41,4 +36,4 @@ public class WireMockHealthCheck(WireMockServerResource resource) : IHealthCheck
return false; return false;
} }
} }
} }
@@ -21,7 +21,13 @@ internal class WireMockLifecycleSubscriber(ILoggerFactory loggerFactory) : IDist
var endpoint = wireMockServerResource.GetEndpoint(); var endpoint = wireMockServerResource.GetEndpoint();
Debug.Assert(endpoint.IsAllocated); Debug.Assert(endpoint.IsAllocated);
}
});
eventing.Subscribe<ResourceReadyEvent>(async (@event, ct) =>
{
if (@event.Resource is WireMockServerResource wireMockServerResource)
{
await wireMockServerResource.WaitForHealthAsync(ct); await wireMockServerResource.WaitForHealthAsync(ct);
await wireMockServerResource.CallAddProtoDefinitionsAsync(ct); await wireMockServerResource.CallAddProtoDefinitionsAsync(ct);
@@ -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.13.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)
@@ -70,7 +70,7 @@ public class CSharpCodeMatcher : ICSharpCodeMatcher
/// <inheritdoc /> /// <inheritdoc />
public string GetCSharpCodeArguments() public string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(CSharpCodeMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -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 {typeof(SystemTextJsonPathMatcher).FullName}" +
$"(" +
$"{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();
@@ -36,7 +36,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Stef.Validation" Version="0.2.0" /> <PackageReference Include="Stef.Validation" Version="0.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'"> <ItemGroup Condition="'$(Configuration)' == 'Debug'">
@@ -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);
}
+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();
}
}
@@ -80,10 +80,10 @@ public class ContentTypeMatcher : WildcardMatcher
/// <inheritdoc /> /// <inheritdoc />
public override string GetCSharpCodeArguments() public override string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(ContentTypeMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + $"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}" + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}" +
$")"; $")";
} }
@@ -95,7 +95,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc /> /// <inheritdoc />
public string GetCSharpCodeArguments() public string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(ExactMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
@@ -1,10 +1,10 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
using AnyOfTypes; using AnyOfTypes;
using Stef.Validation; using Stef.Validation;
using WireMock.Extensions; using WireMock.Extensions;
using WireMock.Models; using WireMock.Models;
using WireMock.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Matchers; namespace WireMock.Matchers;
@@ -21,7 +21,7 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc /> /// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; } public MatchBehaviour MatchBehaviour { get; }
private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = []; private readonly List<(WildcardMatcher Key, WildcardMatcher[]? Values)> KeyValueMatchers = [];
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class. /// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
@@ -91,9 +91,10 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
{ {
foreach (var nameValue in nameValueCollection) foreach (var nameValue in nameValueCollection)
{ {
var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Key], ignoreCase, MatchOperator); var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, nameValue.Key, ignoreCase);
var valueMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Value], ignoreCase, MatchOperator); var valueMatchers = nameValue.Value.Select(value => new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, ignoreCase)).ToArray();
_pairs.Add((keyMatcher, valueMatcher));
KeyValueMatchers.Add((keyMatcher, valueMatchers));
} }
} }
} }
@@ -119,34 +120,48 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score)); return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
} }
private bool[] GetMatches(IDictionary<string, string> inputNameValueCollection) private List<double> GetMatches(IDictionary<string, WireMockList<string>> inputNameValueCollection)
{ {
var matches = new List<bool>(); var inputPairs = inputNameValueCollection.ToArray();
if (_pairs.Count > inputNameValueCollection.Count) var rowCount = inputPairs.Length;
var columnCount = KeyValueMatchers.Count;
if (rowCount == 0 && columnCount == 0)
{ {
matches.AddRange(Enumerable.Repeat(false, _pairs.Count - inputNameValueCollection.Count)); return [];
} }
foreach (var inputKeyValuePair in inputNameValueCollection) var matrix = new double[rowCount][];
for (var row = 0; row < rowCount; row++)
{ {
var match = false; matrix[row] = new double[columnCount];
foreach (var pair in _pairs)
var inputKeyValuePair = inputPairs[row];
var inputKey = inputKeyValuePair.Key;
var inputValues = inputKeyValuePair.Value;
for (var column = 0; column < columnCount; column++)
{ {
var keyMatchResult = pair.Key.IsMatch(inputKeyValuePair.Key).IsPerfect(); var (keyMatcher, valuesMatchers) = KeyValueMatchers[column];
if (keyMatchResult)
{
match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false;
if (match)
{
break;
}
}
}
matches.Add(match); var keyScore = keyMatcher.IsMatch(inputKey).Score;
var valueScore = valuesMatchers != null ? MatchScores.ToScore(inputValues, valuesMatchers) : MatchScores.Perfect;
matrix[row][column] = Math.Min(keyScore, valueScore);
}
} }
return matches.ToArray(); var rowScores = rowCount == 0 ? [] : matrix.Select(row => row.Length == 0 ? MatchScores.Mismatch : row.Max()).ToList();
var columnScores = new List<double>();
for (var column = 0; column < columnCount; column++)
{
columnScores.Add(rowCount == 0 ? MatchScores.Mismatch : matrix.Max(row => row[column]));
}
rowScores.AddRange(columnScores);
return rowScores;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -167,10 +182,10 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc /> /// <inheritdoc />
public string GetCSharpCodeArguments() public string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(FormUrlEncodedMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + $"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}" + $"{MatchOperator.GetFullyQualifiedEnumValue()}" +
$")"; $")";
@@ -76,7 +76,7 @@ public class FuncMatcher : IFuncMatcher
public string GetCSharpCodeArguments() public string GetCSharpCodeArguments()
{ {
var funcType = _stringFunc != null ? "Func<string?, bool>" : "Func<byte[]?, bool>"; var funcType = _stringFunc != null ? "Func<string?, bool>" : "Func<byte[]?, bool>";
return $"new {Name}" + return $"new {typeof(FuncMatcher).FullName}" +
$"(" + $"(" +
$"/* {funcType} function */, " + $"/* {funcType} function */, " +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}" + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}" +
@@ -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;
@@ -121,7 +119,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
/// <inheritdoc /> /// <inheritdoc />
public string GetCSharpCodeArguments() public string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(JsonPathMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -119,7 +119,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
/// <inheritdoc /> /// <inheritdoc />
public string GetCSharpCodeArguments() public string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(JmesPathMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -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);
@@ -100,12 +110,13 @@ public class JsonMatcher : IJsonMatcher
/// <inheritdoc /> /// <inheritdoc />
public virtual string GetCSharpCodeArguments() public virtual string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(JsonMatcher).FullName}" +
$"(" + $"(" +
$"{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();
@@ -41,7 +41,7 @@ public class JsonPartialMatcher : AbstractJsonPartialMatcher
/// <inheritdoc /> /// <inheritdoc />
public override string GetCSharpCodeArguments() public override string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(JsonPartialMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
@@ -41,7 +41,7 @@ public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher
/// <inheritdoc /> /// <inheritdoc />
public override string GetCSharpCodeArguments() public override string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(JsonPartialWildcardMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
@@ -141,7 +141,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc /> /// <inheritdoc />
public string GetCSharpCodeArguments() public string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(LinqMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -1,8 +1,8 @@
// 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.Types;
using WireMock.Util; using WireMock.Util;
namespace WireMock.Matchers.Request; namespace WireMock.Matchers.Request;
@@ -35,7 +35,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <summary> /// <summary>
/// The body data function for FormUrlEncoded /// The body data function for FormUrlEncoded
/// </summary> /// </summary>
public Func<IDictionary<string, string>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; } public Func<IDictionary<string, WireMockList<string>>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
/// <summary> /// <summary>
/// The matchers. /// The matchers.
@@ -117,7 +117,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class. /// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary> /// </summary>
/// <param name="func">The function.</param> /// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func) public RequestMessageBodyMatcher(Func<IDictionary<string, WireMockList<string>>?, bool> func)
{ {
MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func); MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func);
} }
@@ -142,6 +142,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)
{ {
@@ -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;
@@ -54,7 +53,10 @@ public class RequestMessageParamMatcher : IRequestMatcher
/// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param> /// <param name="ignoreCase">Defines if the key should be matched using case-ignore.</param>
/// <param name="values">The values.</param> /// <param name="values">The values.</param>
public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, params string[]? values) : public RequestMessageParamMatcher(MatchBehaviour matchBehaviour, string key, bool ignoreCase, params string[]? values) :
this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(matchBehaviour, ignoreCase, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray()) // Note: the inner ExactMatcher must use AcceptOnMatch.
// The MatchBehaviour (e.g. RejectOnMatch) is applied once by this matcher's GetMatchingScore.
// Passing matchBehaviour here as well would apply the conversion twice and invert RejectOnMatch.
this(matchBehaviour, key, ignoreCase, values?.Select(value => new ExactMatcher(MatchBehaviour.AcceptOnMatch, ignoreCase, MatchOperator.And, value)).Cast<IStringMatcher>().ToArray())
{ {
} }
@@ -82,6 +84,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)
{ {
@@ -110,36 +115,6 @@ public class RequestMessageParamMatcher : IRequestMatcher
} }
// Return the score based on Matchers and valuesPresentInRequestMessage // Return the score based on Matchers and valuesPresentInRequestMessage
return CalculateScore(Matchers, valuesPresentInRequestMessage); return MatchScores.ToScore(valuesPresentInRequestMessage, Matchers.ToArray());
}
private static double CalculateScore(IReadOnlyList<IStringMatcher> matchers, WireMockList<string> valuesPresentInRequestMessage)
{
var total = new List<double>();
// If the total patterns in all matchers > values in message, use the matcher as base
if (matchers.Sum(m => m.GetPatterns().Length) > valuesPresentInRequestMessage.Count)
{
foreach (var matcher in matchers)
{
double score = 0d;
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
{
score += matcher.IsMatch(valuePresentInRequestMessage).Score / matcher.GetPatterns().Length;
}
total.Add(score);
}
}
else
{
foreach (string valuePresentInRequestMessage in valuesPresentInRequestMessage)
{
var score = matchers.Max(m => m.IsMatch(valuePresentInRequestMessage).Score);
total.Add(score);
}
}
return total.Any() ? MatchScores.ToScore(total, MatchOperator.Average) : 0;
} }
} }
@@ -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)
{ {
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq;
using AnyOfTypes; using AnyOfTypes;
using SimMetrics.Net; using SimMetrics.Net;
using SimMetrics.Net.API; using SimMetrics.Net.API;
@@ -92,10 +91,10 @@ public class SimMetricsMatcher : IStringMatcher
/// <inheritdoc /> /// <inheritdoc />
public virtual string GetCSharpCodeArguments() public virtual string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(SimMetricsMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + $"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
$"{_simMetricType.GetFullyQualifiedEnumValue()}, " + $"{_simMetricType.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}" + $"{MatchOperator.GetFullyQualifiedEnumValue()}" +
$")"; $")";
@@ -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 {typeof(SystemTextJsonMatcher).FullName}" +
$"(" +
$"{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 {typeof(SystemTextJsonPartialMatcher).FullName}" +
$"(" +
$"{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 {typeof(SystemTextJsonPartialWildcardMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" +
$")";
}
}
@@ -101,7 +101,7 @@ public class XPathMatcher : IStringMatcher
/// <inheritdoc /> /// <inheritdoc />
public string GetCSharpCodeArguments() public string GetCSharpCodeArguments()
{ {
return $"new {Name}" + return $"new {typeof(XPathMatcher).FullName}" +
$"(" + $"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -0,0 +1,59 @@
// Copyright © WireMock.Net
using System.Text.RegularExpressions;
using WireMock.Matchers;
namespace WireMock.Owin;
internal sealed class AdminPaths(string? adminPath) : IAdminPaths
{
private const string DefaultAdminPathPrefix = "/__admin";
private readonly string _prefix = adminPath ?? DefaultAdminPathPrefix;
public string Files => $"{_prefix}/files";
public string Health => $"{_prefix}/health";
public string Mappings => $"{_prefix}/mappings";
public string MappingsCode => $"{_prefix}/mappings/code";
public string MappingsWireMockOrg => $"{_prefix}/mappings/wiremock.org";
public string Requests => $"{_prefix}/requests";
public string Settings => $"{_prefix}/settings";
public string Scenarios => $"{_prefix}/scenarios";
public string OpenApi => $"{_prefix}/openapi";
public string ProtoDefinitions => $"{_prefix}/protodefinitions";
private string PrefixRegexEscaped => Regex.Escape(_prefix);
public RegexMatcher MappingsGuidPathMatcher => new($@"^{PrefixRegexEscaped}\/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($@"^{PrefixRegexEscaped}\/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($@"^{PrefixRegexEscaped}\/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($@"^{PrefixRegexEscaped}\/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($@"^{PrefixRegexEscaped}\/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($@"^{PrefixRegexEscaped}\/scenarios\/.+$");
public RegexMatcher ScenariosNameWithStateMatcher => new($@"^{PrefixRegexEscaped}\/scenarios\/.+\/state$");
public RegexMatcher ScenariosNameWithResetMatcher => new($@"^{PrefixRegexEscaped}\/scenarios\/.+\/reset$");
public RegexMatcher FilesFilenamePathMatcher => new($@"^{PrefixRegexEscaped}\/files\/.+$");
public RegexMatcher ProtoDefinitionsIdPathMatcher => new($@"^{PrefixRegexEscaped}\/protodefinitions\/.+$");
public bool Includes(string? path) => path?.StartsWith($"{_prefix}/") ?? false;
public override string ToString() => _prefix;
}
@@ -66,12 +66,14 @@ internal partial class AspNetCoreSelfHost
.ConfigureServices(services => .ConfigureServices(services =>
{ {
services.AddSingleton(_wireMockMiddlewareOptions); services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton(_wireMockMiddlewareOptions.AdminPaths);
services.AddSingleton<IMappingMatcher, MappingMatcher>(); services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>(); services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>(); services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
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>();
@@ -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);
} }
} }
} }
@@ -0,0 +1,48 @@
using WireMock.Matchers;
namespace WireMock.Owin;
internal interface IAdminPaths
{
string Files { get; }
string Health { get; }
string Mappings { get; }
string MappingsCode { get; }
string MappingsWireMockOrg { get; }
RegexMatcher MappingsGuidPathMatcher { get; }
RegexMatcher MappingsGuidEnablePathMatcher { get; }
RegexMatcher MappingsGuidDisablePathMatcher { get; }
RegexMatcher MappingsCodeGuidPathMatcher { get; }
RegexMatcher RequestsGuidPathMatcher { get; }
RegexMatcher ScenariosNameMatcher { get; }
RegexMatcher ScenariosNameWithStateMatcher { get; }
RegexMatcher ScenariosNameWithResetMatcher { get; }
RegexMatcher FilesFilenamePathMatcher { get; }
RegexMatcher ProtoDefinitionsIdPathMatcher { get; }
string Requests { get; }
string Settings { get; }
string Scenarios { get; }
string OpenApi { get; }
string ProtoDefinitions { get; }
bool Includes(string? path);
}
@@ -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;
@@ -99,4 +100,14 @@ 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; }
IAdminPaths AdminPaths { 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);
@@ -3,249 +3,242 @@
using System.Globalization; using System.Globalization;
using System.Net; using System.Net;
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>
/// OwinResponseMapper
/// </summary>
internal class OwinResponseMapper(IWireMockMiddlewareOptions options) : IOwinResponseMapper
{ {
/// <summary> private readonly IRandomizerNumber<double> _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 });
/// OwinResponseMapper private readonly IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 });
/// </summary> private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
internal class OwinResponseMapper : IOwinResponseMapper
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
private static readonly IDictionary<string, Action<HttpResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
new Dictionary<string, Action<HttpResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
{
{ HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() },
{ HttpKnownHeaderNames.ContentLength, (r, hasBody, v) =>
{
// Only set the Content-Length header if the response does not have a body
if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength))
{
r.ContentLength = contentLength;
}
}
}
};
/// <inheritdoc />
public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response)
{ {
private readonly IRandomizerNumber<double> _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 }); if (responseMessage == null || responseMessage is WebSocketHandledResponse)
private readonly IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 });
private readonly IWireMockMiddlewareOptions _options;
private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
private static readonly IDictionary<string, Action<HttpResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
new Dictionary<string, Action<HttpResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
{
{ HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() },
{ HttpKnownHeaderNames.ContentLength, (r, hasBody, v) =>
{
// Only set the Content-Length header if the response does not have a body
if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength))
{
r.ContentLength = contentLength;
}
}
}
};
/// <summary>
/// Constructor
/// </summary>
/// <param name="options">The IWireMockMiddlewareOptions.</param>
public OwinResponseMapper(IWireMockMiddlewareOptions options)
{ {
_options = Guard.NotNull(options); return;
} }
/// <inheritdoc /> var bodyData = responseMessage.BodyData;
public async Task MapAsync(IResponseMessage? responseMessage, HttpResponse response) if (bodyData?.GetDetectedBodyType() == BodyType.SseString)
{ {
if (responseMessage == null || responseMessage is WebSocketHandledResponse) await HandleSseStringAsync(responseMessage, response, bodyData);
{ return;
return; }
}
var bodyData = responseMessage.BodyData; byte[]? bytes;
if (bodyData?.GetDetectedBodyType() == BodyType.SseString) switch (responseMessage.FaultType)
{ {
await HandleSseStringAsync(responseMessage, response, bodyData); case FaultType.EMPTY_RESPONSE:
return; bytes = IsFault(responseMessage) ? [] : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
} break;
byte[]? bytes; case FaultType.MALFORMED_RESPONSE_CHUNK:
switch (responseMessage.FaultType) bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? [];
{ if (IsFault(responseMessage))
case FaultType.EMPTY_RESPONSE:
bytes = IsFault(responseMessage) ? [] : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
case FaultType.MALFORMED_RESPONSE_CHUNK:
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? [];
if (IsFault(responseMessage))
{
bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
}
break;
default:
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
}
if (responseMessage.StatusCode is HttpStatusCode or int)
{
response.StatusCode = MapStatusCode((int) responseMessage.StatusCode);
}
else if (responseMessage.StatusCode is string statusCodeAsString)
{
// Note: this case will also match on null
_ = int.TryParse(statusCodeAsString, out var statusCodeTypeAsInt);
response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
}
SetResponseHeaders(responseMessage, bytes != null, response);
if (bytes != null)
{
try
{ {
await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray();
} }
catch (Exception ex) break;
default:
bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false);
break;
}
if (responseMessage.StatusCode is HttpStatusCode or int)
{
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode);
}
else if (responseMessage.StatusCode is string statusCodeAsString)
{
// Note: this case will also match on null
_ = int.TryParse(statusCodeAsString, out var statusCodeTypeAsInt);
response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
}
SetResponseHeaders(responseMessage, bytes != null, response);
if (bytes != null)
{
try
{
await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
}
catch (Exception ex)
{
options.Logger.Warn("Error writing response body. Exception : {0}", ex);
}
}
SetResponseTrailingHeaders(responseMessage, response);
}
private static async Task HandleSseStringAsync(IResponseMessage responseMessage, HttpResponse response, IBodyData bodyData)
{
if (bodyData.SseStringQueue == null)
{
return;
}
SetResponseHeaders(responseMessage, true, response);
string? text;
do
{
if (bodyData.SseStringQueue.TryRead(out text))
{
await response.WriteAsync(text);
await response.Body.FlushAsync();
}
} while (text != null);
}
private int MapStatusCode(int code)
{
if (options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code))
{
return (int)HttpStatusCode.OK;
}
return code;
}
private bool IsFault(IResponseMessage responseMessage)
{
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
}
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
{
var bodyData = responseMessage.BodyData;
switch (bodyData?.GetDetectedBodyType())
{
case BodyType.String:
case BodyType.FormUrlEncoded:
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);
case BodyType.Json:
var jsonConverterOptions = new JsonConverterOptions
{ {
_options.Logger.Warn("Error writing response body. Exception : {0}", ex); WriteIndented = bodyData.BodyAsJsonIndented == true,
} IgnoreNullValues = true
} };
SetResponseTrailingHeaders(responseMessage, response); var jsonBody = options.DefaultJsonSerializer.Serialize(bodyData.BodyAsJson!, jsonConverterOptions);
} return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
private static async Task HandleSseStringAsync(IResponseMessage responseMessage, HttpResponse response, IBodyData bodyData) case BodyType.ProtoBuf:
{ if (TypeLoader.TryLoadStaticInstance<IProtoBufUtils>(out var protoBufUtils))
if (bodyData.SseStringQueue == null)
{
return;
}
SetResponseHeaders(responseMessage, true, response);
string? text;
do
{
if (bodyData.SseStringQueue.TryRead(out text))
{ {
await response.WriteAsync(text); var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts;
await response.Body.FlushAsync(); return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false);
} }
} while (text != null); break;
case BodyType.Bytes:
return bodyData.BodyAsBytes;
case BodyType.File:
return options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!);
case BodyType.MultiPart:
options.Logger.Warn("MultiPart body type is not handled!");
break;
case BodyType.None:
break;
} }
private int MapStatusCode(int code) return null;
}
private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, HttpResponse response)
{
// Force setting the Date header (#577)
AppendResponseHeader(
response,
HttpKnownHeaderNames.Date,
[DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)]
);
// Set other headers
foreach (var item in responseMessage.Headers!)
{ {
if (_options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code)) var headerName = item.Key;
var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{ {
return (int)HttpStatusCode.OK; action.Invoke(response, hasBody, value);
} }
else
return code;
}
private bool IsFault(IResponseMessage responseMessage)
{
return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage;
}
private async Task<byte[]?> GetNormalBodyAsync(IResponseMessage responseMessage)
{
var bodyData = responseMessage.BodyData;
switch (bodyData?.GetDetectedBodyType())
{ {
case BodyType.String: // Check if this response header can be added (#148, #227 and #720)
case BodyType.FormUrlEncoded: if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!);
case BodyType.Json:
var formatting = bodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None;
var jsonBody = JsonConvert.SerializeObject(bodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore });
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
case BodyType.ProtoBuf:
if (TypeLoader.TryLoadStaticInstance<IProtoBufUtils>(out var protoBufUtils))
{
var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts;
return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false);
}
break;
case BodyType.Bytes:
return bodyData.BodyAsBytes;
case BodyType.File:
return _options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!);
case BodyType.MultiPart:
_options.Logger.Warn("MultiPart body type is not handled!");
break;
case BodyType.None:
break;
}
return null;
}
private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, HttpResponse response)
{
// Force setting the Date header (#577)
AppendResponseHeader(
response,
HttpKnownHeaderNames.Date,
[DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)]
);
// Set other headers
foreach (var item in responseMessage.Headers!)
{
var headerName = item.Key;
var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{ {
action.Invoke(response, hasBody, value); AppendResponseHeader(response, headerName, value.ToArray());
}
else
{
// Check if this response header can be added (#148, #227 and #720)
if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
{
AppendResponseHeader(response, headerName, value.ToArray());
}
} }
} }
} }
}
private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, HttpResponse response)
{ private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, HttpResponse response)
if (responseMessage.TrailingHeaders == null) {
{ if (responseMessage.TrailingHeaders == null)
return; {
} return;
}
#if TRAILINGHEADERS
foreach (var (headerName, value) in responseMessage.TrailingHeaders) #if TRAILINGHEADERS
{ foreach (var (headerName, value) in responseMessage.TrailingHeaders)
if (ResponseHeadersToFix.TryGetValue(headerName, out var action)) {
{ if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
action.Invoke(response, false, value); {
} action.Invoke(response, false, value);
else }
{ else
// Check if this trailing header can be added to the response {
if (response.SupportsTrailers() && !HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName)) // Check if this trailing header can be added to the response
{ if (response.SupportsTrailers() && !HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
response.AppendTrailer(headerName, new Microsoft.Extensions.Primitives.StringValues(value.ToArray())); {
} response.AppendTrailer(headerName, new Microsoft.Extensions.Primitives.StringValues(value.ToArray()));
} }
} }
#endif }
} #endif
}
private static void AppendResponseHeader(HttpResponse response, string headerName, string[] values)
{ private static void AppendResponseHeader(HttpResponse response, string headerName, string[] values)
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();
@@ -21,12 +21,14 @@ internal class WireMockMiddleware(
RequestDelegate next, RequestDelegate next,
#pragma warning restore CS9113 // Parameter is unread. #pragma warning restore CS9113 // Parameter is unread.
IWireMockMiddlewareOptions options, IWireMockMiddlewareOptions options,
IAdminPaths adminPaths,
IOwinRequestMapper requestMapper, IOwinRequestMapper requestMapper,
IOwinResponseMapper responseMapper, IOwinResponseMapper responseMapper,
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();
@@ -63,7 +65,7 @@ internal class WireMockMiddleware(
Activity? activity = null; Activity? activity = null;
// Check if we should trace this request (optionally exclude admin requests) // Check if we should trace this request (optionally exclude admin requests)
var shouldTrace = tracingEnabled && !(excludeAdmin && request.Path.StartsWith("/__admin/")); var shouldTrace = tracingEnabled && !(excludeAdmin && adminPaths.Includes(request.Path));
if (shouldTrace) if (shouldTrace)
{ {
@@ -97,7 +99,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 +111,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 +119,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 +157,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 +167,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 +181,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);
} }
} }
Loaded 100 of 257 files, more files were not shown because too many files have changed in this diff. Show more