Compare commits

...
34 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
141 changed files with 3876 additions and 600 deletions

No files matched your search

-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
+46
View File
@@ -1,3 +1,49 @@
# 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]
+3 -3
View File
@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>2.9.0</VersionPrefix>
<VersionPrefix>2.18.0</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@@ -51,8 +51,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.300" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2026.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.401" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
+1 -1
View File
@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=2.9.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%
+3 -3
View File
@@ -1,5 +1,5 @@
# 2.9.0 (02 June 2026)
- #1470 Fix WireMock.Net.Aspire project when using Aspire 13.4.0 [bug]
- #1469 WireMock.Net doesn't play well with Aspire 13.4.0 [bug]
# 2.18.0 (22 September 2026)
- #1510 WireMock.Net.OpenApiParser: fix depencency on RamlToOpenApiConverter.SourceOnly [bug]
- #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
+7
View File
@@ -1,3 +1,9 @@
parameters:
- name: runWindowsReleaseToMyGet
displayName: Run Windows Release to MyGet
type: boolean
default: false
variables:
Prerelease: 'ci'
buildId: "1$(Build.BuildId)"
@@ -131,6 +137,7 @@ jobs:
- job: Windows_Release_to_MyGet
dependsOn: Windows_Build_Test
condition: and(succeeded(), ${{ parameters.runWindowsReleaseToMyGet }})
pool:
vmImage: 'windows-2025'
@@ -18,7 +18,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup>
<ItemGroup>
@@ -15,7 +15,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup>
</Project>
-2
View File
@@ -1,9 +1,7 @@
// Copyright © WireMock.Net
using System;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RestEase;
using WireMock.Admin.Settings;
@@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.25.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.60.0" />
<PackageReference Include="Grpc.Tools" Version="2.60.0">
<PackageReference Include="Google.Protobuf" Version="3.33.5" />
<PackageReference Include="Grpc.Net.Client" Version="2.76.0" />
<PackageReference Include="Grpc.Tools" Version="2.78.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -9,7 +9,7 @@ namespace WireMock.Net.Console.MimePart;
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)
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.IO;
using WireMock.Logging;
using WireMock.Server;
using WireMock.Settings;
@@ -1,14 +1,8 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SharpYaml.Model;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Models;
@@ -1115,7 +1109,7 @@ namespace WireMock.Net.ConsoleApplication
{
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,
},
StatusCode = code
@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using log4net;
using log4net.Config;
using log4net.Repository;
@@ -12,8 +10,7 @@ namespace WireMock.Net.Console.NET8;
static class Program
{
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()!);
static async Task Main(params string[] args)
{
@@ -1,9 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using WireMock.Logging;
using WireMock.Net.StandAlone;
using WireMock.RequestBuilders;
@@ -16,7 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="WireMock.Net" Version="2.6.0" />
<PackageReference Include="WireMock.Net" Version="2.15.0" />
</ItemGroup>
</Project>
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.IO;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -6,18 +6,13 @@
</PropertyGroup>
<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.OpenApiParser\WireMock.Net.OpenApiParser.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="Microsoft.OpenApi" Version="2.3.0" 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" />-->
<PackageReference Include="Microsoft.OpenApi" Version="3.10.2" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
@@ -36,12 +36,6 @@
<StartupObject>Wiremock.Net.Service.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</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.Configuration" />
<Reference Include="System.Configuration.Install" />
@@ -91,13 +85,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="log4net">
<Version>3.3.0</Version>
<Version>3.3.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core">
<Version>2.3.9</Version>
<Version>2.3.11</Version>
</PackageReference>
<PackageReference Include="WireMock.Net">
<Version>1.25.0</Version>
<Version>2.12.0</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
@@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
@@ -46,7 +46,7 @@ public interface IBodyData
/// <summary>
/// The body as Form UrlEncoded dictionary.
/// </summary>
IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
IDictionary<string, WireMockList<string>>? BodyAsFormUrlEncoded { get; set; }
/// <summary>
/// The detected body type (detection based on body content).
@@ -1,11 +1,11 @@
// Copyright © WireMock.Net
using System.Linq;
namespace WireMock.Types;
/// <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>
/// <typeparam name="T">The generic type</typeparam>
/// <seealso cref="List{T}" />
@@ -46,6 +46,98 @@ public class WireMockList<T> : List<T>
/// <param name="values">The values to set.</param>
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>
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
@@ -62,10 +154,10 @@ public class WireMockList<T> : List<T>
return strValue;
}
return this[0]?.ToString();
return this[0]?.ToString() ?? string.Empty;
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);
}
}
@@ -39,7 +39,7 @@
</ItemGroup>-->
<ItemGroup>
<PackageReference Include="FluentBuilder" Version="0.14.0">
<PackageReference Include="FluentBuilder" Version="0.15.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -45,7 +45,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="13.4.0" />
<PackageReference Include="Aspire.Hosting" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup>
<ItemGroup>
@@ -25,7 +25,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Abstractions" Version="0.12.0" />
<PackageReference Include="JsonConverter.Abstractions" Version="0.13.0" />
</ItemGroup>
<ItemGroup>
@@ -70,7 +70,7 @@ public class CSharpCodeMatcher : ICSharpCodeMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(CSharpCodeMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -126,7 +126,7 @@ public class SystemTextJsonPathMatcher : ISystemTextJsonPathMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(SystemTextJsonPathMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -36,7 +36,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
@@ -80,10 +80,10 @@ public class ContentTypeMatcher : WildcardMatcher
/// <inheritdoc />
public override string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(ContentTypeMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
$"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}" +
$")";
}
@@ -95,7 +95,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(ExactMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
@@ -1,10 +1,10 @@
// Copyright © WireMock.Net
using System.Linq;
using AnyOfTypes;
using Stef.Validation;
using WireMock.Extensions;
using WireMock.Models;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Matchers;
@@ -21,7 +21,7 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = [];
private readonly List<(WildcardMatcher Key, WildcardMatcher[]? Values)> KeyValueMatchers = [];
/// <summary>
/// Initializes a new instance of the <see cref="FormUrlEncodedMatcher"/> class.
@@ -91,9 +91,10 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
{
foreach (var nameValue in nameValueCollection)
{
var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Key], ignoreCase, MatchOperator);
var valueMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, [nameValue.Value], ignoreCase, MatchOperator);
_pairs.Add((keyMatcher, valueMatcher));
var keyMatcher = new WildcardMatcher(MatchBehaviour.AcceptOnMatch, nameValue.Key, ignoreCase);
var valueMatchers = nameValue.Value.Select(value => new WildcardMatcher(MatchBehaviour.AcceptOnMatch, value, ignoreCase)).ToArray();
KeyValueMatchers.Add((keyMatcher, valueMatchers));
}
}
}
@@ -119,34 +120,48 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
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>();
if (_pairs.Count > inputNameValueCollection.Count)
var inputPairs = inputNameValueCollection.ToArray();
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;
foreach (var pair in _pairs)
matrix[row] = new double[columnCount];
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();
if (keyMatchResult)
{
match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false;
if (match)
{
break;
}
}
}
var (keyMatcher, valuesMatchers) = KeyValueMatchers[column];
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 />
@@ -167,10 +182,10 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(FormUrlEncodedMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
$"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}" +
$")";
@@ -76,7 +76,7 @@ public class FuncMatcher : IFuncMatcher
public string GetCSharpCodeArguments()
{
var funcType = _stringFunc != null ? "Func<string?, bool>" : "Func<byte[]?, bool>";
return $"new {Name}" +
return $"new {typeof(FuncMatcher).FullName}" +
$"(" +
$"/* {funcType} function */, " +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}" +
@@ -119,7 +119,7 @@ public class JsonPathMatcher : IJsonPathMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(JsonPathMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -119,7 +119,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(JmesPathMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -110,7 +110,7 @@ public class JsonMatcher : IJsonMatcher
/// <inheritdoc />
public virtual string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(JsonMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
@@ -41,7 +41,7 @@ public class JsonPartialMatcher : AbstractJsonPartialMatcher
/// <inheritdoc />
public override string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(JsonPartialMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
@@ -41,7 +41,7 @@ public class JsonPartialWildcardMatcher : AbstractJsonPartialMatcher
/// <inheritdoc />
public override string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(JsonPartialWildcardMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
@@ -141,7 +141,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(LinqMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}, " +
@@ -2,6 +2,7 @@
using Stef.Validation;
using WireMock.Matchers.Helpers;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Matchers.Request;
@@ -34,7 +35,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <summary>
/// The body data function for FormUrlEncoded
/// </summary>
public Func<IDictionary<string, string>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
public Func<IDictionary<string, WireMockList<string>>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
/// <summary>
/// The matchers.
@@ -116,7 +117,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
public RequestMessageBodyMatcher(Func<IDictionary<string, WireMockList<string>>?, bool> func)
{
MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func);
}
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using Stef.Validation;
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="values">The values.</param>
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())
{
}
@@ -113,36 +115,6 @@ public class RequestMessageParamMatcher : IRequestMatcher
}
// Return the score based on Matchers and valuesPresentInRequestMessage
return CalculateScore(Matchers, valuesPresentInRequestMessage);
}
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;
return MatchScores.ToScore(valuesPresentInRequestMessage, Matchers.ToArray());
}
}
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using AnyOfTypes;
using SimMetrics.Net;
using SimMetrics.Net.API;
@@ -92,10 +91,10 @@ public class SimMetricsMatcher : IStringMatcher
/// <inheritdoc />
public virtual string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(SimMetricsMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
$"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
$"{_simMetricType.GetFullyQualifiedEnumValue()}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}" +
$")";
@@ -115,7 +115,7 @@ public class SystemTextJsonMatcher : IJsonMatcher
/// <inheritdoc />
public virtual string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(SystemTextJsonMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
@@ -41,7 +41,7 @@ public class SystemTextJsonPartialMatcher : AbstractSystemTextJsonPartialMatcher
/// <inheritdoc />
public override string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(SystemTextJsonPartialMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
@@ -41,7 +41,7 @@ public class SystemTextJsonPartialWildcardMatcher : AbstractSystemTextJsonPartia
/// <inheritdoc />
public override string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(SystemTextJsonPartialWildcardMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " +
@@ -101,7 +101,7 @@ public class XPathMatcher : IStringMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(XPathMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.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,6 +66,7 @@ internal partial class AspNetCoreSelfHost
.ConfigureServices(services =>
{
services.AddSingleton(_wireMockMiddlewareOptions);
services.AddSingleton(_wireMockMiddlewareOptions.AdminPaths);
services.AddSingleton<IMappingMatcher, MappingMatcher>();
services.AddSingleton<IRandomizerDoubleBetween0And1, RandomizerDoubleBetween0And1>();
services.AddSingleton<IOwinRequestMapper, OwinRequestMapper>();
@@ -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);
}
@@ -108,4 +108,6 @@ internal interface IWireMockMiddlewareOptions
/// 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; }
}
@@ -21,6 +21,7 @@ internal class WireMockMiddleware(
RequestDelegate next,
#pragma warning restore CS9113 // Parameter is unread.
IWireMockMiddlewareOptions options,
IAdminPaths adminPaths,
IOwinRequestMapper requestMapper,
IOwinResponseMapper responseMapper,
IMappingMatcher mappingMatcher,
@@ -64,7 +65,7 @@ internal class WireMockMiddleware(
Activity? activity = null;
// 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)
{
@@ -9,39 +9,44 @@ using WireMock.Util;
namespace WireMock.Owin;
internal class WireMockMiddlewareLogger(
IWireMockMiddlewareOptions _options,
LogEntryMapper _logEntryMapper,
IGuidUtils _guidUtils
IWireMockMiddlewareOptions options,
LogEntryMapper logEntryMapper,
IGuidUtils guidUtils,
IAdminPaths adminPaths,
IDateTimeUtils dateTimeUtils
) : IWireMockMiddlewareLogger
{
public void LogRequestAndResponse(bool logRequest, RequestMessage request, IResponseMessage? response, MappingMatcherResult? match, MappingMatcherResult? partialMatch, Activity? activity)
{
var mapping = match?.Mapping;
var partialMapping = partialMatch?.Mapping;
var logEntry = new LogEntry
{
Guid = _guidUtils.NewGuid(),
Guid = guidUtils.NewGuid(),
RequestMessage = request,
ResponseMessage = response,
MappingGuid = match?.Mapping?.Guid,
MappingTitle = match?.Mapping?.Title,
MappingGuid = mapping?.Guid,
MappingTitle = mapping?.Title,
RequestMatchResult = match?.RequestMatchResult,
PartialMappingGuid = partialMatch?.Mapping?.Guid,
PartialMappingTitle = partialMatch?.Mapping?.Title,
PartialMappingGuid = partialMapping?.Guid,
PartialMappingTitle = partialMapping?.Title,
PartialMatchResult = partialMatch?.RequestMatchResult
};
WireMockActivitySource.EnrichWithLogEntry(activity, logEntry, _options.ActivityTracingOptions);
WireMockActivitySource.EnrichWithLogEntry(activity, logEntry, options.ActivityTracingOptions);
activity?.Dispose();
LogLogEntry(logEntry, logRequest);
try
{
if (_options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true })
if (options.SaveUnmatchedRequests == true && match?.RequestMatchResult is not { IsPerfectMatch: true })
{
var filename = $"{logEntry.Guid}.LogEntry.json";
_options.FileSystemHandler?.WriteUnmatchedRequest(filename, _options.DefaultJsonSerializer.Serialize(logEntry));
options.FileSystemHandler?.WriteUnmatchedRequest(filename, options.DefaultJsonSerializer.Serialize(logEntry));
}
}
catch
@@ -52,34 +57,35 @@ internal class WireMockMiddlewareLogger(
public void LogLogEntry(LogEntry entry, bool addRequest)
{
_options.Logger.DebugRequestResponse(_logEntryMapper.Map(entry), entry.RequestMessage?.Path.StartsWith("/__admin/") == true);
var isAdminRequest = adminPaths.Includes(entry.RequestMessage?.Path);
options.Logger.DebugRequestResponse(logEntryMapper.Map(entry), isAdminRequest);
// If addRequest is set to true and MaxRequestLogCount is null or does have a value greater than 0, try to add a new request log.
if (addRequest && _options.MaxRequestLogCount is null or > 0)
if (addRequest && options.MaxRequestLogCount is null or > 0)
{
TryAddLogEntry(entry);
}
// In case MaxRequestLogCount has a value greater than 0, try to delete existing request logs based on the count.
if (_options.MaxRequestLogCount is > 0)
if (options.MaxRequestLogCount is > 0)
{
var logEntries = _options.LogEntries.ToList();
var logEntries = options.LogEntries.ToList();
foreach (var logEntry in logEntries
.OrderBy(le => le.RequestMessage?.DateTime ?? le.ResponseMessage?.DateTime)
.Take(logEntries.Count - _options.MaxRequestLogCount.Value))
.Take(logEntries.Count - options.MaxRequestLogCount.Value))
{
TryRemoveLogEntry(logEntry);
}
}
// In case RequestLogExpirationDuration has a value greater than 0, try to delete existing request logs based on the date.
if (_options.RequestLogExpirationDuration is > 0)
if (options.RequestLogExpirationDuration is > 0)
{
var logEntries = _options.LogEntries.ToList();
var logEntries = options.LogEntries.ToList();
var checkTime = DateTime.UtcNow.AddHours(-_options.RequestLogExpirationDuration.Value);
var checkTime = dateTimeUtils.UtcNow.AddHours(-options.RequestLogExpirationDuration.Value);
foreach (var logEntry in logEntries.Where(le => le.RequestMessage?.DateTime < checkTime || le.ResponseMessage?.DateTime < checkTime))
{
TryRemoveLogEntry(logEntry);
@@ -91,7 +97,7 @@ internal class WireMockMiddlewareLogger(
{
try
{
_options.LogEntries.Add(logEntry);
options.LogEntries.Add(logEntry);
}
catch
{
@@ -103,7 +109,7 @@ internal class WireMockMiddlewareLogger(
{
try
{
_options.LogEntries.Remove(logEntry);
options.LogEntries.Remove(logEntry);
}
catch
{
@@ -115,4 +115,7 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc />
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
/// <inheritdoc />
public IAdminPaths AdminPaths { get; set; } = new AdminPaths(null);
}
@@ -34,6 +34,7 @@ internal static class WireMockMiddlewareOptionsHelper
options.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
options.AdminPaths = new AdminPaths(settings.AdminPath);
#if USE_ASPNETCORE
options.AdditionalServiceRegistration = settings.AdditionalServiceRegistration;
@@ -19,7 +19,7 @@ public partial class Request
{
Guard.NotNullOrEmpty(matchers);
_requestMatchers.Add(new RequestMessageClientIPMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers));
_requestMatchers.Add(new RequestMessageClientIPMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
return this;
}
@@ -5,6 +5,7 @@
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.RequestBuilders;
@@ -84,7 +85,7 @@ public partial class Request
}
/// <inheritdoc />
public IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func)
public IRequestBuilder WithBody(Func<IDictionary<string, WireMockList<string>>?, bool> func)
{
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
@@ -11,7 +11,6 @@ using WireMock.RequestBuilders;
using WireMock.Settings;
using WireMock.Transformers;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.ResponseBuilders;
@@ -86,7 +85,11 @@ public partial class Response : IResponseBuilder
[PublicAPI]
public static IResponseBuilder Create(ResponseMessage? responseMessage = null)
{
var message = responseMessage ?? new ResponseMessage();
var message = responseMessage ?? new ResponseMessage
{
DateTime = DateTime.UtcNow // We set it here to the current time.
};
return new Response(message);
}
@@ -146,7 +146,7 @@ internal class MappingConverter(MatcherMapper mapper)
case JsonMatcher jsonMatcher:
{
var matcherType = jsonMatcher.GetType().Name;
var matcherType = jsonMatcher.GetType().FullName;
sb.AppendLine($" .WithBody(new {matcherType}(");
sb.AppendLine($" value: {ConvertToAnonymousObjectDefinition(jsonMatcher.Value, 3)},");
sb.AppendLine($" ignoreCase: {ToCSharpBooleanLiteral(jsonMatcher.IgnoreCase)},");
@@ -162,14 +162,18 @@ internal class MappingConverter(MatcherMapper mapper)
// Guid
sb.AppendLine($" .WithGuid(\"{mapping.Guid}\")");
// Scenario + State
// Scenario, ExecutionConditionState, NextState and StateTimes
if (!string.IsNullOrEmpty(mapping.Scenario))
{
sb.AppendLine($" .InScenario({ToCSharpStringLiteral(mapping.Scenario)})");
}
if (!string.IsNullOrEmpty(mapping.ExecutionConditionState))
{
sb.AppendLine($" .WhenStateIs({ToCSharpStringLiteral(mapping.ExecutionConditionState)})");
}
if (!string.IsNullOrEmpty(mapping.NextState))
{
sb.AppendLine($" .WhenStateIs({ToCSharpStringLiteral(mapping.NextState)}, {ToCSharpIntLiteral(mapping.TimesInSameState)})");
sb.AppendLine($" .WillSetStateTo({ToCSharpStringLiteral(mapping.NextState)}, {ToCSharpIntLiteral(mapping.TimesInSameState)})");
}
if (mapping.Probability != null)
@@ -342,7 +346,7 @@ internal class MappingConverter(MatcherMapper mapper)
if (clientIPMatcher?.Matchers != null)
{
var clientIPMatchers = _mapper.Map(clientIPMatcher.Matchers);
mappingModel.Request.Path = new ClientIPModel
mappingModel.Request.ClientIP = new ClientIPModel
{
Matchers = clientIPMatchers,
MatchOperator = clientIPMatchers?.Length > 1 ? clientIPMatcher.MatchOperator.ToString() : null
@@ -19,7 +19,6 @@ using WireMock.Owin;
using WireMock.RequestBuilders;
using WireMock.ResponseProviders;
using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
@@ -28,50 +27,16 @@ namespace WireMock.Server;
public partial class WireMockServer
{
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
private const string DefaultAdminPathPrefix = "/__admin";
private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
private AdminPaths? _adminPaths;
private sealed class AdminPaths
{
private readonly string _prefix;
private readonly string _prefixEscaped;
public AdminPaths(WireMockServerSettings settings)
{
_prefix = settings.AdminPath ?? DefaultAdminPathPrefix;
_prefixEscaped = _prefix.Replace("/", "\\/");
}
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 RegexMatcher MappingsGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher MappingsGuidEnablePathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\\/enable$");
public RegexMatcher MappingsGuidDisablePathMatcher => new($"^{_prefixEscaped}\\/mappings\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})\\/disable$");
public RegexMatcher MappingsCodeGuidPathMatcher => new($"^{_prefixEscaped}\\/mappings\\/code\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher RequestsGuidPathMatcher => new($"^{_prefixEscaped}\\/requests\\/([0-9A-Fa-f]{{8}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{4}}[-][0-9A-Fa-f]{{12}})$");
public RegexMatcher ScenariosNameMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+$");
public RegexMatcher ScenariosNameWithStateMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/state$");
public RegexMatcher ScenariosNameWithResetMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/reset$");
public RegexMatcher FilesFilenamePathMatcher => new($"^{_prefixEscaped}\\/files\\/.+$");
public RegexMatcher ProtoDefinitionsIdPathMatcher => new($"^{_prefixEscaped}\\/protodefinitions\\/.+$");
}
private IAdminPaths? _adminPaths;
#region InitAdmin
private void InitAdmin()
{
_adminPaths = new AdminPaths(_settings);
_adminPaths = _options.AdminPaths;
// __admin/health
Given(Request.Create().WithPath(_adminPaths.Health).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(HealthGet));
@@ -631,7 +596,7 @@ public partial class WireMockServer
{
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
{
var entry = LogEntries.SingleOrDefault(r => r.RequestMessage != null && !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
var entry = LogEntries.SingleOrDefault(r => r.RequestMessage != null && !_adminPaths!.Includes(r.RequestMessage.Path) && r.Guid == guid);
if (entry is { })
{
var model = new LogEntryMapper(_options).Map(entry);
@@ -660,7 +625,7 @@ public partial class WireMockServer
{
var logEntryMapper = new LogEntryMapper(_options);
var result = LogEntries
.Where(r => r.RequestMessage != null && !r.RequestMessage.Path.StartsWith("/__admin/"))
.Where(r => r.RequestMessage != null && !_adminPaths!.Includes(r.RequestMessage.Path))
.Select(logEntryMapper.Map);
return ToJson(result);
@@ -682,7 +647,7 @@ public partial class WireMockServer
var request = (Request)InitRequestBuilder(requestModel);
var dict = new Dictionary<ILogEntry, RequestMatchResult>();
foreach (var logEntry in LogEntries.Where(le => le.RequestMessage != null && !le.RequestMessage.Path.StartsWith("/__admin/")))
foreach (var logEntry in LogEntries.Where(le => le.RequestMessage != null && !_adminPaths!.Includes(le.RequestMessage.Path)))
{
var requestMatchResult = new RequestMatchResult();
if (request.GetMatchingScore(logEntry.RequestMessage!, requestMatchResult) > MatchScores.AlmostPerfect)
@@ -704,7 +669,7 @@ public partial class WireMockServer
Guid.TryParse(value.ToString(), out var mappingGuid)
)
{
var logEntries = LogEntries.Where(le => le.RequestMessage != null && !le.RequestMessage.Path.StartsWith("/__admin/") && le.MappingGuid == mappingGuid);
var logEntries = LogEntries.Where(le => le.RequestMessage != null && !_adminPaths!.Includes(le.RequestMessage.Path) && le.MappingGuid == mappingGuid);
var logEntryMapper = new LogEntryMapper(_options);
var result = logEntries.Select(logEntryMapper.Map);
return ToJson(result);
@@ -155,7 +155,8 @@ public partial class WireMockServer
var clientIPModel = _settings.DefaultJsonSerializer.ParseJsonToken<ClientIPModel>(requestModel.ClientIP);
if (clientIPModel.Matchers != null)
{
requestBuilder = requestBuilder.WithPath(clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
var matchOperator = StringUtils.ParseMatchOperator(clientIPModel.MatchOperator);
requestBuilder = requestBuilder.WithClientIP(matchOperator, clientIPModel.Matchers.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
}
}
}
File diff suppressed because it is too large. Load diff
@@ -43,7 +43,7 @@
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
<PackageReference Include="TinyMapper.Signed" Version="4.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.34.0" />
<PackageReference Include="Scriban.Signed" Version="7.2.0" />
<PackageReference Include="Scriban.Signed" Version="7.2.5" />
</ItemGroup>
<ItemGroup>
@@ -85,4 +85,4 @@
<ProjectReference Include="..\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" />
</ItemGroup>
</Project>
</Project>
@@ -25,9 +25,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.13.0" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
</ItemGroup>
<ItemGroup>
@@ -1,10 +1,10 @@
// Copyright © WireMock.Net
#if NETSTANDARD2_0
namespace System.Collections.Generic;
internal static class DictionaryExtensions
{
#if NETSTANDARD2_0
public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue>? dictionary, TKey key, TValue value)
{
if (dictionary is null || dictionary.ContainsKey(key))
@@ -16,5 +16,29 @@ internal static class DictionaryExtensions
return true;
}
}
#endif
#endif
public static bool TryGetFirstValue<TKey, TValue>(this IDictionary<TKey, TValue>? dictionary, out TValue? value)
{
if (dictionary != null && dictionary.Count > 0)
{
value = dictionary.First().Value;
return true;
}
value = default;
return false;
}
public static bool TryGetFirstOrDefault<TKey, TValue>(this IDictionary<TKey, TValue>? dictionary, out KeyValuePair<TKey, TValue> value)
{
if (dictionary != null && dictionary.Count > 0)
{
value = dictionary.First();
return true;
}
value = default;
return false;
}
}
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
@@ -56,37 +54,25 @@ internal static class OpenApiSchemaExtensions
public static SchemaFormat GetSchemaFormat(this IOpenApiSchema? schema)
{
switch (schema?.Format)
return (schema?.Format) switch
{
case "float":
return SchemaFormat.Float;
"float" => SchemaFormat.Float,
"double" => SchemaFormat.Double,
"int32" => SchemaFormat.Int32,
"int64" => SchemaFormat.Int64,
"date" => SchemaFormat.Date,
"date-time" => SchemaFormat.DateTime,
"password" => SchemaFormat.Password,
"byte" => SchemaFormat.Byte,
"binary" => SchemaFormat.Binary,
_ => SchemaFormat.Undefined,
};
}
case "double":
return SchemaFormat.Double;
case "int32":
return SchemaFormat.Int32;
case "int64":
return SchemaFormat.Int64;
case "date":
return SchemaFormat.Date;
case "date-time":
return SchemaFormat.DateTime;
case "password":
return SchemaFormat.Password;
case "byte":
return SchemaFormat.Byte;
case "binary":
return SchemaFormat.Binary;
default:
return SchemaFormat.Undefined;
}
internal static JsonNode? FindFirstExample(this IOpenApiSchema? schema)
{
#pragma warning disable CS0618 // Type or member is obsolete
return schema?.Examples?.FirstOrDefault() ?? schema?.Example;
#pragma warning restore CS0618 // Type or member is obsolete
}
}
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Net.OpenApiParser.Models;
@@ -16,7 +14,8 @@ namespace WireMock.Net.OpenApiParser.Extensions;
public static class WireMockServerExtensions
{
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file.
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 file.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
@@ -28,7 +27,8 @@ public static class WireMockServerExtensions
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file.
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 file.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
@@ -46,7 +46,8 @@ public static class WireMockServerExtensions
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream.
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 stream.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
@@ -58,7 +59,8 @@ public static class WireMockServerExtensions
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream.
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 stream.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
@@ -77,7 +79,8 @@ public static class WireMockServerExtensions
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 document.
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 document.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="document">The OpenAPI document to use as mappings.</param>
@@ -9,11 +9,11 @@
<InputAssemblies Include="@(ReferencePathWithRefAssemblies)" Condition="'%(filename)' == 'Microsoft.OpenApi'" />
<LibraryPath Include="%(ReferencePathWithRefAssemblies.RelativeDir)" />
</ItemGroup>
<ItemGroup>
<DoNotInternalizeAssemblies Include="WireMock.Net.OpenApiParser" />
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.IO;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Models;
using WireMock.Net.OpenApiParser.Settings;
@@ -15,6 +13,7 @@ public interface IWireMockOpenApiParser
{
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file.</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
@@ -23,6 +22,7 @@ public interface IWireMockOpenApiParser
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file.</param>
/// <param name="settings">Additional settings</param>
@@ -40,6 +40,7 @@ public interface IWireMockOpenApiParser
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
@@ -48,6 +49,7 @@ public interface IWireMockOpenApiParser
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="settings">Additional settings</param>
@@ -57,6 +59,7 @@ public interface IWireMockOpenApiParser
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="text">The source text</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
@@ -65,6 +68,7 @@ public interface IWireMockOpenApiParser
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary>
/// <param name="text">The source text</param>
/// <param name="settings">Additional settings</param>
@@ -53,7 +53,7 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
Headers = MapRequestHeaders(requestHeaders),
Body = GetRequestBodyModel(operation.RequestBody)
},
Response = GetResponseModel(operation.Responses?.FirstOrDefault())
Response = operation.Responses?.TryGetFirstOrDefault(out var firstResponse) == true ? GetResponseModel(firstResponse) : new ResponseModel { StatusCode = 200 }
};
}
@@ -70,8 +70,7 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
var requestExample = requestContent?.Example;
var requestExamples = requestContent?.Examples;
var requestSchemaExample = requestContent?.Schema?.Example;
var requestSchemaExamples = requestContent?.Schema?.Examples;
var requestSchemaExample = requestContent?.Schema.FindFirstExample();
JsonNode? request;
if (requestExample != null)
@@ -86,10 +85,6 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{
request = requestExamples.FirstOrDefault().Value.Value;
}
else if (requestSchemaExamples != null)
{
request = requestSchemaExamples.FirstOrDefault();
}
else
{
var requestSchema = content?.FirstOrDefault().Value.Schema;
@@ -99,16 +94,15 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
return MapRequestBody(request) ?? new BodyModel();
}
private ResponseModel GetResponseModel(KeyValuePair<string, IOpenApiResponse>? openApiResponse)
private ResponseModel GetResponseModel(KeyValuePair<string, IOpenApiResponse> openApiResponse)
{
var content = openApiResponse?.Value.Content;
var content = openApiResponse.Value.Content;
TryGetContent(content, out var responseContent, out var contentType);
var responseExample = responseContent?.Example;
var responseExamples = responseContent?.Examples;
var responseSchemaExample = responseContent?.Schema?.Example;
var responseSchemaExamples = responseContent?.Schema?.Examples;
var responseSchemaExample = responseContent?.Schema.FindFirstExample();
JsonNode? response;
if (responseExample != null)
@@ -119,24 +113,19 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{
response = responseSchemaExample;
}
else if (responseExamples != null)
else if (responseExamples.TryGetFirstValue(out var firstResponseFromExamples))
{
response = responseExamples.FirstOrDefault().Value.Value;
}
else if (responseSchemaExamples != null)
{
response = responseSchemaExamples.FirstOrDefault();
response = firstResponseFromExamples?.Value;
}
else
{
var responseSchema = content?.FirstOrDefault().Value?.Schema;
response = MapSchemaToObject(responseSchema);
response = content.TryGetFirstValue(out var firstContent) ? MapSchemaToObject(firstContent?.Schema) : null;
}
return new ResponseModel
{
StatusCode = int.TryParse(openApiResponse?.Key, out var httpStatusCode) ? httpStatusCode : 200,
Headers = MapHeaders(contentType, openApiResponse?.Value.Headers),
StatusCode = int.TryParse(openApiResponse.Key, out var httpStatusCode) ? httpStatusCode : 200,
Headers = MapHeaders(contentType, openApiResponse.Value.Headers),
BodyAsJson = response != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(response)) : null
};
}
@@ -159,7 +148,7 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
};
}
private static bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
private static bool TryGetContent(IDictionary<string, IOpenApiMediaType>? contents, [NotNullWhen(true)] out IOpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
{
openApiMediaType = null;
contentType = null;
@@ -1,9 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using RamlToOpenApiConverter;
namespace WireMock.Net.OpenApiParser.Models;
/// <summary>
@@ -24,5 +21,5 @@ public class OpenApiDiagnostic
/// <summary>
/// Open API specification version of the document parsed.
/// </summary>
public OpenApiSpecificationVersion SpecificationVersion { get; set; }
public OpenApiSpecVersion SpecificationVersion { get; set; }
}
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using RamlToOpenApiConverter;
using MicrosoftOpenApiDiagnostic = Microsoft.OpenApi.Reader.OpenApiDiagnostic;
namespace WireMock.Net.OpenApiParser.Models;
@@ -19,7 +17,7 @@ internal static class OpenApiMapper
{
Errors = openApiDiagnostic.Errors.Select(e => new OpenApiError(e.Pointer, e.Message)).ToList(),
Warnings = openApiDiagnostic.Warnings.Select(e => new OpenApiError(e.Pointer, e.Message)).ToList(),
SpecificationVersion = (OpenApiSpecificationVersion)openApiDiagnostic.SpecificationVersion
SpecificationVersion = (OpenApiSpecVersion)openApiDiagnostic.SpecificationVersion
};
}
}
@@ -0,0 +1,29 @@
// Copyright © WireMock.Net
namespace WireMock.Net.OpenApiParser.Models;
/// <summary>
/// Represents versions of OpenAPI specification.
/// </summary>
public enum OpenApiSpecVersion
{
/// <summary>
/// Represents OpenAPI V2.0 spec
/// </summary>
OpenApi2_0,
/// <summary>
/// Represents all patches of OpenAPI V3.0 spec (e.g. 3.0.0, 3.0.1)
/// </summary>
OpenApi3_0,
/// <summary>
/// Represents OpenAPI V3.1 spec
/// </summary>
OpenApi3_1,
/// <summary>
/// Represents OpenAPI V3.2 spec
/// </summary>
OpenApi3_2
}
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi;
namespace WireMock.Net.OpenApiParser.Settings;
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi;
namespace WireMock.Net.OpenApiParser.Settings;
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Globalization;
namespace WireMock.Net.OpenApiParser.Utils;
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using System.Text.Json.Nodes;
using Microsoft.OpenApi;
using Stef.Validation;
@@ -39,7 +37,7 @@ internal class ExampleValueGenerator
public JsonNode GetExampleValue(IOpenApiSchema? schema)
{
var schemaExample = schema?.Example;
var schemaExample = schema?.FindFirstExample();
var schemaEnum = schema?.Enum?.FirstOrDefault();
_exampleValues.Schema = schema;
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description>
@@ -23,7 +23,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Required" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -40,25 +40,29 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.11.0" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.19.1" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
</ItemGroup>
<!-- https://github.com/wiremock/WireMock.Net/issues/1508 -->
<ItemGroup>
<PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.50.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="3.10.2" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<!-- Keep at version 2.0.43 (https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task/issues/80) -->
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.43" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi" Version="2.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" PrivateAssets="All" />
<PackageReference Include="SharpYaml" Version="2.1.3" />
<PackageReference Include="Microsoft.OpenApi" Version="3.10.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="3.10.2" PrivateAssets="All" />
<PackageReference Include="SharpYaml" Version="2.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
</Project>
</Project>
@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using JetBrains.Annotations;
using Microsoft.OpenApi;
@@ -18,7 +16,7 @@ using OpenApiDiagnostic = WireMock.Net.OpenApiParser.Models.OpenApiDiagnostic;
namespace WireMock.Net.OpenApiParser;
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3/V3.1 to WireMock.Net MappingModels.
/// Parse a OpenApi/Swagger/V2/V3/V3.1/V3.2 to WireMock.Net MappingModels.
/// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser
{
@@ -32,8 +30,15 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
_readerSettings = new OpenApiReaderSettings();
_readerSettings.AddMicrosoftExtensionParsers();
_readerSettings.AddJsonReader();
_readerSettings.TryAddReader(OpenApiConstants.Yaml, new OpenApiYamlReader());
_readerSettings.TryAddReader(OpenApiConstants.Yml, new OpenApiYamlReader());
var openApiYamlReaderSettings = new OpenApiYamlReaderSettings
{
MaxAliasExpansionNodeCount = 1_000_000
};
var openApiYamlReader = new OpenApiYamlReader(openApiYamlReaderSettings);
_readerSettings.TryAddReader(OpenApiConstants.Yaml, openApiYamlReader);
_readerSettings.TryAddReader(OpenApiConstants.Yml, openApiYamlReader);
}
/// <inheritdoc />
@@ -55,7 +60,12 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
}
else
{
document = Read(File.OpenRead(path), out diagnostic);
if (!TryRead(File.OpenRead(path), out var documentFromYaml, out diagnostic))
{
return [];
}
document = documentFromYaml;
}
return FromDocument(document, settings);
@@ -74,14 +84,24 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
[PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{
return FromDocument(Read(stream, out diagnostic));
if (TryRead(stream, out var openApiDocument, out diagnostic))
{
return FromDocument(openApiDocument);
}
return [];
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
return FromDocument(Read(stream, out diagnostic), settings);
if (TryRead(stream, out var openApiDocument, out diagnostic))
{
return FromDocument(openApiDocument, settings);
}
return [];
}
/// <inheritdoc />
@@ -98,7 +118,7 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), settings, out diagnostic);
}
private OpenApiDocument Read(Stream stream, out OpenApiDiagnostic diagnostic)
private bool TryRead(Stream stream, [NotNullWhen(true)] out OpenApiDocument? openApiDocument, out OpenApiDiagnostic diagnostic)
{
if (stream is not MemoryStream memoryStream)
{
@@ -108,7 +128,8 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
var result = OpenApiDocument.Load(memoryStream, settings: _readerSettings);
diagnostic = OpenApiMapper.Map(result.Diagnostic) ?? new OpenApiDiagnostic();
return result.Document ?? throw new InvalidOperationException("The document is null.");
openApiDocument = result.Document;
return openApiDocument != null && !diagnostic.Errors.Any();
}
private static MemoryStream ReadStreamIntoMemoryStream(Stream stream)
@@ -33,9 +33,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.13.0" />
<PackageReference Include="RestEase" Version="1.6.4" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
</ItemGroup>
<ItemGroup>
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using AnyOfTypes;
using WireMock.Models;
@@ -1,8 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Types;
namespace WireMock.Matchers;
@@ -57,7 +55,7 @@ public static class MatchScores
/// <param name="values">The values.</param>
/// <param name="matchOperator">The <see cref="MatchOperator"/>.</param>
/// <returns>average score</returns>
public static double ToScore(IReadOnlyCollection<bool> values, MatchOperator matchOperator)
public static double ToScore(IReadOnlyList<bool> values, MatchOperator matchOperator)
{
return ToScore(values.Select(ToScore).ToArray(), matchOperator);
}
@@ -68,9 +66,9 @@ public static class MatchScores
/// <param name="values">The values.</param>
/// <param name="matchOperator"></param>
/// <returns>average score</returns>
public static double ToScore(IReadOnlyCollection<double> values, MatchOperator matchOperator)
public static double ToScore(IReadOnlyList<double> values, MatchOperator matchOperator)
{
if (!values.Any())
if (values.Count == 0)
{
return Mismatch;
}
@@ -82,4 +80,28 @@ public static class MatchScores
_ => values.Average()
};
}
internal static double ToScore(WireMockList<string> values, IStringMatcher[] matchers, MatchOperator matchOperator = MatchOperator.And)
{
// Create a matrix of scores where each row corresponds to a value and each column corresponds to a matcher.
var matrix = values
.Select(value => matchers
.Select(matcher => matcher.IsMatch(value).Score).ToArray()
)
.ToArray();
if (matrix.Length == 0 || matchers.Length == 0)
{
return Mismatch;
}
// For each value, how well was it matched by its best matcher?
var rowScore = matchOperator == MatchOperator.And ? matrix.Average(row => row.Max()) : matrix.Max(row => row.Max());
// For each matcher, how well was it satisfied by its best value?
var columnRange = Enumerable.Range(0, matchers.Length);
var columnScore = matchOperator == MatchOperator.And ? columnRange.Average(column => matrix.Max(row => row[column])) : columnRange.Max(column => matrix.Max(row => row[column]));
return matchOperator == MatchOperator.And ? Math.Min(rowScore, columnScore) : Math.Max(rowScore, columnScore);
}
}
@@ -75,7 +75,7 @@ public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(NotNullOrEmptyMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}" +
$")";
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net
using System;
using System.Linq;
using System.Text.RegularExpressions;
using AnyOfTypes;
using JetBrains.Annotations;
@@ -132,10 +130,10 @@ public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher
/// <inheritdoc />
public virtual string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(RegexMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
$"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(_useRegexExtended)}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}" +
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Text.RegularExpressions;
using AnyOfTypes;
using Stef.Validation;
@@ -74,10 +73,10 @@ public class WildcardMatcher : RegexMatcher
/// <inheritdoc />
public override string GetCSharpCodeArguments()
{
return $"new {Name}" +
return $"new {typeof(WildcardMatcher).FullName}" +
$"(" +
$"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " +
$"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " +
$"new AnyOfTypes.AnyOf<string, WireMock.Models.StringPattern>[] {{ {MappingConverterUtils.ToCSharpCodeArguments(_patterns)} }}, " +
$"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " +
$"{MatchOperator.GetFullyQualifiedEnumValue()}" +
$")";
+1 -1
View File
@@ -22,7 +22,7 @@ public class BodyData : IBodyData
public string? BodyAsString { get; set; }
/// <inheritdoc />
public IDictionary<string, string>? BodyAsFormUrlEncoded { get; set; }
public IDictionary<string, WireMockList<string>>? BodyAsFormUrlEncoded { get; set; }
/// <inheritdoc />
public object? BodyAsJson { get; set; }
@@ -1,8 +1,7 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using WireMock.Matchers;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.RequestBuilders;
@@ -100,5 +99,5 @@ public interface IBodyRequestBuilder : IMultiPartRequestBuilder
/// </summary>
/// <param name="func">The form-urlencoded values.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<IDictionary<string, string>?, bool> func);
IRequestBuilder WithBody(Func<IDictionary<string, WireMockList<string>>?, bool> func);
}
@@ -305,12 +305,6 @@ public class WireMockServerSettings
[PublicAPI, JsonIgnore]
public IDictionary<string, Func<MatcherModel, IMatcher>>? CustomMatcherMappings { get; set; }
/// <summary>
/// The <see cref="JsonSerializerSettings"/> used when the JSON response is generated.
/// </summary>
[PublicAPI, JsonIgnore]
public JsonSerializerSettings? JsonSerializerSettings { get; set; }
/// <summary>
/// The Culture to use.
/// Currently used for:
+3 -2
View File
@@ -5,6 +5,7 @@ using System.Text;
using Stef.Validation;
using WireMock.Constants;
using WireMock.Matchers;
using WireMock.Serialization;
using WireMock.Types;
namespace WireMock.Util;
@@ -25,7 +26,7 @@ internal static class BodyParser
CONNECT - No defined body semantics
PATCH - Body supported.
*/
private static readonly IDictionary<string, bool> BodyAllowedForMethods = new Dictionary<string, bool>
private static readonly Dictionary<string, bool> BodyAllowedForMethods = new()
{
{ HttpRequestMethod.HEAD, false },
{ HttpRequestMethod.GET, false },
@@ -173,7 +174,7 @@ internal static class BodyParser
{
try
{
data.BodyAsJson = settings.DefaultJsonConverter.Deserialize<object>(data.BodyAsString);
data.BodyAsJson = settings.DefaultJsonConverter.Deserialize<object>(data.BodyAsString, JsonSerializationConstants.JsonConverterOptionsWithDateParsingNone);
data.DetectedBodyType = BodyType.Json;
}
catch
@@ -1,9 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using WireMock.Types;
@@ -16,7 +13,7 @@ internal static class QueryStringParser
{
private static readonly Dictionary<string, WireMockList<string>> Empty = new();
public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary<string, string>? nameValueCollection)
public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary<string, WireMockList<string>>? nameValueCollection)
{
if (queryString == null)
{
@@ -29,12 +26,22 @@ internal static class QueryStringParser
.Select(parameter => parameter.Split('='))
.Distinct();
nameValueCollection = caseIgnore ? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, string>();
nameValueCollection = caseIgnore ? new Dictionary<string, WireMockList<string>>(StringComparer.OrdinalIgnoreCase) : new Dictionary<string, WireMockList<string>>();
foreach (var part in parts)
{
if (part.Length == 2)
{
nameValueCollection.Add(part[0], WebUtility.UrlDecode(part[1]));
var key = part[0];
var value = WebUtility.UrlDecode(part[1]);
if (!nameValueCollection.TryGetValue(key, out var stringList))
{
nameValueCollection.Add(key, value);
}
else
{
stringList.Add(value);
}
}
}
@@ -27,11 +27,11 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.9" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
<PackageReference Include="AnyOf" Version="0.5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.12.0" />
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.12.0" />
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.13.0" />
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.13.0" />
</ItemGroup>
<ItemGroup>
@@ -27,7 +27,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
<PackageReference Include="TUnit.Core" Version="1.17.29" />
</ItemGroup>
@@ -38,8 +38,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Testcontainers" Version="4.12.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
<PackageReference Include="Testcontainers" Version="4.14.0" />
</ItemGroup>
<ItemGroup>
@@ -23,7 +23,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
<PackageReference Include="xunit.v3.extensibility.core" Version="3.2.2" />
</ItemGroup>
@@ -1,6 +1,5 @@
// Copyright © WireMock.Net
using System;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Requests;
@@ -14,51 +13,61 @@ namespace WireMock.Net.Xunit;
/// </summary>
public sealed class TestOutputHelperWireMockLogger : IWireMockLogger
{
private readonly ITestOutputHelper _testOutputHelper;
private readonly Func<ITestOutputHelper?> _testOutputHelperFactory;
/// <summary>
/// Create a new instance on the <see cref="TestOutputHelperWireMockLogger"/>.
/// </summary>
/// <param name="testOutputHelper">Represents a class which can be used to provide test output.</param>
public TestOutputHelperWireMockLogger(ITestOutputHelper testOutputHelper)
public TestOutputHelperWireMockLogger(ITestOutputHelper testOutputHelper) :
this(() => testOutputHelper)
{
_testOutputHelper = Guard.NotNull(testOutputHelper);
Guard.NotNull(testOutputHelper);
}
/// <summary>
/// Create a new instance on the <see cref="TestOutputHelperWireMockLogger"/>.
/// </summary>
/// <param name="testOutputHelperFactory">Represents a factory to provide current test output.</param>
public TestOutputHelperWireMockLogger(Func<ITestOutputHelper?> testOutputHelperFactory)
{
_testOutputHelperFactory = Guard.NotNull(testOutputHelperFactory);
}
/// <inheritdoc />
public void Debug(string formatString, params object[] args)
{
_testOutputHelper.WriteLine(Format("Debug", formatString, args));
_testOutputHelperFactory()?.WriteLine(Format("Debug", formatString, args));
}
/// <inheritdoc />
public void Info(string formatString, params object[] args)
{
_testOutputHelper.WriteLine(Format("Info", formatString, args));
_testOutputHelperFactory()?.WriteLine(Format("Info", formatString, args));
}
/// <inheritdoc />
public void Warn(string formatString, params object[] args)
{
_testOutputHelper.WriteLine(Format("Warning", formatString, args));
_testOutputHelperFactory()?.WriteLine(Format("Warning", formatString, args));
}
/// <inheritdoc />
public void Error(string formatString, params object[] args)
{
_testOutputHelper.WriteLine(Format("Error", formatString, args));
_testOutputHelperFactory()?.WriteLine(Format("Error", formatString, args));
}
/// <inheritdoc />
public void Error(string message, Exception exception)
{
_testOutputHelper.WriteLine(Format("Error", $"{message} {{0}}", exception));
_testOutputHelperFactory()?.WriteLine(Format("Error", $"{message} {{0}}", exception));
if (exception is AggregateException ae)
{
ae.Handle(ex =>
{
_testOutputHelper.WriteLine(Format("Error", "Exception {0}", ex));
_testOutputHelperFactory()?.WriteLine(Format("Error", "Exception {0}", ex));
return true;
});
}
@@ -72,7 +81,7 @@ public sealed class TestOutputHelperWireMockLogger : IWireMockLogger
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
});
_testOutputHelper.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
_testOutputHelperFactory()?.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
}
private static string Format(string level, string formatString, params object[] args)
@@ -26,7 +26,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="Stef.Validation" Version="0.3.0" />
<PackageReference Include="xUnit.abstractions" Version="2.0.3" />
</ItemGroup>
@@ -19,7 +19,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.3" />
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup>
<ItemGroup>
@@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="13.4.0" />
<PackageReference Include="Aspire.Hosting.Testing" Version="13.4.3" />
<PackageReference Include="Codecov" Version="1.13.0" />
<PackageReference Include="coverlet.msbuild" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
@@ -32,6 +32,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- CVE-2026-48109 -->
<PackageReference Include="MessagePack" Version="2.5.302" />
</ItemGroup>
<ItemGroup>
@@ -16,7 +16,7 @@
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -24,7 +24,7 @@
</PackageReference>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
@@ -11,12 +11,12 @@
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
<PackageReference Include="coverlet.collector" Version="8.0.1">
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="WireMock.Net" Version="2.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageReference Include="WireMock.Net" Version="2.12.0" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
@@ -29,13 +29,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="JetBrains.Annotations" Version="2025.2.4" />
<PackageReference Include="OpenTelemetry" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Api" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<!--<PackageReference Update="JetBrains.Annotations" Version="2025.2.4" />-->
<PackageReference Include="OpenTelemetry" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Api" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.17.0" />
<PackageReference Update="SonarAnalyzer.CSharp" Version="10.11.0.117924" />
</ItemGroup>
Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff. Show more