From 96eca4262aa89335b0bede4e46b743055132bfeb Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 24 May 2025 12:17:42 +0200 Subject: [PATCH] Create WireMock.Net.MimePart project (#1300) * Create WireMock.Net.MimePart project * . * REFACTOR * ILRepack * -- * ... * x * x * . * fix * public class MimePartMatcher * shared * min * . * * Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 7 + WireMock.Net Solution.sln | 21 + .../MainApp.cs | 4 +- .../Models/StringPattern.cs | 0 .../Properties/AssemblyInfo.cs | 8 + .../WireMock.Net.Abstractions.csproj | 48 +- .../WireMock.Net.AspNetCore.Middleware.csproj | 7 +- .../WireMock.Net.Aspire.csproj | 9 +- .../WireMock.Net.AwesomeAssertions.csproj | 5 +- .../WireMock.Net.FluentAssertions.csproj | 7 +- .../Matchers/CSharpCodeMatcher.cs | 4 +- .../WireMock.Net.Matchers.CSharpCode.csproj | 83 +- src/WireMock.Net.MimePart/ILRepack.targets | 28 + .../Matchers/MimePartMatcher.cs | 35 +- .../Properties/AssemblyInfo.cs | 5 + .../Util/MimeKitUtils.cs | 31 +- .../WireMock.Net.MimePart.csproj | 63 + .../.filenesting.json | 0 .../AzureADAuthenticationMatcher.cs | 4 +- .../BasicAuthenticationMatcher.cs | 0 .../Compatibility/StringExtensions.cs | 2 +- .../Compatibility/WebProxy.cs | 0 .../Constants/WireMockConstants.cs | 4 - .../Extensions/DictionaryExtensions.cs | 0 .../Extensions/StringExtensions.cs | 0 .../Extensions/TimeSettingsExtensions.cs | 0 .../Handlers/LocalFileSystemHandler.cs | 0 .../Http/ByteArrayContentHelper.cs | 0 .../Http/HttpClientBuilder.cs | 0 .../Http/HttpClientFactory2.cs | 0 .../Http/HttpRequestMessageHelper.cs | 2 +- .../Http/HttpResponseMessageHelper.cs | 0 .../Http/StringContentHelper.cs | 0 .../Http/WebhookSender.cs | 242 +-- .../HttpsCertificate/CertificateLoader.cs | 0 .../PublicCertificateHelper.cs | 0 .../IMapping.cs | 0 .../IMappingBuilder.cs | 0 .../Json/DynamicJsonClassOptions.cs | 0 .../Json/FloatBehavior.cs | 0 .../Json/IntegerBehavior.cs | 0 .../Logging/LogEntry.cs | 76 +- .../Logging/WireMockConsoleLogger.cs | 0 .../Logging/WireMockNullLogger.cs | 0 .../Mapping.cs | 392 ++-- .../MappingBuilder.cs | 0 .../MappingRegistrationCallback.cs | 0 .../Matchers/AbstractJsonPartialMatcher.cs | 0 .../Matchers/ContentTypeMatcher.cs | 178 +- .../Matchers/ExactMatcher.cs | 212 +- .../Matchers/FormUrlEncodedMatcher.cs | 356 ++-- .../Matchers/GraphQLMatcher.cs | 0 .../Matchers/IJsonMatcher.cs | 0 .../Matchers/JSONPathMatcher.cs | 332 ++-- .../Matchers/JmesPathMatcher.cs | 258 +-- .../Matchers/JsonMatcher.cs | 498 ++--- .../Matchers/JsonPartialMatcher.cs | 0 .../Matchers/JsonPartialWildCardMatcher.cs | 0 .../Matchers/LinqMatcher.cs | 300 +-- .../Matchers/MatcherExtensions.cs | 0 .../Models/WireMockCustomScalarGraphType.cs | 0 .../Matchers/ProtoBufMatcher.cs | 0 .../Matchers/Request/CompositeMatcherType.cs | 0 .../Matchers/Request/RequestMatchResult.cs | 0 .../Request/RequestMessageBodyMatcher.cs | 376 ++-- .../Request/RequestMessageClientIPMatcher.cs | 0 .../Request/RequestMessageCompositeMatcher.cs | 0 .../Request/RequestMessageCookieMatcher.cs | 0 .../Request/RequestMessageGraphQLMatcher.cs | 0 .../Request/RequestMessageHeaderMatcher.cs | 0 .../RequestMessageHttpVersionMatcher.cs | 0 .../Request/RequestMessageMethodMatcher.cs | 0 .../Request/RequestMessageMultiPartMatcher.cs | 17 +- .../Request/RequestMessageParamMatcher.cs | 0 .../Request/RequestMessagePathMatcher.cs | 0 .../Request/RequestMessageProtoBufMatcher.cs | 0 .../RequestMessageScenarioAndStateMatcher.cs | 0 .../Request/RequestMessageUrlMatcher.cs | 0 .../Matchers/Request/_mock4net-license.txt | 0 .../Matchers/SimMetricsMatcher.cs | 278 +-- .../Matchers/XPathMatcher.cs | 366 ++-- .../Models/BlockingQueue.cs | 0 .../Models/GraphQLSchemaDetails.cs | 0 .../Models/ProtoDefinitionData.cs | 0 .../Models/TimeSettings.cs | 0 .../Models/UrlDetails.cs | 0 .../Models/Webhook.cs | 22 +- .../Models/WebhookRequest.cs | 84 +- .../Owin/AspNetCoreSelfHost.NETCore.cs | 0 .../Owin/AspNetCoreSelfHost.NETStandard.cs | 246 +-- .../Owin/AspNetCoreSelfHost.NETStandard13.cs | 132 +- .../Owin/AspNetCoreSelfHost.cs | 376 ++-- .../Owin/GlobalExceptionMiddleware.cs | 0 .../Owin/HostUrlDetails.cs | 0 .../Owin/HostUrlOptions.cs | 126 +- .../Owin/IMappingMatcher.cs | 14 +- .../Owin/IOwinSelfHost.cs | 0 .../Owin/IWireMockMiddlewareOptions.cs | 0 .../Owin/Mappers/IOwinRequestMapper.cs | 0 .../Owin/Mappers/IOwinResponseMapper.cs | 0 .../Owin/Mappers/OwinRequestMapper.cs | 0 .../Owin/Mappers/OwinResponseMapper.cs | 536 ++--- .../Owin/MappingMatcher.cs | 0 .../Owin/MappingMatcherResult.cs | 0 .../Owin/OwinSelfHost.cs | 0 .../Owin/WireMockMiddleware.cs | 736 +++---- .../Owin/WireMockMiddlewareOptions.cs | 0 .../Owin/WireMockMiddlewareOptionsHelper.cs | 0 .../Pact/Models/V2/Interaction.cs | 0 .../Pact/Models/V2/MatchingRule.cs | 0 .../Pact/Models/V2/Metadata.cs | 0 .../Pact/Models/V2/Pact.cs | 0 .../Pact/Models/V2/PactRequest.cs | 0 .../Pact/Models/V2/PactResponse.cs | 0 .../Pact/Models/V2/PactRust.cs | 0 .../Pact/Models/V2/PactSpecification.cs | 0 .../Pact/Models/V2/Pacticipant.cs | 0 .../Pact/Models/V2/ProviderState.cs | 0 .../Pact/Models/V2/_v2.json | 0 .../Properties/AssemblyInfo.cs | 18 +- .../Proxy/ProxyHelper.cs | 204 +- .../RequestBuilders/IBodyRequestBuilder.cs | 206 +- .../IClientIPRequestBuilder.cs | 0 .../RequestBuilders/ICookiesRequestBuilder.cs | 0 .../RequestBuilders/IGraphQLRequestBuilder.cs | 0 .../RequestBuilders/IHeadersRequestBuilder.cs | 0 .../RequestBuilders/IHttpVersionBuilder.cs | 0 .../RequestBuilders/IMethodRequestBuilder.cs | 0 .../IMultiPartRequestBuilder.cs | 0 .../RequestBuilders/IParamsRequestBuilder.cs | 0 .../IProtoBufRequestBuilder.cs | 0 .../RequestBuilders/IRequestBuilder.cs | 0 .../IUrlAndPathRequestBuilder.cs | 0 .../RequestBuilders/Request.ClientIP.cs | 0 .../RequestBuilders/Request.UsingMethods.cs | 0 .../RequestBuilders/Request.WithBody.cs | 212 +- .../Request.WithBodyAsProtoBuf.cs | 0 .../RequestBuilders/Request.WithCookies.cs | 0 .../Request.WithGraphQLSchema.cs | 0 .../RequestBuilders/Request.WithHeaders.cs | 0 .../Request.WithHttpVersion.cs | 0 .../RequestBuilders/Request.WithMultiPart.cs | 0 .../RequestBuilders/Request.WithParam.cs | 0 .../RequestBuilders/Request.WithPath.cs | 0 .../RequestBuilders/Request.WithUrl.cs | 0 .../RequestBuilders/Request.cs | 0 .../RequestMessage.cs | 422 ++-- .../ResponseBuilders/BodyDestinationFormat.cs | 0 .../ResponseBuilders/IBodyResponseBuilder.cs | 336 ++-- .../ICallbackResponseBuilder.cs | 54 +- .../ResponseBuilders/IDelayResponseBuilder.cs | 0 .../ResponseBuilders/IFaultRequestBuilder.cs | 0 .../IHeadersResponseBuilder.cs | 0 .../ResponseBuilders/IProxyResponseBuilder.cs | 0 .../ResponseBuilders/IResponseBuilder.cs | 0 .../IStatusCodeResponseBuilder.cs | 0 .../ITransformResponseBuilder.cs | 0 .../ResponseBuilders/Response.WithBody.cs | 0 .../ResponseBuilders/Response.WithCallback.cs | 126 +- .../ResponseBuilders/Response.WithFault.cs | 0 .../ResponseBuilders/Response.WithHeaders.cs | 0 .../ResponseBuilders/Response.WithProxy.cs | 0 .../Response.WithTransformer.cs | 0 .../ResponseBuilders/Response.cs | 612 +++--- .../ResponseMessage.cs | 0 .../ResponseMessageBuilder.cs | 0 .../DynamicAsyncResponseProvider.cs | 0 .../DynamicResponseProvider.cs | 0 .../ResponseProviders/IResponseProvider.cs | 0 .../ProxyAsyncResponseProvider.cs | 0 .../ScenarioState.cs | 0 .../Serialization/LogEntryMapper.cs | 0 .../Serialization/MappingConverter.cs | 25 +- .../Serialization/MappingConverterSettings.cs | 0 .../Serialization/MappingFileNameSanitizer.cs | 100 +- .../Serialization/MappingToFileSaver.cs | 0 .../Serialization/MatcherMapper.cs | 606 +++--- .../Serialization/PactMapper.cs | 0 .../Serialization/ProxyMappingConverter.cs | 6 +- .../Serialization/SwaggerMapper.cs | 0 .../Serialization/TimeSettingsMapper.cs | 0 .../Serialization/WebhookMapper.cs | 234 +-- .../Server/IRespondWithAProvider.cs | 508 ++--- .../Server/RespondWithAProvider.cs | 806 ++++---- .../Server/WireMockServer.Admin.cs | 1770 ++++++++--------- .../Server/WireMockServer.AdminFiles.cs | 0 .../Server/WireMockServer.ConvertMapping.cs | 0 .../Server/WireMockServer.Fluent.cs | 0 .../WireMockServer.ImportWireMockOrg.cs | 0 .../Server/WireMockServer.LogEntries.cs | 196 +- .../Server/WireMockServer.OpenApiParser.cs | 0 .../Server/WireMockServer.Proxy.cs | 0 .../Server/WireMockServer.cs | 1424 ++++++------- .../Services/IRandomizerDoubleBetween0And1.cs | 0 .../Services/RandomizerDoubleBetween0And1.cs | 0 .../Settings/HandlebarsSettings.cs | 0 .../Settings/HttpClientSettings.cs | 0 .../Settings/ProxyAndRecordSettings.cs | 228 +-- .../Settings/ProxySaveMappingSetting.cs | 0 .../Settings/ProxySaveMappingSettings.cs | 0 .../Settings/ProxyUrlReplaceSettings.cs | 46 +- .../Settings/SimpleSettingsParser.cs | 2 +- .../Settings/WebProxySettings.cs | 0 .../Settings/WebhookSettings.cs | 0 .../Settings/WireMockCertificateSettings.cs | 0 .../Settings/WireMockServerSettings.cs | 0 .../Settings/WireMockServerSettingsParser.cs | 430 ++-- .../Transformers/Handlebars/FileHelpers.cs | 66 +- .../Handlebars/HandlebarsContext.cs | 76 +- .../Handlebars/HandlebarsContextFactory.cs | 0 .../Handlebars/IHandlebarsContext.cs | 18 +- .../Handlebars/WireMockHandlebarsHelpers.cs | 114 +- .../Transformers/ITransformer.cs | 34 +- .../Transformers/ITransformerContext.cs | 26 +- .../ITransformerContextFactory.cs | 0 .../Transformers/Scriban/ScribanContext.cs | 66 +- .../Scriban/ScribanContextFactory.cs | 46 +- .../Scriban/WireMockListAccessor.cs | 140 +- .../Scriban/WireMockTemplateContext.cs | 32 +- .../Transformers/TransformModel.cs | 0 .../Transformers/Transformer.cs | 738 +++---- .../Util/ConcurrentObservableCollection.cs | 0 .../Util/CultureInfoExtensions.cs | 0 .../Util/DateTimeUtils.cs | 0 .../Util/DictionaryExtensions.cs | 0 .../Util/EnhancedFileSystemWatcher.cs | 0 .../Util/FileHelper.cs | 0 .../Util/FilePathUtils.cs | 0 .../Util/GuidUtils.cs | 0 .../Util/HttpStatusRangeParser.cs | 0 .../Util/HttpVersionParser.cs | 2 +- .../Util/PortUtils.cs | 2 +- .../Util/ProtoBufUtils.cs | 0 .../Util/ProtoDefinitionHelper.cs | 0 .../Util/ReflectionUtils.cs | 0 .../Util/RegexUtils.cs | 4 +- .../Util/SingletonFactory.cs | 0 .../Util/StreamUtils.cs | 0 .../Util/StringUtils.cs | 0 .../Util/SystemUtils.cs | 0 .../Util/TinyMapperUtils.cs | 110 +- .../Util/TypeLoader.cs | 45 +- .../Util/UrlUtils.cs | 0 .../Util/WireMockProtoFileResolver.cs | 0 .../WireMock.Net.Minimal.csproj | 186 ++ .../WireMock.Net.OpenApiParser.csproj | 7 +- .../WireMock.Net.RestClient.csproj | 73 +- .../Constants/RegexConstants.cs | 16 + .../Exceptions/WireMockException.cs | 0 .../Extensions/AnyOfExtensions.cs | 0 .../Extensions/EnumExtensions.cs | 9 + .../Extensions/ExceptionExtensions.cs | 0 .../Http/HttpKnownHeaderNames.cs | 4 +- .../Matchers/ExactObjectMatcher.cs | 162 +- .../Helpers/BodyDataMatchScoreCalculator.cs | 2 +- .../Matchers/IBytesMatcher.cs | 0 .../Matchers/ICSharpCodeMatcher.cs | 22 +- .../Matchers/IDecodeBytesMatcher.cs} | 0 .../Matchers/IIgnoreCaseMatcher.cs | 0 .../Matchers/IMatcher.cs | 48 +- .../Matchers/IMimePartMatcher.cs | 37 + .../Matchers/IObjectMatcher.cs | 0 .../Matchers/IProtoBufMatcher.cs | 0 .../Matchers/IStringMatcher.cs | 0 .../Matchers/MatchBehaviour.cs | 0 .../Matchers/MatchBehaviourHelper.cs | 14 +- .../Matchers/MatchOperator.cs | 0 .../Matchers/MatchResult.cs | 0 .../Matchers/MatchScores.cs | 0 .../Matchers/NotNullOrEmptyMatcher.cs | 167 +- .../Matchers/RegexMatcher.cs | 286 +-- .../Matchers/WildcardMatcher.cs | 192 +- .../Models/BodyData.cs | 0 .../Properties/AssemblyInfo.cs | 12 + .../RegularExpressions/RegexExtended.cs | 1 + .../JsonSerializationConstants.cs | 0 .../Util/BodyParser.cs | 431 ++-- .../Util/BodyParserSettings.cs | 38 + .../Util/BytesEncodingUtils.cs | 9 +- .../Util/CSharpFormatter.cs | 7 +- .../Util/CompressionUtils.cs | 15 + src/WireMock.Net.Shared/Util/IMimeKitUtils.cs | 35 + .../Util/JsonUtils.cs | 0 .../Util/MappingConverterUtils.cs | 36 + .../Util/QueryStringParser.cs | 6 +- .../WireMock.Net.Shared.csproj | 45 + .../WireMock.Net.StandAlone.csproj | 74 +- .../WireMock.Net.Testcontainers.csproj | 16 +- src/WireMock.Net/Compatibility/EmptyArray.cs | 13 - src/WireMock.Net/Util/BodyParserSettings.cs | 20 - .../Util/MappingConverterUtils.cs | 23 - src/WireMock.Net/WireMock.Net.csproj | 181 +- .../WireMock.Org.Abstractions.csproj | 7 +- .../WireMock.Org.RestClient.csproj | 5 +- ...nningInLinuxContainerModeFactAttribute.cs} | 4 +- .../WireMockAssertionsTests.cs | 38 +- .../Matchers/JmesPathMatcherTests.cs | 3 +- .../Matchers/JsonMatcherTests.cs | 2 +- .../Matchers/JsonPartialMatcherTests.cs | 2 +- .../JsonPartialWildcardMatcherTests.cs | 2 +- .../Matchers/JsonPathMatcherTests.cs | 2 +- .../Matchers/MimePartMatcherTests.cs | 62 +- .../Owin/Mappers/OwinResponseMapperTests.cs | 6 +- .../RequestMessageMultiPartMatcher.cs | 8 +- .../Util/TypeLoaderTests.cs | 77 +- .../WireMock.Net.Tests.csproj | 6 +- 306 files changed, 9746 insertions(+), 9285 deletions(-) rename src/{WireMock.Net => WireMock.Net.Abstractions}/Models/StringPattern.cs (100%) create mode 100644 src/WireMock.Net.Abstractions/Properties/AssemblyInfo.cs create mode 100644 src/WireMock.Net.MimePart/ILRepack.targets rename src/{WireMock.Net => WireMock.Net.MimePart}/Matchers/MimePartMatcher.cs (79%) create mode 100644 src/WireMock.Net.MimePart/Properties/AssemblyInfo.cs rename src/{WireMock.Net => WireMock.Net.MimePart}/Util/MimeKitUtils.cs (70%) create mode 100644 src/WireMock.Net.MimePart/WireMock.Net.MimePart.csproj rename src/{WireMock.Net => WireMock.Net.Minimal}/.filenesting.json (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Authentication/AzureADAuthenticationMatcher.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Authentication/BasicAuthenticationMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Compatibility/StringExtensions.cs (92%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Compatibility/WebProxy.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Constants/WireMockConstants.cs (79%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Extensions/DictionaryExtensions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Extensions/StringExtensions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Extensions/TimeSettingsExtensions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Handlers/LocalFileSystemHandler.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Http/ByteArrayContentHelper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Http/HttpClientBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Http/HttpClientFactory2.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Http/HttpRequestMessageHelper.cs (99%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Http/HttpResponseMessageHelper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Http/StringContentHelper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Http/WebhookSender.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/HttpsCertificate/CertificateLoader.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/HttpsCertificate/PublicCertificateHelper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/IMapping.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/IMappingBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Json/DynamicJsonClassOptions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Json/FloatBehavior.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Json/IntegerBehavior.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Logging/LogEntry.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Logging/WireMockConsoleLogger.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Logging/WireMockNullLogger.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Mapping.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/MappingBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/MappingRegistrationCallback.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/AbstractJsonPartialMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/ContentTypeMatcher.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/ExactMatcher.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/FormUrlEncodedMatcher.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/GraphQLMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/IJsonMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/JSONPathMatcher.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/JmesPathMatcher.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/JsonMatcher.cs (94%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/JsonPartialMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/JsonPartialWildCardMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/LinqMatcher.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/MatcherExtensions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Models/WireMockCustomScalarGraphType.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/ProtoBufMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/CompositeMatcherType.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMatchResult.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageBodyMatcher.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageClientIPMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageCompositeMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageCookieMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageGraphQLMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageHeaderMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageHttpVersionMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageMethodMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageMultiPartMatcher.cs (82%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageParamMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessagePathMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageProtoBufMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/RequestMessageUrlMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/Request/_mock4net-license.txt (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/SimMetricsMatcher.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Matchers/XPathMatcher.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Models/BlockingQueue.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Models/GraphQLSchemaDetails.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Models/ProtoDefinitionData.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Models/TimeSettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Models/UrlDetails.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Models/Webhook.cs (94%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Models/WebhookRequest.cs (95%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/AspNetCoreSelfHost.NETCore.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/AspNetCoreSelfHost.NETStandard.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/AspNetCoreSelfHost.NETStandard13.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/AspNetCoreSelfHost.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/GlobalExceptionMiddleware.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/HostUrlDetails.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/HostUrlOptions.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/IMappingMatcher.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/IOwinSelfHost.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/IWireMockMiddlewareOptions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/Mappers/IOwinRequestMapper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/Mappers/IOwinResponseMapper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/Mappers/OwinRequestMapper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/Mappers/OwinResponseMapper.cs (95%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/MappingMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/MappingMatcherResult.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/OwinSelfHost.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/WireMockMiddleware.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/WireMockMiddlewareOptions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Owin/WireMockMiddlewareOptionsHelper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/Interaction.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/MatchingRule.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/Metadata.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/Pact.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/PactRequest.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/PactResponse.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/PactRust.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/PactSpecification.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/Pacticipant.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/ProviderState.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Pact/Models/V2/_v2.json (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Properties/AssemblyInfo.cs (75%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Proxy/ProxyHelper.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IBodyRequestBuilder.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IClientIPRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/ICookiesRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IGraphQLRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IHeadersRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IHttpVersionBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IMethodRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IMultiPartRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IParamsRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IProtoBufRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/IUrlAndPathRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.ClientIP.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.UsingMethods.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithBody.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithBodyAsProtoBuf.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithCookies.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithGraphQLSchema.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithHeaders.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithHttpVersion.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithMultiPart.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithParam.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithPath.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.WithUrl.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestBuilders/Request.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/RequestMessage.cs (95%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/BodyDestinationFormat.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/IBodyResponseBuilder.cs (98%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/ICallbackResponseBuilder.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/IDelayResponseBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/IFaultRequestBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/IHeadersResponseBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/IProxyResponseBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/IResponseBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/IStatusCodeResponseBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/ITransformResponseBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/Response.WithBody.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/Response.WithCallback.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/Response.WithFault.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/Response.WithHeaders.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/Response.WithProxy.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/Response.WithTransformer.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseBuilders/Response.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseMessage.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseMessageBuilder.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseProviders/DynamicAsyncResponseProvider.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseProviders/DynamicResponseProvider.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseProviders/IResponseProvider.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ResponseProviders/ProxyAsyncResponseProvider.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/ScenarioState.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/LogEntryMapper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/MappingConverter.cs (98%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/MappingConverterSettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/MappingFileNameSanitizer.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/MappingToFileSaver.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/MatcherMapper.cs (92%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/PactMapper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/ProxyMappingConverter.cs (98%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/SwaggerMapper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/TimeSettingsMapper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Serialization/WebhookMapper.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/IRespondWithAProvider.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/RespondWithAProvider.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.Admin.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.AdminFiles.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.ConvertMapping.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.Fluent.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.ImportWireMockOrg.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.LogEntries.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.OpenApiParser.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.Proxy.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Server/WireMockServer.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Services/IRandomizerDoubleBetween0And1.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Services/RandomizerDoubleBetween0And1.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/HandlebarsSettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/HttpClientSettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/ProxyAndRecordSettings.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/ProxySaveMappingSetting.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/ProxySaveMappingSettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/ProxyUrlReplaceSettings.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/SimpleSettingsParser.cs (98%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/WebProxySettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/WebhookSettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/WireMockCertificateSettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/WireMockServerSettings.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Settings/WireMockServerSettingsParser.cs (98%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Handlebars/FileHelpers.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Handlebars/HandlebarsContext.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Handlebars/HandlebarsContextFactory.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Handlebars/IHandlebarsContext.cs (95%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Handlebars/WireMockHandlebarsHelpers.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/ITransformer.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/ITransformerContext.cs (95%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/ITransformerContextFactory.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Scriban/ScribanContext.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Scriban/ScribanContextFactory.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Scriban/WireMockListAccessor.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Scriban/WireMockTemplateContext.cs (96%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/TransformModel.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Transformers/Transformer.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/ConcurrentObservableCollection.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/CultureInfoExtensions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/DateTimeUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/DictionaryExtensions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/EnhancedFileSystemWatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/FileHelper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/FilePathUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/GuidUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/HttpStatusRangeParser.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/HttpVersionParser.cs (91%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/PortUtils.cs (98%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/ProtoBufUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/ProtoDefinitionHelper.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/ReflectionUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/RegexUtils.cs (88%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/SingletonFactory.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/StreamUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/StringUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/SystemUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/TinyMapperUtils.cs (97%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/TypeLoader.cs (65%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/UrlUtils.cs (100%) rename src/{WireMock.Net => WireMock.Net.Minimal}/Util/WireMockProtoFileResolver.cs (100%) create mode 100644 src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj create mode 100644 src/WireMock.Net.Shared/Constants/RegexConstants.cs rename src/{WireMock.Net => WireMock.Net.Shared}/Exceptions/WireMockException.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Extensions/AnyOfExtensions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Extensions/EnumExtensions.cs (60%) rename src/{WireMock.Net => WireMock.Net.Shared}/Extensions/ExceptionExtensions.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Http/HttpKnownHeaderNames.cs (99%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/ExactObjectMatcher.cs (96%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/Helpers/BodyDataMatchScoreCalculator.cs (96%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/IBytesMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/ICSharpCodeMatcher.cs (95%) rename src/{WireMock.Net/Matchers/IDecodeMatcher.cs => WireMock.Net.Shared/Matchers/IDecodeBytesMatcher.cs} (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/IIgnoreCaseMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/IMatcher.cs (94%) create mode 100644 src/WireMock.Net.Shared/Matchers/IMimePartMatcher.cs rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/IObjectMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/IProtoBufMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/IStringMatcher.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/MatchBehaviour.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/MatchBehaviourHelper.cs (67%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/MatchOperator.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/MatchResult.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/MatchScores.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/NotNullOrEmptyMatcher.cs (92%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/RegexMatcher.cs (96%) rename src/{WireMock.Net => WireMock.Net.Shared}/Matchers/WildcardMatcher.cs (97%) rename src/{WireMock.Net => WireMock.Net.Shared}/Models/BodyData.cs (100%) create mode 100644 src/WireMock.Net.Shared/Properties/AssemblyInfo.cs rename src/{WireMock.Net => WireMock.Net.Shared}/RegularExpressions/RegexExtended.cs (99%) rename src/{WireMock.Net => WireMock.Net.Shared}/Serialization/JsonSerializationConstants.cs (100%) rename src/{WireMock.Net => WireMock.Net.Shared}/Util/BodyParser.cs (95%) create mode 100644 src/WireMock.Net.Shared/Util/BodyParserSettings.cs rename src/{WireMock.Net => WireMock.Net.Shared}/Util/BytesEncodingUtils.cs (95%) rename src/{WireMock.Net => WireMock.Net.Shared}/Util/CSharpFormatter.cs (95%) rename src/{WireMock.Net => WireMock.Net.Shared}/Util/CompressionUtils.cs (66%) create mode 100644 src/WireMock.Net.Shared/Util/IMimeKitUtils.cs rename src/{WireMock.Net => WireMock.Net.Shared}/Util/JsonUtils.cs (100%) create mode 100644 src/WireMock.Net.Shared/Util/MappingConverterUtils.cs rename src/{WireMock.Net => WireMock.Net.Shared}/Util/QueryStringParser.cs (94%) create mode 100644 src/WireMock.Net.Shared/WireMock.Net.Shared.csproj delete mode 100644 src/WireMock.Net/Compatibility/EmptyArray.cs delete mode 100644 src/WireMock.Net/Util/BodyParserSettings.cs delete mode 100644 src/WireMock.Net/Util/MappingConverterUtils.cs rename test/WireMock.Net.Aspire.Tests/Facts/{DockerIsRunningInLinuxContainerModeFact.cs => DockerIsRunningInLinuxContainerModeFactAttribute.cs} (67%) diff --git a/README.md b/README.md index a584d2b8..b3142bf3 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co | | Official | Preview [:information_source:](https://github.com/wiremock/WireMock.Net/wiki/MyGet-preview-versions) | | - | - | - | |   **WireMock.Net** | [![NuGet Badge WireMock.Net](https://img.shields.io/nuget/v/WireMock.Net)](https://www.nuget.org/packages/WireMock.Net) | [![MyGet Badge WireMock.Net](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net) +|   **WireMock.Net.Minimal** 🔺| [![NuGet Badge WireMock.Net.Minimal](https://img.shields.io/nuget/v/WireMock.Net.Minimal)](https://www.nuget.org/packages/WireMock.Net.Minimal) | [![MyGet Badge WireMock.Net](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Minimal?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Minimal) |   **WireMock.Net.StandAlone** | [![NuGet Badge WireMock.Net](https://img.shields.io/nuget/v/WireMock.Net.StandAlone)](https://www.nuget.org/packages/WireMock.Net.StandAlone) | [![MyGet Badge WireMock.Net.StandAlone](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.StandAlone?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.StandAlone) |   **WireMock.Net.Testcontainers** | [![NuGet Badge WireMock.Net.Testcontainers](https://img.shields.io/nuget/v/WireMock.Net.Testcontainers)](https://www.nuget.org/packages/WireMock.Net.Testcontainers) | [![MyGet Badge WireMock.Net.Testcontainers](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Testcontainers?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Testcontainers) |   **WireMock.Net.Aspire** | [![NuGet Badge WireMock.Net.Aspire](https://img.shields.io/nuget/v/WireMock.Net.Aspire)](https://www.nuget.org/packages/WireMock.Net.Aspire) | [![MyGet Badge WireMock.Net.Aspire](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Aspire?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Aspire) @@ -52,10 +53,15 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co | | | | |   **WireMock.Net.Matchers.CSharpCode** | [![NuGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/nuget/v/WireMock.Net.Matchers.CSharpCode)](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [![MyGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Matchers.CSharpCode?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode) |   **WireMock.Net.OpenApiParser** | [![NuGet Badge WireMock.Net.OpenApiParser](https://img.shields.io/nuget/v/WireMock.Net.OpenApiParser)](https://www.nuget.org/packages/WireMock.Net.OpenApiParser) | [![MyGet Badge WireMock.Net.OpenApiParser](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.OpenApiParser?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenApiParser) +|   **WireMock.Net.MimePart** | [![NuGet Badge WireMock.Net.MimePart](https://img.shields.io/nuget/v/WireMock.Net.MimePart)](https://www.nuget.org/packages/WireMock.Net.MimePart) | [![MyGet Badge WireMock.Net.MimePart](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.MimePart?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.MimePart) | | | | |   **WireMock.Net.RestClient** | [![NuGet Badge WireMock.Net.RestClient](https://img.shields.io/nuget/v/WireMock.Net.RestClient)](https://www.nuget.org/packages/WireMock.Net.RestClient) | [![MyGet Badge WireMock.Net.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient) |   **WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://img.shields.io/nuget/v/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Org.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient) +
+ +🔺 **WireMock.Net.Minimal** does not include: **WireMock.Net.MimePart** + --- ## :exclamation: Breaking changes @@ -65,6 +71,7 @@ A breaking change is introduced which is related to System.Linq.Dynamic.Core Dyn - The `LinqMatcher` is not allowed. - The [Handlebars.Net.Helpers.DynamicLinq](https://www.nuget.org/packages/Handlebars.Net.Helpers.DynamicLinq) package is not included anymore. + ### 1.8.0 Some breaking changes are introduced in this version: diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index 7965831f..d02f17b7 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -130,6 +130,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.AwesomeAsserti EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenApiParser", "src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj", "{E5B03EEF-822C-4295-952B-4479AD30082B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.MimePart", "src\WireMock.Net.MimePart\WireMock.Net.MimePart.csproj", "{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Shared", "src\WireMock.Net.Shared\WireMock.Net.Shared.csproj", "{D3804228-91F4-4502-9595-39584E5A0177}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Minimal", "src\WireMock.Net.Minimal\WireMock.Net.Minimal.csproj", "{BFEF8990-65B3-4274-310F-7355F0B84035}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -308,6 +314,18 @@ Global {E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5B03EEF-822C-4295-952B-4479AD30082B}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5B03EEF-822C-4295-952B-4479AD30082B}.Release|Any CPU.Build.0 = Release|Any CPU + {F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|Any CPU.Build.0 = Release|Any CPU + {D3804228-91F4-4502-9595-39584E5A0177}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3804228-91F4-4502-9595-39584E5A0177}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3804228-91F4-4502-9595-39584E5A0177}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3804228-91F4-4502-9595-39584E5A0177}.Release|Any CPU.Build.0 = Release|Any CPU + {BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFEF8990-65B3-4274-310F-7355F0B84035}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFEF8990-65B3-4274-310F-7355F0B84035}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -358,6 +376,9 @@ Global {A5FEF4F7-7DA2-4962-89A8-16BA942886E5} = {0BB8B634-407A-4610-A91F-11586990767A} {7753670F-7C7F-44BF-8BC7-08325588E60C} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {E5B03EEF-822C-4295-952B-4479AD30082B} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {F8B4A93E-46EF-4237-88FE-15FDAB7635D4} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {D3804228-91F4-4502-9595-39584E5A0177} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {BFEF8990-65B3-4274-310F-7355F0B84035} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs index 8265a81f..7aeb2bb8 100644 --- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs +++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs @@ -339,8 +339,8 @@ namespace WireMock.Net.ConsoleApplication }); System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); - //server.SetBasicAuthentication("a", "b"); - server.SetAzureADAuthentication(Environment.GetEnvironmentVariable("WIREMOCK_AAD_TENANT")!, "api://e083d51a-01a6-446c-8ad5-0c5c7f002208"); + server.SetBasicAuthentication("a", "b"); + //server.SetAzureADAuthentication(Environment.GetEnvironmentVariable("WIREMOCK_AAD_TENANT")!, "api://e083d51a-01a6-446c-8ad5-0c5c7f002208"); //var http = new HttpClient(); //var response = await http.GetAsync($"{_wireMockServer.Url}/pricing"); diff --git a/src/WireMock.Net/Models/StringPattern.cs b/src/WireMock.Net.Abstractions/Models/StringPattern.cs similarity index 100% rename from src/WireMock.Net/Models/StringPattern.cs rename to src/WireMock.Net.Abstractions/Models/StringPattern.cs diff --git a/src/WireMock.Net.Abstractions/Properties/AssemblyInfo.cs b/src/WireMock.Net.Abstractions/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..0f6668a5 --- /dev/null +++ b/src/WireMock.Net.Abstractions/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// Copyright © WireMock.Net + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] + +// Needed for Moq in the UnitTest project +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj index 4b010184..49e7fae7 100644 --- a/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj +++ b/src/WireMock.Net.Abstractions/WireMock.Net.Abstractions.csproj @@ -1,7 +1,7 @@ - + - Commonly used models, enumerations and types. + Commonly used interfaces, models, enumerations and types. WireMock.Net.Abstractions Stef Heyenrath net45;net451;net461;netstandard1.3;netstandard2.0;netstandard2.1 @@ -17,7 +17,6 @@ true true true - ../WireMock.Net/WireMock.Net.ruleset true ../WireMock.Net/WireMock.Net.snk @@ -25,6 +24,10 @@ MIT + + ../WireMock.Net/WireMock.Net.ruleset + + true @@ -35,37 +38,46 @@ + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive - - - + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net.AspNetCore.Middleware/WireMock.Net.AspNetCore.Middleware.csproj b/src/WireMock.Net.AspNetCore.Middleware/WireMock.Net.AspNetCore.Middleware.csproj index 08d5571e..4e54e6bd 100644 --- a/src/WireMock.Net.AspNetCore.Middleware/WireMock.Net.AspNetCore.Middleware.csproj +++ b/src/WireMock.Net.AspNetCore.Middleware/WireMock.Net.AspNetCore.Middleware.csproj @@ -1,4 +1,4 @@ - + enable @@ -16,7 +16,6 @@ true true true - ../WireMock.Net/WireMock.Net.ruleset true ../WireMock.Net/WireMock.Net.snk true @@ -25,6 +24,10 @@ ../../resources/WireMock.Net-LogoAspire.ico + + ../WireMock.Net/WireMock.Net.ruleset + + true diff --git a/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj b/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj index 7fd91ef2..a835e090 100644 --- a/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj +++ b/src/WireMock.Net.Aspire/WireMock.Net.Aspire.csproj @@ -1,4 +1,4 @@ - + enable @@ -16,7 +16,6 @@ true true true - ../WireMock.Net/WireMock.Net.ruleset true ../WireMock.Net/WireMock.Net.snk true @@ -31,9 +30,13 @@ - + + + ../WireMock.Net/WireMock.Net.ruleset + + true diff --git a/src/WireMock.Net.AwesomeAssertions/WireMock.Net.AwesomeAssertions.csproj b/src/WireMock.Net.AwesomeAssertions/WireMock.Net.AwesomeAssertions.csproj index 5bebc1a1..c4b9f5e2 100644 --- a/src/WireMock.Net.AwesomeAssertions/WireMock.Net.AwesomeAssertions.csproj +++ b/src/WireMock.Net.AwesomeAssertions/WireMock.Net.AwesomeAssertions.csproj @@ -16,7 +16,6 @@ true true true - ../WireMock.Net/WireMock.Net.ruleset true ../WireMock.Net/WireMock.Net.snk @@ -24,6 +23,10 @@ MIT + + ../WireMock.Net/WireMock.Net.ruleset + + true diff --git a/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj b/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj index c8d40372..d72d4d61 100644 --- a/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj +++ b/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj @@ -1,4 +1,4 @@ - + FluentAssertions extensions for WireMock.Net @@ -16,7 +16,6 @@ true true true - ../WireMock.Net/WireMock.Net.ruleset true ../WireMock.Net/WireMock.Net.snk @@ -24,6 +23,10 @@ MIT + + ../WireMock.Net/WireMock.Net.ruleset + + true diff --git a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs index cdb4a3f4..f3a9a68c 100644 --- a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs +++ b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs @@ -25,13 +25,13 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher private const string TemplateForIsMatchWithDynamic = "public class CodeHelper {{ public bool IsMatch(dynamic it) {{ {0} }} }}"; private readonly string[] _usings = - { + [ "System", "System.Linq", "System.Collections.Generic", "Microsoft.CSharp", "Newtonsoft.Json.Linq" - }; + ]; /// public MatchBehaviour MatchBehaviour { get; } diff --git a/src/WireMock.Net.Matchers.CSharpCode/WireMock.Net.Matchers.CSharpCode.csproj b/src/WireMock.Net.Matchers.CSharpCode/WireMock.Net.Matchers.CSharpCode.csproj index 2f412e6b..319ba1db 100644 --- a/src/WireMock.Net.Matchers.CSharpCode/WireMock.Net.Matchers.CSharpCode.csproj +++ b/src/WireMock.Net.Matchers.CSharpCode/WireMock.Net.Matchers.CSharpCode.csproj @@ -1,49 +1,52 @@ - + - - A CSharpCodeMatcher which can be used to match WireMock.Net Requests using C# code. - WireMock.Net.Matchers.CSharpCode - Stef Heyenrath - net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 - true - wiremock;matchers;matcher;csharp;csharpcode - WireMock - {B6269AAC-170A-4346-8B9A-444DED3D9A44} - true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - true - true - true - ../WireMock.Net/WireMock.Net.ruleset - true - ../WireMock.Net/WireMock.Net.snk - - true - MIT - + + A CSharpCodeMatcher which can be used to match WireMock.Net Requests using C# code. + WireMock.Net.Matchers.CSharpCode + Stef Heyenrath + net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + true + wiremock;matchers;matcher;csharp;csharpcode + WireMock + {B6269AAC-170A-4346-8B9A-444DED3D9A44} + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + true + true + ../WireMock.Net/WireMock.Net.snk + + true + MIT + - - true - + + ../WireMock.Net/WireMock.Net.ruleset + - - - + + true + - - - + + + - - - + + + - - - + + + - - - + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net.MimePart/ILRepack.targets b/src/WireMock.Net.MimePart/ILRepack.targets new file mode 100644 index 00000000..1e2e0929 --- /dev/null +++ b/src/WireMock.Net.MimePart/ILRepack.targets @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/MimePartMatcher.cs b/src/WireMock.Net.MimePart/Matchers/MimePartMatcher.cs similarity index 79% rename from src/WireMock.Net/Matchers/MimePartMatcher.cs rename to src/WireMock.Net.MimePart/Matchers/MimePartMatcher.cs index 6b41413b..6ad739ff 100644 --- a/src/WireMock.Net/Matchers/MimePartMatcher.cs +++ b/src/WireMock.Net.MimePart/Matchers/MimePartMatcher.cs @@ -1,12 +1,8 @@ // Copyright © WireMock.Net -#if MIMEKIT using System; using MimeKit; -using WireMock.Extensions; -using WireMock.Matchers; using WireMock.Matchers.Helpers; -using WireMock.Models; using WireMock.Util; namespace WireMock.Matchers; @@ -14,31 +10,23 @@ namespace WireMock.Matchers; /// /// MimePartMatcher /// -public class MimePartMatcher : IMatcher +public class MimePartMatcher : IMimePartMatcher { private readonly Func[] _funcs; /// public string Name => nameof(MimePartMatcher); - /// - /// ContentType Matcher (image/png; name=image.png.) - /// + /// public IStringMatcher? ContentTypeMatcher { get; } - /// - /// ContentDisposition Matcher (attachment; filename=image.png) - /// + /// public IStringMatcher? ContentDispositionMatcher { get; } - /// - /// ContentTransferEncoding Matcher (base64) - /// + /// public IStringMatcher? ContentTransferEncodingMatcher { get; } - /// - /// Content Matcher - /// + /// public IMatcher? ContentMatcher { get; } /// @@ -70,19 +58,15 @@ public class MimePartMatcher : IMatcher ]; } - /// - /// Determines whether the specified MimePart is match. - /// - /// The MimePart. - /// A value between 0.0 - 1.0 of the similarity. - public MatchResult IsMatch(MimePart mimePart) + /// + public MatchResult IsMatch(object value) { var score = MatchScores.Mismatch; Exception? exception = null; try { - if (Array.TrueForAll(_funcs, func => func(mimePart).IsPerfect())) + if (value is MimePart mimePart && Array.TrueForAll(_funcs, func => func(mimePart).IsPerfect())) { score = MatchScores.Perfect; } @@ -125,5 +109,4 @@ public class MimePartMatcher : IMatcher { return contentType?.ToString().Replace("Content-Type: ", string.Empty); } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/WireMock.Net.MimePart/Properties/AssemblyInfo.cs b/src/WireMock.Net.MimePart/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..fb354ea5 --- /dev/null +++ b/src/WireMock.Net.MimePart/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +// Copyright © WireMock.Net + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] \ No newline at end of file diff --git a/src/WireMock.Net/Util/MimeKitUtils.cs b/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs similarity index 70% rename from src/WireMock.Net/Util/MimeKitUtils.cs rename to src/WireMock.Net.MimePart/Util/MimeKitUtils.cs index 91e22414..bd05f1e0 100644 --- a/src/WireMock.Net/Util/MimeKitUtils.cs +++ b/src/WireMock.Net.MimePart/Util/MimeKitUtils.cs @@ -1,7 +1,7 @@ // Copyright © WireMock.Net -#if MIMEKIT using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -13,9 +13,16 @@ using WireMock.Types; namespace WireMock.Util; -internal static class MimeKitUtils +internal class MimeKitUtils : IMimeKitUtils { - public static bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true)] out MimeMessage? mimeMessage) + /// + public object LoadFromStream(Stream stream) + { + return MimeMessage.Load(Guard.NotNull(stream)); + } + + /// + public bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true)] out object? mimeMessage) { Guard.NotNull(requestMessage); @@ -37,7 +44,7 @@ internal static class MimeKitUtils var fixedBytes = FixBytes(bytes, contentTypeHeader[0]); - mimeMessage = MimeMessage.Load(new MemoryStream(fixedBytes)); + mimeMessage = LoadFromStream(new MemoryStream(fixedBytes)); return true; } @@ -45,6 +52,19 @@ internal static class MimeKitUtils return false; } + /// + public IReadOnlyList GetBodyParts(object mimeMessage) + { + if (mimeMessage is not MimeMessage mm) + { + throw new ArgumentException($"The mimeMessage must be of type {nameof(MimeMessage)}", nameof(mimeMessage)); + } + + return mm.BodyParts + .OfType() + .ToArray(); + } + private static bool StartsWithMultiPart(WireMockList contentTypeHeader) { return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase)); @@ -61,5 +81,4 @@ internal static class MimeKitUtils return result; } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/WireMock.Net.MimePart/WireMock.Net.MimePart.csproj b/src/WireMock.Net.MimePart/WireMock.Net.MimePart.csproj new file mode 100644 index 00000000..edbfad31 --- /dev/null +++ b/src/WireMock.Net.MimePart/WireMock.Net.MimePart.csproj @@ -0,0 +1,63 @@ + + + + MultiPart Mime support for WireMock.Net using MimeKitLite + WireMock.Net.MimePart + Stef Heyenrath + netstandard2.0;netstandard2.1;net462;net47;net48;net6.0;net8.0 + true + wiremock;matchers;matcher;mime;multipart;mimekit + WireMock + {F8B4A93E-46EF-4237-88FE-15FDAB7635D4} + true + true + true + true + MIT + + true + + ../WireMock.Net/WireMock.Net.snk + true + + + + true + ../WireMock.Net/WireMock.Net.snk + true + + + + ../WireMock.Net/WireMock.Net.ruleset + + + + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net/.filenesting.json b/src/WireMock.Net.Minimal/.filenesting.json similarity index 100% rename from src/WireMock.Net/.filenesting.json rename to src/WireMock.Net.Minimal/.filenesting.json diff --git a/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs b/src/WireMock.Net.Minimal/Authentication/AzureADAuthenticationMatcher.cs similarity index 96% rename from src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs rename to src/WireMock.Net.Minimal/Authentication/AzureADAuthenticationMatcher.cs index 4622052d..93d0fedd 100644 --- a/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs +++ b/src/WireMock.Net.Minimal/Authentication/AzureADAuthenticationMatcher.cs @@ -45,7 +45,7 @@ internal class AzureADAuthenticationMatcher : IStringMatcher public AnyOf[] GetPatterns() { - return EmptyArray>.Value; + return []; } public MatchOperator MatchOperator => MatchOperator.Or; @@ -57,7 +57,7 @@ internal class AzureADAuthenticationMatcher : IStringMatcher return MatchScores.Mismatch; } - var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase, WireMockConstants.DefaultRegexTimeout); + var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase, RegexConstants.DefaultTimeout); try { diff --git a/src/WireMock.Net/Authentication/BasicAuthenticationMatcher.cs b/src/WireMock.Net.Minimal/Authentication/BasicAuthenticationMatcher.cs similarity index 100% rename from src/WireMock.Net/Authentication/BasicAuthenticationMatcher.cs rename to src/WireMock.Net.Minimal/Authentication/BasicAuthenticationMatcher.cs diff --git a/src/WireMock.Net/Compatibility/StringExtensions.cs b/src/WireMock.Net.Minimal/Compatibility/StringExtensions.cs similarity index 92% rename from src/WireMock.Net/Compatibility/StringExtensions.cs rename to src/WireMock.Net.Minimal/Compatibility/StringExtensions.cs index d95c2bc2..a4eb0e92 100644 --- a/src/WireMock.Net/Compatibility/StringExtensions.cs +++ b/src/WireMock.Net.Minimal/Compatibility/StringExtensions.cs @@ -12,7 +12,7 @@ internal static class StringExtensions public static string Replace(this string text, string oldValue, string newValue, StringComparison stringComparison) { var options = stringComparison == StringComparison.OrdinalIgnoreCase ? RegexOptions.IgnoreCase : RegexOptions.None; - return Regex.Replace(text, oldValue, newValue, options, WireMockConstants.DefaultRegexTimeout); + return Regex.Replace(text, oldValue, newValue, options, RegexConstants.DefaultTimeout); } } #endif \ No newline at end of file diff --git a/src/WireMock.Net/Compatibility/WebProxy.cs b/src/WireMock.Net.Minimal/Compatibility/WebProxy.cs similarity index 100% rename from src/WireMock.Net/Compatibility/WebProxy.cs rename to src/WireMock.Net.Minimal/Compatibility/WebProxy.cs diff --git a/src/WireMock.Net/Constants/WireMockConstants.cs b/src/WireMock.Net.Minimal/Constants/WireMockConstants.cs similarity index 79% rename from src/WireMock.Net/Constants/WireMockConstants.cs rename to src/WireMock.Net.Minimal/Constants/WireMockConstants.cs index 126a57aa..305c2102 100644 --- a/src/WireMock.Net/Constants/WireMockConstants.cs +++ b/src/WireMock.Net.Minimal/Constants/WireMockConstants.cs @@ -1,13 +1,9 @@ // Copyright © WireMock.Net -using System; - namespace WireMock.Constants; internal static class WireMockConstants { - internal static readonly TimeSpan DefaultRegexTimeout = TimeSpan.FromSeconds(10); - internal const int AdminPriority = int.MinValue; internal const int MinPriority = -1_000_000; internal const int ProxyPriority = -2_000_000; diff --git a/src/WireMock.Net/Extensions/DictionaryExtensions.cs b/src/WireMock.Net.Minimal/Extensions/DictionaryExtensions.cs similarity index 100% rename from src/WireMock.Net/Extensions/DictionaryExtensions.cs rename to src/WireMock.Net.Minimal/Extensions/DictionaryExtensions.cs diff --git a/src/WireMock.Net/Extensions/StringExtensions.cs b/src/WireMock.Net.Minimal/Extensions/StringExtensions.cs similarity index 100% rename from src/WireMock.Net/Extensions/StringExtensions.cs rename to src/WireMock.Net.Minimal/Extensions/StringExtensions.cs diff --git a/src/WireMock.Net/Extensions/TimeSettingsExtensions.cs b/src/WireMock.Net.Minimal/Extensions/TimeSettingsExtensions.cs similarity index 100% rename from src/WireMock.Net/Extensions/TimeSettingsExtensions.cs rename to src/WireMock.Net.Minimal/Extensions/TimeSettingsExtensions.cs diff --git a/src/WireMock.Net/Handlers/LocalFileSystemHandler.cs b/src/WireMock.Net.Minimal/Handlers/LocalFileSystemHandler.cs similarity index 100% rename from src/WireMock.Net/Handlers/LocalFileSystemHandler.cs rename to src/WireMock.Net.Minimal/Handlers/LocalFileSystemHandler.cs diff --git a/src/WireMock.Net/Http/ByteArrayContentHelper.cs b/src/WireMock.Net.Minimal/Http/ByteArrayContentHelper.cs similarity index 100% rename from src/WireMock.Net/Http/ByteArrayContentHelper.cs rename to src/WireMock.Net.Minimal/Http/ByteArrayContentHelper.cs diff --git a/src/WireMock.Net/Http/HttpClientBuilder.cs b/src/WireMock.Net.Minimal/Http/HttpClientBuilder.cs similarity index 100% rename from src/WireMock.Net/Http/HttpClientBuilder.cs rename to src/WireMock.Net.Minimal/Http/HttpClientBuilder.cs diff --git a/src/WireMock.Net/Http/HttpClientFactory2.cs b/src/WireMock.Net.Minimal/Http/HttpClientFactory2.cs similarity index 100% rename from src/WireMock.Net/Http/HttpClientFactory2.cs rename to src/WireMock.Net.Minimal/Http/HttpClientFactory2.cs diff --git a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs b/src/WireMock.Net.Minimal/Http/HttpRequestMessageHelper.cs similarity index 99% rename from src/WireMock.Net/Http/HttpRequestMessageHelper.cs rename to src/WireMock.Net.Minimal/Http/HttpRequestMessageHelper.cs index 7c8f051a..8b1fa5a0 100644 --- a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs +++ b/src/WireMock.Net.Minimal/Http/HttpRequestMessageHelper.cs @@ -61,7 +61,7 @@ internal static class HttpRequestMessageHelper if (contentLengthHeaderAllowed) { // Set Content to empty ByteArray to be able to set the Content-Length on the content in case of a HEAD method. - httpRequestMessage.Content ??= new ByteArrayContent(EmptyArray.Value); + httpRequestMessage.Content ??= new ByteArrayContent([]); } else { diff --git a/src/WireMock.Net/Http/HttpResponseMessageHelper.cs b/src/WireMock.Net.Minimal/Http/HttpResponseMessageHelper.cs similarity index 100% rename from src/WireMock.Net/Http/HttpResponseMessageHelper.cs rename to src/WireMock.Net.Minimal/Http/HttpResponseMessageHelper.cs diff --git a/src/WireMock.Net/Http/StringContentHelper.cs b/src/WireMock.Net.Minimal/Http/StringContentHelper.cs similarity index 100% rename from src/WireMock.Net/Http/StringContentHelper.cs rename to src/WireMock.Net.Minimal/Http/StringContentHelper.cs diff --git a/src/WireMock.Net/Http/WebhookSender.cs b/src/WireMock.Net.Minimal/Http/WebhookSender.cs similarity index 97% rename from src/WireMock.Net/Http/WebhookSender.cs rename to src/WireMock.Net.Minimal/Http/WebhookSender.cs index 606dadd0..431d773f 100644 --- a/src/WireMock.Net/Http/WebhookSender.cs +++ b/src/WireMock.Net.Minimal/Http/WebhookSender.cs @@ -1,122 +1,122 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Stef.Validation; -using WireMock.Models; -using WireMock.Settings; -using WireMock.Transformers; -using WireMock.Transformers.Handlebars; -using WireMock.Transformers.Scriban; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Http; - -internal class WebhookSender -{ - private const string ClientIp = "::1"; - private static readonly ThreadLocal Random = new(() => new Random(DateTime.UtcNow.Millisecond)); - - private readonly WireMockServerSettings _settings; - - public WebhookSender(WireMockServerSettings settings) - { - _settings = Guard.NotNull(settings); - } - - public async Task SendAsync( - HttpClient client, - IMapping mapping, - IWebhookRequest webhookRequest, - IRequestMessage originalRequestMessage, - IResponseMessage originalResponseMessage - ) - { - Guard.NotNull(client); - Guard.NotNull(mapping); - Guard.NotNull(webhookRequest); - Guard.NotNull(originalRequestMessage); - Guard.NotNull(originalResponseMessage); - - IBodyData? bodyData; - IDictionary>? headers; - string requestUrl; - if (webhookRequest.UseTransformer == true) - { - ITransformer transformer; - switch (webhookRequest.TransformerType) - { - case TransformerType.Handlebars: - var factoryHandlebars = new HandlebarsContextFactory(_settings); - transformer = new Transformer(_settings, factoryHandlebars); - break; - - case TransformerType.Scriban: - case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType); - transformer = new Transformer(_settings, factoryDotLiquid); - break; - - default: - throw new NotImplementedException($"TransformerType '{webhookRequest.TransformerType}' is not supported."); - } - - bodyData = transformer.TransformBody(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.BodyData, webhookRequest.TransformerReplaceNodeOptions); - headers = transformer.TransformHeaders(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Headers); - requestUrl = transformer.TransformString(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Url); - - mapping.Settings.WebhookSettings?.PostTransform(mapping, requestUrl, bodyData, headers); - } - else - { - bodyData = webhookRequest.BodyData; - headers = webhookRequest.Headers; - requestUrl = webhookRequest.Url; - } - - // Create RequestMessage - var requestMessage = new RequestMessage( - new UrlDetails(requestUrl), - webhookRequest.Method, - ClientIp, - bodyData, - headers?.ToDictionary(x => x.Key, x => x.Value.ToArray()) - ) - { - DateTime = DateTime.UtcNow - }; - - // Create HttpRequestMessage - var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, requestUrl); - - // Delay (if required) - if (TryGetDelay(webhookRequest, out var delay)) - { - await Task.Delay(delay.Value).ConfigureAwait(false); - } - - // Call the URL - return await client.SendAsync(httpRequestMessage).ConfigureAwait(false); - } - - private static bool TryGetDelay(IWebhookRequest webhookRequest, [NotNullWhen(true)] out int? delay) - { - delay = webhookRequest.Delay; - var minimumDelay = webhookRequest.MinimumRandomDelay; - var maximumDelay = webhookRequest.MaximumRandomDelay; - - if (minimumDelay is not null && maximumDelay is not null && maximumDelay >= minimumDelay) - { - delay = Random.Value!.Next(minimumDelay.Value, maximumDelay.Value); - return true; - } - - return delay is not null; - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Stef.Validation; +using WireMock.Models; +using WireMock.Settings; +using WireMock.Transformers; +using WireMock.Transformers.Handlebars; +using WireMock.Transformers.Scriban; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Http; + +internal class WebhookSender +{ + private const string ClientIp = "::1"; + private static readonly ThreadLocal Random = new(() => new Random(DateTime.UtcNow.Millisecond)); + + private readonly WireMockServerSettings _settings; + + public WebhookSender(WireMockServerSettings settings) + { + _settings = Guard.NotNull(settings); + } + + public async Task SendAsync( + HttpClient client, + IMapping mapping, + IWebhookRequest webhookRequest, + IRequestMessage originalRequestMessage, + IResponseMessage originalResponseMessage + ) + { + Guard.NotNull(client); + Guard.NotNull(mapping); + Guard.NotNull(webhookRequest); + Guard.NotNull(originalRequestMessage); + Guard.NotNull(originalResponseMessage); + + IBodyData? bodyData; + IDictionary>? headers; + string requestUrl; + if (webhookRequest.UseTransformer == true) + { + ITransformer transformer; + switch (webhookRequest.TransformerType) + { + case TransformerType.Handlebars: + var factoryHandlebars = new HandlebarsContextFactory(_settings); + transformer = new Transformer(_settings, factoryHandlebars); + break; + + case TransformerType.Scriban: + case TransformerType.ScribanDotLiquid: + var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType); + transformer = new Transformer(_settings, factoryDotLiquid); + break; + + default: + throw new NotImplementedException($"TransformerType '{webhookRequest.TransformerType}' is not supported."); + } + + bodyData = transformer.TransformBody(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.BodyData, webhookRequest.TransformerReplaceNodeOptions); + headers = transformer.TransformHeaders(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Headers); + requestUrl = transformer.TransformString(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Url); + + mapping.Settings.WebhookSettings?.PostTransform(mapping, requestUrl, bodyData, headers); + } + else + { + bodyData = webhookRequest.BodyData; + headers = webhookRequest.Headers; + requestUrl = webhookRequest.Url; + } + + // Create RequestMessage + var requestMessage = new RequestMessage( + new UrlDetails(requestUrl), + webhookRequest.Method, + ClientIp, + bodyData, + headers?.ToDictionary(x => x.Key, x => x.Value.ToArray()) + ) + { + DateTime = DateTime.UtcNow + }; + + // Create HttpRequestMessage + var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, requestUrl); + + // Delay (if required) + if (TryGetDelay(webhookRequest, out var delay)) + { + await Task.Delay(delay.Value).ConfigureAwait(false); + } + + // Call the URL + return await client.SendAsync(httpRequestMessage).ConfigureAwait(false); + } + + private static bool TryGetDelay(IWebhookRequest webhookRequest, [NotNullWhen(true)] out int? delay) + { + delay = webhookRequest.Delay; + var minimumDelay = webhookRequest.MinimumRandomDelay; + var maximumDelay = webhookRequest.MaximumRandomDelay; + + if (minimumDelay is not null && maximumDelay is not null && maximumDelay >= minimumDelay) + { + delay = Random.Value!.Next(minimumDelay.Value, maximumDelay.Value); + return true; + } + + return delay is not null; + } } \ No newline at end of file diff --git a/src/WireMock.Net/HttpsCertificate/CertificateLoader.cs b/src/WireMock.Net.Minimal/HttpsCertificate/CertificateLoader.cs similarity index 100% rename from src/WireMock.Net/HttpsCertificate/CertificateLoader.cs rename to src/WireMock.Net.Minimal/HttpsCertificate/CertificateLoader.cs diff --git a/src/WireMock.Net/HttpsCertificate/PublicCertificateHelper.cs b/src/WireMock.Net.Minimal/HttpsCertificate/PublicCertificateHelper.cs similarity index 100% rename from src/WireMock.Net/HttpsCertificate/PublicCertificateHelper.cs rename to src/WireMock.Net.Minimal/HttpsCertificate/PublicCertificateHelper.cs diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net.Minimal/IMapping.cs similarity index 100% rename from src/WireMock.Net/IMapping.cs rename to src/WireMock.Net.Minimal/IMapping.cs diff --git a/src/WireMock.Net/IMappingBuilder.cs b/src/WireMock.Net.Minimal/IMappingBuilder.cs similarity index 100% rename from src/WireMock.Net/IMappingBuilder.cs rename to src/WireMock.Net.Minimal/IMappingBuilder.cs diff --git a/src/WireMock.Net/Json/DynamicJsonClassOptions.cs b/src/WireMock.Net.Minimal/Json/DynamicJsonClassOptions.cs similarity index 100% rename from src/WireMock.Net/Json/DynamicJsonClassOptions.cs rename to src/WireMock.Net.Minimal/Json/DynamicJsonClassOptions.cs diff --git a/src/WireMock.Net/Json/FloatBehavior.cs b/src/WireMock.Net.Minimal/Json/FloatBehavior.cs similarity index 100% rename from src/WireMock.Net/Json/FloatBehavior.cs rename to src/WireMock.Net.Minimal/Json/FloatBehavior.cs diff --git a/src/WireMock.Net/Json/IntegerBehavior.cs b/src/WireMock.Net.Minimal/Json/IntegerBehavior.cs similarity index 100% rename from src/WireMock.Net/Json/IntegerBehavior.cs rename to src/WireMock.Net.Minimal/Json/IntegerBehavior.cs diff --git a/src/WireMock.Net/Logging/LogEntry.cs b/src/WireMock.Net.Minimal/Logging/LogEntry.cs similarity index 96% rename from src/WireMock.Net/Logging/LogEntry.cs rename to src/WireMock.Net.Minimal/Logging/LogEntry.cs index b00b83c2..33ad6ee1 100644 --- a/src/WireMock.Net/Logging/LogEntry.cs +++ b/src/WireMock.Net.Minimal/Logging/LogEntry.cs @@ -1,39 +1,39 @@ -// Copyright © WireMock.Net - -using System; -using WireMock.Matchers.Request; - -namespace WireMock.Logging; - -/// -/// LogEntry -/// -public class LogEntry : ILogEntry -{ - /// - public Guid Guid { get; set; } - - /// - public IRequestMessage RequestMessage { get; set; } = null!; - - /// - public IResponseMessage ResponseMessage { get; set; } = null!; - - /// - public IRequestMatchResult RequestMatchResult { get; set; } = null!; - - /// - public Guid? MappingGuid { get; set; } - - /// - public string? MappingTitle { get; set; } - - /// - public Guid? PartialMappingGuid { get; set; } - - /// - public string? PartialMappingTitle { get; set; } - - /// - public IRequestMatchResult PartialMatchResult { get; set; } = null!; +// Copyright © WireMock.Net + +using System; +using WireMock.Matchers.Request; + +namespace WireMock.Logging; + +/// +/// LogEntry +/// +public class LogEntry : ILogEntry +{ + /// + public Guid Guid { get; set; } + + /// + public IRequestMessage RequestMessage { get; set; } = null!; + + /// + public IResponseMessage ResponseMessage { get; set; } = null!; + + /// + public IRequestMatchResult RequestMatchResult { get; set; } = null!; + + /// + public Guid? MappingGuid { get; set; } + + /// + public string? MappingTitle { get; set; } + + /// + public Guid? PartialMappingGuid { get; set; } + + /// + public string? PartialMappingTitle { get; set; } + + /// + public IRequestMatchResult PartialMatchResult { get; set; } = null!; } \ No newline at end of file diff --git a/src/WireMock.Net/Logging/WireMockConsoleLogger.cs b/src/WireMock.Net.Minimal/Logging/WireMockConsoleLogger.cs similarity index 100% rename from src/WireMock.Net/Logging/WireMockConsoleLogger.cs rename to src/WireMock.Net.Minimal/Logging/WireMockConsoleLogger.cs diff --git a/src/WireMock.Net/Logging/WireMockNullLogger.cs b/src/WireMock.Net.Minimal/Logging/WireMockNullLogger.cs similarity index 100% rename from src/WireMock.Net/Logging/WireMockNullLogger.cs rename to src/WireMock.Net.Minimal/Logging/WireMockNullLogger.cs diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net.Minimal/Mapping.cs similarity index 97% rename from src/WireMock.Net/Mapping.cs rename to src/WireMock.Net.Minimal/Mapping.cs index fc83ce6a..fef92cbc 100644 --- a/src/WireMock.Net/Mapping.cs +++ b/src/WireMock.Net.Minimal/Mapping.cs @@ -1,197 +1,197 @@ -// Copyright © WireMock.Net - -using System; -using System.Threading.Tasks; -using Stef.Validation; -using WireMock.Matchers.Request; -using WireMock.Models; -using WireMock.ResponseProviders; -using WireMock.Settings; - -namespace WireMock; - -/// -/// The Mapping. -/// -public class Mapping : IMapping -{ - /// - public Guid Guid { get; } - - /// - public DateTime? UpdatedAt { get; set; } - - /// - public string? Title { get; } - - /// - public string? Description { get; } - - /// - public string? Path { get; set; } - - /// - public int Priority { get; } - - /// - public string? Scenario { get; private set; } - - /// - public string? ExecutionConditionState { get; } - - /// - public string? NextState { get; } - - /// - public int? StateTimes { get; } - - /// - public IRequestMatcher RequestMatcher { get; } - - /// - public IResponseProvider Provider { get; } - - /// - public WireMockServerSettings Settings { get; } - - /// - public bool IsStartState => Scenario == null || Scenario != null && NextState != null && ExecutionConditionState == null; - - /// - public bool IsAdminInterface => Provider is DynamicResponseProvider or DynamicAsyncResponseProvider or ProxyAsyncResponseProvider; - - /// - public bool IsProxy => Provider is ProxyAsyncResponseProvider; - - /// - public bool LogMapping => Provider is not (DynamicResponseProvider or DynamicAsyncResponseProvider); - - /// - public IWebhook[]? Webhooks { get; } - - /// - public bool? UseWebhooksFireAndForget { get; } - - /// - public ITimeSettings? TimeSettings { get; } - - /// - public object? Data { get; } - - /// - public double? Probability { get; private set; } - - /// - public IdOrTexts? ProtoDefinition { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The unique identifier. - /// The datetime when this mapping was created. - /// The unique title (can be null). - /// The description (can be null). - /// The full file path from this mapping title (can be null). - /// The WireMockServerSettings. - /// The request matcher. - /// The provider. - /// The priority for this mapping. - /// The scenario. [Optional] - /// State in which the current mapping can occur. [Optional] - /// The next state which will occur after the current mapping execution. [Optional] - /// Only when the current state is executed this number, the next state which will occur. [Optional] - /// The Webhooks. [Optional] - /// Use Fire and Forget for the defined webhook(s). [Optional] - /// The TimeSettings. [Optional] - /// The data object. [Optional] - public Mapping - ( - Guid guid, - DateTime updatedAt, - string? title, - string? description, - string? path, - WireMockServerSettings settings, - IRequestMatcher requestMatcher, - IResponseProvider provider, - int priority, - string? scenario, - string? executionConditionState, - string? nextState, - int? stateTimes, - IWebhook[]? webhooks, - bool? useWebhooksFireAndForget, - ITimeSettings? timeSettings, - object? data - ) - { - Guid = guid; - UpdatedAt = updatedAt; - Title = title; - Description = description; - Path = path; - Settings = settings; - RequestMatcher = requestMatcher; - Provider = provider; - Priority = priority; - Scenario = scenario; - ExecutionConditionState = executionConditionState; - NextState = nextState; - StateTimes = stateTimes; - Webhooks = webhooks; - UseWebhooksFireAndForget = useWebhooksFireAndForget; - TimeSettings = timeSettings; - Data = data; - } - - /// - public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage) - { - return Provider.ProvideResponseAsync(this, requestMessage, Settings); - } - - /// - public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState) - { - var result = new RequestMatchResult(); - - RequestMatcher.GetMatchingScore(requestMessage, result); - - // Only check state if Scenario is defined - if (Scenario != null) - { - var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState); - matcher.GetMatchingScore(requestMessage, result); - //// If ExecutionConditionState is null, this means that request is the start from a scenario. So just return. - //if (ExecutionConditionState != null) - //{ - // // ExecutionConditionState is not null, so get score for matching with the nextState. - // var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState); - // matcher.GetMatchingScore(requestMessage, result); - //} - } - - return result; - } - - /// - public IMapping WithProbability(double probability) - { - Probability = Guard.NotNull(probability); - return this; - } - - /// - public IMapping WithScenario(string scenario) - { - Scenario = Guard.NotNullOrWhiteSpace(scenario); - return this; - } - - /// - public IMapping WithProtoDefinition(IdOrTexts protoDefinition) - { - ProtoDefinition = protoDefinition; - return this; - } +// Copyright © WireMock.Net + +using System; +using System.Threading.Tasks; +using Stef.Validation; +using WireMock.Matchers.Request; +using WireMock.Models; +using WireMock.ResponseProviders; +using WireMock.Settings; + +namespace WireMock; + +/// +/// The Mapping. +/// +public class Mapping : IMapping +{ + /// + public Guid Guid { get; } + + /// + public DateTime? UpdatedAt { get; set; } + + /// + public string? Title { get; } + + /// + public string? Description { get; } + + /// + public string? Path { get; set; } + + /// + public int Priority { get; } + + /// + public string? Scenario { get; private set; } + + /// + public string? ExecutionConditionState { get; } + + /// + public string? NextState { get; } + + /// + public int? StateTimes { get; } + + /// + public IRequestMatcher RequestMatcher { get; } + + /// + public IResponseProvider Provider { get; } + + /// + public WireMockServerSettings Settings { get; } + + /// + public bool IsStartState => Scenario == null || Scenario != null && NextState != null && ExecutionConditionState == null; + + /// + public bool IsAdminInterface => Provider is DynamicResponseProvider or DynamicAsyncResponseProvider or ProxyAsyncResponseProvider; + + /// + public bool IsProxy => Provider is ProxyAsyncResponseProvider; + + /// + public bool LogMapping => Provider is not (DynamicResponseProvider or DynamicAsyncResponseProvider); + + /// + public IWebhook[]? Webhooks { get; } + + /// + public bool? UseWebhooksFireAndForget { get; } + + /// + public ITimeSettings? TimeSettings { get; } + + /// + public object? Data { get; } + + /// + public double? Probability { get; private set; } + + /// + public IdOrTexts? ProtoDefinition { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier. + /// The datetime when this mapping was created. + /// The unique title (can be null). + /// The description (can be null). + /// The full file path from this mapping title (can be null). + /// The WireMockServerSettings. + /// The request matcher. + /// The provider. + /// The priority for this mapping. + /// The scenario. [Optional] + /// State in which the current mapping can occur. [Optional] + /// The next state which will occur after the current mapping execution. [Optional] + /// Only when the current state is executed this number, the next state which will occur. [Optional] + /// The Webhooks. [Optional] + /// Use Fire and Forget for the defined webhook(s). [Optional] + /// The TimeSettings. [Optional] + /// The data object. [Optional] + public Mapping + ( + Guid guid, + DateTime updatedAt, + string? title, + string? description, + string? path, + WireMockServerSettings settings, + IRequestMatcher requestMatcher, + IResponseProvider provider, + int priority, + string? scenario, + string? executionConditionState, + string? nextState, + int? stateTimes, + IWebhook[]? webhooks, + bool? useWebhooksFireAndForget, + ITimeSettings? timeSettings, + object? data + ) + { + Guid = guid; + UpdatedAt = updatedAt; + Title = title; + Description = description; + Path = path; + Settings = settings; + RequestMatcher = requestMatcher; + Provider = provider; + Priority = priority; + Scenario = scenario; + ExecutionConditionState = executionConditionState; + NextState = nextState; + StateTimes = stateTimes; + Webhooks = webhooks; + UseWebhooksFireAndForget = useWebhooksFireAndForget; + TimeSettings = timeSettings; + Data = data; + } + + /// + public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage) + { + return Provider.ProvideResponseAsync(this, requestMessage, Settings); + } + + /// + public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState) + { + var result = new RequestMatchResult(); + + RequestMatcher.GetMatchingScore(requestMessage, result); + + // Only check state if Scenario is defined + if (Scenario != null) + { + var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState); + matcher.GetMatchingScore(requestMessage, result); + //// If ExecutionConditionState is null, this means that request is the start from a scenario. So just return. + //if (ExecutionConditionState != null) + //{ + // // ExecutionConditionState is not null, so get score for matching with the nextState. + // var matcher = new RequestMessageScenarioAndStateMatcher(nextState, ExecutionConditionState); + // matcher.GetMatchingScore(requestMessage, result); + //} + } + + return result; + } + + /// + public IMapping WithProbability(double probability) + { + Probability = Guard.NotNull(probability); + return this; + } + + /// + public IMapping WithScenario(string scenario) + { + Scenario = Guard.NotNullOrWhiteSpace(scenario); + return this; + } + + /// + public IMapping WithProtoDefinition(IdOrTexts protoDefinition) + { + ProtoDefinition = protoDefinition; + return this; + } } \ No newline at end of file diff --git a/src/WireMock.Net/MappingBuilder.cs b/src/WireMock.Net.Minimal/MappingBuilder.cs similarity index 100% rename from src/WireMock.Net/MappingBuilder.cs rename to src/WireMock.Net.Minimal/MappingBuilder.cs diff --git a/src/WireMock.Net/MappingRegistrationCallback.cs b/src/WireMock.Net.Minimal/MappingRegistrationCallback.cs similarity index 100% rename from src/WireMock.Net/MappingRegistrationCallback.cs rename to src/WireMock.Net.Minimal/MappingRegistrationCallback.cs diff --git a/src/WireMock.Net/Matchers/AbstractJsonPartialMatcher.cs b/src/WireMock.Net.Minimal/Matchers/AbstractJsonPartialMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/AbstractJsonPartialMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/AbstractJsonPartialMatcher.cs diff --git a/src/WireMock.Net/Matchers/ContentTypeMatcher.cs b/src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs similarity index 97% rename from src/WireMock.Net/Matchers/ContentTypeMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs index 2a0459c7..88a56d0f 100644 --- a/src/WireMock.Net/Matchers/ContentTypeMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/ContentTypeMatcher.cs @@ -1,90 +1,90 @@ -// Copyright © WireMock.Net - -using System.Net.Http.Headers; -using AnyOfTypes; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// ContentTypeMatcher which accepts also all charsets -/// -/// -public class ContentTypeMatcher : WildcardMatcher -{ - private readonly AnyOf[] _patterns; - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - /// IgnoreCase (default false) - public ContentTypeMatcher(AnyOf pattern, bool ignoreCase = false) : this([pattern], ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - /// IgnoreCase (default false) - public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, - [pattern], ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - /// IgnoreCase (default false) - public ContentTypeMatcher(AnyOf[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - /// IgnoreCase (default false) - public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf[] patterns, bool ignoreCase = false) : base(matchBehaviour, patterns, ignoreCase) - { - _patterns = Guard.NotNull(patterns); - } - - /// - public override MatchResult IsMatch(string? input) - { - if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType)) - { - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); - } - - return base.IsMatch(contentType.MediaType); - } - - /// - public override AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public override string Name => nameof(ContentTypeMatcher); - - /// - public override string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}" + - $")"; - } +// Copyright © WireMock.Net + +using System.Net.Http.Headers; +using AnyOfTypes; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// ContentTypeMatcher which accepts also all charsets +/// +/// +public class ContentTypeMatcher : WildcardMatcher +{ + private readonly AnyOf[] _patterns; + + /// + /// Initializes a new instance of the class. + /// + /// The pattern. + /// IgnoreCase (default false) + public ContentTypeMatcher(AnyOf pattern, bool ignoreCase = false) : this([pattern], ignoreCase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + /// IgnoreCase (default false) + public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, + [pattern], ignoreCase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// IgnoreCase (default false) + public ContentTypeMatcher(AnyOf[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The patterns. + /// IgnoreCase (default false) + public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf[] patterns, bool ignoreCase = false) : base(matchBehaviour, patterns, ignoreCase) + { + _patterns = Guard.NotNull(patterns); + } + + /// + public override MatchResult IsMatch(string? input) + { + if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType)) + { + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); + } + + return base.IsMatch(contentType.MediaType); + } + + /// + public override AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public override string Name => nameof(ContentTypeMatcher); + + /// + public override string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}" + + $")"; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/ExactMatcher.cs b/src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs similarity index 97% rename from src/WireMock.Net/Matchers/ExactMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs index c210cf59..67ce552b 100644 --- a/src/WireMock.Net/Matchers/ExactMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/ExactMatcher.cs @@ -1,107 +1,107 @@ -// Copyright © WireMock.Net - -using System; -using System.Linq; -using AnyOfTypes; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// ExactMatcher -/// -/// and -public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher -{ - private readonly AnyOf[] _values; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The string value. - public ExactMatcher(MatchBehaviour matchBehaviour, string value) : this(matchBehaviour, true, MatchOperator.Or, new AnyOf(value)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The values. - public ExactMatcher(params AnyOf[] values) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, values) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Ignore the case from the pattern(s). - /// The values. - public ExactMatcher(bool ignoreCase, params AnyOf[] values) : this(MatchBehaviour.AcceptOnMatch, ignoreCase, MatchOperator.Or, values) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// Ignore the case from the pattern(s). - /// The to use. (default = "Or") - /// The values. - public ExactMatcher( - MatchBehaviour matchBehaviour, - bool ignoreCase = false, - MatchOperator matchOperator = MatchOperator.Or, - params AnyOf[] values) - { - _values = Guard.NotNull(values); - - MatchBehaviour = matchBehaviour; - IgnoreCase = ignoreCase; - MatchOperator = matchOperator; - } - - /// - public MatchResult IsMatch(string? input) - { - Func equals = IgnoreCase - ? pattern => string.Equals(pattern, input, StringComparison.OrdinalIgnoreCase) - : pattern => pattern == input; - - var score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator); - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score)); - } - - /// - public AnyOf[] GetPatterns() - { - return _values; - } - - /// - public MatchOperator MatchOperator { get; } - - /// - public string Name => nameof(ExactMatcher); - - /// - public bool IgnoreCase { get; } - - /// - public string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_values)}" + - $")"; - } +// Copyright © WireMock.Net + +using System; +using System.Linq; +using AnyOfTypes; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// ExactMatcher +/// +/// and +public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher +{ + private readonly AnyOf[] _values; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The string value. + public ExactMatcher(MatchBehaviour matchBehaviour, string value) : this(matchBehaviour, true, MatchOperator.Or, new AnyOf(value)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The values. + public ExactMatcher(params AnyOf[] values) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, values) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Ignore the case from the pattern(s). + /// The values. + public ExactMatcher(bool ignoreCase, params AnyOf[] values) : this(MatchBehaviour.AcceptOnMatch, ignoreCase, MatchOperator.Or, values) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// Ignore the case from the pattern(s). + /// The to use. (default = "Or") + /// The values. + public ExactMatcher( + MatchBehaviour matchBehaviour, + bool ignoreCase = false, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] values) + { + _values = Guard.NotNull(values); + + MatchBehaviour = matchBehaviour; + IgnoreCase = ignoreCase; + MatchOperator = matchOperator; + } + + /// + public MatchResult IsMatch(string? input) + { + Func equals = IgnoreCase + ? pattern => string.Equals(pattern, input, StringComparison.OrdinalIgnoreCase) + : pattern => pattern == input; + + var score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator); + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score)); + } + + /// + public AnyOf[] GetPatterns() + { + return _values; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => nameof(ExactMatcher); + + /// + public bool IgnoreCase { get; } + + /// + public string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_values)}" + + $")"; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/FormUrlEncodedMatcher.cs b/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs similarity index 97% rename from src/WireMock.Net/Matchers/FormUrlEncodedMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs index c5e4ac32..5dd81f5b 100644 --- a/src/WireMock.Net/Matchers/FormUrlEncodedMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/FormUrlEncodedMatcher.cs @@ -1,179 +1,179 @@ -// Copyright © WireMock.Net - -using System.Collections.Generic; -using System.Linq; -using AnyOfTypes; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// FormUrl Encoded fields Matcher -/// -/// -/// -public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher -{ - private readonly AnyOf[] _patterns; - - /// - public MatchBehaviour MatchBehaviour { get; } - - private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = []; - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - /// Ignore the case from the pattern. - /// The to use. (default = "Or") - public FormUrlEncodedMatcher( - AnyOf pattern, - bool ignoreCase = false, - MatchOperator matchOperator = MatchOperator.Or) : - this(MatchBehaviour.AcceptOnMatch, [pattern], ignoreCase, matchOperator) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - /// Ignore the case from the pattern. - /// The to use. (default = "Or") - public FormUrlEncodedMatcher( - MatchBehaviour matchBehaviour, - AnyOf pattern, - bool ignoreCase = false, - MatchOperator matchOperator = MatchOperator.Or) : - this(matchBehaviour, [pattern], ignoreCase, matchOperator) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - /// Ignore the case from the pattern. - /// The to use. (default = "Or") - public FormUrlEncodedMatcher( - AnyOf[] patterns, - bool ignoreCase = false, - MatchOperator matchOperator = MatchOperator.Or) : - this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase, matchOperator) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - /// Ignore the case from the pattern. - /// The to use. (default = "Or") - public FormUrlEncodedMatcher( - MatchBehaviour matchBehaviour, - AnyOf[] patterns, - bool ignoreCase = false, - MatchOperator matchOperator = MatchOperator.Or) - { - _patterns = Guard.NotNull(patterns); - IgnoreCase = ignoreCase; - MatchBehaviour = matchBehaviour; - MatchOperator = matchOperator; - - foreach (var pattern in _patterns) - { - if (QueryStringParser.TryParse(pattern, IgnoreCase, out var nameValueCollection)) - { - 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)); - } - } - } - } - - /// - public MatchResult IsMatch(string? input) - { - // Input is null or empty and if no patterns defined, return Perfect match. - if (string.IsNullOrEmpty(input) && _patterns.Length == 0) - { - return new MatchResult(MatchScores.Perfect); - } - - if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection)) - { - return new MatchResult(MatchScores.Mismatch); - } - - var matches = GetMatches(inputNameValueCollection); - - var score = MatchScores.ToScore(matches, MatchOperator); - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score)); - } - - private bool[] GetMatches(IDictionary inputNameValueCollection) - { - var matches = new List(); - if (_pairs.Count > inputNameValueCollection.Count) - { - matches.AddRange(Enumerable.Repeat(false, _pairs.Count - inputNameValueCollection.Count)); - } - - foreach (var inputKeyValuePair in inputNameValueCollection) - { - var match = false; - foreach (var pair in _pairs) - { - var keyMatchResult = pair.Key.IsMatch(inputKeyValuePair.Key).IsPerfect(); - if (keyMatchResult) - { - match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false; - if (match) - { - break; - } - } - } - - matches.Add(match); - } - - return matches.ToArray(); - } - - /// - public virtual AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public virtual string Name => nameof(FormUrlEncodedMatcher); - - /// - public bool IgnoreCase { get; } - - /// - public MatchOperator MatchOperator { get; } - - /// - public string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}" + - $")"; - } +// Copyright © WireMock.Net + +using System.Collections.Generic; +using System.Linq; +using AnyOfTypes; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// FormUrl Encoded fields Matcher +/// +/// +/// +public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher +{ + private readonly AnyOf[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + + private readonly List<(WildcardMatcher Key, WildcardMatcher? Value)> _pairs = []; + + /// + /// Initializes a new instance of the class. + /// + /// The pattern. + /// Ignore the case from the pattern. + /// The to use. (default = "Or") + public FormUrlEncodedMatcher( + AnyOf pattern, + bool ignoreCase = false, + MatchOperator matchOperator = MatchOperator.Or) : + this(MatchBehaviour.AcceptOnMatch, [pattern], ignoreCase, matchOperator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + /// Ignore the case from the pattern. + /// The to use. (default = "Or") + public FormUrlEncodedMatcher( + MatchBehaviour matchBehaviour, + AnyOf pattern, + bool ignoreCase = false, + MatchOperator matchOperator = MatchOperator.Or) : + this(matchBehaviour, [pattern], ignoreCase, matchOperator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// Ignore the case from the pattern. + /// The to use. (default = "Or") + public FormUrlEncodedMatcher( + AnyOf[] patterns, + bool ignoreCase = false, + MatchOperator matchOperator = MatchOperator.Or) : + this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase, matchOperator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The patterns. + /// Ignore the case from the pattern. + /// The to use. (default = "Or") + public FormUrlEncodedMatcher( + MatchBehaviour matchBehaviour, + AnyOf[] patterns, + bool ignoreCase = false, + MatchOperator matchOperator = MatchOperator.Or) + { + _patterns = Guard.NotNull(patterns); + IgnoreCase = ignoreCase; + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + + foreach (var pattern in _patterns) + { + if (QueryStringParser.TryParse(pattern, IgnoreCase, out var nameValueCollection)) + { + 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)); + } + } + } + } + + /// + public MatchResult IsMatch(string? input) + { + // Input is null or empty and if no patterns defined, return Perfect match. + if (string.IsNullOrEmpty(input) && _patterns.Length == 0) + { + return new MatchResult(MatchScores.Perfect); + } + + if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection)) + { + return new MatchResult(MatchScores.Mismatch); + } + + var matches = GetMatches(inputNameValueCollection); + + var score = MatchScores.ToScore(matches, MatchOperator); + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score)); + } + + private bool[] GetMatches(IDictionary inputNameValueCollection) + { + var matches = new List(); + if (_pairs.Count > inputNameValueCollection.Count) + { + matches.AddRange(Enumerable.Repeat(false, _pairs.Count - inputNameValueCollection.Count)); + } + + foreach (var inputKeyValuePair in inputNameValueCollection) + { + var match = false; + foreach (var pair in _pairs) + { + var keyMatchResult = pair.Key.IsMatch(inputKeyValuePair.Key).IsPerfect(); + if (keyMatchResult) + { + match = pair.Value?.IsMatch(inputKeyValuePair.Value).IsPerfect() ?? false; + if (match) + { + break; + } + } + } + + matches.Add(match); + } + + return matches.ToArray(); + } + + /// + public virtual AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public virtual string Name => nameof(FormUrlEncodedMatcher); + + /// + public bool IgnoreCase { get; } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}" + + $")"; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/GraphQLMatcher.cs b/src/WireMock.Net.Minimal/Matchers/GraphQLMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/GraphQLMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/GraphQLMatcher.cs diff --git a/src/WireMock.Net/Matchers/IJsonMatcher.cs b/src/WireMock.Net.Minimal/Matchers/IJsonMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/IJsonMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/IJsonMatcher.cs diff --git a/src/WireMock.Net/Matchers/JSONPathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs similarity index 96% rename from src/WireMock.Net/Matchers/JSONPathMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs index b4274dbe..517a4d7d 100644 --- a/src/WireMock.Net/Matchers/JSONPathMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/JSONPathMatcher.cs @@ -1,167 +1,167 @@ -// Copyright © WireMock.Net - -using System; -using System.Linq; -using AnyOfTypes; -using Newtonsoft.Json.Linq; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// JsonPathMatcher -/// -/// -/// -public class JsonPathMatcher : IStringMatcher, IObjectMatcher -{ - private readonly AnyOf[] _patterns; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public object Value { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public JsonPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, - patterns.ToAnyOfPatterns()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public JsonPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, - MatchOperator.Or, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The to use. (default = "Or") - /// The patterns. - public JsonPathMatcher( - MatchBehaviour matchBehaviour, - MatchOperator matchOperator = MatchOperator.Or, - params AnyOf[] patterns) - { - _patterns = Guard.NotNull(patterns); - MatchBehaviour = matchBehaviour; - MatchOperator = matchOperator; - Value = patterns; - } - - /// - public MatchResult IsMatch(string? input) - { - var score = MatchScores.Mismatch; - Exception? exception = null; - - if (!string.IsNullOrWhiteSpace(input)) - { - try - { - var jToken = JToken.Parse(input); - score = IsMatch(jToken); - } - catch (Exception ex) - { - exception = ex; - } - } - - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); - } - - /// - public MatchResult IsMatch(object? input) - { - var score = MatchScores.Mismatch; - Exception? exception = null; - - // When input is null or byte[], return Mismatch. - if (input != null && input is not byte[]) - { - try - { - // Check if JToken or object - JToken jToken = input as JToken ?? JObject.FromObject(input); - score = IsMatch(jToken); - } - catch (Exception ex) - { - exception = ex; - } - } - - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); - } - - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public MatchOperator MatchOperator { get; } - - /// - public string Name => nameof(JsonPathMatcher); - - /// - public string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + - $")"; - } - - private double IsMatch(JToken jToken) - { - var array = ConvertJTokenToJArrayIfNeeded(jToken); - - // The SelectToken method can accept a string path to a child token ( i.e. "Manufacturers[0].Products[0].Price"). - // In that case it will return a JValue (some type) which does not implement the IEnumerable interface. - var values = _patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray(); - - return MatchScores.ToScore(values, MatchOperator); - } - - // https://github.com/wiremock/WireMock.Net/issues/965 - // https://stackoverflow.com/questions/66922188/newtonsoft-jsonpath-with-c-sharp-syntax - // Filtering using SelectToken() isn't guaranteed to work for objects inside objects -- only objects inside arrays. - // So this code checks if it's an JArray, if it's not an array, construct a new JArray. - private static JToken ConvertJTokenToJArrayIfNeeded(JToken jToken) - { - if (jToken.Count() == 1) - { - var property = jToken.First(); - var item = property.First(); - if (item is JArray) - { - return jToken; - } - - return new JObject - { - [property.Path] = new JArray(item) - }; - } - - return jToken; - } +// Copyright © WireMock.Net + +using System; +using System.Linq; +using AnyOfTypes; +using Newtonsoft.Json.Linq; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// JsonPathMatcher +/// +/// +/// +public class JsonPathMatcher : IStringMatcher, IObjectMatcher +{ + private readonly AnyOf[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public object Value { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public JsonPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, + patterns.ToAnyOfPatterns()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public JsonPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, + MatchOperator.Or, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. (default = "Or") + /// The patterns. + public JsonPathMatcher( + MatchBehaviour matchBehaviour, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + Value = patterns; + } + + /// + public MatchResult IsMatch(string? input) + { + var score = MatchScores.Mismatch; + Exception? exception = null; + + if (!string.IsNullOrWhiteSpace(input)) + { + try + { + var jToken = JToken.Parse(input); + score = IsMatch(jToken); + } + catch (Exception ex) + { + exception = ex; + } + } + + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + } + + /// + public MatchResult IsMatch(object? input) + { + var score = MatchScores.Mismatch; + Exception? exception = null; + + // When input is null or byte[], return Mismatch. + if (input != null && input is not byte[]) + { + try + { + // Check if JToken or object + JToken jToken = input as JToken ?? JObject.FromObject(input); + score = IsMatch(jToken); + } + catch (Exception ex) + { + exception = ex; + } + } + + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => nameof(JsonPathMatcher); + + /// + public string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + + $")"; + } + + private double IsMatch(JToken jToken) + { + var array = ConvertJTokenToJArrayIfNeeded(jToken); + + // The SelectToken method can accept a string path to a child token ( i.e. "Manufacturers[0].Products[0].Price"). + // In that case it will return a JValue (some type) which does not implement the IEnumerable interface. + var values = _patterns.Select(pattern => array.SelectToken(pattern.GetPattern()) != null).ToArray(); + + return MatchScores.ToScore(values, MatchOperator); + } + + // https://github.com/wiremock/WireMock.Net/issues/965 + // https://stackoverflow.com/questions/66922188/newtonsoft-jsonpath-with-c-sharp-syntax + // Filtering using SelectToken() isn't guaranteed to work for objects inside objects -- only objects inside arrays. + // So this code checks if it's an JArray, if it's not an array, construct a new JArray. + private static JToken ConvertJTokenToJArrayIfNeeded(JToken jToken) + { + if (jToken.Count() == 1) + { + var property = jToken.First(); + var item = property.First(); + if (item is JArray) + { + return jToken; + } + + return new JObject + { + [property.Path] = new JArray(item) + }; + } + + return jToken; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JmesPathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs similarity index 96% rename from src/WireMock.Net/Matchers/JmesPathMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs index 884ba0ff..3e468448 100644 --- a/src/WireMock.Net/Matchers/JmesPathMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/JmesPathMatcher.cs @@ -1,130 +1,130 @@ -// Copyright © WireMock.Net - -using System; -using System.Linq; -using AnyOfTypes; -using DevLab.JmesPath; -using Newtonsoft.Json; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// http://jmespath.org/ -/// -public class JmesPathMatcher : IStringMatcher, IObjectMatcher -{ - private readonly AnyOf[] _patterns; - - /// - public object Value { get; } - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns.ToAnyOfPatterns()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public JmesPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The to use. - /// The patterns. - public JmesPathMatcher(MatchOperator matchOperator = MatchOperator.Or, params AnyOf[] patterns) : - this(MatchBehaviour.AcceptOnMatch, matchOperator, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The to use. - /// The patterns. - public JmesPathMatcher( - MatchBehaviour matchBehaviour, - MatchOperator matchOperator = MatchOperator.Or, - params AnyOf[] patterns) - { - _patterns = Guard.NotNull(patterns); - MatchBehaviour = matchBehaviour; - MatchOperator = matchOperator; - Value = patterns; - } - - /// - public MatchResult IsMatch(string? input) - { - var score = MatchScores.Mismatch; - Exception? exception = null; - - if (!string.IsNullOrWhiteSpace(input)) - { - try - { - var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray(); - score = MatchScores.ToScore(results, MatchOperator); - } - catch (Exception ex) - { - exception = ex; - } - } - - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); - } - - /// - public MatchResult IsMatch(object? input) - { - var score = MatchScores.Mismatch; - - // When input is null or byte[], return Mismatch. - if (input != null && !(input is byte[])) - { - var inputAsString = JsonConvert.SerializeObject(input); - return IsMatch(inputAsString); - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, score); - } - - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public MatchOperator MatchOperator { get; } - - /// - public string Name => nameof(JmesPathMatcher); - - /// - public string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + - $")"; - } +// Copyright © WireMock.Net + +using System; +using System.Linq; +using AnyOfTypes; +using DevLab.JmesPath; +using Newtonsoft.Json; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// http://jmespath.org/ +/// +public class JmesPathMatcher : IStringMatcher, IObjectMatcher +{ + private readonly AnyOf[] _patterns; + + /// + public object Value { get; } + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns.ToAnyOfPatterns()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public JmesPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use. + /// The patterns. + public JmesPathMatcher(MatchOperator matchOperator = MatchOperator.Or, params AnyOf[] patterns) : + this(MatchBehaviour.AcceptOnMatch, matchOperator, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. + /// The patterns. + public JmesPathMatcher( + MatchBehaviour matchBehaviour, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + Value = patterns; + } + + /// + public MatchResult IsMatch(string? input) + { + var score = MatchScores.Mismatch; + Exception? exception = null; + + if (!string.IsNullOrWhiteSpace(input)) + { + try + { + var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray(); + score = MatchScores.ToScore(results, MatchOperator); + } + catch (Exception ex) + { + exception = ex; + } + } + + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + } + + /// + public MatchResult IsMatch(object? input) + { + var score = MatchScores.Mismatch; + + // When input is null or byte[], return Mismatch. + if (input != null && !(input is byte[])) + { + var inputAsString = JsonConvert.SerializeObject(input); + return IsMatch(inputAsString); + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, score); + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => nameof(JmesPathMatcher); + + /// + public string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + + $")"; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JsonMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs similarity index 94% rename from src/WireMock.Net/Matchers/JsonMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs index 81b356b2..52ee6960 100644 --- a/src/WireMock.Net/Matchers/JsonMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/JsonMatcher.cs @@ -1,250 +1,250 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json.Linq; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Util; -using JsonUtils = WireMock.Util.JsonUtils; - -namespace WireMock.Matchers; - -/// -/// JsonMatcher -/// -public class JsonMatcher : IJsonMatcher -{ - /// - public virtual string Name => nameof(JsonMatcher); - - /// - public object Value { get; } - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public bool IgnoreCase { get; } - - /// - /// Support Regex - /// - public bool Regex { get; } - - private readonly JToken _valueAsJToken; - - /// - /// Initializes a new instance of the class. - /// - /// The string value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Support Regex. - public JsonMatcher(string value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The object value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Support Regex. - public JsonMatcher(object value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The value to check for equality. - /// Ignore the case from the PropertyName and PropertyValue (string only). - /// Support Regex. - public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) - { - Guard.NotNull(value); - - MatchBehaviour = matchBehaviour; - IgnoreCase = ignoreCase; - Regex = regex; - - Value = value; - _valueAsJToken = JsonUtils.ConvertValueToJToken(value); - } - - /// - public MatchResult IsMatch(object? input) - { - var score = MatchScores.Mismatch; - Exception? error = null; - - // When input is null or byte[], return Mismatch. - if (input != null && input is not byte[]) - { - try - { - var inputAsJToken = JsonUtils.ConvertValueToJToken(input); - - var match = IsMatch(RenameJToken(_valueAsJToken), RenameJToken(inputAsJToken)); - score = MatchScores.ToScore(match); - } - catch (Exception ex) - { - error = ex; - } - } - - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error); - } - - /// - public virtual string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" + - $")"; - } - - /// - /// Compares the input against the matcher value - /// - /// Matcher value - /// Input value - /// - protected virtual bool IsMatch(JToken value, JToken? input) - { - // If equal, return true. - if (input == value) - { - return true; - } - - // If input is null, return false. - if (input == null) - { - return false; - } - - // If using Regex and the value is a string, use the MatchRegex method. - if (Regex && value.Type == JTokenType.String) - { - var valueAsString = value.ToString(); - - var (valid, result) = RegexUtils.MatchRegex(valueAsString, input.ToString()); - if (valid) - { - return result; - } - } - - // If the value is a Guid and the input is a string, or vice versa, convert them to strings and compare the string values. - if ((value.Type == JTokenType.Guid && input.Type == JTokenType.String) || (value.Type == JTokenType.String && input.Type == JTokenType.Guid)) - { - return JToken.DeepEquals(value.ToString().ToUpperInvariant(), input.ToString().ToUpperInvariant()); - } - - switch (value.Type) - { - // If the value is an object, compare all properties. - case JTokenType.Object: - var valueProperties = value.ToObject>() ?? new Dictionary(); - var inputProperties = input.ToObject>() ?? new Dictionary(); - - // If the number of properties is different, return false. - if (valueProperties.Count != inputProperties.Count) - { - return false; - } - - // Compare all properties. The input must match all properties of the value. - foreach (var pair in valueProperties) - { - if (!IsMatch(pair.Value, inputProperties[pair.Key])) - { - return false; - } - } - - return true; - - // If the value is an array, compare all elements. - case JTokenType.Array: - var valueArray = value.ToObject() ?? EmptyArray.Value; - var inputArray = input.ToObject() ?? EmptyArray.Value; - - // If the number of elements is different, return false. - if (valueArray.Length != inputArray.Length) - { - return false; - } - - return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any(); - - default: - // Use JToken.DeepEquals() for all other types. - return JToken.DeepEquals(value, input); - } - } - - // https://stackoverflow.com/questions/11679804/json-net-rename-properties - private JToken RenameJToken(JToken input) - { - if (!IgnoreCase) - { - return input; - } - - return input switch - { - JProperty property => RenameJProperty(property), - JArray array => RenameJArray(array), - JObject obj => RenameJObject(obj), - _ => input - }; - } - - private JProperty RenameJProperty(JProperty property) - { - if (!IgnoreCase) - { - return property; - } - - var propertyValue = property.Value; - if (propertyValue.Type == JTokenType.String && !Regex) - { - var stringValue = propertyValue.Value()!; - propertyValue = ToUpper(stringValue); - } - - return new JProperty(ToUpper(property.Name)!, RenameJToken(propertyValue)); - } - - private JArray RenameJArray(JArray array) - { - if (Regex) - { - return array; - } - - var renamedValues = array.Select(RenameJToken); - return new JArray(renamedValues); - } - - private JObject RenameJObject(JObject obj) - { - var renamedProperties = obj.Properties().Select(RenameJProperty); - return new JObject(renamedProperties); - } - - private static string? ToUpper(string? input) - { - return input?.ToUpperInvariant(); - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Util; +using JsonUtils = WireMock.Util.JsonUtils; + +namespace WireMock.Matchers; + +/// +/// JsonMatcher +/// +public class JsonMatcher : IJsonMatcher +{ + /// + public virtual string Name => nameof(JsonMatcher); + + /// + public object Value { get; } + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool IgnoreCase { get; } + + /// + /// Support Regex + /// + public bool Regex { get; } + + private readonly JToken _valueAsJToken; + + /// + /// Initializes a new instance of the class. + /// + /// The string value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Support Regex. + public JsonMatcher(string value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Support Regex. + public JsonMatcher(object value, bool ignoreCase = false, bool regex = false) : this(MatchBehaviour.AcceptOnMatch, value, ignoreCase, regex) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The value to check for equality. + /// Ignore the case from the PropertyName and PropertyValue (string only). + /// Support Regex. + public JsonMatcher(MatchBehaviour matchBehaviour, object value, bool ignoreCase = false, bool regex = false) + { + Guard.NotNull(value); + + MatchBehaviour = matchBehaviour; + IgnoreCase = ignoreCase; + Regex = regex; + + Value = value; + _valueAsJToken = JsonUtils.ConvertValueToJToken(value); + } + + /// + public MatchResult IsMatch(object? input) + { + var score = MatchScores.Mismatch; + Exception? error = null; + + // When input is null or byte[], return Mismatch. + if (input != null && input is not byte[]) + { + try + { + var inputAsJToken = JsonUtils.ConvertValueToJToken(input); + + var match = IsMatch(RenameJToken(_valueAsJToken), RenameJToken(inputAsJToken)); + score = MatchScores.ToScore(match); + } + catch (Exception ex) + { + error = ex; + } + } + + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error); + } + + /// + public virtual string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{CSharpFormatter.ConvertToAnonymousObjectDefinition(Value, 3)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(Regex)}" + + $")"; + } + + /// + /// Compares the input against the matcher value + /// + /// Matcher value + /// Input value + /// + protected virtual bool IsMatch(JToken value, JToken? input) + { + // If equal, return true. + if (input == value) + { + return true; + } + + // If input is null, return false. + if (input == null) + { + return false; + } + + // If using Regex and the value is a string, use the MatchRegex method. + if (Regex && value.Type == JTokenType.String) + { + var valueAsString = value.ToString(); + + var (valid, result) = RegexUtils.MatchRegex(valueAsString, input.ToString()); + if (valid) + { + return result; + } + } + + // If the value is a Guid and the input is a string, or vice versa, convert them to strings and compare the string values. + if ((value.Type == JTokenType.Guid && input.Type == JTokenType.String) || (value.Type == JTokenType.String && input.Type == JTokenType.Guid)) + { + return JToken.DeepEquals(value.ToString().ToUpperInvariant(), input.ToString().ToUpperInvariant()); + } + + switch (value.Type) + { + // If the value is an object, compare all properties. + case JTokenType.Object: + var valueProperties = value.ToObject>() ?? new Dictionary(); + var inputProperties = input.ToObject>() ?? new Dictionary(); + + // If the number of properties is different, return false. + if (valueProperties.Count != inputProperties.Count) + { + return false; + } + + // Compare all properties. The input must match all properties of the value. + foreach (var pair in valueProperties) + { + if (!IsMatch(pair.Value, inputProperties[pair.Key])) + { + return false; + } + } + + return true; + + // If the value is an array, compare all elements. + case JTokenType.Array: + var valueArray = value.ToObject() ?? []; + var inputArray = input.ToObject() ?? []; + + // If the number of elements is different, return false. + if (valueArray.Length != inputArray.Length) + { + return false; + } + + return !valueArray.Where((valueToken, index) => !IsMatch(valueToken, inputArray[index])).Any(); + + default: + // Use JToken.DeepEquals() for all other types. + return JToken.DeepEquals(value, input); + } + } + + // https://stackoverflow.com/questions/11679804/json-net-rename-properties + private JToken RenameJToken(JToken input) + { + if (!IgnoreCase) + { + return input; + } + + return input switch + { + JProperty property => RenameJProperty(property), + JArray array => RenameJArray(array), + JObject obj => RenameJObject(obj), + _ => input + }; + } + + private JProperty RenameJProperty(JProperty property) + { + if (!IgnoreCase) + { + return property; + } + + var propertyValue = property.Value; + if (propertyValue.Type == JTokenType.String && !Regex) + { + var stringValue = propertyValue.Value()!; + propertyValue = ToUpper(stringValue); + } + + return new JProperty(ToUpper(property.Name)!, RenameJToken(propertyValue)); + } + + private JArray RenameJArray(JArray array) + { + if (Regex) + { + return array; + } + + var renamedValues = array.Select(RenameJToken); + return new JArray(renamedValues); + } + + private JObject RenameJObject(JObject obj) + { + var renamedProperties = obj.Properties().Select(RenameJProperty); + return new JObject(renamedProperties); + } + + private static string? ToUpper(string? input) + { + return input?.ToUpperInvariant(); + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JsonPartialMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JsonPartialMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/JsonPartialMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/JsonPartialMatcher.cs diff --git a/src/WireMock.Net/Matchers/JsonPartialWildCardMatcher.cs b/src/WireMock.Net.Minimal/Matchers/JsonPartialWildCardMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/JsonPartialWildCardMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/JsonPartialWildCardMatcher.cs diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net.Minimal/Matchers/LinqMatcher.cs similarity index 96% rename from src/WireMock.Net/Matchers/LinqMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/LinqMatcher.cs index cb82f3b3..63621cf8 100644 --- a/src/WireMock.Net/Matchers/LinqMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/LinqMatcher.cs @@ -1,151 +1,151 @@ -// Copyright © WireMock.Net - -using System; -using System.Linq; -using System.Linq.Dynamic.Core; -using AnyOfTypes; -using Newtonsoft.Json.Linq; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Json; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// System.Linq.Dynamic.Core Expression Matcher -/// -/// -/// -public class LinqMatcher : IObjectMatcher, IStringMatcher -{ - private readonly AnyOf[] _patterns; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public object Value { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - public LinqMatcher(AnyOf pattern) : this(new[] { pattern }) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public LinqMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - public LinqMatcher(MatchBehaviour matchBehaviour, AnyOf pattern) : this(matchBehaviour, MatchOperator.Or, pattern) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The to use. (default = "Or") - /// The patterns. - public LinqMatcher( - MatchBehaviour matchBehaviour, - MatchOperator matchOperator = MatchOperator.Or, - params AnyOf[] patterns) - { - _patterns = Guard.NotNull(patterns); - MatchBehaviour = matchBehaviour; - MatchOperator = matchOperator; - Value = patterns; - } - - /// - public MatchResult IsMatch(string? input) - { - var score = MatchScores.Mismatch; - Exception? error = null; - - // Convert a single input string to a Queryable string-list with 1 entry. - IQueryable queryable = new[] { input }.AsQueryable(); - - try - { - // Use the Any(...) method to check if the result matches - score = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern())).ToArray(), MatchOperator); - } - catch (Exception e) - { - error = e; - } - - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error); - } - - /// - public MatchResult IsMatch(object? input) - { - var score = MatchScores.Mismatch; - Exception? error = null; - - JArray jArray; - try - { - jArray = new JArray { input }; - } - catch - { - jArray = input == null ? new JArray() : new JArray { JToken.FromObject(input) }; - } - - // Convert a single object to a Queryable JObject-list with 1 entry. - var queryable = jArray.ToDynamicClassArray().AsQueryable(); - - try - { - var patternsAsStringArray = _patterns.Select(p => p.GetPattern()).ToArray(); - var scores = patternsAsStringArray.Select(p => queryable.Any(p)).ToArray(); - - score = MatchScores.ToScore(scores, MatchOperator); - } - catch (Exception e) - { - error = e; - } - - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error); - } - - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public MatchOperator MatchOperator { get; } - - /// - public string Name => nameof(LinqMatcher); - - /// - public string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + - $")"; - } +// Copyright © WireMock.Net + +using System; +using System.Linq; +using System.Linq.Dynamic.Core; +using AnyOfTypes; +using Newtonsoft.Json.Linq; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Json; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// System.Linq.Dynamic.Core Expression Matcher +/// +/// +/// +public class LinqMatcher : IObjectMatcher, IStringMatcher +{ + private readonly AnyOf[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public object Value { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The pattern. + public LinqMatcher(AnyOf pattern) : this(new[] { pattern }) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public LinqMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + public LinqMatcher(MatchBehaviour matchBehaviour, AnyOf pattern) : this(matchBehaviour, MatchOperator.Or, pattern) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. (default = "Or") + /// The patterns. + public LinqMatcher( + MatchBehaviour matchBehaviour, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + Value = patterns; + } + + /// + public MatchResult IsMatch(string? input) + { + var score = MatchScores.Mismatch; + Exception? error = null; + + // Convert a single input string to a Queryable string-list with 1 entry. + IQueryable queryable = new[] { input }.AsQueryable(); + + try + { + // Use the Any(...) method to check if the result matches + score = MatchScores.ToScore(_patterns.Select(pattern => queryable.Any(pattern.GetPattern())).ToArray(), MatchOperator); + } + catch (Exception e) + { + error = e; + } + + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error); + } + + /// + public MatchResult IsMatch(object? input) + { + var score = MatchScores.Mismatch; + Exception? error = null; + + JArray jArray; + try + { + jArray = new JArray { input }; + } + catch + { + jArray = input == null ? new JArray() : new JArray { JToken.FromObject(input) }; + } + + // Convert a single object to a Queryable JObject-list with 1 entry. + var queryable = jArray.ToDynamicClassArray().AsQueryable(); + + try + { + var patternsAsStringArray = _patterns.Select(p => p.GetPattern()).ToArray(); + var scores = patternsAsStringArray.Select(p => queryable.Any(p)).ToArray(); + + score = MatchScores.ToScore(scores, MatchOperator); + } + catch (Exception e) + { + error = e; + } + + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error); + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => nameof(LinqMatcher); + + /// + public string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + + $")"; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/MatcherExtensions.cs b/src/WireMock.Net.Minimal/Matchers/MatcherExtensions.cs similarity index 100% rename from src/WireMock.Net/Matchers/MatcherExtensions.cs rename to src/WireMock.Net.Minimal/Matchers/MatcherExtensions.cs diff --git a/src/WireMock.Net/Matchers/Models/WireMockCustomScalarGraphType.cs b/src/WireMock.Net.Minimal/Matchers/Models/WireMockCustomScalarGraphType.cs similarity index 100% rename from src/WireMock.Net/Matchers/Models/WireMockCustomScalarGraphType.cs rename to src/WireMock.Net.Minimal/Matchers/Models/WireMockCustomScalarGraphType.cs diff --git a/src/WireMock.Net/Matchers/ProtoBufMatcher.cs b/src/WireMock.Net.Minimal/Matchers/ProtoBufMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/ProtoBufMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/ProtoBufMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/CompositeMatcherType.cs b/src/WireMock.Net.Minimal/Matchers/Request/CompositeMatcherType.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/CompositeMatcherType.cs rename to src/WireMock.Net.Minimal/Matchers/Request/CompositeMatcherType.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMatchResult.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMatchResult.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMatchResult.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs similarity index 97% rename from src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs index 5db7a9ae..e05ddf94 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageBodyMatcher.cs @@ -1,189 +1,189 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Linq; -using Stef.Validation; -using WireMock.Matchers.Helpers; -using WireMock.Util; - -namespace WireMock.Matchers.Request; - -/// -/// The request body matcher. -/// -public class RequestMessageBodyMatcher : IRequestMatcher -{ - /// - /// The body function - /// - public Func? Func { get; } - - /// - /// The body data function for byte[] - /// - public Func? DataFunc { get; } - - /// - /// The body data function for json - /// - public Func? JsonFunc { get; } - - /// - /// The body data function for BodyData - /// - public Func? BodyDataFunc { get; } - - /// - /// The body data function for FormUrlEncoded - /// - public Func?, bool>? FormUrlEncodedFunc { get; } - - /// - /// The matchers. - /// - public IMatcher[]? Matchers { get; } - - /// - /// The - /// - public MatchOperator MatchOperator { get; } = MatchOperator.Or; - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, string body) : - this(new[] { new WildcardMatcher(matchBehaviour, body) }.Cast().ToArray()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, byte[] body) : - this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The body. - public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, object body) : - this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher(Func func) - { - Func = Guard.NotNull(func); - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher(Func func) - { - DataFunc = Guard.NotNull(func); - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher(Func func) - { - JsonFunc = Guard.NotNull(func); - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher(Func func) - { - BodyDataFunc = Guard.NotNull(func); - } - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public RequestMessageBodyMatcher(Func?, bool> func) - { - FormUrlEncodedFunc = Guard.NotNull(func); - } - - /// - /// Initializes a new instance of the class. - /// - /// The matchers. - public RequestMessageBodyMatcher(params IMatcher[] matchers) - { - Matchers = Guard.NotNull(matchers); - } - - /// - /// Initializes a new instance of the class. - /// - /// The matchers. - /// The to use. - public RequestMessageBodyMatcher(MatchOperator matchOperator, params IMatcher[] matchers) - { - Matchers = Guard.NotNull(matchers); - MatchOperator = matchOperator; - } - - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - var (score, exception) = CalculateMatchScore(requestMessage).Expand(); - return requestMatchResult.AddScore(GetType(), score, exception); - } - - private MatchResult CalculateMatchScore(IRequestMessage requestMessage) - { - if (Matchers != null && Matchers.Any()) - { - var results = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray(); - return MatchResult.From(results, MatchOperator); - } - - if (Func != null) - { - return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString)); - } - - if (FormUrlEncodedFunc != null) - { - return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded)); - } - - if (JsonFunc != null) - { - return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson)); - } - - if (DataFunc != null) - { - return MatchScores.ToScore(DataFunc(requestMessage.BodyData?.BodyAsBytes)); - } - - if (BodyDataFunc != null) - { - return MatchScores.ToScore(BodyDataFunc(requestMessage.BodyData)); - } - - return default; - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Linq; +using Stef.Validation; +using WireMock.Matchers.Helpers; +using WireMock.Util; + +namespace WireMock.Matchers.Request; + +/// +/// The request body matcher. +/// +public class RequestMessageBodyMatcher : IRequestMatcher +{ + /// + /// The body function + /// + public Func? Func { get; } + + /// + /// The body data function for byte[] + /// + public Func? DataFunc { get; } + + /// + /// The body data function for json + /// + public Func? JsonFunc { get; } + + /// + /// The body data function for BodyData + /// + public Func? BodyDataFunc { get; } + + /// + /// The body data function for FormUrlEncoded + /// + public Func?, bool>? FormUrlEncodedFunc { get; } + + /// + /// The matchers. + /// + public IMatcher[]? Matchers { get; } + + /// + /// The + /// + public MatchOperator MatchOperator { get; } = MatchOperator.Or; + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The body. + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, string body) : + this(new[] { new WildcardMatcher(matchBehaviour, body) }.Cast().ToArray()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The body. + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, byte[] body) : + this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The body. + public RequestMessageBodyMatcher(MatchBehaviour matchBehaviour, object body) : + this(new[] { new ExactObjectMatcher(matchBehaviour, body) }.Cast().ToArray()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func func) + { + Func = Guard.NotNull(func); + } + + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func func) + { + DataFunc = Guard.NotNull(func); + } + + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func func) + { + JsonFunc = Guard.NotNull(func); + } + + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func func) + { + BodyDataFunc = Guard.NotNull(func); + } + + /// + /// Initializes a new instance of the class. + /// + /// The function. + public RequestMessageBodyMatcher(Func?, bool> func) + { + FormUrlEncodedFunc = Guard.NotNull(func); + } + + /// + /// Initializes a new instance of the class. + /// + /// The matchers. + public RequestMessageBodyMatcher(params IMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + } + + /// + /// Initializes a new instance of the class. + /// + /// The matchers. + /// The to use. + public RequestMessageBodyMatcher(MatchOperator matchOperator, params IMatcher[] matchers) + { + Matchers = Guard.NotNull(matchers); + MatchOperator = matchOperator; + } + + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + var (score, exception) = CalculateMatchScore(requestMessage).Expand(); + return requestMatchResult.AddScore(GetType(), score, exception); + } + + private MatchResult CalculateMatchScore(IRequestMessage requestMessage) + { + if (Matchers != null && Matchers.Any()) + { + var results = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray(); + return MatchResult.From(results, MatchOperator); + } + + if (Func != null) + { + return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString)); + } + + if (FormUrlEncodedFunc != null) + { + return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded)); + } + + if (JsonFunc != null) + { + return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson)); + } + + if (DataFunc != null) + { + return MatchScores.ToScore(DataFunc(requestMessage.BodyData?.BodyAsBytes)); + } + + if (BodyDataFunc != null) + { + return MatchScores.ToScore(BodyDataFunc(requestMessage.BodyData)); + } + + return default; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageClientIPMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageClientIPMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCompositeMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCompositeMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageCookieMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageGraphQLMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageGraphQLMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageGraphQLMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageHeaderMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHeaderMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageHttpVersionMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageHttpVersionMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageHttpVersionMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMethodMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageMethodMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMethodMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageMultiPartMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs similarity index 82% rename from src/WireMock.Net/Matchers/Request/RequestMessageMultiPartMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs index 8b18ff5c..6ec3934d 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageMultiPartMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageMultiPartMatcher.cs @@ -1,7 +1,6 @@ // Copyright © WireMock.Net using System; -using System.Collections.Generic; using System.Linq; using Stef.Validation; using WireMock.Util; @@ -13,6 +12,8 @@ namespace WireMock.Matchers.Request; /// public class RequestMessageMultiPartMatcher : IRequestMatcher { + private static readonly IMimeKitUtils MimeKitUtils = TypeLoader.LoadStaticInstance(); + /// /// The matchers. /// @@ -53,9 +54,6 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher /// public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) { -#if !MIMEKIT - throw new System.NotSupportedException("The MultiPartMatcher can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower."); -#else var score = MatchScores.Mismatch; Exception? exception = null; @@ -71,12 +69,10 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher try { - var mimePartMatchers = Matchers.OfType().ToArray(); - - foreach (var mimePartMatcher in Matchers.OfType().ToArray()) + foreach (var mimePartMatcher in Matchers.OfType().ToArray()) { score = MatchScores.Mismatch; - foreach (var mimeBodyPart in message.BodyParts.OfType()) + foreach (var mimeBodyPart in MimeKitUtils.GetBodyParts(message)) { var matchResult = mimePartMatcher.IsMatch(mimeBodyPart); if (matchResult.IsPerfect()) @@ -85,8 +81,8 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher break; } } - if ((MatchOperator == MatchOperator.Or && MatchScores.IsPerfect(score)) - || (MatchOperator == MatchOperator.And && !MatchScores.IsPerfect(score))) + + if ((MatchOperator == MatchOperator.Or && MatchScores.IsPerfect(score)) || (MatchOperator == MatchOperator.And && !MatchScores.IsPerfect(score))) { break; } @@ -98,6 +94,5 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher } return requestMatchResult.AddScore(GetType(), score, exception); -#endif } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageParamMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageParamMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessagePathMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessagePathMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageProtoBufMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageProtoBufMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageProtoBufMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageProtoBufMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageScenarioAndStateMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs b/src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/Request/RequestMessageUrlMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/Request/RequestMessageUrlMatcher.cs diff --git a/src/WireMock.Net/Matchers/Request/_mock4net-license.txt b/src/WireMock.Net.Minimal/Matchers/Request/_mock4net-license.txt similarity index 100% rename from src/WireMock.Net/Matchers/Request/_mock4net-license.txt rename to src/WireMock.Net.Minimal/Matchers/Request/_mock4net-license.txt diff --git a/src/WireMock.Net/Matchers/SimMetricsMatcher.cs b/src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs similarity index 97% rename from src/WireMock.Net/Matchers/SimMetricsMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs index 60c201b2..eb730b43 100644 --- a/src/WireMock.Net/Matchers/SimMetricsMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/SimMetricsMatcher.cs @@ -1,140 +1,140 @@ -// Copyright © WireMock.Net - -using System.Linq; -using AnyOfTypes; -using SimMetrics.Net; -using SimMetrics.Net.API; -using SimMetrics.Net.Metric; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// SimMetricsMatcher -/// -/// -public class SimMetricsMatcher : IStringMatcher -{ - private readonly AnyOf[] _patterns; - private readonly SimMetricType _simMetricType; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - /// The SimMetric Type - public SimMetricsMatcher(AnyOf pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(new[] { pattern }, simMetricType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - /// The SimMetric Type - public SimMetricsMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(matchBehaviour, new[] { pattern }, simMetricType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - /// The SimMetric Type - public SimMetricsMatcher(string[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns.ToAnyOfPatterns(), simMetricType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - /// The SimMetric Type - public SimMetricsMatcher(AnyOf[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns, simMetricType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - /// The SimMetric Type - /// The to use. (default = "Or") - public SimMetricsMatcher( - MatchBehaviour matchBehaviour, - AnyOf[] patterns, - SimMetricType simMetricType = SimMetricType.Levenstein, - MatchOperator matchOperator = MatchOperator.Average) - { - _patterns = Guard.NotNull(patterns); - _simMetricType = simMetricType; - MatchBehaviour = matchBehaviour; - MatchOperator = matchOperator; - } - - /// - public MatchResult IsMatch(string? input) - { - IStringMetric stringMetricType = GetStringMetricType(); - - var score = MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)).ToArray(), MatchOperator); - return MatchBehaviourHelper.Convert(MatchBehaviour, score); - } - - /// - public virtual string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + - $"{_simMetricType.GetFullyQualifiedEnumValue()}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}" + - $")"; - } - - private IStringMetric GetStringMetricType() - { - return _simMetricType switch - { - SimMetricType.BlockDistance => new BlockDistance(), - SimMetricType.ChapmanLengthDeviation => new ChapmanLengthDeviation(), - SimMetricType.CosineSimilarity => new CosineSimilarity(), - SimMetricType.DiceSimilarity => new DiceSimilarity(), - SimMetricType.EuclideanDistance => new EuclideanDistance(), - SimMetricType.JaccardSimilarity => new JaccardSimilarity(), - SimMetricType.Jaro => new Jaro(), - SimMetricType.JaroWinkler => new JaroWinkler(), - SimMetricType.MatchingCoefficient => new MatchingCoefficient(), - SimMetricType.MongeElkan => new MongeElkan(), - SimMetricType.NeedlemanWunch => new NeedlemanWunch(), - SimMetricType.OverlapCoefficient => new OverlapCoefficient(), - SimMetricType.QGramsDistance => new QGramsDistance(), - SimMetricType.SmithWaterman => new SmithWaterman(), - SimMetricType.SmithWatermanGotoh => new SmithWatermanGotoh(), - SimMetricType.SmithWatermanGotohWindowedAffine => new SmithWatermanGotohWindowedAffine(), - SimMetricType.ChapmanMeanLength => new ChapmanMeanLength(), - _ => new Levenstein() - }; - } - - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public MatchOperator MatchOperator { get; } = MatchOperator.Average; - - /// - public string Name => $"SimMetricsMatcher.{_simMetricType}"; +// Copyright © WireMock.Net + +using System.Linq; +using AnyOfTypes; +using SimMetrics.Net; +using SimMetrics.Net.API; +using SimMetrics.Net.Metric; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// SimMetricsMatcher +/// +/// +public class SimMetricsMatcher : IStringMatcher +{ + private readonly AnyOf[] _patterns; + private readonly SimMetricType _simMetricType; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The pattern. + /// The SimMetric Type + public SimMetricsMatcher(AnyOf pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(new[] { pattern }, simMetricType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + /// The SimMetric Type + public SimMetricsMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, SimMetricType simMetricType = SimMetricType.Levenstein) : this(matchBehaviour, new[] { pattern }, simMetricType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// The SimMetric Type + public SimMetricsMatcher(string[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns.ToAnyOfPatterns(), simMetricType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// The SimMetric Type + public SimMetricsMatcher(AnyOf[] patterns, SimMetricType simMetricType = SimMetricType.Levenstein) : this(MatchBehaviour.AcceptOnMatch, patterns, simMetricType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The patterns. + /// The SimMetric Type + /// The to use. (default = "Or") + public SimMetricsMatcher( + MatchBehaviour matchBehaviour, + AnyOf[] patterns, + SimMetricType simMetricType = SimMetricType.Levenstein, + MatchOperator matchOperator = MatchOperator.Average) + { + _patterns = Guard.NotNull(patterns); + _simMetricType = simMetricType; + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + } + + /// + public MatchResult IsMatch(string? input) + { + IStringMetric stringMetricType = GetStringMetricType(); + + var score = MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)).ToArray(), MatchOperator); + return MatchBehaviourHelper.Convert(MatchBehaviour, score); + } + + /// + public virtual string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + + $"{_simMetricType.GetFullyQualifiedEnumValue()}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}" + + $")"; + } + + private IStringMetric GetStringMetricType() + { + return _simMetricType switch + { + SimMetricType.BlockDistance => new BlockDistance(), + SimMetricType.ChapmanLengthDeviation => new ChapmanLengthDeviation(), + SimMetricType.CosineSimilarity => new CosineSimilarity(), + SimMetricType.DiceSimilarity => new DiceSimilarity(), + SimMetricType.EuclideanDistance => new EuclideanDistance(), + SimMetricType.JaccardSimilarity => new JaccardSimilarity(), + SimMetricType.Jaro => new Jaro(), + SimMetricType.JaroWinkler => new JaroWinkler(), + SimMetricType.MatchingCoefficient => new MatchingCoefficient(), + SimMetricType.MongeElkan => new MongeElkan(), + SimMetricType.NeedlemanWunch => new NeedlemanWunch(), + SimMetricType.OverlapCoefficient => new OverlapCoefficient(), + SimMetricType.QGramsDistance => new QGramsDistance(), + SimMetricType.SmithWaterman => new SmithWaterman(), + SimMetricType.SmithWatermanGotoh => new SmithWatermanGotoh(), + SimMetricType.SmithWatermanGotohWindowedAffine => new SmithWatermanGotohWindowedAffine(), + SimMetricType.ChapmanMeanLength => new ChapmanMeanLength(), + _ => new Levenstein() + }; + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } = MatchOperator.Average; + + /// + public string Name => $"SimMetricsMatcher.{_simMetricType}"; } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/XPathMatcher.cs b/src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs similarity index 96% rename from src/WireMock.Net/Matchers/XPathMatcher.cs rename to src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs index 512a2223..7504ce31 100644 --- a/src/WireMock.Net/Matchers/XPathMatcher.cs +++ b/src/WireMock.Net.Minimal/Matchers/XPathMatcher.cs @@ -1,184 +1,184 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; -using System.Xml.XPath; -using AnyOfTypes; -using WireMock.Extensions; -using WireMock.Models; -using Stef.Validation; -using WireMock.Admin.Mappings; -using WireMock.Util; -#if !NETSTANDARD1_3 -using Wmhelp.XPath2; -#endif - -namespace WireMock.Matchers; - -/// -/// XPath2Matcher -/// -/// -public class XPathMatcher : IStringMatcher -{ - private readonly AnyOf[] _patterns; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - /// Array of namespace prefix and uri. - /// - public XmlNamespace[]? XmlNamespaceMap { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public XPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, null, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The to use. (default = "Or") - /// The xml namespaces of the xml document. - /// The patterns. - public XPathMatcher( - MatchBehaviour matchBehaviour, - MatchOperator matchOperator = MatchOperator.Or, - XmlNamespace[]? xmlNamespaceMap = null, - params AnyOf[] patterns) - { - _patterns = Guard.NotNull(patterns); - XmlNamespaceMap = xmlNamespaceMap; - MatchBehaviour = matchBehaviour; - MatchOperator = matchOperator; - } - - /// - public MatchResult IsMatch(string? input) - { - var score = MatchScores.Mismatch; - - if (input == null) - { - return CreateMatchResult(score); - } - - try - { - var xPathEvaluator = new XPathEvaluator(); - xPathEvaluator.Load(input); - - if (!xPathEvaluator.IsXmlDocumentLoaded) - { - return CreateMatchResult(score); - } - - score = MatchScores.ToScore(xPathEvaluator.Evaluate(_patterns, XmlNamespaceMap), MatchOperator); - } - catch (Exception exception) - { - return CreateMatchResult(score, exception); - } - - return CreateMatchResult(score); - } - - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public MatchOperator MatchOperator { get; } - - /// - public string Name => nameof(XPathMatcher); - - /// - public string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + - $"null, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + - $")"; - } - - private MatchResult CreateMatchResult(double score, Exception? exception = null) - { - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); - } - - private sealed class XPathEvaluator - { - private XmlDocument? _xmlDocument; - private XPathNavigator? _xpathNavigator; - - public bool IsXmlDocumentLoaded => _xmlDocument != null; - - public void Load(string input) - { - try - { - _xmlDocument = new XmlDocument { InnerXml = input }; - _xpathNavigator = _xmlDocument.CreateNavigator(); - } - catch - { - _xmlDocument = default; - } - } - - public bool[] Evaluate(AnyOf[] patterns, IEnumerable? xmlNamespaceMap) - { - return _xpathNavigator == null ? [] : patterns.Select(pattern => true.Equals(Evaluate(_xpathNavigator, pattern, xmlNamespaceMap))).ToArray(); - } - - private object Evaluate(XPathNavigator navigator, AnyOf pattern, IEnumerable? xmlNamespaceMap) - { - var xpath = $"boolean({pattern.GetPattern()})"; - - var xmlNamespaceManager = GetXmlNamespaceManager(xmlNamespaceMap); - if (xmlNamespaceManager == null) - { -#if NETSTANDARD1_3 - return navigator.Evaluate(xpath); -#else - return navigator.XPath2Evaluate(xpath); -#endif - } - -#if NETSTANDARD1_3 - return navigator.Evaluate(xpath, xmlNamespaceManager); -#else - return navigator.XPath2Evaluate(xpath, xmlNamespaceManager); -#endif - } - - private XmlNamespaceManager? GetXmlNamespaceManager(IEnumerable? xmlNamespaceMap) - { - if (_xpathNavigator == null || xmlNamespaceMap == null) - { - return default; - } - - var nsManager = new XmlNamespaceManager(_xpathNavigator.NameTable); - foreach (var xmlNamespace in xmlNamespaceMap) - { - nsManager.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri); - } - - return nsManager; - } - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using System.Xml.XPath; +using AnyOfTypes; +using WireMock.Extensions; +using WireMock.Models; +using Stef.Validation; +using WireMock.Admin.Mappings; +using WireMock.Util; +#if !NETSTANDARD1_3 +using Wmhelp.XPath2; +#endif + +namespace WireMock.Matchers; + +/// +/// XPath2Matcher +/// +/// +public class XPathMatcher : IStringMatcher +{ + private readonly AnyOf[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// Array of namespace prefix and uri. + /// + public XmlNamespace[]? XmlNamespaceMap { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public XPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, null, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The to use. (default = "Or") + /// The xml namespaces of the xml document. + /// The patterns. + public XPathMatcher( + MatchBehaviour matchBehaviour, + MatchOperator matchOperator = MatchOperator.Or, + XmlNamespace[]? xmlNamespaceMap = null, + params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + XmlNamespaceMap = xmlNamespaceMap; + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + } + + /// + public MatchResult IsMatch(string? input) + { + var score = MatchScores.Mismatch; + + if (input == null) + { + return CreateMatchResult(score); + } + + try + { + var xPathEvaluator = new XPathEvaluator(); + xPathEvaluator.Load(input); + + if (!xPathEvaluator.IsXmlDocumentLoaded) + { + return CreateMatchResult(score); + } + + score = MatchScores.ToScore(xPathEvaluator.Evaluate(_patterns, XmlNamespaceMap), MatchOperator); + } + catch (Exception exception) + { + return CreateMatchResult(score, exception); + } + + return CreateMatchResult(score); + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => nameof(XPathMatcher); + + /// + public string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + + $"null, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + + $")"; + } + + private MatchResult CreateMatchResult(double score, Exception? exception = null) + { + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + } + + private sealed class XPathEvaluator + { + private XmlDocument? _xmlDocument; + private XPathNavigator? _xpathNavigator; + + public bool IsXmlDocumentLoaded => _xmlDocument != null; + + public void Load(string input) + { + try + { + _xmlDocument = new XmlDocument { InnerXml = input }; + _xpathNavigator = _xmlDocument.CreateNavigator(); + } + catch + { + _xmlDocument = default; + } + } + + public bool[] Evaluate(AnyOf[] patterns, IEnumerable? xmlNamespaceMap) + { + return _xpathNavigator == null ? [] : patterns.Select(pattern => true.Equals(Evaluate(_xpathNavigator, pattern, xmlNamespaceMap))).ToArray(); + } + + private object Evaluate(XPathNavigator navigator, AnyOf pattern, IEnumerable? xmlNamespaceMap) + { + var xpath = $"boolean({pattern.GetPattern()})"; + + var xmlNamespaceManager = GetXmlNamespaceManager(xmlNamespaceMap); + if (xmlNamespaceManager == null) + { +#if NETSTANDARD1_3 + return navigator.Evaluate(xpath); +#else + return navigator.XPath2Evaluate(xpath); +#endif + } + +#if NETSTANDARD1_3 + return navigator.Evaluate(xpath, xmlNamespaceManager); +#else + return navigator.XPath2Evaluate(xpath, xmlNamespaceManager); +#endif + } + + private XmlNamespaceManager? GetXmlNamespaceManager(IEnumerable? xmlNamespaceMap) + { + if (_xpathNavigator == null || xmlNamespaceMap == null) + { + return default; + } + + var nsManager = new XmlNamespaceManager(_xpathNavigator.NameTable); + foreach (var xmlNamespace in xmlNamespaceMap) + { + nsManager.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri); + } + + return nsManager; + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/Models/BlockingQueue.cs b/src/WireMock.Net.Minimal/Models/BlockingQueue.cs similarity index 100% rename from src/WireMock.Net/Models/BlockingQueue.cs rename to src/WireMock.Net.Minimal/Models/BlockingQueue.cs diff --git a/src/WireMock.Net/Models/GraphQLSchemaDetails.cs b/src/WireMock.Net.Minimal/Models/GraphQLSchemaDetails.cs similarity index 100% rename from src/WireMock.Net/Models/GraphQLSchemaDetails.cs rename to src/WireMock.Net.Minimal/Models/GraphQLSchemaDetails.cs diff --git a/src/WireMock.Net/Models/ProtoDefinitionData.cs b/src/WireMock.Net.Minimal/Models/ProtoDefinitionData.cs similarity index 100% rename from src/WireMock.Net/Models/ProtoDefinitionData.cs rename to src/WireMock.Net.Minimal/Models/ProtoDefinitionData.cs diff --git a/src/WireMock.Net/Models/TimeSettings.cs b/src/WireMock.Net.Minimal/Models/TimeSettings.cs similarity index 100% rename from src/WireMock.Net/Models/TimeSettings.cs rename to src/WireMock.Net.Minimal/Models/TimeSettings.cs diff --git a/src/WireMock.Net/Models/UrlDetails.cs b/src/WireMock.Net.Minimal/Models/UrlDetails.cs similarity index 100% rename from src/WireMock.Net/Models/UrlDetails.cs rename to src/WireMock.Net.Minimal/Models/UrlDetails.cs diff --git a/src/WireMock.Net/Models/Webhook.cs b/src/WireMock.Net.Minimal/Models/Webhook.cs similarity index 94% rename from src/WireMock.Net/Models/Webhook.cs rename to src/WireMock.Net.Minimal/Models/Webhook.cs index 5e7a017a..8469d681 100644 --- a/src/WireMock.Net/Models/Webhook.cs +++ b/src/WireMock.Net.Minimal/Models/Webhook.cs @@ -1,12 +1,12 @@ -// Copyright © WireMock.Net - -namespace WireMock.Models; - -/// -/// Webhook -/// -public class Webhook : IWebhook -{ - /// - public IWebhookRequest Request { get; set; } = null!; +// Copyright © WireMock.Net + +namespace WireMock.Models; + +/// +/// Webhook +/// +public class Webhook : IWebhook +{ + /// + public IWebhookRequest Request { get; set; } = null!; } \ No newline at end of file diff --git a/src/WireMock.Net/Models/WebhookRequest.cs b/src/WireMock.Net.Minimal/Models/WebhookRequest.cs similarity index 95% rename from src/WireMock.Net/Models/WebhookRequest.cs rename to src/WireMock.Net.Minimal/Models/WebhookRequest.cs index 9c6e0978..db4ac062 100644 --- a/src/WireMock.Net/Models/WebhookRequest.cs +++ b/src/WireMock.Net.Minimal/Models/WebhookRequest.cs @@ -1,43 +1,43 @@ -// Copyright © WireMock.Net - -using System.Collections.Generic; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Models; - -/// -/// WebhookRequest -/// -public class WebhookRequest : IWebhookRequest -{ - /// - public string Url { get; set; } = null!; - - /// - public string Method { get; set; } = null!; - - /// - public IDictionary>? Headers { get; set; } - - /// - public IBodyData? BodyData { get; set; } - - /// - public bool? UseTransformer { get; set; } - - /// - public TransformerType TransformerType { get; set; } - - /// - public ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; } - - /// - public int? Delay { get; set; } - - /// - public int? MinimumRandomDelay { get; set; } - - /// - public int? MaximumRandomDelay { get; set; } +// Copyright © WireMock.Net + +using System.Collections.Generic; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Models; + +/// +/// WebhookRequest +/// +public class WebhookRequest : IWebhookRequest +{ + /// + public string Url { get; set; } = null!; + + /// + public string Method { get; set; } = null!; + + /// + public IDictionary>? Headers { get; set; } + + /// + public IBodyData? BodyData { get; set; } + + /// + public bool? UseTransformer { get; set; } + + /// + public TransformerType TransformerType { get; set; } + + /// + public ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; } + + /// + public int? Delay { get; set; } + + /// + public int? MinimumRandomDelay { get; set; } + + /// + public int? MaximumRandomDelay { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.NETCore.cs similarity index 100% rename from src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs rename to src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.NETCore.cs diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.NETStandard.cs similarity index 97% rename from src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs rename to src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.NETStandard.cs index 0cbbed20..510bd9ea 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs +++ b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.NETStandard.cs @@ -1,123 +1,123 @@ -// Copyright © WireMock.Net - -#if USE_ASPNETCORE && !NETSTANDARD1_3 -using System; -using System.Collections.Generic; -using System.Net; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.AspNetCore.Server.Kestrel.Https; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using CertificateLoader = WireMock.HttpsCertificate.CertificateLoader; - -namespace WireMock.Owin -{ - internal partial class AspNetCoreSelfHost - { - private static void SetKestrelOptionsLimits(KestrelServerOptions options) - { - options.Limits.MaxRequestBodySize = null; // https://stackoverflow.com/questions/46738364/increase-upload-request-length-limit-in-kestrel - options.Limits.MaxRequestBufferSize = null; - options.Limits.MaxRequestHeaderCount = 100; - options.Limits.MaxResponseBufferSize = null; - } - - private static void SetHttpsAndUrls(KestrelServerOptions kestrelOptions, IWireMockMiddlewareOptions wireMockMiddlewareOptions, IEnumerable urlDetails) - { - foreach (var urlDetail in urlDetails) - { - if (urlDetail.IsHttps) - { - Listen(kestrelOptions, urlDetail, listenOptions => - { - listenOptions.UseHttps(options => - { - if (wireMockMiddlewareOptions.CustomCertificateDefined) - { - options.ServerCertificate = CertificateLoader.LoadCertificate( - wireMockMiddlewareOptions.X509StoreName, - wireMockMiddlewareOptions.X509StoreLocation, - wireMockMiddlewareOptions.X509ThumbprintOrSubjectName, - wireMockMiddlewareOptions.X509CertificateFilePath, - wireMockMiddlewareOptions.X509CertificatePassword, - urlDetail.Host - ); - } - - options.ClientCertificateMode = (ClientCertificateMode)wireMockMiddlewareOptions.ClientCertificateMode; - if (wireMockMiddlewareOptions.AcceptAnyClientCertificate) - { - options.ClientCertificateValidation = (_, _, _) => true; - } - }); - - if (urlDetail.IsHttp2) - { - listenOptions.Protocols = HttpProtocols.Http2; - } - }); - continue; - } - - if (urlDetail.IsHttp2) - { - Listen(kestrelOptions, urlDetail, listenOptions => - { - listenOptions.Protocols = HttpProtocols.Http2; - }); - continue; - } - - Listen(kestrelOptions, urlDetail, _ => { }); - } - } - - private static void Listen(KestrelServerOptions kestrelOptions, HostUrlDetails urlDetail, Action configure) - { - // Listens on any IP with the given port. - if (urlDetail is { Port: > 0, Host: "0.0.0.0" }) - { - kestrelOptions.ListenAnyIP(urlDetail.Port, configure); - return; - } - - // Listens on ::1 and 127.0.0.1 with the given port. - if (urlDetail is { Port: > 0, Host: "localhost" or "127.0.0.1" or "::1" }) - { - kestrelOptions.ListenLocalhost(urlDetail.Port, configure); - return; - } - - // Try to parse the host as a valid IP address and bind to the given IP address and port. - if (IPAddress.TryParse(urlDetail.Host, out var ipAddress)) - { - kestrelOptions.Listen(ipAddress, urlDetail.Port, configure); - return; - } - - // Otherwise, listen on all IPs. - kestrelOptions.ListenAnyIP(urlDetail.Port, configure); - } - } - - internal static class IWebHostBuilderExtensions - { - internal static IWebHostBuilder ConfigureAppConfigurationUsingEnvironmentVariables(this IWebHostBuilder builder) - { - return builder.ConfigureAppConfiguration(config => - { - config.AddEnvironmentVariables(); - }); - } - - internal static IWebHostBuilder ConfigureKestrelServerOptions(this IWebHostBuilder builder) - { - return builder.ConfigureServices((context, services) => - { - services.Configure(context.Configuration.GetSection("Kestrel")); - }); - } - } -} -#endif +// Copyright © WireMock.Net + +#if USE_ASPNETCORE && !NETSTANDARD1_3 +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Https; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using CertificateLoader = WireMock.HttpsCertificate.CertificateLoader; + +namespace WireMock.Owin +{ + internal partial class AspNetCoreSelfHost + { + private static void SetKestrelOptionsLimits(KestrelServerOptions options) + { + options.Limits.MaxRequestBodySize = null; // https://stackoverflow.com/questions/46738364/increase-upload-request-length-limit-in-kestrel + options.Limits.MaxRequestBufferSize = null; + options.Limits.MaxRequestHeaderCount = 100; + options.Limits.MaxResponseBufferSize = null; + } + + private static void SetHttpsAndUrls(KestrelServerOptions kestrelOptions, IWireMockMiddlewareOptions wireMockMiddlewareOptions, IEnumerable urlDetails) + { + foreach (var urlDetail in urlDetails) + { + if (urlDetail.IsHttps) + { + Listen(kestrelOptions, urlDetail, listenOptions => + { + listenOptions.UseHttps(options => + { + if (wireMockMiddlewareOptions.CustomCertificateDefined) + { + options.ServerCertificate = CertificateLoader.LoadCertificate( + wireMockMiddlewareOptions.X509StoreName, + wireMockMiddlewareOptions.X509StoreLocation, + wireMockMiddlewareOptions.X509ThumbprintOrSubjectName, + wireMockMiddlewareOptions.X509CertificateFilePath, + wireMockMiddlewareOptions.X509CertificatePassword, + urlDetail.Host + ); + } + + options.ClientCertificateMode = (ClientCertificateMode)wireMockMiddlewareOptions.ClientCertificateMode; + if (wireMockMiddlewareOptions.AcceptAnyClientCertificate) + { + options.ClientCertificateValidation = (_, _, _) => true; + } + }); + + if (urlDetail.IsHttp2) + { + listenOptions.Protocols = HttpProtocols.Http2; + } + }); + continue; + } + + if (urlDetail.IsHttp2) + { + Listen(kestrelOptions, urlDetail, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + }); + continue; + } + + Listen(kestrelOptions, urlDetail, _ => { }); + } + } + + private static void Listen(KestrelServerOptions kestrelOptions, HostUrlDetails urlDetail, Action configure) + { + // Listens on any IP with the given port. + if (urlDetail is { Port: > 0, Host: "0.0.0.0" }) + { + kestrelOptions.ListenAnyIP(urlDetail.Port, configure); + return; + } + + // Listens on ::1 and 127.0.0.1 with the given port. + if (urlDetail is { Port: > 0, Host: "localhost" or "127.0.0.1" or "::1" }) + { + kestrelOptions.ListenLocalhost(urlDetail.Port, configure); + return; + } + + // Try to parse the host as a valid IP address and bind to the given IP address and port. + if (IPAddress.TryParse(urlDetail.Host, out var ipAddress)) + { + kestrelOptions.Listen(ipAddress, urlDetail.Port, configure); + return; + } + + // Otherwise, listen on all IPs. + kestrelOptions.ListenAnyIP(urlDetail.Port, configure); + } + } + + internal static class IWebHostBuilderExtensions + { + internal static IWebHostBuilder ConfigureAppConfigurationUsingEnvironmentVariables(this IWebHostBuilder builder) + { + return builder.ConfigureAppConfiguration(config => + { + config.AddEnvironmentVariables(); + }); + } + + internal static IWebHostBuilder ConfigureKestrelServerOptions(this IWebHostBuilder builder) + { + return builder.ConfigureServices((context, services) => + { + services.Configure(context.Configuration.GetSection("Kestrel")); + }); + } + } +} +#endif diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard13.cs b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.NETStandard13.cs similarity index 97% rename from src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard13.cs rename to src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.NETStandard13.cs index 9b85d76e..32821624 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard13.cs +++ b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.NETStandard13.cs @@ -1,67 +1,67 @@ -// Copyright © WireMock.Net - -#if USE_ASPNETCORE && NETSTANDARD1_3 -using System.Collections.Generic; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Server.Kestrel; -using Microsoft.AspNetCore.Server.Kestrel.Https; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using WireMock.HttpsCertificate; - -namespace WireMock.Owin; - -internal partial class AspNetCoreSelfHost -{ - private static void SetKestrelOptionsLimits(KestrelServerOptions options) - { - options.Limits.MaxRequestBufferSize = null; - options.Limits.MaxRequestHeaderCount = 100; - options.Limits.MaxResponseBufferSize = null; - } - - private static void SetHttpsAndUrls(KestrelServerOptions options, IWireMockMiddlewareOptions wireMockMiddlewareOptions, IEnumerable urlDetails) - { - foreach (var urlDetail in urlDetails) - { - if (urlDetail.IsHttps) - { - options.UseHttps(new HttpsConnectionFilterOptions - { - ServerCertificate = wireMockMiddlewareOptions.CustomCertificateDefined - ? CertificateLoader.LoadCertificate( - wireMockMiddlewareOptions.X509StoreName, - wireMockMiddlewareOptions.X509StoreLocation, - wireMockMiddlewareOptions.X509ThumbprintOrSubjectName, - wireMockMiddlewareOptions.X509CertificateFilePath, - wireMockMiddlewareOptions.X509CertificatePassword, - urlDetail.Host) - : PublicCertificateHelper.GetX509Certificate2(), - ClientCertificateMode = (ClientCertificateMode) wireMockMiddlewareOptions.ClientCertificateMode, - ClientCertificateValidation = wireMockMiddlewareOptions.AcceptAnyClientCertificate - ? (_, _, _) => true - : null, - }); - } - } - } -} - -internal static class IWebHostBuilderExtensions -{ - internal static IWebHostBuilder ConfigureAppConfigurationUsingEnvironmentVariables(this IWebHostBuilder builder) => builder; - - internal static IWebHostBuilder ConfigureKestrelServerOptions(this IWebHostBuilder builder) - { - var configuration = new ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); - - return builder.ConfigureServices(services => - { - services.Configure(configuration.GetSection("Kestrel")); - }); - } -} - +// Copyright © WireMock.Net + +#if USE_ASPNETCORE && NETSTANDARD1_3 +using System.Collections.Generic; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel; +using Microsoft.AspNetCore.Server.Kestrel.Https; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using WireMock.HttpsCertificate; + +namespace WireMock.Owin; + +internal partial class AspNetCoreSelfHost +{ + private static void SetKestrelOptionsLimits(KestrelServerOptions options) + { + options.Limits.MaxRequestBufferSize = null; + options.Limits.MaxRequestHeaderCount = 100; + options.Limits.MaxResponseBufferSize = null; + } + + private static void SetHttpsAndUrls(KestrelServerOptions options, IWireMockMiddlewareOptions wireMockMiddlewareOptions, IEnumerable urlDetails) + { + foreach (var urlDetail in urlDetails) + { + if (urlDetail.IsHttps) + { + options.UseHttps(new HttpsConnectionFilterOptions + { + ServerCertificate = wireMockMiddlewareOptions.CustomCertificateDefined + ? CertificateLoader.LoadCertificate( + wireMockMiddlewareOptions.X509StoreName, + wireMockMiddlewareOptions.X509StoreLocation, + wireMockMiddlewareOptions.X509ThumbprintOrSubjectName, + wireMockMiddlewareOptions.X509CertificateFilePath, + wireMockMiddlewareOptions.X509CertificatePassword, + urlDetail.Host) + : PublicCertificateHelper.GetX509Certificate2(), + ClientCertificateMode = (ClientCertificateMode) wireMockMiddlewareOptions.ClientCertificateMode, + ClientCertificateValidation = wireMockMiddlewareOptions.AcceptAnyClientCertificate + ? (_, _, _) => true + : null, + }); + } + } + } +} + +internal static class IWebHostBuilderExtensions +{ + internal static IWebHostBuilder ConfigureAppConfigurationUsingEnvironmentVariables(this IWebHostBuilder builder) => builder; + + internal static IWebHostBuilder ConfigureKestrelServerOptions(this IWebHostBuilder builder) + { + var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + return builder.ConfigureServices(services => + { + services.Configure(configuration.GetSection("Kestrel")); + }); + } +} + #endif \ No newline at end of file diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.cs similarity index 96% rename from src/WireMock.Net/Owin/AspNetCoreSelfHost.cs rename to src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.cs index bf414a52..7ce3c14d 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.cs @@ -1,189 +1,189 @@ -// Copyright © WireMock.Net - -#if USE_ASPNETCORE -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Stef.Validation; -using WireMock.Logging; -using WireMock.Owin.Mappers; -using WireMock.Services; -using WireMock.Util; - -namespace WireMock.Owin; - -internal partial class AspNetCoreSelfHost : IOwinSelfHost -{ - private const string CorsPolicyName = "WireMock.Net - Policy"; - - private readonly CancellationTokenSource _cts = new(); - private readonly IWireMockMiddlewareOptions _wireMockMiddlewareOptions; - private readonly IWireMockLogger _logger; - private readonly HostUrlOptions _urlOptions; - - private Exception _runningException; - private IWebHost _host; - - public bool IsStarted { get; private set; } - - public List Urls { get; } = new(); - - public List Ports { get; } = new(); - - public Exception RunningException => _runningException; - - public AspNetCoreSelfHost(IWireMockMiddlewareOptions wireMockMiddlewareOptions, HostUrlOptions urlOptions) - { - Guard.NotNull(wireMockMiddlewareOptions); - Guard.NotNull(urlOptions); - - _logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger(); - - _wireMockMiddlewareOptions = wireMockMiddlewareOptions; - _urlOptions = urlOptions; - } - - public Task StartAsync() - { - var builder = new WebHostBuilder(); - - // Workaround for https://github.com/wiremock/WireMock.Net/issues/292 - // On some platforms, AppContext.BaseDirectory is null, which causes WebHostBuilder to fail if ContentRoot is not - // specified (even though we don't actually use that base path mechanism, since we have our own way of configuring - // a filesystem handler). - if (string.IsNullOrEmpty(AppContext.BaseDirectory)) - { - builder.UseContentRoot(Directory.GetCurrentDirectory()); - } - - _host = builder - .UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/ - .ConfigureAppConfigurationUsingEnvironmentVariables() - .ConfigureServices(services => - { - services.AddSingleton(_wireMockMiddlewareOptions); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - -#if NETCOREAPP3_1 || NET5_0_OR_GREATER - AddCors(services); -#endif - _wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services); - }) - .Configure(appBuilder => - { - appBuilder.UseMiddleware(); - -#if NETCOREAPP3_1 || NET5_0_OR_GREATER - UseCors(appBuilder); -#endif - _wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder); - - appBuilder.UseMiddleware(); - - _wireMockMiddlewareOptions.PostWireMockMiddlewareInit?.Invoke(appBuilder); - }) - .UseKestrel(options => - { - SetKestrelOptionsLimits(options); - - SetHttpsAndUrls(options, _wireMockMiddlewareOptions, _urlOptions.GetDetails()); - }) - .ConfigureKestrelServerOptions() - -#if NETSTANDARD1_3 - .UseUrls(_urlOptions.GetDetails().Select(u => u.Url).ToArray()) -#endif - .Build(); - - return RunHost(_cts.Token); - } - - private Task RunHost(CancellationToken token) - { - try - { -#if NETCOREAPP3_1 || NET5_0_OR_GREATER - var appLifetime = _host.Services.GetRequiredService(); -#else - var appLifetime = _host.Services.GetRequiredService(); -#endif - appLifetime.ApplicationStarted.Register(() => - { - var addresses = _host.ServerFeatures - .Get()! - .Addresses; - - foreach (var address in addresses) - { - Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost")); - - PortUtils.TryExtract(address, out _, out _, out _, out _, out var port); - Ports.Add(port); - } - - IsStarted = true; - }); - -#if NETSTANDARD1_3 - _logger.Info("Server using netstandard1.3"); -#elif NETSTANDARD2_0 - _logger.Info("Server using netstandard2.0"); -#elif NETSTANDARD2_1 - _logger.Info("Server using netstandard2.1"); -#elif NETCOREAPP3_1 - _logger.Info("Server using .NET Core App 3.1"); -#elif NET5_0 - _logger.Info("Server using .NET 5.0"); -#elif NET6_0 - _logger.Info("Server using .NET 6.0"); -#elif NET7_0 - _logger.Info("Server using .NET 7.0"); -#elif NET8_0 - _logger.Info("Server using .NET 8.0"); -#elif NET46 - _logger.Info("Server using .NET Framework 4.6.1 or higher"); -#endif - -#if NETSTANDARD1_3 - return Task.Run(() => - { - _host.Run(token); - }); -#else - return _host.RunAsync(token); -#endif - } - catch (Exception e) - { - _runningException = e; - _logger.Error(e.ToString()); - - IsStarted = false; - - return Task.CompletedTask; - } - } - - public Task StopAsync() - { - _cts.Cancel(); - - IsStarted = false; -#if NETSTANDARD1_3 - return Task.CompletedTask; -#else - return _host.StopAsync(); -#endif - } -} +// Copyright © WireMock.Net + +#if USE_ASPNETCORE +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Stef.Validation; +using WireMock.Logging; +using WireMock.Owin.Mappers; +using WireMock.Services; +using WireMock.Util; + +namespace WireMock.Owin; + +internal partial class AspNetCoreSelfHost : IOwinSelfHost +{ + private const string CorsPolicyName = "WireMock.Net - Policy"; + + private readonly CancellationTokenSource _cts = new(); + private readonly IWireMockMiddlewareOptions _wireMockMiddlewareOptions; + private readonly IWireMockLogger _logger; + private readonly HostUrlOptions _urlOptions; + + private Exception _runningException; + private IWebHost _host; + + public bool IsStarted { get; private set; } + + public List Urls { get; } = new(); + + public List Ports { get; } = new(); + + public Exception RunningException => _runningException; + + public AspNetCoreSelfHost(IWireMockMiddlewareOptions wireMockMiddlewareOptions, HostUrlOptions urlOptions) + { + Guard.NotNull(wireMockMiddlewareOptions); + Guard.NotNull(urlOptions); + + _logger = wireMockMiddlewareOptions.Logger ?? new WireMockConsoleLogger(); + + _wireMockMiddlewareOptions = wireMockMiddlewareOptions; + _urlOptions = urlOptions; + } + + public Task StartAsync() + { + var builder = new WebHostBuilder(); + + // Workaround for https://github.com/wiremock/WireMock.Net/issues/292 + // On some platforms, AppContext.BaseDirectory is null, which causes WebHostBuilder to fail if ContentRoot is not + // specified (even though we don't actually use that base path mechanism, since we have our own way of configuring + // a filesystem handler). + if (string.IsNullOrEmpty(AppContext.BaseDirectory)) + { + builder.UseContentRoot(Directory.GetCurrentDirectory()); + } + + _host = builder + .UseSetting("suppressStatusMessages", "True") // https://andrewlock.net/suppressing-the-startup-and-shutdown-messages-in-asp-net-core/ + .ConfigureAppConfigurationUsingEnvironmentVariables() + .ConfigureServices(services => + { + services.AddSingleton(_wireMockMiddlewareOptions); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + +#if NETCOREAPP3_1 || NET5_0_OR_GREATER + AddCors(services); +#endif + _wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services); + }) + .Configure(appBuilder => + { + appBuilder.UseMiddleware(); + +#if NETCOREAPP3_1 || NET5_0_OR_GREATER + UseCors(appBuilder); +#endif + _wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder); + + appBuilder.UseMiddleware(); + + _wireMockMiddlewareOptions.PostWireMockMiddlewareInit?.Invoke(appBuilder); + }) + .UseKestrel(options => + { + SetKestrelOptionsLimits(options); + + SetHttpsAndUrls(options, _wireMockMiddlewareOptions, _urlOptions.GetDetails()); + }) + .ConfigureKestrelServerOptions() + +#if NETSTANDARD1_3 + .UseUrls(_urlOptions.GetDetails().Select(u => u.Url).ToArray()) +#endif + .Build(); + + return RunHost(_cts.Token); + } + + private Task RunHost(CancellationToken token) + { + try + { +#if NETCOREAPP3_1 || NET5_0_OR_GREATER + var appLifetime = _host.Services.GetRequiredService(); +#else + var appLifetime = _host.Services.GetRequiredService(); +#endif + appLifetime.ApplicationStarted.Register(() => + { + var addresses = _host.ServerFeatures + .Get()! + .Addresses; + + foreach (var address in addresses) + { + Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost")); + + PortUtils.TryExtract(address, out _, out _, out _, out _, out var port); + Ports.Add(port); + } + + IsStarted = true; + }); + +#if NETSTANDARD1_3 + _logger.Info("Server using netstandard1.3"); +#elif NETSTANDARD2_0 + _logger.Info("Server using netstandard2.0"); +#elif NETSTANDARD2_1 + _logger.Info("Server using netstandard2.1"); +#elif NETCOREAPP3_1 + _logger.Info("Server using .NET Core App 3.1"); +#elif NET5_0 + _logger.Info("Server using .NET 5.0"); +#elif NET6_0 + _logger.Info("Server using .NET 6.0"); +#elif NET7_0 + _logger.Info("Server using .NET 7.0"); +#elif NET8_0 + _logger.Info("Server using .NET 8.0"); +#elif NET46 + _logger.Info("Server using .NET Framework 4.6.1 or higher"); +#endif + +#if NETSTANDARD1_3 + return Task.Run(() => + { + _host.Run(token); + }); +#else + return _host.RunAsync(token); +#endif + } + catch (Exception e) + { + _runningException = e; + _logger.Error(e.ToString()); + + IsStarted = false; + + return Task.CompletedTask; + } + } + + public Task StopAsync() + { + _cts.Cancel(); + + IsStarted = false; +#if NETSTANDARD1_3 + return Task.CompletedTask; +#else + return _host.StopAsync(); +#endif + } +} #endif \ No newline at end of file diff --git a/src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs b/src/WireMock.Net.Minimal/Owin/GlobalExceptionMiddleware.cs similarity index 100% rename from src/WireMock.Net/Owin/GlobalExceptionMiddleware.cs rename to src/WireMock.Net.Minimal/Owin/GlobalExceptionMiddleware.cs diff --git a/src/WireMock.Net/Owin/HostUrlDetails.cs b/src/WireMock.Net.Minimal/Owin/HostUrlDetails.cs similarity index 100% rename from src/WireMock.Net/Owin/HostUrlDetails.cs rename to src/WireMock.Net.Minimal/Owin/HostUrlDetails.cs diff --git a/src/WireMock.Net/Owin/HostUrlOptions.cs b/src/WireMock.Net.Minimal/Owin/HostUrlOptions.cs similarity index 97% rename from src/WireMock.Net/Owin/HostUrlOptions.cs rename to src/WireMock.Net.Minimal/Owin/HostUrlOptions.cs index f5e0b22a..ad74a926 100644 --- a/src/WireMock.Net/Owin/HostUrlOptions.cs +++ b/src/WireMock.Net.Minimal/Owin/HostUrlOptions.cs @@ -1,64 +1,64 @@ -// Copyright © WireMock.Net - -using System.Collections.Generic; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Owin; - -internal class HostUrlOptions -{ - private const string Star = "*"; - - public ICollection? Urls { get; set; } - - public int? Port { get; set; } - - public HostingScheme HostingScheme { get; set; } - - public bool? UseHttp2 { get; set; } - - public IReadOnlyList GetDetails() - { - var list = new List(); - if (Urls == null) - { - if (HostingScheme is HostingScheme.Http or HostingScheme.Https) - { - var port = Port > 0 ? Port.Value : FindFreeTcpPort(); - var scheme = HostingScheme == HostingScheme.Https ? "https" : "http"; - list.Add(new HostUrlDetails { IsHttps = HostingScheme == HostingScheme.Https, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port }); - } - - if (HostingScheme == HostingScheme.HttpAndHttps) - { - var httpPort = Port > 0 ? Port.Value : FindFreeTcpPort(); - list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"http://{Star}:{httpPort}", Scheme = "http", Host = Star, Port = httpPort }); - - var httpsPort = FindFreeTcpPort(); // In this scenario, always get a free port for https. - list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"https://{Star}:{httpsPort}", Scheme = "https", Host = Star, Port = httpsPort }); - } - } - else - { - foreach (var url in Urls) - { - if (PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var protocol, out var host, out var port)) - { - list.Add(new HostUrlDetails { IsHttps = isHttps, IsHttp2 = isGrpc, Url = url, Scheme = protocol, Host = host, Port = port }); - } - } - } - - return list; - } - - private static int FindFreeTcpPort() - { -#if USE_ASPNETCORE || NETSTANDARD2_0 || NETSTANDARD2_1 - return 0; -#else - return PortUtils.FindFreeTcpPort(); -#endif - } +// Copyright © WireMock.Net + +using System.Collections.Generic; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Owin; + +internal class HostUrlOptions +{ + private const string Star = "*"; + + public ICollection? Urls { get; set; } + + public int? Port { get; set; } + + public HostingScheme HostingScheme { get; set; } + + public bool? UseHttp2 { get; set; } + + public IReadOnlyList GetDetails() + { + var list = new List(); + if (Urls == null) + { + if (HostingScheme is HostingScheme.Http or HostingScheme.Https) + { + var port = Port > 0 ? Port.Value : FindFreeTcpPort(); + var scheme = HostingScheme == HostingScheme.Https ? "https" : "http"; + list.Add(new HostUrlDetails { IsHttps = HostingScheme == HostingScheme.Https, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port }); + } + + if (HostingScheme == HostingScheme.HttpAndHttps) + { + var httpPort = Port > 0 ? Port.Value : FindFreeTcpPort(); + list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"http://{Star}:{httpPort}", Scheme = "http", Host = Star, Port = httpPort }); + + var httpsPort = FindFreeTcpPort(); // In this scenario, always get a free port for https. + list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"https://{Star}:{httpsPort}", Scheme = "https", Host = Star, Port = httpsPort }); + } + } + else + { + foreach (var url in Urls) + { + if (PortUtils.TryExtract(url, out var isHttps, out var isGrpc, out var protocol, out var host, out var port)) + { + list.Add(new HostUrlDetails { IsHttps = isHttps, IsHttp2 = isGrpc, Url = url, Scheme = protocol, Host = host, Port = port }); + } + } + } + + return list; + } + + private static int FindFreeTcpPort() + { +#if USE_ASPNETCORE || NETSTANDARD2_0 || NETSTANDARD2_1 + return 0; +#else + return PortUtils.FindFreeTcpPort(); +#endif + } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/IMappingMatcher.cs b/src/WireMock.Net.Minimal/Owin/IMappingMatcher.cs similarity index 96% rename from src/WireMock.Net/Owin/IMappingMatcher.cs rename to src/WireMock.Net.Minimal/Owin/IMappingMatcher.cs index 73e6e152..8047d64d 100644 --- a/src/WireMock.Net/Owin/IMappingMatcher.cs +++ b/src/WireMock.Net.Minimal/Owin/IMappingMatcher.cs @@ -1,8 +1,8 @@ -// Copyright © WireMock.Net - -namespace WireMock.Owin; - -internal interface IMappingMatcher -{ - (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request); +// Copyright © WireMock.Net + +namespace WireMock.Owin; + +internal interface IMappingMatcher +{ + (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request); } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/IOwinSelfHost.cs b/src/WireMock.Net.Minimal/Owin/IOwinSelfHost.cs similarity index 100% rename from src/WireMock.Net/Owin/IOwinSelfHost.cs rename to src/WireMock.Net.Minimal/Owin/IOwinSelfHost.cs diff --git a/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs b/src/WireMock.Net.Minimal/Owin/IWireMockMiddlewareOptions.cs similarity index 100% rename from src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs rename to src/WireMock.Net.Minimal/Owin/IWireMockMiddlewareOptions.cs diff --git a/src/WireMock.Net/Owin/Mappers/IOwinRequestMapper.cs b/src/WireMock.Net.Minimal/Owin/Mappers/IOwinRequestMapper.cs similarity index 100% rename from src/WireMock.Net/Owin/Mappers/IOwinRequestMapper.cs rename to src/WireMock.Net.Minimal/Owin/Mappers/IOwinRequestMapper.cs diff --git a/src/WireMock.Net/Owin/Mappers/IOwinResponseMapper.cs b/src/WireMock.Net.Minimal/Owin/Mappers/IOwinResponseMapper.cs similarity index 100% rename from src/WireMock.Net/Owin/Mappers/IOwinResponseMapper.cs rename to src/WireMock.Net.Minimal/Owin/Mappers/IOwinResponseMapper.cs diff --git a/src/WireMock.Net/Owin/Mappers/OwinRequestMapper.cs b/src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs similarity index 100% rename from src/WireMock.Net/Owin/Mappers/OwinRequestMapper.cs rename to src/WireMock.Net.Minimal/Owin/Mappers/OwinRequestMapper.cs diff --git a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs b/src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs similarity index 95% rename from src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs rename to src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs index 7a5025c6..41a8fc09 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net.Minimal/Owin/Mappers/OwinResponseMapper.cs @@ -1,269 +1,269 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; -using RandomDataGenerator.FieldOptions; -using RandomDataGenerator.Randomizers; -using WireMock.Http; -using WireMock.ResponseBuilders; -using WireMock.Types; -using Stef.Validation; -using WireMock.Util; - -#if !USE_ASPNETCORE -using IResponse = Microsoft.Owin.IOwinResponse; -#else -using Microsoft.AspNetCore.Http; -using IResponse = Microsoft.AspNetCore.Http.HttpResponse; -#endif - -namespace WireMock.Owin.Mappers -{ - /// - /// OwinResponseMapper - /// - internal class OwinResponseMapper : IOwinResponseMapper - { - private readonly IRandomizerNumber _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 }); - private readonly IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 }); - private readonly IWireMockMiddlewareOptions _options; - private readonly Encoding _utf8NoBom = new UTF8Encoding(false); - - // https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx - private static readonly IDictionary>> ResponseHeadersToFix = - new Dictionary>>(StringComparer.OrdinalIgnoreCase) - { - { HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() }, - { HttpKnownHeaderNames.ContentLength, (r, hasBody, v) => - { - // Only set the Content-Length header if the response does not have a body - if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength)) - { - r.ContentLength = contentLength; - } - } - } - }; - - /// - /// Constructor - /// - /// The IWireMockMiddlewareOptions. - public OwinResponseMapper(IWireMockMiddlewareOptions options) - { - _options = Guard.NotNull(options); - } - - /// - public async Task MapAsync(IResponseMessage? responseMessage, IResponse response) - { - if (responseMessage == null) - { - return; - } - - var bodyData = responseMessage.BodyData; - if (bodyData?.GetDetectedBodyType() == BodyType.SseString) - { - await HandleSseStringAsync(responseMessage, response, bodyData); - return; - } - - byte[]? bytes; - switch (responseMessage.FaultType) - { - case FaultType.EMPTY_RESPONSE: - bytes = IsFault(responseMessage) ? EmptyArray.Value : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false); - break; - - case FaultType.MALFORMED_RESPONSE_CHUNK: - bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? EmptyArray.Value; - if (IsFault(responseMessage)) - { - bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray(); - } - break; - - default: - bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false); - break; - } - - var statusCodeType = responseMessage.StatusCode?.GetType(); - if (statusCodeType != null) - { - if (statusCodeType == typeof(int) || statusCodeType == typeof(int?) || statusCodeType.GetTypeInfo().IsEnum) - { - response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!); - } - else if (statusCodeType == typeof(string)) - { - // Note: this case will also match on null - int.TryParse(responseMessage.StatusCode as string, out var statusCodeTypeAsInt); - response.StatusCode = MapStatusCode(statusCodeTypeAsInt); - } - } - - SetResponseHeaders(responseMessage, bytes != null, response); - - if (bytes != null) - { - try - { - await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - } - catch (Exception ex) - { - _options.Logger.Warn("Error writing response body. Exception : {0}", ex); - } - } - - SetResponseTrailingHeaders(responseMessage, response); - } - - private static async Task HandleSseStringAsync(IResponseMessage responseMessage, IResponse response, IBodyData bodyData) - { - if (bodyData.SseStringQueue == null) - { - return; - } - - SetResponseHeaders(responseMessage, true, response); - - string? text; - do - { - if (bodyData.SseStringQueue.TryRead(out text)) - { - await response.WriteAsync(text); - await response.Body.FlushAsync(); - } - } while (text != null); - } - - private int MapStatusCode(int code) - { - if (_options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code)) - { - return (int)HttpStatusCode.OK; - } - - return code; - } - - private bool IsFault(IResponseMessage responseMessage) - { - return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage; - } - - private async Task GetNormalBodyAsync(IResponseMessage responseMessage) - { - var bodyData = responseMessage.BodyData; - switch (bodyData?.GetDetectedBodyType()) - { - case BodyType.String: - case BodyType.FormUrlEncoded: - return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!); - - case BodyType.Json: - var formatting = bodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None; - var jsonBody = JsonConvert.SerializeObject(bodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore }); - return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody); - -#if PROTOBUF - case BodyType.ProtoBuf: - var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts; - return await ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false); -#endif - - case BodyType.Bytes: - return bodyData.BodyAsBytes; - - case BodyType.File: - return _options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!); - - case BodyType.MultiPart: - _options.Logger.Warn("MultiPart body type is not handled!"); - break; - - case BodyType.None: - break; - } - - return null; - } - - private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, IResponse response) - { - // Force setting the Date header (#577) - AppendResponseHeader( - response, - HttpKnownHeaderNames.Date, - [DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)] - ); - - // Set other headers - foreach (var item in responseMessage.Headers!) - { - var headerName = item.Key; - var value = item.Value; - if (ResponseHeadersToFix.TryGetValue(headerName, out var action)) - { - action?.Invoke(response, hasBody, value); - } - else - { - // Check if this response header can be added (#148, #227 and #720) - if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName)) - { - AppendResponseHeader(response, headerName, value.ToArray()); - } - } - } - } - - private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, IResponse response) - { - if (responseMessage.TrailingHeaders == null) - { - return; - } - -#if TRAILINGHEADERS - foreach (var item in responseMessage.TrailingHeaders) - { - var headerName = item.Key; - var value = item.Value; - if (ResponseHeadersToFix.TryGetValue(headerName, out var action)) - { - action?.Invoke(response, false, value); - } - else - { - // Check if this trailing header can be added to the response - if (response.SupportsTrailers() && !HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName)) - { - response.AppendTrailer(headerName, new Microsoft.Extensions.Primitives.StringValues(value.ToArray())); - } - } - } -#endif - } - - private static void AppendResponseHeader(IResponse response, string headerName, string[] values) - { -#if !USE_ASPNETCORE - response.Headers.AppendValues(headerName, values); -#else - response.Headers.Append(headerName, values); -#endif - } - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using RandomDataGenerator.FieldOptions; +using RandomDataGenerator.Randomizers; +using WireMock.Http; +using WireMock.ResponseBuilders; +using WireMock.Types; +using Stef.Validation; +using WireMock.Util; + +#if !USE_ASPNETCORE +using IResponse = Microsoft.Owin.IOwinResponse; +#else +using Microsoft.AspNetCore.Http; +using IResponse = Microsoft.AspNetCore.Http.HttpResponse; +#endif + +namespace WireMock.Owin.Mappers +{ + /// + /// OwinResponseMapper + /// + internal class OwinResponseMapper : IOwinResponseMapper + { + private readonly IRandomizerNumber _randomizerDouble = RandomizerFactory.GetRandomizer(new FieldOptionsDouble { Min = 0, Max = 1 }); + private readonly IRandomizerBytes _randomizerBytes = RandomizerFactory.GetRandomizer(new FieldOptionsBytes { Min = 100, Max = 200 }); + private readonly IWireMockMiddlewareOptions _options; + private readonly Encoding _utf8NoBom = new UTF8Encoding(false); + + // https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx + private static readonly IDictionary>> ResponseHeadersToFix = + new Dictionary>>(StringComparer.OrdinalIgnoreCase) + { + { HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() }, + { HttpKnownHeaderNames.ContentLength, (r, hasBody, v) => + { + // Only set the Content-Length header if the response does not have a body + if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength)) + { + r.ContentLength = contentLength; + } + } + } + }; + + /// + /// Constructor + /// + /// The IWireMockMiddlewareOptions. + public OwinResponseMapper(IWireMockMiddlewareOptions options) + { + _options = Guard.NotNull(options); + } + + /// + public async Task MapAsync(IResponseMessage? responseMessage, IResponse response) + { + if (responseMessage == null) + { + return; + } + + var bodyData = responseMessage.BodyData; + if (bodyData?.GetDetectedBodyType() == BodyType.SseString) + { + await HandleSseStringAsync(responseMessage, response, bodyData); + return; + } + + byte[]? bytes; + switch (responseMessage.FaultType) + { + case FaultType.EMPTY_RESPONSE: + bytes = IsFault(responseMessage) ? [] : await GetNormalBodyAsync(responseMessage).ConfigureAwait(false); + break; + + case FaultType.MALFORMED_RESPONSE_CHUNK: + bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false) ?? []; + if (IsFault(responseMessage)) + { + bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray(); + } + break; + + default: + bytes = await GetNormalBodyAsync(responseMessage).ConfigureAwait(false); + break; + } + + var statusCodeType = responseMessage.StatusCode?.GetType(); + if (statusCodeType != null) + { + if (statusCodeType == typeof(int) || statusCodeType == typeof(int?) || statusCodeType.GetTypeInfo().IsEnum) + { + response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!); + } + else if (statusCodeType == typeof(string)) + { + // Note: this case will also match on null + int.TryParse(responseMessage.StatusCode as string, out var statusCodeTypeAsInt); + response.StatusCode = MapStatusCode(statusCodeTypeAsInt); + } + } + + SetResponseHeaders(responseMessage, bytes != null, response); + + if (bytes != null) + { + try + { + await response.Body.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } + catch (Exception ex) + { + _options.Logger.Warn("Error writing response body. Exception : {0}", ex); + } + } + + SetResponseTrailingHeaders(responseMessage, response); + } + + private static async Task HandleSseStringAsync(IResponseMessage responseMessage, IResponse response, IBodyData bodyData) + { + if (bodyData.SseStringQueue == null) + { + return; + } + + SetResponseHeaders(responseMessage, true, response); + + string? text; + do + { + if (bodyData.SseStringQueue.TryRead(out text)) + { + await response.WriteAsync(text); + await response.Body.FlushAsync(); + } + } while (text != null); + } + + private int MapStatusCode(int code) + { + if (_options.AllowOnlyDefinedHttpStatusCodeInResponse == true && !Enum.IsDefined(typeof(HttpStatusCode), code)) + { + return (int)HttpStatusCode.OK; + } + + return code; + } + + private bool IsFault(IResponseMessage responseMessage) + { + return responseMessage.FaultPercentage == null || _randomizerDouble.Generate() <= responseMessage.FaultPercentage; + } + + private async Task GetNormalBodyAsync(IResponseMessage responseMessage) + { + var bodyData = responseMessage.BodyData; + switch (bodyData?.GetDetectedBodyType()) + { + case BodyType.String: + case BodyType.FormUrlEncoded: + return (bodyData.Encoding ?? _utf8NoBom).GetBytes(bodyData.BodyAsString!); + + case BodyType.Json: + var formatting = bodyData.BodyAsJsonIndented == true ? Formatting.Indented : Formatting.None; + var jsonBody = JsonConvert.SerializeObject(bodyData.BodyAsJson, new JsonSerializerSettings { Formatting = formatting, NullValueHandling = NullValueHandling.Ignore }); + return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody); + +#if PROTOBUF + case BodyType.ProtoBuf: + var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts; + return await ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false); +#endif + + case BodyType.Bytes: + return bodyData.BodyAsBytes; + + case BodyType.File: + return _options.FileSystemHandler?.ReadResponseBodyAsFile(bodyData.BodyAsFile!); + + case BodyType.MultiPart: + _options.Logger.Warn("MultiPart body type is not handled!"); + break; + + case BodyType.None: + break; + } + + return null; + } + + private static void SetResponseHeaders(IResponseMessage responseMessage, bool hasBody, IResponse response) + { + // Force setting the Date header (#577) + AppendResponseHeader( + response, + HttpKnownHeaderNames.Date, + [DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.RFC1123Pattern, CultureInfo.InvariantCulture)] + ); + + // Set other headers + foreach (var item in responseMessage.Headers!) + { + var headerName = item.Key; + var value = item.Value; + if (ResponseHeadersToFix.TryGetValue(headerName, out var action)) + { + action?.Invoke(response, hasBody, value); + } + else + { + // Check if this response header can be added (#148, #227 and #720) + if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName)) + { + AppendResponseHeader(response, headerName, value.ToArray()); + } + } + } + } + + private static void SetResponseTrailingHeaders(IResponseMessage responseMessage, IResponse response) + { + if (responseMessage.TrailingHeaders == null) + { + return; + } + +#if TRAILINGHEADERS + foreach (var item in responseMessage.TrailingHeaders) + { + var headerName = item.Key; + var value = item.Value; + if (ResponseHeadersToFix.TryGetValue(headerName, out var action)) + { + action?.Invoke(response, false, value); + } + else + { + // Check if this trailing header can be added to the response + if (response.SupportsTrailers() && !HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName)) + { + response.AppendTrailer(headerName, new Microsoft.Extensions.Primitives.StringValues(value.ToArray())); + } + } + } +#endif + } + + private static void AppendResponseHeader(IResponse response, string headerName, string[] values) + { +#if !USE_ASPNETCORE + response.Headers.AppendValues(headerName, values); +#else + response.Headers.Append(headerName, values); +#endif + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/MappingMatcher.cs b/src/WireMock.Net.Minimal/Owin/MappingMatcher.cs similarity index 100% rename from src/WireMock.Net/Owin/MappingMatcher.cs rename to src/WireMock.Net.Minimal/Owin/MappingMatcher.cs diff --git a/src/WireMock.Net/Owin/MappingMatcherResult.cs b/src/WireMock.Net.Minimal/Owin/MappingMatcherResult.cs similarity index 100% rename from src/WireMock.Net/Owin/MappingMatcherResult.cs rename to src/WireMock.Net.Minimal/Owin/MappingMatcherResult.cs diff --git a/src/WireMock.Net/Owin/OwinSelfHost.cs b/src/WireMock.Net.Minimal/Owin/OwinSelfHost.cs similarity index 100% rename from src/WireMock.Net/Owin/OwinSelfHost.cs rename to src/WireMock.Net.Minimal/Owin/OwinSelfHost.cs diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs similarity index 97% rename from src/WireMock.Net/Owin/WireMockMiddleware.cs rename to src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs index 9788caf4..5832eded 100644 --- a/src/WireMock.Net/Owin/WireMockMiddleware.cs +++ b/src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs @@ -1,368 +1,368 @@ -// Copyright © WireMock.Net - -using System; -using System.Threading.Tasks; -using System.Linq; -using System.Net; -using Stef.Validation; -using WireMock.Logging; -using WireMock.Matchers; -using WireMock.Http; -using WireMock.Owin.Mappers; -using WireMock.Serialization; -using WireMock.ResponseBuilders; -using WireMock.Settings; -using System.Collections.Generic; -using WireMock.Constants; -using WireMock.Exceptions; -using WireMock.Util; -#if !USE_ASPNETCORE -using IContext = Microsoft.Owin.IOwinContext; -using OwinMiddleware = Microsoft.Owin.OwinMiddleware; -using Next = Microsoft.Owin.OwinMiddleware; -#else -using OwinMiddleware = System.Object; -using IContext = Microsoft.AspNetCore.Http.HttpContext; -using Next = Microsoft.AspNetCore.Http.RequestDelegate; -#endif - -namespace WireMock.Owin -{ - internal class WireMockMiddleware : OwinMiddleware - { - private readonly object _lock = new(); - private static readonly Task CompletedTask = Task.FromResult(false); - - private readonly IWireMockMiddlewareOptions _options; - private readonly IOwinRequestMapper _requestMapper; - private readonly IOwinResponseMapper _responseMapper; - private readonly IMappingMatcher _mappingMatcher; - private readonly LogEntryMapper _logEntryMapper; - private readonly IGuidUtils _guidUtils; - -#if !USE_ASPNETCORE - public WireMockMiddleware( - Next next, - IWireMockMiddlewareOptions options, - IOwinRequestMapper requestMapper, - IOwinResponseMapper responseMapper, - IMappingMatcher mappingMatcher, - IGuidUtils guidUtils - ) : base(next) - { - _options = Guard.NotNull(options); - _requestMapper = Guard.NotNull(requestMapper); - _responseMapper = Guard.NotNull(responseMapper); - _mappingMatcher = Guard.NotNull(mappingMatcher); - _logEntryMapper = new LogEntryMapper(options); - _guidUtils = Guard.NotNull(guidUtils); - } -#else - public WireMockMiddleware( - Next next, - IWireMockMiddlewareOptions options, - IOwinRequestMapper requestMapper, - IOwinResponseMapper responseMapper, - IMappingMatcher mappingMatcher, - IGuidUtils guidUtils - ) - { - _options = Guard.NotNull(options); - _requestMapper = Guard.NotNull(requestMapper); - _responseMapper = Guard.NotNull(responseMapper); - _mappingMatcher = Guard.NotNull(mappingMatcher); - _logEntryMapper = new LogEntryMapper(options); - _guidUtils = Guard.NotNull(guidUtils); - } -#endif - -#if !USE_ASPNETCORE - public override Task Invoke(IContext ctx) -#else - public Task Invoke(IContext ctx) -#endif - { - if (_options.HandleRequestsSynchronously.GetValueOrDefault(false)) - { - lock (_lock) - { - return InvokeInternalAsync(ctx); - } - } - - return InvokeInternalAsync(ctx); - } - - private async Task InvokeInternalAsync(IContext ctx) - { - var request = await _requestMapper.MapAsync(ctx.Request, _options).ConfigureAwait(false); - - var logRequest = false; - IResponseMessage? response = null; - (MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null); - - try - { - foreach (var mapping in _options.Mappings.Values) - { - if (mapping.Scenario is null) - { - continue; - } - - // Set scenario start - if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState) - { - _options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState - { - Name = mapping.Scenario - }); - } - } - - result = _mappingMatcher.FindBestMatch(request); - - var targetMapping = result.Match?.Mapping; - if (targetMapping == null) - { - logRequest = true; - _options.Logger.Warn("HttpStatusCode set to 404 : No matching mapping found"); - response = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound); - return; - } - - logRequest = targetMapping.LogMapping; - - if (targetMapping.IsAdminInterface && _options.AuthenticationMatcher != null && request.Headers != null) - { - var authorizationHeaderPresent = request.Headers.TryGetValue(HttpKnownHeaderNames.Authorization, out var authorization); - if (!authorizationHeaderPresent) - { - _options.Logger.Error("HttpStatusCode set to 401, authorization header is missing."); - response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null); - return; - } - - var authorizationHeaderMatchResult = _options.AuthenticationMatcher.IsMatch(authorization!.ToString()); - if (!MatchScores.IsPerfect(authorizationHeaderMatchResult.Score)) - { - _options.Logger.Error("HttpStatusCode set to 401, authentication failed.", authorizationHeaderMatchResult.Exception ?? throw new WireMockException("Authentication failed")); - response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null); - return; - } - } - - if (!targetMapping.IsAdminInterface && _options.RequestProcessingDelay > TimeSpan.Zero) - { - await Task.Delay(_options.RequestProcessingDelay.Value).ConfigureAwait(false); - } - - var (theResponse, theOptionalNewMapping) = await targetMapping.ProvideResponseAsync(request).ConfigureAwait(false); - response = theResponse; - - var responseBuilder = targetMapping.Provider as Response; - - if (!targetMapping.IsAdminInterface && theOptionalNewMapping != null) - { - if (responseBuilder?.ProxyAndRecordSettings?.SaveMapping == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMapping == true) - { - _options.Mappings.TryAdd(theOptionalNewMapping.Guid, theOptionalNewMapping); - } - - if (responseBuilder?.ProxyAndRecordSettings?.SaveMappingToFile == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMappingToFile == true) - { - var matcherMapper = new MatcherMapper(targetMapping.Settings); - var mappingConverter = new MappingConverter(matcherMapper); - var mappingToFileSaver = new MappingToFileSaver(targetMapping.Settings, mappingConverter); - - mappingToFileSaver.SaveMappingToFile(theOptionalNewMapping); - } - } - - if (targetMapping.Scenario != null) - { - UpdateScenarioState(targetMapping); - } - - if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0) - { - await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false); - } - } - catch (Exception ex) - { - _options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}"); - response = ResponseMessageBuilder.Create(500, ex.Message); - } - finally - { - var log = new LogEntry - { - Guid = _guidUtils.NewGuid(), - RequestMessage = request, - ResponseMessage = response, - - MappingGuid = result.Match?.Mapping?.Guid, - MappingTitle = result.Match?.Mapping?.Title, - RequestMatchResult = result.Match?.RequestMatchResult, - - PartialMappingGuid = result.Partial?.Mapping?.Guid, - PartialMappingTitle = result.Partial?.Mapping?.Title, - PartialMatchResult = result.Partial?.RequestMatchResult - }; - - LogRequest(log, logRequest); - - try - { - if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult is not { IsPerfectMatch: true }) - { - var filename = $"{log.Guid}.LogEntry.json"; - _options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(log)); - } - } - catch - { - // Empty catch - } - - try - { - await _responseMapper.MapAsync(response, ctx.Response).ConfigureAwait(false); - } - catch (Exception ex) - { - _options.Logger.Error("HttpStatusCode set to 404 : No matching mapping found", ex); - - var notFoundResponse = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound); - await _responseMapper.MapAsync(notFoundResponse, ctx.Response).ConfigureAwait(false); - } - } - - await CompletedTask.ConfigureAwait(false); - } - - private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response) - { - var tasks = new List>(); - for (int index = 0; index < mapping.Webhooks?.Length; index++) - { - var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings()); - var webhookSender = new WebhookSender(mapping.Settings); - var webhookRequest = mapping.Webhooks[index].Request; - var webHookIndex = index; - - tasks.Add(async () => - { - try - { - var result = await webhookSender.SendAsync(httpClientForWebhook, mapping, webhookRequest, request, response).ConfigureAwait(false); - if (!result.IsSuccessStatusCode) - { - var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false); - _options.Logger.Warn($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. HttpStatusCode: {result.StatusCode} Content: {content}"); - } - } - catch (Exception ex) - { - _options.Logger.Error($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. Exception: {ex}"); - } - }); - } - - if (mapping.UseWebhooksFireAndForget == true) - { - try - { - // Do not wait - await Task.Run(() => - { - Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false); - }); - } - catch - { - // Ignore - } - } - else - { - await Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false); - } - } - - private void UpdateScenarioState(IMapping mapping) - { - var scenario = _options.Scenarios[mapping.Scenario!]; - - // Increase the number of times this state has been executed - scenario.Counter++; - - // Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0 - if (scenario.Counter == (mapping.StateTimes ?? 1)) - { - scenario.NextState = mapping.NextState; - scenario.Counter = 0; - } - - // Else just update Started and Finished - scenario.Started = true; - scenario.Finished = mapping.NextState == null; - } - - private void LogRequest(LogEntry entry, bool addRequest) - { - _options.Logger.DebugRequestResponse(_logEntryMapper.Map(entry), entry.RequestMessage.Path.StartsWith("/__admin/")); - - // 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) - { - 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) - { - var logEntries = _options.LogEntries.ToList(); - foreach (var logEntry in logEntries.OrderBy(le => le.RequestMessage.DateTime).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) - { - var checkTime = DateTime.UtcNow.AddHours(-_options.RequestLogExpirationDuration.Value); - foreach (var logEntry in _options.LogEntries.ToList().Where(le => le.RequestMessage.DateTime < checkTime)) - { - TryRemoveLogEntry(logEntry); - } - } - } - - private void TryAddLogEntry(LogEntry logEntry) - { - try - { - _options.LogEntries.Add(logEntry); - } - catch - { - // Ignore exception (can happen during stress testing) - } - } - - private void TryRemoveLogEntry(LogEntry logEntry) - { - try - { - _options.LogEntries.Remove(logEntry); - } - catch - { - // Ignore exception (can happen during stress testing) - } - } - } -} +// Copyright © WireMock.Net + +using System; +using System.Threading.Tasks; +using System.Linq; +using System.Net; +using Stef.Validation; +using WireMock.Logging; +using WireMock.Matchers; +using WireMock.Http; +using WireMock.Owin.Mappers; +using WireMock.Serialization; +using WireMock.ResponseBuilders; +using WireMock.Settings; +using System.Collections.Generic; +using WireMock.Constants; +using WireMock.Exceptions; +using WireMock.Util; +#if !USE_ASPNETCORE +using IContext = Microsoft.Owin.IOwinContext; +using OwinMiddleware = Microsoft.Owin.OwinMiddleware; +using Next = Microsoft.Owin.OwinMiddleware; +#else +using OwinMiddleware = System.Object; +using IContext = Microsoft.AspNetCore.Http.HttpContext; +using Next = Microsoft.AspNetCore.Http.RequestDelegate; +#endif + +namespace WireMock.Owin +{ + internal class WireMockMiddleware : OwinMiddleware + { + private readonly object _lock = new(); + private static readonly Task CompletedTask = Task.FromResult(false); + + private readonly IWireMockMiddlewareOptions _options; + private readonly IOwinRequestMapper _requestMapper; + private readonly IOwinResponseMapper _responseMapper; + private readonly IMappingMatcher _mappingMatcher; + private readonly LogEntryMapper _logEntryMapper; + private readonly IGuidUtils _guidUtils; + +#if !USE_ASPNETCORE + public WireMockMiddleware( + Next next, + IWireMockMiddlewareOptions options, + IOwinRequestMapper requestMapper, + IOwinResponseMapper responseMapper, + IMappingMatcher mappingMatcher, + IGuidUtils guidUtils + ) : base(next) + { + _options = Guard.NotNull(options); + _requestMapper = Guard.NotNull(requestMapper); + _responseMapper = Guard.NotNull(responseMapper); + _mappingMatcher = Guard.NotNull(mappingMatcher); + _logEntryMapper = new LogEntryMapper(options); + _guidUtils = Guard.NotNull(guidUtils); + } +#else + public WireMockMiddleware( + Next next, + IWireMockMiddlewareOptions options, + IOwinRequestMapper requestMapper, + IOwinResponseMapper responseMapper, + IMappingMatcher mappingMatcher, + IGuidUtils guidUtils + ) + { + _options = Guard.NotNull(options); + _requestMapper = Guard.NotNull(requestMapper); + _responseMapper = Guard.NotNull(responseMapper); + _mappingMatcher = Guard.NotNull(mappingMatcher); + _logEntryMapper = new LogEntryMapper(options); + _guidUtils = Guard.NotNull(guidUtils); + } +#endif + +#if !USE_ASPNETCORE + public override Task Invoke(IContext ctx) +#else + public Task Invoke(IContext ctx) +#endif + { + if (_options.HandleRequestsSynchronously.GetValueOrDefault(false)) + { + lock (_lock) + { + return InvokeInternalAsync(ctx); + } + } + + return InvokeInternalAsync(ctx); + } + + private async Task InvokeInternalAsync(IContext ctx) + { + var request = await _requestMapper.MapAsync(ctx.Request, _options).ConfigureAwait(false); + + var logRequest = false; + IResponseMessage? response = null; + (MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null); + + try + { + foreach (var mapping in _options.Mappings.Values) + { + if (mapping.Scenario is null) + { + continue; + } + + // Set scenario start + if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState) + { + _options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState + { + Name = mapping.Scenario + }); + } + } + + result = _mappingMatcher.FindBestMatch(request); + + var targetMapping = result.Match?.Mapping; + if (targetMapping == null) + { + logRequest = true; + _options.Logger.Warn("HttpStatusCode set to 404 : No matching mapping found"); + response = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound); + return; + } + + logRequest = targetMapping.LogMapping; + + if (targetMapping.IsAdminInterface && _options.AuthenticationMatcher != null && request.Headers != null) + { + var authorizationHeaderPresent = request.Headers.TryGetValue(HttpKnownHeaderNames.Authorization, out var authorization); + if (!authorizationHeaderPresent) + { + _options.Logger.Error("HttpStatusCode set to 401, authorization header is missing."); + response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null); + return; + } + + var authorizationHeaderMatchResult = _options.AuthenticationMatcher.IsMatch(authorization!.ToString()); + if (!MatchScores.IsPerfect(authorizationHeaderMatchResult.Score)) + { + _options.Logger.Error("HttpStatusCode set to 401, authentication failed.", authorizationHeaderMatchResult.Exception ?? throw new WireMockException("Authentication failed")); + response = ResponseMessageBuilder.Create(HttpStatusCode.Unauthorized, null); + return; + } + } + + if (!targetMapping.IsAdminInterface && _options.RequestProcessingDelay > TimeSpan.Zero) + { + await Task.Delay(_options.RequestProcessingDelay.Value).ConfigureAwait(false); + } + + var (theResponse, theOptionalNewMapping) = await targetMapping.ProvideResponseAsync(request).ConfigureAwait(false); + response = theResponse; + + var responseBuilder = targetMapping.Provider as Response; + + if (!targetMapping.IsAdminInterface && theOptionalNewMapping != null) + { + if (responseBuilder?.ProxyAndRecordSettings?.SaveMapping == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMapping == true) + { + _options.Mappings.TryAdd(theOptionalNewMapping.Guid, theOptionalNewMapping); + } + + if (responseBuilder?.ProxyAndRecordSettings?.SaveMappingToFile == true || targetMapping.Settings.ProxyAndRecordSettings?.SaveMappingToFile == true) + { + var matcherMapper = new MatcherMapper(targetMapping.Settings); + var mappingConverter = new MappingConverter(matcherMapper); + var mappingToFileSaver = new MappingToFileSaver(targetMapping.Settings, mappingConverter); + + mappingToFileSaver.SaveMappingToFile(theOptionalNewMapping); + } + } + + if (targetMapping.Scenario != null) + { + UpdateScenarioState(targetMapping); + } + + if (!targetMapping.IsAdminInterface && targetMapping.Webhooks?.Length > 0) + { + await SendToWebhooksAsync(targetMapping, request, response).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}"); + response = ResponseMessageBuilder.Create(500, ex.Message); + } + finally + { + var log = new LogEntry + { + Guid = _guidUtils.NewGuid(), + RequestMessage = request, + ResponseMessage = response, + + MappingGuid = result.Match?.Mapping?.Guid, + MappingTitle = result.Match?.Mapping?.Title, + RequestMatchResult = result.Match?.RequestMatchResult, + + PartialMappingGuid = result.Partial?.Mapping?.Guid, + PartialMappingTitle = result.Partial?.Mapping?.Title, + PartialMatchResult = result.Partial?.RequestMatchResult + }; + + LogRequest(log, logRequest); + + try + { + if (_options.SaveUnmatchedRequests == true && result.Match?.RequestMatchResult is not { IsPerfectMatch: true }) + { + var filename = $"{log.Guid}.LogEntry.json"; + _options.FileSystemHandler?.WriteUnmatchedRequest(filename, JsonUtils.Serialize(log)); + } + } + catch + { + // Empty catch + } + + try + { + await _responseMapper.MapAsync(response, ctx.Response).ConfigureAwait(false); + } + catch (Exception ex) + { + _options.Logger.Error("HttpStatusCode set to 404 : No matching mapping found", ex); + + var notFoundResponse = ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound); + await _responseMapper.MapAsync(notFoundResponse, ctx.Response).ConfigureAwait(false); + } + } + + await CompletedTask.ConfigureAwait(false); + } + + private async Task SendToWebhooksAsync(IMapping mapping, IRequestMessage request, IResponseMessage response) + { + var tasks = new List>(); + for (int index = 0; index < mapping.Webhooks?.Length; index++) + { + var httpClientForWebhook = HttpClientBuilder.Build(mapping.Settings.WebhookSettings ?? new WebhookSettings()); + var webhookSender = new WebhookSender(mapping.Settings); + var webhookRequest = mapping.Webhooks[index].Request; + var webHookIndex = index; + + tasks.Add(async () => + { + try + { + var result = await webhookSender.SendAsync(httpClientForWebhook, mapping, webhookRequest, request, response).ConfigureAwait(false); + if (!result.IsSuccessStatusCode) + { + var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false); + _options.Logger.Warn($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. HttpStatusCode: {result.StatusCode} Content: {content}"); + } + } + catch (Exception ex) + { + _options.Logger.Error($"Sending message to Webhook [{webHookIndex}] from Mapping '{mapping.Guid}' failed. Exception: {ex}"); + } + }); + } + + if (mapping.UseWebhooksFireAndForget == true) + { + try + { + // Do not wait + await Task.Run(() => + { + Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false); + }); + } + catch + { + // Ignore + } + } + else + { + await Task.WhenAll(tasks.Select(async task => await task.Invoke())).ConfigureAwait(false); + } + } + + private void UpdateScenarioState(IMapping mapping) + { + var scenario = _options.Scenarios[mapping.Scenario!]; + + // Increase the number of times this state has been executed + scenario.Counter++; + + // Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0 + if (scenario.Counter == (mapping.StateTimes ?? 1)) + { + scenario.NextState = mapping.NextState; + scenario.Counter = 0; + } + + // Else just update Started and Finished + scenario.Started = true; + scenario.Finished = mapping.NextState == null; + } + + private void LogRequest(LogEntry entry, bool addRequest) + { + _options.Logger.DebugRequestResponse(_logEntryMapper.Map(entry), entry.RequestMessage.Path.StartsWith("/__admin/")); + + // 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) + { + 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) + { + var logEntries = _options.LogEntries.ToList(); + foreach (var logEntry in logEntries.OrderBy(le => le.RequestMessage.DateTime).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) + { + var checkTime = DateTime.UtcNow.AddHours(-_options.RequestLogExpirationDuration.Value); + foreach (var logEntry in _options.LogEntries.ToList().Where(le => le.RequestMessage.DateTime < checkTime)) + { + TryRemoveLogEntry(logEntry); + } + } + } + + private void TryAddLogEntry(LogEntry logEntry) + { + try + { + _options.LogEntries.Add(logEntry); + } + catch + { + // Ignore exception (can happen during stress testing) + } + } + + private void TryRemoveLogEntry(LogEntry logEntry) + { + try + { + _options.LogEntries.Remove(logEntry); + } + catch + { + // Ignore exception (can happen during stress testing) + } + } + } +} diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs b/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareOptions.cs similarity index 100% rename from src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs rename to src/WireMock.Net.Minimal/Owin/WireMockMiddlewareOptions.cs diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptionsHelper.cs b/src/WireMock.Net.Minimal/Owin/WireMockMiddlewareOptionsHelper.cs similarity index 100% rename from src/WireMock.Net/Owin/WireMockMiddlewareOptionsHelper.cs rename to src/WireMock.Net.Minimal/Owin/WireMockMiddlewareOptionsHelper.cs diff --git a/src/WireMock.Net/Pact/Models/V2/Interaction.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/Interaction.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/Interaction.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/Interaction.cs diff --git a/src/WireMock.Net/Pact/Models/V2/MatchingRule.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/MatchingRule.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/MatchingRule.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/MatchingRule.cs diff --git a/src/WireMock.Net/Pact/Models/V2/Metadata.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/Metadata.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/Metadata.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/Metadata.cs diff --git a/src/WireMock.Net/Pact/Models/V2/Pact.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/Pact.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/Pact.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/Pact.cs diff --git a/src/WireMock.Net/Pact/Models/V2/PactRequest.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/PactRequest.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/PactRequest.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/PactRequest.cs diff --git a/src/WireMock.Net/Pact/Models/V2/PactResponse.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/PactResponse.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/PactResponse.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/PactResponse.cs diff --git a/src/WireMock.Net/Pact/Models/V2/PactRust.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/PactRust.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/PactRust.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/PactRust.cs diff --git a/src/WireMock.Net/Pact/Models/V2/PactSpecification.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/PactSpecification.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/PactSpecification.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/PactSpecification.cs diff --git a/src/WireMock.Net/Pact/Models/V2/Pacticipant.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/Pacticipant.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/Pacticipant.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/Pacticipant.cs diff --git a/src/WireMock.Net/Pact/Models/V2/ProviderState.cs b/src/WireMock.Net.Minimal/Pact/Models/V2/ProviderState.cs similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/ProviderState.cs rename to src/WireMock.Net.Minimal/Pact/Models/V2/ProviderState.cs diff --git a/src/WireMock.Net/Pact/Models/V2/_v2.json b/src/WireMock.Net.Minimal/Pact/Models/V2/_v2.json similarity index 100% rename from src/WireMock.Net/Pact/Models/V2/_v2.json rename to src/WireMock.Net.Minimal/Pact/Models/V2/_v2.json diff --git a/src/WireMock.Net/Properties/AssemblyInfo.cs b/src/WireMock.Net.Minimal/Properties/AssemblyInfo.cs similarity index 75% rename from src/WireMock.Net/Properties/AssemblyInfo.cs rename to src/WireMock.Net.Minimal/Properties/AssemblyInfo.cs index 9779fef7..ab3957c2 100644 --- a/src/WireMock.Net/Properties/AssemblyInfo.cs +++ b/src/WireMock.Net.Minimal/Properties/AssemblyInfo.cs @@ -1,10 +1,10 @@ -// Copyright © WireMock.Net - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] -[assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] -[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] - -// Needed for Moq in the UnitTest project +// Copyright © WireMock.Net + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] +// [assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] +[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] + +// Needed for Moq in the UnitTest project [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/src/WireMock.Net/Proxy/ProxyHelper.cs b/src/WireMock.Net.Minimal/Proxy/ProxyHelper.cs similarity index 97% rename from src/WireMock.Net/Proxy/ProxyHelper.cs rename to src/WireMock.Net.Minimal/Proxy/ProxyHelper.cs index 78360a01..51dcadc5 100644 --- a/src/WireMock.Net/Proxy/ProxyHelper.cs +++ b/src/WireMock.Net.Minimal/Proxy/ProxyHelper.cs @@ -1,103 +1,103 @@ -// Copyright © WireMock.Net - -using System; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Stef.Validation; -using WireMock.Http; -using WireMock.Matchers; -using WireMock.Serialization; -using WireMock.Settings; -using WireMock.Util; - -namespace WireMock.Proxy; - -internal class ProxyHelper -{ - private readonly WireMockServerSettings _settings; - private readonly ProxyMappingConverter _proxyMappingConverter; - - public ProxyHelper(WireMockServerSettings settings) - { - _settings = Guard.NotNull(settings); - _proxyMappingConverter = new ProxyMappingConverter(settings, new GuidUtils(), new DateTimeUtils()); - } - - public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync( - IMapping? mapping, - ProxyAndRecordSettings proxyAndRecordSettings, - HttpClient client, - IRequestMessage requestMessage, - string url) - { - Guard.NotNull(client); - Guard.NotNull(requestMessage); - Guard.NotNull(url); - - var originalUri = new Uri(requestMessage.Url); - var requiredUri = new Uri(url); - - // Create HttpRequestMessage - var replaceSettings = proxyAndRecordSettings.ReplaceSettings; - string proxyUrl; - if (replaceSettings is not null) - { - var stringComparison = replaceSettings.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - proxyUrl = url.Replace(replaceSettings.OldValue, replaceSettings.NewValue, stringComparison); - } - else - { - proxyUrl = url; - } - - var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, proxyUrl); - - // Call the URL - var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false); - - // Create ResponseMessage - bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false); - bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.GetValueOrDefault(false); - bool deserializeFormUrlEncoded = !_settings.DisableDeserializeFormUrlEncoded.GetValueOrDefault(false); - - var responseMessage = await HttpResponseMessageHelper.CreateAsync( - httpResponseMessage, - requiredUri, - originalUri, - deserializeJson, - decompressGzipAndDeflate, - deserializeFormUrlEncoded - ).ConfigureAwait(false); - - IMapping? newMapping = null; - - var saveMappingSettings = proxyAndRecordSettings.SaveMappingSettings; - - bool save = true; - if (saveMappingSettings != null) - { - save &= Check(saveMappingSettings.StatusCodePattern, - () => saveMappingSettings.StatusCodePattern != null && HttpStatusRangeParser.IsMatch(saveMappingSettings.StatusCodePattern, responseMessage.StatusCode) - ); - - save &= Check(saveMappingSettings.HttpMethods, - () => saveMappingSettings.HttpMethods != null && saveMappingSettings.HttpMethods.Value.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase) - ); - } - - if (save && (proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile)) - { - newMapping = _proxyMappingConverter.ToMapping(mapping, proxyAndRecordSettings, requestMessage, responseMessage); - } - - return (responseMessage, newMapping); - } - - private static bool Check(ProxySaveMappingSetting? saveMappingSetting, Func action) where T : notnull - { - var isMatch = saveMappingSetting is null || action(); - var matchBehaviour = saveMappingSetting?.MatchBehaviour ?? MatchBehaviour.AcceptOnMatch; - return isMatch == (matchBehaviour == MatchBehaviour.AcceptOnMatch); - } +// Copyright © WireMock.Net + +using System; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Stef.Validation; +using WireMock.Http; +using WireMock.Matchers; +using WireMock.Serialization; +using WireMock.Settings; +using WireMock.Util; + +namespace WireMock.Proxy; + +internal class ProxyHelper +{ + private readonly WireMockServerSettings _settings; + private readonly ProxyMappingConverter _proxyMappingConverter; + + public ProxyHelper(WireMockServerSettings settings) + { + _settings = Guard.NotNull(settings); + _proxyMappingConverter = new ProxyMappingConverter(settings, new GuidUtils(), new DateTimeUtils()); + } + + public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync( + IMapping? mapping, + ProxyAndRecordSettings proxyAndRecordSettings, + HttpClient client, + IRequestMessage requestMessage, + string url) + { + Guard.NotNull(client); + Guard.NotNull(requestMessage); + Guard.NotNull(url); + + var originalUri = new Uri(requestMessage.Url); + var requiredUri = new Uri(url); + + // Create HttpRequestMessage + var replaceSettings = proxyAndRecordSettings.ReplaceSettings; + string proxyUrl; + if (replaceSettings is not null) + { + var stringComparison = replaceSettings.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + proxyUrl = url.Replace(replaceSettings.OldValue, replaceSettings.NewValue, stringComparison); + } + else + { + proxyUrl = url; + } + + var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, proxyUrl); + + // Call the URL + var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false); + + // Create ResponseMessage + bool deserializeJson = !_settings.DisableJsonBodyParsing.GetValueOrDefault(false); + bool decompressGzipAndDeflate = !_settings.DisableRequestBodyDecompressing.GetValueOrDefault(false); + bool deserializeFormUrlEncoded = !_settings.DisableDeserializeFormUrlEncoded.GetValueOrDefault(false); + + var responseMessage = await HttpResponseMessageHelper.CreateAsync( + httpResponseMessage, + requiredUri, + originalUri, + deserializeJson, + decompressGzipAndDeflate, + deserializeFormUrlEncoded + ).ConfigureAwait(false); + + IMapping? newMapping = null; + + var saveMappingSettings = proxyAndRecordSettings.SaveMappingSettings; + + bool save = true; + if (saveMappingSettings != null) + { + save &= Check(saveMappingSettings.StatusCodePattern, + () => saveMappingSettings.StatusCodePattern != null && HttpStatusRangeParser.IsMatch(saveMappingSettings.StatusCodePattern, responseMessage.StatusCode) + ); + + save &= Check(saveMappingSettings.HttpMethods, + () => saveMappingSettings.HttpMethods != null && saveMappingSettings.HttpMethods.Value.Contains(requestMessage.Method, StringComparer.OrdinalIgnoreCase) + ); + } + + if (save && (proxyAndRecordSettings.SaveMapping || proxyAndRecordSettings.SaveMappingToFile)) + { + newMapping = _proxyMappingConverter.ToMapping(mapping, proxyAndRecordSettings, requestMessage, responseMessage); + } + + return (responseMessage, newMapping); + } + + private static bool Check(ProxySaveMappingSetting? saveMappingSetting, Func action) where T : notnull + { + var isMatch = saveMappingSetting is null || action(); + var matchBehaviour = saveMappingSetting?.MatchBehaviour ?? MatchBehaviour.AcceptOnMatch; + return isMatch == (matchBehaviour == MatchBehaviour.AcceptOnMatch); + } } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IBodyRequestBuilder.cs similarity index 97% rename from src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IBodyRequestBuilder.cs index ac01e3ea..c935e1f2 100644 --- a/src/WireMock.Net/RequestBuilders/IBodyRequestBuilder.cs +++ b/src/WireMock.Net.Minimal/RequestBuilders/IBodyRequestBuilder.cs @@ -1,104 +1,104 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using WireMock.Matchers; -using WireMock.Util; - -namespace WireMock.RequestBuilders; - -/// -/// The BodyRequestBuilder interface. -/// -public interface IBodyRequestBuilder : IProtoBufRequestBuilder -{ - /// - /// WithBody: IMatcher - /// - /// The matcher. - /// The . - IRequestBuilder WithBody(IMatcher matcher); - - /// - /// WithBody: IMatcher[] - /// - /// The matchers. - /// The to use. - /// The . - IRequestBuilder WithBody(IMatcher[] matchers, MatchOperator matchOperator = MatchOperator.Or); - - /// - /// WithBody: Body as string - /// - /// The body. - /// The match behaviour. - /// The . - IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - - /// - /// WithBody: Body as byte[] - /// - /// The body. - /// The match behaviour. - /// The . - IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - - /// - /// WithBody: Body as object - /// - /// The body. - /// The match behaviour [default is AcceptOnMatch]. - /// The . - IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - - /// - /// WithBodyAsJson: A will be used to match this object. - /// - /// The body. - /// The match behaviour [default is AcceptOnMatch]. - /// A . - IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); - - /// - /// WithBody: func (string) - /// - /// The function. - /// The . - IRequestBuilder WithBody(Func func); - - /// - /// WithBody: func (byte[]) - /// - /// The function. - /// The . - IRequestBuilder WithBody(Func func); - - /// - /// WithBody: func (json object) - /// - /// The function. - /// The . - IRequestBuilder WithBody(Func func); - - /// - /// WithBody: func (BodyData object) - /// - /// The function. - /// The . - IRequestBuilder WithBody(Func func); - - /// - /// WithBody: Body as form-urlencoded values. - /// - /// The form-urlencoded values. - /// The . - IRequestBuilder WithBody(Func?, bool> func); - - /// - /// WithBodyAsGraphQLSchema: Body as GraphQL schema as a string. - /// - /// The GraphQL schema. - /// The match behaviour. (Default is MatchBehaviour.AcceptOnMatch). - /// The . - IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using WireMock.Matchers; +using WireMock.Util; + +namespace WireMock.RequestBuilders; + +/// +/// The BodyRequestBuilder interface. +/// +public interface IBodyRequestBuilder : IProtoBufRequestBuilder +{ + /// + /// WithBody: IMatcher + /// + /// The matcher. + /// The . + IRequestBuilder WithBody(IMatcher matcher); + + /// + /// WithBody: IMatcher[] + /// + /// The matchers. + /// The to use. + /// The . + IRequestBuilder WithBody(IMatcher[] matchers, MatchOperator matchOperator = MatchOperator.Or); + + /// + /// WithBody: Body as string + /// + /// The body. + /// The match behaviour. + /// The . + IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + + /// + /// WithBody: Body as byte[] + /// + /// The body. + /// The match behaviour. + /// The . + IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + + /// + /// WithBody: Body as object + /// + /// The body. + /// The match behaviour [default is AcceptOnMatch]. + /// The . + IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + + /// + /// WithBodyAsJson: A will be used to match this object. + /// + /// The body. + /// The match behaviour [default is AcceptOnMatch]. + /// A . + IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); + + /// + /// WithBody: func (string) + /// + /// The function. + /// The . + IRequestBuilder WithBody(Func func); + + /// + /// WithBody: func (byte[]) + /// + /// The function. + /// The . + IRequestBuilder WithBody(Func func); + + /// + /// WithBody: func (json object) + /// + /// The function. + /// The . + IRequestBuilder WithBody(Func func); + + /// + /// WithBody: func (BodyData object) + /// + /// The function. + /// The . + IRequestBuilder WithBody(Func func); + + /// + /// WithBody: Body as form-urlencoded values. + /// + /// The form-urlencoded values. + /// The . + IRequestBuilder WithBody(Func?, bool> func); + + /// + /// WithBodyAsGraphQLSchema: Body as GraphQL schema as a string. + /// + /// The GraphQL schema. + /// The match behaviour. (Default is MatchBehaviour.AcceptOnMatch). + /// The . + IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch); } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/IClientIPRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IClientIPRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IClientIPRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IClientIPRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/ICookiesRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/ICookiesRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/ICookiesRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/ICookiesRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IGraphQLRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IGraphQLRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IGraphQLRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IHeadersRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IHeadersRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IHeadersRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IHeadersRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IHttpVersionBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IHttpVersionBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IHttpVersionBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IHttpVersionBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IMethodRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IMethodRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IMethodRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IMultiPartRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IMultiPartRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IMultiPartRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IMultiPartRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IParamsRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IParamsRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IParamsRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IProtoBufRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IProtoBufRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IProtoBufRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IProtoBufRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/IUrlAndPathRequestBuilder.cs b/src/WireMock.Net.Minimal/RequestBuilders/IUrlAndPathRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/IUrlAndPathRequestBuilder.cs rename to src/WireMock.Net.Minimal/RequestBuilders/IUrlAndPathRequestBuilder.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.ClientIP.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.ClientIP.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.ClientIP.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.ClientIP.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.UsingMethods.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.UsingMethods.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.UsingMethods.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.UsingMethods.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithBody.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithBody.cs similarity index 96% rename from src/WireMock.Net/RequestBuilders/Request.WithBody.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithBody.cs index 12b15595..8aa01516 100644 --- a/src/WireMock.Net/RequestBuilders/Request.WithBody.cs +++ b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithBody.cs @@ -1,107 +1,107 @@ -// Copyright © WireMock.Net and mock4net by Alexandre Victoor - -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. -// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. -using System; -using System.Collections.Generic; -using Stef.Validation; -using WireMock.Matchers; -using WireMock.Matchers.Request; -using WireMock.Util; - -namespace WireMock.RequestBuilders; - -public partial class Request -{ - /// - public IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); - return this; - } - - /// - public IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); - return this; - } - - /// - public IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); - return this; - } - - /// - public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - var matcher = body as IMatcher ?? new JsonMatcher(matchBehaviour, body); - return WithBody([matcher]); - } - - /// - public IRequestBuilder WithBody(IMatcher matcher) - { - return WithBody([matcher]); - } - - /// - public IRequestBuilder WithBody(IMatcher[] matchers, MatchOperator matchOperator = MatchOperator.Or) - { - Guard.NotNull(matchers); - - _requestMatchers.Add(new RequestMessageBodyMatcher(matchOperator, matchers)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Guard.NotNull(func); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Guard.NotNull(func); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Guard.NotNull(func); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func func) - { - Guard.NotNull(func); - - _requestMatchers.Add(new RequestMessageBodyMatcher(func)); - return this; - } - - /// - public IRequestBuilder WithBody(Func?, bool> func) - { - _requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func))); - return this; - } - - /// - public IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - return WithGraphQLSchema(body, matchBehaviour); - } +// Copyright © WireMock.Net and mock4net by Alexandre Victoor + +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. +using System; +using System.Collections.Generic; +using Stef.Validation; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.Util; + +namespace WireMock.RequestBuilders; + +public partial class Request +{ + /// + public IRequestBuilder WithBody(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); + return this; + } + + /// + public IRequestBuilder WithBody(byte[] body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); + return this; + } + + /// + public IRequestBuilder WithBody(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + _requestMatchers.Add(new RequestMessageBodyMatcher(matchBehaviour, body)); + return this; + } + + /// + public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + var matcher = body as IMatcher ?? new JsonMatcher(matchBehaviour, body); + return WithBody([matcher]); + } + + /// + public IRequestBuilder WithBody(IMatcher matcher) + { + return WithBody([matcher]); + } + + /// + public IRequestBuilder WithBody(IMatcher[] matchers, MatchOperator matchOperator = MatchOperator.Or) + { + Guard.NotNull(matchers); + + _requestMatchers.Add(new RequestMessageBodyMatcher(matchOperator, matchers)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Guard.NotNull(func); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Guard.NotNull(func); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Guard.NotNull(func); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func func) + { + Guard.NotNull(func); + + _requestMatchers.Add(new RequestMessageBodyMatcher(func)); + return this; + } + + /// + public IRequestBuilder WithBody(Func?, bool> func) + { + _requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func))); + return this; + } + + /// + public IRequestBuilder WithBodyAsGraphQLSchema(string body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + return WithGraphQLSchema(body, matchBehaviour); + } } \ No newline at end of file diff --git a/src/WireMock.Net/RequestBuilders/Request.WithBodyAsProtoBuf.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithBodyAsProtoBuf.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithBodyAsProtoBuf.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithBodyAsProtoBuf.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithCookies.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithCookies.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithCookies.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithCookies.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithGraphQLSchema.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithGraphQLSchema.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithGraphQLSchema.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithGraphQLSchema.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithHeaders.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithHeaders.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithHeaders.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithHeaders.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithHttpVersion.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithHttpVersion.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithHttpVersion.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithHttpVersion.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithMultiPart.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithMultiPart.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithMultiPart.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithMultiPart.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithParam.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithParam.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithParam.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithParam.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithPath.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithPath.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithPath.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.WithUrl.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.WithUrl.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.WithUrl.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.WithUrl.cs diff --git a/src/WireMock.Net/RequestBuilders/Request.cs b/src/WireMock.Net.Minimal/RequestBuilders/Request.cs similarity index 100% rename from src/WireMock.Net/RequestBuilders/Request.cs rename to src/WireMock.Net.Minimal/RequestBuilders/Request.cs diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net.Minimal/RequestMessage.cs similarity index 95% rename from src/WireMock.Net/RequestMessage.cs rename to src/WireMock.Net.Minimal/RequestMessage.cs index a9f599ca..aa93b682 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net.Minimal/RequestMessage.cs @@ -1,212 +1,212 @@ -// Copyright © WireMock.Net and mock4net by Alexandre Victoor - -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. -// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -#if USE_ASPNETCORE -using System.Security.Cryptography.X509Certificates; -#endif -using Stef.Validation; -using WireMock.Models; -using WireMock.Owin; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock; - -/// -/// The RequestMessage. -/// -public class RequestMessage : IRequestMessage -{ - /// - public string ClientIP { get; } - - /// - public string Url { get; } - - /// - public string AbsoluteUrl { get; } - - /// - public string? ProxyUrl { get; set; } - - /// - public DateTime DateTime { get; set; } - - /// - public string Path { get; } - - /// - public string AbsolutePath { get; } - - /// - public string[] PathSegments { get; } - - /// - public string[] AbsolutePathSegments { get; } - - /// - public string Method { get; } - - /// - public string HttpVersion { get; } - - /// - public IDictionary>? Headers { get; } - - /// - public IDictionary? Cookies { get; } - - /// - public IDictionary>? Query { get; } - - /// - public IDictionary>? QueryIgnoreCase { get; } - - /// - public string RawQuery { get; } - - /// - public IBodyData? BodyData { get; } - - /// - public string? Body { get; } - - /// - public object? BodyAsJson { get; set; } - - /// - public byte[]? BodyAsBytes { get; } - -#if MIMEKIT - /// - [Newtonsoft.Json.JsonIgnore] // Issue 1001 - public object? BodyAsMimeMessage { get; } -#endif - - /// - public string? DetectedBodyType { get; } - - /// - public string? DetectedBodyTypeFromContentType { get; } - - /// - public string? DetectedCompression { get; } - - /// - public string Host { get; } - - /// - public string Protocol { get; } - - /// - public int Port { get; } - - /// - public string Origin { get; } - -#if USE_ASPNETCORE - /// - public X509Certificate2? ClientCertificate { get; } -#endif - - /// - /// Used for Unit Testing - /// - internal RequestMessage( - UrlDetails urlDetails, - string method, - string clientIP, - IBodyData? bodyData = null, - IDictionary? headers = null, - IDictionary? cookies = null) : this(null, urlDetails, method, clientIP, bodyData, headers, cookies) - { - } - - internal RequestMessage( - IWireMockMiddlewareOptions? options, - UrlDetails urlDetails, - string method, - string clientIP, - IBodyData? bodyData = null, - IDictionary? headers = null, - IDictionary? cookies = null, - string httpVersion = "1.1" -#if USE_ASPNETCORE - , X509Certificate2? clientCertificate = null -#endif - ) - { - Guard.NotNull(urlDetails); - Guard.NotNull(method); - Guard.NotNull(clientIP); - - AbsoluteUrl = urlDetails.AbsoluteUrl.ToString(); - Url = urlDetails.Url.ToString(); - Protocol = urlDetails.Url.Scheme; - Host = urlDetails.Url.Host; - Port = urlDetails.Url.Port; - Origin = $"{Protocol}://{Host}:{Port}"; - - AbsolutePath = WebUtility.UrlDecode(urlDetails.AbsoluteUrl.AbsolutePath); - Path = WebUtility.UrlDecode(urlDetails.Url.AbsolutePath); - PathSegments = Path.Split('/').Skip(1).ToArray(); - AbsolutePathSegments = AbsolutePath.Split('/').Skip(1).ToArray(); - - Method = method; - HttpVersion = httpVersion; - ClientIP = clientIP; - - BodyData = bodyData; - - // Convenience getters for e.g. Handlebars - Body = BodyData?.BodyAsString; - BodyAsJson = BodyData?.BodyAsJson; - BodyAsBytes = BodyData?.BodyAsBytes; - - DetectedBodyType = BodyData?.DetectedBodyType.ToString(); - DetectedBodyTypeFromContentType = BodyData?.DetectedBodyTypeFromContentType.ToString(); - DetectedCompression = BodyData?.DetectedCompression; - - Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); - Cookies = cookies; - RawQuery = urlDetails.Url.Query; - Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport); - QueryIgnoreCase = new Dictionary>(Query, StringComparer.OrdinalIgnoreCase); - -#if USE_ASPNETCORE - ClientCertificate = clientCertificate; -#endif - -#if MIMEKIT - try - { - if (MimeKitUtils.TryGetMimeMessage(this, out var mimeMessage)) - { - BodyAsMimeMessage = mimeMessage; - } - } - catch - { - // Ignore exception from MimeMessage.Load - } -#endif - } - - /// - public WireMockList? GetParameter(string key, bool ignoreCase = false) - { - if (Query == null) - { - return null; - } - - var query = !ignoreCase ? Query : new Dictionary>(Query, StringComparer.OrdinalIgnoreCase); - - return query.ContainsKey(key) ? query[key] : null; - } +// Copyright © WireMock.Net and mock4net by Alexandre Victoor + +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +#if USE_ASPNETCORE +using System.Security.Cryptography.X509Certificates; +#endif +using Stef.Validation; +using WireMock.Models; +using WireMock.Owin; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock; + +/// +/// The RequestMessage. +/// +public class RequestMessage : IRequestMessage +{ + /// + public string ClientIP { get; } + + /// + public string Url { get; } + + /// + public string AbsoluteUrl { get; } + + /// + public string? ProxyUrl { get; set; } + + /// + public DateTime DateTime { get; set; } + + /// + public string Path { get; } + + /// + public string AbsolutePath { get; } + + /// + public string[] PathSegments { get; } + + /// + public string[] AbsolutePathSegments { get; } + + /// + public string Method { get; } + + /// + public string HttpVersion { get; } + + /// + public IDictionary>? Headers { get; } + + /// + public IDictionary? Cookies { get; } + + /// + public IDictionary>? Query { get; } + + /// + public IDictionary>? QueryIgnoreCase { get; } + + /// + public string RawQuery { get; } + + /// + public IBodyData? BodyData { get; } + + /// + public string? Body { get; } + + /// + public object? BodyAsJson { get; set; } + + /// + public byte[]? BodyAsBytes { get; } + +#if MIMEKIT + /// + [Newtonsoft.Json.JsonIgnore] // Issue 1001 + public object? BodyAsMimeMessage { get; } +#endif + + /// + public string? DetectedBodyType { get; } + + /// + public string? DetectedBodyTypeFromContentType { get; } + + /// + public string? DetectedCompression { get; } + + /// + public string Host { get; } + + /// + public string Protocol { get; } + + /// + public int Port { get; } + + /// + public string Origin { get; } + +#if USE_ASPNETCORE + /// + public X509Certificate2? ClientCertificate { get; } +#endif + + /// + /// Used for Unit Testing + /// + internal RequestMessage( + UrlDetails urlDetails, + string method, + string clientIP, + IBodyData? bodyData = null, + IDictionary? headers = null, + IDictionary? cookies = null) : this(null, urlDetails, method, clientIP, bodyData, headers, cookies) + { + } + + internal RequestMessage( + IWireMockMiddlewareOptions? options, + UrlDetails urlDetails, + string method, + string clientIP, + IBodyData? bodyData = null, + IDictionary? headers = null, + IDictionary? cookies = null, + string httpVersion = "1.1" +#if USE_ASPNETCORE + , X509Certificate2? clientCertificate = null +#endif + ) + { + Guard.NotNull(urlDetails); + Guard.NotNull(method); + Guard.NotNull(clientIP); + + AbsoluteUrl = urlDetails.AbsoluteUrl.ToString(); + Url = urlDetails.Url.ToString(); + Protocol = urlDetails.Url.Scheme; + Host = urlDetails.Url.Host; + Port = urlDetails.Url.Port; + Origin = $"{Protocol}://{Host}:{Port}"; + + AbsolutePath = WebUtility.UrlDecode(urlDetails.AbsoluteUrl.AbsolutePath); + Path = WebUtility.UrlDecode(urlDetails.Url.AbsolutePath); + PathSegments = Path.Split('/').Skip(1).ToArray(); + AbsolutePathSegments = AbsolutePath.Split('/').Skip(1).ToArray(); + + Method = method; + HttpVersion = httpVersion; + ClientIP = clientIP; + + BodyData = bodyData; + + // Convenience getters for e.g. Handlebars + Body = BodyData?.BodyAsString; + BodyAsJson = BodyData?.BodyAsJson; + BodyAsBytes = BodyData?.BodyAsBytes; + + DetectedBodyType = BodyData?.DetectedBodyType.ToString(); + DetectedBodyTypeFromContentType = BodyData?.DetectedBodyTypeFromContentType.ToString(); + DetectedCompression = BodyData?.DetectedCompression; + + Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); + Cookies = cookies; + RawQuery = urlDetails.Url.Query; + Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport); + QueryIgnoreCase = new Dictionary>(Query, StringComparer.OrdinalIgnoreCase); + +#if USE_ASPNETCORE + ClientCertificate = clientCertificate; +#endif + +#if MIMEKIT + try + { + if (TypeLoader.LoadStaticInstance().TryGetMimeMessage(this, out var mimeMessage)) + { + BodyAsMimeMessage = mimeMessage; + } + } + catch + { + // Ignore exception from MimeMessage.Load + } +#endif + } + + /// + public WireMockList? GetParameter(string key, bool ignoreCase = false) + { + if (Query == null) + { + return null; + } + + var query = !ignoreCase ? Query : new Dictionary>(Query, StringComparer.OrdinalIgnoreCase); + + return query.ContainsKey(key) ? query[key] : null; + } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/BodyDestinationFormat.cs b/src/WireMock.Net.Minimal/ResponseBuilders/BodyDestinationFormat.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/BodyDestinationFormat.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/BodyDestinationFormat.cs diff --git a/src/WireMock.Net/ResponseBuilders/IBodyResponseBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/IBodyResponseBuilder.cs similarity index 98% rename from src/WireMock.Net/ResponseBuilders/IBodyResponseBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/IBodyResponseBuilder.cs index 303ce15b..758ea69a 100644 --- a/src/WireMock.Net/ResponseBuilders/IBodyResponseBuilder.cs +++ b/src/WireMock.Net.Minimal/ResponseBuilders/IBodyResponseBuilder.cs @@ -1,169 +1,169 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using JsonConverter.Abstractions; -using WireMock.Models; - -namespace WireMock.ResponseBuilders; - -/// -/// The BodyResponseBuilder interface. -/// -public interface IBodyResponseBuilder : IFaultResponseBuilder -{ - /// - /// WithBody : Create a ... response based on a string. - /// - /// The body. - /// The Body Destination format (SameAsSource, String or Bytes). - /// The body encoding. - /// A . - IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); - - /// - /// WithBody : Create a ... response based on a callback function. - /// - /// The delegate to build the body. - /// The Body Destination format (SameAsSource, String or Bytes). - /// The body encoding. - /// A . - IResponseBuilder WithBody(Func bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); - - /// - /// WithBody : Create a ... response based on an async callback function. - /// - /// The async delegate to build the body. - /// The Body Destination format (SameAsSource, String or Bytes). - /// The body encoding. - /// A . - IResponseBuilder WithBody(Func> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); - - /// - /// WithBody : Create a ... response based on an async callback function. - /// - /// The async delegate to build the body. - /// The timeout to wait on new items in the queue. Default value is 1 hour. - /// A . - IResponseBuilder WithSseBody(Func, Task> bodyFactory, TimeSpan? timeout = null); - - /// - /// WithBody : Create a ... response based on a bytearray. - /// - /// The body. - /// The Body Destination format (SameAsSource, String or Bytes). - /// The body encoding. - /// A . - IResponseBuilder WithBody(byte[] body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); - - /// - /// WithBody : Create a string response based on a object (which will be converted to a JSON string). - /// - /// The body. - /// The body encoding. - /// Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. - /// A . - IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null); - - /// - /// WithBody : Create a string response based on a object (which will be converted to a JSON string). - /// - /// The body. - /// Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. - /// A . - IResponseBuilder WithBodyAsJson(object body, bool indented); - - /// - /// WithBodyAsJson : Create a ... response based on a callback function. - /// - /// The delegate to build the body. - /// The body encoding. - /// A . - IResponseBuilder WithBodyAsJson(Func bodyFactory, Encoding? encoding = null); - - /// - /// WithBodyAsJson : Create a ... response based on a async callback function. - /// - /// The async delegate to build the body. - /// The body encoding. - /// A . - IResponseBuilder WithBodyAsJson(Func> bodyFactory, Encoding? encoding = null); - - /// - /// WithBodyFromFile : Create a ... response based on a File. - /// - /// The filename. - /// Defines if this file is cached in memory or retrieved from disk every time the response is created. - /// A . - IResponseBuilder WithBodyFromFile(string filename, bool cache = true); - - /// - /// WithBody : Create a string response based on a object (which will be converted to a JSON string using the ). - /// - /// The body. - /// The . - /// The [optional]. - /// A . - IResponseBuilder WithBody(object body, IJsonConverter jsonConverter, JsonConverterOptions? options = null); - - /// - /// WithBody : Create a string response based on a object (which will be converted to a JSON string using the ). - /// - /// The body. - /// The body encoding, can be null. - /// The . - /// The [optional]. - /// A . - IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter jsonConverter, JsonConverterOptions? options = null); - - /// - /// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on a proto definition, message type and the value. - /// - /// The proto definition as text. - /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}". - /// The object to convert to protobuf byte[]. - /// The [optional]. Default value is NewtonsoftJsonConverter. - /// The [optional]. - /// A . - IResponseBuilder WithBodyAsProtoBuf( - string protoDefinition, - string messageType, - object value, - IJsonConverter? jsonConverter = null, - JsonConverterOptions? options = null - ); - - /// - /// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on proto definitions, message type and the value. - /// - /// The proto definition as text. - /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}". - /// The object to convert to protobuf byte[]. - /// The [optional]. Default value is NewtonsoftJsonConverter. - /// The [optional]. - /// A . - IResponseBuilder WithBodyAsProtoBuf( - IReadOnlyList protoDefinitions, - string messageType, - object value, - IJsonConverter? jsonConverter = null, - JsonConverterOptions? options = null - ); - - /// - /// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on a proto definition, message type and the value. - /// - /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}". - /// The object to convert to protobuf byte[]. - /// The [optional]. Default value is NewtonsoftJsonConverter. - /// The [optional]. - /// A . - IResponseBuilder WithBodyAsProtoBuf( - string messageType, - object value, - IJsonConverter? jsonConverter = null, - JsonConverterOptions? options = null - ); +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using JsonConverter.Abstractions; +using WireMock.Models; + +namespace WireMock.ResponseBuilders; + +/// +/// The BodyResponseBuilder interface. +/// +public interface IBodyResponseBuilder : IFaultResponseBuilder +{ + /// + /// WithBody : Create a ... response based on a string. + /// + /// The body. + /// The Body Destination format (SameAsSource, String or Bytes). + /// The body encoding. + /// A . + IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); + + /// + /// WithBody : Create a ... response based on a callback function. + /// + /// The delegate to build the body. + /// The Body Destination format (SameAsSource, String or Bytes). + /// The body encoding. + /// A . + IResponseBuilder WithBody(Func bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); + + /// + /// WithBody : Create a ... response based on an async callback function. + /// + /// The async delegate to build the body. + /// The Body Destination format (SameAsSource, String or Bytes). + /// The body encoding. + /// A . + IResponseBuilder WithBody(Func> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); + + /// + /// WithBody : Create a ... response based on an async callback function. + /// + /// The async delegate to build the body. + /// The timeout to wait on new items in the queue. Default value is 1 hour. + /// A . + IResponseBuilder WithSseBody(Func, Task> bodyFactory, TimeSpan? timeout = null); + + /// + /// WithBody : Create a ... response based on a bytearray. + /// + /// The body. + /// The Body Destination format (SameAsSource, String or Bytes). + /// The body encoding. + /// A . + IResponseBuilder WithBody(byte[] body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null); + + /// + /// WithBody : Create a string response based on a object (which will be converted to a JSON string). + /// + /// The body. + /// The body encoding. + /// Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. + /// A . + IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null); + + /// + /// WithBody : Create a string response based on a object (which will be converted to a JSON string). + /// + /// The body. + /// Define whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings. + /// A . + IResponseBuilder WithBodyAsJson(object body, bool indented); + + /// + /// WithBodyAsJson : Create a ... response based on a callback function. + /// + /// The delegate to build the body. + /// The body encoding. + /// A . + IResponseBuilder WithBodyAsJson(Func bodyFactory, Encoding? encoding = null); + + /// + /// WithBodyAsJson : Create a ... response based on a async callback function. + /// + /// The async delegate to build the body. + /// The body encoding. + /// A . + IResponseBuilder WithBodyAsJson(Func> bodyFactory, Encoding? encoding = null); + + /// + /// WithBodyFromFile : Create a ... response based on a File. + /// + /// The filename. + /// Defines if this file is cached in memory or retrieved from disk every time the response is created. + /// A . + IResponseBuilder WithBodyFromFile(string filename, bool cache = true); + + /// + /// WithBody : Create a string response based on a object (which will be converted to a JSON string using the ). + /// + /// The body. + /// The . + /// The [optional]. + /// A . + IResponseBuilder WithBody(object body, IJsonConverter jsonConverter, JsonConverterOptions? options = null); + + /// + /// WithBody : Create a string response based on a object (which will be converted to a JSON string using the ). + /// + /// The body. + /// The body encoding, can be null. + /// The . + /// The [optional]. + /// A . + IResponseBuilder WithBody(object body, Encoding? encoding, IJsonConverter jsonConverter, JsonConverterOptions? options = null); + + /// + /// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on a proto definition, message type and the value. + /// + /// The proto definition as text. + /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}". + /// The object to convert to protobuf byte[]. + /// The [optional]. Default value is NewtonsoftJsonConverter. + /// The [optional]. + /// A . + IResponseBuilder WithBodyAsProtoBuf( + string protoDefinition, + string messageType, + object value, + IJsonConverter? jsonConverter = null, + JsonConverterOptions? options = null + ); + + /// + /// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on proto definitions, message type and the value. + /// + /// The proto definition as text. + /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}". + /// The object to convert to protobuf byte[]. + /// The [optional]. Default value is NewtonsoftJsonConverter. + /// The [optional]. + /// A . + IResponseBuilder WithBodyAsProtoBuf( + IReadOnlyList protoDefinitions, + string messageType, + object value, + IJsonConverter? jsonConverter = null, + JsonConverterOptions? options = null + ); + + /// + /// WithBodyAsProtoBuf : Create a ProtoBuf byte[] response based on a proto definition, message type and the value. + /// + /// The full type of the protobuf (request/response) message object. Format is "{package-name}.{type-name}". + /// The object to convert to protobuf byte[]. + /// The [optional]. Default value is NewtonsoftJsonConverter. + /// The [optional]. + /// A . + IResponseBuilder WithBodyAsProtoBuf( + string messageType, + object value, + IJsonConverter? jsonConverter = null, + JsonConverterOptions? options = null + ); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/ICallbackResponseBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/ICallbackResponseBuilder.cs similarity index 96% rename from src/WireMock.Net/ResponseBuilders/ICallbackResponseBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/ICallbackResponseBuilder.cs index 0498d87b..1eaf1148 100644 --- a/src/WireMock.Net/ResponseBuilders/ICallbackResponseBuilder.cs +++ b/src/WireMock.Net.Minimal/ResponseBuilders/ICallbackResponseBuilder.cs @@ -1,28 +1,28 @@ -// Copyright © WireMock.Net - -using System; -using System.Threading.Tasks; -using JetBrains.Annotations; -using WireMock.ResponseProviders; - -namespace WireMock.ResponseBuilders; - -/// -/// The CallbackResponseBuilder interface. -/// -public interface ICallbackResponseBuilder : IResponseProvider -{ - /// - /// The callback builder - /// - /// The . - [PublicAPI] - IResponseBuilder WithCallback(Func callbackHandler); - - /// - /// The async callback builder - /// - /// The . - [PublicAPI] - IResponseBuilder WithCallback(Func> callbackHandler); +// Copyright © WireMock.Net + +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using WireMock.ResponseProviders; + +namespace WireMock.ResponseBuilders; + +/// +/// The CallbackResponseBuilder interface. +/// +public interface ICallbackResponseBuilder : IResponseProvider +{ + /// + /// The callback builder + /// + /// The . + [PublicAPI] + IResponseBuilder WithCallback(Func callbackHandler); + + /// + /// The async callback builder + /// + /// The . + [PublicAPI] + IResponseBuilder WithCallback(Func> callbackHandler); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/IDelayResponseBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/IDelayResponseBuilder.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/IDelayResponseBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/IDelayResponseBuilder.cs diff --git a/src/WireMock.Net/ResponseBuilders/IFaultRequestBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/IFaultRequestBuilder.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/IFaultRequestBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/IFaultRequestBuilder.cs diff --git a/src/WireMock.Net/ResponseBuilders/IHeadersResponseBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/IHeadersResponseBuilder.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/IHeadersResponseBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/IHeadersResponseBuilder.cs diff --git a/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/IProxyResponseBuilder.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/IProxyResponseBuilder.cs diff --git a/src/WireMock.Net/ResponseBuilders/IResponseBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/IResponseBuilder.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/IResponseBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/IResponseBuilder.cs diff --git a/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/IStatusCodeResponseBuilder.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/IStatusCodeResponseBuilder.cs diff --git a/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs b/src/WireMock.Net.Minimal/ResponseBuilders/ITransformResponseBuilder.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/ITransformResponseBuilder.cs diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithBody.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithBody.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/Response.WithBody.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/Response.WithBody.cs diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithCallback.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithCallback.cs similarity index 96% rename from src/WireMock.Net/ResponseBuilders/Response.WithCallback.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/Response.WithCallback.cs index 53ce4313..cdfca99d 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.WithCallback.cs +++ b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithCallback.cs @@ -1,64 +1,64 @@ -// Copyright © WireMock.Net - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Stef.Validation; - -namespace WireMock.ResponseBuilders; - -public partial class Response -{ - /// - /// A delegate to execute to generate the response. - /// - [MemberNotNullWhen(true, nameof(WithCallbackUsed))] - public Func? Callback { get; private set; } - - /// - /// A delegate to execute to generate the response async. - /// - [MemberNotNullWhen(true, nameof(WithCallbackUsed))] - public Func>? CallbackAsync { get; private set; } - - /// - /// Defines if the method WithCallback(...) is used. - /// - public bool WithCallbackUsed { get; private set; } - - /// - public IResponseBuilder WithCallback(Func callbackHandler) - { - Guard.NotNull(callbackHandler); - - return WithCallbackInternal(true, callbackHandler); - } - - /// - public IResponseBuilder WithCallback(Func> callbackHandler) - { - Guard.NotNull(callbackHandler); - - return WithCallbackInternal(true, callbackHandler); - } - - private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func callbackHandler) - { - Guard.NotNull(callbackHandler); - - WithCallbackUsed = withCallbackUsed; - Callback = callbackHandler; - - return this; - } - - private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func> callbackHandler) - { - Guard.NotNull(callbackHandler); - - WithCallbackUsed = withCallbackUsed; - CallbackAsync = callbackHandler; - - return this; - } +// Copyright © WireMock.Net + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Stef.Validation; + +namespace WireMock.ResponseBuilders; + +public partial class Response +{ + /// + /// A delegate to execute to generate the response. + /// + [MemberNotNullWhen(true, nameof(WithCallbackUsed))] + public Func? Callback { get; private set; } + + /// + /// A delegate to execute to generate the response async. + /// + [MemberNotNullWhen(true, nameof(WithCallbackUsed))] + public Func>? CallbackAsync { get; private set; } + + /// + /// Defines if the method WithCallback(...) is used. + /// + public bool WithCallbackUsed { get; private set; } + + /// + public IResponseBuilder WithCallback(Func callbackHandler) + { + Guard.NotNull(callbackHandler); + + return WithCallbackInternal(true, callbackHandler); + } + + /// + public IResponseBuilder WithCallback(Func> callbackHandler) + { + Guard.NotNull(callbackHandler); + + return WithCallbackInternal(true, callbackHandler); + } + + private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func callbackHandler) + { + Guard.NotNull(callbackHandler); + + WithCallbackUsed = withCallbackUsed; + Callback = callbackHandler; + + return this; + } + + private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func> callbackHandler) + { + Guard.NotNull(callbackHandler); + + WithCallbackUsed = withCallbackUsed; + CallbackAsync = callbackHandler; + + return this; + } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithFault.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithFault.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/Response.WithFault.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/Response.WithFault.cs diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithHeaders.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithHeaders.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/Response.WithHeaders.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/Response.WithHeaders.cs diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithProxy.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/Response.WithProxy.cs diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithTransformer.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithTransformer.cs similarity index 100% rename from src/WireMock.Net/ResponseBuilders/Response.WithTransformer.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/Response.WithTransformer.cs diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs similarity index 97% rename from src/WireMock.Net/ResponseBuilders/Response.cs rename to src/WireMock.Net.Minimal/ResponseBuilders/Response.cs index b04b7970..08713fa2 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.cs +++ b/src/WireMock.Net.Minimal/ResponseBuilders/Response.cs @@ -1,307 +1,307 @@ -// Copyright © WireMock.Net and mock4net by Alexandre Victoor - -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. -// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. -using System; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Stef.Validation; -using WireMock.Proxy; -using WireMock.RequestBuilders; -using WireMock.Settings; -using WireMock.Transformers; -using WireMock.Transformers.Handlebars; -using WireMock.Transformers.Scriban; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.ResponseBuilders; - -/// -/// The Response. -/// -public partial class Response : IResponseBuilder -{ - private static readonly ThreadLocal Random = new(() => new Random(DateTime.UtcNow.Millisecond)); - - private TimeSpan? _delay; - - /// - /// The link back to the mapping. - /// - public IMapping Mapping { get; set; } = null!; - - /// - /// The minimum random delay in milliseconds. - /// - public int? MinimumDelayMilliseconds { get; private set; } - - /// - /// The maximum random delay in milliseconds. - /// - public int? MaximumDelayMilliseconds { get; private set; } - - /// - /// The delay - /// - public TimeSpan? Delay - { - get - { - if (MinimumDelayMilliseconds != null && MaximumDelayMilliseconds != null) - { - return TimeSpan.FromMilliseconds(Random.Value!.Next(MinimumDelayMilliseconds.Value, MaximumDelayMilliseconds.Value)); - } - - return _delay; - } - - private set => _delay = value; - } - - /// - /// Gets a value indicating whether [use transformer]. - /// - public bool UseTransformer { get; private set; } - - /// - /// Gets the type of the transformer. - /// - public TransformerType TransformerType { get; private set; } - - /// - /// Gets a value indicating whether to use the Handlebars transformer for the content from the referenced BodyAsFile. - /// - public bool UseTransformerForBodyAsFile { get; private set; } - - /// - /// Gets the ReplaceNodeOptions to use when transforming a JSON node. - /// - public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; } - - /// - /// Gets the response message. - /// - public ResponseMessage ResponseMessage { get; } - - /// - /// Creates this instance. - /// - /// ResponseMessage - /// A . - [PublicAPI] - public static IResponseBuilder Create(ResponseMessage? responseMessage = null) - { - var message = responseMessage ?? new ResponseMessage(); - return new Response(message); - } - - /// - /// Creates this instance with the specified function. - /// - /// The callback function. - /// A . - [PublicAPI] - public static IResponseBuilder Create(Func func) - { - Guard.NotNull(func); - - return new Response(func()); - } - - /// - /// Initializes a new instance of the class. - /// - /// The response. - private Response(ResponseMessage responseMessage) - { - ResponseMessage = responseMessage; - } - - /// - [PublicAPI] - public IResponseBuilder WithStatusCode(int code) - { - ResponseMessage.StatusCode = code; - return this; - } - - /// - [PublicAPI] - public IResponseBuilder WithStatusCode(string code) - { - ResponseMessage.StatusCode = code; - return this; - } - - /// - [PublicAPI] - public IResponseBuilder WithStatusCode(HttpStatusCode code) - { - return WithStatusCode((int)code); - } - - /// - /// The with Success status code (200). - /// - /// A . - [PublicAPI] - public IResponseBuilder WithSuccess() - { - return WithStatusCode((int)HttpStatusCode.OK); - } - - /// - /// The with NotFound status code (404). - /// - /// The . - [PublicAPI] - public IResponseBuilder WithNotFound() - { - return WithStatusCode((int)HttpStatusCode.NotFound); - } - - /// - public IResponseBuilder WithDelay(TimeSpan delay) - { - Guard.Condition(delay, d => d == Timeout.InfiniteTimeSpan || d > TimeSpan.Zero); - - Delay = delay; - return this; - } - - /// - public IResponseBuilder WithDelay(int milliseconds) - { - return WithDelay(TimeSpan.FromMilliseconds(milliseconds)); - } - - /// - public IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000) - { - Guard.Condition(minimumMilliseconds, min => min >= 0); - Guard.Condition(maximumMilliseconds, max => max > minimumMilliseconds); - - MinimumDelayMilliseconds = minimumMilliseconds; - MaximumDelayMilliseconds = maximumMilliseconds; - - return this; - } - - /// - public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings) - { - Guard.NotNull(requestMessage); - Guard.NotNull(settings); - - if (Delay != null) - { - await Task.Delay(Delay.Value).ConfigureAwait(false); - } - - if (ProxyAndRecordSettings != null && _httpClientForProxy != null) - { - string RemoveFirstOccurrence(string source, string find) - { - int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase); - return place >= 0 ? source.Remove(place, find.Length) : source; - } - - var requestUri = new Uri(requestMessage.Url); - - // Build the proxy url and skip duplicates - string extra = RemoveFirstOccurrence(requestUri.LocalPath.TrimEnd('/'), new Uri(ProxyAndRecordSettings.Url).LocalPath.TrimEnd('/')); - requestMessage.ProxyUrl = ProxyAndRecordSettings.Url + extra + requestUri.Query; - - var proxyHelper = new ProxyHelper(settings); - - return await proxyHelper.SendAsync( - mapping, - ProxyAndRecordSettings, - _httpClientForProxy, - requestMessage, - requestMessage.ProxyUrl - ).ConfigureAwait(false); - } - - ResponseMessage responseMessage; - if (!WithCallbackUsed) - { - responseMessage = ResponseMessage; - } - else - { - if (Callback != null) - { - responseMessage = Callback(requestMessage); - } - else - { - responseMessage = await CallbackAsync!(requestMessage).ConfigureAwait(false); - } - - // Copy StatusCode from ResponseMessage (if defined) - if (ResponseMessage.StatusCode != null) - { - responseMessage.StatusCode = ResponseMessage.StatusCode; - } - - // Copy Headers from ResponseMessage (if defined) - if (ResponseMessage.Headers?.Count > 0) - { - responseMessage.Headers = ResponseMessage.Headers; - } - - // Copy TrailingHeaders from ResponseMessage (if defined) - if (ResponseMessage.TrailingHeaders?.Count > 0) - { - responseMessage.TrailingHeaders = ResponseMessage.TrailingHeaders; - } - } - - if (UseTransformer) - { - // If the body matcher is a RequestMessageProtoBufMatcher or BodyMatcher with a ProtoBufMatcher then try to decode the byte-array to a BodyAsJson. - if (mapping.RequestMatcher is Request request && requestMessage is RequestMessage requestMessageImplementation) - { - if (request.TryGetProtoBufMatcher(out var protoBufMatcher)) - { - var decoded = await protoBufMatcher.DecodeAsync(requestMessage.BodyData?.BodyAsBytes).ConfigureAwait(false); - if (decoded != null) - { - requestMessageImplementation.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded); - } - } - } - - ITransformer responseMessageTransformer; - switch (TransformerType) - { - case TransformerType.Handlebars: - var factoryHandlebars = new HandlebarsContextFactory(settings); - responseMessageTransformer = new Transformer(settings, factoryHandlebars); - break; - - case TransformerType.Scriban: - case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType); - responseMessageTransformer = new Transformer(settings, factoryDotLiquid); - break; - - default: - throw new NotSupportedException($"TransformerType '{TransformerType}' is not supported."); - } - - return (responseMessageTransformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null); - } - - if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true && responseMessage.BodyData?.BodyAsFile is not null) - { - ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile); - } - - return (responseMessage, null); - } +// Copyright © WireMock.Net and mock4net by Alexandre Victoor + +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Stef.Validation; +using WireMock.Proxy; +using WireMock.RequestBuilders; +using WireMock.Settings; +using WireMock.Transformers; +using WireMock.Transformers.Handlebars; +using WireMock.Transformers.Scriban; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.ResponseBuilders; + +/// +/// The Response. +/// +public partial class Response : IResponseBuilder +{ + private static readonly ThreadLocal Random = new(() => new Random(DateTime.UtcNow.Millisecond)); + + private TimeSpan? _delay; + + /// + /// The link back to the mapping. + /// + public IMapping Mapping { get; set; } = null!; + + /// + /// The minimum random delay in milliseconds. + /// + public int? MinimumDelayMilliseconds { get; private set; } + + /// + /// The maximum random delay in milliseconds. + /// + public int? MaximumDelayMilliseconds { get; private set; } + + /// + /// The delay + /// + public TimeSpan? Delay + { + get + { + if (MinimumDelayMilliseconds != null && MaximumDelayMilliseconds != null) + { + return TimeSpan.FromMilliseconds(Random.Value!.Next(MinimumDelayMilliseconds.Value, MaximumDelayMilliseconds.Value)); + } + + return _delay; + } + + private set => _delay = value; + } + + /// + /// Gets a value indicating whether [use transformer]. + /// + public bool UseTransformer { get; private set; } + + /// + /// Gets the type of the transformer. + /// + public TransformerType TransformerType { get; private set; } + + /// + /// Gets a value indicating whether to use the Handlebars transformer for the content from the referenced BodyAsFile. + /// + public bool UseTransformerForBodyAsFile { get; private set; } + + /// + /// Gets the ReplaceNodeOptions to use when transforming a JSON node. + /// + public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; } + + /// + /// Gets the response message. + /// + public ResponseMessage ResponseMessage { get; } + + /// + /// Creates this instance. + /// + /// ResponseMessage + /// A . + [PublicAPI] + public static IResponseBuilder Create(ResponseMessage? responseMessage = null) + { + var message = responseMessage ?? new ResponseMessage(); + return new Response(message); + } + + /// + /// Creates this instance with the specified function. + /// + /// The callback function. + /// A . + [PublicAPI] + public static IResponseBuilder Create(Func func) + { + Guard.NotNull(func); + + return new Response(func()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The response. + private Response(ResponseMessage responseMessage) + { + ResponseMessage = responseMessage; + } + + /// + [PublicAPI] + public IResponseBuilder WithStatusCode(int code) + { + ResponseMessage.StatusCode = code; + return this; + } + + /// + [PublicAPI] + public IResponseBuilder WithStatusCode(string code) + { + ResponseMessage.StatusCode = code; + return this; + } + + /// + [PublicAPI] + public IResponseBuilder WithStatusCode(HttpStatusCode code) + { + return WithStatusCode((int)code); + } + + /// + /// The with Success status code (200). + /// + /// A . + [PublicAPI] + public IResponseBuilder WithSuccess() + { + return WithStatusCode((int)HttpStatusCode.OK); + } + + /// + /// The with NotFound status code (404). + /// + /// The . + [PublicAPI] + public IResponseBuilder WithNotFound() + { + return WithStatusCode((int)HttpStatusCode.NotFound); + } + + /// + public IResponseBuilder WithDelay(TimeSpan delay) + { + Guard.Condition(delay, d => d == Timeout.InfiniteTimeSpan || d > TimeSpan.Zero); + + Delay = delay; + return this; + } + + /// + public IResponseBuilder WithDelay(int milliseconds) + { + return WithDelay(TimeSpan.FromMilliseconds(milliseconds)); + } + + /// + public IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000) + { + Guard.Condition(minimumMilliseconds, min => min >= 0); + Guard.Condition(maximumMilliseconds, max => max > minimumMilliseconds); + + MinimumDelayMilliseconds = minimumMilliseconds; + MaximumDelayMilliseconds = maximumMilliseconds; + + return this; + } + + /// + public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IMapping mapping, IRequestMessage requestMessage, WireMockServerSettings settings) + { + Guard.NotNull(requestMessage); + Guard.NotNull(settings); + + if (Delay != null) + { + await Task.Delay(Delay.Value).ConfigureAwait(false); + } + + if (ProxyAndRecordSettings != null && _httpClientForProxy != null) + { + string RemoveFirstOccurrence(string source, string find) + { + int place = source.IndexOf(find, StringComparison.OrdinalIgnoreCase); + return place >= 0 ? source.Remove(place, find.Length) : source; + } + + var requestUri = new Uri(requestMessage.Url); + + // Build the proxy url and skip duplicates + string extra = RemoveFirstOccurrence(requestUri.LocalPath.TrimEnd('/'), new Uri(ProxyAndRecordSettings.Url).LocalPath.TrimEnd('/')); + requestMessage.ProxyUrl = ProxyAndRecordSettings.Url + extra + requestUri.Query; + + var proxyHelper = new ProxyHelper(settings); + + return await proxyHelper.SendAsync( + mapping, + ProxyAndRecordSettings, + _httpClientForProxy, + requestMessage, + requestMessage.ProxyUrl + ).ConfigureAwait(false); + } + + ResponseMessage responseMessage; + if (!WithCallbackUsed) + { + responseMessage = ResponseMessage; + } + else + { + if (Callback != null) + { + responseMessage = Callback(requestMessage); + } + else + { + responseMessage = await CallbackAsync!(requestMessage).ConfigureAwait(false); + } + + // Copy StatusCode from ResponseMessage (if defined) + if (ResponseMessage.StatusCode != null) + { + responseMessage.StatusCode = ResponseMessage.StatusCode; + } + + // Copy Headers from ResponseMessage (if defined) + if (ResponseMessage.Headers?.Count > 0) + { + responseMessage.Headers = ResponseMessage.Headers; + } + + // Copy TrailingHeaders from ResponseMessage (if defined) + if (ResponseMessage.TrailingHeaders?.Count > 0) + { + responseMessage.TrailingHeaders = ResponseMessage.TrailingHeaders; + } + } + + if (UseTransformer) + { + // If the body matcher is a RequestMessageProtoBufMatcher or BodyMatcher with a ProtoBufMatcher then try to decode the byte-array to a BodyAsJson. + if (mapping.RequestMatcher is Request request && requestMessage is RequestMessage requestMessageImplementation) + { + if (request.TryGetProtoBufMatcher(out var protoBufMatcher)) + { + var decoded = await protoBufMatcher.DecodeAsync(requestMessage.BodyData?.BodyAsBytes).ConfigureAwait(false); + if (decoded != null) + { + requestMessageImplementation.BodyAsJson = JsonUtils.ConvertValueToJToken(decoded); + } + } + } + + ITransformer responseMessageTransformer; + switch (TransformerType) + { + case TransformerType.Handlebars: + var factoryHandlebars = new HandlebarsContextFactory(settings); + responseMessageTransformer = new Transformer(settings, factoryHandlebars); + break; + + case TransformerType.Scriban: + case TransformerType.ScribanDotLiquid: + var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType); + responseMessageTransformer = new Transformer(settings, factoryDotLiquid); + break; + + default: + throw new NotSupportedException($"TransformerType '{TransformerType}' is not supported."); + } + + return (responseMessageTransformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null); + } + + if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true && responseMessage.BodyData?.BodyAsFile is not null) + { + ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile); + } + + return (responseMessage, null); + } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseMessage.cs b/src/WireMock.Net.Minimal/ResponseMessage.cs similarity index 100% rename from src/WireMock.Net/ResponseMessage.cs rename to src/WireMock.Net.Minimal/ResponseMessage.cs diff --git a/src/WireMock.Net/ResponseMessageBuilder.cs b/src/WireMock.Net.Minimal/ResponseMessageBuilder.cs similarity index 100% rename from src/WireMock.Net/ResponseMessageBuilder.cs rename to src/WireMock.Net.Minimal/ResponseMessageBuilder.cs diff --git a/src/WireMock.Net/ResponseProviders/DynamicAsyncResponseProvider.cs b/src/WireMock.Net.Minimal/ResponseProviders/DynamicAsyncResponseProvider.cs similarity index 100% rename from src/WireMock.Net/ResponseProviders/DynamicAsyncResponseProvider.cs rename to src/WireMock.Net.Minimal/ResponseProviders/DynamicAsyncResponseProvider.cs diff --git a/src/WireMock.Net/ResponseProviders/DynamicResponseProvider.cs b/src/WireMock.Net.Minimal/ResponseProviders/DynamicResponseProvider.cs similarity index 100% rename from src/WireMock.Net/ResponseProviders/DynamicResponseProvider.cs rename to src/WireMock.Net.Minimal/ResponseProviders/DynamicResponseProvider.cs diff --git a/src/WireMock.Net/ResponseProviders/IResponseProvider.cs b/src/WireMock.Net.Minimal/ResponseProviders/IResponseProvider.cs similarity index 100% rename from src/WireMock.Net/ResponseProviders/IResponseProvider.cs rename to src/WireMock.Net.Minimal/ResponseProviders/IResponseProvider.cs diff --git a/src/WireMock.Net/ResponseProviders/ProxyAsyncResponseProvider.cs b/src/WireMock.Net.Minimal/ResponseProviders/ProxyAsyncResponseProvider.cs similarity index 100% rename from src/WireMock.Net/ResponseProviders/ProxyAsyncResponseProvider.cs rename to src/WireMock.Net.Minimal/ResponseProviders/ProxyAsyncResponseProvider.cs diff --git a/src/WireMock.Net/ScenarioState.cs b/src/WireMock.Net.Minimal/ScenarioState.cs similarity index 100% rename from src/WireMock.Net/ScenarioState.cs rename to src/WireMock.Net.Minimal/ScenarioState.cs diff --git a/src/WireMock.Net/Serialization/LogEntryMapper.cs b/src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs similarity index 100% rename from src/WireMock.Net/Serialization/LogEntryMapper.cs rename to src/WireMock.Net.Minimal/Serialization/LogEntryMapper.cs diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net.Minimal/Serialization/MappingConverter.cs similarity index 98% rename from src/WireMock.Net/Serialization/MappingConverter.cs rename to src/WireMock.Net.Minimal/Serialization/MappingConverter.cs index 1bb70ba0..aacc94a5 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net.Minimal/Serialization/MappingConverter.cs @@ -121,15 +121,13 @@ internal class MappingConverter(MatcherMapper mapper) } #endif -#if MIMEKIT if (requestMessageMultiPartMatcher?.Matchers != null) { - if (requestMessageMultiPartMatcher.Matchers.OfType().Any()) + if (requestMessageMultiPartMatcher.Matchers.OfType().Any()) { sb.AppendLine(" // .WithMultiPart() is not yet supported"); } } -#endif #if PROTOBUF if (requestMessageProtoBufMatcher?.Matcher != null) @@ -368,7 +366,7 @@ internal class MappingConverter(MatcherMapper mapper) mappingModel.Response.Delay = (int?)(response.Delay == Timeout.InfiniteTimeSpan ? TimeSpan.MaxValue.TotalMilliseconds : response.Delay?.TotalMilliseconds); } - var nonNullableWebHooks = mapping.Webhooks?.ToArray() ?? EmptyArray.Value; + var nonNullableWebHooks = mapping.Webhooks?.ToArray() ?? []; if (nonNullableWebHooks.Length == 1) { mappingModel.Webhook = WebhookMapper.Map(nonNullableWebHooks[0]); @@ -581,7 +579,12 @@ internal class MappingConverter(MatcherMapper mapper) sb.AppendFormat("{0}, ", matchBehaviour.Value.GetFullyQualifiedEnumValue()); } - return To1Or2Arguments(matchOperator, values, defaultValue); + if (matchOperator.HasValue && matchOperator != MatchOperator.Or) + { + sb.AppendFormat("{0}, ", matchOperator.Value.GetFullyQualifiedEnumValue()); + } + + return sb.Append(ToValueArguments(values, defaultValue)).ToString(); } private static string To1Or2Arguments(MatchOperator? matchOperator, IReadOnlyList matchers) @@ -598,18 +601,6 @@ internal class MappingConverter(MatcherMapper mapper) return sb.ToString(); } - private static string To1Or2Arguments(MatchOperator? matchOperator, string[]? values, string defaultValue) - { - var sb = new StringBuilder(); - - if (matchOperator.HasValue && matchOperator != MatchOperator.Or) - { - sb.AppendFormat("{0}, ", matchOperator.Value.GetFullyQualifiedEnumValue()); - } - - return sb.Append(ToValueArguments(values, defaultValue)).ToString(); - } - private static string ToValueArguments(string[]? values, string defaultValue = "") { return values is { } ? string.Join(", ", values.Select(ToCSharpStringLiteral)) : ToCSharpStringLiteral(defaultValue); diff --git a/src/WireMock.Net/Serialization/MappingConverterSettings.cs b/src/WireMock.Net.Minimal/Serialization/MappingConverterSettings.cs similarity index 100% rename from src/WireMock.Net/Serialization/MappingConverterSettings.cs rename to src/WireMock.Net.Minimal/Serialization/MappingConverterSettings.cs diff --git a/src/WireMock.Net/Serialization/MappingFileNameSanitizer.cs b/src/WireMock.Net.Minimal/Serialization/MappingFileNameSanitizer.cs similarity index 96% rename from src/WireMock.Net/Serialization/MappingFileNameSanitizer.cs rename to src/WireMock.Net.Minimal/Serialization/MappingFileNameSanitizer.cs index c41716d5..e00651be 100644 --- a/src/WireMock.Net/Serialization/MappingFileNameSanitizer.cs +++ b/src/WireMock.Net.Minimal/Serialization/MappingFileNameSanitizer.cs @@ -1,50 +1,50 @@ -// Copyright © WireMock.Net - -using System.IO; -using System.Linq; -using Stef.Validation; -using WireMock.Settings; - -namespace WireMock.Serialization; - -/// -/// Creates sanitized file names for mappings -/// -public class MappingFileNameSanitizer -{ - private const char ReplaceChar = '_'; - - private readonly WireMockServerSettings _settings; - - public MappingFileNameSanitizer(WireMockServerSettings settings) - { - _settings = Guard.NotNull(settings); - } - - /// - /// Creates sanitized file names for mappings - /// - public string BuildSanitizedFileName(IMapping mapping) - { - string name; - if (!string.IsNullOrEmpty(mapping.Title)) - { - // remove 'Proxy Mapping for ' and an extra space character after the HTTP request method - name = mapping.Title.Replace(ProxyAndRecordSettings.DefaultPrefixForSavedMappingFile, "").Replace(' '.ToString(), string.Empty); - if (_settings.ProxyAndRecordSettings?.AppendGuidToSavedMappingFile == true) - { - name += $"{ReplaceChar}{mapping.Guid}"; - } - } - else - { - name = mapping.Guid.ToString(); - } - - if (!string.IsNullOrEmpty(_settings.ProxyAndRecordSettings?.PrefixForSavedMappingFile)) - { - name = $"{_settings.ProxyAndRecordSettings.PrefixForSavedMappingFile}{ReplaceChar}{name}"; - } - return $"{Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, ReplaceChar))}.json"; - } -} +// Copyright © WireMock.Net + +using System.IO; +using System.Linq; +using Stef.Validation; +using WireMock.Settings; + +namespace WireMock.Serialization; + +/// +/// Creates sanitized file names for mappings +/// +public class MappingFileNameSanitizer +{ + private const char ReplaceChar = '_'; + + private readonly WireMockServerSettings _settings; + + public MappingFileNameSanitizer(WireMockServerSettings settings) + { + _settings = Guard.NotNull(settings); + } + + /// + /// Creates sanitized file names for mappings + /// + public string BuildSanitizedFileName(IMapping mapping) + { + string name; + if (!string.IsNullOrEmpty(mapping.Title)) + { + // remove 'Proxy Mapping for ' and an extra space character after the HTTP request method + name = mapping.Title.Replace(ProxyAndRecordSettings.DefaultPrefixForSavedMappingFile, "").Replace(' '.ToString(), string.Empty); + if (_settings.ProxyAndRecordSettings?.AppendGuidToSavedMappingFile == true) + { + name += $"{ReplaceChar}{mapping.Guid}"; + } + } + else + { + name = mapping.Guid.ToString(); + } + + if (!string.IsNullOrEmpty(_settings.ProxyAndRecordSettings?.PrefixForSavedMappingFile)) + { + name = $"{_settings.ProxyAndRecordSettings.PrefixForSavedMappingFile}{ReplaceChar}{name}"; + } + return $"{Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, ReplaceChar))}.json"; + } +} diff --git a/src/WireMock.Net/Serialization/MappingToFileSaver.cs b/src/WireMock.Net.Minimal/Serialization/MappingToFileSaver.cs similarity index 100% rename from src/WireMock.Net/Serialization/MappingToFileSaver.cs rename to src/WireMock.Net.Minimal/Serialization/MappingToFileSaver.cs diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs similarity index 92% rename from src/WireMock.Net/Serialization/MatcherMapper.cs rename to src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs index d8cc8ad5..06b1a284 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net.Minimal/Serialization/MatcherMapper.cs @@ -1,307 +1,301 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Linq; -using AnyOfTypes; -using SimMetrics.Net; -using Stef.Validation; -using WireMock.Admin.Mappings; -using WireMock.Extensions; -using WireMock.Matchers; -using WireMock.Models; -using WireMock.Settings; -using WireMock.Util; - -namespace WireMock.Serialization; - -internal class MatcherMapper -{ - private readonly WireMockServerSettings _settings; - - public MatcherMapper(WireMockServerSettings settings) - { - _settings = Guard.NotNull(settings); - } - - public IMatcher[]? Map(IEnumerable? matchers) - { - return matchers?.Select(Map).OfType().ToArray(); - } - - public IMatcher? Map(MatcherModel? matcherModel) - { - if (matcherModel == null) - { - return null; - } - - string[] parts = matcherModel.Name.Split('.'); - string matcherName = parts[0]; - string? matcherType = parts.Length > 1 ? parts[1] : null; - var stringPatterns = ParseStringPatterns(matcherModel); - var matchBehaviour = matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch; - var matchOperator = StringUtils.ParseMatchOperator(matcherModel.MatchOperator); - bool ignoreCase = matcherModel.IgnoreCase == true; - bool useRegexExtended = _settings.UseRegexExtended == true; - bool useRegex = matcherModel.Regex == true; - - switch (matcherName) - { - case nameof(NotNullOrEmptyMatcher): - return new NotNullOrEmptyMatcher(matchBehaviour); - - case "CSharpCodeMatcher": - if (_settings.AllowCSharpCodeMatcher == true) - { - return TypeLoader.Load(matchBehaviour, matchOperator, stringPatterns); - } - - throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because WireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'."); - - case "LinqMatcher": - throw new NotSupportedException("It's not allowed to use the 'LinqMatcher' due to CVE."); - - //case nameof(LinqMatcher): - // return new LinqMatcher(matchBehaviour, matchOperator, stringPatterns); - - case nameof(ExactMatcher): - return new ExactMatcher(matchBehaviour, ignoreCase, matchOperator, stringPatterns); - - case nameof(ExactObjectMatcher): - return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0]); -#if GRAPHQL - case nameof(GraphQLMatcher): - return new GraphQLMatcher(stringPatterns[0].GetPattern(), matcherModel.CustomScalars, matchBehaviour, matchOperator); -#endif - -#if MIMEKIT - case nameof(MimePartMatcher): - return CreateMimePartMatcher(matchBehaviour, matcherModel); -#endif - -#if PROTOBUF - case nameof(ProtoBufMatcher): - return CreateProtoBufMatcher(matchBehaviour, stringPatterns.GetPatterns(), matcherModel); -#endif - case nameof(RegexMatcher): - return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, useRegexExtended, matchOperator); - - case nameof(JsonMatcher): - var valueForJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns; - return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, useRegex); - - case nameof(JsonPartialMatcher): - var valueForJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns; - return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, useRegex); - - case nameof(JsonPartialWildcardMatcher): - var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns; - return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex); - - case nameof(JsonPathMatcher): - return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns); - - case nameof(JmesPathMatcher): - return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns); - - case nameof(XPathMatcher): - return new XPathMatcher(matchBehaviour, matchOperator, matcherModel.XmlNamespaceMap, stringPatterns); - - case nameof(WildcardMatcher): - return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, matchOperator); - - case nameof(ContentTypeMatcher): - return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase); - - case nameof(FormUrlEncodedMatcher): - return new FormUrlEncodedMatcher(matchBehaviour, stringPatterns, ignoreCase); - - case nameof(SimMetricsMatcher): - SimMetricType type = SimMetricType.Levenstein; - if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type)) - { - throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported."); - } - - return new SimMetricsMatcher(matchBehaviour, stringPatterns, type); - - default: - if (_settings.CustomMatcherMappings != null && _settings.CustomMatcherMappings.ContainsKey(matcherName)) - { - return _settings.CustomMatcherMappings[matcherName](matcherModel); - } - - throw new NotSupportedException($"Matcher '{matcherName}' is not supported."); - } - } - - public MatcherModel[]? Map(IEnumerable? matchers, Action? afterMap = null) - { - return matchers?.Select(m => Map(m, afterMap)).OfType().ToArray(); - } - - public MatcherModel? Map(IMatcher? matcher, Action? afterMap = null) - { - if (matcher == null) - { - return null; - } - - bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : null; - bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null; - - var model = new MatcherModel - { - RejectOnMatch = rejectOnMatch, - IgnoreCase = ignoreCase, - Name = matcher.Name - }; - - switch (matcher) - { - case JsonMatcher jsonMatcher: - model.Regex = jsonMatcher.Regex; - break; - - case XPathMatcher xpathMatcher: - model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap; - break; -#if GRAPHQL - case GraphQLMatcher graphQLMatcher: - model.CustomScalars = graphQLMatcher.CustomScalars; - break; -#endif - } - - switch (matcher) - { - // If the matcher is a IStringMatcher, get the operator & patterns. - case IStringMatcher stringMatcher: - var stringPatterns = stringMatcher.GetPatterns(); - if (stringPatterns.Length == 1) - { - if (stringPatterns[0].IsFirst) - { - model.Pattern = stringPatterns[0].First; - } - else - { - model.Pattern = stringPatterns[0].Second.Pattern; - model.PatternAsFile = stringPatterns[0].Second.PatternAsFile; - } - } - else - { - model.Patterns = stringPatterns.Select(p => p.GetPattern()).Cast().ToArray(); - model.MatchOperator = stringMatcher.MatchOperator.ToString(); - } - break; - - // If the matcher is a IObjectMatcher, get the value (can be string or object or byte[]). - case IObjectMatcher objectMatcher: - model.Pattern = objectMatcher.Value; - break; - -#if MIMEKIT - case MimePartMatcher mimePartMatcher: - model.ContentDispositionMatcher = Map(mimePartMatcher.ContentDispositionMatcher); - model.ContentMatcher = Map(mimePartMatcher.ContentMatcher); - model.ContentTransferEncodingMatcher = Map(mimePartMatcher.ContentTransferEncodingMatcher); - model.ContentTypeMatcher = Map(mimePartMatcher.ContentTypeMatcher); - break; -#endif - -#if PROTOBUF - case ProtoBufMatcher protoBufMatcher: - protoBufMatcher.ProtoDefinition().Value(id => model.Pattern = id, texts => - { - if (texts.Count == 1) - { - model.Pattern = texts[0]; - } - else if (texts.Count > 1) - { - model.Patterns = texts.Cast().ToArray(); - } - }); - - model.ProtoBufMessageType = protoBufMatcher.MessageType; - model.ContentMatcher = Map(protoBufMatcher.Matcher); - break; -#endif - } - - afterMap?.Invoke(model); - - return model; - } - - private AnyOf[] ParseStringPatterns(MatcherModel matcher) - { - if (matcher.Pattern is string patternAsString) - { - return [new AnyOf(patternAsString)]; - } - - if (matcher.Pattern is IEnumerable patternAsStringArray) - { - return patternAsStringArray.ToAnyOfPatterns(); - } - - if (matcher.Patterns?.OfType() is { } patternsAsStringArray) - { - return patternsAsStringArray.ToAnyOfPatterns(); - } - - if (!string.IsNullOrEmpty(matcher.PatternAsFile)) - { - var patternAsFile = matcher.PatternAsFile!; - var pattern = _settings.FileSystemHandler.ReadFileAsString(patternAsFile); - return [new AnyOf(new StringPattern { Pattern = pattern, PatternAsFile = patternAsFile })]; - } - - return EmptyArray>.Value; - } - - private static ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, AnyOf stringPattern) - { - byte[] bytePattern; - try - { - bytePattern = Convert.FromBase64String(stringPattern.GetPattern()); - } - catch - { - throw new ArgumentException($"Matcher 'ExactObjectMatcher' has invalid pattern. The pattern value '{stringPattern}' is not a Base64String.", nameof(stringPattern)); - } - - return new ExactObjectMatcher(matchBehaviour, bytePattern); - } - -#if MIMEKIT - private MimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel matcher) - { - var contentTypeMatcher = Map(matcher?.ContentTypeMatcher) as IStringMatcher; - var contentDispositionMatcher = Map(matcher?.ContentDispositionMatcher) as IStringMatcher; - var contentTransferEncodingMatcher = Map(matcher?.ContentTransferEncodingMatcher) as IStringMatcher; - var contentMatcher = Map(matcher?.ContentMatcher); - - return new MimePartMatcher(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher); - } -#endif - -#if PROTOBUF - private ProtoBufMatcher CreateProtoBufMatcher(MatchBehaviour? matchBehaviour, IReadOnlyList protoDefinitions, MatcherModel matcher) - { - var objectMatcher = Map(matcher.ContentMatcher) as IObjectMatcher; - - return new ProtoBufMatcher( - () => ProtoDefinitionHelper.GetIdOrTexts(_settings, protoDefinitions.ToArray()), - matcher.ProtoBufMessageType!, - matchBehaviour ?? MatchBehaviour.AcceptOnMatch, - objectMatcher - ); - } -#endif +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Linq; +using AnyOfTypes; +using SimMetrics.Net; +using Stef.Validation; +using WireMock.Admin.Mappings; +using WireMock.Extensions; +using WireMock.Matchers; +using WireMock.Models; +using WireMock.Settings; +using WireMock.Util; + +namespace WireMock.Serialization; + +internal class MatcherMapper +{ + private readonly WireMockServerSettings _settings; + + public MatcherMapper(WireMockServerSettings settings) + { + _settings = Guard.NotNull(settings); + } + + public IMatcher[]? Map(IEnumerable? matchers) + { + return matchers?.Select(Map).OfType().ToArray(); + } + + public IMatcher? Map(MatcherModel? matcherModel) + { + if (matcherModel == null) + { + return null; + } + + string[] parts = matcherModel.Name.Split('.'); + string matcherName = parts[0]; + string? matcherType = parts.Length > 1 ? parts[1] : null; + var stringPatterns = ParseStringPatterns(matcherModel); + var matchBehaviour = matcherModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch; + var matchOperator = StringUtils.ParseMatchOperator(matcherModel.MatchOperator); + bool ignoreCase = matcherModel.IgnoreCase == true; + bool useRegexExtended = _settings.UseRegexExtended == true; + bool useRegex = matcherModel.Regex == true; + + switch (matcherName) + { + case nameof(NotNullOrEmptyMatcher): + return new NotNullOrEmptyMatcher(matchBehaviour); + + case "CSharpCodeMatcher": + if (_settings.AllowCSharpCodeMatcher == true) + { + return TypeLoader.LoadNewInstance(matchBehaviour, matchOperator, stringPatterns); + } + + throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because WireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'."); + + case "LinqMatcher": + throw new NotSupportedException("It's not allowed to use the 'LinqMatcher' due to CVE."); + + //case nameof(LinqMatcher): + // return new LinqMatcher(matchBehaviour, matchOperator, stringPatterns); + + case nameof(ExactMatcher): + return new ExactMatcher(matchBehaviour, ignoreCase, matchOperator, stringPatterns); + + case nameof(ExactObjectMatcher): + return CreateExactObjectMatcher(matchBehaviour, stringPatterns[0]); +#if GRAPHQL + case nameof(GraphQLMatcher): + return new GraphQLMatcher(stringPatterns[0].GetPattern(), matcherModel.CustomScalars, matchBehaviour, matchOperator); +#endif + + case "MimePartMatcher": + return CreateMimePartMatcher(matchBehaviour, matcherModel); + +#if PROTOBUF + case nameof(ProtoBufMatcher): + return CreateProtoBufMatcher(matchBehaviour, stringPatterns.GetPatterns(), matcherModel); +#endif + case nameof(RegexMatcher): + return new RegexMatcher(matchBehaviour, stringPatterns, ignoreCase, useRegexExtended, matchOperator); + + case nameof(JsonMatcher): + var valueForJsonMatcher = matcherModel.Pattern ?? matcherModel.Patterns; + return new JsonMatcher(matchBehaviour, valueForJsonMatcher!, ignoreCase, useRegex); + + case nameof(JsonPartialMatcher): + var valueForJsonPartialMatcher = matcherModel.Pattern ?? matcherModel.Patterns; + return new JsonPartialMatcher(matchBehaviour, valueForJsonPartialMatcher!, ignoreCase, useRegex); + + case nameof(JsonPartialWildcardMatcher): + var valueForJsonPartialWildcardMatcher = matcherModel.Pattern ?? matcherModel.Patterns; + return new JsonPartialWildcardMatcher(matchBehaviour, valueForJsonPartialWildcardMatcher!, ignoreCase, useRegex); + + case nameof(JsonPathMatcher): + return new JsonPathMatcher(matchBehaviour, matchOperator, stringPatterns); + + case nameof(JmesPathMatcher): + return new JmesPathMatcher(matchBehaviour, matchOperator, stringPatterns); + + case nameof(XPathMatcher): + return new XPathMatcher(matchBehaviour, matchOperator, matcherModel.XmlNamespaceMap, stringPatterns); + + case nameof(WildcardMatcher): + return new WildcardMatcher(matchBehaviour, stringPatterns, ignoreCase, matchOperator); + + case nameof(ContentTypeMatcher): + return new ContentTypeMatcher(matchBehaviour, stringPatterns, ignoreCase); + + case nameof(FormUrlEncodedMatcher): + return new FormUrlEncodedMatcher(matchBehaviour, stringPatterns, ignoreCase); + + case nameof(SimMetricsMatcher): + SimMetricType type = SimMetricType.Levenstein; + if (!string.IsNullOrEmpty(matcherType) && !Enum.TryParse(matcherType, out type)) + { + throw new NotSupportedException($"Matcher '{matcherName}' with Type '{matcherType}' is not supported."); + } + + return new SimMetricsMatcher(matchBehaviour, stringPatterns, type); + + default: + if (_settings.CustomMatcherMappings != null && _settings.CustomMatcherMappings.ContainsKey(matcherName)) + { + return _settings.CustomMatcherMappings[matcherName](matcherModel); + } + + throw new NotSupportedException($"Matcher '{matcherName}' is not supported."); + } + } + + public MatcherModel[]? Map(IEnumerable? matchers, Action? afterMap = null) + { + return matchers?.Select(m => Map(m, afterMap)).OfType().ToArray(); + } + + public MatcherModel? Map(IMatcher? matcher, Action? afterMap = null) + { + if (matcher == null) + { + return null; + } + + bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : null; + bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : null; + + var model = new MatcherModel + { + RejectOnMatch = rejectOnMatch, + IgnoreCase = ignoreCase, + Name = matcher.Name + }; + + switch (matcher) + { + case JsonMatcher jsonMatcher: + model.Regex = jsonMatcher.Regex; + break; + + case XPathMatcher xpathMatcher: + model.XmlNamespaceMap = xpathMatcher.XmlNamespaceMap; + break; +#if GRAPHQL + case GraphQLMatcher graphQLMatcher: + model.CustomScalars = graphQLMatcher.CustomScalars; + break; +#endif + } + + switch (matcher) + { + // If the matcher is a IStringMatcher, get the operator & patterns. + case IStringMatcher stringMatcher: + var stringPatterns = stringMatcher.GetPatterns(); + if (stringPatterns.Length == 1) + { + if (stringPatterns[0].IsFirst) + { + model.Pattern = stringPatterns[0].First; + } + else + { + model.Pattern = stringPatterns[0].Second.Pattern; + model.PatternAsFile = stringPatterns[0].Second.PatternAsFile; + } + } + else + { + model.Patterns = stringPatterns.Select(p => p.GetPattern()).Cast().ToArray(); + model.MatchOperator = stringMatcher.MatchOperator.ToString(); + } + break; + + // If the matcher is a IObjectMatcher, get the value (can be string or object or byte[]). + case IObjectMatcher objectMatcher: + model.Pattern = objectMatcher.Value; + break; + + case IMimePartMatcher mimePartMatcher: + model.ContentDispositionMatcher = Map(mimePartMatcher.ContentDispositionMatcher); + model.ContentMatcher = Map(mimePartMatcher.ContentMatcher); + model.ContentTransferEncodingMatcher = Map(mimePartMatcher.ContentTransferEncodingMatcher); + model.ContentTypeMatcher = Map(mimePartMatcher.ContentTypeMatcher); + break; + +#if PROTOBUF + case ProtoBufMatcher protoBufMatcher: + protoBufMatcher.ProtoDefinition().Value(id => model.Pattern = id, texts => + { + if (texts.Count == 1) + { + model.Pattern = texts[0]; + } + else if (texts.Count > 1) + { + model.Patterns = texts.Cast().ToArray(); + } + }); + + model.ProtoBufMessageType = protoBufMatcher.MessageType; + model.ContentMatcher = Map(protoBufMatcher.Matcher); + break; +#endif + } + + afterMap?.Invoke(model); + + return model; + } + + private AnyOf[] ParseStringPatterns(MatcherModel matcher) + { + if (matcher.Pattern is string patternAsString) + { + return [new AnyOf(patternAsString)]; + } + + if (matcher.Pattern is IEnumerable patternAsStringArray) + { + return patternAsStringArray.ToAnyOfPatterns(); + } + + if (matcher.Patterns?.OfType() is { } patternsAsStringArray) + { + return patternsAsStringArray.ToAnyOfPatterns(); + } + + if (!string.IsNullOrEmpty(matcher.PatternAsFile)) + { + var patternAsFile = matcher.PatternAsFile!; + var pattern = _settings.FileSystemHandler.ReadFileAsString(patternAsFile); + return [new AnyOf(new StringPattern { Pattern = pattern, PatternAsFile = patternAsFile })]; + } + + return []; + } + + private static ExactObjectMatcher CreateExactObjectMatcher(MatchBehaviour matchBehaviour, AnyOf stringPattern) + { + byte[] bytePattern; + try + { + bytePattern = Convert.FromBase64String(stringPattern.GetPattern()); + } + catch + { + throw new ArgumentException($"Matcher 'ExactObjectMatcher' has invalid pattern. The pattern value '{stringPattern}' is not a Base64String.", nameof(stringPattern)); + } + + return new ExactObjectMatcher(matchBehaviour, bytePattern); + } + + private IMimePartMatcher CreateMimePartMatcher(MatchBehaviour matchBehaviour, MatcherModel matcher) + { + var contentTypeMatcher = Map(matcher?.ContentTypeMatcher) as IStringMatcher; + var contentDispositionMatcher = Map(matcher?.ContentDispositionMatcher) as IStringMatcher; + var contentTransferEncodingMatcher = Map(matcher?.ContentTransferEncodingMatcher) as IStringMatcher; + var contentMatcher = Map(matcher?.ContentMatcher); + + return TypeLoader.LoadNewInstance(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher); + } + +#if PROTOBUF + private ProtoBufMatcher CreateProtoBufMatcher(MatchBehaviour? matchBehaviour, IReadOnlyList protoDefinitions, MatcherModel matcher) + { + var objectMatcher = Map(matcher.ContentMatcher) as IObjectMatcher; + + return new ProtoBufMatcher( + () => ProtoDefinitionHelper.GetIdOrTexts(_settings, protoDefinitions.ToArray()), + matcher.ProtoBufMessageType!, + matchBehaviour ?? MatchBehaviour.AcceptOnMatch, + objectMatcher + ); + } +#endif } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/PactMapper.cs b/src/WireMock.Net.Minimal/Serialization/PactMapper.cs similarity index 100% rename from src/WireMock.Net/Serialization/PactMapper.cs rename to src/WireMock.Net.Minimal/Serialization/PactMapper.cs diff --git a/src/WireMock.Net/Serialization/ProxyMappingConverter.cs b/src/WireMock.Net.Minimal/Serialization/ProxyMappingConverter.cs similarity index 98% rename from src/WireMock.Net/Serialization/ProxyMappingConverter.cs rename to src/WireMock.Net.Minimal/Serialization/ProxyMappingConverter.cs index 7fe7ae85..2100c9f5 100644 --- a/src/WireMock.Net/Serialization/ProxyMappingConverter.cs +++ b/src/WireMock.Net.Minimal/Serialization/ProxyMappingConverter.cs @@ -31,9 +31,9 @@ internal class ProxyMappingConverter public IMapping? ToMapping(IMapping? mapping, ProxyAndRecordSettings proxyAndRecordSettings, IRequestMessage requestMessage, ResponseMessage responseMessage) { var useDefinedRequestMatchers = proxyAndRecordSettings.UseDefinedRequestMatchers; - var excludedHeaders = new List(proxyAndRecordSettings.ExcludedHeaders ?? new string[] { }) { "Cookie" }; - var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? EmptyArray.Value; - var excludedParams = proxyAndRecordSettings.ExcludedParams ?? EmptyArray.Value; + var excludedHeaders = new List(proxyAndRecordSettings.ExcludedHeaders ?? []) { "Cookie" }; + var excludedCookies = proxyAndRecordSettings.ExcludedCookies ?? []; + var excludedParams = proxyAndRecordSettings.ExcludedParams ?? []; var request = (Request?)mapping?.RequestMatcher; var clientIPMatcher = request?.GetRequestMessageMatcher(); diff --git a/src/WireMock.Net/Serialization/SwaggerMapper.cs b/src/WireMock.Net.Minimal/Serialization/SwaggerMapper.cs similarity index 100% rename from src/WireMock.Net/Serialization/SwaggerMapper.cs rename to src/WireMock.Net.Minimal/Serialization/SwaggerMapper.cs diff --git a/src/WireMock.Net/Serialization/TimeSettingsMapper.cs b/src/WireMock.Net.Minimal/Serialization/TimeSettingsMapper.cs similarity index 100% rename from src/WireMock.Net/Serialization/TimeSettingsMapper.cs rename to src/WireMock.Net.Minimal/Serialization/TimeSettingsMapper.cs diff --git a/src/WireMock.Net/Serialization/WebhookMapper.cs b/src/WireMock.Net.Minimal/Serialization/WebhookMapper.cs similarity index 97% rename from src/WireMock.Net/Serialization/WebhookMapper.cs rename to src/WireMock.Net.Minimal/Serialization/WebhookMapper.cs index 610b8c2c..01ff0fec 100644 --- a/src/WireMock.Net/Serialization/WebhookMapper.cs +++ b/src/WireMock.Net.Minimal/Serialization/WebhookMapper.cs @@ -1,118 +1,118 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Linq; -using Stef.Validation; -using WireMock.Admin.Mappings; -using WireMock.Http; -using WireMock.Models; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Serialization; - -internal static class WebhookMapper -{ - public static IWebhook Map(WebhookModel model) - { - var webhook = new Webhook - { - Request = new WebhookRequest - { - Url = model.Request.Url, - Method = model.Request.Method, - Delay = model.Request.Delay, - MinimumRandomDelay = model.Request.MinimumRandomDelay, - MaximumRandomDelay = model.Request.MaximumRandomDelay, - Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList(x.Value)) ?? new Dictionary>() - } - }; - - if (model.Request.UseTransformer == true) - { - webhook.Request.UseTransformer = true; - - if (!Enum.TryParse(model.Request.TransformerType, out var transformerType)) - { - transformerType = TransformerType.Handlebars; - } - webhook.Request.TransformerType = transformerType; - - if (!Enum.TryParse(model.Request.TransformerReplaceNodeOptions, out var option)) - { - option = ReplaceNodeOptions.EvaluateAndTryToConvert; - } - webhook.Request.TransformerReplaceNodeOptions = option; - } - - IEnumerable? contentTypeHeader = null; - if (webhook.Request.Headers != null && webhook.Request.Headers.Any(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase))) - { - contentTypeHeader = webhook.Request.Headers.First(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)).Value; - } - - if (model.Request.Body != null) - { - webhook.Request.BodyData = new BodyData - { - BodyAsString = model.Request.Body, - DetectedBodyType = BodyType.String, - DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault()) - }; - } - else if (model.Request.BodyAsJson != null) - { - webhook.Request.BodyData = new BodyData - { - BodyAsJson = model.Request.BodyAsJson, - DetectedBodyType = BodyType.Json, - DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault()) - }; - } - - return webhook; - } - - public static WebhookModel Map(IWebhook webhook) - { - Guard.NotNull(webhook); - - var model = new WebhookModel - { - Request = new WebhookRequestModel - { - Url = webhook.Request.Url, - Method = webhook.Request.Method, - Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()), - UseTransformer = webhook.Request.UseTransformer, - TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null, - TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString(), - Delay = webhook.Request.Delay, - MinimumRandomDelay = webhook.Request.MinimumRandomDelay, - MaximumRandomDelay = webhook.Request.MaximumRandomDelay, - } - }; - - if (webhook.Request.BodyData != null) - { - switch (webhook.Request.BodyData.DetectedBodyType) - { - case BodyType.String: - case BodyType.FormUrlEncoded: - model.Request.Body = webhook.Request.BodyData.BodyAsString; - break; - - case BodyType.Json: - model.Request.BodyAsJson = webhook.Request.BodyData.BodyAsJson; - break; - - default: - // Empty - break; - } - } - - return model; - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Linq; +using Stef.Validation; +using WireMock.Admin.Mappings; +using WireMock.Http; +using WireMock.Models; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Serialization; + +internal static class WebhookMapper +{ + public static IWebhook Map(WebhookModel model) + { + var webhook = new Webhook + { + Request = new WebhookRequest + { + Url = model.Request.Url, + Method = model.Request.Method, + Delay = model.Request.Delay, + MinimumRandomDelay = model.Request.MinimumRandomDelay, + MaximumRandomDelay = model.Request.MaximumRandomDelay, + Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList(x.Value)) ?? new Dictionary>() + } + }; + + if (model.Request.UseTransformer == true) + { + webhook.Request.UseTransformer = true; + + if (!Enum.TryParse(model.Request.TransformerType, out var transformerType)) + { + transformerType = TransformerType.Handlebars; + } + webhook.Request.TransformerType = transformerType; + + if (!Enum.TryParse(model.Request.TransformerReplaceNodeOptions, out var option)) + { + option = ReplaceNodeOptions.EvaluateAndTryToConvert; + } + webhook.Request.TransformerReplaceNodeOptions = option; + } + + IEnumerable? contentTypeHeader = null; + if (webhook.Request.Headers != null && webhook.Request.Headers.Any(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase))) + { + contentTypeHeader = webhook.Request.Headers.First(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase)).Value; + } + + if (model.Request.Body != null) + { + webhook.Request.BodyData = new BodyData + { + BodyAsString = model.Request.Body, + DetectedBodyType = BodyType.String, + DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault()) + }; + } + else if (model.Request.BodyAsJson != null) + { + webhook.Request.BodyData = new BodyData + { + BodyAsJson = model.Request.BodyAsJson, + DetectedBodyType = BodyType.Json, + DetectedBodyTypeFromContentType = BodyParser.DetectBodyTypeFromContentType(contentTypeHeader?.FirstOrDefault()) + }; + } + + return webhook; + } + + public static WebhookModel Map(IWebhook webhook) + { + Guard.NotNull(webhook); + + var model = new WebhookModel + { + Request = new WebhookRequestModel + { + Url = webhook.Request.Url, + Method = webhook.Request.Method, + Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()), + UseTransformer = webhook.Request.UseTransformer, + TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null, + TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString(), + Delay = webhook.Request.Delay, + MinimumRandomDelay = webhook.Request.MinimumRandomDelay, + MaximumRandomDelay = webhook.Request.MaximumRandomDelay, + } + }; + + if (webhook.Request.BodyData != null) + { + switch (webhook.Request.BodyData.DetectedBodyType) + { + case BodyType.String: + case BodyType.FormUrlEncoded: + model.Request.Body = webhook.Request.BodyData.BodyAsString; + break; + + case BodyType.Json: + model.Request.BodyAsJson = webhook.Request.BodyData.BodyAsJson; + break; + + default: + // Empty + break; + } + } + + return model; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/IRespondWithAProvider.cs b/src/WireMock.Net.Minimal/Server/IRespondWithAProvider.cs similarity index 97% rename from src/WireMock.Net/Server/IRespondWithAProvider.cs rename to src/WireMock.Net.Minimal/Server/IRespondWithAProvider.cs index 4d1ffa51..57c6cd87 100644 --- a/src/WireMock.Net/Server/IRespondWithAProvider.cs +++ b/src/WireMock.Net.Minimal/Server/IRespondWithAProvider.cs @@ -1,255 +1,255 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Net; -using WireMock.Models; -using WireMock.ResponseBuilders; -using WireMock.ResponseProviders; -using WireMock.Settings; -using WireMock.Types; - -namespace WireMock.Server; - -/// -/// IRespondWithAProvider -/// -public interface IRespondWithAProvider -{ - /// - /// Gets the unique identifier for this mapping. - /// - Guid Guid { get; } - - /// - /// Define a unique identifier for this mapping. - /// - /// The unique identifier. - /// The . - IRespondWithAProvider WithGuid(Guid guid); - - /// - /// Define a unique identifier for this mapping. - /// - /// The unique identifier. - /// The . - IRespondWithAProvider WithGuid(string guid); - - /// - /// Define a unique identifier for this mapping. - /// - /// The unique identifier. - /// The . - IRespondWithAProvider DefineGuid(Guid guid); - - /// - /// Define a unique identifier for this mapping. - /// - /// The unique identifier. - /// The . - IRespondWithAProvider DefineGuid(string guid); - - /// - /// Define the TimeSettings for this mapping. - /// - /// The TimeSettings. - /// The . - IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings); - - /// - /// Define a unique title for this mapping. - /// - /// The unique title. - /// The . - IRespondWithAProvider WithTitle(string title); - - /// - /// Define a description for this mapping. - /// - /// The description. - /// The . - IRespondWithAProvider WithDescription(string description); - - /// - /// Define the full filepath for this mapping. - /// - /// The full filepath. - /// The . - IRespondWithAProvider WithPath(string path); - - /// - /// Define the priority for this mapping. - /// - /// The priority. (A lower value means a higher priority.) - /// The . - IRespondWithAProvider AtPriority(int priority); - - /// - /// RespondWith - /// - /// The provider. - void RespondWith(IResponseProvider provider); - - /// - /// RespondWith - /// - /// The action to use the fluent . - void ThenRespondWith(Action action); - - /// - /// RespondWith a status code 200 (OK); - /// - void ThenRespondWithOK(); - - /// - /// RespondWith a status code. - /// By default all status codes are allowed, to change this behaviour, see . - /// - /// The code. - void ThenRespondWithStatusCode(int code); - - /// - /// RespondWith a status code. - /// By default all status codes are allowed, to change this behaviour, see . - /// - /// The code. - void ThenRespondWithStatusCode(string code); - - /// - /// RespondWith a status code. - /// By default all status codes are allowed, to change this behaviour, see . - /// - /// The code. - void ThenRespondWithStatusCode(HttpStatusCode code); - - /// - /// Sets the scenario. - /// - /// The scenario. - /// The . - IRespondWithAProvider InScenario(string scenario); - - /// - /// Sets the scenario with an integer value. - /// - /// The scenario. - /// The . - IRespondWithAProvider InScenario(int scenario); - - /// - /// Execute this respond only in case the current state is equal to specified one. - /// - /// Any object which identifies the current state - /// The . - IRespondWithAProvider WhenStateIs(string state); - - /// - /// Execute this respond only in case the current state is equal to specified one. - /// - /// Any object which identifies the current state - /// The . - IRespondWithAProvider WhenStateIs(int state); - - /// - /// Once this mapping is executed the state will be changed to specified one. - /// - /// Any object which identifies the new state - /// The number of times this match should be matched before the state will be changed to the specified one. Default value is 1. - /// The . - IRespondWithAProvider WillSetStateTo(string state, int? times = 1); - - /// - /// Once this mapping is executed the state will be changed to specified one. - /// - /// Any object which identifies the new state - /// The number of times this match should be matched before the state will be changed to the specified one. Default value is 1. - /// The . - IRespondWithAProvider WillSetStateTo(int state, int? times = 1); - - /// - /// Add (multiple) Webhook(s) to call after the response has been generated. - /// - /// The Webhooks - /// The . - IRespondWithAProvider WithWebhook(params IWebhook[] webhooks); - - /// - /// Support FireAndForget for any configured Webhooks - /// - /// - /// - IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget); - - /// - /// Add a Webhook to call after the response has been generated. - /// - /// The Webhook Url - /// The method to use. [optional] - /// The Headers to send. [optional] - /// The body (as string) to send. [optional] - /// Use Transformer. [optional] - /// The transformer type. [optional] - /// The . - IRespondWithAProvider WithWebhook( - string url, - string method = "post", - IDictionary>? headers = null, - string? body = null, - bool useTransformer = true, - TransformerType transformerType = TransformerType.Handlebars - ); - - /// - /// Add a Webhook to call after the response has been generated. - /// - /// The Webhook Url - /// The method to use. [optional] - /// The Headers to send. [optional] - /// The body (as json) to send. [optional] - /// Use Transformer. [optional] - /// The transformer type. [optional] - /// The . - IRespondWithAProvider WithWebhook( - string url, - string method = "post", - IDictionary>? headers = null, - object? body = null, - bool useTransformer = true, - TransformerType transformerType = TransformerType.Handlebars - ); - - /// - /// Data Object which can be used when WithTransformer is used. - /// e.g. lookup a path in this object using - /// The data dictionary object. - /// - /// lookup data "1" - /// - /// - /// The . - IRespondWithAProvider WithData(object data); - - /// - /// Define the probability when this request should be matched. Value is between 0 and 1. - /// - /// The probability when this request should be matched. Value is between 0 and 1. - /// The . - IRespondWithAProvider WithProbability(double probability); - - /// - /// Define a Grpc ProtoDefinition which is used for the request and the response. - /// This can be a ProtoDefinition as a string, or an id when the ProtoDefinitions are defined at the WireMockServer. - /// - /// The proto definition as text or as id. - /// The . - IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId); - - /// - /// Define a GraphQL Schema which is used for the request and the response. - /// This can be a GraphQL Schema as a string, or an id when the GraphQL Schema are defined at the WireMockServer. - /// - /// The GraphQL Schema as text or as id. - /// A dictionary defining the custom scalars used in this schema. [optional] - /// The . - IRespondWithAProvider WithGraphQLSchema(string graphQLSchemaOrId, IDictionary? customScalars = null); +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Net; +using WireMock.Models; +using WireMock.ResponseBuilders; +using WireMock.ResponseProviders; +using WireMock.Settings; +using WireMock.Types; + +namespace WireMock.Server; + +/// +/// IRespondWithAProvider +/// +public interface IRespondWithAProvider +{ + /// + /// Gets the unique identifier for this mapping. + /// + Guid Guid { get; } + + /// + /// Define a unique identifier for this mapping. + /// + /// The unique identifier. + /// The . + IRespondWithAProvider WithGuid(Guid guid); + + /// + /// Define a unique identifier for this mapping. + /// + /// The unique identifier. + /// The . + IRespondWithAProvider WithGuid(string guid); + + /// + /// Define a unique identifier for this mapping. + /// + /// The unique identifier. + /// The . + IRespondWithAProvider DefineGuid(Guid guid); + + /// + /// Define a unique identifier for this mapping. + /// + /// The unique identifier. + /// The . + IRespondWithAProvider DefineGuid(string guid); + + /// + /// Define the TimeSettings for this mapping. + /// + /// The TimeSettings. + /// The . + IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings); + + /// + /// Define a unique title for this mapping. + /// + /// The unique title. + /// The . + IRespondWithAProvider WithTitle(string title); + + /// + /// Define a description for this mapping. + /// + /// The description. + /// The . + IRespondWithAProvider WithDescription(string description); + + /// + /// Define the full filepath for this mapping. + /// + /// The full filepath. + /// The . + IRespondWithAProvider WithPath(string path); + + /// + /// Define the priority for this mapping. + /// + /// The priority. (A lower value means a higher priority.) + /// The . + IRespondWithAProvider AtPriority(int priority); + + /// + /// RespondWith + /// + /// The provider. + void RespondWith(IResponseProvider provider); + + /// + /// RespondWith + /// + /// The action to use the fluent . + void ThenRespondWith(Action action); + + /// + /// RespondWith a status code 200 (OK); + /// + void ThenRespondWithOK(); + + /// + /// RespondWith a status code. + /// By default all status codes are allowed, to change this behaviour, see . + /// + /// The code. + void ThenRespondWithStatusCode(int code); + + /// + /// RespondWith a status code. + /// By default all status codes are allowed, to change this behaviour, see . + /// + /// The code. + void ThenRespondWithStatusCode(string code); + + /// + /// RespondWith a status code. + /// By default all status codes are allowed, to change this behaviour, see . + /// + /// The code. + void ThenRespondWithStatusCode(HttpStatusCode code); + + /// + /// Sets the scenario. + /// + /// The scenario. + /// The . + IRespondWithAProvider InScenario(string scenario); + + /// + /// Sets the scenario with an integer value. + /// + /// The scenario. + /// The . + IRespondWithAProvider InScenario(int scenario); + + /// + /// Execute this respond only in case the current state is equal to specified one. + /// + /// Any object which identifies the current state + /// The . + IRespondWithAProvider WhenStateIs(string state); + + /// + /// Execute this respond only in case the current state is equal to specified one. + /// + /// Any object which identifies the current state + /// The . + IRespondWithAProvider WhenStateIs(int state); + + /// + /// Once this mapping is executed the state will be changed to specified one. + /// + /// Any object which identifies the new state + /// The number of times this match should be matched before the state will be changed to the specified one. Default value is 1. + /// The . + IRespondWithAProvider WillSetStateTo(string state, int? times = 1); + + /// + /// Once this mapping is executed the state will be changed to specified one. + /// + /// Any object which identifies the new state + /// The number of times this match should be matched before the state will be changed to the specified one. Default value is 1. + /// The . + IRespondWithAProvider WillSetStateTo(int state, int? times = 1); + + /// + /// Add (multiple) Webhook(s) to call after the response has been generated. + /// + /// The Webhooks + /// The . + IRespondWithAProvider WithWebhook(params IWebhook[] webhooks); + + /// + /// Support FireAndForget for any configured Webhooks + /// + /// + /// + IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget); + + /// + /// Add a Webhook to call after the response has been generated. + /// + /// The Webhook Url + /// The method to use. [optional] + /// The Headers to send. [optional] + /// The body (as string) to send. [optional] + /// Use Transformer. [optional] + /// The transformer type. [optional] + /// The . + IRespondWithAProvider WithWebhook( + string url, + string method = "post", + IDictionary>? headers = null, + string? body = null, + bool useTransformer = true, + TransformerType transformerType = TransformerType.Handlebars + ); + + /// + /// Add a Webhook to call after the response has been generated. + /// + /// The Webhook Url + /// The method to use. [optional] + /// The Headers to send. [optional] + /// The body (as json) to send. [optional] + /// Use Transformer. [optional] + /// The transformer type. [optional] + /// The . + IRespondWithAProvider WithWebhook( + string url, + string method = "post", + IDictionary>? headers = null, + object? body = null, + bool useTransformer = true, + TransformerType transformerType = TransformerType.Handlebars + ); + + /// + /// Data Object which can be used when WithTransformer is used. + /// e.g. lookup a path in this object using + /// The data dictionary object. + /// + /// lookup data "1" + /// + /// + /// The . + IRespondWithAProvider WithData(object data); + + /// + /// Define the probability when this request should be matched. Value is between 0 and 1. + /// + /// The probability when this request should be matched. Value is between 0 and 1. + /// The . + IRespondWithAProvider WithProbability(double probability); + + /// + /// Define a Grpc ProtoDefinition which is used for the request and the response. + /// This can be a ProtoDefinition as a string, or an id when the ProtoDefinitions are defined at the WireMockServer. + /// + /// The proto definition as text or as id. + /// The . + IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId); + + /// + /// Define a GraphQL Schema which is used for the request and the response. + /// This can be a GraphQL Schema as a string, or an id when the GraphQL Schema are defined at the WireMockServer. + /// + /// The GraphQL Schema as text or as id. + /// A dictionary defining the custom scalars used in this schema. [optional] + /// The . + IRespondWithAProvider WithGraphQLSchema(string graphQLSchemaOrId, IDictionary? customScalars = null); } \ No newline at end of file diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net.Minimal/Server/RespondWithAProvider.cs similarity index 96% rename from src/WireMock.Net/Server/RespondWithAProvider.cs rename to src/WireMock.Net.Minimal/Server/RespondWithAProvider.cs index 5407159e..2dc4e07c 100644 --- a/src/WireMock.Net/Server/RespondWithAProvider.cs +++ b/src/WireMock.Net.Minimal/Server/RespondWithAProvider.cs @@ -1,404 +1,404 @@ -// Copyright © WireMock.Net and mock4net by Alexandre Victoor - -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. -// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. -using System; -using System.Collections.Generic; -using System.Net; -using Stef.Validation; -using WireMock.Matchers.Request; -using WireMock.Models; -using WireMock.ResponseBuilders; -using WireMock.ResponseProviders; -using WireMock.Settings; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Server; - -/// -/// The RespondWithAProvider. -/// -internal class RespondWithAProvider : IRespondWithAProvider -{ - private readonly RegistrationCallback _registrationCallback; - private readonly IRequestMatcher _requestMatcher; - private readonly WireMockServerSettings _settings; - private readonly IDateTimeUtils _dateTimeUtils; - private readonly bool _saveToFile; - - private int _priority; - private string? _title; - private string? _description; - private string? _path; - private string? _executionConditionState; - private string? _nextState; - private string? _scenario; - private int _timesInSameState = 1; - private bool? _useWebhookFireAndForget; - private double? _probability; - private GraphQLSchemaDetails? _graphQLSchemaDetails; - - public Guid Guid { get; private set; } - - public IWebhook[]? Webhooks { get; private set; } - - public ITimeSettings? TimeSettings { get; private set; } - - public object? Data { get; private set; } - - public IdOrTexts? ProtoDefinition { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The registration callback. - /// The request matcher. - /// The WireMockServerSettings. - /// GuidUtils to make unit testing possible. - /// DateTimeUtils to make unit testing possible. - /// Optional boolean to indicate if this mapping should be saved as static mapping file. - public RespondWithAProvider( - RegistrationCallback registrationCallback, - IRequestMatcher requestMatcher, - WireMockServerSettings settings, - IGuidUtils guidUtils, - IDateTimeUtils dateTimeUtils, - bool saveToFile = false - ) - { - _registrationCallback = Guard.NotNull(registrationCallback); - _requestMatcher = Guard.NotNull(requestMatcher); - _settings = Guard.NotNull(settings); - _dateTimeUtils = Guard.NotNull(dateTimeUtils); - _saveToFile = saveToFile; - - Guid = guidUtils.NewGuid(); - } - - /// - public void RespondWith(IResponseProvider provider) - { - var mapping = new Mapping - ( - Guid, - _dateTimeUtils.UtcNow, - _title, - _description, - _path, - _settings, - _requestMatcher, - provider, - _priority, - _scenario, - _executionConditionState, - _nextState, - _timesInSameState, - Webhooks, - _useWebhookFireAndForget, - TimeSettings, - Data - ); - - if (_probability != null) - { - mapping.WithProbability(_probability.Value); - } - - if (ProtoDefinition != null) - { - mapping.WithProtoDefinition(ProtoDefinition.Value); - } - - _registrationCallback(mapping, _saveToFile); - } - - /// - public void ThenRespondWith(Action action) - { - var responseBuilder = Response.Create(); - - action(responseBuilder); - - RespondWith(responseBuilder); - } - - /// - public void ThenRespondWithOK() - { - var responseBuilder = Response.Create(); - - RespondWith(responseBuilder); - } - - /// - public void ThenRespondWithStatusCode(int code) - { - var responseBuilder = Response.Create().WithStatusCode(code); - - RespondWith(responseBuilder); - } - - /// - public void ThenRespondWithStatusCode(string code) - { - var responseBuilder = Response.Create().WithStatusCode(code); - - RespondWith(responseBuilder); - } - - /// - public void ThenRespondWithStatusCode(HttpStatusCode code) - { - var responseBuilder = Response.Create().WithStatusCode(code); - - RespondWith(responseBuilder); - } - - /// - public IRespondWithAProvider WithData(object data) - { - Data = data; - return this; - } - - /// - public IRespondWithAProvider WithGuid(string guid) - { - return WithGuid(Guid.Parse(guid)); - } - - /// - public IRespondWithAProvider WithGuid(Guid guid) - { - Guid = guid; - return this; - } - - /// - public IRespondWithAProvider DefineGuid(Guid guid) - { - return WithGuid(guid); - } - - /// - public IRespondWithAProvider DefineGuid(string guid) - { - return WithGuid(guid); - } - - /// - public IRespondWithAProvider WithTitle(string title) - { - _title = title; - - return this; - } - - /// - public IRespondWithAProvider WithDescription(string description) - { - _description = description; - return this; - } - - /// - public IRespondWithAProvider WithPath(string path) - { - _path = path; - return this; - } - - /// - public IRespondWithAProvider AtPriority(int priority) - { - _priority = priority; - return this; - } - - /// - public IRespondWithAProvider InScenario(string scenario) - { - _scenario = Guard.NotNullOrWhiteSpace(scenario); - return this; - } - - /// - public IRespondWithAProvider InScenario(int scenario) - { - return InScenario(scenario.ToString()); - } - - /// - public IRespondWithAProvider WhenStateIs(string state) - { - if (string.IsNullOrEmpty(_scenario)) - { - throw new NotSupportedException("Unable to set state condition when no scenario is defined."); - } - - _executionConditionState = state; - - return this; - } - - /// - public IRespondWithAProvider WhenStateIs(int state) - { - return WhenStateIs(state.ToString()); - } - - /// - public IRespondWithAProvider WillSetStateTo(string state, int? times = 1) - { - if (string.IsNullOrEmpty(_scenario)) - { - throw new NotSupportedException("Unable to set next state when no scenario is defined."); - } - - _nextState = state; - _timesInSameState = times ?? 1; - - return this; - } - - /// - public IRespondWithAProvider WillSetStateTo(int state, int? times = 1) - { - return WillSetStateTo(state.ToString(), times); - } - - /// - public IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings) - { - TimeSettings = Guard.NotNull(timeSettings); - - return this; - } - - /// - public IRespondWithAProvider WithWebhook(params IWebhook[] webhooks) - { - Guard.HasNoNulls(webhooks); - - Webhooks = webhooks; - return this; - } - - /// - public IRespondWithAProvider WithWebhook( - string url, - string method = "post", - IDictionary>? headers = null, - string? body = null, - bool useTransformer = true, - TransformerType transformerType = TransformerType.Handlebars) - { - Guard.NotNull(url); - Guard.NotNull(method); - - Webhooks = [InitWebhook(url, method, headers, useTransformer, transformerType)]; - - if (body != null) - { - Webhooks[0].Request.BodyData = new BodyData - { - BodyAsString = body, - DetectedBodyType = BodyType.String, - DetectedBodyTypeFromContentType = BodyType.String - }; - } - - return this; - } - - /// - public IRespondWithAProvider WithWebhook( - string url, - string method = "post", - IDictionary>? headers = null, - object? body = null, - bool useTransformer = true, - TransformerType transformerType = TransformerType.Handlebars) - { - Guard.NotNull(url); - Guard.NotNull(method); - - Webhooks = [InitWebhook(url, method, headers, useTransformer, transformerType)]; - - if (body != null) - { - Webhooks[0].Request.BodyData = new BodyData - { - BodyAsJson = body, - DetectedBodyType = BodyType.Json, - DetectedBodyTypeFromContentType = BodyType.Json - }; - } - - return this; - } - - public IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget) - { - _useWebhookFireAndForget = useWebhooksFireAndForget; - return this; - } - - public IRespondWithAProvider WithProbability(double probability) - { - _probability = Guard.Condition(probability, p => p is >= 0 and <= 1.0); - return this; - } - - /// - public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId) - { - Guard.NotNull(protoDefinitionOrId); - -#if PROTOBUF - ProtoDefinition = ProtoDefinitionHelper.GetIdOrTexts(_settings, protoDefinitionOrId); - return this; -#else - throw new NotSupportedException("The WithProtoDefinition method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower."); -#endif - } - - /// - public IRespondWithAProvider WithGraphQLSchema(string graphQLSchemaOrId, IDictionary? customScalars = null) - { - Guard.NotNullOrWhiteSpace(graphQLSchemaOrId); - - if (_settings.GraphQLSchemas?.TryGetValue(graphQLSchemaOrId, out _graphQLSchemaDetails) != true) - { - _graphQLSchemaDetails = new GraphQLSchemaDetails - { - SchemaAsString = graphQLSchemaOrId, - CustomScalars = customScalars - }; - } - - return this; - } - - private static IWebhook InitWebhook( - string url, - string method, - IDictionary>? headers, - bool useTransformer, - TransformerType transformerType - ) - { - return new Webhook - { - Request = new WebhookRequest - { - Url = url, - Method = method, - Headers = headers, - UseTransformer = useTransformer, - TransformerType = transformerType - } - }; - } +// Copyright © WireMock.Net and mock4net by Alexandre Victoor + +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. +using System; +using System.Collections.Generic; +using System.Net; +using Stef.Validation; +using WireMock.Matchers.Request; +using WireMock.Models; +using WireMock.ResponseBuilders; +using WireMock.ResponseProviders; +using WireMock.Settings; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Server; + +/// +/// The RespondWithAProvider. +/// +internal class RespondWithAProvider : IRespondWithAProvider +{ + private readonly RegistrationCallback _registrationCallback; + private readonly IRequestMatcher _requestMatcher; + private readonly WireMockServerSettings _settings; + private readonly IDateTimeUtils _dateTimeUtils; + private readonly bool _saveToFile; + + private int _priority; + private string? _title; + private string? _description; + private string? _path; + private string? _executionConditionState; + private string? _nextState; + private string? _scenario; + private int _timesInSameState = 1; + private bool? _useWebhookFireAndForget; + private double? _probability; + private GraphQLSchemaDetails? _graphQLSchemaDetails; + + public Guid Guid { get; private set; } + + public IWebhook[]? Webhooks { get; private set; } + + public ITimeSettings? TimeSettings { get; private set; } + + public object? Data { get; private set; } + + public IdOrTexts? ProtoDefinition { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The registration callback. + /// The request matcher. + /// The WireMockServerSettings. + /// GuidUtils to make unit testing possible. + /// DateTimeUtils to make unit testing possible. + /// Optional boolean to indicate if this mapping should be saved as static mapping file. + public RespondWithAProvider( + RegistrationCallback registrationCallback, + IRequestMatcher requestMatcher, + WireMockServerSettings settings, + IGuidUtils guidUtils, + IDateTimeUtils dateTimeUtils, + bool saveToFile = false + ) + { + _registrationCallback = Guard.NotNull(registrationCallback); + _requestMatcher = Guard.NotNull(requestMatcher); + _settings = Guard.NotNull(settings); + _dateTimeUtils = Guard.NotNull(dateTimeUtils); + _saveToFile = saveToFile; + + Guid = guidUtils.NewGuid(); + } + + /// + public void RespondWith(IResponseProvider provider) + { + var mapping = new Mapping + ( + Guid, + _dateTimeUtils.UtcNow, + _title, + _description, + _path, + _settings, + _requestMatcher, + provider, + _priority, + _scenario, + _executionConditionState, + _nextState, + _timesInSameState, + Webhooks, + _useWebhookFireAndForget, + TimeSettings, + Data + ); + + if (_probability != null) + { + mapping.WithProbability(_probability.Value); + } + + if (ProtoDefinition != null) + { + mapping.WithProtoDefinition(ProtoDefinition.Value); + } + + _registrationCallback(mapping, _saveToFile); + } + + /// + public void ThenRespondWith(Action action) + { + var responseBuilder = Response.Create(); + + action(responseBuilder); + + RespondWith(responseBuilder); + } + + /// + public void ThenRespondWithOK() + { + var responseBuilder = Response.Create(); + + RespondWith(responseBuilder); + } + + /// + public void ThenRespondWithStatusCode(int code) + { + var responseBuilder = Response.Create().WithStatusCode(code); + + RespondWith(responseBuilder); + } + + /// + public void ThenRespondWithStatusCode(string code) + { + var responseBuilder = Response.Create().WithStatusCode(code); + + RespondWith(responseBuilder); + } + + /// + public void ThenRespondWithStatusCode(HttpStatusCode code) + { + var responseBuilder = Response.Create().WithStatusCode(code); + + RespondWith(responseBuilder); + } + + /// + public IRespondWithAProvider WithData(object data) + { + Data = data; + return this; + } + + /// + public IRespondWithAProvider WithGuid(string guid) + { + return WithGuid(Guid.Parse(guid)); + } + + /// + public IRespondWithAProvider WithGuid(Guid guid) + { + Guid = guid; + return this; + } + + /// + public IRespondWithAProvider DefineGuid(Guid guid) + { + return WithGuid(guid); + } + + /// + public IRespondWithAProvider DefineGuid(string guid) + { + return WithGuid(guid); + } + + /// + public IRespondWithAProvider WithTitle(string title) + { + _title = title; + + return this; + } + + /// + public IRespondWithAProvider WithDescription(string description) + { + _description = description; + return this; + } + + /// + public IRespondWithAProvider WithPath(string path) + { + _path = path; + return this; + } + + /// + public IRespondWithAProvider AtPriority(int priority) + { + _priority = priority; + return this; + } + + /// + public IRespondWithAProvider InScenario(string scenario) + { + _scenario = Guard.NotNullOrWhiteSpace(scenario); + return this; + } + + /// + public IRespondWithAProvider InScenario(int scenario) + { + return InScenario(scenario.ToString()); + } + + /// + public IRespondWithAProvider WhenStateIs(string state) + { + if (string.IsNullOrEmpty(_scenario)) + { + throw new NotSupportedException("Unable to set state condition when no scenario is defined."); + } + + _executionConditionState = state; + + return this; + } + + /// + public IRespondWithAProvider WhenStateIs(int state) + { + return WhenStateIs(state.ToString()); + } + + /// + public IRespondWithAProvider WillSetStateTo(string state, int? times = 1) + { + if (string.IsNullOrEmpty(_scenario)) + { + throw new NotSupportedException("Unable to set next state when no scenario is defined."); + } + + _nextState = state; + _timesInSameState = times ?? 1; + + return this; + } + + /// + public IRespondWithAProvider WillSetStateTo(int state, int? times = 1) + { + return WillSetStateTo(state.ToString(), times); + } + + /// + public IRespondWithAProvider WithTimeSettings(ITimeSettings timeSettings) + { + TimeSettings = Guard.NotNull(timeSettings); + + return this; + } + + /// + public IRespondWithAProvider WithWebhook(params IWebhook[] webhooks) + { + Guard.HasNoNulls(webhooks); + + Webhooks = webhooks; + return this; + } + + /// + public IRespondWithAProvider WithWebhook( + string url, + string method = "post", + IDictionary>? headers = null, + string? body = null, + bool useTransformer = true, + TransformerType transformerType = TransformerType.Handlebars) + { + Guard.NotNull(url); + Guard.NotNull(method); + + Webhooks = [InitWebhook(url, method, headers, useTransformer, transformerType)]; + + if (body != null) + { + Webhooks[0].Request.BodyData = new BodyData + { + BodyAsString = body, + DetectedBodyType = BodyType.String, + DetectedBodyTypeFromContentType = BodyType.String + }; + } + + return this; + } + + /// + public IRespondWithAProvider WithWebhook( + string url, + string method = "post", + IDictionary>? headers = null, + object? body = null, + bool useTransformer = true, + TransformerType transformerType = TransformerType.Handlebars) + { + Guard.NotNull(url); + Guard.NotNull(method); + + Webhooks = [InitWebhook(url, method, headers, useTransformer, transformerType)]; + + if (body != null) + { + Webhooks[0].Request.BodyData = new BodyData + { + BodyAsJson = body, + DetectedBodyType = BodyType.Json, + DetectedBodyTypeFromContentType = BodyType.Json + }; + } + + return this; + } + + public IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget) + { + _useWebhookFireAndForget = useWebhooksFireAndForget; + return this; + } + + public IRespondWithAProvider WithProbability(double probability) + { + _probability = Guard.Condition(probability, p => p is >= 0 and <= 1.0); + return this; + } + + /// + public IRespondWithAProvider WithProtoDefinition(params string[] protoDefinitionOrId) + { + Guard.NotNull(protoDefinitionOrId); + +#if PROTOBUF + ProtoDefinition = ProtoDefinitionHelper.GetIdOrTexts(_settings, protoDefinitionOrId); + return this; +#else + throw new NotSupportedException("The WithProtoDefinition method can not be used for .NETStandard1.3 or .NET Framework 4.6.1 or lower."); +#endif + } + + /// + public IRespondWithAProvider WithGraphQLSchema(string graphQLSchemaOrId, IDictionary? customScalars = null) + { + Guard.NotNullOrWhiteSpace(graphQLSchemaOrId); + + if (_settings.GraphQLSchemas?.TryGetValue(graphQLSchemaOrId, out _graphQLSchemaDetails) != true) + { + _graphQLSchemaDetails = new GraphQLSchemaDetails + { + SchemaAsString = graphQLSchemaOrId, + CustomScalars = customScalars + }; + } + + return this; + } + + private static IWebhook InitWebhook( + string url, + string method, + IDictionary>? headers, + bool useTransformer, + TransformerType transformerType + ) + { + return new Webhook + { + Request = new WebhookRequest + { + Url = url, + Method = method, + Headers = headers, + UseTransformer = useTransformer, + TransformerType = transformerType + } + }; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs similarity index 97% rename from src/WireMock.Net/Server/WireMockServer.Admin.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs index 6b6bea8c..f77e189d 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs @@ -1,886 +1,886 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using JetBrains.Annotations; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Stef.Validation; -using WireMock.Admin.Mappings; -using WireMock.Admin.Scenarios; -using WireMock.Admin.Settings; -using WireMock.Constants; -using WireMock.Http; -using WireMock.Logging; -using WireMock.Matchers; -using WireMock.Matchers.Request; -using WireMock.Owin; -using WireMock.RequestBuilders; -using WireMock.ResponseProviders; -using WireMock.Serialization; -using WireMock.Settings; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Server; - -/// -/// The fluent mock 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 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 ScenariosNameWithResetMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/reset$"); - public RegexMatcher FilesFilenamePathMatcher => new($"^{_prefixEscaped}\\/files\\/.+$"); - public RegexMatcher ProtoDefinitionsIdPathMatcher => new($"^{_prefixEscaped}\\/protodefinitions\\/.+$"); - } - - #region InitAdmin - private void InitAdmin() - { - _adminPaths = new AdminPaths(_settings); - - // __admin/health - Given(Request.Create().WithPath(_adminPaths.Health).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(HealthGet)); - - // __admin/settings - Given(Request.Create().WithPath(_adminPaths.Settings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet)); - Given(Request.Create().WithPath(_adminPaths.Settings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); - - // __admin/mappings - Given(Request.Create().WithPath(_adminPaths.Mappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet)); - Given(Request.Create().WithPath(_adminPaths.Mappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost)); - Given(Request.Create().WithPath(_adminPaths.Mappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); - - // __admin/mappings/code - Given(Request.Create().WithPath(_adminPaths.MappingsCode).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsCodeGet)); - - // __admin/mappings/wiremock.org - Given(Request.Create().WithPath(_adminPaths.MappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg)); - - // __admin/mappings/reset - Given(Request.Create().WithPath(_adminPaths.Mappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); - - // __admin/mappings/reloadStaticMappings - Given(Request.Create().WithPath(_adminPaths.Mappings + "/reloadStaticMappings").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ReloadStaticMappings)); - - // __admin/mappings/{guid} - Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); - Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); - Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); - - // __admin/mappings/code/{guid} - Given(Request.Create().WithPath(_adminPaths.MappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet)); - - // __admin/mappings/save - Given(Request.Create().WithPath($"{_adminPaths.Mappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave)); - - // __admin/mappings/swagger - Given(Request.Create().WithPath($"{_adminPaths.Mappings}/swagger").UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SwaggerGet)); - - // __admin/requests - Given(Request.Create().WithPath(_adminPaths.Requests).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet)); - Given(Request.Create().WithPath(_adminPaths.Requests).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); - - // __admin/requests/reset - Given(Request.Create().WithPath(_adminPaths.Requests + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); - - // __admin/request/{guid} - Given(Request.Create().WithPath(_adminPaths.RequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); - Given(Request.Create().WithPath(_adminPaths.RequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); - - // __admin/requests/find - Given(Request.Create().WithPath(_adminPaths.Requests + "/find").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind)); - Given(Request.Create().WithPath(_adminPaths.Requests + "/find").UsingGet().WithParam("mappingGuid", new NotNullOrEmptyMatcher())).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFindByMappingGuid)); - - // __admin/scenarios - Given(Request.Create().WithPath(_adminPaths.Scenarios).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosGet)); - Given(Request.Create().WithPath(_adminPaths.Scenarios).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); - Given(Request.Create().WithPath(_adminPaths.ScenariosNameMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset)); - - // __admin/scenarios/reset - Given(Request.Create().WithPath(_adminPaths.Scenarios + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); - Given(Request.Create().WithPath(_adminPaths.ScenariosNameWithResetMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset)); - - // __admin/files/{filename} - Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePost)); - Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePut)); - Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileGet)); - Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingHead()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileHead)); - Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete)); - - // __admin/openapi - Given(Request.Create().WithPath($"{_adminPaths.OpenApi}/convert").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiConvertToMappings)); - Given(Request.Create().WithPath($"{_adminPaths.OpenApi}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiSaveToMappings)); - - // __admin/protodefinitions/{id} - Given(Request.Create().WithPath(_adminPaths.ProtoDefinitionsIdPathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ProtoDefinitionAdd)); - } - #endregion - - #region StaticMappings - /// - [PublicAPI] - public void SaveStaticMappings(string? folder = null) - { - _mappingBuilder.SaveMappingsToFolder(folder); - } - - /// - [PublicAPI] - public void ReadStaticMappings(string? folder = null) - { - if (folder == null) - { - folder = _settings.FileSystemHandler.GetMappingFolder(); - } - - if (!_settings.FileSystemHandler.FolderExists(folder)) - { - _settings.Logger.Info("The Static Mapping folder '{0}' does not exist, reading Static MappingFiles will be skipped.", folder); - return; - } - - foreach (var filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f)) - { - _settings.Logger.Info("Reading Static MappingFile : '{0}'.", filename); - - try - { - ReadStaticMappingAndAddOrUpdate(filename); - } - catch (Exception exception) - { - _settings.Logger.Error($"Static MappingFile : '{filename}' could not be read. This file will be skipped.", exception); - } - } - } - - /// - [PublicAPI] - public void WatchStaticMappings(string? folder = null) - { - if (folder == null) - { - folder = _settings.FileSystemHandler.GetMappingFolder(); - } - - if (!_settings.FileSystemHandler.FolderExists(folder)) - { - return; - } - - bool includeSubdirectories = _settings.WatchStaticMappingsInSubdirectories == true; - string includeSubdirectoriesText = includeSubdirectories ? " and Subdirectories" : string.Empty; - - _settings.Logger.Info($"Watching folder '{folder}'{includeSubdirectoriesText} for new, updated and deleted MappingFiles."); - - DisposeEnhancedFileSystemWatcher(); - _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(folder, "*.json", EnhancedFileSystemWatcherTimeoutMs) - { - IncludeSubdirectories = includeSubdirectories - }; - _enhancedFileSystemWatcher.Created += EnhancedFileSystemWatcherCreated; - _enhancedFileSystemWatcher.Changed += EnhancedFileSystemWatcherChanged; - _enhancedFileSystemWatcher.Deleted += EnhancedFileSystemWatcherDeleted; - _enhancedFileSystemWatcher.EnableRaisingEvents = true; - } - - /// - [PublicAPI] - public bool ReadStaticMappingAndAddOrUpdate(string path) - { - Guard.NotNull(path); - - string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); - - if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value)) - { - var mappingModels = DeserializeJsonToArray(value); - if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) - { - ConvertMappingAndRegisterAsRespondProvider(mappingModels[0], guidFromFilename, path); - } - else - { - ConvertMappingsAndRegisterAsRespondProvider(mappingModels, path); - } - - return true; - } - - return false; - } - #endregion - - #region Health - private static IResponseMessage HealthGet(IRequestMessage requestMessage) - { - return new ResponseMessage - { - BodyData = new BodyData - { - DetectedBodyType = BodyType.String, - BodyAsString = "Healthy" - }, - StatusCode = (int)HttpStatusCode.OK, - Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeTextPlain) } } - }; - } - #endregion - - #region Settings - private IResponseMessage SettingsGet(IRequestMessage requestMessage) - { - var model = new SettingsModel - { - AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods, - AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse, - AllowPartialMapping = _settings.AllowPartialMapping, - DisableDeserializeFormUrlEncoded = _settings.DisableDeserializeFormUrlEncoded, - DisableJsonBodyParsing = _settings.DisableJsonBodyParsing, - DisableRequestBodyDecompressing = _settings.DisableRequestBodyDecompressing, - DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry, - GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds, - // GraphQLSchemas TODO - HandleRequestsSynchronously = _settings.HandleRequestsSynchronously, - HostingScheme = _settings.HostingScheme, - MaxRequestLogCount = _settings.MaxRequestLogCount, - ProtoDefinitions = _settings.ProtoDefinitions, - QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport, - ReadStaticMappings = _settings.ReadStaticMappings, - RequestLogExpirationDuration = _settings.RequestLogExpirationDuration, - SaveUnmatchedRequests = _settings.SaveUnmatchedRequests, - UseRegexExtended = _settings.UseRegexExtended, - WatchStaticMappings = _settings.WatchStaticMappings, - WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories, - -#if USE_ASPNETCORE - AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate, - ClientCertificateMode = _settings.ClientCertificateMode, - CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString() -#endif - }; - - model.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(_settings.ProxyAndRecordSettings); - - return ToJson(model); - } - - private IResponseMessage SettingsUpdate(IRequestMessage requestMessage) - { - var settings = DeserializeObject(requestMessage); - - // _settings - _settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods; - _settings.AllowOnlyDefinedHttpStatusCodeInResponse = settings.AllowOnlyDefinedHttpStatusCodeInResponse; - _settings.AllowPartialMapping = settings.AllowPartialMapping; - _settings.DisableDeserializeFormUrlEncoded = settings.DisableDeserializeFormUrlEncoded; - _settings.DisableJsonBodyParsing = settings.DisableJsonBodyParsing; - _settings.DisableRequestBodyDecompressing = settings.DisableRequestBodyDecompressing; - _settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry; - _settings.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; - _settings.MaxRequestLogCount = settings.MaxRequestLogCount; - _settings.ProtoDefinitions = settings.ProtoDefinitions; - _settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings); - _settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport; - _settings.ReadStaticMappings = settings.ReadStaticMappings; - _settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; - _settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; - _settings.UseRegexExtended = settings.UseRegexExtended; - _settings.WatchStaticMappings = settings.WatchStaticMappings; - _settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories; - - InitSettings(_settings); - -#if USE_ASPNETCORE - if (Enum.TryParse(settings.CorsPolicyOptions, true, out var corsPolicyOptions)) - { - _settings.CorsPolicyOptions = corsPolicyOptions; - } -#endif - - WireMockMiddlewareOptionsHelper.InitFromSettings(_settings, _options, o => - { - if (settings.GlobalProcessingDelay != null) - { - o.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value); - } - -#if USE_ASPNETCORE - o.CorsPolicyOptions = corsPolicyOptions; - o.ClientCertificateMode = _settings.ClientCertificateMode; - o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate; -#endif - }); - - return ResponseMessageBuilder.Create(200, "Settings updated"); - } - #endregion Settings - - #region Mapping/{guid} - private IResponseMessage MappingGet(IRequestMessage requestMessage) - { - var mapping = FindMappingByGuid(requestMessage); - if (mapping == null) - { - _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); - return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found"); - } - - var model = _mappingConverter.ToMappingModel(mapping); - - return ToJson(model); - } - - private IResponseMessage MappingCodeGet(IRequestMessage requestMessage) - { - if (TryParseGuidFromRequestMessage(requestMessage, out var guid)) - { - var code = _mappingBuilder.ToCSharpCode(guid, GetEnumFromQuery(requestMessage, MappingConverterType.Server)); - if (code is null) - { - _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); - return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found"); - } - - return ToResponseMessage(code); - } - - _settings.Logger.Warn("HttpStatusCode set to 400"); - return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "GUID is missing"); - } - - private static TEnum GetEnumFromQuery(IRequestMessage requestMessage, TEnum defaultValue) - where TEnum : struct - { - if (requestMessage.QueryIgnoreCase?.TryGetValue(typeof(TEnum).Name, out var values) == true && - Enum.TryParse(values.FirstOrDefault(), true, out var parsed)) - { - return parsed; - } - - return defaultValue; - } - - private IMapping? FindMappingByGuid(IRequestMessage requestMessage) - { - return TryParseGuidFromRequestMessage(requestMessage, out var guid) ? Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid) : null; - } - - private IResponseMessage MappingPut(IRequestMessage requestMessage) - { - if (TryParseGuidFromRequestMessage(requestMessage, out var guid)) - { - var mappingModel = DeserializeObject(requestMessage); - var guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); - - return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping added or updated", guidFromPut); - } - - _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); - return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found"); - } - - private IResponseMessage MappingDelete(IRequestMessage requestMessage) - { - if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid)) - { - return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping removed", guid); - } - - _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); - return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found"); - } - - private static bool TryParseGuidFromRequestMessage(IRequestMessage requestMessage, out Guid guid) - { - var lastPart = requestMessage.Path.Split('/').LastOrDefault(); - return Guid.TryParse(lastPart, out guid); - } - #endregion Mapping/{guid} - - #region Mappings - private IResponseMessage SwaggerGet(IRequestMessage requestMessage) - { - return new ResponseMessage - { - BodyData = new BodyData - { - DetectedBodyType = BodyType.String, - BodyAsString = SwaggerMapper.ToSwagger(this) - }, - StatusCode = (int)HttpStatusCode.OK, - Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeJson) } } - }; - } - - private IResponseMessage MappingsSave(IRequestMessage requestMessage) - { - SaveStaticMappings(); - - return ResponseMessageBuilder.Create(200, "Mappings saved to disk"); - } - - private MappingModel[] ToMappingModels() - { - return _mappingBuilder.GetMappings(); - } - - private IResponseMessage MappingsGet(IRequestMessage requestMessage) - { - return ToJson(ToMappingModels()); - } - - private IResponseMessage MappingsCodeGet(IRequestMessage requestMessage) - { - var converterType = GetEnumFromQuery(requestMessage, MappingConverterType.Server); - - var code = _mappingBuilder.ToCSharpCode(converterType); - - return ToResponseMessage(code); - } - - private IResponseMessage MappingsPost(IRequestMessage requestMessage) - { - try - { - var mappingModels = DeserializeRequestMessageToArray(requestMessage); - if (mappingModels.Length == 1) - { - var guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]); - return ResponseMessageBuilder.Create(201, "Mapping added", guid); - } - - ConvertMappingsAndRegisterAsRespondProvider(mappingModels); - - return ResponseMessageBuilder.Create(201, "Mappings added"); - } - catch (ArgumentException a) - { - _settings.Logger.Error("HttpStatusCode set to 400 {0}", a); - return ResponseMessageBuilder.Create(400, a.Message); - } - catch (Exception e) - { - _settings.Logger.Error("HttpStatusCode set to 500 {0}", e); - return ResponseMessageBuilder.Create(500, e.ToString()); - } - } - - private IResponseMessage MappingsDelete(IRequestMessage requestMessage) - { - if (!string.IsNullOrEmpty(requestMessage.Body)) - { - var deletedGuids = MappingsDeleteMappingFromBody(requestMessage); - if (deletedGuids != null) - { - return ResponseMessageBuilder.Create(200, $"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]"); - } - - // return bad request - return ResponseMessageBuilder.Create(400, "Poorly formed mapping JSON."); - } - - ResetMappings(); - - ResetScenarios(); - - return ResponseMessageBuilder.Create(200, "Mappings deleted"); - } - - private IEnumerable? MappingsDeleteMappingFromBody(IRequestMessage requestMessage) - { - var deletedGuids = new List(); - - try - { - var mappingModels = DeserializeRequestMessageToArray(requestMessage); - foreach (var guid in mappingModels.Where(mm => mm.Guid.HasValue).Select(mm => mm.Guid!.Value)) - { - if (DeleteMapping(guid)) - { - deletedGuids.Add(guid); - } - else - { - _settings.Logger.Debug($"Did not find/delete mapping with GUID: {guid}."); - } - } - } - catch (ArgumentException a) - { - _settings.Logger.Error("ArgumentException: {0}", a); - return null; - } - catch (Exception e) - { - _settings.Logger.Error("Exception: {0}", e); - return null; - } - - return deletedGuids; - } - - private IResponseMessage MappingsReset(IRequestMessage requestMessage) - { - ResetMappings(); - - ResetScenarios(); - - var message = "Mappings reset"; - if (requestMessage.Query != null && - requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && - bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out var reloadStaticMappings) && - reloadStaticMappings) - { - ReadStaticMappings(); - message += " and static mappings reloaded"; - } - - return ResponseMessageBuilder.Create(200, message); - } - - private IResponseMessage ReloadStaticMappings(IRequestMessage _) - { - ReadStaticMappings(); - - return ResponseMessageBuilder.Create(200, "Static Mappings reloaded"); - } - #endregion Mappings - - #region Request/{guid} - private IResponseMessage RequestGet(IRequestMessage requestMessage) - { - if (TryParseGuidFromRequestMessage(requestMessage, out var guid)) - { - var entry = LogEntries.SingleOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid); - if (entry is { }) - { - var model = new LogEntryMapper(_options).Map(entry); - return ToJson(model); - } - } - - _settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); - return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found"); - } - - private IResponseMessage RequestDelete(IRequestMessage requestMessage) - { - if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid)) - { - return ResponseMessageBuilder.Create(200, "Request removed"); - } - - _settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); - return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found"); - } - #endregion Request/{guid} - - #region Requests - private IResponseMessage RequestsGet(IRequestMessage requestMessage) - { - var logEntryMapper = new LogEntryMapper(_options); - var result = LogEntries - .Where(r => !r.RequestMessage.Path.StartsWith("/__admin/")) - .Select(logEntryMapper.Map); - - return ToJson(result); - } - - private IResponseMessage RequestsDelete(IRequestMessage requestMessage) - { - ResetLogEntries(); - - return ResponseMessageBuilder.Create(200, "Requests deleted"); - } - #endregion Requests - - #region Requests/find - private IResponseMessage RequestsFind(IRequestMessage requestMessage) - { - var requestModel = DeserializeObject(requestMessage); - - var request = (Request)InitRequestBuilder(requestModel); - - var dict = new Dictionary(); - foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) - { - var requestMatchResult = new RequestMatchResult(); - if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect) - { - dict.Add(logEntry, requestMatchResult); - } - } - - var logEntryMapper = new LogEntryMapper(_options); - var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(logEntryMapper.Map); - - return ToJson(result); - } - - private IResponseMessage RequestsFindByMappingGuid(IRequestMessage requestMessage) - { - if (requestMessage.Query != null && - requestMessage.Query.TryGetValue("mappingGuid", out var value) && - Guid.TryParse(value.ToString(), out var mappingGuid) - ) - { - var logEntries = LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/") && le.MappingGuid == mappingGuid); - var logEntryMapper = new LogEntryMapper(_options); - var result = logEntries.Select(logEntryMapper.Map); - return ToJson(result); - } - - return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest); - } - #endregion Requests/find - - #region Scenarios - private IResponseMessage ScenariosGet(IRequestMessage requestMessage) - { - var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel - { - Name = s.Name, - NextState = s.NextState, - Started = s.Started, - Finished = s.Finished, - Counter = s.Counter - }); - - return ToJson(scenariosStates, true); - } - - private IResponseMessage ScenariosReset(IRequestMessage requestMessage) - { - ResetScenarios(); - - return ResponseMessageBuilder.Create(200, "Scenarios reset"); - } - - private IResponseMessage ScenarioReset(IRequestMessage requestMessage) - { - var name = string.Equals(HttpRequestMethod.DELETE, requestMessage.Method, StringComparison.OrdinalIgnoreCase) ? - requestMessage.Path.Substring(_adminPaths!.Scenarios.Length + 1) : - requestMessage.Path.Split('/').Reverse().Skip(1).First(); - - return ResetScenario(name) ? - ResponseMessageBuilder.Create(200, "Scenario reset") : - ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'."); - } - #endregion - - #region Pact - /// - /// Save the mappings as a Pact Json file V2. - /// - /// The folder to save the pact file. - /// The filename for the .json file [optional]. - [PublicAPI] - public void SavePact(string folder, string? filename = null) - { - var (filenameUpdated, bytes) = PactMapper.ToPact(this, filename); - _settings.FileSystemHandler.WriteFile(folder, filenameUpdated, bytes); - } - - /// - /// Save the mappings as a Pact Json file V2. - /// - /// The (file) stream. - [PublicAPI] - public void SavePact(Stream stream) - { - var (_, bytes) = PactMapper.ToPact(this); - using var writer = new BinaryWriter(stream); - writer.Write(bytes); - - if (stream.CanSeek) - { - stream.Seek(0, SeekOrigin.Begin); - } - } - - /// - /// This stores details about the consumer of the interaction. - /// - /// the consumer - [PublicAPI] - public WireMockServer WithConsumer(string consumer) - { - Consumer = consumer; - return this; - } - - /// - /// This stores details about the provider of the interaction. - /// - /// the provider - [PublicAPI] - public WireMockServer WithProvider(string provider) - { - Provider = provider; - return this; - } - #endregion - - private void DisposeEnhancedFileSystemWatcher() - { - if (_enhancedFileSystemWatcher != null) - { - _enhancedFileSystemWatcher.EnableRaisingEvents = false; - - _enhancedFileSystemWatcher.Created -= EnhancedFileSystemWatcherCreated; - _enhancedFileSystemWatcher.Changed -= EnhancedFileSystemWatcherChanged; - _enhancedFileSystemWatcher.Deleted -= EnhancedFileSystemWatcherDeleted; - - _enhancedFileSystemWatcher.Dispose(); - } - } - - private void EnhancedFileSystemWatcherCreated(object sender, FileSystemEventArgs args) - { - _settings.Logger.Info("MappingFile created : '{0}', reading file.", args.FullPath); - if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) - { - _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); - } - } - - private void EnhancedFileSystemWatcherChanged(object sender, FileSystemEventArgs args) - { - _settings.Logger.Info("MappingFile updated : '{0}', reading file.", args.FullPath); - if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) - { - _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); - } - } - - private void EnhancedFileSystemWatcherDeleted(object sender, FileSystemEventArgs args) - { - _settings.Logger.Info("MappingFile deleted : '{0}'", args.FullPath); - var filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); - - if (Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) - { - DeleteMapping(guidFromFilename); - } - else - { - DeleteMapping(args.FullPath); - } - } - - private static Encoding? ToEncoding(EncodingModel? encodingModel) - { - return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; - } - - private static ResponseMessage ToJson(T result, bool keepNullValues = false, object? statusCode = null) - { - return new ResponseMessage - { - BodyData = new BodyData - { - DetectedBodyType = BodyType.String, - BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault) - }, - StatusCode = statusCode ?? (int)HttpStatusCode.OK, - Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeJson) } } - }; - } - - private static ResponseMessage ToResponseMessage(string text) - { - return new ResponseMessage - { - BodyData = new BodyData - { - DetectedBodyType = BodyType.String, - BodyAsString = text - }, - StatusCode = (int)HttpStatusCode.OK, - Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeTextPlain) } } - }; - } - - private static T DeserializeObject(IRequestMessage requestMessage) where T : new() - { - switch (requestMessage.BodyData?.DetectedBodyType) - { - case BodyType.String: - case BodyType.FormUrlEncoded: - return JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString!); - - case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null: - return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject()!; - - default: - throw new NotSupportedException(); - } - } - - private static T[] DeserializeRequestMessageToArray(IRequestMessage requestMessage) - { - if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null) - { - var bodyAsJson = requestMessage.BodyData.BodyAsJson; - - return DeserializeObjectToArray(bodyAsJson); - } - - throw new NotSupportedException(); - } - - private static T[] DeserializeJsonToArray(string value) - { - return DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); - } - - private static T[] DeserializeObjectToArray(object value) - { - if (value is JArray jArray) - { - return jArray.ToObject()!; - } - - var singleResult = ((JObject)value).ToObject(); - return new[] { singleResult! }; - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Stef.Validation; +using WireMock.Admin.Mappings; +using WireMock.Admin.Scenarios; +using WireMock.Admin.Settings; +using WireMock.Constants; +using WireMock.Http; +using WireMock.Logging; +using WireMock.Matchers; +using WireMock.Matchers.Request; +using WireMock.Owin; +using WireMock.RequestBuilders; +using WireMock.ResponseProviders; +using WireMock.Serialization; +using WireMock.Settings; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Server; + +/// +/// The fluent mock 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 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 ScenariosNameWithResetMatcher => new($"^{_prefixEscaped}\\/scenarios\\/.+\\/reset$"); + public RegexMatcher FilesFilenamePathMatcher => new($"^{_prefixEscaped}\\/files\\/.+$"); + public RegexMatcher ProtoDefinitionsIdPathMatcher => new($"^{_prefixEscaped}\\/protodefinitions\\/.+$"); + } + + #region InitAdmin + private void InitAdmin() + { + _adminPaths = new AdminPaths(_settings); + + // __admin/health + Given(Request.Create().WithPath(_adminPaths.Health).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(HealthGet)); + + // __admin/settings + Given(Request.Create().WithPath(_adminPaths.Settings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsGet)); + Given(Request.Create().WithPath(_adminPaths.Settings).UsingMethod("PUT", "POST").WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SettingsUpdate)); + + // __admin/mappings + Given(Request.Create().WithPath(_adminPaths.Mappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet)); + Given(Request.Create().WithPath(_adminPaths.Mappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost)); + Given(Request.Create().WithPath(_adminPaths.Mappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete)); + + // __admin/mappings/code + Given(Request.Create().WithPath(_adminPaths.MappingsCode).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsCodeGet)); + + // __admin/mappings/wiremock.org + Given(Request.Create().WithPath(_adminPaths.MappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg)); + + // __admin/mappings/reset + Given(Request.Create().WithPath(_adminPaths.Mappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset)); + + // __admin/mappings/reloadStaticMappings + Given(Request.Create().WithPath(_adminPaths.Mappings + "/reloadStaticMappings").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ReloadStaticMappings)); + + // __admin/mappings/{guid} + Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingGet)); + Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut)); + Given(Request.Create().WithPath(_adminPaths.MappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete)); + + // __admin/mappings/code/{guid} + Given(Request.Create().WithPath(_adminPaths.MappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet)); + + // __admin/mappings/save + Given(Request.Create().WithPath($"{_adminPaths.Mappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave)); + + // __admin/mappings/swagger + Given(Request.Create().WithPath($"{_adminPaths.Mappings}/swagger").UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SwaggerGet)); + + // __admin/requests + Given(Request.Create().WithPath(_adminPaths.Requests).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet)); + Given(Request.Create().WithPath(_adminPaths.Requests).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); + + // __admin/requests/reset + Given(Request.Create().WithPath(_adminPaths.Requests + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsDelete)); + + // __admin/request/{guid} + Given(Request.Create().WithPath(_adminPaths.RequestsGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestGet)); + Given(Request.Create().WithPath(_adminPaths.RequestsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestDelete)); + + // __admin/requests/find + Given(Request.Create().WithPath(_adminPaths.Requests + "/find").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFind)); + Given(Request.Create().WithPath(_adminPaths.Requests + "/find").UsingGet().WithParam("mappingGuid", new NotNullOrEmptyMatcher())).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsFindByMappingGuid)); + + // __admin/scenarios + Given(Request.Create().WithPath(_adminPaths.Scenarios).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosGet)); + Given(Request.Create().WithPath(_adminPaths.Scenarios).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); + Given(Request.Create().WithPath(_adminPaths.ScenariosNameMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset)); + + // __admin/scenarios/reset + Given(Request.Create().WithPath(_adminPaths.Scenarios + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenariosReset)); + Given(Request.Create().WithPath(_adminPaths.ScenariosNameWithResetMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ScenarioReset)); + + // __admin/files/{filename} + Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePost)); + Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingPut()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FilePut)); + Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileGet)); + Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingHead()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileHead)); + Given(Request.Create().WithPath(_adminPaths.FilesFilenamePathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete)); + + // __admin/openapi + Given(Request.Create().WithPath($"{_adminPaths.OpenApi}/convert").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiConvertToMappings)); + Given(Request.Create().WithPath($"{_adminPaths.OpenApi}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiSaveToMappings)); + + // __admin/protodefinitions/{id} + Given(Request.Create().WithPath(_adminPaths.ProtoDefinitionsIdPathMatcher).UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(ProtoDefinitionAdd)); + } + #endregion + + #region StaticMappings + /// + [PublicAPI] + public void SaveStaticMappings(string? folder = null) + { + _mappingBuilder.SaveMappingsToFolder(folder); + } + + /// + [PublicAPI] + public void ReadStaticMappings(string? folder = null) + { + if (folder == null) + { + folder = _settings.FileSystemHandler.GetMappingFolder(); + } + + if (!_settings.FileSystemHandler.FolderExists(folder)) + { + _settings.Logger.Info("The Static Mapping folder '{0}' does not exist, reading Static MappingFiles will be skipped.", folder); + return; + } + + foreach (var filename in _settings.FileSystemHandler.EnumerateFiles(folder, _settings.WatchStaticMappingsInSubdirectories == true).OrderBy(f => f)) + { + _settings.Logger.Info("Reading Static MappingFile : '{0}'.", filename); + + try + { + ReadStaticMappingAndAddOrUpdate(filename); + } + catch (Exception exception) + { + _settings.Logger.Error($"Static MappingFile : '{filename}' could not be read. This file will be skipped.", exception); + } + } + } + + /// + [PublicAPI] + public void WatchStaticMappings(string? folder = null) + { + if (folder == null) + { + folder = _settings.FileSystemHandler.GetMappingFolder(); + } + + if (!_settings.FileSystemHandler.FolderExists(folder)) + { + return; + } + + bool includeSubdirectories = _settings.WatchStaticMappingsInSubdirectories == true; + string includeSubdirectoriesText = includeSubdirectories ? " and Subdirectories" : string.Empty; + + _settings.Logger.Info($"Watching folder '{folder}'{includeSubdirectoriesText} for new, updated and deleted MappingFiles."); + + DisposeEnhancedFileSystemWatcher(); + _enhancedFileSystemWatcher = new EnhancedFileSystemWatcher(folder, "*.json", EnhancedFileSystemWatcherTimeoutMs) + { + IncludeSubdirectories = includeSubdirectories + }; + _enhancedFileSystemWatcher.Created += EnhancedFileSystemWatcherCreated; + _enhancedFileSystemWatcher.Changed += EnhancedFileSystemWatcherChanged; + _enhancedFileSystemWatcher.Deleted += EnhancedFileSystemWatcherDeleted; + _enhancedFileSystemWatcher.EnableRaisingEvents = true; + } + + /// + [PublicAPI] + public bool ReadStaticMappingAndAddOrUpdate(string path) + { + Guard.NotNull(path); + + string filenameWithoutExtension = Path.GetFileNameWithoutExtension(path); + + if (FileHelper.TryReadMappingFileWithRetryAndDelay(_settings.FileSystemHandler, path, out var value)) + { + var mappingModels = DeserializeJsonToArray(value); + if (mappingModels.Length == 1 && Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) + { + ConvertMappingAndRegisterAsRespondProvider(mappingModels[0], guidFromFilename, path); + } + else + { + ConvertMappingsAndRegisterAsRespondProvider(mappingModels, path); + } + + return true; + } + + return false; + } + #endregion + + #region Health + private static IResponseMessage HealthGet(IRequestMessage requestMessage) + { + return new ResponseMessage + { + BodyData = new BodyData + { + DetectedBodyType = BodyType.String, + BodyAsString = "Healthy" + }, + StatusCode = (int)HttpStatusCode.OK, + Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeTextPlain) } } + }; + } + #endregion + + #region Settings + private IResponseMessage SettingsGet(IRequestMessage requestMessage) + { + var model = new SettingsModel + { + AllowBodyForAllHttpMethods = _settings.AllowBodyForAllHttpMethods, + AllowOnlyDefinedHttpStatusCodeInResponse = _settings.AllowOnlyDefinedHttpStatusCodeInResponse, + AllowPartialMapping = _settings.AllowPartialMapping, + DisableDeserializeFormUrlEncoded = _settings.DisableDeserializeFormUrlEncoded, + DisableJsonBodyParsing = _settings.DisableJsonBodyParsing, + DisableRequestBodyDecompressing = _settings.DisableRequestBodyDecompressing, + DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry, + GlobalProcessingDelay = (int?)_options.RequestProcessingDelay?.TotalMilliseconds, + // GraphQLSchemas TODO + HandleRequestsSynchronously = _settings.HandleRequestsSynchronously, + HostingScheme = _settings.HostingScheme, + MaxRequestLogCount = _settings.MaxRequestLogCount, + ProtoDefinitions = _settings.ProtoDefinitions, + QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport, + ReadStaticMappings = _settings.ReadStaticMappings, + RequestLogExpirationDuration = _settings.RequestLogExpirationDuration, + SaveUnmatchedRequests = _settings.SaveUnmatchedRequests, + UseRegexExtended = _settings.UseRegexExtended, + WatchStaticMappings = _settings.WatchStaticMappings, + WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories, + +#if USE_ASPNETCORE + AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate, + ClientCertificateMode = _settings.ClientCertificateMode, + CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString() +#endif + }; + + model.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(_settings.ProxyAndRecordSettings); + + return ToJson(model); + } + + private IResponseMessage SettingsUpdate(IRequestMessage requestMessage) + { + var settings = DeserializeObject(requestMessage); + + // _settings + _settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods; + _settings.AllowOnlyDefinedHttpStatusCodeInResponse = settings.AllowOnlyDefinedHttpStatusCodeInResponse; + _settings.AllowPartialMapping = settings.AllowPartialMapping; + _settings.DisableDeserializeFormUrlEncoded = settings.DisableDeserializeFormUrlEncoded; + _settings.DisableJsonBodyParsing = settings.DisableJsonBodyParsing; + _settings.DisableRequestBodyDecompressing = settings.DisableRequestBodyDecompressing; + _settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry; + _settings.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; + _settings.MaxRequestLogCount = settings.MaxRequestLogCount; + _settings.ProtoDefinitions = settings.ProtoDefinitions; + _settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings); + _settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport; + _settings.ReadStaticMappings = settings.ReadStaticMappings; + _settings.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; + _settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; + _settings.UseRegexExtended = settings.UseRegexExtended; + _settings.WatchStaticMappings = settings.WatchStaticMappings; + _settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories; + + InitSettings(_settings); + +#if USE_ASPNETCORE + if (Enum.TryParse(settings.CorsPolicyOptions, true, out var corsPolicyOptions)) + { + _settings.CorsPolicyOptions = corsPolicyOptions; + } +#endif + + WireMockMiddlewareOptionsHelper.InitFromSettings(_settings, _options, o => + { + if (settings.GlobalProcessingDelay != null) + { + o.RequestProcessingDelay = TimeSpan.FromMilliseconds(settings.GlobalProcessingDelay.Value); + } + +#if USE_ASPNETCORE + o.CorsPolicyOptions = corsPolicyOptions; + o.ClientCertificateMode = _settings.ClientCertificateMode; + o.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate; +#endif + }); + + return ResponseMessageBuilder.Create(200, "Settings updated"); + } + #endregion Settings + + #region Mapping/{guid} + private IResponseMessage MappingGet(IRequestMessage requestMessage) + { + var mapping = FindMappingByGuid(requestMessage); + if (mapping == null) + { + _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); + return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found"); + } + + var model = _mappingConverter.ToMappingModel(mapping); + + return ToJson(model); + } + + private IResponseMessage MappingCodeGet(IRequestMessage requestMessage) + { + if (TryParseGuidFromRequestMessage(requestMessage, out var guid)) + { + var code = _mappingBuilder.ToCSharpCode(guid, GetEnumFromQuery(requestMessage, MappingConverterType.Server)); + if (code is null) + { + _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); + return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found"); + } + + return ToResponseMessage(code); + } + + _settings.Logger.Warn("HttpStatusCode set to 400"); + return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest, "GUID is missing"); + } + + private static TEnum GetEnumFromQuery(IRequestMessage requestMessage, TEnum defaultValue) + where TEnum : struct + { + if (requestMessage.QueryIgnoreCase?.TryGetValue(typeof(TEnum).Name, out var values) == true && + Enum.TryParse(values.FirstOrDefault(), true, out var parsed)) + { + return parsed; + } + + return defaultValue; + } + + private IMapping? FindMappingByGuid(IRequestMessage requestMessage) + { + return TryParseGuidFromRequestMessage(requestMessage, out var guid) ? Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid) : null; + } + + private IResponseMessage MappingPut(IRequestMessage requestMessage) + { + if (TryParseGuidFromRequestMessage(requestMessage, out var guid)) + { + var mappingModel = DeserializeObject(requestMessage); + var guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid); + + return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping added or updated", guidFromPut); + } + + _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); + return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found"); + } + + private IResponseMessage MappingDelete(IRequestMessage requestMessage) + { + if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid)) + { + return ResponseMessageBuilder.Create(HttpStatusCode.OK, "Mapping removed", guid); + } + + _settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found"); + return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Mapping not found"); + } + + private static bool TryParseGuidFromRequestMessage(IRequestMessage requestMessage, out Guid guid) + { + var lastPart = requestMessage.Path.Split('/').LastOrDefault(); + return Guid.TryParse(lastPart, out guid); + } + #endregion Mapping/{guid} + + #region Mappings + private IResponseMessage SwaggerGet(IRequestMessage requestMessage) + { + return new ResponseMessage + { + BodyData = new BodyData + { + DetectedBodyType = BodyType.String, + BodyAsString = SwaggerMapper.ToSwagger(this) + }, + StatusCode = (int)HttpStatusCode.OK, + Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeJson) } } + }; + } + + private IResponseMessage MappingsSave(IRequestMessage requestMessage) + { + SaveStaticMappings(); + + return ResponseMessageBuilder.Create(200, "Mappings saved to disk"); + } + + private MappingModel[] ToMappingModels() + { + return _mappingBuilder.GetMappings(); + } + + private IResponseMessage MappingsGet(IRequestMessage requestMessage) + { + return ToJson(ToMappingModels()); + } + + private IResponseMessage MappingsCodeGet(IRequestMessage requestMessage) + { + var converterType = GetEnumFromQuery(requestMessage, MappingConverterType.Server); + + var code = _mappingBuilder.ToCSharpCode(converterType); + + return ToResponseMessage(code); + } + + private IResponseMessage MappingsPost(IRequestMessage requestMessage) + { + try + { + var mappingModels = DeserializeRequestMessageToArray(requestMessage); + if (mappingModels.Length == 1) + { + var guid = ConvertMappingAndRegisterAsRespondProvider(mappingModels[0]); + return ResponseMessageBuilder.Create(201, "Mapping added", guid); + } + + ConvertMappingsAndRegisterAsRespondProvider(mappingModels); + + return ResponseMessageBuilder.Create(201, "Mappings added"); + } + catch (ArgumentException a) + { + _settings.Logger.Error("HttpStatusCode set to 400 {0}", a); + return ResponseMessageBuilder.Create(400, a.Message); + } + catch (Exception e) + { + _settings.Logger.Error("HttpStatusCode set to 500 {0}", e); + return ResponseMessageBuilder.Create(500, e.ToString()); + } + } + + private IResponseMessage MappingsDelete(IRequestMessage requestMessage) + { + if (!string.IsNullOrEmpty(requestMessage.Body)) + { + var deletedGuids = MappingsDeleteMappingFromBody(requestMessage); + if (deletedGuids != null) + { + return ResponseMessageBuilder.Create(200, $"Mappings deleted. Affected GUIDs: [{string.Join(", ", deletedGuids.ToArray())}]"); + } + + // return bad request + return ResponseMessageBuilder.Create(400, "Poorly formed mapping JSON."); + } + + ResetMappings(); + + ResetScenarios(); + + return ResponseMessageBuilder.Create(200, "Mappings deleted"); + } + + private IEnumerable? MappingsDeleteMappingFromBody(IRequestMessage requestMessage) + { + var deletedGuids = new List(); + + try + { + var mappingModels = DeserializeRequestMessageToArray(requestMessage); + foreach (var guid in mappingModels.Where(mm => mm.Guid.HasValue).Select(mm => mm.Guid!.Value)) + { + if (DeleteMapping(guid)) + { + deletedGuids.Add(guid); + } + else + { + _settings.Logger.Debug($"Did not find/delete mapping with GUID: {guid}."); + } + } + } + catch (ArgumentException a) + { + _settings.Logger.Error("ArgumentException: {0}", a); + return null; + } + catch (Exception e) + { + _settings.Logger.Error("Exception: {0}", e); + return null; + } + + return deletedGuids; + } + + private IResponseMessage MappingsReset(IRequestMessage requestMessage) + { + ResetMappings(); + + ResetScenarios(); + + var message = "Mappings reset"; + if (requestMessage.Query != null && + requestMessage.Query.ContainsKey(QueryParamReloadStaticMappings) && + bool.TryParse(requestMessage.Query[QueryParamReloadStaticMappings].ToString(), out var reloadStaticMappings) && + reloadStaticMappings) + { + ReadStaticMappings(); + message += " and static mappings reloaded"; + } + + return ResponseMessageBuilder.Create(200, message); + } + + private IResponseMessage ReloadStaticMappings(IRequestMessage _) + { + ReadStaticMappings(); + + return ResponseMessageBuilder.Create(200, "Static Mappings reloaded"); + } + #endregion Mappings + + #region Request/{guid} + private IResponseMessage RequestGet(IRequestMessage requestMessage) + { + if (TryParseGuidFromRequestMessage(requestMessage, out var guid)) + { + var entry = LogEntries.SingleOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid); + if (entry is { }) + { + var model = new LogEntryMapper(_options).Map(entry); + return ToJson(model); + } + } + + _settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); + return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found"); + } + + private IResponseMessage RequestDelete(IRequestMessage requestMessage) + { + if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid)) + { + return ResponseMessageBuilder.Create(200, "Request removed"); + } + + _settings.Logger.Warn("HttpStatusCode set to 404 : Request not found"); + return ResponseMessageBuilder.Create(HttpStatusCode.NotFound, "Request not found"); + } + #endregion Request/{guid} + + #region Requests + private IResponseMessage RequestsGet(IRequestMessage requestMessage) + { + var logEntryMapper = new LogEntryMapper(_options); + var result = LogEntries + .Where(r => !r.RequestMessage.Path.StartsWith("/__admin/")) + .Select(logEntryMapper.Map); + + return ToJson(result); + } + + private IResponseMessage RequestsDelete(IRequestMessage requestMessage) + { + ResetLogEntries(); + + return ResponseMessageBuilder.Create(200, "Requests deleted"); + } + #endregion Requests + + #region Requests/find + private IResponseMessage RequestsFind(IRequestMessage requestMessage) + { + var requestModel = DeserializeObject(requestMessage); + + var request = (Request)InitRequestBuilder(requestModel); + + var dict = new Dictionary(); + foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) + { + var requestMatchResult = new RequestMatchResult(); + if (request.GetMatchingScore(logEntry.RequestMessage, requestMatchResult) > MatchScores.AlmostPerfect) + { + dict.Add(logEntry, requestMatchResult); + } + } + + var logEntryMapper = new LogEntryMapper(_options); + var result = dict.OrderBy(x => x.Value.AverageTotalScore).Select(x => x.Key).Select(logEntryMapper.Map); + + return ToJson(result); + } + + private IResponseMessage RequestsFindByMappingGuid(IRequestMessage requestMessage) + { + if (requestMessage.Query != null && + requestMessage.Query.TryGetValue("mappingGuid", out var value) && + Guid.TryParse(value.ToString(), out var mappingGuid) + ) + { + var logEntries = LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/") && le.MappingGuid == mappingGuid); + var logEntryMapper = new LogEntryMapper(_options); + var result = logEntries.Select(logEntryMapper.Map); + return ToJson(result); + } + + return ResponseMessageBuilder.Create(HttpStatusCode.BadRequest); + } + #endregion Requests/find + + #region Scenarios + private IResponseMessage ScenariosGet(IRequestMessage requestMessage) + { + var scenariosStates = Scenarios.Values.Select(s => new ScenarioStateModel + { + Name = s.Name, + NextState = s.NextState, + Started = s.Started, + Finished = s.Finished, + Counter = s.Counter + }); + + return ToJson(scenariosStates, true); + } + + private IResponseMessage ScenariosReset(IRequestMessage requestMessage) + { + ResetScenarios(); + + return ResponseMessageBuilder.Create(200, "Scenarios reset"); + } + + private IResponseMessage ScenarioReset(IRequestMessage requestMessage) + { + var name = string.Equals(HttpRequestMethod.DELETE, requestMessage.Method, StringComparison.OrdinalIgnoreCase) ? + requestMessage.Path.Substring(_adminPaths!.Scenarios.Length + 1) : + requestMessage.Path.Split('/').Reverse().Skip(1).First(); + + return ResetScenario(name) ? + ResponseMessageBuilder.Create(200, "Scenario reset") : + ResponseMessageBuilder.Create(HttpStatusCode.NotFound, $"No scenario found by name '{name}'."); + } + #endregion + + #region Pact + /// + /// Save the mappings as a Pact Json file V2. + /// + /// The folder to save the pact file. + /// The filename for the .json file [optional]. + [PublicAPI] + public void SavePact(string folder, string? filename = null) + { + var (filenameUpdated, bytes) = PactMapper.ToPact(this, filename); + _settings.FileSystemHandler.WriteFile(folder, filenameUpdated, bytes); + } + + /// + /// Save the mappings as a Pact Json file V2. + /// + /// The (file) stream. + [PublicAPI] + public void SavePact(Stream stream) + { + var (_, bytes) = PactMapper.ToPact(this); + using var writer = new BinaryWriter(stream); + writer.Write(bytes); + + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + } + + /// + /// This stores details about the consumer of the interaction. + /// + /// the consumer + [PublicAPI] + public WireMockServer WithConsumer(string consumer) + { + Consumer = consumer; + return this; + } + + /// + /// This stores details about the provider of the interaction. + /// + /// the provider + [PublicAPI] + public WireMockServer WithProvider(string provider) + { + Provider = provider; + return this; + } + #endregion + + private void DisposeEnhancedFileSystemWatcher() + { + if (_enhancedFileSystemWatcher != null) + { + _enhancedFileSystemWatcher.EnableRaisingEvents = false; + + _enhancedFileSystemWatcher.Created -= EnhancedFileSystemWatcherCreated; + _enhancedFileSystemWatcher.Changed -= EnhancedFileSystemWatcherChanged; + _enhancedFileSystemWatcher.Deleted -= EnhancedFileSystemWatcherDeleted; + + _enhancedFileSystemWatcher.Dispose(); + } + } + + private void EnhancedFileSystemWatcherCreated(object sender, FileSystemEventArgs args) + { + _settings.Logger.Info("MappingFile created : '{0}', reading file.", args.FullPath); + if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) + { + _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); + } + } + + private void EnhancedFileSystemWatcherChanged(object sender, FileSystemEventArgs args) + { + _settings.Logger.Info("MappingFile updated : '{0}', reading file.", args.FullPath); + if (!ReadStaticMappingAndAddOrUpdate(args.FullPath)) + { + _settings.Logger.Error("Unable to read MappingFile '{0}'.", args.FullPath); + } + } + + private void EnhancedFileSystemWatcherDeleted(object sender, FileSystemEventArgs args) + { + _settings.Logger.Info("MappingFile deleted : '{0}'", args.FullPath); + var filenameWithoutExtension = Path.GetFileNameWithoutExtension(args.FullPath); + + if (Guid.TryParse(filenameWithoutExtension, out var guidFromFilename)) + { + DeleteMapping(guidFromFilename); + } + else + { + DeleteMapping(args.FullPath); + } + } + + private static Encoding? ToEncoding(EncodingModel? encodingModel) + { + return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null; + } + + private static ResponseMessage ToJson(T result, bool keepNullValues = false, object? statusCode = null) + { + return new ResponseMessage + { + BodyData = new BodyData + { + DetectedBodyType = BodyType.String, + BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault) + }, + StatusCode = statusCode ?? (int)HttpStatusCode.OK, + Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeJson) } } + }; + } + + private static ResponseMessage ToResponseMessage(string text) + { + return new ResponseMessage + { + BodyData = new BodyData + { + DetectedBodyType = BodyType.String, + BodyAsString = text + }, + StatusCode = (int)HttpStatusCode.OK, + Headers = new Dictionary> { { HttpKnownHeaderNames.ContentType, new WireMockList(WireMockConstants.ContentTypeTextPlain) } } + }; + } + + private static T DeserializeObject(IRequestMessage requestMessage) where T : new() + { + switch (requestMessage.BodyData?.DetectedBodyType) + { + case BodyType.String: + case BodyType.FormUrlEncoded: + return JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString!); + + case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null: + return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject()!; + + default: + throw new NotSupportedException(); + } + } + + private static T[] DeserializeRequestMessageToArray(IRequestMessage requestMessage) + { + if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null) + { + var bodyAsJson = requestMessage.BodyData.BodyAsJson; + + return DeserializeObjectToArray(bodyAsJson); + } + + throw new NotSupportedException(); + } + + private static T[] DeserializeJsonToArray(string value) + { + return DeserializeObjectToArray(JsonUtils.DeserializeObject(value)); + } + + private static T[] DeserializeObjectToArray(object value) + { + if (value is JArray jArray) + { + return jArray.ToObject()!; + } + + var singleResult = ((JObject)value).ToObject(); + return new[] { singleResult! }; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.AdminFiles.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.AdminFiles.cs similarity index 100% rename from src/WireMock.Net/Server/WireMockServer.AdminFiles.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.AdminFiles.cs diff --git a/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs similarity index 100% rename from src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.ConvertMapping.cs diff --git a/src/WireMock.Net/Server/WireMockServer.Fluent.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.Fluent.cs similarity index 100% rename from src/WireMock.Net/Server/WireMockServer.Fluent.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.Fluent.cs diff --git a/src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.ImportWireMockOrg.cs similarity index 100% rename from src/WireMock.Net/Server/WireMockServer.ImportWireMockOrg.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.ImportWireMockOrg.cs diff --git a/src/WireMock.Net/Server/WireMockServer.LogEntries.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.LogEntries.cs similarity index 96% rename from src/WireMock.Net/Server/WireMockServer.LogEntries.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.LogEntries.cs index ba4e98cf..7bc1a6df 100644 --- a/src/WireMock.Net/Server/WireMockServer.LogEntries.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.LogEntries.cs @@ -1,99 +1,99 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using JetBrains.Annotations; -using Stef.Validation; -using WireMock.Logging; -using WireMock.Matchers; -using WireMock.Matchers.Request; - -namespace WireMock.Server; - -public partial class WireMockServer -{ - /// - [PublicAPI] - public event NotifyCollectionChangedEventHandler LogEntriesChanged - { - add => _logEntriesChanged += value; - remove => _logEntriesChanged -= value; - } - - /// - [PublicAPI] - public IReadOnlyList LogEntries => _options.LogEntries.ToArray(); - - /// - [PublicAPI] - public IReadOnlyList FindLogEntries(params IRequestMatcher[] matchers) - { - Guard.NotNull(matchers); - - var results = new Dictionary(); - - var allLogEntries = LogEntries; - foreach (var log in allLogEntries) - { - var requestMatchResult = new RequestMatchResult(); - foreach (var matcher in matchers) - { - matcher.GetMatchingScore(log.RequestMessage, requestMatchResult); - } - - if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect) - { - results.Add(log, requestMatchResult); - } - } - - return results - .OrderBy(x => x.Value) - .Select(x => x.Key) - .ToArray(); - } - - /// - [PublicAPI] - public void ResetLogEntries() - { - _options.LogEntries.Clear(); - } - - /// - [PublicAPI] - public bool DeleteLogEntry(Guid guid) - { - // Check a LogEntry exists with the same GUID, if so, remove it. - var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid); - if (existing != null) - { - _options.LogEntries.Remove(existing); - return true; - } - - return false; - } - - private NotifyCollectionChangedEventHandler? _logEntriesChanged; - - private void LogEntries_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - if (_logEntriesChanged is { }) - { - foreach (var handler in _logEntriesChanged.GetInvocationList()) - { - try - { - handler.DynamicInvoke(this, e); - } - catch (Exception exception) - { - _options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message); - } - } - } - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using JetBrains.Annotations; +using Stef.Validation; +using WireMock.Logging; +using WireMock.Matchers; +using WireMock.Matchers.Request; + +namespace WireMock.Server; + +public partial class WireMockServer +{ + /// + [PublicAPI] + public event NotifyCollectionChangedEventHandler LogEntriesChanged + { + add => _logEntriesChanged += value; + remove => _logEntriesChanged -= value; + } + + /// + [PublicAPI] + public IReadOnlyList LogEntries => _options.LogEntries.ToArray(); + + /// + [PublicAPI] + public IReadOnlyList FindLogEntries(params IRequestMatcher[] matchers) + { + Guard.NotNull(matchers); + + var results = new Dictionary(); + + var allLogEntries = LogEntries; + foreach (var log in allLogEntries) + { + var requestMatchResult = new RequestMatchResult(); + foreach (var matcher in matchers) + { + matcher.GetMatchingScore(log.RequestMessage, requestMatchResult); + } + + if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect) + { + results.Add(log, requestMatchResult); + } + } + + return results + .OrderBy(x => x.Value) + .Select(x => x.Key) + .ToArray(); + } + + /// + [PublicAPI] + public void ResetLogEntries() + { + _options.LogEntries.Clear(); + } + + /// + [PublicAPI] + public bool DeleteLogEntry(Guid guid) + { + // Check a LogEntry exists with the same GUID, if so, remove it. + var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid); + if (existing != null) + { + _options.LogEntries.Remove(existing); + return true; + } + + return false; + } + + private NotifyCollectionChangedEventHandler? _logEntriesChanged; + + private void LogEntries_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (_logEntriesChanged is { }) + { + foreach (var handler in _logEntriesChanged.GetInvocationList()) + { + try + { + handler.DynamicInvoke(this, e); + } + catch (Exception exception) + { + _options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message); + } + } + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.OpenApiParser.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.OpenApiParser.cs similarity index 100% rename from src/WireMock.Net/Server/WireMockServer.OpenApiParser.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.OpenApiParser.cs diff --git a/src/WireMock.Net/Server/WireMockServer.Proxy.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.Proxy.cs similarity index 100% rename from src/WireMock.Net/Server/WireMockServer.Proxy.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.Proxy.cs diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net.Minimal/Server/WireMockServer.cs similarity index 97% rename from src/WireMock.Net/Server/WireMockServer.cs rename to src/WireMock.Net.Minimal/Server/WireMockServer.cs index adbfe120..ba88d50d 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net.Minimal/Server/WireMockServer.cs @@ -1,713 +1,713 @@ -// Copyright © WireMock.Net and mock4net by Alexandre Victoor - -// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. -// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using AnyOfTypes; -using JetBrains.Annotations; -using Newtonsoft.Json; -using Stef.Validation; -using WireMock.Admin.Mappings; -using WireMock.Authentication; -using WireMock.Constants; -using WireMock.Exceptions; -using WireMock.Handlers; -using WireMock.Http; -using WireMock.Logging; -using WireMock.Models; -using WireMock.Owin; -using WireMock.RequestBuilders; -using WireMock.ResponseProviders; -using WireMock.Serialization; -using WireMock.Settings; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Server; - -/// -/// The fluent mock server. -/// -public partial class WireMockServer : IWireMockServer -{ - private const int ServerStartDelayInMs = 100; - - private readonly WireMockServerSettings _settings; - private readonly IOwinSelfHost? _httpServer; - private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); - private readonly MappingConverter _mappingConverter; - private readonly MatcherMapper _matcherMapper; - private readonly MappingToFileSaver _mappingToFileSaver; - private readonly MappingBuilder _mappingBuilder; - private readonly IGuidUtils _guidUtils = new GuidUtils(); - private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils(); - - /// - [PublicAPI] - public bool IsStarted => _httpServer is { IsStarted: true }; - - /// - [PublicAPI] - public bool IsStartedWithAdminInterface => IsStarted && _settings.StartAdminInterface.GetValueOrDefault(); - - /// - [PublicAPI] - public List Ports { get; } - - /// - [PublicAPI] - public int Port => Ports?.FirstOrDefault() ?? default; - - /// - [PublicAPI] - public string[] Urls { get; } - - /// - [PublicAPI] - public string? Url => Urls?.FirstOrDefault(); - - /// - [PublicAPI] - public string? Consumer { get; private set; } - - /// - [PublicAPI] - public string? Provider { get; private set; } - - /// - /// Gets the mappings. - /// - [PublicAPI] - public IReadOnlyList Mappings => _options.Mappings.Values.ToArray(); - - /// - [PublicAPI] - public IReadOnlyList MappingModels => ToMappingModels(); - - /// - /// Gets the scenarios. - /// - [PublicAPI] - public ConcurrentDictionary Scenarios => new(_options.Scenarios); - - #region IDisposable Members - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - _options.LogEntries.CollectionChanged -= LogEntries_CollectionChanged; - - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - DisposeEnhancedFileSystemWatcher(); - _httpServer?.StopAsync(); - } - #endregion - - #region HttpClient - /// - /// Create a which can be used to call this instance. - /// - /// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked - /// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient - /// to the network and an System.Net.Http.HttpResponseMessage travels from the network - /// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion. - /// That is, the first entry is invoked first for an outbound request message but - /// last for an inbound response message. - /// - /// - [PublicAPI] - public HttpClient CreateClient(params DelegatingHandler[] handlers) - { - if (!IsStarted) - { - throw new InvalidOperationException("Unable to create HttpClient because the service is not started."); - } - - var client = HttpClientFactory2.Create(handlers); - client.BaseAddress = new Uri(Url!); - return client; - } - - /// - /// Create a which can be used to call this instance. - /// - /// The inner handler represents the destination of the HTTP message channel. - /// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked - /// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient - /// to the network and an System.Net.Http.HttpResponseMessage travels from the network - /// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion. - /// That is, the first entry is invoked first for an outbound request message but - /// last for an inbound response message. - /// - /// - [PublicAPI] - public HttpClient CreateClient(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers) - { - if (!IsStarted) - { - throw new InvalidOperationException("Unable to create HttpClient because the service is not started."); - } - - var client = HttpClientFactory2.Create(innerHandler, handlers); - client.BaseAddress = new Uri(Url!); - return client; - } - - /// - /// Create s (one for each URL) which can be used to call this instance. - /// The inner handler represents the destination of the HTTP message channel. - /// - /// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked - /// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient - /// to the network and an System.Net.Http.HttpResponseMessage travels from the network - /// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion. - /// That is, the first entry is invoked first for an outbound request message but - /// last for an inbound response message. - /// - /// - [PublicAPI] - public HttpClient[] CreateClients(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers) - { - if (!IsStarted) - { - throw new InvalidOperationException("Unable to create HttpClients because the service is not started."); - } - - return Urls.Select(url => - { - var client = HttpClientFactory2.Create(innerHandler, handlers); - client.BaseAddress = new Uri(url); - return client; - }).ToArray(); - } - #endregion - - #region Start/Stop - /// - /// Starts this WireMockServer with the specified settings. - /// - /// The WireMockServerSettings. - /// The . - [PublicAPI] - public static WireMockServer Start(WireMockServerSettings settings) - { - Guard.NotNull(settings); - - return new WireMockServer(settings); - } - - /// - /// Starts this WireMockServer with the specified settings. - /// - /// The action to configure the WireMockServerSettings. - /// The . - [PublicAPI] - public static WireMockServer Start(Action action) - { - Guard.NotNull(action); - - var settings = new WireMockServerSettings(); - - action(settings); - - return new WireMockServer(settings); - } - - /// - /// Start this WireMockServer. - /// - /// The port. - /// The SSL support. - /// Use HTTP 2 (needed for Grpc). - /// The . - [PublicAPI] - public static WireMockServer Start(int? port = 0, bool useSSL = false, bool useHttp2 = false) - { - return new WireMockServer(new WireMockServerSettings - { - Port = port, - UseSSL = useSSL, - UseHttp2 = useHttp2 - }); - } - - /// - /// Start this WireMockServer. - /// - /// The urls to listen on. - /// The . - [PublicAPI] - public static WireMockServer Start(params string[] urls) - { - Guard.NotNullOrEmpty(urls); - - return new WireMockServer(new WireMockServerSettings - { - Urls = urls - }); - } - - /// - /// Start this WireMockServer with the admin interface. - /// - /// The port. - /// The SSL support. - /// Use HTTP 2 (needed for Grpc). - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterface(int? port = 0, bool useSSL = false, bool useHttp2 = false) - { - return new WireMockServer(new WireMockServerSettings - { - Port = port, - UseSSL = useSSL, - UseHttp2 = useHttp2, - StartAdminInterface = true - }); - } - - /// - /// Start this WireMockServer with the admin interface. - /// - /// The urls. - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterface(params string[] urls) - { - Guard.NotNullOrEmpty(urls); - - return new WireMockServer(new WireMockServerSettings - { - Urls = urls, - StartAdminInterface = true - }); - } - - /// - /// Start this WireMockServer with the admin interface and read static mappings. - /// - /// The urls. - /// The . - [PublicAPI] - public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls) - { - Guard.NotNullOrEmpty(urls); - - return new WireMockServer(new WireMockServerSettings - { - Urls = urls, - StartAdminInterface = true, - ReadStaticMappings = true - }); - } - - /// - /// Initializes a new instance of the class. - /// - /// The settings. - /// - /// Service start failed with error: {_httpServer.RunningException.Message} - /// or - /// Service start failed with error: {startTask.Exception.Message} - /// - /// Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)} - protected WireMockServer(WireMockServerSettings settings) - { - _settings = Guard.NotNull(settings); - - // Set default values if not provided - _settings.Logger = settings.Logger ?? new WireMockNullLogger(); - _settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler(); - - _settings.Logger.Info("By Stef Heyenrath (https://github.com/wiremock/WireMock.Net)"); - _settings.Logger.Debug("Server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented)); - - HostUrlOptions urlOptions; - if (settings.Urls != null) - { - urlOptions = new HostUrlOptions - { - Urls = settings.Urls - }; - } - else - { - if (settings.HostingScheme is not null) - { - urlOptions = new HostUrlOptions - { - HostingScheme = settings.HostingScheme.Value, - UseHttp2 = settings.UseHttp2, - Port = settings.Port - }; - } - else - { - urlOptions = new HostUrlOptions - { - HostingScheme = settings.UseSSL == true ? HostingScheme.Https : HostingScheme.Http, - UseHttp2 = settings.UseHttp2, - Port = settings.Port - }; - } - } - - WireMockMiddlewareOptionsHelper.InitFromSettings(settings, _options, o => - { - o.LogEntries.CollectionChanged += LogEntries_CollectionChanged; - }); - - _matcherMapper = new MatcherMapper(_settings); - _mappingConverter = new MappingConverter(_matcherMapper); - _mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter); - _mappingBuilder = new MappingBuilder( - settings, - _options, - _mappingConverter, - _mappingToFileSaver, - _guidUtils, - _dateTimeUtils - ); - -#if USE_ASPNETCORE - _options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration; - _options.CorsPolicyOptions = _settings.CorsPolicyOptions; - _options.ClientCertificateMode = _settings.ClientCertificateMode; - _options.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate; - - _httpServer = new AspNetCoreSelfHost(_options, urlOptions); -#else - _httpServer = new OwinSelfHost(_options, urlOptions); -#endif - var startTask = _httpServer.StartAsync(); - - using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout)) - { - while (!_httpServer.IsStarted) - { - // Throw exception if service start fails - if (_httpServer.RunningException != null) - { - throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException); - } - - if (ctsStartTimeout.IsCancellationRequested) - { - // In case of an aggregate exception, throw the exception. - if (startTask.Exception != null) - { - throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception); - } - - // Else throw TimeoutException - throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}"); - } - - ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs); - } - - Urls = _httpServer.Urls.ToArray(); - Ports = _httpServer.Ports; - } - - InitSettings(settings); - } - - /// - [PublicAPI] - public void Stop() - { - var result = _httpServer?.StopAsync(); - result?.Wait(); // wait for stop to actually happen - } - #endregion - - /// - [PublicAPI] - public void AddCatchAllMapping() - { - Given(Request.Create().WithPath("/*").UsingAnyMethod()) - .WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05")) - .AtPriority(1000) - .RespondWith(new DynamicResponseProvider(_ => ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound))); - } - - /// - [PublicAPI] - public void Reset() - { - ResetLogEntries(); - - ResetScenarios(); - - ResetMappings(); - } - - /// - [PublicAPI] - public void ResetMappings() - { - foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface)) - { - _options.Mappings.TryRemove(nonAdmin.Key, out _); - } - } - - /// - [PublicAPI] - public bool DeleteMapping(Guid guid) - { - // Check a mapping exists with the same GUID, if so, remove it. - if (_options.Mappings.ContainsKey(guid)) - { - return _options.Mappings.TryRemove(guid, out _); - } - - return false; - } - - private bool DeleteMapping(string path) - { - // Check a mapping exists with the same path, if so, remove it. - var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase)); - return DeleteMapping(mapping.Key); - } - - /// - [PublicAPI] - public void AddGlobalProcessingDelay(TimeSpan delay) - { - _options.RequestProcessingDelay = delay; - } - - /// - [PublicAPI] - public void AllowPartialMapping(bool allow = true) - { - _settings.Logger.Info("AllowPartialMapping is set to {0}", allow); - _options.AllowPartialMapping = allow; - } - - /// - [PublicAPI] - public void SetAzureADAuthentication(string tenant, string audience) - { - Guard.NotNull(tenant); - Guard.NotNull(audience); - -#if NETSTANDARD1_3 - throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3"); -#else - _options.AuthenticationMatcher = new AzureADAuthenticationMatcher( - new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(), - new Microsoft.IdentityModel.Protocols.ConfigurationManager($"https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration", new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever()), - tenant, - audience); -#endif - } - - /// - [PublicAPI] - public void SetBasicAuthentication(string username, string password) - { - Guard.NotNull(username); - Guard.NotNull(password); - - _options.AuthenticationMatcher = new BasicAuthenticationMatcher(username, password); - } - - /// - [PublicAPI] - public void RemoveAuthentication() - { - _options.AuthenticationMatcher = null; - } - - /// - [PublicAPI] - public void SetMaxRequestLogCount(int? maxRequestLogCount) - { - _options.MaxRequestLogCount = maxRequestLogCount; - } - - /// - [PublicAPI] - public void SetRequestLogExpirationDuration(int? requestLogExpirationDuration) - { - _options.RequestLogExpirationDuration = requestLogExpirationDuration; - } - - /// - [PublicAPI] - public void ResetScenarios() - { - _options.Scenarios.Clear(); - } - - /// - [PublicAPI] - public bool ResetScenario(string name) - { - return _options.Scenarios.ContainsKey(name) && _options.Scenarios.TryRemove(name, out _); - } - - /// - [PublicAPI] - public IWireMockServer WithMapping(params MappingModel[] mappings) - { - foreach (var mapping in mappings) - { - ConvertMappingAndRegisterAsRespondProvider(mapping, mapping.Guid ?? Guid.NewGuid()); - } - - return this; - } - - /// - [PublicAPI] - public IWireMockServer WithMapping(string mappings) - { - var mappingModels = DeserializeJsonToArray(mappings); - foreach (var mappingModel in mappingModels) - { - ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid()); - } - - return this; - } - - /// - /// Add a Grpc ProtoDefinition at server-level. - /// - /// Unique identifier for the ProtoDefinition. - /// The ProtoDefinition as text. - /// - [PublicAPI] - public WireMockServer AddProtoDefinition(string id, params string[] protoDefinition) - { - Guard.NotNullOrWhiteSpace(id); - Guard.NotNullOrEmpty(protoDefinition); - - _settings.ProtoDefinitions ??= new Dictionary(); - - if (_settings.ProtoDefinitions.TryGetValue(id, out var existingProtoDefinitions)) - { - _settings.ProtoDefinitions[id] = existingProtoDefinitions.Union(protoDefinition).ToArray(); - } - else - { - _settings.ProtoDefinitions[id] = protoDefinition; - } - - return this; - } - - /// - /// Add a GraphQL Schema at server-level. - /// - /// Unique identifier for the GraphQL Schema. - /// The GraphQL Schema as string or StringPattern. - /// A dictionary defining the custom scalars used in this schema. [optional] - /// - [PublicAPI] - public WireMockServer AddGraphQLSchema(string id, AnyOf graphQLSchema, Dictionary? customScalars = null) - { - Guard.NotNullOrWhiteSpace(id); - Guard.NotNullOrWhiteSpace(graphQLSchema); - - _settings.GraphQLSchemas ??= new Dictionary(); - - _settings.GraphQLSchemas[id] = new GraphQLSchemaDetails - { - SchemaAsString = graphQLSchema, - CustomScalars = customScalars - }; - - return this; - } - - /// - [PublicAPI] - public string? MappingToCSharpCode(Guid guid, MappingConverterType converterType) - { - return _mappingBuilder.ToCSharpCode(guid, converterType); - } - - /// - [PublicAPI] - public string MappingsToCSharpCode(MappingConverterType converterType) - { - return _mappingBuilder.ToCSharpCode(converterType); - } - - private void InitSettings(WireMockServerSettings settings) - { - if (settings.AllowBodyForAllHttpMethods == true) - { - _settings.Logger.Info("AllowBodyForAllHttpMethods is set to True"); - } - - if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true) - { - _settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True"); - } - - if (settings.AllowPartialMapping == true) - { - AllowPartialMapping(); - } - - if (settings.StartAdminInterface == true) - { - if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) - { - SetBasicAuthentication(settings.AdminUsername!, settings.AdminPassword!); - } - - if (!string.IsNullOrEmpty(settings.AdminAzureADTenant) && !string.IsNullOrEmpty(settings.AdminAzureADAudience)) - { - SetAzureADAuthentication(settings.AdminAzureADTenant!, settings.AdminAzureADAudience!); - } - - InitAdmin(); - } - - if (settings.ReadStaticMappings == true) - { - ReadStaticMappings(); - } - - if (settings.WatchStaticMappings == true) - { - WatchStaticMappings(); - } - - InitProxyAndRecord(settings); - - if (settings.RequestLogExpirationDuration != null) - { - SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration); - } - - if (settings.MaxRequestLogCount != null) - { - SetMaxRequestLogCount(settings.MaxRequestLogCount); - } - } +// Copyright © WireMock.Net and mock4net by Alexandre Victoor + +// This source file is based on mock4net by Alexandre Victoor which is licensed under the Apache 2.0 License. +// For more details see 'mock4net/LICENSE.txt' and 'mock4net/readme.md' in this project root. +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using AnyOfTypes; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Stef.Validation; +using WireMock.Admin.Mappings; +using WireMock.Authentication; +using WireMock.Constants; +using WireMock.Exceptions; +using WireMock.Handlers; +using WireMock.Http; +using WireMock.Logging; +using WireMock.Models; +using WireMock.Owin; +using WireMock.RequestBuilders; +using WireMock.ResponseProviders; +using WireMock.Serialization; +using WireMock.Settings; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Server; + +/// +/// The fluent mock server. +/// +public partial class WireMockServer : IWireMockServer +{ + private const int ServerStartDelayInMs = 100; + + private readonly WireMockServerSettings _settings; + private readonly IOwinSelfHost? _httpServer; + private readonly IWireMockMiddlewareOptions _options = new WireMockMiddlewareOptions(); + private readonly MappingConverter _mappingConverter; + private readonly MatcherMapper _matcherMapper; + private readonly MappingToFileSaver _mappingToFileSaver; + private readonly MappingBuilder _mappingBuilder; + private readonly IGuidUtils _guidUtils = new GuidUtils(); + private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils(); + + /// + [PublicAPI] + public bool IsStarted => _httpServer is { IsStarted: true }; + + /// + [PublicAPI] + public bool IsStartedWithAdminInterface => IsStarted && _settings.StartAdminInterface.GetValueOrDefault(); + + /// + [PublicAPI] + public List Ports { get; } + + /// + [PublicAPI] + public int Port => Ports?.FirstOrDefault() ?? default; + + /// + [PublicAPI] + public string[] Urls { get; } + + /// + [PublicAPI] + public string? Url => Urls?.FirstOrDefault(); + + /// + [PublicAPI] + public string? Consumer { get; private set; } + + /// + [PublicAPI] + public string? Provider { get; private set; } + + /// + /// Gets the mappings. + /// + [PublicAPI] + public IReadOnlyList Mappings => _options.Mappings.Values.ToArray(); + + /// + [PublicAPI] + public IReadOnlyList MappingModels => ToMappingModels(); + + /// + /// Gets the scenarios. + /// + [PublicAPI] + public ConcurrentDictionary Scenarios => new(_options.Scenarios); + + #region IDisposable Members + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + _options.LogEntries.CollectionChanged -= LogEntries_CollectionChanged; + + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + DisposeEnhancedFileSystemWatcher(); + _httpServer?.StopAsync(); + } + #endregion + + #region HttpClient + /// + /// Create a which can be used to call this instance. + /// + /// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked + /// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient + /// to the network and an System.Net.Http.HttpResponseMessage travels from the network + /// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion. + /// That is, the first entry is invoked first for an outbound request message but + /// last for an inbound response message. + /// + /// + [PublicAPI] + public HttpClient CreateClient(params DelegatingHandler[] handlers) + { + if (!IsStarted) + { + throw new InvalidOperationException("Unable to create HttpClient because the service is not started."); + } + + var client = HttpClientFactory2.Create(handlers); + client.BaseAddress = new Uri(Url!); + return client; + } + + /// + /// Create a which can be used to call this instance. + /// + /// The inner handler represents the destination of the HTTP message channel. + /// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked + /// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient + /// to the network and an System.Net.Http.HttpResponseMessage travels from the network + /// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion. + /// That is, the first entry is invoked first for an outbound request message but + /// last for an inbound response message. + /// + /// + [PublicAPI] + public HttpClient CreateClient(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers) + { + if (!IsStarted) + { + throw new InvalidOperationException("Unable to create HttpClient because the service is not started."); + } + + var client = HttpClientFactory2.Create(innerHandler, handlers); + client.BaseAddress = new Uri(Url!); + return client; + } + + /// + /// Create s (one for each URL) which can be used to call this instance. + /// The inner handler represents the destination of the HTTP message channel. + /// + /// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked + /// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient + /// to the network and an System.Net.Http.HttpResponseMessage travels from the network + /// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion. + /// That is, the first entry is invoked first for an outbound request message but + /// last for an inbound response message. + /// + /// + [PublicAPI] + public HttpClient[] CreateClients(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers) + { + if (!IsStarted) + { + throw new InvalidOperationException("Unable to create HttpClients because the service is not started."); + } + + return Urls.Select(url => + { + var client = HttpClientFactory2.Create(innerHandler, handlers); + client.BaseAddress = new Uri(url); + return client; + }).ToArray(); + } + #endregion + + #region Start/Stop + /// + /// Starts this WireMockServer with the specified settings. + /// + /// The WireMockServerSettings. + /// The . + [PublicAPI] + public static WireMockServer Start(WireMockServerSettings settings) + { + Guard.NotNull(settings); + + return new WireMockServer(settings); + } + + /// + /// Starts this WireMockServer with the specified settings. + /// + /// The action to configure the WireMockServerSettings. + /// The . + [PublicAPI] + public static WireMockServer Start(Action action) + { + Guard.NotNull(action); + + var settings = new WireMockServerSettings(); + + action(settings); + + return new WireMockServer(settings); + } + + /// + /// Start this WireMockServer. + /// + /// The port. + /// The SSL support. + /// Use HTTP 2 (needed for Grpc). + /// The . + [PublicAPI] + public static WireMockServer Start(int? port = 0, bool useSSL = false, bool useHttp2 = false) + { + return new WireMockServer(new WireMockServerSettings + { + Port = port, + UseSSL = useSSL, + UseHttp2 = useHttp2 + }); + } + + /// + /// Start this WireMockServer. + /// + /// The urls to listen on. + /// The . + [PublicAPI] + public static WireMockServer Start(params string[] urls) + { + Guard.NotNullOrEmpty(urls); + + return new WireMockServer(new WireMockServerSettings + { + Urls = urls + }); + } + + /// + /// Start this WireMockServer with the admin interface. + /// + /// The port. + /// The SSL support. + /// Use HTTP 2 (needed for Grpc). + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterface(int? port = 0, bool useSSL = false, bool useHttp2 = false) + { + return new WireMockServer(new WireMockServerSettings + { + Port = port, + UseSSL = useSSL, + UseHttp2 = useHttp2, + StartAdminInterface = true + }); + } + + /// + /// Start this WireMockServer with the admin interface. + /// + /// The urls. + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterface(params string[] urls) + { + Guard.NotNullOrEmpty(urls); + + return new WireMockServer(new WireMockServerSettings + { + Urls = urls, + StartAdminInterface = true + }); + } + + /// + /// Start this WireMockServer with the admin interface and read static mappings. + /// + /// The urls. + /// The . + [PublicAPI] + public static WireMockServer StartWithAdminInterfaceAndReadStaticMappings(params string[] urls) + { + Guard.NotNullOrEmpty(urls); + + return new WireMockServer(new WireMockServerSettings + { + Urls = urls, + StartAdminInterface = true, + ReadStaticMappings = true + }); + } + + /// + /// Initializes a new instance of the class. + /// + /// The settings. + /// + /// Service start failed with error: {_httpServer.RunningException.Message} + /// or + /// Service start failed with error: {startTask.Exception.Message} + /// + /// Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)} + protected WireMockServer(WireMockServerSettings settings) + { + _settings = Guard.NotNull(settings); + + // Set default values if not provided + _settings.Logger = settings.Logger ?? new WireMockNullLogger(); + _settings.FileSystemHandler = settings.FileSystemHandler ?? new LocalFileSystemHandler(); + + _settings.Logger.Info("By Stef Heyenrath (https://github.com/wiremock/WireMock.Net)"); + _settings.Logger.Debug("Server settings {0}", JsonConvert.SerializeObject(settings, Formatting.Indented)); + + HostUrlOptions urlOptions; + if (settings.Urls != null) + { + urlOptions = new HostUrlOptions + { + Urls = settings.Urls + }; + } + else + { + if (settings.HostingScheme is not null) + { + urlOptions = new HostUrlOptions + { + HostingScheme = settings.HostingScheme.Value, + UseHttp2 = settings.UseHttp2, + Port = settings.Port + }; + } + else + { + urlOptions = new HostUrlOptions + { + HostingScheme = settings.UseSSL == true ? HostingScheme.Https : HostingScheme.Http, + UseHttp2 = settings.UseHttp2, + Port = settings.Port + }; + } + } + + WireMockMiddlewareOptionsHelper.InitFromSettings(settings, _options, o => + { + o.LogEntries.CollectionChanged += LogEntries_CollectionChanged; + }); + + _matcherMapper = new MatcherMapper(_settings); + _mappingConverter = new MappingConverter(_matcherMapper); + _mappingToFileSaver = new MappingToFileSaver(_settings, _mappingConverter); + _mappingBuilder = new MappingBuilder( + settings, + _options, + _mappingConverter, + _mappingToFileSaver, + _guidUtils, + _dateTimeUtils + ); + +#if USE_ASPNETCORE + _options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration; + _options.CorsPolicyOptions = _settings.CorsPolicyOptions; + _options.ClientCertificateMode = _settings.ClientCertificateMode; + _options.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate; + + _httpServer = new AspNetCoreSelfHost(_options, urlOptions); +#else + _httpServer = new OwinSelfHost(_options, urlOptions); +#endif + var startTask = _httpServer.StartAsync(); + + using (var ctsStartTimeout = new CancellationTokenSource(settings.StartTimeout)) + { + while (!_httpServer.IsStarted) + { + // Throw exception if service start fails + if (_httpServer.RunningException != null) + { + throw new WireMockException($"Service start failed with error: {_httpServer.RunningException.Message}", _httpServer.RunningException); + } + + if (ctsStartTimeout.IsCancellationRequested) + { + // In case of an aggregate exception, throw the exception. + if (startTask.Exception != null) + { + throw new WireMockException($"Service start failed with error: {startTask.Exception.Message}", startTask.Exception); + } + + // Else throw TimeoutException + throw new TimeoutException($"Service start timed out after {TimeSpan.FromMilliseconds(settings.StartTimeout)}"); + } + + ctsStartTimeout.Token.WaitHandle.WaitOne(ServerStartDelayInMs); + } + + Urls = _httpServer.Urls.ToArray(); + Ports = _httpServer.Ports; + } + + InitSettings(settings); + } + + /// + [PublicAPI] + public void Stop() + { + var result = _httpServer?.StopAsync(); + result?.Wait(); // wait for stop to actually happen + } + #endregion + + /// + [PublicAPI] + public void AddCatchAllMapping() + { + Given(Request.Create().WithPath("/*").UsingAnyMethod()) + .WithGuid(Guid.Parse("90008000-0000-4444-a17e-669cd84f1f05")) + .AtPriority(1000) + .RespondWith(new DynamicResponseProvider(_ => ResponseMessageBuilder.Create(HttpStatusCode.NotFound, WireMockConstants.NoMatchingFound))); + } + + /// + [PublicAPI] + public void Reset() + { + ResetLogEntries(); + + ResetScenarios(); + + ResetMappings(); + } + + /// + [PublicAPI] + public void ResetMappings() + { + foreach (var nonAdmin in _options.Mappings.ToArray().Where(m => !m.Value.IsAdminInterface)) + { + _options.Mappings.TryRemove(nonAdmin.Key, out _); + } + } + + /// + [PublicAPI] + public bool DeleteMapping(Guid guid) + { + // Check a mapping exists with the same GUID, if so, remove it. + if (_options.Mappings.ContainsKey(guid)) + { + return _options.Mappings.TryRemove(guid, out _); + } + + return false; + } + + private bool DeleteMapping(string path) + { + // Check a mapping exists with the same path, if so, remove it. + var mapping = _options.Mappings.ToArray().FirstOrDefault(entry => string.Equals(entry.Value.Path, path, StringComparison.OrdinalIgnoreCase)); + return DeleteMapping(mapping.Key); + } + + /// + [PublicAPI] + public void AddGlobalProcessingDelay(TimeSpan delay) + { + _options.RequestProcessingDelay = delay; + } + + /// + [PublicAPI] + public void AllowPartialMapping(bool allow = true) + { + _settings.Logger.Info("AllowPartialMapping is set to {0}", allow); + _options.AllowPartialMapping = allow; + } + + /// + [PublicAPI] + public void SetAzureADAuthentication(string tenant, string audience) + { + Guard.NotNull(tenant); + Guard.NotNull(audience); + +#if NETSTANDARD1_3 + throw new NotSupportedException("AzureADAuthentication is not supported for NETStandard 1.3"); +#else + _options.AuthenticationMatcher = new AzureADAuthenticationMatcher( + new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(), + new Microsoft.IdentityModel.Protocols.ConfigurationManager($"https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration", new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever()), + tenant, + audience); +#endif + } + + /// + [PublicAPI] + public void SetBasicAuthentication(string username, string password) + { + Guard.NotNull(username); + Guard.NotNull(password); + + _options.AuthenticationMatcher = new BasicAuthenticationMatcher(username, password); + } + + /// + [PublicAPI] + public void RemoveAuthentication() + { + _options.AuthenticationMatcher = null; + } + + /// + [PublicAPI] + public void SetMaxRequestLogCount(int? maxRequestLogCount) + { + _options.MaxRequestLogCount = maxRequestLogCount; + } + + /// + [PublicAPI] + public void SetRequestLogExpirationDuration(int? requestLogExpirationDuration) + { + _options.RequestLogExpirationDuration = requestLogExpirationDuration; + } + + /// + [PublicAPI] + public void ResetScenarios() + { + _options.Scenarios.Clear(); + } + + /// + [PublicAPI] + public bool ResetScenario(string name) + { + return _options.Scenarios.ContainsKey(name) && _options.Scenarios.TryRemove(name, out _); + } + + /// + [PublicAPI] + public IWireMockServer WithMapping(params MappingModel[] mappings) + { + foreach (var mapping in mappings) + { + ConvertMappingAndRegisterAsRespondProvider(mapping, mapping.Guid ?? Guid.NewGuid()); + } + + return this; + } + + /// + [PublicAPI] + public IWireMockServer WithMapping(string mappings) + { + var mappingModels = DeserializeJsonToArray(mappings); + foreach (var mappingModel in mappingModels) + { + ConvertMappingAndRegisterAsRespondProvider(mappingModel, mappingModel.Guid ?? Guid.NewGuid()); + } + + return this; + } + + /// + /// Add a Grpc ProtoDefinition at server-level. + /// + /// Unique identifier for the ProtoDefinition. + /// The ProtoDefinition as text. + /// + [PublicAPI] + public WireMockServer AddProtoDefinition(string id, params string[] protoDefinition) + { + Guard.NotNullOrWhiteSpace(id); + Guard.NotNullOrEmpty(protoDefinition); + + _settings.ProtoDefinitions ??= new Dictionary(); + + if (_settings.ProtoDefinitions.TryGetValue(id, out var existingProtoDefinitions)) + { + _settings.ProtoDefinitions[id] = existingProtoDefinitions.Union(protoDefinition).ToArray(); + } + else + { + _settings.ProtoDefinitions[id] = protoDefinition; + } + + return this; + } + + /// + /// Add a GraphQL Schema at server-level. + /// + /// Unique identifier for the GraphQL Schema. + /// The GraphQL Schema as string or StringPattern. + /// A dictionary defining the custom scalars used in this schema. [optional] + /// + [PublicAPI] + public WireMockServer AddGraphQLSchema(string id, AnyOf graphQLSchema, Dictionary? customScalars = null) + { + Guard.NotNullOrWhiteSpace(id); + Guard.NotNullOrWhiteSpace(graphQLSchema); + + _settings.GraphQLSchemas ??= new Dictionary(); + + _settings.GraphQLSchemas[id] = new GraphQLSchemaDetails + { + SchemaAsString = graphQLSchema, + CustomScalars = customScalars + }; + + return this; + } + + /// + [PublicAPI] + public string? MappingToCSharpCode(Guid guid, MappingConverterType converterType) + { + return _mappingBuilder.ToCSharpCode(guid, converterType); + } + + /// + [PublicAPI] + public string MappingsToCSharpCode(MappingConverterType converterType) + { + return _mappingBuilder.ToCSharpCode(converterType); + } + + private void InitSettings(WireMockServerSettings settings) + { + if (settings.AllowBodyForAllHttpMethods == true) + { + _settings.Logger.Info("AllowBodyForAllHttpMethods is set to True"); + } + + if (settings.AllowOnlyDefinedHttpStatusCodeInResponse == true) + { + _settings.Logger.Info("AllowOnlyDefinedHttpStatusCodeInResponse is set to True"); + } + + if (settings.AllowPartialMapping == true) + { + AllowPartialMapping(); + } + + if (settings.StartAdminInterface == true) + { + if (!string.IsNullOrEmpty(settings.AdminUsername) && !string.IsNullOrEmpty(settings.AdminPassword)) + { + SetBasicAuthentication(settings.AdminUsername!, settings.AdminPassword!); + } + + if (!string.IsNullOrEmpty(settings.AdminAzureADTenant) && !string.IsNullOrEmpty(settings.AdminAzureADAudience)) + { + SetAzureADAuthentication(settings.AdminAzureADTenant!, settings.AdminAzureADAudience!); + } + + InitAdmin(); + } + + if (settings.ReadStaticMappings == true) + { + ReadStaticMappings(); + } + + if (settings.WatchStaticMappings == true) + { + WatchStaticMappings(); + } + + InitProxyAndRecord(settings); + + if (settings.RequestLogExpirationDuration != null) + { + SetRequestLogExpirationDuration(settings.RequestLogExpirationDuration); + } + + if (settings.MaxRequestLogCount != null) + { + SetMaxRequestLogCount(settings.MaxRequestLogCount); + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/Services/IRandomizerDoubleBetween0And1.cs b/src/WireMock.Net.Minimal/Services/IRandomizerDoubleBetween0And1.cs similarity index 100% rename from src/WireMock.Net/Services/IRandomizerDoubleBetween0And1.cs rename to src/WireMock.Net.Minimal/Services/IRandomizerDoubleBetween0And1.cs diff --git a/src/WireMock.Net/Services/RandomizerDoubleBetween0And1.cs b/src/WireMock.Net.Minimal/Services/RandomizerDoubleBetween0And1.cs similarity index 100% rename from src/WireMock.Net/Services/RandomizerDoubleBetween0And1.cs rename to src/WireMock.Net.Minimal/Services/RandomizerDoubleBetween0And1.cs diff --git a/src/WireMock.Net/Settings/HandlebarsSettings.cs b/src/WireMock.Net.Minimal/Settings/HandlebarsSettings.cs similarity index 100% rename from src/WireMock.Net/Settings/HandlebarsSettings.cs rename to src/WireMock.Net.Minimal/Settings/HandlebarsSettings.cs diff --git a/src/WireMock.Net/Settings/HttpClientSettings.cs b/src/WireMock.Net.Minimal/Settings/HttpClientSettings.cs similarity index 100% rename from src/WireMock.Net/Settings/HttpClientSettings.cs rename to src/WireMock.Net.Minimal/Settings/HttpClientSettings.cs diff --git a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs b/src/WireMock.Net.Minimal/Settings/ProxyAndRecordSettings.cs similarity index 96% rename from src/WireMock.Net/Settings/ProxyAndRecordSettings.cs rename to src/WireMock.Net.Minimal/Settings/ProxyAndRecordSettings.cs index 01694f01..35536a70 100644 --- a/src/WireMock.Net/Settings/ProxyAndRecordSettings.cs +++ b/src/WireMock.Net.Minimal/Settings/ProxyAndRecordSettings.cs @@ -1,115 +1,115 @@ -// Copyright © WireMock.Net - -using JetBrains.Annotations; - -namespace WireMock.Settings; - -/// -/// ProxyAndRecordSettings -/// -public class ProxyAndRecordSettings : HttpClientSettings -{ - /// - /// Default prefix value for saved mapping file - /// - public const string DefaultPrefixForSavedMappingFile = "Proxy Mapping for "; - - /// - /// The URL to proxy. - /// - [PublicAPI] - public string Url { get; set; } = null!; - - /// - /// Save the mapping for each request/response to the internal Mappings. - /// - [PublicAPI] - public bool SaveMapping { get; set; } - - /// - /// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.) - /// - [PublicAPI] - public bool SaveMappingToFile { get; set; } - - /// - /// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.) - /// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported. - /// - /// Deprecated : use SaveMappingSettings. - /// - [PublicAPI] - public string SaveMappingForStatusCodePattern - { - set - { - if (SaveMappingSettings is null) - { - SaveMappingSettings = new ProxySaveMappingSettings(); - } - - SaveMappingSettings.StatusCodePattern = value; - } - } - - /// - /// Additional SaveMappingSettings. - /// - [PublicAPI] - public ProxySaveMappingSettings? SaveMappingSettings { get; set; } - - /// - /// Defines a list from headers which will be excluded from the saved mappings. - /// - [PublicAPI] - public string[]? ExcludedHeaders { get; set; } - - /// - /// Defines a list of params which will be excluded from the saved mappings. - /// - [PublicAPI] - public string[]? ExcludedParams { get; set; } - - /// - /// Defines a list of cookies which will be excluded from the saved mappings. - /// - [PublicAPI] - public string[]? ExcludedCookies { get; set; } - - /// - /// Replace Settings - /// - [PublicAPI] - public ProxyUrlReplaceSettings? ReplaceSettings { get; set; } - - /// - /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to true). - /// - //[PublicAPI] - //public bool PreferProxyMapping { get; set; } - - /// - /// When SaveMapping is set to true, this setting can be used to control the behavior of the generated request matchers for the new mapping. - /// - false, the default matchers will be used. - /// - true, the defined mappings in the request wil be used for the new mapping. - /// - /// Default value is false. - /// - public bool UseDefinedRequestMatchers { get; set; } - - /// - /// Append a unique GUID to the filename from the saved mapping file. - /// - public bool AppendGuidToSavedMappingFile { get; set; } - - /// - /// Set prefix for saved mapping file. - /// - public string PrefixForSavedMappingFile { get; set; } = DefaultPrefixForSavedMappingFile; - - /// - /// Proxy all Api calls, irrespective of any condition - /// - [PublicAPI] - public bool ProxyAll { get; set; } +// Copyright © WireMock.Net + +using JetBrains.Annotations; + +namespace WireMock.Settings; + +/// +/// ProxyAndRecordSettings +/// +public class ProxyAndRecordSettings : HttpClientSettings +{ + /// + /// Default prefix value for saved mapping file + /// + public const string DefaultPrefixForSavedMappingFile = "Proxy Mapping for "; + + /// + /// The URL to proxy. + /// + [PublicAPI] + public string Url { get; set; } = null!; + + /// + /// Save the mapping for each request/response to the internal Mappings. + /// + [PublicAPI] + public bool SaveMapping { get; set; } + + /// + /// Save the mapping for each request/response also to a file. (Note that SaveMapping must also be set to true.) + /// + [PublicAPI] + public bool SaveMappingToFile { get; set; } + + /// + /// Only save request/response to the internal Mappings if the status code is included in this pattern. (Note that SaveMapping must also be set to true.) + /// The pattern can contain a single value like "200", but also ranges like "2xx", "100,300,600" or "100-299,6xx" are supported. + /// + /// Deprecated : use SaveMappingSettings. + /// + [PublicAPI] + public string SaveMappingForStatusCodePattern + { + set + { + if (SaveMappingSettings is null) + { + SaveMappingSettings = new ProxySaveMappingSettings(); + } + + SaveMappingSettings.StatusCodePattern = value; + } + } + + /// + /// Additional SaveMappingSettings. + /// + [PublicAPI] + public ProxySaveMappingSettings? SaveMappingSettings { get; set; } + + /// + /// Defines a list from headers which will be excluded from the saved mappings. + /// + [PublicAPI] + public string[]? ExcludedHeaders { get; set; } + + /// + /// Defines a list of params which will be excluded from the saved mappings. + /// + [PublicAPI] + public string[]? ExcludedParams { get; set; } + + /// + /// Defines a list of cookies which will be excluded from the saved mappings. + /// + [PublicAPI] + public string[]? ExcludedCookies { get; set; } + + /// + /// Replace Settings + /// + [PublicAPI] + public ProxyUrlReplaceSettings? ReplaceSettings { get; set; } + + /// + /// Prefer the Proxy Mapping over the saved Mapping (in case SaveMapping is set to true). + /// + //[PublicAPI] + //public bool PreferProxyMapping { get; set; } + + /// + /// When SaveMapping is set to true, this setting can be used to control the behavior of the generated request matchers for the new mapping. + /// - false, the default matchers will be used. + /// - true, the defined mappings in the request wil be used for the new mapping. + /// + /// Default value is false. + /// + public bool UseDefinedRequestMatchers { get; set; } + + /// + /// Append a unique GUID to the filename from the saved mapping file. + /// + public bool AppendGuidToSavedMappingFile { get; set; } + + /// + /// Set prefix for saved mapping file. + /// + public string PrefixForSavedMappingFile { get; set; } = DefaultPrefixForSavedMappingFile; + + /// + /// Proxy all Api calls, irrespective of any condition + /// + [PublicAPI] + public bool ProxyAll { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/ProxySaveMappingSetting.cs b/src/WireMock.Net.Minimal/Settings/ProxySaveMappingSetting.cs similarity index 100% rename from src/WireMock.Net/Settings/ProxySaveMappingSetting.cs rename to src/WireMock.Net.Minimal/Settings/ProxySaveMappingSetting.cs diff --git a/src/WireMock.Net/Settings/ProxySaveMappingSettings.cs b/src/WireMock.Net.Minimal/Settings/ProxySaveMappingSettings.cs similarity index 100% rename from src/WireMock.Net/Settings/ProxySaveMappingSettings.cs rename to src/WireMock.Net.Minimal/Settings/ProxySaveMappingSettings.cs diff --git a/src/WireMock.Net/Settings/ProxyUrlReplaceSettings.cs b/src/WireMock.Net.Minimal/Settings/ProxyUrlReplaceSettings.cs similarity index 96% rename from src/WireMock.Net/Settings/ProxyUrlReplaceSettings.cs rename to src/WireMock.Net.Minimal/Settings/ProxyUrlReplaceSettings.cs index 25a6efb1..bfffb64c 100644 --- a/src/WireMock.Net/Settings/ProxyUrlReplaceSettings.cs +++ b/src/WireMock.Net.Minimal/Settings/ProxyUrlReplaceSettings.cs @@ -1,24 +1,24 @@ -// Copyright © WireMock.Net - -namespace WireMock.Settings; - -/// -/// Defines an old path param and a new path param to be replaced when proxying. -/// -public class ProxyUrlReplaceSettings -{ - /// - /// The old path value to be replaced by the new path value - /// - public string OldValue { get; set; } = null!; - - /// - /// The new path value to replace the old value with - /// - public string NewValue { get; set; } = null!; - - /// - /// Defines if the case should be ignored when replacing. - /// - public bool IgnoreCase { get; set; } +// Copyright © WireMock.Net + +namespace WireMock.Settings; + +/// +/// Defines an old path param and a new path param to be replaced when proxying. +/// +public class ProxyUrlReplaceSettings +{ + /// + /// The old path value to be replaced by the new path value + /// + public string OldValue { get; set; } = null!; + + /// + /// The new path value to replace the old value with + /// + public string NewValue { get; set; } = null!; + + /// + /// Defines if the case should be ignored when replacing. + /// + public bool IgnoreCase { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/SimpleSettingsParser.cs b/src/WireMock.Net.Minimal/Settings/SimpleSettingsParser.cs similarity index 98% rename from src/WireMock.Net/Settings/SimpleSettingsParser.cs rename to src/WireMock.Net.Minimal/Settings/SimpleSettingsParser.cs index 54fa92bb..5c282c23 100644 --- a/src/WireMock.Net/Settings/SimpleSettingsParser.cs +++ b/src/WireMock.Net.Minimal/Settings/SimpleSettingsParser.cs @@ -39,7 +39,7 @@ internal class SimpleSettingsParser } else if (string.IsNullOrEmpty(currentName)) { - Arguments[arg] = EmptyArray.Value; + Arguments[arg] = []; } else { diff --git a/src/WireMock.Net/Settings/WebProxySettings.cs b/src/WireMock.Net.Minimal/Settings/WebProxySettings.cs similarity index 100% rename from src/WireMock.Net/Settings/WebProxySettings.cs rename to src/WireMock.Net.Minimal/Settings/WebProxySettings.cs diff --git a/src/WireMock.Net/Settings/WebhookSettings.cs b/src/WireMock.Net.Minimal/Settings/WebhookSettings.cs similarity index 100% rename from src/WireMock.Net/Settings/WebhookSettings.cs rename to src/WireMock.Net.Minimal/Settings/WebhookSettings.cs diff --git a/src/WireMock.Net/Settings/WireMockCertificateSettings.cs b/src/WireMock.Net.Minimal/Settings/WireMockCertificateSettings.cs similarity index 100% rename from src/WireMock.Net/Settings/WireMockCertificateSettings.cs rename to src/WireMock.Net.Minimal/Settings/WireMockCertificateSettings.cs diff --git a/src/WireMock.Net/Settings/WireMockServerSettings.cs b/src/WireMock.Net.Minimal/Settings/WireMockServerSettings.cs similarity index 100% rename from src/WireMock.Net/Settings/WireMockServerSettings.cs rename to src/WireMock.Net.Minimal/Settings/WireMockServerSettings.cs diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsParser.cs similarity index 98% rename from src/WireMock.Net/Settings/WireMockServerSettingsParser.cs rename to src/WireMock.Net.Minimal/Settings/WireMockServerSettingsParser.cs index e99b2981..d65029b1 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsParser.cs @@ -1,216 +1,216 @@ -// Copyright © WireMock.Net - -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using JetBrains.Annotations; -using Stef.Validation; -using WireMock.Logging; -using WireMock.Models; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Settings; - -/// -/// A static helper class to parse commandline arguments into WireMockServerSettings. -/// -public static class WireMockServerSettingsParser -{ - /// - /// Parse commandline arguments into WireMockServerSettings. - /// - /// The commandline arguments - /// The environment settings (optional) - /// The logger (optional, can be null) - /// The parsed settings - [PublicAPI] - public static bool TryParseArguments(string[] args, IDictionary? environment, [NotNullWhen(true)] out WireMockServerSettings? settings, IWireMockLogger? logger = null) - { - Guard.HasNoNulls(args); - - var parser = new SimpleSettingsParser(); - parser.Parse(args, environment); - - if (parser.GetBoolSwitchValue("help")) - { - (logger ?? new WireMockConsoleLogger()).Info("See https://github.com/wiremock/WireMock.Net/wiki/WireMock-commandline-parameters for details on all commandline options."); - settings = null; - return false; - } - - settings = new WireMockServerSettings - { - AdminAzureADAudience = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADAudience)), - AdminAzureADTenant = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADTenant)), - AdminPassword = parser.GetStringValue(nameof(WireMockServerSettings.AdminPassword)), - AdminUsername = parser.GetStringValue(nameof(WireMockServerSettings.AdminUsername)), - AdminPath = parser.GetStringValue(nameof(WireMockServerSettings.AdminPath), "/__admin"), - AllowBodyForAllHttpMethods = parser.GetBoolValue(nameof(WireMockServerSettings.AllowBodyForAllHttpMethods)), - AllowCSharpCodeMatcher = parser.GetBoolValue(nameof(WireMockServerSettings.AllowCSharpCodeMatcher)), - AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue(nameof(WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse)), - AllowPartialMapping = parser.GetBoolValue(nameof(WireMockServerSettings.AllowPartialMapping)), - Culture = parser.GetValue(nameof(WireMockServerSettings.Culture), strings => CultureInfoUtils.Parse(strings.FirstOrDefault()), CultureInfo.CurrentCulture), - DisableJsonBodyParsing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableJsonBodyParsing)), - DisableRequestBodyDecompressing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableRequestBodyDecompressing)), - DisableDeserializeFormUrlEncoded = parser.GetBoolValue(nameof(WireMockServerSettings.DisableDeserializeFormUrlEncoded)), - DoNotSaveDynamicResponseInLogEntry = parser.GetBoolValue(nameof(WireMockServerSettings.DoNotSaveDynamicResponseInLogEntry)), - GraphQLSchemas = parser.GetObjectValueFromJson>(nameof(settings.GraphQLSchemas)), - HandleRequestsSynchronously = parser.GetBoolValue(nameof(WireMockServerSettings.HandleRequestsSynchronously)), - HostingScheme = parser.GetEnumValue(nameof(WireMockServerSettings.HostingScheme)), - MaxRequestLogCount = parser.GetIntValue(nameof(WireMockServerSettings.MaxRequestLogCount)), - ProtoDefinitions = parser.GetObjectValueFromJson>(nameof(settings.ProtoDefinitions)), - QueryParameterMultipleValueSupport = parser.GetEnumValue(nameof(WireMockServerSettings.QueryParameterMultipleValueSupport)), - ReadStaticMappings = parser.GetBoolValue(nameof(WireMockServerSettings.ReadStaticMappings)), - RequestLogExpirationDuration = parser.GetIntValue(nameof(WireMockServerSettings.RequestLogExpirationDuration)), - SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)), - StartAdminInterface = parser.GetBoolValue(nameof(WireMockServerSettings.StartAdminInterface), true), - StartTimeout = parser.GetIntValue(nameof(WireMockServerSettings.StartTimeout), WireMockServerSettings.DefaultStartTimeout), - UseHttp2 = parser.GetBoolValue(nameof(WireMockServerSettings.UseHttp2)), - UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true), - WatchStaticMappings = parser.GetBoolValue(nameof(WireMockServerSettings.WatchStaticMappings)), - WatchStaticMappingsInSubdirectories = parser.GetBoolValue(nameof(WireMockServerSettings.WatchStaticMappingsInSubdirectories)), - }; - -#if USE_ASPNETCORE - settings.CorsPolicyOptions = parser.GetEnumValue(nameof(WireMockServerSettings.CorsPolicyOptions), CorsPolicyOptions.None); - settings.ClientCertificateMode = parser.GetEnumValue(nameof(WireMockServerSettings.ClientCertificateMode), ClientCertificateMode.NoCertificate); - settings.AcceptAnyClientCertificate = parser.GetBoolValue(nameof(WireMockServerSettings.AcceptAnyClientCertificate)); -#endif - - ParseLoggerSettings(settings, logger, parser); - ParsePortSettings(settings, parser); - ParseProxyAndRecordSettings(settings, parser); - ParseCertificateSettings(settings, parser); - ParseHandlebarsSettings(settings, parser); - - return true; - } - - private static void ParseLoggerSettings(WireMockServerSettings settings, IWireMockLogger? logger, SimpleSettingsParser parser) - { - var loggerType = parser.GetStringValue("WireMockLogger"); - switch (loggerType) - { - case nameof(WireMockConsoleLogger): - settings.Logger = new WireMockConsoleLogger(); - break; - - case nameof(WireMockNullLogger): - settings.Logger = new WireMockNullLogger(); - break; - - default: - if (logger != null) - { - settings.Logger = logger; - } - break; - } - } - - private static void ParseProxyAndRecordSettings(WireMockServerSettings settings, SimpleSettingsParser parser) - { - var proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl"); - if (!string.IsNullOrEmpty(proxyUrl)) - { - var proxyAndRecordSettings = new ProxyAndRecordSettings - { - AllowAutoRedirect = parser.GetBoolValue(nameof(ProxyAndRecordSettings.AllowAutoRedirect)), - ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue(nameof(ProxyAndRecordSettings.ClientX509Certificate2ThumbprintOrSubjectName)), - ExcludedCookies = parser.GetValues(nameof(ProxyAndRecordSettings.ExcludedCookies)), - ExcludedHeaders = parser.GetValues(nameof(ProxyAndRecordSettings.ExcludedHeaders)), - // PreferProxyMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.PreferProxyMapping)), - SaveMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.SaveMapping)), - SaveMappingForStatusCodePattern = parser.GetStringValue(nameof(ProxyAndRecordSettings.SaveMappingForStatusCodePattern), "*"), - SaveMappingToFile = parser.GetBoolValue(nameof(ProxyAndRecordSettings.SaveMappingToFile)), - UseDefinedRequestMatchers = parser.GetBoolValue(nameof(ProxyAndRecordSettings.UseDefinedRequestMatchers)), - AppendGuidToSavedMappingFile = parser.GetBoolValue(nameof(ProxyAndRecordSettings.AppendGuidToSavedMappingFile)), - PrefixForSavedMappingFile = parser.GetStringValue(nameof(ProxyAndRecordSettings.PrefixForSavedMappingFile), ProxyAndRecordSettings.DefaultPrefixForSavedMappingFile), - Url = proxyUrl!, - SaveMappingSettings = new ProxySaveMappingSettings - { - StatusCodePattern = parser.GetStringValue(nameof(ProxyAndRecordSettings.SaveMappingForStatusCodePattern), "*"), - // HttpMethods = new ProxySaveMappingSetting(parser.GetValues("DoNotSaveMappingForHttpMethods", new string[0]), MatchBehaviour.RejectOnMatch) - }, - ProxyAll = parser.GetBoolValue(nameof(ProxyAndRecordSettings.ProxyAll)) - }; - - ParseWebProxyAddressSettings(proxyAndRecordSettings, parser); - ParseProxyUrlReplaceSettings(proxyAndRecordSettings, parser); - - settings.ProxyAndRecordSettings = proxyAndRecordSettings; - } - } - - private static void ParsePortSettings(WireMockServerSettings settings, SimpleSettingsParser parser) - { - if (parser.Contains(nameof(WireMockServerSettings.Port))) - { - settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port)); - } - else if (settings.HostingScheme is null) - { - settings.Urls = parser.GetValues("Urls", ["http://*:9091/"]); - } - } - - private static void ParseCertificateSettings(WireMockServerSettings settings, SimpleSettingsParser parser) - { - var certificateSettings = new WireMockCertificateSettings - { - X509StoreName = parser.GetStringValue("X509StoreName"), - X509StoreLocation = parser.GetStringValue("X509StoreLocation"), - X509StoreThumbprintOrSubjectName = parser.GetStringValue("X509StoreThumbprintOrSubjectName"), - X509CertificateFilePath = parser.GetStringValue("X509CertificateFilePath"), - X509CertificatePassword = parser.GetStringValue("X509CertificatePassword") - }; - - if (certificateSettings.IsDefined) - { - settings.CertificateSettings = certificateSettings; - } - } - - private static void ParseHandlebarsSettings(WireMockServerSettings settings, SimpleSettingsParser parser) - { - if (parser.ContainsAny(nameof(HandlebarsSettings.AllowedCustomHandlebarsHelpers), nameof(HandlebarsSettings.AllowedHandlebarsHelpers))) - { - settings.HandlebarsSettings = new HandlebarsSettings - { - AllowedCustomHandlebarsHelpers = parser.GetEnumValue(nameof(HandlebarsSettings.AllowedCustomHandlebarsHelpers), CustomHandlebarsHelpers.None), - AllowedHandlebarsHelpers = parser.GetEnumValues(nameof(HandlebarsSettings.AllowedHandlebarsHelpers), HandlebarsSettings.DefaultAllowedHandlebarsHelpers) - }; - } - } - - private static void ParseWebProxyAddressSettings(ProxyAndRecordSettings settings, SimpleSettingsParser parser) - { - string? proxyAddress = parser.GetStringValue("WebProxyAddress"); - if (!string.IsNullOrEmpty(proxyAddress)) - { - settings.WebProxySettings = new WebProxySettings - { - Address = proxyAddress!, - UserName = parser.GetStringValue("WebProxyUserName"), - Password = parser.GetStringValue("WebProxyPassword") - }; - } - } - - private static void ParseProxyUrlReplaceSettings(ProxyAndRecordSettings settings, SimpleSettingsParser parser) - { - var proxyUrlReplaceOldValue = parser.GetStringValue("ProxyUrlReplaceOldValue"); - var proxyUrlReplaceNewValue = parser.GetStringValue("ProxyUrlReplaceNewValue"); - if (!string.IsNullOrEmpty(proxyUrlReplaceOldValue) && proxyUrlReplaceNewValue != null) - { - settings.ReplaceSettings = new ProxyUrlReplaceSettings - { - OldValue = proxyUrlReplaceOldValue!, - NewValue = proxyUrlReplaceNewValue - }; - } - } +// Copyright © WireMock.Net + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using JetBrains.Annotations; +using Stef.Validation; +using WireMock.Logging; +using WireMock.Models; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Settings; + +/// +/// A static helper class to parse commandline arguments into WireMockServerSettings. +/// +public static class WireMockServerSettingsParser +{ + /// + /// Parse commandline arguments into WireMockServerSettings. + /// + /// The commandline arguments + /// The environment settings (optional) + /// The logger (optional, can be null) + /// The parsed settings + [PublicAPI] + public static bool TryParseArguments(string[] args, IDictionary? environment, [NotNullWhen(true)] out WireMockServerSettings? settings, IWireMockLogger? logger = null) + { + Guard.HasNoNulls(args); + + var parser = new SimpleSettingsParser(); + parser.Parse(args, environment); + + if (parser.GetBoolSwitchValue("help")) + { + (logger ?? new WireMockConsoleLogger()).Info("See https://github.com/wiremock/WireMock.Net/wiki/WireMock-commandline-parameters for details on all commandline options."); + settings = null; + return false; + } + + settings = new WireMockServerSettings + { + AdminAzureADAudience = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADAudience)), + AdminAzureADTenant = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADTenant)), + AdminPassword = parser.GetStringValue(nameof(WireMockServerSettings.AdminPassword)), + AdminUsername = parser.GetStringValue(nameof(WireMockServerSettings.AdminUsername)), + AdminPath = parser.GetStringValue(nameof(WireMockServerSettings.AdminPath), "/__admin"), + AllowBodyForAllHttpMethods = parser.GetBoolValue(nameof(WireMockServerSettings.AllowBodyForAllHttpMethods)), + AllowCSharpCodeMatcher = parser.GetBoolValue(nameof(WireMockServerSettings.AllowCSharpCodeMatcher)), + AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue(nameof(WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse)), + AllowPartialMapping = parser.GetBoolValue(nameof(WireMockServerSettings.AllowPartialMapping)), + Culture = parser.GetValue(nameof(WireMockServerSettings.Culture), strings => CultureInfoUtils.Parse(strings.FirstOrDefault()), CultureInfo.CurrentCulture), + DisableJsonBodyParsing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableJsonBodyParsing)), + DisableRequestBodyDecompressing = parser.GetBoolValue(nameof(WireMockServerSettings.DisableRequestBodyDecompressing)), + DisableDeserializeFormUrlEncoded = parser.GetBoolValue(nameof(WireMockServerSettings.DisableDeserializeFormUrlEncoded)), + DoNotSaveDynamicResponseInLogEntry = parser.GetBoolValue(nameof(WireMockServerSettings.DoNotSaveDynamicResponseInLogEntry)), + GraphQLSchemas = parser.GetObjectValueFromJson>(nameof(settings.GraphQLSchemas)), + HandleRequestsSynchronously = parser.GetBoolValue(nameof(WireMockServerSettings.HandleRequestsSynchronously)), + HostingScheme = parser.GetEnumValue(nameof(WireMockServerSettings.HostingScheme)), + MaxRequestLogCount = parser.GetIntValue(nameof(WireMockServerSettings.MaxRequestLogCount)), + ProtoDefinitions = parser.GetObjectValueFromJson>(nameof(settings.ProtoDefinitions)), + QueryParameterMultipleValueSupport = parser.GetEnumValue(nameof(WireMockServerSettings.QueryParameterMultipleValueSupport)), + ReadStaticMappings = parser.GetBoolValue(nameof(WireMockServerSettings.ReadStaticMappings)), + RequestLogExpirationDuration = parser.GetIntValue(nameof(WireMockServerSettings.RequestLogExpirationDuration)), + SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)), + StartAdminInterface = parser.GetBoolValue(nameof(WireMockServerSettings.StartAdminInterface), true), + StartTimeout = parser.GetIntValue(nameof(WireMockServerSettings.StartTimeout), WireMockServerSettings.DefaultStartTimeout), + UseHttp2 = parser.GetBoolValue(nameof(WireMockServerSettings.UseHttp2)), + UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true), + WatchStaticMappings = parser.GetBoolValue(nameof(WireMockServerSettings.WatchStaticMappings)), + WatchStaticMappingsInSubdirectories = parser.GetBoolValue(nameof(WireMockServerSettings.WatchStaticMappingsInSubdirectories)), + }; + +#if USE_ASPNETCORE + settings.CorsPolicyOptions = parser.GetEnumValue(nameof(WireMockServerSettings.CorsPolicyOptions), CorsPolicyOptions.None); + settings.ClientCertificateMode = parser.GetEnumValue(nameof(WireMockServerSettings.ClientCertificateMode), ClientCertificateMode.NoCertificate); + settings.AcceptAnyClientCertificate = parser.GetBoolValue(nameof(WireMockServerSettings.AcceptAnyClientCertificate)); +#endif + + ParseLoggerSettings(settings, logger, parser); + ParsePortSettings(settings, parser); + ParseProxyAndRecordSettings(settings, parser); + ParseCertificateSettings(settings, parser); + ParseHandlebarsSettings(settings, parser); + + return true; + } + + private static void ParseLoggerSettings(WireMockServerSettings settings, IWireMockLogger? logger, SimpleSettingsParser parser) + { + var loggerType = parser.GetStringValue("WireMockLogger"); + switch (loggerType) + { + case nameof(WireMockConsoleLogger): + settings.Logger = new WireMockConsoleLogger(); + break; + + case nameof(WireMockNullLogger): + settings.Logger = new WireMockNullLogger(); + break; + + default: + if (logger != null) + { + settings.Logger = logger; + } + break; + } + } + + private static void ParseProxyAndRecordSettings(WireMockServerSettings settings, SimpleSettingsParser parser) + { + var proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl"); + if (!string.IsNullOrEmpty(proxyUrl)) + { + var proxyAndRecordSettings = new ProxyAndRecordSettings + { + AllowAutoRedirect = parser.GetBoolValue(nameof(ProxyAndRecordSettings.AllowAutoRedirect)), + ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue(nameof(ProxyAndRecordSettings.ClientX509Certificate2ThumbprintOrSubjectName)), + ExcludedCookies = parser.GetValues(nameof(ProxyAndRecordSettings.ExcludedCookies)), + ExcludedHeaders = parser.GetValues(nameof(ProxyAndRecordSettings.ExcludedHeaders)), + // PreferProxyMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.PreferProxyMapping)), + SaveMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.SaveMapping)), + SaveMappingForStatusCodePattern = parser.GetStringValue(nameof(ProxyAndRecordSettings.SaveMappingForStatusCodePattern), "*"), + SaveMappingToFile = parser.GetBoolValue(nameof(ProxyAndRecordSettings.SaveMappingToFile)), + UseDefinedRequestMatchers = parser.GetBoolValue(nameof(ProxyAndRecordSettings.UseDefinedRequestMatchers)), + AppendGuidToSavedMappingFile = parser.GetBoolValue(nameof(ProxyAndRecordSettings.AppendGuidToSavedMappingFile)), + PrefixForSavedMappingFile = parser.GetStringValue(nameof(ProxyAndRecordSettings.PrefixForSavedMappingFile), ProxyAndRecordSettings.DefaultPrefixForSavedMappingFile), + Url = proxyUrl!, + SaveMappingSettings = new ProxySaveMappingSettings + { + StatusCodePattern = parser.GetStringValue(nameof(ProxyAndRecordSettings.SaveMappingForStatusCodePattern), "*"), + // HttpMethods = new ProxySaveMappingSetting(parser.GetValues("DoNotSaveMappingForHttpMethods", new string[0]), MatchBehaviour.RejectOnMatch) + }, + ProxyAll = parser.GetBoolValue(nameof(ProxyAndRecordSettings.ProxyAll)) + }; + + ParseWebProxyAddressSettings(proxyAndRecordSettings, parser); + ParseProxyUrlReplaceSettings(proxyAndRecordSettings, parser); + + settings.ProxyAndRecordSettings = proxyAndRecordSettings; + } + } + + private static void ParsePortSettings(WireMockServerSettings settings, SimpleSettingsParser parser) + { + if (parser.Contains(nameof(WireMockServerSettings.Port))) + { + settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port)); + } + else if (settings.HostingScheme is null) + { + settings.Urls = parser.GetValues("Urls", ["http://*:9091/"]); + } + } + + private static void ParseCertificateSettings(WireMockServerSettings settings, SimpleSettingsParser parser) + { + var certificateSettings = new WireMockCertificateSettings + { + X509StoreName = parser.GetStringValue("X509StoreName"), + X509StoreLocation = parser.GetStringValue("X509StoreLocation"), + X509StoreThumbprintOrSubjectName = parser.GetStringValue("X509StoreThumbprintOrSubjectName"), + X509CertificateFilePath = parser.GetStringValue("X509CertificateFilePath"), + X509CertificatePassword = parser.GetStringValue("X509CertificatePassword") + }; + + if (certificateSettings.IsDefined) + { + settings.CertificateSettings = certificateSettings; + } + } + + private static void ParseHandlebarsSettings(WireMockServerSettings settings, SimpleSettingsParser parser) + { + if (parser.ContainsAny(nameof(HandlebarsSettings.AllowedCustomHandlebarsHelpers), nameof(HandlebarsSettings.AllowedHandlebarsHelpers))) + { + settings.HandlebarsSettings = new HandlebarsSettings + { + AllowedCustomHandlebarsHelpers = parser.GetEnumValue(nameof(HandlebarsSettings.AllowedCustomHandlebarsHelpers), CustomHandlebarsHelpers.None), + AllowedHandlebarsHelpers = parser.GetEnumValues(nameof(HandlebarsSettings.AllowedHandlebarsHelpers), HandlebarsSettings.DefaultAllowedHandlebarsHelpers) + }; + } + } + + private static void ParseWebProxyAddressSettings(ProxyAndRecordSettings settings, SimpleSettingsParser parser) + { + string? proxyAddress = parser.GetStringValue("WebProxyAddress"); + if (!string.IsNullOrEmpty(proxyAddress)) + { + settings.WebProxySettings = new WebProxySettings + { + Address = proxyAddress!, + UserName = parser.GetStringValue("WebProxyUserName"), + Password = parser.GetStringValue("WebProxyPassword") + }; + } + } + + private static void ParseProxyUrlReplaceSettings(ProxyAndRecordSettings settings, SimpleSettingsParser parser) + { + var proxyUrlReplaceOldValue = parser.GetStringValue("ProxyUrlReplaceOldValue"); + var proxyUrlReplaceNewValue = parser.GetStringValue("ProxyUrlReplaceNewValue"); + if (!string.IsNullOrEmpty(proxyUrlReplaceOldValue) && proxyUrlReplaceNewValue != null) + { + settings.ReplaceSettings = new ProxyUrlReplaceSettings + { + OldValue = proxyUrlReplaceOldValue!, + NewValue = proxyUrlReplaceNewValue + }; + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Handlebars/FileHelpers.cs b/src/WireMock.Net.Minimal/Transformers/Handlebars/FileHelpers.cs similarity index 96% rename from src/WireMock.Net/Transformers/Handlebars/FileHelpers.cs rename to src/WireMock.Net.Minimal/Transformers/Handlebars/FileHelpers.cs index 92a41383..eabde765 100644 --- a/src/WireMock.Net/Transformers/Handlebars/FileHelpers.cs +++ b/src/WireMock.Net.Minimal/Transformers/Handlebars/FileHelpers.cs @@ -1,34 +1,34 @@ -// Copyright © WireMock.Net - -using HandlebarsDotNet; -using HandlebarsDotNet.Helpers.Attributes; -using HandlebarsDotNet.Helpers.Enums; -using HandlebarsDotNet.Helpers.Helpers; -using HandlebarsDotNet.Helpers.Options; -using Stef.Validation; -using WireMock.Handlers; -using WireMock.Settings; - -namespace WireMock.Transformers.Handlebars; - -internal class FileHelpers : BaseHelpers, IHelpers -{ - internal const string Name = "File"; - - private readonly IFileSystemHandler _fileSystemHandler; - - public FileHelpers(IHandlebars context, WireMockServerSettings settings) : base(context, new HandlebarsHelpersOptions()) - { - _fileSystemHandler = Guard.NotNull(settings.FileSystemHandler); - } - - [HandlebarsWriter(WriterType.String, usage: HelperUsage.Both, passContext: true, name: Name)] - public string Read(Context context, string path) - { - var templateFunc = Context.Compile(path); - var transformedPath = templateFunc(context.Value); - return _fileSystemHandler.ReadResponseBodyAsString(transformedPath); - } - - public Category Category => Category.Custom; +// Copyright © WireMock.Net + +using HandlebarsDotNet; +using HandlebarsDotNet.Helpers.Attributes; +using HandlebarsDotNet.Helpers.Enums; +using HandlebarsDotNet.Helpers.Helpers; +using HandlebarsDotNet.Helpers.Options; +using Stef.Validation; +using WireMock.Handlers; +using WireMock.Settings; + +namespace WireMock.Transformers.Handlebars; + +internal class FileHelpers : BaseHelpers, IHelpers +{ + internal const string Name = "File"; + + private readonly IFileSystemHandler _fileSystemHandler; + + public FileHelpers(IHandlebars context, WireMockServerSettings settings) : base(context, new HandlebarsHelpersOptions()) + { + _fileSystemHandler = Guard.NotNull(settings.FileSystemHandler); + } + + [HandlebarsWriter(WriterType.String, usage: HelperUsage.Both, passContext: true, name: Name)] + public string Read(Context context, string path) + { + var templateFunc = Context.Compile(path); + var transformedPath = templateFunc(context.Value); + return _fileSystemHandler.ReadResponseBodyAsString(transformedPath); + } + + public Category Category => Category.Custom; } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContext.cs b/src/WireMock.Net.Minimal/Transformers/Handlebars/HandlebarsContext.cs similarity index 96% rename from src/WireMock.Net/Transformers/Handlebars/HandlebarsContext.cs rename to src/WireMock.Net.Minimal/Transformers/Handlebars/HandlebarsContext.cs index 5907e188..57632ba3 100644 --- a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContext.cs +++ b/src/WireMock.Net.Minimal/Transformers/Handlebars/HandlebarsContext.cs @@ -1,39 +1,39 @@ -// Copyright © WireMock.Net - -using HandlebarsDotNet; -using HandlebarsDotNet.Helpers.Extensions; -using Stef.Validation; -using WireMock.Handlers; - -namespace WireMock.Transformers.Handlebars; - -internal class HandlebarsContext : IHandlebarsContext -{ - public IHandlebars Handlebars { get; } - - public IFileSystemHandler FileSystemHandler { get; } - - public HandlebarsContext(IHandlebars handlebars, IFileSystemHandler fileSystemHandler) - { - Handlebars = Guard.NotNull(handlebars); - FileSystemHandler = Guard.NotNull(fileSystemHandler); - } - - public string ParseAndRender(string text, object model) - { - var template = Handlebars.Compile(text); - return template(model); - } - - public object? ParseAndEvaluate(string text, object model) - { - if (text.StartsWith("{{") && text.EndsWith("}}") && - Handlebars.TryEvaluate(text, model, out var result) && - result is not UndefinedBindingResult) - { - return result; - } - - return ParseAndRender(text, model); - } +// Copyright © WireMock.Net + +using HandlebarsDotNet; +using HandlebarsDotNet.Helpers.Extensions; +using Stef.Validation; +using WireMock.Handlers; + +namespace WireMock.Transformers.Handlebars; + +internal class HandlebarsContext : IHandlebarsContext +{ + public IHandlebars Handlebars { get; } + + public IFileSystemHandler FileSystemHandler { get; } + + public HandlebarsContext(IHandlebars handlebars, IFileSystemHandler fileSystemHandler) + { + Handlebars = Guard.NotNull(handlebars); + FileSystemHandler = Guard.NotNull(fileSystemHandler); + } + + public string ParseAndRender(string text, object model) + { + var template = Handlebars.Compile(text); + return template(model); + } + + public object? ParseAndEvaluate(string text, object model) + { + if (text.StartsWith("{{") && text.EndsWith("}}") && + Handlebars.TryEvaluate(text, model, out var result) && + result is not UndefinedBindingResult) + { + return result; + } + + return ParseAndRender(text, model); + } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs b/src/WireMock.Net.Minimal/Transformers/Handlebars/HandlebarsContextFactory.cs similarity index 100% rename from src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs rename to src/WireMock.Net.Minimal/Transformers/Handlebars/HandlebarsContextFactory.cs diff --git a/src/WireMock.Net/Transformers/Handlebars/IHandlebarsContext.cs b/src/WireMock.Net.Minimal/Transformers/Handlebars/IHandlebarsContext.cs similarity index 95% rename from src/WireMock.Net/Transformers/Handlebars/IHandlebarsContext.cs rename to src/WireMock.Net.Minimal/Transformers/Handlebars/IHandlebarsContext.cs index 8e9b2da6..619e11e4 100644 --- a/src/WireMock.Net/Transformers/Handlebars/IHandlebarsContext.cs +++ b/src/WireMock.Net.Minimal/Transformers/Handlebars/IHandlebarsContext.cs @@ -1,10 +1,10 @@ -// Copyright © WireMock.Net - -using HandlebarsDotNet; - -namespace WireMock.Transformers.Handlebars; - -internal interface IHandlebarsContext : ITransformerContext -{ - IHandlebars Handlebars { get; } +// Copyright © WireMock.Net + +using HandlebarsDotNet; + +namespace WireMock.Transformers.Handlebars; + +internal interface IHandlebarsContext : ITransformerContext +{ + IHandlebars Handlebars { get; } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Handlebars/WireMockHandlebarsHelpers.cs b/src/WireMock.Net.Minimal/Transformers/Handlebars/WireMockHandlebarsHelpers.cs similarity index 97% rename from src/WireMock.Net/Transformers/Handlebars/WireMockHandlebarsHelpers.cs rename to src/WireMock.Net.Minimal/Transformers/Handlebars/WireMockHandlebarsHelpers.cs index 70fd7633..c270b15d 100644 --- a/src/WireMock.Net/Transformers/Handlebars/WireMockHandlebarsHelpers.cs +++ b/src/WireMock.Net.Minimal/Transformers/Handlebars/WireMockHandlebarsHelpers.cs @@ -1,58 +1,58 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.IO; -using HandlebarsDotNet; -using HandlebarsDotNet.Helpers; -using HandlebarsDotNet.Helpers.Helpers; -using WireMock.Settings; -using WireMock.Types; - -namespace WireMock.Transformers.Handlebars; - -internal static class WireMockHandlebarsHelpers -{ - internal static void Register(IHandlebars handlebarsContext, WireMockServerSettings settings) - { - // Register https://github.com/Handlebars.Net/Handlebars.Net.Helpers - HandlebarsHelpers.Register(handlebarsContext, o => - { - var paths = new List - { - Directory.GetCurrentDirectory(), - GetBaseDirectory(), - }; - -#if !NETSTANDARD1_3_OR_GREATER - void Add(string? path, ICollection customHelperPaths) - { - if (!string.IsNullOrEmpty(path)) - { - customHelperPaths.Add(path!); - } - } - Add(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()?.Location), paths); - Add(Path.GetDirectoryName(System.Reflection.Assembly.GetCallingAssembly().Location), paths); - Add(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), paths); - Add(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName), paths); -#endif - o.CustomHelperPaths = paths; - - o.CustomHelpers = new Dictionary(); - if (settings.HandlebarsSettings?.AllowedCustomHandlebarsHelpers.HasFlag(CustomHandlebarsHelpers.File) == true) - { - o.CustomHelpers.Add(FileHelpers.Name, new FileHelpers(handlebarsContext, settings)); - } - }); - } - - private static string GetBaseDirectory() - { -#if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER - return AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); -#else - return AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); -#endif - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.IO; +using HandlebarsDotNet; +using HandlebarsDotNet.Helpers; +using HandlebarsDotNet.Helpers.Helpers; +using WireMock.Settings; +using WireMock.Types; + +namespace WireMock.Transformers.Handlebars; + +internal static class WireMockHandlebarsHelpers +{ + internal static void Register(IHandlebars handlebarsContext, WireMockServerSettings settings) + { + // Register https://github.com/Handlebars.Net/Handlebars.Net.Helpers + HandlebarsHelpers.Register(handlebarsContext, o => + { + var paths = new List + { + Directory.GetCurrentDirectory(), + GetBaseDirectory(), + }; + +#if !NETSTANDARD1_3_OR_GREATER + void Add(string? path, ICollection customHelperPaths) + { + if (!string.IsNullOrEmpty(path)) + { + customHelperPaths.Add(path!); + } + } + Add(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()?.Location), paths); + Add(Path.GetDirectoryName(System.Reflection.Assembly.GetCallingAssembly().Location), paths); + Add(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), paths); + Add(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName), paths); +#endif + o.CustomHelperPaths = paths; + + o.CustomHelpers = new Dictionary(); + if (settings.HandlebarsSettings?.AllowedCustomHandlebarsHelpers.HasFlag(CustomHandlebarsHelpers.File) == true) + { + o.CustomHelpers.Add(FileHelpers.Name, new FileHelpers(handlebarsContext, settings)); + } + }); + } + + private static string GetBaseDirectory() + { +#if NETSTANDARD1_3_OR_GREATER || NET6_0_OR_GREATER + return AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); +#else + return AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar); +#endif + } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/ITransformer.cs b/src/WireMock.Net.Minimal/Transformers/ITransformer.cs similarity index 97% rename from src/WireMock.Net/Transformers/ITransformer.cs rename to src/WireMock.Net.Minimal/Transformers/ITransformer.cs index 8b363580..65c0d248 100644 --- a/src/WireMock.Net/Transformers/ITransformer.cs +++ b/src/WireMock.Net.Minimal/Transformers/ITransformer.cs @@ -1,18 +1,18 @@ -// Copyright © WireMock.Net - -using System.Collections.Generic; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Transformers; - -interface ITransformer -{ - ResponseMessage Transform(IMapping mapping, IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options); - - IBodyData? TransformBody(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IBodyData? bodyData, ReplaceNodeOptions options); - - IDictionary> TransformHeaders(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IDictionary>? headers); - - string TransformString(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, string? value); +// Copyright © WireMock.Net + +using System.Collections.Generic; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Transformers; + +interface ITransformer +{ + ResponseMessage Transform(IMapping mapping, IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options); + + IBodyData? TransformBody(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IBodyData? bodyData, ReplaceNodeOptions options); + + IDictionary> TransformHeaders(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IDictionary>? headers); + + string TransformString(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, string? value); } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/ITransformerContext.cs b/src/WireMock.Net.Minimal/Transformers/ITransformerContext.cs similarity index 95% rename from src/WireMock.Net/Transformers/ITransformerContext.cs rename to src/WireMock.Net.Minimal/Transformers/ITransformerContext.cs index 0c460fad..8f7eeef6 100644 --- a/src/WireMock.Net/Transformers/ITransformerContext.cs +++ b/src/WireMock.Net.Minimal/Transformers/ITransformerContext.cs @@ -1,14 +1,14 @@ -// Copyright © WireMock.Net - -using WireMock.Handlers; - -namespace WireMock.Transformers; - -internal interface ITransformerContext -{ - IFileSystemHandler FileSystemHandler { get; } - - string ParseAndRender(string text, object model); - - object? ParseAndEvaluate(string text, object model); +// Copyright © WireMock.Net + +using WireMock.Handlers; + +namespace WireMock.Transformers; + +internal interface ITransformerContext +{ + IFileSystemHandler FileSystemHandler { get; } + + string ParseAndRender(string text, object model); + + object? ParseAndEvaluate(string text, object model); } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/ITransformerContextFactory.cs b/src/WireMock.Net.Minimal/Transformers/ITransformerContextFactory.cs similarity index 100% rename from src/WireMock.Net/Transformers/ITransformerContextFactory.cs rename to src/WireMock.Net.Minimal/Transformers/ITransformerContextFactory.cs diff --git a/src/WireMock.Net/Transformers/Scriban/ScribanContext.cs b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs similarity index 96% rename from src/WireMock.Net/Transformers/Scriban/ScribanContext.cs rename to src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs index 21190b90..c54febbf 100644 --- a/src/WireMock.Net/Transformers/Scriban/ScribanContext.cs +++ b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs @@ -1,34 +1,34 @@ -// Copyright © WireMock.Net - -using Scriban; -using Stef.Validation; -using WireMock.Handlers; -using WireMock.Types; - -namespace WireMock.Transformers.Scriban; - -internal class ScribanContext : ITransformerContext -{ - private readonly TransformerType _transformerType; - - public IFileSystemHandler FileSystemHandler { get; } - - public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType) - { - FileSystemHandler = Guard.NotNull(fileSystemHandler); - _transformerType = transformerType; - } - - public string ParseAndRender(string text, object model) - { - var template = _transformerType == TransformerType.ScribanDotLiquid ? Template.ParseLiquid(text) : Template.Parse(text); - - return template.Render(model, member => member.Name); - } - - public object? ParseAndEvaluate(string text, object model) - { - // In case of Scriban, call ParseAndRender. - return ParseAndRender(text, model); - } +// Copyright © WireMock.Net + +using Scriban; +using Stef.Validation; +using WireMock.Handlers; +using WireMock.Types; + +namespace WireMock.Transformers.Scriban; + +internal class ScribanContext : ITransformerContext +{ + private readonly TransformerType _transformerType; + + public IFileSystemHandler FileSystemHandler { get; } + + public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType) + { + FileSystemHandler = Guard.NotNull(fileSystemHandler); + _transformerType = transformerType; + } + + public string ParseAndRender(string text, object model) + { + var template = _transformerType == TransformerType.ScribanDotLiquid ? Template.ParseLiquid(text) : Template.Parse(text); + + return template.Render(model, member => member.Name); + } + + public object? ParseAndEvaluate(string text, object model) + { + // In case of Scriban, call ParseAndRender. + return ParseAndRender(text, model); + } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Scriban/ScribanContextFactory.cs b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs similarity index 96% rename from src/WireMock.Net/Transformers/Scriban/ScribanContextFactory.cs rename to src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs index d50f9737..91db290c 100644 --- a/src/WireMock.Net/Transformers/Scriban/ScribanContextFactory.cs +++ b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs @@ -1,24 +1,24 @@ -// Copyright © WireMock.Net - -using WireMock.Handlers; -using WireMock.Types; -using Stef.Validation; - -namespace WireMock.Transformers.Scriban; - -internal class ScribanContextFactory : ITransformerContextFactory -{ - private readonly IFileSystemHandler _fileSystemHandler; - private readonly TransformerType _transformerType; - - public ScribanContextFactory(IFileSystemHandler fileSystemHandler, TransformerType transformerType) - { - _fileSystemHandler = Guard.NotNull(fileSystemHandler); - _transformerType = Guard.Condition(transformerType, t => t is TransformerType.Scriban or TransformerType.ScribanDotLiquid); - } - - public ITransformerContext Create() - { - return new ScribanContext(_fileSystemHandler, _transformerType); - } +// Copyright © WireMock.Net + +using WireMock.Handlers; +using WireMock.Types; +using Stef.Validation; + +namespace WireMock.Transformers.Scriban; + +internal class ScribanContextFactory : ITransformerContextFactory +{ + private readonly IFileSystemHandler _fileSystemHandler; + private readonly TransformerType _transformerType; + + public ScribanContextFactory(IFileSystemHandler fileSystemHandler, TransformerType transformerType) + { + _fileSystemHandler = Guard.NotNull(fileSystemHandler); + _transformerType = Guard.Condition(transformerType, t => t is TransformerType.Scriban or TransformerType.ScribanDotLiquid); + } + + public ITransformerContext Create() + { + return new ScribanContext(_fileSystemHandler, _transformerType); + } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Scriban/WireMockListAccessor.cs b/src/WireMock.Net.Minimal/Transformers/Scriban/WireMockListAccessor.cs similarity index 96% rename from src/WireMock.Net/Transformers/Scriban/WireMockListAccessor.cs rename to src/WireMock.Net.Minimal/Transformers/Scriban/WireMockListAccessor.cs index e578ca06..c657fdfa 100644 --- a/src/WireMock.Net/Transformers/Scriban/WireMockListAccessor.cs +++ b/src/WireMock.Net.Minimal/Transformers/Scriban/WireMockListAccessor.cs @@ -1,71 +1,71 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using Scriban; -using Scriban.Parsing; -using Scriban.Runtime; - -namespace WireMock.Transformers.Scriban -{ - internal class WireMockListAccessor : IListAccessor, IObjectAccessor - { - #region IListAccessor - public int GetLength(TemplateContext context, SourceSpan span, object target) - { - throw new NotImplementedException(); - } - - public object GetValue(TemplateContext context, SourceSpan span, object target, int index) - { - return target.ToString(); - } - - public void SetValue(TemplateContext context, SourceSpan span, object target, int index, object value) - { - throw new NotImplementedException(); - } - #endregion - - #region IObjectAccessor - public int GetMemberCount(TemplateContext context, SourceSpan span, object target) - { - throw new NotImplementedException(); - } - - public IEnumerable GetMembers(TemplateContext context, SourceSpan span, object target) - { - throw new NotImplementedException(); - } - - public bool HasMember(TemplateContext context, SourceSpan span, object target, string member) - { - throw new NotImplementedException(); - } - - public bool TryGetValue(TemplateContext context, SourceSpan span, object target, string member, out object value) - { - throw new NotImplementedException(); - } - - public bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object value) - { - throw new NotImplementedException(); - } - - public bool TryGetItem(TemplateContext context, SourceSpan span, object target, object index, out object value) - { - throw new NotImplementedException(); - } - - public bool TrySetItem(TemplateContext context, SourceSpan span, object target, object index, object value) - { - throw new NotImplementedException(); - } - - public bool HasIndexer => throw new NotImplementedException(); - - public Type IndexType => throw new NotImplementedException(); - #endregion - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using Scriban; +using Scriban.Parsing; +using Scriban.Runtime; + +namespace WireMock.Transformers.Scriban +{ + internal class WireMockListAccessor : IListAccessor, IObjectAccessor + { + #region IListAccessor + public int GetLength(TemplateContext context, SourceSpan span, object target) + { + throw new NotImplementedException(); + } + + public object GetValue(TemplateContext context, SourceSpan span, object target, int index) + { + return target.ToString(); + } + + public void SetValue(TemplateContext context, SourceSpan span, object target, int index, object value) + { + throw new NotImplementedException(); + } + #endregion + + #region IObjectAccessor + public int GetMemberCount(TemplateContext context, SourceSpan span, object target) + { + throw new NotImplementedException(); + } + + public IEnumerable GetMembers(TemplateContext context, SourceSpan span, object target) + { + throw new NotImplementedException(); + } + + public bool HasMember(TemplateContext context, SourceSpan span, object target, string member) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(TemplateContext context, SourceSpan span, object target, string member, out object value) + { + throw new NotImplementedException(); + } + + public bool TrySetValue(TemplateContext context, SourceSpan span, object target, string member, object value) + { + throw new NotImplementedException(); + } + + public bool TryGetItem(TemplateContext context, SourceSpan span, object target, object index, out object value) + { + throw new NotImplementedException(); + } + + public bool TrySetItem(TemplateContext context, SourceSpan span, object target, object index, object value) + { + throw new NotImplementedException(); + } + + public bool HasIndexer => throw new NotImplementedException(); + + public Type IndexType => throw new NotImplementedException(); + #endregion + } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/Scriban/WireMockTemplateContext.cs b/src/WireMock.Net.Minimal/Transformers/Scriban/WireMockTemplateContext.cs similarity index 96% rename from src/WireMock.Net/Transformers/Scriban/WireMockTemplateContext.cs rename to src/WireMock.Net.Minimal/Transformers/Scriban/WireMockTemplateContext.cs index ed73b36c..49422d62 100644 --- a/src/WireMock.Net/Transformers/Scriban/WireMockTemplateContext.cs +++ b/src/WireMock.Net.Minimal/Transformers/Scriban/WireMockTemplateContext.cs @@ -1,17 +1,17 @@ -// Copyright © WireMock.Net - -using Scriban; -using Scriban.Runtime; -using WireMock.Types; - -namespace WireMock.Transformers.Scriban; - -internal class WireMockTemplateContext : TemplateContext -{ - protected override IObjectAccessor GetMemberAccessorImpl(object target) - { - return target?.GetType().GetGenericTypeDefinition() == typeof(WireMockList<>) ? - new WireMockListAccessor() : - base.GetMemberAccessorImpl(target); - } +// Copyright © WireMock.Net + +using Scriban; +using Scriban.Runtime; +using WireMock.Types; + +namespace WireMock.Transformers.Scriban; + +internal class WireMockTemplateContext : TemplateContext +{ + protected override IObjectAccessor GetMemberAccessorImpl(object target) + { + return target?.GetType().GetGenericTypeDefinition() == typeof(WireMockList<>) ? + new WireMockListAccessor() : + base.GetMemberAccessorImpl(target); + } } \ No newline at end of file diff --git a/src/WireMock.Net/Transformers/TransformModel.cs b/src/WireMock.Net.Minimal/Transformers/TransformModel.cs similarity index 100% rename from src/WireMock.Net/Transformers/TransformModel.cs rename to src/WireMock.Net.Minimal/Transformers/TransformModel.cs diff --git a/src/WireMock.Net/Transformers/Transformer.cs b/src/WireMock.Net.Minimal/Transformers/Transformer.cs similarity index 97% rename from src/WireMock.Net/Transformers/Transformer.cs rename to src/WireMock.Net.Minimal/Transformers/Transformer.cs index fb993f53..ca70c78e 100644 --- a/src/WireMock.Net/Transformers/Transformer.cs +++ b/src/WireMock.Net.Minimal/Transformers/Transformer.cs @@ -1,370 +1,370 @@ -// Copyright © WireMock.Net - -using System; -using System.Collections.Generic; -using System.Linq; -using HandlebarsDotNet.Helpers.Models; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Stef.Validation; -using WireMock.Settings; -using WireMock.Types; -using WireMock.Util; - -namespace WireMock.Transformers; - -internal class Transformer : ITransformer -{ - private readonly JsonSerializer _jsonSerializer; - private readonly ITransformerContextFactory _factory; - - public Transformer(WireMockServerSettings settings, ITransformerContextFactory factory) - { - _factory = Guard.NotNull(factory); - - _jsonSerializer = new JsonSerializer - { - Culture = Guard.NotNull(settings).Culture - }; - } - - public IBodyData? TransformBody( - IMapping mapping, - IRequestMessage originalRequestMessage, - IResponseMessage originalResponseMessage, - IBodyData? bodyData, - ReplaceNodeOptions options) - { - var (transformerContext, model) = Create(mapping, originalRequestMessage, originalResponseMessage); - - IBodyData? newBodyData = null; - if (bodyData?.DetectedBodyType != null) - { - newBodyData = TransformBodyData(transformerContext, options, model, bodyData, false); - } - - return newBodyData; - } - - public IDictionary> TransformHeaders( - IMapping mapping, - IRequestMessage originalRequestMessage, - IResponseMessage originalResponseMessage, - IDictionary>? headers - ) - { - var (transformerContext, model) = Create(mapping, originalRequestMessage, originalResponseMessage); - - return TransformHeaders(transformerContext, model, headers); - } - - public string TransformString( - IMapping mapping, - IRequestMessage originalRequestMessage, - IResponseMessage originalResponseMessage, - string? value - ) - { - if (value is null) - { - return string.Empty; - } - - var (transformerContext, model) = Create(mapping, originalRequestMessage, originalResponseMessage); - return transformerContext.ParseAndRender(value, model); - } - - public ResponseMessage Transform(IMapping mapping, IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options) - { - var responseMessage = new ResponseMessage(); - - var (transformerContext, model) = Create(mapping, requestMessage, null); - - if (original.BodyData?.DetectedBodyType != null) - { - responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile); - - if (original.BodyData.DetectedBodyType is BodyType.String or BodyType.FormUrlEncoded) - { - responseMessage.BodyOriginal = original.BodyData.BodyAsString; - } - } - - responseMessage.FaultType = original.FaultType; - responseMessage.FaultPercentage = original.FaultPercentage; - - responseMessage.Headers = TransformHeaders(transformerContext, model, original.Headers); - responseMessage.TrailingHeaders = TransformHeaders(transformerContext, model, original.TrailingHeaders); - - responseMessage.StatusCode = original.StatusCode switch - { - int statusCodeAsInteger => statusCodeAsInteger, - string statusCodeAsString => transformerContext.ParseAndRender(statusCodeAsString, model), - _ => responseMessage.StatusCode - }; - - return responseMessage; - } - - private (ITransformerContext TransformerContext, TransformModel Model) Create(IMapping mapping, IRequestMessage request, IResponseMessage? response) - { - return (_factory.Create(), new TransformModel - { - mapping = mapping, - request = request, - response = response, - data = mapping.Data ?? new { } - }); - } - - private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile) - { - switch (original.DetectedBodyType) - { - case BodyType.Json: - case BodyType.ProtoBuf: - return TransformBodyAsJson(transformerContext, options, model, original); - - case BodyType.File: - return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile); - - case BodyType.String or BodyType.FormUrlEncoded: - return TransformBodyAsString(transformerContext, model, original); - - default: - return null; - } - } - - private static IDictionary> TransformHeaders(ITransformerContext transformerContext, TransformModel model, IDictionary>? original) - { - if (original == null) - { - return new Dictionary>(); - } - - var newHeaders = new Dictionary>(); - foreach (var header in original) - { - var headerKey = transformerContext.ParseAndRender(header.Key, model); - var templateHeaderValues = header.Value.Select(text => transformerContext.ParseAndRender(text, model)).ToArray(); - - newHeaders.Add(headerKey, new WireMockList(templateHeaderValues)); - } - - return newHeaders; - } - - private IBodyData TransformBodyAsJson(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original) - { - JToken? jToken = null; - switch (original.BodyAsJson) - { - case JObject bodyAsJObject: - jToken = bodyAsJObject.DeepClone(); - WalkNode(transformerContext, options, jToken, model); - break; - - case JArray bodyAsJArray: - jToken = bodyAsJArray.DeepClone(); - WalkNode(transformerContext, options, jToken, model); - break; - - case Array bodyAsArray: - jToken = JArray.FromObject(bodyAsArray, _jsonSerializer); - WalkNode(transformerContext, options, jToken, model); - break; - - case string bodyAsString: - jToken = ReplaceSingleNode(transformerContext, options, bodyAsString, model); - break; - - case not null: - jToken = JObject.FromObject(original.BodyAsJson, _jsonSerializer); - WalkNode(transformerContext, options, jToken, model); - break; - } - - return new BodyData - { - Encoding = original.Encoding, - DetectedBodyType = original.DetectedBodyType, - DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, - ProtoDefinition = original.ProtoDefinition, - ProtoBufMessageType = original.ProtoBufMessageType, - BodyAsJson = jToken - }; - } - - private JToken ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model) - { - var transformedString = transformerContext.ParseAndRender(stringValue, model); - - if (!string.Equals(stringValue, transformedString)) - { - const string property = "_"; - JObject dummy = JObject.Parse($"{{ \"{property}\": null }}"); - if (dummy[property] == null) - { - // TODO: check if just returning null is fine - return string.Empty; - } - - JToken node = dummy[property]!; - - ReplaceNodeValue(options, node, transformedString); - - return dummy[property]!; - } - - return stringValue; - } - - private void WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JToken node, object model) - { - switch (node.Type) - { - case JTokenType.Object: - // In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions. - foreach (var child in node.Children().ToArray()) - { - WalkNode(transformerContext, options, child.Value, model); - } - break; - - case JTokenType.Array: - // In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions. - foreach (var child in node.Children().ToArray()) - { - WalkNode(transformerContext, options, child, model); - } - break; - - case JTokenType.String: - // In case of string, try to transform the value. - var stringValue = node.Value(); - if (string.IsNullOrEmpty(stringValue)) - { - return; - } - - var transformed = transformerContext.ParseAndEvaluate(stringValue!, model); - if (!Equals(stringValue, transformed)) - { - ReplaceNodeValue(options, node, transformed); - } - break; - } - } - - // ReSharper disable once UnusedParameter.Local - private void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, object? transformedValue) - { - switch (transformedValue) - { - case JValue jValue: - node.Replace(jValue); - return; - - case string transformedString: - var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString); - if (isConvertedFromString) - { - node.Replace(JToken.FromObject(convertedValueFromString, _jsonSerializer)); - } - else - { - node.Replace(ParseAsJObject(transformedString)); - } - break; - - case WireMockList strings: - switch (strings.Count) - { - case 1: - node.Replace(ParseAsJObject(strings[0])); - return; - - case > 1: - node.Replace(JToken.FromObject(strings.ToArray(), _jsonSerializer)); - return; - } - break; - - case { }: - var (isConverted, convertedValue) = TryConvert(options, transformedValue); - if (isConverted) - { - node.Replace(JToken.FromObject(convertedValue, _jsonSerializer)); - } - return; - - default: // It's null, skip it. Maybe remove it ? - return; - } - } - - private static JToken ParseAsJObject(string stringValue) - { - return JsonUtils.TryParseAsJObject(stringValue, out var parsedAsjObject) ? parsedAsjObject : stringValue; - } - - private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value) - { - var valueAsString = value as string; - - if (options == ReplaceNodeOptions.Evaluate) - { - if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded)) - { - return (true, decoded); - } - - return (false, value); - } - - if (valueAsString != null) - { - return WrappedString.TryDecode(valueAsString, out var decoded) ? - (true, decoded) : - StringUtils.TryConvertToKnownType(valueAsString); - } - - return (false, value); - } - - private static IBodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original) - { - return new BodyData - { - Encoding = original.Encoding, - DetectedBodyType = original.DetectedBodyType, - DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, - BodyAsString = transformerContext.ParseAndRender(original.BodyAsString!, model) - }; - } - - private static IBodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile) - { - var transformedBodyAsFilename = transformerContext.ParseAndRender(original.BodyAsFile!, model); - - if (!useTransformerForBodyAsFile) - { - return new BodyData - { - DetectedBodyType = original.DetectedBodyType, - DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, - BodyAsFile = transformedBodyAsFilename - }; - } - - var text = transformerContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename); - return new BodyData - { - DetectedBodyType = BodyType.String, - DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, - BodyAsString = transformerContext.ParseAndRender(text, model), - BodyAsFile = transformedBodyAsFilename - }; - } +// Copyright © WireMock.Net + +using System; +using System.Collections.Generic; +using System.Linq; +using HandlebarsDotNet.Helpers.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Stef.Validation; +using WireMock.Settings; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Transformers; + +internal class Transformer : ITransformer +{ + private readonly JsonSerializer _jsonSerializer; + private readonly ITransformerContextFactory _factory; + + public Transformer(WireMockServerSettings settings, ITransformerContextFactory factory) + { + _factory = Guard.NotNull(factory); + + _jsonSerializer = new JsonSerializer + { + Culture = Guard.NotNull(settings).Culture + }; + } + + public IBodyData? TransformBody( + IMapping mapping, + IRequestMessage originalRequestMessage, + IResponseMessage originalResponseMessage, + IBodyData? bodyData, + ReplaceNodeOptions options) + { + var (transformerContext, model) = Create(mapping, originalRequestMessage, originalResponseMessage); + + IBodyData? newBodyData = null; + if (bodyData?.DetectedBodyType != null) + { + newBodyData = TransformBodyData(transformerContext, options, model, bodyData, false); + } + + return newBodyData; + } + + public IDictionary> TransformHeaders( + IMapping mapping, + IRequestMessage originalRequestMessage, + IResponseMessage originalResponseMessage, + IDictionary>? headers + ) + { + var (transformerContext, model) = Create(mapping, originalRequestMessage, originalResponseMessage); + + return TransformHeaders(transformerContext, model, headers); + } + + public string TransformString( + IMapping mapping, + IRequestMessage originalRequestMessage, + IResponseMessage originalResponseMessage, + string? value + ) + { + if (value is null) + { + return string.Empty; + } + + var (transformerContext, model) = Create(mapping, originalRequestMessage, originalResponseMessage); + return transformerContext.ParseAndRender(value, model); + } + + public ResponseMessage Transform(IMapping mapping, IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options) + { + var responseMessage = new ResponseMessage(); + + var (transformerContext, model) = Create(mapping, requestMessage, null); + + if (original.BodyData?.DetectedBodyType != null) + { + responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile); + + if (original.BodyData.DetectedBodyType is BodyType.String or BodyType.FormUrlEncoded) + { + responseMessage.BodyOriginal = original.BodyData.BodyAsString; + } + } + + responseMessage.FaultType = original.FaultType; + responseMessage.FaultPercentage = original.FaultPercentage; + + responseMessage.Headers = TransformHeaders(transformerContext, model, original.Headers); + responseMessage.TrailingHeaders = TransformHeaders(transformerContext, model, original.TrailingHeaders); + + responseMessage.StatusCode = original.StatusCode switch + { + int statusCodeAsInteger => statusCodeAsInteger, + string statusCodeAsString => transformerContext.ParseAndRender(statusCodeAsString, model), + _ => responseMessage.StatusCode + }; + + return responseMessage; + } + + private (ITransformerContext TransformerContext, TransformModel Model) Create(IMapping mapping, IRequestMessage request, IResponseMessage? response) + { + return (_factory.Create(), new TransformModel + { + mapping = mapping, + request = request, + response = response, + data = mapping.Data ?? new { } + }); + } + + private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile) + { + switch (original.DetectedBodyType) + { + case BodyType.Json: + case BodyType.ProtoBuf: + return TransformBodyAsJson(transformerContext, options, model, original); + + case BodyType.File: + return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile); + + case BodyType.String or BodyType.FormUrlEncoded: + return TransformBodyAsString(transformerContext, model, original); + + default: + return null; + } + } + + private static IDictionary> TransformHeaders(ITransformerContext transformerContext, TransformModel model, IDictionary>? original) + { + if (original == null) + { + return new Dictionary>(); + } + + var newHeaders = new Dictionary>(); + foreach (var header in original) + { + var headerKey = transformerContext.ParseAndRender(header.Key, model); + var templateHeaderValues = header.Value.Select(text => transformerContext.ParseAndRender(text, model)).ToArray(); + + newHeaders.Add(headerKey, new WireMockList(templateHeaderValues)); + } + + return newHeaders; + } + + private IBodyData TransformBodyAsJson(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original) + { + JToken? jToken = null; + switch (original.BodyAsJson) + { + case JObject bodyAsJObject: + jToken = bodyAsJObject.DeepClone(); + WalkNode(transformerContext, options, jToken, model); + break; + + case JArray bodyAsJArray: + jToken = bodyAsJArray.DeepClone(); + WalkNode(transformerContext, options, jToken, model); + break; + + case Array bodyAsArray: + jToken = JArray.FromObject(bodyAsArray, _jsonSerializer); + WalkNode(transformerContext, options, jToken, model); + break; + + case string bodyAsString: + jToken = ReplaceSingleNode(transformerContext, options, bodyAsString, model); + break; + + case not null: + jToken = JObject.FromObject(original.BodyAsJson, _jsonSerializer); + WalkNode(transformerContext, options, jToken, model); + break; + } + + return new BodyData + { + Encoding = original.Encoding, + DetectedBodyType = original.DetectedBodyType, + DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, + ProtoDefinition = original.ProtoDefinition, + ProtoBufMessageType = original.ProtoBufMessageType, + BodyAsJson = jToken + }; + } + + private JToken ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model) + { + var transformedString = transformerContext.ParseAndRender(stringValue, model); + + if (!string.Equals(stringValue, transformedString)) + { + const string property = "_"; + JObject dummy = JObject.Parse($"{{ \"{property}\": null }}"); + if (dummy[property] == null) + { + // TODO: check if just returning null is fine + return string.Empty; + } + + JToken node = dummy[property]!; + + ReplaceNodeValue(options, node, transformedString); + + return dummy[property]!; + } + + return stringValue; + } + + private void WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JToken node, object model) + { + switch (node.Type) + { + case JTokenType.Object: + // In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions. + foreach (var child in node.Children().ToArray()) + { + WalkNode(transformerContext, options, child.Value, model); + } + break; + + case JTokenType.Array: + // In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions. + foreach (var child in node.Children().ToArray()) + { + WalkNode(transformerContext, options, child, model); + } + break; + + case JTokenType.String: + // In case of string, try to transform the value. + var stringValue = node.Value(); + if (string.IsNullOrEmpty(stringValue)) + { + return; + } + + var transformed = transformerContext.ParseAndEvaluate(stringValue!, model); + if (!Equals(stringValue, transformed)) + { + ReplaceNodeValue(options, node, transformed); + } + break; + } + } + + // ReSharper disable once UnusedParameter.Local + private void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, object? transformedValue) + { + switch (transformedValue) + { + case JValue jValue: + node.Replace(jValue); + return; + + case string transformedString: + var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString); + if (isConvertedFromString) + { + node.Replace(JToken.FromObject(convertedValueFromString, _jsonSerializer)); + } + else + { + node.Replace(ParseAsJObject(transformedString)); + } + break; + + case WireMockList strings: + switch (strings.Count) + { + case 1: + node.Replace(ParseAsJObject(strings[0])); + return; + + case > 1: + node.Replace(JToken.FromObject(strings.ToArray(), _jsonSerializer)); + return; + } + break; + + case { }: + var (isConverted, convertedValue) = TryConvert(options, transformedValue); + if (isConverted) + { + node.Replace(JToken.FromObject(convertedValue, _jsonSerializer)); + } + return; + + default: // It's null, skip it. Maybe remove it ? + return; + } + } + + private static JToken ParseAsJObject(string stringValue) + { + return JsonUtils.TryParseAsJObject(stringValue, out var parsedAsjObject) ? parsedAsjObject : stringValue; + } + + private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value) + { + var valueAsString = value as string; + + if (options == ReplaceNodeOptions.Evaluate) + { + if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded)) + { + return (true, decoded); + } + + return (false, value); + } + + if (valueAsString != null) + { + return WrappedString.TryDecode(valueAsString, out var decoded) ? + (true, decoded) : + StringUtils.TryConvertToKnownType(valueAsString); + } + + return (false, value); + } + + private static IBodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original) + { + return new BodyData + { + Encoding = original.Encoding, + DetectedBodyType = original.DetectedBodyType, + DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, + BodyAsString = transformerContext.ParseAndRender(original.BodyAsString!, model) + }; + } + + private static IBodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile) + { + var transformedBodyAsFilename = transformerContext.ParseAndRender(original.BodyAsFile!, model); + + if (!useTransformerForBodyAsFile) + { + return new BodyData + { + DetectedBodyType = original.DetectedBodyType, + DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, + BodyAsFile = transformedBodyAsFilename + }; + } + + var text = transformerContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename); + return new BodyData + { + DetectedBodyType = BodyType.String, + DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType, + BodyAsString = transformerContext.ParseAndRender(text, model), + BodyAsFile = transformedBodyAsFilename + }; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/ConcurrentObservableCollection.cs b/src/WireMock.Net.Minimal/Util/ConcurrentObservableCollection.cs similarity index 100% rename from src/WireMock.Net/Util/ConcurrentObservableCollection.cs rename to src/WireMock.Net.Minimal/Util/ConcurrentObservableCollection.cs diff --git a/src/WireMock.Net/Util/CultureInfoExtensions.cs b/src/WireMock.Net.Minimal/Util/CultureInfoExtensions.cs similarity index 100% rename from src/WireMock.Net/Util/CultureInfoExtensions.cs rename to src/WireMock.Net.Minimal/Util/CultureInfoExtensions.cs diff --git a/src/WireMock.Net/Util/DateTimeUtils.cs b/src/WireMock.Net.Minimal/Util/DateTimeUtils.cs similarity index 100% rename from src/WireMock.Net/Util/DateTimeUtils.cs rename to src/WireMock.Net.Minimal/Util/DateTimeUtils.cs diff --git a/src/WireMock.Net/Util/DictionaryExtensions.cs b/src/WireMock.Net.Minimal/Util/DictionaryExtensions.cs similarity index 100% rename from src/WireMock.Net/Util/DictionaryExtensions.cs rename to src/WireMock.Net.Minimal/Util/DictionaryExtensions.cs diff --git a/src/WireMock.Net/Util/EnhancedFileSystemWatcher.cs b/src/WireMock.Net.Minimal/Util/EnhancedFileSystemWatcher.cs similarity index 100% rename from src/WireMock.Net/Util/EnhancedFileSystemWatcher.cs rename to src/WireMock.Net.Minimal/Util/EnhancedFileSystemWatcher.cs diff --git a/src/WireMock.Net/Util/FileHelper.cs b/src/WireMock.Net.Minimal/Util/FileHelper.cs similarity index 100% rename from src/WireMock.Net/Util/FileHelper.cs rename to src/WireMock.Net.Minimal/Util/FileHelper.cs diff --git a/src/WireMock.Net/Util/FilePathUtils.cs b/src/WireMock.Net.Minimal/Util/FilePathUtils.cs similarity index 100% rename from src/WireMock.Net/Util/FilePathUtils.cs rename to src/WireMock.Net.Minimal/Util/FilePathUtils.cs diff --git a/src/WireMock.Net/Util/GuidUtils.cs b/src/WireMock.Net.Minimal/Util/GuidUtils.cs similarity index 100% rename from src/WireMock.Net/Util/GuidUtils.cs rename to src/WireMock.Net.Minimal/Util/GuidUtils.cs diff --git a/src/WireMock.Net/Util/HttpStatusRangeParser.cs b/src/WireMock.Net.Minimal/Util/HttpStatusRangeParser.cs similarity index 100% rename from src/WireMock.Net/Util/HttpStatusRangeParser.cs rename to src/WireMock.Net.Minimal/Util/HttpStatusRangeParser.cs diff --git a/src/WireMock.Net/Util/HttpVersionParser.cs b/src/WireMock.Net.Minimal/Util/HttpVersionParser.cs similarity index 91% rename from src/WireMock.Net/Util/HttpVersionParser.cs rename to src/WireMock.Net.Minimal/Util/HttpVersionParser.cs index a8e22bdc..e8591a00 100644 --- a/src/WireMock.Net/Util/HttpVersionParser.cs +++ b/src/WireMock.Net.Minimal/Util/HttpVersionParser.cs @@ -11,7 +11,7 @@ namespace WireMock.Util; /// internal static class HttpVersionParser { - private static readonly Regex HttpVersionRegex = new(@"HTTP/(\d+(\.\d+)?(?!\.))", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled, WireMockConstants.DefaultRegexTimeout); + private static readonly Regex HttpVersionRegex = new(@"HTTP/(\d+(\.\d+)?(?!\.))", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled, RegexConstants.DefaultTimeout); /// /// Try to extract the version (as a string) from the protocol. diff --git a/src/WireMock.Net/Util/PortUtils.cs b/src/WireMock.Net.Minimal/Util/PortUtils.cs similarity index 98% rename from src/WireMock.Net/Util/PortUtils.cs rename to src/WireMock.Net.Minimal/Util/PortUtils.cs index 5605dfc8..1cc2aad8 100644 --- a/src/WireMock.Net/Util/PortUtils.cs +++ b/src/WireMock.Net.Minimal/Util/PortUtils.cs @@ -16,7 +16,7 @@ namespace WireMock.Util; /// internal static class PortUtils { - private static readonly Regex UrlDetailsRegex = new(@"^((?\w+)://)(?[^/]+?):(?\d+)\/?$", RegexOptions.Compiled, WireMockConstants.DefaultRegexTimeout); + private static readonly Regex UrlDetailsRegex = new(@"^((?\w+)://)(?[^/]+?):(?\d+)\/?$", RegexOptions.Compiled, RegexConstants.DefaultTimeout); /// /// Finds a random, free port to be listened on. diff --git a/src/WireMock.Net/Util/ProtoBufUtils.cs b/src/WireMock.Net.Minimal/Util/ProtoBufUtils.cs similarity index 100% rename from src/WireMock.Net/Util/ProtoBufUtils.cs rename to src/WireMock.Net.Minimal/Util/ProtoBufUtils.cs diff --git a/src/WireMock.Net/Util/ProtoDefinitionHelper.cs b/src/WireMock.Net.Minimal/Util/ProtoDefinitionHelper.cs similarity index 100% rename from src/WireMock.Net/Util/ProtoDefinitionHelper.cs rename to src/WireMock.Net.Minimal/Util/ProtoDefinitionHelper.cs diff --git a/src/WireMock.Net/Util/ReflectionUtils.cs b/src/WireMock.Net.Minimal/Util/ReflectionUtils.cs similarity index 100% rename from src/WireMock.Net/Util/ReflectionUtils.cs rename to src/WireMock.Net.Minimal/Util/ReflectionUtils.cs diff --git a/src/WireMock.Net/Util/RegexUtils.cs b/src/WireMock.Net.Minimal/Util/RegexUtils.cs similarity index 88% rename from src/WireMock.Net/Util/RegexUtils.cs rename to src/WireMock.Net.Minimal/Util/RegexUtils.cs index 622df7d5..b9799c91 100644 --- a/src/WireMock.Net/Util/RegexUtils.cs +++ b/src/WireMock.Net.Minimal/Util/RegexUtils.cs @@ -36,11 +36,11 @@ internal static class RegexUtils { if (useRegexExtended) { - var regexExtended = new RegexExtended(pattern!, RegexOptions.None, WireMockConstants.DefaultRegexTimeout); + var regexExtended = new RegexExtended(pattern!, RegexOptions.None, RegexConstants.DefaultTimeout); return (true, regexExtended.IsMatch(input)); } - var regex = new Regex(pattern, RegexOptions.None, WireMockConstants.DefaultRegexTimeout); + var regex = new Regex(pattern, RegexOptions.None, RegexConstants.DefaultTimeout); return (true, regex.IsMatch(input)); } catch diff --git a/src/WireMock.Net/Util/SingletonFactory.cs b/src/WireMock.Net.Minimal/Util/SingletonFactory.cs similarity index 100% rename from src/WireMock.Net/Util/SingletonFactory.cs rename to src/WireMock.Net.Minimal/Util/SingletonFactory.cs diff --git a/src/WireMock.Net/Util/StreamUtils.cs b/src/WireMock.Net.Minimal/Util/StreamUtils.cs similarity index 100% rename from src/WireMock.Net/Util/StreamUtils.cs rename to src/WireMock.Net.Minimal/Util/StreamUtils.cs diff --git a/src/WireMock.Net/Util/StringUtils.cs b/src/WireMock.Net.Minimal/Util/StringUtils.cs similarity index 100% rename from src/WireMock.Net/Util/StringUtils.cs rename to src/WireMock.Net.Minimal/Util/StringUtils.cs diff --git a/src/WireMock.Net/Util/SystemUtils.cs b/src/WireMock.Net.Minimal/Util/SystemUtils.cs similarity index 100% rename from src/WireMock.Net/Util/SystemUtils.cs rename to src/WireMock.Net.Minimal/Util/SystemUtils.cs diff --git a/src/WireMock.Net/Util/TinyMapperUtils.cs b/src/WireMock.Net.Minimal/Util/TinyMapperUtils.cs similarity index 97% rename from src/WireMock.Net/Util/TinyMapperUtils.cs rename to src/WireMock.Net.Minimal/Util/TinyMapperUtils.cs index 9077c457..5fcee4cd 100644 --- a/src/WireMock.Net/Util/TinyMapperUtils.cs +++ b/src/WireMock.Net.Minimal/Util/TinyMapperUtils.cs @@ -1,56 +1,56 @@ -// Copyright © WireMock.Net - -using Nelibur.ObjectMapper; -using WireMock.Admin.Mappings; -using WireMock.Admin.Settings; -using WireMock.Settings; - -namespace WireMock.Util; - -internal sealed class TinyMapperUtils -{ - public static TinyMapperUtils Instance { get; } = new(); - - private TinyMapperUtils() - { - TinyMapper.Bind(); - TinyMapper.Bind(); - TinyMapper.Bind(); - TinyMapper.Bind(); - - TinyMapper.Bind(); - TinyMapper.Bind(); - TinyMapper.Bind(); - TinyMapper.Bind(); - } - - public ProxyAndRecordSettingsModel? Map(ProxyAndRecordSettings? instance) - { - return instance == null ? null : TinyMapper.Map(instance); - } - - public ProxyAndRecordSettings? Map(ProxyAndRecordSettingsModel? model) - { - return model == null ? null : TinyMapper.Map(model); - } - - public ProxyUrlReplaceSettingsModel? Map(ProxyUrlReplaceSettings? instance) - { - return instance == null ? null : TinyMapper.Map(instance); - } - - public ProxyUrlReplaceSettings? Map(ProxyUrlReplaceSettingsModel? model) - { - return model == null ? null : TinyMapper.Map(model); - } - - public WebProxyModel? Map(WebProxySettings? instance) - { - return instance == null ? null : TinyMapper.Map(instance); - } - - public WebProxySettings? Map(WebProxyModel? model) - { - return model == null ? null : TinyMapper.Map(model); - } +// Copyright © WireMock.Net + +using Nelibur.ObjectMapper; +using WireMock.Admin.Mappings; +using WireMock.Admin.Settings; +using WireMock.Settings; + +namespace WireMock.Util; + +internal sealed class TinyMapperUtils +{ + public static TinyMapperUtils Instance { get; } = new(); + + private TinyMapperUtils() + { + TinyMapper.Bind(); + TinyMapper.Bind(); + TinyMapper.Bind(); + TinyMapper.Bind(); + + TinyMapper.Bind(); + TinyMapper.Bind(); + TinyMapper.Bind(); + TinyMapper.Bind(); + } + + public ProxyAndRecordSettingsModel? Map(ProxyAndRecordSettings? instance) + { + return instance == null ? null : TinyMapper.Map(instance); + } + + public ProxyAndRecordSettings? Map(ProxyAndRecordSettingsModel? model) + { + return model == null ? null : TinyMapper.Map(model); + } + + public ProxyUrlReplaceSettingsModel? Map(ProxyUrlReplaceSettings? instance) + { + return instance == null ? null : TinyMapper.Map(instance); + } + + public ProxyUrlReplaceSettings? Map(ProxyUrlReplaceSettingsModel? model) + { + return model == null ? null : TinyMapper.Map(model); + } + + public WebProxyModel? Map(WebProxySettings? instance) + { + return instance == null ? null : TinyMapper.Map(instance); + } + + public WebProxySettings? Map(WebProxyModel? model) + { + return model == null ? null : TinyMapper.Map(model); + } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/TypeLoader.cs b/src/WireMock.Net.Minimal/Util/TypeLoader.cs similarity index 65% rename from src/WireMock.Net/Util/TypeLoader.cs rename to src/WireMock.Net.Minimal/Util/TypeLoader.cs index 2be7e020..35ac0272 100644 --- a/src/WireMock.Net/Util/TypeLoader.cs +++ b/src/WireMock.Net.Minimal/Util/TypeLoader.cs @@ -13,8 +13,41 @@ namespace WireMock.Util; internal static class TypeLoader { private static readonly ConcurrentDictionary Assemblies = new(); + private static readonly ConcurrentDictionary Instances = new(); - public static TInterface Load(params object[] args) where TInterface : class + public static TInterface LoadNewInstance(params object?[] args) where TInterface : class + { + var pluginType = GetPluginType(); + + return (TInterface)Activator.CreateInstance(pluginType, args)!; + } + + public static TInterface LoadStaticInstance(params object?[] args) where TInterface : class + { + var pluginType = GetPluginType(); + + return (TInterface)Instances.GetOrAdd(pluginType, key => Activator.CreateInstance(key, args)!); + } + + public static TInterface LoadNewInstanceByFullName(string implementationTypeFullName, params object?[] args) where TInterface : class + { + Guard.NotNullOrEmpty(implementationTypeFullName); + + var pluginType = GetPluginTypeByFullName(implementationTypeFullName); + + return (TInterface)Activator.CreateInstance(pluginType, args)!; + } + + public static TInterface LoadStaticInstanceByFullName(string implementationTypeFullName, params object?[] args) where TInterface : class + { + Guard.NotNullOrEmpty(implementationTypeFullName); + + var pluginType = GetPluginTypeByFullName(implementationTypeFullName); + + return (TInterface)Instances.GetOrAdd(pluginType, key => Activator.CreateInstance(key, args)!); + } + + private static Type GetPluginType() where TInterface : class { var key = typeof(TInterface).FullName!; @@ -27,14 +60,11 @@ internal static class TypeLoader throw new DllNotFoundException($"No dll found which implements interface '{key}'."); }); - - return (TInterface)Activator.CreateInstance(pluginType, args)!; + return pluginType; } - public static TInterface LoadByFullName(string implementationTypeFullName, params object[] args) where TInterface : class + private static Type GetPluginTypeByFullName(string implementationTypeFullName) where TInterface : class { - Guard.NotNullOrEmpty(implementationTypeFullName); - var @interface = typeof(TInterface).FullName; var key = $"{@interface}_{implementationTypeFullName}"; @@ -47,8 +77,7 @@ internal static class TypeLoader throw new DllNotFoundException($"No dll found which implements Interface '{@interface}' and has FullName '{implementationTypeFullName}'."); }); - - return (TInterface)Activator.CreateInstance(pluginType, args)!; + return pluginType; } private static bool TryFindTypeInDlls(string? implementationTypeFullName, [NotNullWhen(true)] out Type? pluginType) where TInterface : class diff --git a/src/WireMock.Net/Util/UrlUtils.cs b/src/WireMock.Net.Minimal/Util/UrlUtils.cs similarity index 100% rename from src/WireMock.Net/Util/UrlUtils.cs rename to src/WireMock.Net.Minimal/Util/UrlUtils.cs diff --git a/src/WireMock.Net/Util/WireMockProtoFileResolver.cs b/src/WireMock.Net.Minimal/Util/WireMockProtoFileResolver.cs similarity index 100% rename from src/WireMock.Net/Util/WireMockProtoFileResolver.cs rename to src/WireMock.Net.Minimal/Util/WireMockProtoFileResolver.cs diff --git a/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj new file mode 100644 index 00000000..6c256fdc --- /dev/null +++ b/src/WireMock.Net.Minimal/WireMock.Net.Minimal.csproj @@ -0,0 +1,186 @@ + + + Minimal version from the lightweight Http Mocking Server for .NET + WireMock.Net.Minimal + Stef Heyenrath + net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + true + WireMock.Net.Minimal + WireMock.Net.Minimal + tdd;mock;http;wiremock;test;server;unittest + WireMock + {5501E6AC-6854-4ABD-8EC3-9AD0B62A08A9} + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + ../WireMock.Net/WireMock.Net.snk + + true + + + + + true + + + + ../WireMock.Net/WireMock.Net.ruleset + + + + $(DefineConstants);NETSTANDARD;USE_ASPNETCORE + + + + $(DefineConstants);USE_ASPNETCORE + + + + $(DefineConstants);USE_ASPNETCORE;NET46 + + + + $(DefineConstants);OPENAPIPARSER + + + + $(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF + + + + $(DefineConstants);TRAILINGHEADERS + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WireMockServer.cs + + + + + + Request.cs + + + + + + Response.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj index e73861d9..d8eca00e 100644 --- a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj +++ b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj @@ -7,20 +7,23 @@ wiremock;openapi;OAS;raml;converter;parser;openapiparser {E5B03EEF-822C-4295-952B-4479AD30082B} true - ../WireMock.Net/WireMock.Net.ruleset MIT + + ../WireMock.Net/WireMock.Net.ruleset + + true - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj b/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj index f3802517..3baecc43 100644 --- a/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj +++ b/src/WireMock.Net.RestClient/WireMock.Net.RestClient.csproj @@ -1,42 +1,45 @@ - - A RestClient using RestEase to access the admin interface. - WireMock.Net.RestClient - Stef Heyenrath - net45;netstandard1.3;netstandard2.0;netstandard2.1 - true - WireMock.Net.RestClient - WireMock.Net.RestClient - wiremock;rest;client;restclient;restease;rest;json - WireMock.Client - {B6269AAC-170A-43D6-8B9A-579DED3D9A96} - true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - true - true - true - ../WireMock.Net/WireMock.Net.ruleset - true - ../WireMock.Net/WireMock.Net.snk - - true - MIT - + + A RestClient using RestEase to access the admin interface. + WireMock.Net.RestClient + Stef Heyenrath + net45;netstandard1.3;netstandard2.0;netstandard2.1 + true + WireMock.Net.RestClient + WireMock.Net.RestClient + wiremock;rest;client;restclient;restease;rest;json + WireMock.Client + {B6269AAC-170A-43D6-8B9A-579DED3D9A96} + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + true + true + ../WireMock.Net/WireMock.Net.snk + + true + MIT + - - - true - + + ../WireMock.Net/WireMock.Net.ruleset + - - - - - + + + true + - - - + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Constants/RegexConstants.cs b/src/WireMock.Net.Shared/Constants/RegexConstants.cs new file mode 100644 index 00000000..64052bd8 --- /dev/null +++ b/src/WireMock.Net.Shared/Constants/RegexConstants.cs @@ -0,0 +1,16 @@ +// Copyright © WireMock.Net + +using System; + +namespace WireMock.Constants; + +/// +/// Some constants for Regex. +/// +internal static class RegexConstants +{ + /// + /// The default timeout for regex operations. + /// + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10); +} \ No newline at end of file diff --git a/src/WireMock.Net/Exceptions/WireMockException.cs b/src/WireMock.Net.Shared/Exceptions/WireMockException.cs similarity index 100% rename from src/WireMock.Net/Exceptions/WireMockException.cs rename to src/WireMock.Net.Shared/Exceptions/WireMockException.cs diff --git a/src/WireMock.Net/Extensions/AnyOfExtensions.cs b/src/WireMock.Net.Shared/Extensions/AnyOfExtensions.cs similarity index 100% rename from src/WireMock.Net/Extensions/AnyOfExtensions.cs rename to src/WireMock.Net.Shared/Extensions/AnyOfExtensions.cs diff --git a/src/WireMock.Net/Extensions/EnumExtensions.cs b/src/WireMock.Net.Shared/Extensions/EnumExtensions.cs similarity index 60% rename from src/WireMock.Net/Extensions/EnumExtensions.cs rename to src/WireMock.Net.Shared/Extensions/EnumExtensions.cs index e2ea842d..cd0775d7 100644 --- a/src/WireMock.Net/Extensions/EnumExtensions.cs +++ b/src/WireMock.Net.Shared/Extensions/EnumExtensions.cs @@ -5,8 +5,17 @@ using System.Reflection; namespace WireMock.Extensions; +/// +/// Some extension methods for Enums. +/// internal static class EnumExtensions { + /// + /// Get the fully qualified enum value. + /// + /// The type. + /// The value. + /// The fully qualified enum value. public static string GetFullyQualifiedEnumValue(this T enumValue) where T : struct, IConvertible { diff --git a/src/WireMock.Net/Extensions/ExceptionExtensions.cs b/src/WireMock.Net.Shared/Extensions/ExceptionExtensions.cs similarity index 100% rename from src/WireMock.Net/Extensions/ExceptionExtensions.cs rename to src/WireMock.Net.Shared/Extensions/ExceptionExtensions.cs diff --git a/src/WireMock.Net/Http/HttpKnownHeaderNames.cs b/src/WireMock.Net.Shared/Http/HttpKnownHeaderNames.cs similarity index 99% rename from src/WireMock.Net/Http/HttpKnownHeaderNames.cs rename to src/WireMock.Net.Shared/Http/HttpKnownHeaderNames.cs index fdb13ecf..a2a66abb 100644 --- a/src/WireMock.Net/Http/HttpKnownHeaderNames.cs +++ b/src/WireMock.Net.Shared/Http/HttpKnownHeaderNames.cs @@ -17,7 +17,7 @@ internal static class HttpKnownHeaderNames // - https://docs.microsoft.com/en-us/dotnet/api/system.net.webheadercollection.isrestricted // - ContentLength is allowed per #720 private static readonly string[] RestrictedResponseHeaders = - { + [ Accept, Connection, ContentType, @@ -30,7 +30,7 @@ internal static class HttpKnownHeaderNames TransferEncoding, UserAgent, ProxyConnection - }; + ]; /// Tests whether the specified HTTP header can be set for the response. /// The header to test. diff --git a/src/WireMock.Net/Matchers/ExactObjectMatcher.cs b/src/WireMock.Net.Shared/Matchers/ExactObjectMatcher.cs similarity index 96% rename from src/WireMock.Net/Matchers/ExactObjectMatcher.cs rename to src/WireMock.Net.Shared/Matchers/ExactObjectMatcher.cs index f23ed191..f37590b1 100644 --- a/src/WireMock.Net/Matchers/ExactObjectMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/ExactObjectMatcher.cs @@ -1,82 +1,82 @@ -// Copyright © WireMock.Net - -using System.Linq; -using Stef.Validation; - -namespace WireMock.Matchers; - -/// -/// ExactObjectMatcher -/// -/// -public class ExactObjectMatcher : IObjectMatcher -{ - /// - public object Value { get; } - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The value. - public ExactObjectMatcher(object value) : this(MatchBehaviour.AcceptOnMatch, value) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The value. - public ExactObjectMatcher(MatchBehaviour matchBehaviour, object value) - { - Value = Guard.NotNull(value); - MatchBehaviour = matchBehaviour; - } - - /// - /// Initializes a new instance of the class. - /// - /// The value. - public ExactObjectMatcher(byte[] value) : this(MatchBehaviour.AcceptOnMatch, value) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The value. - public ExactObjectMatcher(MatchBehaviour matchBehaviour, byte[] value) - { - Value = Guard.NotNull(value); - MatchBehaviour = matchBehaviour; - } - - /// - public MatchResult IsMatch(object? input) - { - bool equals; - if (Value is byte[] valueAsBytes && input is byte[] inputAsBytes) - { - equals = valueAsBytes.SequenceEqual(inputAsBytes); - } - else - { - equals = Equals(Value, input); - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)); - } - - /// - public string Name => nameof(ExactObjectMatcher); - - /// - public string GetCSharpCodeArguments() - { - return "NotImplemented"; - } +// Copyright © WireMock.Net + +using System.Linq; +using Stef.Validation; + +namespace WireMock.Matchers; + +/// +/// ExactObjectMatcher +/// +/// +public class ExactObjectMatcher : IObjectMatcher +{ + /// + public object Value { get; } + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public ExactObjectMatcher(object value) : this(MatchBehaviour.AcceptOnMatch, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The value. + public ExactObjectMatcher(MatchBehaviour matchBehaviour, object value) + { + Value = Guard.NotNull(value); + MatchBehaviour = matchBehaviour; + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public ExactObjectMatcher(byte[] value) : this(MatchBehaviour.AcceptOnMatch, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The value. + public ExactObjectMatcher(MatchBehaviour matchBehaviour, byte[] value) + { + Value = Guard.NotNull(value); + MatchBehaviour = matchBehaviour; + } + + /// + public MatchResult IsMatch(object? input) + { + bool equals; + if (Value is byte[] valueAsBytes && input is byte[] inputAsBytes) + { + equals = valueAsBytes.SequenceEqual(inputAsBytes); + } + else + { + equals = Equals(Value, input); + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals)); + } + + /// + public string Name => nameof(ExactObjectMatcher); + + /// + public string GetCSharpCodeArguments() + { + return "NotImplemented"; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs b/src/WireMock.Net.Shared/Matchers/Helpers/BodyDataMatchScoreCalculator.cs similarity index 96% rename from src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs rename to src/WireMock.Net.Shared/Matchers/Helpers/BodyDataMatchScoreCalculator.cs index cb3bbb61..5b662f9e 100644 --- a/src/WireMock.Net/Matchers/Helpers/BodyDataMatchScoreCalculator.cs +++ b/src/WireMock.Net.Shared/Matchers/Helpers/BodyDataMatchScoreCalculator.cs @@ -8,7 +8,7 @@ namespace WireMock.Matchers.Helpers; internal static class BodyDataMatchScoreCalculator { - public static MatchResult CalculateMatchScore(IBodyData? requestMessage, IMatcher matcher) + internal static MatchResult CalculateMatchScore(IBodyData? requestMessage, IMatcher matcher) { Guard.NotNull(matcher); diff --git a/src/WireMock.Net/Matchers/IBytesMatcher.cs b/src/WireMock.Net.Shared/Matchers/IBytesMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/IBytesMatcher.cs rename to src/WireMock.Net.Shared/Matchers/IBytesMatcher.cs diff --git a/src/WireMock.Net/Matchers/ICSharpCodeMatcher.cs b/src/WireMock.Net.Shared/Matchers/ICSharpCodeMatcher.cs similarity index 95% rename from src/WireMock.Net/Matchers/ICSharpCodeMatcher.cs rename to src/WireMock.Net.Shared/Matchers/ICSharpCodeMatcher.cs index 42abf986..32e35821 100644 --- a/src/WireMock.Net/Matchers/ICSharpCodeMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/ICSharpCodeMatcher.cs @@ -1,12 +1,12 @@ -// Copyright © WireMock.Net - -namespace WireMock.Matchers; - -/// -/// CSharpCode / CS-Script Matcher -/// -/// -/// -public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher -{ +// Copyright © WireMock.Net + +namespace WireMock.Matchers; + +/// +/// CSharpCode / CS-Script Matcher +/// +/// +/// +public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher +{ } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/IDecodeMatcher.cs b/src/WireMock.Net.Shared/Matchers/IDecodeBytesMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/IDecodeMatcher.cs rename to src/WireMock.Net.Shared/Matchers/IDecodeBytesMatcher.cs diff --git a/src/WireMock.Net/Matchers/IIgnoreCaseMatcher.cs b/src/WireMock.Net.Shared/Matchers/IIgnoreCaseMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/IIgnoreCaseMatcher.cs rename to src/WireMock.Net.Shared/Matchers/IIgnoreCaseMatcher.cs diff --git a/src/WireMock.Net/Matchers/IMatcher.cs b/src/WireMock.Net.Shared/Matchers/IMatcher.cs similarity index 94% rename from src/WireMock.Net/Matchers/IMatcher.cs rename to src/WireMock.Net.Shared/Matchers/IMatcher.cs index e6b6c183..e3ad9119 100644 --- a/src/WireMock.Net/Matchers/IMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/IMatcher.cs @@ -1,25 +1,25 @@ -// Copyright © WireMock.Net - -namespace WireMock.Matchers; - -/// -/// IMatcher -/// -public interface IMatcher -{ - /// - /// Gets the name. - /// - string Name { get; } - - /// - /// Gets the match behaviour. - /// - MatchBehaviour MatchBehaviour { get; } - - /// - /// Get the C# code arguments. - /// - /// - string GetCSharpCodeArguments(); +// Copyright © WireMock.Net + +namespace WireMock.Matchers; + +/// +/// IMatcher +/// +public interface IMatcher +{ + /// + /// Gets the name. + /// + string Name { get; } + + /// + /// Gets the match behaviour. + /// + MatchBehaviour MatchBehaviour { get; } + + /// + /// Get the C# code arguments. + /// + /// + string GetCSharpCodeArguments(); } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Matchers/IMimePartMatcher.cs b/src/WireMock.Net.Shared/Matchers/IMimePartMatcher.cs new file mode 100644 index 00000000..37fa2168 --- /dev/null +++ b/src/WireMock.Net.Shared/Matchers/IMimePartMatcher.cs @@ -0,0 +1,37 @@ +// Copyright © WireMock.Net + +namespace WireMock.Matchers; + +/// +/// MimePartMatcher +/// +/// +public interface IMimePartMatcher : IMatcher +{ + /// + /// ContentType Matcher (image/png; name=image.png.) + /// + IStringMatcher? ContentTypeMatcher { get; } + + /// + /// ContentDisposition Matcher (attachment; filename=image.png) + /// + IStringMatcher? ContentDispositionMatcher { get; } + + /// + /// ContentTransferEncoding Matcher (base64) + /// + IStringMatcher? ContentTransferEncodingMatcher { get; } + + /// + /// Content Matcher + /// + IMatcher? ContentMatcher { get; } + + /// + /// Determines whether the specified MimePart is match. + /// + /// The MimePart. + /// A value between 0.0 - 1.0 of the similarity. + public MatchResult IsMatch(object value); +} \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/IObjectMatcher.cs b/src/WireMock.Net.Shared/Matchers/IObjectMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/IObjectMatcher.cs rename to src/WireMock.Net.Shared/Matchers/IObjectMatcher.cs diff --git a/src/WireMock.Net/Matchers/IProtoBufMatcher.cs b/src/WireMock.Net.Shared/Matchers/IProtoBufMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/IProtoBufMatcher.cs rename to src/WireMock.Net.Shared/Matchers/IProtoBufMatcher.cs diff --git a/src/WireMock.Net/Matchers/IStringMatcher.cs b/src/WireMock.Net.Shared/Matchers/IStringMatcher.cs similarity index 100% rename from src/WireMock.Net/Matchers/IStringMatcher.cs rename to src/WireMock.Net.Shared/Matchers/IStringMatcher.cs diff --git a/src/WireMock.Net/Matchers/MatchBehaviour.cs b/src/WireMock.Net.Shared/Matchers/MatchBehaviour.cs similarity index 100% rename from src/WireMock.Net/Matchers/MatchBehaviour.cs rename to src/WireMock.Net.Shared/Matchers/MatchBehaviour.cs diff --git a/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs b/src/WireMock.Net.Shared/Matchers/MatchBehaviourHelper.cs similarity index 67% rename from src/WireMock.Net/Matchers/MatchBehaviourHelper.cs rename to src/WireMock.Net.Shared/Matchers/MatchBehaviourHelper.cs index bd7975d3..a9450282 100644 --- a/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs +++ b/src/WireMock.Net.Shared/Matchers/MatchBehaviourHelper.cs @@ -2,6 +2,9 @@ namespace WireMock.Matchers; +/// +/// MatchBehaviourHelper +/// internal static class MatchBehaviourHelper { /// @@ -12,7 +15,6 @@ internal static class MatchBehaviourHelper /// if RejectOnMatch and match = 0.? --> return 0.0 /// if RejectOnMatch and match = 1.0 --> return 0.0 /// - /// /// The match behaviour. /// The match. /// match value @@ -26,10 +28,14 @@ internal static class MatchBehaviourHelper return match <= MatchScores.Tolerance ? MatchScores.Perfect : MatchScores.Mismatch; } + /// + /// Converts the specified match behaviour and match result to a new match result value. + /// + /// The match behaviour. + /// The match result. + /// match result internal static MatchResult Convert(MatchBehaviour matchBehaviour, MatchResult result) { - return matchBehaviour == MatchBehaviour.AcceptOnMatch ? - result : - new MatchResult(Convert(matchBehaviour, result.Score), result.Exception); + return matchBehaviour == MatchBehaviour.AcceptOnMatch ? result : new MatchResult(Convert(matchBehaviour, result.Score), result.Exception); } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/MatchOperator.cs b/src/WireMock.Net.Shared/Matchers/MatchOperator.cs similarity index 100% rename from src/WireMock.Net/Matchers/MatchOperator.cs rename to src/WireMock.Net.Shared/Matchers/MatchOperator.cs diff --git a/src/WireMock.Net/Matchers/MatchResult.cs b/src/WireMock.Net.Shared/Matchers/MatchResult.cs similarity index 100% rename from src/WireMock.Net/Matchers/MatchResult.cs rename to src/WireMock.Net.Shared/Matchers/MatchResult.cs diff --git a/src/WireMock.Net/Matchers/MatchScores.cs b/src/WireMock.Net.Shared/Matchers/MatchScores.cs similarity index 100% rename from src/WireMock.Net/Matchers/MatchScores.cs rename to src/WireMock.Net.Shared/Matchers/MatchScores.cs diff --git a/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs b/src/WireMock.Net.Shared/Matchers/NotNullOrEmptyMatcher.cs similarity index 92% rename from src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs rename to src/WireMock.Net.Shared/Matchers/NotNullOrEmptyMatcher.cs index e159b65d..51f89adf 100644 --- a/src/WireMock.Net/Matchers/NotNullOrEmptyMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/NotNullOrEmptyMatcher.cs @@ -1,85 +1,84 @@ -// Copyright © WireMock.Net - -using System; -using System.Linq; -using AnyOfTypes; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// NotNullOrEmptyMatcher -/// -/// -public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher -{ - /// - public string Name => nameof(NotNullOrEmptyMatcher); - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public object Value { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - public NotNullOrEmptyMatcher(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) - { - MatchBehaviour = matchBehaviour; - Value = string.Empty; - } - - /// - public MatchResult IsMatch(object? input) - { - bool match; - - switch (input) - { - case string @string: - match = !string.IsNullOrEmpty(@string); - break; - - case byte[] bytes: - match = bytes.Any(); - break; - - default: - match = input != null; - break; - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); - } - - /// - public MatchResult IsMatch(string? input) - { - var match = !string.IsNullOrEmpty(input); - - return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); - } - - /// - public AnyOf[] GetPatterns() - { - return EmptyArray>.Value; - } - - /// - public MatchOperator MatchOperator => MatchOperator.Or; - - /// - public string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}" + - $")"; - } +// Copyright © WireMock.Net + +using System; +using System.Linq; +using AnyOfTypes; +using WireMock.Extensions; +using WireMock.Models; + +namespace WireMock.Matchers; + +/// +/// NotNullOrEmptyMatcher +/// +/// +public class NotNullOrEmptyMatcher : IObjectMatcher, IStringMatcher +{ + /// + public string Name => nameof(NotNullOrEmptyMatcher); + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public object Value { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + public NotNullOrEmptyMatcher(MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch) + { + MatchBehaviour = matchBehaviour; + Value = string.Empty; + } + + /// + public MatchResult IsMatch(object? input) + { + bool match; + + switch (input) + { + case string @string: + match = !string.IsNullOrEmpty(@string); + break; + + case byte[] bytes: + match = bytes.Any(); + break; + + default: + match = input != null; + break; + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); + } + + /// + public MatchResult IsMatch(string? input) + { + var match = !string.IsNullOrEmpty(input); + + return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(match)); + } + + /// + public AnyOf[] GetPatterns() + { + return []; + } + + /// + public MatchOperator MatchOperator => MatchOperator.Or; + + /// + public string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}" + + $")"; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/RegexMatcher.cs b/src/WireMock.Net.Shared/Matchers/RegexMatcher.cs similarity index 96% rename from src/WireMock.Net/Matchers/RegexMatcher.cs rename to src/WireMock.Net.Shared/Matchers/RegexMatcher.cs index 4efbe9ad..fab9edf6 100644 --- a/src/WireMock.Net/Matchers/RegexMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/RegexMatcher.cs @@ -1,144 +1,144 @@ -// Copyright © WireMock.Net - -using System; -using System.Linq; -using System.Text.RegularExpressions; -using AnyOfTypes; -using JetBrains.Annotations; -using Stef.Validation; -using WireMock.Constants; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.RegularExpressions; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// Regular Expression Matcher -/// -/// -/// -public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher -{ - private readonly AnyOf[] _patterns; - private readonly Regex[] _expressions; - private readonly bool _useRegexExtended; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - /// Ignore the case from the pattern. - /// Use RegexExtended (default = true). - /// The to use. (default = "Or") - public RegexMatcher( - [RegexPattern] AnyOf pattern, - bool ignoreCase = false, - bool useRegexExtended = true, - MatchOperator matchOperator = MatchOperator.Or) : - this(MatchBehaviour.AcceptOnMatch, [pattern], ignoreCase, useRegexExtended, matchOperator) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - /// Ignore the case from the pattern. - /// Use RegexExtended (default = true). - /// The to use. (default = "Or") - public RegexMatcher( - MatchBehaviour matchBehaviour, - [RegexPattern] AnyOf pattern, - bool ignoreCase = false, - bool useRegexExtended = true, - MatchOperator matchOperator = MatchOperator.Or) : - this(matchBehaviour, [pattern], ignoreCase, useRegexExtended, matchOperator) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - /// Ignore the case from the pattern. - /// Use RegexExtended (default = true). - /// The to use. (default = "Or") - public RegexMatcher( - MatchBehaviour matchBehaviour, - [RegexPattern] AnyOf[] patterns, - bool ignoreCase = false, - bool useRegexExtended = true, - MatchOperator matchOperator = MatchOperator.Or) - { - _patterns = Guard.NotNull(patterns); - IgnoreCase = ignoreCase; - _useRegexExtended = useRegexExtended; - MatchBehaviour = matchBehaviour; - MatchOperator = matchOperator; - - var options = RegexOptions.Compiled | RegexOptions.Multiline; - - if (ignoreCase) - { - options |= RegexOptions.IgnoreCase; - } - - _expressions = patterns.Select(p => useRegexExtended ? new RegexExtended(p.GetPattern(), options) : new Regex(p.GetPattern(), options, WireMockConstants.DefaultRegexTimeout)).ToArray(); - } - - /// - public virtual MatchResult IsMatch(string? input) - { - var score = MatchScores.Mismatch; - Exception? exception = null; - - if (input != null) - { - try - { - score = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)).ToArray(), MatchOperator); - } - catch (Exception ex) - { - exception = ex; - } - } - - return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); - } - - /// - public virtual AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public virtual string Name => nameof(RegexMatcher); - - /// - public bool IgnoreCase { get; } - - /// - public MatchOperator MatchOperator { get; } - - /// - public virtual string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(_useRegexExtended)}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}" + - $")"; - } +// Copyright © WireMock.Net + +using System; +using System.Linq; +using System.Text.RegularExpressions; +using AnyOfTypes; +using JetBrains.Annotations; +using Stef.Validation; +using WireMock.Constants; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.RegularExpressions; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// Regular Expression Matcher +/// +/// +/// +public class RegexMatcher : IStringMatcher, IIgnoreCaseMatcher +{ + private readonly AnyOf[] _patterns; + private readonly Regex[] _expressions; + private readonly bool _useRegexExtended; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The pattern. + /// Ignore the case from the pattern. + /// Use RegexExtended (default = true). + /// The to use. (default = "Or") + public RegexMatcher( + [RegexPattern] AnyOf pattern, + bool ignoreCase = false, + bool useRegexExtended = true, + MatchOperator matchOperator = MatchOperator.Or) : + this(MatchBehaviour.AcceptOnMatch, [pattern], ignoreCase, useRegexExtended, matchOperator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + /// Ignore the case from the pattern. + /// Use RegexExtended (default = true). + /// The to use. (default = "Or") + public RegexMatcher( + MatchBehaviour matchBehaviour, + [RegexPattern] AnyOf pattern, + bool ignoreCase = false, + bool useRegexExtended = true, + MatchOperator matchOperator = MatchOperator.Or) : + this(matchBehaviour, [pattern], ignoreCase, useRegexExtended, matchOperator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The patterns. + /// Ignore the case from the pattern. + /// Use RegexExtended (default = true). + /// The to use. (default = "Or") + public RegexMatcher( + MatchBehaviour matchBehaviour, + [RegexPattern] AnyOf[] patterns, + bool ignoreCase = false, + bool useRegexExtended = true, + MatchOperator matchOperator = MatchOperator.Or) + { + _patterns = Guard.NotNull(patterns); + IgnoreCase = ignoreCase; + _useRegexExtended = useRegexExtended; + MatchBehaviour = matchBehaviour; + MatchOperator = matchOperator; + + var options = RegexOptions.Compiled | RegexOptions.Multiline; + + if (ignoreCase) + { + options |= RegexOptions.IgnoreCase; + } + + _expressions = patterns.Select(p => useRegexExtended ? new RegexExtended(p.GetPattern(), options) : new Regex(p.GetPattern(), options, RegexConstants.DefaultTimeout)).ToArray(); + } + + /// + public virtual MatchResult IsMatch(string? input) + { + var score = MatchScores.Mismatch; + Exception? exception = null; + + if (input != null) + { + try + { + score = MatchScores.ToScore(_expressions.Select(e => e.IsMatch(input)).ToArray(), MatchOperator); + } + catch (Exception ex) + { + exception = ex; + } + } + + return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); + } + + /// + public virtual AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public virtual string Name => nameof(RegexMatcher); + + /// + public bool IgnoreCase { get; } + + /// + public MatchOperator MatchOperator { get; } + + /// + public virtual string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(_useRegexExtended)}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}" + + $")"; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/WildcardMatcher.cs b/src/WireMock.Net.Shared/Matchers/WildcardMatcher.cs similarity index 97% rename from src/WireMock.Net/Matchers/WildcardMatcher.cs rename to src/WireMock.Net.Shared/Matchers/WildcardMatcher.cs index c225e481..32e45431 100644 --- a/src/WireMock.Net/Matchers/WildcardMatcher.cs +++ b/src/WireMock.Net.Shared/Matchers/WildcardMatcher.cs @@ -1,97 +1,97 @@ -// Copyright © WireMock.Net - -using System.Linq; -using System.Text.RegularExpressions; -using AnyOfTypes; -using Stef.Validation; -using WireMock.Extensions; -using WireMock.Models; -using WireMock.Util; - -namespace WireMock.Matchers; - -/// -/// WildcardMatcher -/// -/// -public class WildcardMatcher : RegexMatcher -{ - private readonly AnyOf[] _patterns; - - /// - /// Initializes a new instance of the class. - /// - /// The pattern. - /// IgnoreCase - public WildcardMatcher(AnyOf pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The pattern. - /// IgnoreCase - public WildcardMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - /// IgnoreCase - public WildcardMatcher(AnyOf[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The patterns. - /// IgnoreCase - /// The to use. (default = "Or") - public WildcardMatcher( - MatchBehaviour matchBehaviour, - AnyOf[] patterns, - bool ignoreCase = false, - MatchOperator matchOperator = MatchOperator.Or) : base(matchBehaviour, CreateArray(patterns), ignoreCase, true, matchOperator) - { - _patterns = Guard.NotNull(patterns); - } - - /// - public override AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public override string Name => nameof(WildcardMatcher); - - /// - public override string GetCSharpCodeArguments() - { - return $"new {Name}" + - $"(" + - $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + - $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + - $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + - $"{MatchOperator.GetFullyQualifiedEnumValue()}" + - $")"; - } - - private static AnyOf[] CreateArray(AnyOf[] patterns) - { - return patterns - .Select(pattern => new AnyOf( - new StringPattern - { - Pattern = "^" + Regex.Escape(pattern.GetPattern()).Replace(@"\*", ".*").Replace(@"\?", ".") + "$", - PatternAsFile = pattern.IsSecond ? pattern.Second.PatternAsFile : null - })) - .ToArray(); - } +// Copyright © WireMock.Net + +using System.Linq; +using System.Text.RegularExpressions; +using AnyOfTypes; +using Stef.Validation; +using WireMock.Extensions; +using WireMock.Models; +using WireMock.Util; + +namespace WireMock.Matchers; + +/// +/// WildcardMatcher +/// +/// +public class WildcardMatcher : RegexMatcher +{ + private readonly AnyOf[] _patterns; + + /// + /// Initializes a new instance of the class. + /// + /// The pattern. + /// IgnoreCase + public WildcardMatcher(AnyOf pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The pattern. + /// IgnoreCase + public WildcardMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + /// IgnoreCase + public WildcardMatcher(AnyOf[] patterns, bool ignoreCase = false) : this(MatchBehaviour.AcceptOnMatch, patterns, ignoreCase) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The patterns. + /// IgnoreCase + /// The to use. (default = "Or") + public WildcardMatcher( + MatchBehaviour matchBehaviour, + AnyOf[] patterns, + bool ignoreCase = false, + MatchOperator matchOperator = MatchOperator.Or) : base(matchBehaviour, CreateArray(patterns), ignoreCase, true, matchOperator) + { + _patterns = Guard.NotNull(patterns); + } + + /// + public override AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public override string Name => nameof(WildcardMatcher); + + /// + public override string GetCSharpCodeArguments() + { + return $"new {Name}" + + $"(" + + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}, " + + $"{CSharpFormatter.ToCSharpBooleanLiteral(IgnoreCase)}, " + + $"{MatchOperator.GetFullyQualifiedEnumValue()}" + + $")"; + } + + private static AnyOf[] CreateArray(AnyOf[] patterns) + { + return patterns + .Select(pattern => new AnyOf( + new StringPattern + { + Pattern = "^" + Regex.Escape(pattern.GetPattern()).Replace(@"\*", ".*").Replace(@"\?", ".") + "$", + PatternAsFile = pattern.IsSecond ? pattern.Second.PatternAsFile : null + })) + .ToArray(); + } } \ No newline at end of file diff --git a/src/WireMock.Net/Models/BodyData.cs b/src/WireMock.Net.Shared/Models/BodyData.cs similarity index 100% rename from src/WireMock.Net/Models/BodyData.cs rename to src/WireMock.Net.Shared/Models/BodyData.cs diff --git a/src/WireMock.Net.Shared/Properties/AssemblyInfo.cs b/src/WireMock.Net.Shared/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..922e9942 --- /dev/null +++ b/src/WireMock.Net.Shared/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +// Copyright © WireMock.Net + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("WireMock.Net.Minimal, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] +[assembly: InternalsVisibleTo("WireMock.Net.MimePart, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] +[assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] +// [assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] +[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] + +// Needed for Moq in the UnitTest project +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/src/WireMock.Net/RegularExpressions/RegexExtended.cs b/src/WireMock.Net.Shared/RegularExpressions/RegexExtended.cs similarity index 99% rename from src/WireMock.Net/RegularExpressions/RegexExtended.cs rename to src/WireMock.Net.Shared/RegularExpressions/RegexExtended.cs index f3e5990b..529e39e8 100644 --- a/src/WireMock.Net/RegularExpressions/RegexExtended.cs +++ b/src/WireMock.Net.Shared/RegularExpressions/RegexExtended.cs @@ -39,6 +39,7 @@ internal class RegexExtended : Regex { } #endif + // Dictionary of various Guid tokens with a corresponding regular expression pattern to use instead. private static readonly Dictionary GuidTokenPatterns = new() { diff --git a/src/WireMock.Net/Serialization/JsonSerializationConstants.cs b/src/WireMock.Net.Shared/Serialization/JsonSerializationConstants.cs similarity index 100% rename from src/WireMock.Net/Serialization/JsonSerializationConstants.cs rename to src/WireMock.Net.Shared/Serialization/JsonSerializationConstants.cs diff --git a/src/WireMock.Net/Util/BodyParser.cs b/src/WireMock.Net.Shared/Util/BodyParser.cs similarity index 95% rename from src/WireMock.Net/Util/BodyParser.cs rename to src/WireMock.Net.Shared/Util/BodyParser.cs index c5b49829..de940a24 100644 --- a/src/WireMock.Net/Util/BodyParser.cs +++ b/src/WireMock.Net.Shared/Util/BodyParser.cs @@ -1,215 +1,218 @@ -// Copyright © WireMock.Net - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; -using Stef.Validation; -using WireMock.Constants; -using WireMock.Matchers; -using WireMock.Types; - -namespace WireMock.Util; - -internal static class BodyParser -{ - private static readonly Encoding DefaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); - private static readonly Encoding[] SupportedBodyAsStringEncodingForMultipart = { DefaultEncoding, Encoding.ASCII }; - - /* - HEAD - No defined body semantics. - GET - No defined body semantics. - PUT - Body supported. - POST - Body supported. - DELETE - No defined body semantics. - TRACE - Body not supported. - OPTIONS - Body supported but no semantics on usage (maybe in the future). - CONNECT - No defined body semantics - PATCH - Body supported. - */ - private static readonly IDictionary BodyAllowedForMethods = new Dictionary - { - { HttpRequestMethod.HEAD, false }, - { HttpRequestMethod.GET, false }, - { HttpRequestMethod.PUT, true }, - { HttpRequestMethod.POST, true }, - { HttpRequestMethod.DELETE, true }, - { HttpRequestMethod.TRACE, false }, - { HttpRequestMethod.OPTIONS, true }, - { HttpRequestMethod.CONNECT, false }, - { HttpRequestMethod.PATCH, true } - }; - - private static readonly IStringMatcher[] MultipartContentTypesMatchers = { - new WildcardMatcher("multipart/*", true) - }; - - private static readonly IStringMatcher[] JsonContentTypesMatchers = { - new WildcardMatcher("application/json", true), - new WildcardMatcher("application/vnd.*+json", true) - }; - - private static readonly IStringMatcher FormUrlEncodedMatcher = new WildcardMatcher("application/x-www-form-urlencoded", true); - - private static readonly IStringMatcher[] TextContentTypeMatchers = - { - new WildcardMatcher("text/*", true), - new RegexMatcher("^application\\/(java|type)script$", true), - new WildcardMatcher("application/*xml", true), - FormUrlEncodedMatcher - }; - - private static readonly IStringMatcher[] GrpcContentTypesMatchers = { - new WildcardMatcher("application/grpc", true), - new WildcardMatcher("application/grpc+proto", true) - }; - - public static bool ShouldParseBody(string? httpMethod, bool allowBodyForAllHttpMethods) - { - if (string.IsNullOrEmpty(httpMethod)) - { - return false; - } - - if (allowBodyForAllHttpMethods) - { - return true; - } - - if (BodyAllowedForMethods.TryGetValue(httpMethod!.ToUpper(), out var allowed)) - { - return allowed; - } - - // If we don't have any knowledge of this method, we should assume that a body *may* - // be present, so we should parse it if it is. Therefore, if a new method is added to - // the HTTP Method Registry, we only really need to add it to BodyAllowedForMethods if - // we want to make it clear that a body is *not* allowed. - return true; - } - - public static BodyType DetectBodyTypeFromContentType(string? contentTypeValue) - { - if (string.IsNullOrEmpty(contentTypeValue) || !MediaTypeHeaderValue.TryParse(contentTypeValue, out var contentType)) - { - return BodyType.Bytes; - } - - if (FormUrlEncodedMatcher.IsMatch(contentType.MediaType).IsPerfect()) - { - return BodyType.FormUrlEncoded; - } - - if (TextContentTypeMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect())) - { - return BodyType.String; - } - - if (JsonContentTypesMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect())) - { - return BodyType.Json; - } - - if (GrpcContentTypesMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect())) - { - return BodyType.ProtoBuf; - } - - if (MultipartContentTypesMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect())) - { - return BodyType.MultiPart; - } - - return BodyType.Bytes; - } - - public static async Task ParseAsync(BodyParserSettings settings) - { - Guard.NotNull(settings); - - var bodyWithContentEncoding = await ReadBytesAsync(settings.Stream, settings.ContentEncoding, settings.DecompressGZipAndDeflate).ConfigureAwait(false); - var data = new BodyData - { - BodyAsBytes = bodyWithContentEncoding.Bytes, - DetectedCompression = bodyWithContentEncoding.ContentType, - DetectedBodyType = BodyType.Bytes, - DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(settings.ContentType) - }; - - // In case of MultiPart: check if the BodyAsBytes is a valid UTF8 or ASCII string, in that case read as String else keep as-is - if (data.DetectedBodyTypeFromContentType == BodyType.MultiPart) - { - if (BytesEncodingUtils.TryGetEncoding(data.BodyAsBytes, out var encoding) && - SupportedBodyAsStringEncodingForMultipart.Select(x => x.Equals(encoding)).Any()) - { - data.BodyAsString = encoding.GetString(data.BodyAsBytes); - data.Encoding = encoding; - data.DetectedBodyType = BodyType.String; - } - - return data; - } - - // Try to get the body as String, FormUrlEncoded or Json - try - { - data.BodyAsString = DefaultEncoding.GetString(data.BodyAsBytes); - data.Encoding = DefaultEncoding; - data.DetectedBodyType = BodyType.String; - - // If string is not null or empty, try to deserialize the string to a IDictionary - if (settings.DeserializeFormUrlEncoded && - data.DetectedBodyTypeFromContentType == BodyType.FormUrlEncoded && - QueryStringParser.TryParse(data.BodyAsString, false, out var nameValueCollection) - ) - { - try - { - data.BodyAsFormUrlEncoded = nameValueCollection; - data.DetectedBodyType = BodyType.FormUrlEncoded; - } - catch - { - // Deserialize FormUrlEncoded failed, just ignore. - } - } - - // If string is not null or empty, try to deserialize the string to a JObject - if (settings.DeserializeJson && JsonUtils.IsJson(data.BodyAsString)) - { - try - { - data.BodyAsJson = JsonUtils.DeserializeObject(data.BodyAsString); - data.DetectedBodyType = BodyType.Json; - } - catch - { - // JsonConvert failed, just ignore. - } - } - } - catch - { - // Reading as string failed, just ignore - } - - return data; - } - - private static async Task<(string? ContentType, byte[] Bytes)> ReadBytesAsync(Stream stream, string? contentEncoding = null, bool decompressGZipAndDeflate = true) - { - using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - byte[] data = memoryStream.ToArray(); - - var type = contentEncoding?.ToLowerInvariant(); - if (decompressGZipAndDeflate && type is "gzip" or "deflate") - { - return (type, CompressionUtils.Decompress(type, data)); - } - - return (null, data); - } +// Copyright © WireMock.Net + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Stef.Validation; +using WireMock.Constants; +using WireMock.Matchers; +using WireMock.Types; + +namespace WireMock.Util; + +internal static class BodyParser +{ + private static readonly Encoding DefaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + private static readonly Encoding[] SupportedBodyAsStringEncodingForMultipart = [ DefaultEncoding, Encoding.ASCII ]; + + /* + HEAD - No defined body semantics. + GET - No defined body semantics. + PUT - Body supported. + POST - Body supported. + DELETE - No defined body semantics. + TRACE - Body not supported. + OPTIONS - Body supported but no semantics on usage (maybe in the future). + CONNECT - No defined body semantics + PATCH - Body supported. + */ + private static readonly IDictionary BodyAllowedForMethods = new Dictionary + { + { HttpRequestMethod.HEAD, false }, + { HttpRequestMethod.GET, false }, + { HttpRequestMethod.PUT, true }, + { HttpRequestMethod.POST, true }, + { HttpRequestMethod.DELETE, true }, + { HttpRequestMethod.TRACE, false }, + { HttpRequestMethod.OPTIONS, true }, + { HttpRequestMethod.CONNECT, false }, + { HttpRequestMethod.PATCH, true } + }; + + private static readonly IStringMatcher[] MultipartContentTypesMatchers = + [ + new WildcardMatcher("multipart/*", true) + ]; + + private static readonly IStringMatcher[] JsonContentTypesMatchers = + [ + new WildcardMatcher("application/json", true), + new WildcardMatcher("application/vnd.*+json", true) + ]; + + private static readonly IStringMatcher FormUrlEncodedMatcher = new WildcardMatcher("application/x-www-form-urlencoded", true); + + private static readonly IStringMatcher[] TextContentTypeMatchers = + [ + new WildcardMatcher("text/*", true), + new RegexMatcher("^application\\/(java|type)script$", true), + new WildcardMatcher("application/*xml", true), + FormUrlEncodedMatcher + ]; + + private static readonly IStringMatcher[] GrpcContentTypesMatchers = + [ + new WildcardMatcher("application/grpc", true), + new WildcardMatcher("application/grpc+proto", true) + ]; + + public static bool ShouldParseBody(string? httpMethod, bool allowBodyForAllHttpMethods) + { + if (string.IsNullOrEmpty(httpMethod)) + { + return false; + } + + if (allowBodyForAllHttpMethods) + { + return true; + } + + if (BodyAllowedForMethods.TryGetValue(httpMethod!.ToUpper(), out var allowed)) + { + return allowed; + } + + // If we don't have any knowledge of this method, we should assume that a body *may* + // be present, so we should parse it if it is. Therefore, if a new method is added to + // the HTTP Method Registry, we only really need to add it to BodyAllowedForMethods if + // we want to make it clear that a body is *not* allowed. + return true; + } + + public static BodyType DetectBodyTypeFromContentType(string? contentTypeValue) + { + if (string.IsNullOrEmpty(contentTypeValue) || !MediaTypeHeaderValue.TryParse(contentTypeValue, out var contentType)) + { + return BodyType.Bytes; + } + + if (FormUrlEncodedMatcher.IsMatch(contentType.MediaType).IsPerfect()) + { + return BodyType.FormUrlEncoded; + } + + if (TextContentTypeMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect())) + { + return BodyType.String; + } + + if (JsonContentTypesMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect())) + { + return BodyType.Json; + } + + if (GrpcContentTypesMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect())) + { + return BodyType.ProtoBuf; + } + + if (MultipartContentTypesMatchers.Any(matcher => matcher.IsMatch(contentType.MediaType).IsPerfect())) + { + return BodyType.MultiPart; + } + + return BodyType.Bytes; + } + + public static async Task ParseAsync(BodyParserSettings settings) + { + Guard.NotNull(settings); + + var bodyWithContentEncoding = await ReadBytesAsync(settings.Stream, settings.ContentEncoding, settings.DecompressGZipAndDeflate).ConfigureAwait(false); + var data = new BodyData + { + BodyAsBytes = bodyWithContentEncoding.Bytes, + DetectedCompression = bodyWithContentEncoding.ContentType, + DetectedBodyType = BodyType.Bytes, + DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(settings.ContentType) + }; + + // In case of MultiPart: check if the BodyAsBytes is a valid UTF8 or ASCII string, in that case read as String else keep as-is + if (data.DetectedBodyTypeFromContentType == BodyType.MultiPart) + { + if (BytesEncodingUtils.TryGetEncoding(data.BodyAsBytes, out var encoding) && + SupportedBodyAsStringEncodingForMultipart.Select(x => x.Equals(encoding)).Any()) + { + data.BodyAsString = encoding.GetString(data.BodyAsBytes); + data.Encoding = encoding; + data.DetectedBodyType = BodyType.String; + } + + return data; + } + + // Try to get the body as String, FormUrlEncoded or Json + try + { + data.BodyAsString = DefaultEncoding.GetString(data.BodyAsBytes); + data.Encoding = DefaultEncoding; + data.DetectedBodyType = BodyType.String; + + // If string is not null or empty, try to deserialize the string to a IDictionary + if (settings.DeserializeFormUrlEncoded && + data.DetectedBodyTypeFromContentType == BodyType.FormUrlEncoded && + QueryStringParser.TryParse(data.BodyAsString, false, out var nameValueCollection) + ) + { + try + { + data.BodyAsFormUrlEncoded = nameValueCollection; + data.DetectedBodyType = BodyType.FormUrlEncoded; + } + catch + { + // Deserialize FormUrlEncoded failed, just ignore. + } + } + + // If string is not null or empty, try to deserialize the string to a JObject + if (settings.DeserializeJson && JsonUtils.IsJson(data.BodyAsString)) + { + try + { + data.BodyAsJson = JsonUtils.DeserializeObject(data.BodyAsString); + data.DetectedBodyType = BodyType.Json; + } + catch + { + // JsonConvert failed, just ignore. + } + } + } + catch + { + // Reading as string failed, just ignore + } + + return data; + } + + private static async Task<(string? ContentType, byte[] Bytes)> ReadBytesAsync(Stream stream, string? contentEncoding = null, bool decompressGZipAndDeflate = true) + { + using var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream).ConfigureAwait(false); + byte[] data = memoryStream.ToArray(); + + var type = contentEncoding?.ToLowerInvariant(); + if (decompressGZipAndDeflate && type is "gzip" or "deflate") + { + return (type, CompressionUtils.Decompress(type, data)); + } + + return (null, data); + } } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Util/BodyParserSettings.cs b/src/WireMock.Net.Shared/Util/BodyParserSettings.cs new file mode 100644 index 00000000..2fdaea0b --- /dev/null +++ b/src/WireMock.Net.Shared/Util/BodyParserSettings.cs @@ -0,0 +1,38 @@ +// Copyright © WireMock.Net + +using System.IO; + +namespace WireMock.Util; + +internal class BodyParserSettings +{ + /// + /// The body stream to parse. + /// + public Stream Stream { get; set; } = null!; + + /// + /// The (optional) content type of the body. + /// + public string? ContentType { get; set; } + + /// + /// The (optional) content encoding of the body. + /// + public string? ContentEncoding { get; set; } + + /// + /// Automatically decompress GZip and Deflate encoded content. + /// + public bool DecompressGZipAndDeflate { get; set; } = true; + + /// + /// Try to deserialize the body as JSON. + /// + public bool DeserializeJson { get; set; } = true; + + /// + /// Try to deserialize the body as FormUrlEncoded. + /// + public bool DeserializeFormUrlEncoded { get; set; } = true; +} \ No newline at end of file diff --git a/src/WireMock.Net/Util/BytesEncodingUtils.cs b/src/WireMock.Net.Shared/Util/BytesEncodingUtils.cs similarity index 95% rename from src/WireMock.Net/Util/BytesEncodingUtils.cs rename to src/WireMock.Net.Shared/Util/BytesEncodingUtils.cs index 727720fb..db1d50be 100644 --- a/src/WireMock.Net/Util/BytesEncodingUtils.cs +++ b/src/WireMock.Net.Shared/Util/BytesEncodingUtils.cs @@ -9,6 +9,7 @@ using System.Text; namespace WireMock.Util; /// +/// Some utility methods for encoding. /// Based on: /// http://utf8checker.codeplex.com /// https://github.com/0x53A/Mvvm/blob/master/src/Mvvm/src/Utf8Checker.cs @@ -35,25 +36,25 @@ internal static class BytesEncodingUtils return true; } - if (StartsWith(bytes, new byte[] { 0xff, 0xfe, 0x00, 0x00 })) + if (StartsWith(bytes, [0xff, 0xfe, 0x00, 0x00])) { encoding = Encoding.UTF32; return true; } - if (StartsWith(bytes, new byte[] { 0xfe, 0xff })) + if (StartsWith(bytes, [0xfe, 0xff])) { encoding = Encoding.BigEndianUnicode; return true; } - if (StartsWith(bytes, new byte[] { 0xff, 0xfe })) + if (StartsWith(bytes, [0xff, 0xfe])) { encoding = Encoding.Unicode; return true; } - if (StartsWith(bytes, new byte[] { 0xef, 0xbb, 0xbf })) + if (StartsWith(bytes, [0xef, 0xbb, 0xbf])) { encoding = Encoding.UTF8; return true; diff --git a/src/WireMock.Net/Util/CSharpFormatter.cs b/src/WireMock.Net.Shared/Util/CSharpFormatter.cs similarity index 95% rename from src/WireMock.Net/Util/CSharpFormatter.cs rename to src/WireMock.Net.Shared/Util/CSharpFormatter.cs index 0ef6c1b1..4e34d272 100644 --- a/src/WireMock.Net/Util/CSharpFormatter.cs +++ b/src/WireMock.Net.Shared/Util/CSharpFormatter.cs @@ -10,6 +10,9 @@ using Newtonsoft.Json.Linq; namespace WireMock.Util; +/// +/// A utility class for converting JSON to C# anonymous object definitions. +/// internal static class CSharpFormatter { private const string Null = "null"; @@ -142,12 +145,12 @@ internal static class CSharpFormatter if (value.Contains('\n')) { - var escapedValue = value.Replace("\"", "\"\""); + var escapedValue = value!.Replace("\"", "\"\""); return $"@\"{escapedValue}\""; } else { - var escapedValue = value.Replace("\"", "\\\""); + var escapedValue = value!.Replace("\"", "\\\""); return $"\"{escapedValue}\""; } } diff --git a/src/WireMock.Net/Util/CompressionUtils.cs b/src/WireMock.Net.Shared/Util/CompressionUtils.cs similarity index 66% rename from src/WireMock.Net/Util/CompressionUtils.cs rename to src/WireMock.Net.Shared/Util/CompressionUtils.cs index f38f7da1..290ff9c8 100644 --- a/src/WireMock.Net/Util/CompressionUtils.cs +++ b/src/WireMock.Net.Shared/Util/CompressionUtils.cs @@ -6,8 +6,17 @@ using System.IO.Compression; namespace WireMock.Util; +/// +/// Some utility methods for compressing and decompressing data. +/// internal static class CompressionUtils { + /// + /// Compresses the specified data using the specified content encoding. + /// + /// The content-encoding. + /// The data. + /// Compressed data public static byte[] Compress(string contentEncoding, byte[] data) { using var compressedStream = new MemoryStream(); @@ -20,6 +29,12 @@ internal static class CompressionUtils return compressedStream.ToArray(); } + /// + /// Decompresses the specified data using the specified content encoding. + /// + /// The content-encoding. + /// The compressed data. + /// Uncompressed data public static byte[] Decompress(string contentEncoding, byte[] data) { using var compressedStream = new MemoryStream(data); diff --git a/src/WireMock.Net.Shared/Util/IMimeKitUtils.cs b/src/WireMock.Net.Shared/Util/IMimeKitUtils.cs new file mode 100644 index 00000000..35d5adbb --- /dev/null +++ b/src/WireMock.Net.Shared/Util/IMimeKitUtils.cs @@ -0,0 +1,35 @@ +// Copyright © WireMock.Net + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; + +namespace WireMock.Util; + +/// +/// Defines the interface for MimeKitUtils. +/// +public interface IMimeKitUtils +{ + /// + /// Loads the MimeKit.MimeMessage from the stream. + /// + /// The stream + /// MimeKit.MimeMessage + object LoadFromStream(Stream stream); + + /// + /// Tries to get the MimeKit.MimeMessage from the request message. + /// + /// The request message. + /// The MimeKit.MimeMessage + /// true when parsed correctly, else false + bool TryGetMimeMessage(IRequestMessage requestMessage, [NotNullWhen(true)] out object? mimeMessage); + + /// + /// Gets the body parts from the MimeKit.MimeMessage. + /// + /// The MimeKit.MimeMessage. + /// A list of MimeParts. + IReadOnlyList GetBodyParts(object mimeMessage); +} \ No newline at end of file diff --git a/src/WireMock.Net/Util/JsonUtils.cs b/src/WireMock.Net.Shared/Util/JsonUtils.cs similarity index 100% rename from src/WireMock.Net/Util/JsonUtils.cs rename to src/WireMock.Net.Shared/Util/JsonUtils.cs diff --git a/src/WireMock.Net.Shared/Util/MappingConverterUtils.cs b/src/WireMock.Net.Shared/Util/MappingConverterUtils.cs new file mode 100644 index 00000000..e3cdd65f --- /dev/null +++ b/src/WireMock.Net.Shared/Util/MappingConverterUtils.cs @@ -0,0 +1,36 @@ +// Copyright © WireMock.Net + +using System.Collections.Generic; +using System.Linq; +using AnyOfTypes; +using WireMock.Extensions; +using WireMock.Matchers; +using WireMock.Models; + +namespace WireMock.Util; + +/// +/// Some MappingConverter utility methods. +/// +internal static class MappingConverterUtils +{ + /// + /// Convert a list of matchers to C# code arguments. + /// + /// A list of matchers. + /// The C# code arguments as string. + public static string ToCSharpCodeArguments(IReadOnlyList matchers) + { + return string.Join(", ", matchers.Select(m => m.GetCSharpCodeArguments())); + } + + /// + /// Convert a list of patterns to C# code arguments. + /// + /// The patterns. + /// The C# code arguments as string. + public static string ToCSharpCodeArguments(AnyOf[] patterns) + { + return string.Join(", ", patterns.Select(p => CSharpFormatter.ToCSharpStringLiteral(p.GetPattern()))); + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Util/QueryStringParser.cs b/src/WireMock.Net.Shared/Util/QueryStringParser.cs similarity index 94% rename from src/WireMock.Net/Util/QueryStringParser.cs rename to src/WireMock.Net.Shared/Util/QueryStringParser.cs index c10f02b7..0a2c4630 100644 --- a/src/WireMock.Net/Util/QueryStringParser.cs +++ b/src/WireMock.Net.Shared/Util/QueryStringParser.cs @@ -10,7 +10,7 @@ using WireMock.Types; namespace WireMock.Util; /// -/// Based on https://stackoverflow.com/questions/659887/get-url-parameters-from-a-string-in-net +/// QueryStringParser (based on https://stackoverflow.com/questions/659887/get-url-parameters-from-a-string-in-net) /// internal static class QueryStringParser { @@ -18,13 +18,13 @@ internal static class QueryStringParser public static bool TryParse(string? queryString, bool caseIgnore, [NotNullWhen(true)] out IDictionary? nameValueCollection) { - if (queryString is null) + if (queryString == null) { nameValueCollection = null; return false; } - var parts = queryString! + var parts = queryString .Split(["&"], StringSplitOptions.RemoveEmptyEntries) .Select(parameter => parameter.Split('=')) .Distinct(); diff --git a/src/WireMock.Net.Shared/WireMock.Net.Shared.csproj b/src/WireMock.Net.Shared/WireMock.Net.Shared.csproj new file mode 100644 index 00000000..6889ff22 --- /dev/null +++ b/src/WireMock.Net.Shared/WireMock.Net.Shared.csproj @@ -0,0 +1,45 @@ + + + Shared interfaces, models, enumerations and types. + Stef Heyenrath + net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + true + tdd;mock;http;wiremock;test;server;shared + WireMock + {D3804228-91F4-4502-9595-39584E5A0177} + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + ../WireMock.Net/WireMock.Net.snk + + true + + + + + + + + ../WireMock.Net/WireMock.Net.ruleset + + + + + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj index b4ebcd22..c1715a86 100644 --- a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj +++ b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj @@ -1,41 +1,45 @@ - - Lightweight StandAlone Http Mocking Server for .Net. - WireMock.Net.StandAlone - Stef Heyenrath - net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 - true - WireMock.Net.StandAlone - WireMock.Net.StandAlone - tdd;mock;http;wiremock;test;server;unittest - WireMock.Net.StandAlone - {B6269AAC-170A-43D5-8B9A-579DED3D9A95} - true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - true - true - true - WireMock.Net.StandAlone.ruleset - true - ../WireMock.Net/WireMock.Net.snk - - true - + + Lightweight StandAlone Http Mocking Server for .Net. + WireMock.Net.StandAlone + Stef Heyenrath + net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + true + WireMock.Net.StandAlone + WireMock.Net.StandAlone + tdd;mock;http;wiremock;test;server;unittest + WireMock.Net.StandAlone + {B6269AAC-170A-43D5-8B9A-579DED3D9A95} + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + true + WireMock.Net.StandAlone.ruleset + true + ../WireMock.Net/WireMock.Net.snk + + true + - - - true - + + + true + - - NETSTANDARD;USE_ASPNETCORE - + + NETSTANDARD;USE_ASPNETCORE + - - USE_ASPNETCORE;NET46 - + + USE_ASPNETCORE;NET46 + - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj b/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj index a0c36b8e..98e4d3d3 100644 --- a/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj +++ b/src/WireMock.Net.Testcontainers/WireMock.Net.Testcontainers.csproj @@ -1,4 +1,4 @@ - + A fluent testcontainer builder for the Docker version of WireMock.Net @@ -7,22 +7,26 @@ wiremock;docker;testcontainer;testcontainers {12B016A5-9D8B-4EFE-96C2-CA51BE43367D} true - ../WireMock.Net/WireMock.Net.ruleset true ../WireMock.Net/WireMock.Net.snk true MIT + + ../WireMock.Net/WireMock.Net.ruleset + + true - - - - + + + + + diff --git a/src/WireMock.Net/Compatibility/EmptyArray.cs b/src/WireMock.Net/Compatibility/EmptyArray.cs deleted file mode 100644 index 686343cd..00000000 --- a/src/WireMock.Net/Compatibility/EmptyArray.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright © WireMock.Net - -// ReSharper disable once CheckNamespace -namespace System; - -internal static class EmptyArray -{ -#if NET451 || NET452 - public static readonly T[] Value = new T[0]; -#else - public static readonly T[] Value = Array.Empty(); -#endif -} \ No newline at end of file diff --git a/src/WireMock.Net/Util/BodyParserSettings.cs b/src/WireMock.Net/Util/BodyParserSettings.cs deleted file mode 100644 index a9ddef6d..00000000 --- a/src/WireMock.Net/Util/BodyParserSettings.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright © WireMock.Net - -using System.IO; - -namespace WireMock.Util; - -internal class BodyParserSettings -{ - public Stream Stream { get; set; } = null!; - - public string? ContentType { get; set; } - - public string? ContentEncoding { get; set; } - - public bool DecompressGZipAndDeflate { get; set; } = true; - - public bool DeserializeJson { get; set; } = true; - - public bool DeserializeFormUrlEncoded { get; set; } = true; -} \ No newline at end of file diff --git a/src/WireMock.Net/Util/MappingConverterUtils.cs b/src/WireMock.Net/Util/MappingConverterUtils.cs deleted file mode 100644 index f9beb4d6..00000000 --- a/src/WireMock.Net/Util/MappingConverterUtils.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright © WireMock.Net - -using System.Collections.Generic; -using System.Linq; -using AnyOfTypes; -using WireMock.Extensions; -using WireMock.Matchers; -using WireMock.Models; - -namespace WireMock.Util; - -internal static class MappingConverterUtils -{ - internal static string ToCSharpCodeArguments(IReadOnlyList matchers) - { - return string.Join(", ", matchers.Select(m => m.GetCSharpCodeArguments())); - } - - internal static string ToCSharpCodeArguments(AnyOf[] patterns) - { - return string.Join(", ", patterns.Select(p => CSharpFormatter.ToCSharpStringLiteral(p.GetPattern()))); - } -} \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index d54d5538..af948cb3 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -1,6 +1,6 @@ - + - Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape. + Lightweight Http Mocking Server for .NET, inspired by WireMock from the Java landscape. WireMock.Net Stef Heyenrath net451;net452;net46;net461;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 @@ -9,22 +9,16 @@ WireMock.Net tdd;mock;http;wiremock;test;server;unittest WireMock - {D3804228-91F4-4502-9595-39584E5A01AD} + {D3804228-91F4-4502-7854-39584E5A01AD} true $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true - true WireMock.Net.snk true - - - - true @@ -34,176 +28,11 @@ WireMock.Net.ruleset - - $(DefineConstants);NETSTANDARD;USE_ASPNETCORE - - - - $(DefineConstants);USE_ASPNETCORE - - - - $(DefineConstants);USE_ASPNETCORE;NET46 - - - - $(DefineConstants);OPENAPIPARSER - - - - $(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF - - - - $(DefineConstants);TRAILINGHEADERS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - WireMockServer.cs - - - - - - Request.cs - - - - - - Response.cs - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/src/WireMock.Org.Abstractions/WireMock.Org.Abstractions.csproj b/src/WireMock.Org.Abstractions/WireMock.Org.Abstractions.csproj index d87b02e4..bd776b0b 100644 --- a/src/WireMock.Org.Abstractions/WireMock.Org.Abstractions.csproj +++ b/src/WireMock.Org.Abstractions/WireMock.Org.Abstractions.csproj @@ -1,4 +1,4 @@ - + Commonly used interfaces, models, enumerations and types. @@ -17,7 +17,6 @@ true true true - ../WireMock.Net/WireMock.Net.ruleset true ../WireMock.Net/WireMock.Net.snk @@ -26,6 +25,10 @@ disable + + ../WireMock.Net/WireMock.Net.ruleset + + true diff --git a/src/WireMock.Org.RestClient/WireMock.Org.RestClient.csproj b/src/WireMock.Org.RestClient/WireMock.Org.RestClient.csproj index e21ce895..34a7796e 100644 --- a/src/WireMock.Org.RestClient/WireMock.Org.RestClient.csproj +++ b/src/WireMock.Org.RestClient/WireMock.Org.RestClient.csproj @@ -16,7 +16,6 @@ true true true - ../WireMock.Net/WireMock.Net.ruleset true ../WireMock.Net/WireMock.Net.snk @@ -24,6 +23,10 @@ MIT + + ../WireMock.Net/WireMock.Net.ruleset + + true diff --git a/test/WireMock.Net.Aspire.Tests/Facts/DockerIsRunningInLinuxContainerModeFact.cs b/test/WireMock.Net.Aspire.Tests/Facts/DockerIsRunningInLinuxContainerModeFactAttribute.cs similarity index 67% rename from test/WireMock.Net.Aspire.Tests/Facts/DockerIsRunningInLinuxContainerModeFact.cs rename to test/WireMock.Net.Aspire.Tests/Facts/DockerIsRunningInLinuxContainerModeFactAttribute.cs index 7be0668d..53fdddcb 100644 --- a/test/WireMock.Net.Aspire.Tests/Facts/DockerIsRunningInLinuxContainerModeFact.cs +++ b/test/WireMock.Net.Aspire.Tests/Facts/DockerIsRunningInLinuxContainerModeFactAttribute.cs @@ -2,11 +2,11 @@ namespace WireMock.Net.Aspire.Tests.Facts; -public sealed class DockerIsRunningInLinuxContainerModeFact : FactAttribute +public sealed class DockerIsRunningInLinuxContainerModeFactAttribute : FactAttribute { private const string SkipReason = "Docker is not running in Linux container mode. Skipping test."; - public DockerIsRunningInLinuxContainerModeFact() + public DockerIsRunningInLinuxContainerModeFactAttribute() { if (!DockerUtils.IsDockerRunningLinuxContainerMode.Value) { diff --git a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs index 93148dc3..fecd784e 100644 --- a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs +++ b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs @@ -177,9 +177,9 @@ public class WireMockAssertionsTests : IDisposable _server.Should() .HaveReceivedACall() - .WithHeader("Accept", new[] { "application/xml", "application/json" }) + .WithHeader("Accept", ["application/xml", "application/json"]) .And - .WithHeader("Accept-Language", new[] { "EN" }); + .WithHeader("Accept-Language", ["EN"]); } [Fact] @@ -209,7 +209,7 @@ public class WireMockAssertionsTests : IDisposable act.Should() .Throw() - .WithMessage("Expected _server to have been called with Header \"Accept\" and Values {\"missing-value\"}, but didn't find it among the calls with Header(s) {{[\"Accept\"] = {\"application/xml, application/json\"}, [\"Host\"] = {\"localhost:*\"}}}."); + .WithMessage("Expected _server to have been called with Header \"Accept\" and Values {\"missing-value\"}, but didn't find it among the calls with Header(s)*"); } [Fact] @@ -223,11 +223,11 @@ public class WireMockAssertionsTests : IDisposable Action act = () => _server.Should() .HaveReceivedACall() - .WithHeader("Accept", new[] { "missing-value1", "missing-value2" }); + .WithHeader("Accept", ["missing-value1", "missing-value2"]); act.Should() .Throw() - .WithMessage("Expected _server to have been called with Header \"Accept\" and Values {\"missing-value1\", \"missing-value2\"}, but didn't find it among the calls with Header(s) {{[\"Accept\"] = {\"application/xml, application/json\"}, [\"Host\"] = {\"localhost:*\"}}}."); + .WithMessage("Expected _server to have been called with Header \"Accept\" and Values {\"missing-value1\", \"missing-value2\"}, but didn't find it among the calls with Header(s)*"); } [Fact] @@ -853,14 +853,14 @@ public class WireMockAssertionsTests : IDisposable // Act var httpClient = new HttpClient(); - await httpClient.PostAsync($"{server.Url}/a", new ByteArrayContent(new byte[] { 5 })); + await httpClient.PostAsync($"{server.Url}/a", new ByteArrayContent([5])); // Assert Action act = () => server .Should() .HaveReceived(1) .Calls() - .WithBodyAsBytes(new byte[] { 1 }) + .WithBodyAsBytes([1]) .And .UsingPost(); @@ -878,20 +878,20 @@ public class WireMockAssertionsTests : IDisposable var server = WireMockServer.Start(); server - .Given(Request.Create().WithPath("/a").UsingPut().WithBody(new byte[] { 100 })) + .Given(Request.Create().WithPath("/a").UsingPut().WithBody([100])) .RespondWith(Response.Create().WithBody("A response")); // Act var httpClient = new HttpClient(); - await httpClient.PutAsync($"{server.Url}/a", new ByteArrayContent(new byte[] { 100 })); + await httpClient.PutAsync($"{server.Url}/a", new ByteArrayContent([100])); // Assert server .Should() .HaveReceived(1) .Calls() - .WithBodyAsBytes(new byte[] { 100 }) + .WithBodyAsBytes([100]) .And .UsingPut(); @@ -899,7 +899,7 @@ public class WireMockAssertionsTests : IDisposable .Should() .HaveReceived(0) .Calls() - .WithBodyAsBytes(new byte[0]) + .WithBodyAsBytes([]) .And .UsingPut(); @@ -907,7 +907,7 @@ public class WireMockAssertionsTests : IDisposable .Should() .HaveReceived(0) .Calls() - .WithBodyAsBytes(new byte[] { 42 }) + .WithBodyAsBytes([42]) .And .UsingPut(); @@ -961,7 +961,7 @@ public class WireMockAssertionsTests : IDisposable public async Task HaveReceivedACall_WithHeader_Should_ThrowWhenHttpMethodDoesNotMatch() { // Arrange - using var server = WireMockServer.Start(); + var server = WireMockServer.Start(); // Act : HTTP GET using var httpClient = new HttpClient(); @@ -969,24 +969,24 @@ public class WireMockAssertionsTests : IDisposable // Act : HTTP POST var request = new HttpRequestMessage(HttpMethod.Post, server.Url!); - request.Headers.Add("TestHeader", new[] { "Value", "Value2" }); + request.Headers.Add("TestHeader", ["Value", "Value2"]); await httpClient.SendAsync(request); // Assert - server.Should().HaveReceivedACall().UsingPost().And.WithHeader("TestHeader", new[] { "Value", "Value2" }); + server.Should().HaveReceivedACall().UsingPost().And.WithHeader("TestHeader", ["Value", "Value2"]); Action act = () => server.Should().HaveReceivedACall().UsingGet().And.WithHeader("TestHeader", "Value"); act.Should() .Throw() - .WithMessage("Expected server to have been called with Header \"TestHeader\" and Values {\"Value\"}, but didn't find it among the calls with Header(s) {{[\"Host\"] = {\"localhost:*\"}}}."); + .WithMessage("Expected server to have been called with Header \"TestHeader\" and Values {\"Value\"}, but didn't find it among the calls with Header(s)*"); } [Fact] public async Task HaveReceivedACall_WithHeaderKey_Should_ThrowWhenHttpMethodDoesNotMatch() { // Arrange - using var server = WireMockServer.Start(); + var server = WireMockServer.Start(); // Act : HTTP GET using var httpClient = new HttpClient(); @@ -994,7 +994,7 @@ public class WireMockAssertionsTests : IDisposable // Act : HTTP POST var request = new HttpRequestMessage(HttpMethod.Post, server.Url!); - request.Headers.Add("TestHeader", new[] { "Value", "Value2" }); + request.Headers.Add("TestHeader", ["Value", "Value2"]); await httpClient.SendAsync(request); @@ -1004,7 +1004,7 @@ public class WireMockAssertionsTests : IDisposable Action act = () => server.Should().HaveReceivedACall().UsingGet().And.WitHeaderKey("TestHeader"); act.Should() .Throw() - .WithMessage("Expected server to have been called with Header \"TestHeader\", but didn't find it among the calls with Header(s) {{[\"Host\"] = {\"localhost:*\"}}}."); + .WithMessage("Expected server to have been called with Header \"TestHeader\", but didn't find it among the calls with Header(s)*"); } public void Dispose() diff --git a/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs index 01d6e522..944a0487 100644 --- a/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/JmesPathMatcherTests.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using FluentAssertions; using Newtonsoft.Json.Linq; using NFluent; @@ -41,7 +40,7 @@ public class JmesPathMatcherTests public void JmesPathMatcher_IsMatch_ByteArray() { // Assign - var bytes = EmptyArray.Value; + var bytes = new byte[0]; var matcher = new JmesPathMatcher(""); // Act diff --git a/test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs index ed529e85..710e6476 100644 --- a/test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/JsonMatcherTests.cs @@ -101,7 +101,7 @@ public class JsonMatcherTests public void JsonMatcher_IsMatch_ByteArray() { // Assign - var bytes = EmptyArray.Value; + var bytes = new byte[0]; var matcher = new JsonMatcher(""); // Act diff --git a/test/WireMock.Net.Tests/Matchers/JsonPartialMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JsonPartialMatcherTests.cs index 7b3802d1..62bb961b 100644 --- a/test/WireMock.Net.Tests/Matchers/JsonPartialMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/JsonPartialMatcherTests.cs @@ -79,7 +79,7 @@ public class JsonPartialMatcherTests public void JsonPartialMatcher_IsMatch_ByteArray() { // Assign - var bytes = EmptyArray.Value; + var bytes = new byte[0]; var matcher = new JsonPartialMatcher(""); // Act diff --git a/test/WireMock.Net.Tests/Matchers/JsonPartialWildcardMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JsonPartialWildcardMatcherTests.cs index 3bf67734..57a38710 100644 --- a/test/WireMock.Net.Tests/Matchers/JsonPartialWildcardMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/JsonPartialWildcardMatcherTests.cs @@ -79,7 +79,7 @@ public class JsonPartialWildcardMatcherTests public void JsonPartialWildcardMatcher_IsMatch_ByteArray() { // Assign - var bytes = EmptyArray.Value; + var bytes = new byte[0]; var matcher = new JsonPartialWildcardMatcher(""); // Act diff --git a/test/WireMock.Net.Tests/Matchers/JsonPathMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/JsonPathMatcherTests.cs index 53aa8ecb..c6a04044 100644 --- a/test/WireMock.Net.Tests/Matchers/JsonPathMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/JsonPathMatcherTests.cs @@ -41,7 +41,7 @@ public class JsonPathMatcherTests public void JsonPathMatcher_IsMatch_ByteArray() { // Arrange - var bytes = EmptyArray.Value; + var bytes = new byte[0]; var matcher = new JsonPathMatcher(""); // Act diff --git a/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs b/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs index e9e4ade4..2f9e02e3 100644 --- a/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs +++ b/test/WireMock.Net.Tests/Matchers/MimePartMatcherTests.cs @@ -2,9 +2,7 @@ #if MIMEKIT using System; -using System.Linq; using FluentAssertions; -using MimeKit; using WireMock.Matchers; using WireMock.Util; using Xunit; @@ -13,40 +11,44 @@ namespace WireMock.Net.Tests.Matchers; public class MimePartMatcherTests { - private const string TestMultiPart = @"From: -Date: Sun, 23 Jul 2023 16:13:13 +0200 -Subject: -Message-Id: -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary=""=-5XgmpXt0XOfzdtcgNJc2ZQ=="" + private static readonly IMimeKitUtils MimeKitUtils = new MimeKitUtils(); ---=-5XgmpXt0XOfzdtcgNJc2ZQ== -Content-Type: text/plain; charset=utf-8 + private const string TestMultiPart = + """ + From: + Date: Sun, 23 Jul 2023 16:13:13 +0200 + Subject: + Message-Id: + MIME-Version: 1.0 + Content-Type: multipart/mixed; boundary="=-5XgmpXt0XOfzdtcgNJc2ZQ==" -This is some plain text ---=-5XgmpXt0XOfzdtcgNJc2ZQ== -Content-Type: text/json; charset=utf-8 + --=-5XgmpXt0XOfzdtcgNJc2ZQ== + Content-Type: text/plain; charset=utf-8 -{ - ""Key"": ""Value"" - } ---=-5XgmpXt0XOfzdtcgNJc2ZQ== -Content-Type: image/png; name=image.png -Content-Disposition: attachment; filename=image.png -Content-Transfer-Encoding: base64 + This is some plain text + --=-5XgmpXt0XOfzdtcgNJc2ZQ== + Content-Type: text/json; charset=utf-8 -iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjl -AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC + { + "Key": "Value" + } + --=-5XgmpXt0XOfzdtcgNJc2ZQ== + Content-Type: image/png; name=image.png + Content-Disposition: attachment; filename=image.png + Content-Transfer-Encoding: base64 ---=-5XgmpXt0XOfzdtcgNJc2ZQ==-- -"; + iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjl + AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC + + --=-5XgmpXt0XOfzdtcgNJc2ZQ==-- + """; [Fact] public void MimePartMatcher_IsMatch_Part_TextPlain() { // Arrange - var message = MimeMessage.Load(StreamUtils.CreateStream(TestMultiPart)); - var part = (MimePart)message.BodyParts.ToArray()[0]; + var message = MimeKitUtils.LoadFromStream(StreamUtils.CreateStream(TestMultiPart)); + var part = MimeKitUtils.GetBodyParts(message)[0]; // Act var contentTypeMatcher = new ContentTypeMatcher("text/plain"); @@ -64,8 +66,8 @@ AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC public void MimePartMatcher_IsMatch_Part_TextJson() { // Arrange - var message = MimeMessage.Load(StreamUtils.CreateStream(TestMultiPart)); - var part = (MimePart)message.BodyParts.ToArray()[1]; + var message = MimeKitUtils.LoadFromStream(StreamUtils.CreateStream(TestMultiPart)); + var part = MimeKitUtils.GetBodyParts(message)[1]; // Act var contentTypeMatcher = new ContentTypeMatcher("text/json"); @@ -82,8 +84,8 @@ AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC public void MimePartMatcher_IsMatch_Part_ImagePng() { // Arrange - var message = MimeMessage.Load(StreamUtils.CreateStream(TestMultiPart)); - var part = (MimePart)message.BodyParts.ToArray()[2]; + var message = MimeKitUtils.LoadFromStream(StreamUtils.CreateStream(TestMultiPart)); + var part = MimeKitUtils.GetBodyParts(message)[2]; // Act var contentTypeMatcher = new ContentTypeMatcher("image/png"); diff --git a/test/WireMock.Net.Tests/Owin/Mappers/OwinResponseMapperTests.cs b/test/WireMock.Net.Tests/Owin/Mappers/OwinResponseMapperTests.cs index 73674d98..4060938a 100644 --- a/test/WireMock.Net.Tests/Owin/Mappers/OwinResponseMapperTests.cs +++ b/test/WireMock.Net.Tests/Owin/Mappers/OwinResponseMapperTests.cs @@ -17,11 +17,11 @@ using WireMock.Owin; #if NET452 using Microsoft.Owin; using IResponse = Microsoft.Owin.IOwinResponse; -using Response = Microsoft.Owin.OwinResponse; +// using Response = Microsoft.Owin.OwinResponse; #else using Microsoft.AspNetCore.Http; using IResponse = Microsoft.AspNetCore.Http.HttpResponse; -using Response = Microsoft.AspNetCore.Http.HttpResponse; +// using Response = Microsoft.AspNetCore.Http.HttpResponse; using Microsoft.Extensions.Primitives; #endif @@ -273,7 +273,7 @@ public class OwinResponseMapperTests await _sut.MapAsync(responseMessage, _responseMock.Object).ConfigureAwait(false); // Assert - _stream.Verify(s => s.WriteAsync(EmptyArray.Value, 0, 0, It.IsAny()), Times.Once); + _stream.Verify(s => s.WriteAsync(new byte[0], 0, 0, It.IsAny()), Times.Once); } [Theory] diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs index 26489afd..36e2bda2 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageMultiPartMatcher.cs @@ -164,12 +164,12 @@ AAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC var headers = new Dictionary { - { "Content-Type", new[] { @"multipart/mixed; boundary=""=-5XgmpXt0XOfzdtcgNJc2ZQ==""" } } + { "Content-Type", [ @"multipart/mixed; boundary=""=-5XgmpXt0XOfzdtcgNJc2ZQ==""" ] } }; var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", body, headers); - var matchers = new IMatcher?[] { matcher1, matcher2, matcher3 } - .Where(m => m is not null) - .ToArray(); + var matchers = new[] { matcher1, matcher2, matcher3 } + .OfType() + .ToArray(); var matcher = new RequestMessageMultiPartMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers!); diff --git a/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs b/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs index 2a2c5c94..7d1accb7 100644 --- a/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs +++ b/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs @@ -24,42 +24,103 @@ public class TypeLoaderTests { } + public interface IDummyInterfaceWithImplementationUsedForStaticTest + { + } + + public class DummyClass1UsedForStaticTest : IDummyInterfaceWithImplementationUsedForStaticTest + { + public DummyClass1UsedForStaticTest(Counter counter) + { + counter.AddOne(); + } + } + + public class DummyClass2UsedForStaticTest : IDummyInterfaceWithImplementationUsedForStaticTest + { + public DummyClass2UsedForStaticTest(Counter counter) + { + counter.AddOne(); + } + } + + public class Counter + { + public int Value { get; private set; } + + public void AddOne() + { + Value++; + } + } + [Fact] - public void Load_ByInterface() + public void LoadNewInstance() { // Act AnyOf pattern = "x"; - var result = TypeLoader.Load(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern); + var result = TypeLoader.LoadNewInstance(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern); // Assert result.Should().NotBeNull(); } [Fact] - public void Load_ByInterfaceAndFullName() + public void LoadNewInstanceByFullName() { // Act - var result = TypeLoader.LoadByFullName(typeof(DummyClass).FullName!); + var result = TypeLoader.LoadNewInstanceByFullName(typeof(DummyClass).FullName!); // Assert result.Should().BeOfType(); } [Fact] - public void Load_ByInterface_ButNoImplementationFoundForInterface_ThrowsException() + public void LoadStaticInstance_ShouldOnlyCreateInstanceOnce() + { + // Arrange + var counter = new Counter(); + + // Act + var result = TypeLoader.LoadStaticInstance(counter); + TypeLoader.LoadStaticInstance(counter); + + // Assert + result.Should().BeOfType(); + counter.Value.Should().Be(1); + } + + [Fact] + public void LoadStaticInstanceByFullName_ShouldOnlyCreateInstanceOnce() + { + // Arrange + var counter = new Counter(); + var fullName = typeof(DummyClass2UsedForStaticTest).FullName!; + + // Act + var result = TypeLoader.LoadStaticInstanceByFullName(fullName, counter); + TypeLoader.LoadStaticInstanceByFullName(fullName, counter); + + // Assert + result.Should().BeOfType(); + counter.Value.Should().Be(1); + } + + [Fact] + public void LoadNewInstance_ButNoImplementationFoundForInterface_ThrowsException() { // Act - Action a = () => TypeLoader.Load(); + Action a = () => TypeLoader.LoadNewInstance(); // Assert a.Should().Throw().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Util.TypeLoaderTests+IDummyInterfaceNoImplementation'."); } [Fact] - public void Load_ByInterfaceAndFullName_ButNoImplementationFoundForInterface_ThrowsException() + public void LoadNewInstanceByFullName_ButNoImplementationFoundForInterface_ThrowsException() { // Act - Action a = () => TypeLoader.LoadByFullName("xyz"); + Action a = () => TypeLoader.LoadNewInstanceByFullName("xyz"); // Assert a.Should().Throw().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Util.TypeLoaderTests+IDummyInterfaceWithImplementation' and has FullName 'xyz'."); diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index a0929ccb..e097a4a9 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -1,8 +1,8 @@ - + Stef Heyenrath - net452;net461;netcoreapp3.1;net6.0;net8.0 + net452;net461;net6.0;net8.0 enable false full @@ -51,7 +51,6 @@ --> - @@ -73,7 +72,6 @@ -