From d2a1d0f069c7572511881488d9a7aaf1489767c5 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 11 Aug 2022 10:57:33 +0200 Subject: [PATCH] Fix WithBody when using Pact and added more nullable annotations (#783) * More nullable annotations * . * . * FIX * pact * . * p * xxx * ... * auth * array * ... --- WireMock.Net Solution.sln.DotSettings | 1 + .../Admin/Mappings/WebProxyModel.cs | 35 +- .../Admin/Mappings/WebhookRequestModel.cs | 76 +- .../IRequestMessage.cs | 4 +- .../IResponseMessage.cs | 89 +- .../Logging/ILogEntry.cs | 83 +- .../Models/IWebhook.cs | 17 +- .../Matchers/CSharpCodeMatcher.cs | 2 +- .../AzureADAuthenticationMatcher.cs | 100 ++- .../BasicAuthenticationMatcher.cs | 19 +- src/WireMock.Net/Compatibility/EmptyArray.cs | 11 + src/WireMock.Net/Compatibility/WebProxy.cs | 38 +- .../Extensions/AnyOfExtensions.cs | 28 +- .../Http/ByteArrayContentHelper.cs | 42 +- src/WireMock.Net/Http/HttpKnownHeaderNames.cs | 221 +++-- .../Http/HttpRequestMessageHelper.cs | 140 ++- src/WireMock.Net/Http/HttpRequestMethods.cs | 31 +- src/WireMock.Net/Http/StringContentHelper.cs | 36 +- src/WireMock.Net/Http/WebhookSender.cs | 116 ++- .../HttpsCertificate/CertificateLoader.cs | 139 ++- .../PublicCertificateHelper.cs | 29 +- src/WireMock.Net/IMapping.cs | 2 +- src/WireMock.Net/Logging/LogEntry.cs | 51 +- src/WireMock.Net/Mapping.cs | 30 +- .../Matchers/ContentTypeMatcher.cs | 6 +- .../Matchers/ICSharpCodeMatcher.cs | 17 +- .../Matchers/IIgnoreCaseMatcher.cs | 19 +- src/WireMock.Net/Matchers/IMatcher.cs | 33 +- src/WireMock.Net/Matchers/IObjectMatcher.cs | 21 +- src/WireMock.Net/Matchers/IValueMatcher.cs | 21 +- src/WireMock.Net/Matchers/JmesPathMatcher.cs | 211 +++-- src/WireMock.Net/Matchers/JsonMatcher.cs | 2 +- src/WireMock.Net/Matchers/LinqMatcher.cs | 12 +- src/WireMock.Net/Matchers/MatchBehaviour.cs | 25 +- .../Matchers/MatchBehaviourHelper.cs | 45 +- .../Matchers/Request/CompositeMatcherType.cs | 25 +- .../Matchers/Request/RequestMatchResult.cs | 87 +- .../Request/RequestMessageCompositeMatcher.cs | 67 +- .../Request/RequestMessageCookieMatcher.cs | 201 +++-- src/WireMock.Net/Owin/AspNetCoreSelfHost.cs | 5 +- .../Owin/Mappers/OwinResponseMapper.cs | 6 +- .../Pact/Models/V2/Interaction.cs | 8 +- src/WireMock.Net/Plugin/PluginLoader.cs | 69 +- .../RegularExpressions/RegexExtended.cs | 2 +- src/WireMock.Net/RequestMessage.cs | 4 +- .../ResponseBuilders/BodyDestinationFormat.cs | 41 +- .../ResponseBuilders/IBodyResponseBuilder.cs | 117 ++- .../ICallbackResponseBuilder.cs | 33 +- .../ResponseBuilders/IDelayResponseBuilder.cs | 47 +- .../ResponseBuilders/IFaultRequestBuilder.cs | 25 +- .../IHeadersResponseBuilder.cs | 1 - .../ResponseBuilders/IProxyResponseBuilder.cs | 35 +- .../ResponseBuilders/IResponseBuilder.cs | 13 +- .../IStatusCodeResponseBuilder.cs | 73 +- .../ITransformResponseBuilder.cs | 51 +- .../ResponseBuilders/Response.WithCallback.cs | 88 +- .../ResponseBuilders/Response.WithFault.cs | 21 +- .../ResponseBuilders/Response.WithProxy.cs | 2 +- src/WireMock.Net/ResponseBuilders/Response.cs | 829 +++++++++--------- src/WireMock.Net/ResponseMessage.cs | 81 +- .../DynamicAsyncResponseProvider.cs | 23 +- .../DynamicResponseProvider.cs | 25 +- .../ResponseProviders/IResponseProvider.cs | 24 +- .../ProxyAsyncResponseProvider.cs | 27 +- .../Serialization/LogEntryMapper.cs | 2 +- .../Serialization/MappingConverter.cs | 8 +- .../Serialization/MappingToFileSaver.cs | 5 +- .../Serialization/MatcherMapper.cs | 2 +- src/WireMock.Net/Serialization/PactMapper.cs | 34 +- .../Serialization/WebhookMapper.cs | 173 ++-- .../Server/WireMockServer.Admin.cs | 42 +- .../Server/WireMockServer.ConvertMapping.cs | 2 +- .../Settings/SimpleCommandLineParser.cs | 156 ++-- src/WireMock.Net/Settings/WebhookSettings.cs | 13 +- .../Settings/WireMockServerSettings.cs | 6 + .../Settings/WireMockServerSettingsParser.cs | 220 ++--- .../Util/EnhancedFileSystemWatcher.cs | 26 +- .../Util/HttpStatusRangeParser.cs | 2 +- src/WireMock.Net/Util/JsonUtils.cs | 36 +- src/WireMock.Net/Util/PathUtils.cs | 61 +- src/WireMock.Net/Util/SystemUtils.cs | 2 +- src/WireMock.Net/WireMock.Net.csproj | 5 + test/WireMock.Net.Tests/Pact/PactTests.cs | 62 +- .../Settings/SimpleCommandLineParserTests.cs | 159 ++-- .../WireMock.Net.Tests/Util/JsonUtilsTests.cs | 30 +- .../WireMock.Net.Tests/Util/PathUtilsTests.cs | 63 +- .../WireMock.Net.Tests/Util/PortUtilsTests.cs | 142 +-- 87 files changed, 2578 insertions(+), 2455 deletions(-) create mode 100644 src/WireMock.Net/Compatibility/EmptyArray.cs diff --git a/WireMock.Net Solution.sln.DotSettings b/WireMock.Net Solution.sln.DotSettings index e6147e2f..a4b9b087 100644 --- a/WireMock.Net Solution.sln.DotSettings +++ b/WireMock.Net Solution.sln.DotSettings @@ -25,6 +25,7 @@ True True True + True True True \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/WebProxyModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/WebProxyModel.cs index 800474cf..cba3870a 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/WebProxyModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/WebProxyModel.cs @@ -1,24 +1,23 @@ -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// WebProxy settings +/// +[FluentBuilder.AutoGenerateBuilder] +public class WebProxyModel { /// - /// WebProxy settings + /// A string instance that contains the address of the proxy server. /// - [FluentBuilder.AutoGenerateBuilder] - public class WebProxyModel - { - /// - /// A string instance that contains the address of the proxy server. - /// - public string Address { get; set; } + public string Address { get; set; } = null!; - /// - /// The user name associated with the credentials. - /// - public string UserName { get; set; } + /// + /// The user name associated with the credentials. [optional] + /// + public string? UserName { get; set; } - /// - /// The password for the user name associated with the credentials. - /// - public string Password { get; set; } - } + /// + /// The password for the user name associated with the credentials. [optional] + /// + public string? Password { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs index 5dd749af..021f7023 100644 --- a/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs @@ -1,52 +1,50 @@ using System.Collections.Generic; -using WireMock.Types; -namespace WireMock.Admin.Mappings +namespace WireMock.Admin.Mappings; + +/// +/// RequestModel +/// +[FluentBuilder.AutoGenerateBuilder] +public class WebhookRequestModel { /// - /// RequestModel + /// Gets or sets the Url. /// - [FluentBuilder.AutoGenerateBuilder] - public class WebhookRequestModel - { - /// - /// Gets or sets the Url. - /// - public string Url { get; set; } + public string Url { get; set; } - /// - /// The method - /// - public string Method { get; set; } + /// + /// The method + /// + public string Method { get; set; } - /// - /// Gets or sets the headers. - /// - public IDictionary? Headers { get; set; } + /// + /// Gets or sets the headers. + /// + public IDictionary? Headers { get; set; } - /// - /// Gets or sets the body. - /// - public string? Body { get; set; } + /// + /// Gets or sets the body. + /// + public string? Body { get; set; } - /// - /// Gets or sets the body (as JSON object). - /// - public object? BodyAsJson { get; set; } + /// + /// Gets or sets the body (as JSON object). + /// + public object? BodyAsJson { get; set; } - /// - /// Use ResponseMessage Transformer. - /// - public bool? UseTransformer { get; set; } + /// + /// Use ResponseMessage Transformer. + /// + public bool? UseTransformer { get; set; } - /// - /// Gets the type of the transformer. - /// - public string TransformerType { get; set; } + /// + /// Gets the type of the transformer. + /// + public string? TransformerType { get; set; } - /// - /// The ReplaceNodeOptions to use when transforming a JSON node. - /// - public string TransformerReplaceNodeOptions { get; set; } - } + /// + /// The ReplaceNodeOptions to use when transforming a JSON node. + /// + public string? TransformerReplaceNodeOptions { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs index d7a0d301..ca80ec89 100644 --- a/src/WireMock.Net.Abstractions/IRequestMessage.cs +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -63,12 +63,12 @@ public interface IRequestMessage /// /// Gets the headers. /// - IDictionary> Headers { get; } + IDictionary>? Headers { get; } /// /// Gets the cookies. /// - IDictionary Cookies { get; } + IDictionary? Cookies { get; } /// /// Gets the query. diff --git a/src/WireMock.Net.Abstractions/IResponseMessage.cs b/src/WireMock.Net.Abstractions/IResponseMessage.cs index 467edc88..b5faa4cc 100644 --- a/src/WireMock.Net.Abstractions/IResponseMessage.cs +++ b/src/WireMock.Net.Abstractions/IResponseMessage.cs @@ -3,60 +3,59 @@ using WireMock.ResponseBuilders; using WireMock.Types; using WireMock.Util; -namespace WireMock +namespace WireMock; + +/// +/// IResponseMessage +/// +public interface IResponseMessage { /// - /// IResponseMessage + /// The Body. /// - public interface IResponseMessage - { - /// - /// The Body. - /// - IBodyData? BodyData { get; } + IBodyData? BodyData { get; } - /// - /// Gets the body destination (SameAsSource, String or Bytes). - /// - string BodyDestination { get; } + /// + /// Gets the body destination (Null, SameAsSource, String or Bytes). + /// + string? BodyDestination { get; } - /// - /// Gets or sets the body. - /// - string BodyOriginal { get; } + /// + /// Gets or sets the body. + /// + string? BodyOriginal { get; } - /// - /// Gets the Fault percentage. - /// - double? FaultPercentage { get; } + /// + /// Gets the Fault percentage. + /// + double? FaultPercentage { get; } - /// - /// The FaultType. - /// - FaultType FaultType { get; } + /// + /// The FaultType. + /// + FaultType FaultType { get; } - /// - /// Gets the headers. - /// - IDictionary>? Headers { get; } + /// + /// Gets the headers. + /// + IDictionary>? Headers { get; } - /// - /// Gets or sets the status code. - /// - object StatusCode { get; } + /// + /// Gets or sets the status code. + /// + object? StatusCode { get; } - /// - /// Adds the header. - /// - /// The name. - /// The value. - void AddHeader(string name, string value); + /// + /// Adds the header. + /// + /// The name. + /// The value. + void AddHeader(string name, string value); - /// - /// Adds the header. - /// - /// The name. - /// The values. - void AddHeader(string name, params string[] values); - } + /// + /// Adds the header. + /// + /// The name. + /// The values. + void AddHeader(string name, params string[] values); } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Logging/ILogEntry.cs b/src/WireMock.Net.Abstractions/Logging/ILogEntry.cs index 9e5559a6..79f5290b 100644 --- a/src/WireMock.Net.Abstractions/Logging/ILogEntry.cs +++ b/src/WireMock.Net.Abstractions/Logging/ILogEntry.cs @@ -1,56 +1,55 @@ -using System; +using System; using WireMock.Matchers.Request; -namespace WireMock.Logging +namespace WireMock.Logging; + +/// +/// ILogEntry +/// +public interface ILogEntry { /// - /// ILogEntry + /// Gets the unique identifier. /// - public interface ILogEntry - { - /// - /// Gets the unique identifier. - /// - Guid Guid { get; } + Guid Guid { get; } - /// - /// Gets the mapping unique identifier. - /// - Guid? MappingGuid { get; } + /// + /// Gets the mapping unique identifier. + /// + Guid? MappingGuid { get; } - /// - /// Gets the mapping unique title. - /// - string MappingTitle { get; } + /// + /// Gets the mapping unique title. + /// + string? MappingTitle { get; } - /// - /// Gets the partial mapping unique identifier. - /// - Guid? PartialMappingGuid { get; } + /// + /// Gets the partial mapping unique identifier. + /// + Guid? PartialMappingGuid { get; } - /// - /// Gets the partial mapping unique title. - /// - string PartialMappingTitle { get; } + /// + /// Gets the partial mapping unique title. + /// + string? PartialMappingTitle { get; } - /// - /// Gets the partial match result. - /// - IRequestMatchResult PartialMatchResult { get; } + /// + /// Gets the partial match result. + /// + IRequestMatchResult PartialMatchResult { get; } - /// - /// Gets the request match result. - /// - IRequestMatchResult RequestMatchResult { get; } + /// + /// Gets the request match result. + /// + IRequestMatchResult RequestMatchResult { get; } - /// - /// Gets the request message. - /// - IRequestMessage RequestMessage { get; } + /// + /// Gets the request message. + /// + IRequestMessage RequestMessage { get; } - /// - /// Gets the response message. - /// - IResponseMessage ResponseMessage { get; } - } + /// + /// Gets the response message. + /// + IResponseMessage ResponseMessage { get; } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Models/IWebhook.cs b/src/WireMock.Net.Abstractions/Models/IWebhook.cs index a4838c55..66ad9c83 100644 --- a/src/WireMock.Net.Abstractions/Models/IWebhook.cs +++ b/src/WireMock.Net.Abstractions/Models/IWebhook.cs @@ -1,13 +1,12 @@ -namespace WireMock.Models +namespace WireMock.Models; + +/// +/// IWebhook +/// +public interface IWebhook { /// - /// IWebhook + /// Request /// - public interface IWebhook - { - /// - /// Request - /// - IWebhookRequest Request { get; set; } - } + IWebhookRequest Request { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs index d8eb739d..c50acd20 100644 --- a/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs +++ b/src/WireMock.Net.Matchers.CSharpCode/Matchers/CSharpCodeMatcher.cs @@ -59,7 +59,7 @@ internal class CSharpCodeMatcher : ICSharpCodeMatcher MatchOperator = matchOperator; } - public double IsMatch(string input) + public double IsMatch(string? input) { return IsMatchInternal(input); } diff --git a/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs b/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs index 46b56705..e81b8250 100644 --- a/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs +++ b/src/WireMock.Net/Authentication/AzureADAuthenticationMatcher.cs @@ -1,4 +1,5 @@ #if !NETSTANDARD1_3 +using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Text.RegularExpressions; @@ -6,67 +7,72 @@ using AnyOfTypes; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; +using Stef.Validation; using WireMock.Matchers; using WireMock.Models; -namespace WireMock.Authentication +namespace WireMock.Authentication; + +/// +/// https://www.c-sharpcorner.com/article/how-to-validate-azure-ad-token-using-console-application/ +/// https://stackoverflow.com/questions/38684865/validation-of-an-azure-ad-bearer-token-in-a-console-application +/// +internal class AzureADAuthenticationMatcher : IStringMatcher { - /// - /// https://www.c-sharpcorner.com/article/how-to-validate-azure-ad-token-using-console-application/ - /// https://stackoverflow.com/questions/38684865/validation-of-an-azure-ad-bearer-token-in-a-console-application - /// - internal class AzureADAuthenticationMatcher : IStringMatcher + private const string BearerPrefix = "Bearer "; + + private readonly string _audience; + private readonly string _stsDiscoveryEndpoint; + + public AzureADAuthenticationMatcher(string tenant, string audience) { - private const string BearerPrefix = "Bearer "; + _audience = Guard.NotNullOrEmpty(audience); + _stsDiscoveryEndpoint = string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", Guard.NotNullOrEmpty(tenant)); + } - private readonly string _audience; - private readonly string _stsDiscoveryEndpoint; + public string Name => nameof(AzureADAuthenticationMatcher); - public AzureADAuthenticationMatcher(string tenant, string audience) + public MatchBehaviour MatchBehaviour => MatchBehaviour.AcceptOnMatch; + + public bool ThrowException => false; + + public AnyOf[] GetPatterns() + { + return EmptyArray>.Value; + } + + public MatchOperator MatchOperator { get; } = MatchOperator.Or; + + public double IsMatch(string? input) + { + if (string.IsNullOrEmpty(input)) { - _audience = audience; - _stsDiscoveryEndpoint = string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}/.well-known/openid-configuration", tenant); + return MatchScores.Mismatch; } - public string Name => nameof(AzureADAuthenticationMatcher); + var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase); - public MatchBehaviour MatchBehaviour => MatchBehaviour.AcceptOnMatch; - - public bool ThrowException => false; - - public AnyOf[] GetPatterns() + try { - return new AnyOf[0]; + var configManager = new ConfigurationManager(_stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever()); + var config = configManager.GetConfigurationAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + + var validationParameters = new TokenValidationParameters + { + ValidAudience = _audience, + ValidIssuer = config.Issuer, + IssuerSigningKeys = config.SigningKeys, + ValidateLifetime = true + }; + + // Throws an Exception as the token is invalid (expired, invalid-formatted, etc.) + new JwtSecurityTokenHandler().ValidateToken(token, validationParameters, out var _); + + return MatchScores.Perfect; } - - public MatchOperator MatchOperator { get; } = MatchOperator.Or; - - public double IsMatch(string input) + catch { - var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase); - - try - { - var configManager = new ConfigurationManager(_stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever()); - var config = configManager.GetConfigurationAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - - var validationParameters = new TokenValidationParameters - { - ValidAudience = _audience, - ValidIssuer = config.Issuer, - IssuerSigningKeys = config.SigningKeys, - ValidateLifetime = true - }; - - // Throws an Exception as the token is invalid (expired, invalid-formatted, etc.) - new JwtSecurityTokenHandler().ValidateToken(token, validationParameters, out var _); - - return MatchScores.Perfect; - } - catch - { - return MatchScores.Mismatch; - } + return MatchScores.Mismatch; } } } diff --git a/src/WireMock.Net/Authentication/BasicAuthenticationMatcher.cs b/src/WireMock.Net/Authentication/BasicAuthenticationMatcher.cs index 93db0295..d80a0360 100644 --- a/src/WireMock.Net/Authentication/BasicAuthenticationMatcher.cs +++ b/src/WireMock.Net/Authentication/BasicAuthenticationMatcher.cs @@ -2,19 +2,18 @@ using System; using System.Text; using WireMock.Matchers; -namespace WireMock.Authentication +namespace WireMock.Authentication; + +internal class BasicAuthenticationMatcher : RegexMatcher { - internal class BasicAuthenticationMatcher : RegexMatcher + public BasicAuthenticationMatcher(string username, string password) : base(BuildPattern(username, password)) { - public BasicAuthenticationMatcher(string username, string password) : base(BuildPattern(username, password)) - { - } + } - public override string Name => nameof(BasicAuthenticationMatcher); + public override string Name => nameof(BasicAuthenticationMatcher); - private static string BuildPattern(string username, string password) - { - return "^(?i)BASIC " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)) + "$"; - } + private static string BuildPattern(string username, string password) + { + return "^(?i)BASIC " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password)) + "$"; } } \ No newline at end of file diff --git a/src/WireMock.Net/Compatibility/EmptyArray.cs b/src/WireMock.Net/Compatibility/EmptyArray.cs new file mode 100644 index 00000000..125e2212 --- /dev/null +++ b/src/WireMock.Net/Compatibility/EmptyArray.cs @@ -0,0 +1,11 @@ +// 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/Compatibility/WebProxy.cs b/src/WireMock.Net/Compatibility/WebProxy.cs index 538c9c14..dcff2b07 100644 --- a/src/WireMock.Net/Compatibility/WebProxy.cs +++ b/src/WireMock.Net/Compatibility/WebProxy.cs @@ -1,28 +1,26 @@ -#if NETSTANDARD1_3 -using System; -using System.Net; +#if NETSTANDARD1_3 -namespace System.Net +// ReSharper disable once CheckNamespace +namespace System.Net; + +internal class WebProxy : IWebProxy { - internal class WebProxy : IWebProxy + private readonly string _proxy; + public ICredentials? Credentials { get; set; } + + public WebProxy(string proxy) { - private readonly string _proxy; - public ICredentials Credentials { get; set; } + _proxy = proxy; + } - public WebProxy(string proxy) - { - _proxy = proxy; - } + public Uri GetProxy(Uri destination) + { + return new Uri(_proxy); + } - public Uri GetProxy(Uri destination) - { - return new Uri(_proxy); - } - - public bool IsBypassed(Uri host) - { - return true; - } + public bool IsBypassed(Uri host) + { + return true; } } #endif \ No newline at end of file diff --git a/src/WireMock.Net/Extensions/AnyOfExtensions.cs b/src/WireMock.Net/Extensions/AnyOfExtensions.cs index fe4c4dff..41a75288 100644 --- a/src/WireMock.Net/Extensions/AnyOfExtensions.cs +++ b/src/WireMock.Net/Extensions/AnyOfExtensions.cs @@ -1,26 +1,24 @@ using System.Collections.Generic; using System.Linq; using AnyOfTypes; -using JetBrains.Annotations; using WireMock.Models; -namespace WireMock.Extensions +namespace WireMock.Extensions; + +internal static class AnyOfExtensions { - internal static class AnyOfExtensions + public static string GetPattern(this AnyOf value) { - public static string GetPattern([NotNull] this AnyOf value) - { - return value.IsFirst ? value.First : value.Second.Pattern; - } + return value.IsFirst ? value.First : value.Second.Pattern; + } - public static AnyOf[] ToAnyOfPatterns([NotNull] this IEnumerable patterns) - { - return patterns.Select(p => p.ToAnyOfPattern()).ToArray(); - } + public static AnyOf[] ToAnyOfPatterns(this IEnumerable patterns) + { + return patterns.Select(p => p.ToAnyOfPattern()).ToArray(); + } - public static AnyOf ToAnyOfPattern([CanBeNull] this string pattern) - { - return new AnyOf(pattern); - } + public static AnyOf ToAnyOfPattern(this string pattern) + { + return new AnyOf(pattern); } } \ No newline at end of file diff --git a/src/WireMock.Net/Http/ByteArrayContentHelper.cs b/src/WireMock.Net/Http/ByteArrayContentHelper.cs index 14dcc5ec..c0c5ae66 100644 --- a/src/WireMock.Net/Http/ByteArrayContentHelper.cs +++ b/src/WireMock.Net/Http/ByteArrayContentHelper.cs @@ -1,30 +1,28 @@ -using System.Net.Http; +using System.Net.Http; using System.Net.Http.Headers; -using JetBrains.Annotations; using Stef.Validation; -namespace WireMock.Http +namespace WireMock.Http; + +internal static class ByteArrayContentHelper { - internal static class ByteArrayContentHelper + /// + /// Creates a ByteArrayContent object. + /// + /// The byte[] content (cannot be null) + /// The ContentType (can be null) + /// ByteArrayContent + internal static ByteArrayContent Create(byte[] content, MediaTypeHeaderValue? contentType) { - /// - /// Creates a ByteArrayContent object. - /// - /// The byte[] content (cannot be null) - /// The ContentType (can be null) - /// ByteArrayContent - internal static ByteArrayContent Create([NotNull] byte[] content, [CanBeNull] MediaTypeHeaderValue contentType) + Guard.NotNull(content); + + var byteContent = new ByteArrayContent(content); + if (contentType != null) { - Guard.NotNull(content, nameof(content)); - - var byteContent = new ByteArrayContent(content); - if (contentType != null) - { - byteContent.Headers.Remove(HttpKnownHeaderNames.ContentType); - byteContent.Headers.ContentType = contentType; - } - - return byteContent; + byteContent.Headers.Remove(HttpKnownHeaderNames.ContentType); + byteContent.Headers.ContentType = contentType; } + + return byteContent; } -} +} \ No newline at end of file diff --git a/src/WireMock.Net/Http/HttpKnownHeaderNames.cs b/src/WireMock.Net/Http/HttpKnownHeaderNames.cs index 07c2975b..10e23867 100644 --- a/src/WireMock.Net/Http/HttpKnownHeaderNames.cs +++ b/src/WireMock.Net/Http/HttpKnownHeaderNames.cs @@ -1,122 +1,121 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; -namespace WireMock.Http +namespace WireMock.Http; + +/// +/// Copied from https://raw.githubusercontent.com/dotnet/corefx/master/src/Common/src/System/Net/HttpKnownHeaderNames.cs +/// +internal static class HttpKnownHeaderNames { - /// - /// Copied from https://raw.githubusercontent.com/dotnet/corefx/master/src/Common/src/System/Net/HttpKnownHeaderNames.cs - /// - internal static class HttpKnownHeaderNames + // https://docs.microsoft.com/en-us/dotnet/api/system.net.webheadercollection.isrestricted + private static readonly string[] RestrictedResponseHeaders = { - // https://docs.microsoft.com/en-us/dotnet/api/system.net.webheadercollection.isrestricted - private static readonly string[] RestrictedResponseHeaders = - { - Accept, - Connection, - ContentLength, - ContentType, - Date, // RFC1123Pattern - Expect, - Host, - IfModifiedSince, - Range, - Referer, - TransferEncoding, - UserAgent, - ProxyConnection - }; + Accept, + Connection, + ContentLength, + ContentType, + Date, // RFC1123Pattern + Expect, + Host, + IfModifiedSince, + Range, + Referer, + TransferEncoding, + UserAgent, + ProxyConnection + }; - /// Tests whether the specified HTTP header can be set for the response. - /// The header to test. - /// true if the header is restricted; otherwise, false. - public static bool IsRestrictedResponseHeader(string headerName) => RestrictedResponseHeaders.Contains(headerName, StringComparer.OrdinalIgnoreCase); + /// Tests whether the specified HTTP header can be set for the response. + /// The header to test. + /// true if the header is restricted; otherwise, false. + public static bool IsRestrictedResponseHeader(string headerName) => RestrictedResponseHeaders.Contains(headerName, StringComparer.OrdinalIgnoreCase); - public const string Accept = "Accept"; - public const string AcceptCharset = "Accept-Charset"; - public const string AcceptEncoding = "Accept-Encoding"; - public const string AcceptLanguage = "Accept-Language"; - public const string AcceptPatch = "Accept-Patch"; - public const string AcceptRanges = "Accept-Ranges"; - public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; - public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; - public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; - public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; - public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; - public const string AccessControlMaxAge = "Access-Control-Max-Age"; - public const string Age = "Age"; - public const string Allow = "Allow"; - public const string AltSvc = "Alt-Svc"; - public const string Authorization = "Authorization"; - public const string CacheControl = "Cache-Control"; - public const string Connection = "Connection"; - public const string ContentDisposition = "Content-Disposition"; - public const string ContentEncoding = "Content-Encoding"; - public const string ContentLanguage = "Content-Language"; - public const string ContentLength = "Content-Length"; - public const string ContentLocation = "Content-Location"; - public const string ContentMD5 = "Content-MD5"; - public const string ContentRange = "Content-Range"; - public const string ContentSecurityPolicy = "Content-Security-Policy"; - public const string ContentType = "Content-Type"; - public const string Cookie = "Cookie"; - public const string Cookie2 = "Cookie2"; - public const string Date = "Date"; - public const string ETag = "ETag"; - public const string Expect = "Expect"; - public const string Expires = "Expires"; - public const string From = "From"; - public const string Host = "Host"; - public const string IfMatch = "If-Match"; - public const string IfModifiedSince = "If-Modified-Since"; - public const string IfNoneMatch = "If-None-Match"; - public const string IfRange = "If-Range"; - public const string IfUnmodifiedSince = "If-Unmodified-Since"; - public const string KeepAlive = "Keep-Alive"; - public const string LastModified = "Last-Modified"; - public const string Link = "Link"; - public const string Location = "Location"; - public const string MaxForwards = "Max-Forwards"; - public const string Origin = "Origin"; - public const string P3P = "P3P"; - public const string Pragma = "Pragma"; - public const string ProxyAuthenticate = "Proxy-Authenticate"; - public const string ProxyAuthorization = "Proxy-Authorization"; - public const string ProxyConnection = "Proxy-Connection"; - public const string PublicKeyPins = "Public-Key-Pins"; - public const string Range = "Range"; - public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched. - public const string RetryAfter = "Retry-After"; - public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; - public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; - public const string SecWebSocketKey = "Sec-WebSocket-Key"; - public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; - public const string SecWebSocketVersion = "Sec-WebSocket-Version"; - public const string Server = "Server"; - public const string SetCookie = "Set-Cookie"; - public const string SetCookie2 = "Set-Cookie2"; - public const string StrictTransportSecurity = "Strict-Transport-Security"; - public const string TE = "TE"; - public const string TSV = "TSV"; - public const string Trailer = "Trailer"; - public const string TransferEncoding = "Transfer-Encoding"; - public const string Upgrade = "Upgrade"; - public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; - public const string UserAgent = "User-Agent"; - public const string Vary = "Vary"; - public const string Via = "Via"; - public const string WWWAuthenticate = "WWW-Authenticate"; - public const string Warning = "Warning"; - public const string XAspNetVersion = "X-AspNet-Version"; - public const string XContentDuration = "X-Content-Duration"; - public const string XContentTypeOptions = "X-Content-Type-Options"; - public const string XFrameOptions = "X-Frame-Options"; - public const string XMSEdgeRef = "X-MSEdge-Ref"; - public const string XPoweredBy = "X-Powered-By"; - public const string XRequestID = "X-Request-ID"; - public const string XUACompatible = "X-UA-Compatible"; - } + public const string Accept = "Accept"; + public const string AcceptCharset = "Accept-Charset"; + public const string AcceptEncoding = "Accept-Encoding"; + public const string AcceptLanguage = "Accept-Language"; + public const string AcceptPatch = "Accept-Patch"; + public const string AcceptRanges = "Accept-Ranges"; + public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; + public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; + public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; + public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; + public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; + public const string AccessControlMaxAge = "Access-Control-Max-Age"; + public const string Age = "Age"; + public const string Allow = "Allow"; + public const string AltSvc = "Alt-Svc"; + public const string Authorization = "Authorization"; + public const string CacheControl = "Cache-Control"; + public const string Connection = "Connection"; + public const string ContentDisposition = "Content-Disposition"; + public const string ContentEncoding = "Content-Encoding"; + public const string ContentLanguage = "Content-Language"; + public const string ContentLength = "Content-Length"; + public const string ContentLocation = "Content-Location"; + public const string ContentMD5 = "Content-MD5"; + public const string ContentRange = "Content-Range"; + public const string ContentSecurityPolicy = "Content-Security-Policy"; + public const string ContentType = "Content-Type"; + public const string Cookie = "Cookie"; + public const string Cookie2 = "Cookie2"; + public const string Date = "Date"; + public const string ETag = "ETag"; + public const string Expect = "Expect"; + public const string Expires = "Expires"; + public const string From = "From"; + public const string Host = "Host"; + public const string IfMatch = "If-Match"; + public const string IfModifiedSince = "If-Modified-Since"; + public const string IfNoneMatch = "If-None-Match"; + public const string IfRange = "If-Range"; + public const string IfUnmodifiedSince = "If-Unmodified-Since"; + public const string KeepAlive = "Keep-Alive"; + public const string LastModified = "Last-Modified"; + public const string Link = "Link"; + public const string Location = "Location"; + public const string MaxForwards = "Max-Forwards"; + public const string Origin = "Origin"; + public const string P3P = "P3P"; + public const string Pragma = "Pragma"; + public const string ProxyAuthenticate = "Proxy-Authenticate"; + public const string ProxyAuthorization = "Proxy-Authorization"; + public const string ProxyConnection = "Proxy-Connection"; + public const string PublicKeyPins = "Public-Key-Pins"; + public const string Range = "Range"; + public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched. + public const string RetryAfter = "Retry-After"; + public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; + public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; + public const string SecWebSocketKey = "Sec-WebSocket-Key"; + public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; + public const string SecWebSocketVersion = "Sec-WebSocket-Version"; + public const string Server = "Server"; + public const string SetCookie = "Set-Cookie"; + public const string SetCookie2 = "Set-Cookie2"; + public const string StrictTransportSecurity = "Strict-Transport-Security"; + public const string TE = "TE"; + public const string TSV = "TSV"; + public const string Trailer = "Trailer"; + public const string TransferEncoding = "Transfer-Encoding"; + public const string Upgrade = "Upgrade"; + public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; + public const string UserAgent = "User-Agent"; + public const string Vary = "Vary"; + public const string Via = "Via"; + public const string WWWAuthenticate = "WWW-Authenticate"; + public const string Warning = "Warning"; + public const string XAspNetVersion = "X-AspNet-Version"; + public const string XContentDuration = "X-Content-Duration"; + public const string XContentTypeOptions = "X-Content-Type-Options"; + public const string XFrameOptions = "X-Frame-Options"; + public const string XMSEdgeRef = "X-MSEdge-Ref"; + public const string XPoweredBy = "X-Powered-By"; + public const string XRequestID = "X-Request-ID"; + public const string XUACompatible = "X-UA-Compatible"; } \ No newline at end of file diff --git a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs index 596ef3b1..71428546 100644 --- a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs +++ b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs @@ -3,89 +3,87 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; -using JetBrains.Annotations; using Newtonsoft.Json; -using WireMock.Types; using Stef.Validation; +using WireMock.Types; -namespace WireMock.Http +namespace WireMock.Http; + +internal static class HttpRequestMessageHelper { - internal static class HttpRequestMessageHelper + internal static HttpRequestMessage Create(IRequestMessage requestMessage, string url) { - internal static HttpRequestMessage Create([NotNull] IRequestMessage requestMessage, [NotNull] string url) + Guard.NotNull(requestMessage); + Guard.NotNullOrEmpty(url); + + var httpRequestMessage = new HttpRequestMessage(new HttpMethod(requestMessage.Method), url); + + MediaTypeHeaderValue? contentType = null; + if (requestMessage.Headers != null && requestMessage.Headers.ContainsKey(HttpKnownHeaderNames.ContentType)) { - Guard.NotNull(requestMessage, nameof(requestMessage)); - Guard.NotNullOrEmpty(url, nameof(url)); + var value = requestMessage.Headers[HttpKnownHeaderNames.ContentType].FirstOrDefault(); + MediaTypeHeaderValue.TryParse(value, out contentType); + } - var httpRequestMessage = new HttpRequestMessage(new HttpMethod(requestMessage.Method), url); + switch (requestMessage.BodyData?.DetectedBodyType) + { + case BodyType.Bytes: + httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes, contentType); + break; - MediaTypeHeaderValue contentType = null; - if (requestMessage.Headers != null && requestMessage.Headers.ContainsKey(HttpKnownHeaderNames.ContentType)) - { - var value = requestMessage.Headers[HttpKnownHeaderNames.ContentType].FirstOrDefault(); - MediaTypeHeaderValue.TryParse(value, out contentType); - } + case BodyType.Json: + httpRequestMessage.Content = StringContentHelper.Create(JsonConvert.SerializeObject(requestMessage.BodyData.BodyAsJson), contentType); + break; - switch (requestMessage.BodyData?.DetectedBodyType) - { - case BodyType.Bytes: - httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes, contentType); - break; + case BodyType.String: + httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString, contentType); + break; + } - case BodyType.Json: - httpRequestMessage.Content = StringContentHelper.Create(JsonConvert.SerializeObject(requestMessage.BodyData.BodyAsJson), contentType); - break; - - case BodyType.String: - httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString, contentType); - break; - } - - // Overwrite the host header - httpRequestMessage.Headers.Host = new Uri(url).Authority; - - // Set other headers if present - if (requestMessage.Headers == null || requestMessage.Headers.Count == 0) - { - return httpRequestMessage; - } - - var excludeHeaders = new List { HttpKnownHeaderNames.Host, HttpKnownHeaderNames.ContentLength }; - if (contentType != null) - { - // Content-Type should be set on the content - excludeHeaders.Add(HttpKnownHeaderNames.ContentType); - } - - foreach (var header in requestMessage.Headers.Where(h => !excludeHeaders.Contains(h.Key, StringComparer.OrdinalIgnoreCase))) - { - // Skip if already added. We need to ToList() else calling httpRequestMessage.Headers.Contains() with a header starting with a ":" throws an exception. - if (httpRequestMessage.Headers.ToList().Any(h => string.Equals(h.Key, header.Key, StringComparison.OrdinalIgnoreCase))) - { - continue; - } - - // Skip if already added. We need to ToList() else calling httpRequestMessage.Content.Headers.Contains(...) with a header starting with a ":" throws an exception. - if (httpRequestMessage.Content != null && httpRequestMessage.Content.Headers.ToList().Any(h => string.Equals(h.Key, header.Key, StringComparison.OrdinalIgnoreCase))) - { - continue; - } - - // Try to add to request headers. If failed - try to add to content headers. If still fails, just ignore this header. - try - { - if (!httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value)) - { - httpRequestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - } - catch - { - // Just continue - } - } + // Overwrite the host header + httpRequestMessage.Headers.Host = new Uri(url).Authority; + // Set other headers if present + if (requestMessage.Headers == null || requestMessage.Headers.Count == 0) + { return httpRequestMessage; } + + var excludeHeaders = new List { HttpKnownHeaderNames.Host, HttpKnownHeaderNames.ContentLength }; + if (contentType != null) + { + // Content-Type should be set on the content + excludeHeaders.Add(HttpKnownHeaderNames.ContentType); + } + + foreach (var header in requestMessage.Headers.Where(h => !excludeHeaders.Contains(h.Key, StringComparer.OrdinalIgnoreCase))) + { + // Skip if already added. We need to ToList() else calling httpRequestMessage.Headers.Contains() with a header starting with a ":" throws an exception. + if (httpRequestMessage.Headers.ToList().Any(h => string.Equals(h.Key, header.Key, StringComparison.OrdinalIgnoreCase))) + { + continue; + } + + // Skip if already added. We need to ToList() else calling httpRequestMessage.Content.Headers.Contains(...) with a header starting with a ":" throws an exception. + if (httpRequestMessage.Content != null && httpRequestMessage.Content.Headers.ToList().Any(h => string.Equals(h.Key, header.Key, StringComparison.OrdinalIgnoreCase))) + { + continue; + } + + // Try to add to request headers. If failed - try to add to content headers. If still fails, just ignore this header. + try + { + if (!httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value)) + { + httpRequestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + } + catch + { + // Just continue + } + } + + return httpRequestMessage; } } \ No newline at end of file diff --git a/src/WireMock.Net/Http/HttpRequestMethods.cs b/src/WireMock.Net/Http/HttpRequestMethods.cs index 66e012c0..e33399db 100644 --- a/src/WireMock.Net/Http/HttpRequestMethods.cs +++ b/src/WireMock.Net/Http/HttpRequestMethods.cs @@ -1,18 +1,17 @@ -namespace WireMock.Http +namespace WireMock.Http; + +/// +/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods +/// +internal static class HttpRequestMethods { - /// - /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods - /// - internal static class HttpRequestMethods - { - public const string CONNECT = "CONNECT"; - public const string DELETE = "DELETE"; - public const string GET = "GET"; - public const string HEAD = "HEAD"; - public const string OPTIONS = "OPTIONS"; - public const string PATCH = "PATCH"; - public const string POST = "POST"; - public const string PUT = "PUT"; - public const string TRACE = "TRACE"; - } + public const string CONNECT = "CONNECT"; + public const string DELETE = "DELETE"; + public const string GET = "GET"; + public const string HEAD = "HEAD"; + public const string OPTIONS = "OPTIONS"; + public const string PATCH = "PATCH"; + public const string POST = "POST"; + public const string PUT = "PUT"; + public const string TRACE = "TRACE"; } \ No newline at end of file diff --git a/src/WireMock.Net/Http/StringContentHelper.cs b/src/WireMock.Net/Http/StringContentHelper.cs index 671ca593..a5c5dee9 100644 --- a/src/WireMock.Net/Http/StringContentHelper.cs +++ b/src/WireMock.Net/Http/StringContentHelper.cs @@ -1,25 +1,23 @@ -using System.Net.Http; +using System.Net.Http; using System.Net.Http.Headers; -using JetBrains.Annotations; using Stef.Validation; -namespace WireMock.Http -{ - internal static class StringContentHelper - { - /// - /// Creates a StringContent object. - /// - /// The string content (cannot be null) - /// The ContentType (can be null) - /// StringContent - internal static StringContent Create([NotNull] string content, [CanBeNull] MediaTypeHeaderValue contentType) - { - Guard.NotNull(content, nameof(content)); +namespace WireMock.Http; - var stringContent = new StringContent(content); - stringContent.Headers.ContentType = contentType; - return stringContent; - } +internal static class StringContentHelper +{ + /// + /// Creates a StringContent object. + /// + /// The string content (cannot be null) + /// The ContentType (can be null) + /// StringContent + internal static StringContent Create(string content, MediaTypeHeaderValue? contentType) + { + Guard.NotNull(content); + + var stringContent = new StringContent(content); + stringContent.Headers.ContentType = contentType; + return stringContent; } } \ No newline at end of file diff --git a/src/WireMock.Net/Http/WebhookSender.cs b/src/WireMock.Net/Http/WebhookSender.cs index cf3d9272..45a6e454 100644 --- a/src/WireMock.Net/Http/WebhookSender.cs +++ b/src/WireMock.Net/Http/WebhookSender.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using JetBrains.Annotations; +using Stef.Validation; using WireMock.Models; using WireMock.Settings; using WireMock.Transformers; @@ -11,75 +11,73 @@ using WireMock.Transformers.Handlebars; using WireMock.Transformers.Scriban; using WireMock.Types; using WireMock.Util; -using Stef.Validation; -namespace WireMock.Http +namespace WireMock.Http; + +internal class WebhookSender { - internal class WebhookSender + private const string ClientIp = "::1"; + + private readonly WireMockServerSettings _settings; + + public WebhookSender(WireMockServerSettings settings) { - private const string ClientIp = "::1"; + _settings = Guard.NotNull(settings); + } - private readonly WireMockServerSettings _settings; + public Task SendAsync(HttpClient client, IWebhookRequest request, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage) + { + Guard.NotNull(client); + Guard.NotNull(request); + Guard.NotNull(originalRequestMessage); + Guard.NotNull(originalResponseMessage); - public WebhookSender(WireMockServerSettings settings) + IBodyData? bodyData; + IDictionary>? headers; + if (request.UseTransformer == true) { - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - } - - public Task SendAsync(HttpClient client, IWebhookRequest request, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage) - { - Guard.NotNull(client); - Guard.NotNull(request); - Guard.NotNull(originalRequestMessage); - Guard.NotNull(originalResponseMessage); - - IBodyData? bodyData; - IDictionary>? headers; - if (request.UseTransformer == true) + ITransformer responseMessageTransformer; + switch (request.TransformerType) { - ITransformer responseMessageTransformer; - switch (request.TransformerType) - { - case TransformerType.Handlebars: - var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback); - responseMessageTransformer = new Transformer(factoryHandlebars); - break; + case TransformerType.Handlebars: + var factoryHandlebars = new HandlebarsContextFactory(_settings.FileSystemHandler, _settings.HandlebarsRegistrationCallback); + responseMessageTransformer = new Transformer(factoryHandlebars); + break; - case TransformerType.Scriban: - case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, request.TransformerType); - responseMessageTransformer = new Transformer(factoryDotLiquid); - break; + case TransformerType.Scriban: + case TransformerType.ScribanDotLiquid: + var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, request.TransformerType); + responseMessageTransformer = new Transformer(factoryDotLiquid); + break; - default: - throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported."); - } - - (bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers, request.TransformerReplaceNodeOptions); - } - else - { - bodyData = request.BodyData; - headers = request.Headers; + default: + throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported."); } - // Create RequestMessage - var requestMessage = new RequestMessage( - new UrlDetails(request.Url), - request.Method, - ClientIp, - bodyData, - headers?.ToDictionary(x => x.Key, x => x.Value.ToArray()) - ) - { - DateTime = DateTime.UtcNow - }; - - // Create HttpRequestMessage - var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, request.Url); - - // Call the URL - return client.SendAsync(httpRequestMessage); + (bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers, request.TransformerReplaceNodeOptions); } + else + { + bodyData = request.BodyData; + headers = request.Headers; + } + + // Create RequestMessage + var requestMessage = new RequestMessage( + new UrlDetails(request.Url), + request.Method, + ClientIp, + bodyData, + headers?.ToDictionary(x => x.Key, x => x.Value.ToArray()) + ) + { + DateTime = DateTime.UtcNow + }; + + // Create HttpRequestMessage + var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, request.Url); + + // Call the URL + return client.SendAsync(httpRequestMessage); } } \ No newline at end of file diff --git a/src/WireMock.Net/HttpsCertificate/CertificateLoader.cs b/src/WireMock.Net/HttpsCertificate/CertificateLoader.cs index b732b032..5fe48664 100644 --- a/src/WireMock.Net/HttpsCertificate/CertificateLoader.cs +++ b/src/WireMock.Net/HttpsCertificate/CertificateLoader.cs @@ -2,90 +2,40 @@ using System; using System.IO; using System.Security.Cryptography.X509Certificates; -namespace WireMock.HttpsCertificate +namespace WireMock.HttpsCertificate; + +internal static class CertificateLoader { - internal static class CertificateLoader + /// + /// Used by the WireMock.Net server + /// + public static X509Certificate2 LoadCertificate( + string storeName, + string storeLocation, + string thumbprintOrSubjectName, + string filePath, + string password, + string host) { - /// - /// Used by the WireMock.Net server - /// - public static X509Certificate2 LoadCertificate( - string storeName, - string storeLocation, - string thumbprintOrSubjectName, - string filePath, - string password, - string host) + if (!string.IsNullOrEmpty(storeName) && !string.IsNullOrEmpty(storeLocation)) { - if (!string.IsNullOrEmpty(storeName) && !string.IsNullOrEmpty(storeLocation)) - { - var thumbprintOrSubjectNameOrHost = thumbprintOrSubjectName ?? host; + var thumbprintOrSubjectNameOrHost = thumbprintOrSubjectName ?? host; - var certStore = new X509Store((StoreName)Enum.Parse(typeof(StoreName), storeName), (StoreLocation)Enum.Parse(typeof(StoreLocation), storeLocation)); - try - { - certStore.Open(OpenFlags.ReadOnly); - - // Attempt to find by Thumbprint first - var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectNameOrHost, false); - if (matchingCertificates.Count == 0) - { - // Fallback to SubjectName - matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectNameOrHost, false); - if (matchingCertificates.Count == 0) - { - // No certificates matched the search criteria. - throw new FileNotFoundException($"No Certificate found with in store '{storeName}', location '{storeLocation}' for Thumbprint or SubjectName '{thumbprintOrSubjectNameOrHost}'."); - } - } - - // Use the first matching certificate. - return matchingCertificates[0]; - } - finally - { -#if NETSTANDARD || NET46 - certStore.Dispose(); -#else - certStore.Close(); -#endif - } - } - - if (!string.IsNullOrEmpty(filePath) && !string.IsNullOrEmpty(password)) - { - return new X509Certificate2(filePath, password); - } - - if (!string.IsNullOrEmpty(filePath)) - { - return new X509Certificate2(filePath); - } - - throw new InvalidOperationException("X509StoreName and X509StoreLocation OR X509CertificateFilePath are mandatory. Note that X509CertificatePassword is optional."); - } - - /// - /// Used for Proxy - /// - public static X509Certificate2 LoadCertificate(string thumbprintOrSubjectName) - { - var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine); + var certStore = new X509Store((StoreName)Enum.Parse(typeof(StoreName), storeName), (StoreLocation)Enum.Parse(typeof(StoreLocation), storeLocation)); try { - // Certificate must be in the local machine store certStore.Open(OpenFlags.ReadOnly); // Attempt to find by Thumbprint first - var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectName, false); + var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectNameOrHost, false); if (matchingCertificates.Count == 0) { // Fallback to SubjectName - matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectName, false); + matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectNameOrHost, false); if (matchingCertificates.Count == 0) { // No certificates matched the search criteria. - throw new FileNotFoundException("No certificate found with specified Thumbprint or SubjectName.", thumbprintOrSubjectName); + throw new FileNotFoundException($"No Certificate found with in store '{storeName}', location '{storeLocation}' for Thumbprint or SubjectName '{thumbprintOrSubjectNameOrHost}'."); } } @@ -97,9 +47,58 @@ namespace WireMock.HttpsCertificate #if NETSTANDARD || NET46 certStore.Dispose(); #else - certStore.Close(); + certStore.Close(); #endif } } + + if (!string.IsNullOrEmpty(filePath) && !string.IsNullOrEmpty(password)) + { + return new X509Certificate2(filePath, password); + } + + if (!string.IsNullOrEmpty(filePath)) + { + return new X509Certificate2(filePath); + } + + throw new InvalidOperationException("X509StoreName and X509StoreLocation OR X509CertificateFilePath are mandatory. Note that X509CertificatePassword is optional."); + } + + /// + /// Used for Proxy + /// + public static X509Certificate2 LoadCertificate(string thumbprintOrSubjectName) + { + var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine); + try + { + // Certificate must be in the local machine store + certStore.Open(OpenFlags.ReadOnly); + + // Attempt to find by Thumbprint first + var matchingCertificates = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprintOrSubjectName, false); + if (matchingCertificates.Count == 0) + { + // Fallback to SubjectName + matchingCertificates = certStore.Certificates.Find(X509FindType.FindBySubjectName, thumbprintOrSubjectName, false); + if (matchingCertificates.Count == 0) + { + // No certificates matched the search criteria. + throw new FileNotFoundException("No certificate found with specified Thumbprint or SubjectName.", thumbprintOrSubjectName); + } + } + + // Use the first matching certificate. + return matchingCertificates[0]; + } + finally + { +#if NETSTANDARD || NET46 + certStore.Dispose(); +#else + certStore.Close(); +#endif + } } } \ No newline at end of file diff --git a/src/WireMock.Net/HttpsCertificate/PublicCertificateHelper.cs b/src/WireMock.Net/HttpsCertificate/PublicCertificateHelper.cs index 557470a2..a47630af 100644 --- a/src/WireMock.Net/HttpsCertificate/PublicCertificateHelper.cs +++ b/src/WireMock.Net/HttpsCertificate/PublicCertificateHelper.cs @@ -1,16 +1,16 @@ -using System; +using System; using System.Security.Cryptography.X509Certificates; -namespace WireMock.HttpsCertificate +namespace WireMock.HttpsCertificate; + +/// +/// Only used for NetStandard 1.3 +/// +internal static class PublicCertificateHelper { - /// - /// Only used for NetStandard 1.3 - /// - internal static class PublicCertificateHelper - { - // 1] Generate using https://www.pluralsight.com/blog/software-development/selfcert-create-a-self-signed-certificate-interactively-gui-or-programmatically-in-net - // 2] Converted to Base64 - private const string Data = @"MIIQMgIBAzCCD+4GCSqGSIb3DQEHAaCCD98Egg/bMIIP1zCCCogGCSqGSIb3DQEHAaCCCnkEggp1 + // 1] Generate using https://www.pluralsight.com/blog/software-development/selfcert-create-a-self-signed-certificate-interactively-gui-or-programmatically-in-net + // 2] Converted to Base64 + private const string Data = @"MIIQMgIBAzCCD+4GCSqGSIb3DQEHAaCCD98Egg/bMIIP1zCCCogGCSqGSIb3DQEHAaCCCnkEggp1 MIIKcTCCCm0GCyqGSIb3DQEMCgECoIIJfjCCCXowHAYKKoZIhvcNAQwBAzAOBAi1j9x1jTfUewIC B9AEgglYa48lP16+isiGEVT7zwN3XwaPwPOHZcQ7tRA/DA8LZnZbwU7XhtPObF5bZcHn4engX2An ISFpe2S5XJ7BfHmsGOO7Bxj6C2IcZIPTefvAd9vWE0WUAGN11SLhJ3fB/ZRt3Nys7JCJzywQCkYK @@ -85,10 +85,9 @@ TLNGa+UmMnPsnBjlAJ6l9VPsa4uJM2DIQKtZXWq4DkhSAYKF6joIP7nKMDswHzAHBgUrDgMCGgQU wTM1Z+CJZG9xAcf1zAVGl4ggYyYEFGBFyJ8VBwijS2zy1qwN1WYGtcWoAgIH0A== "; - public static X509Certificate2 GetX509Certificate2() - { - byte[] data = Convert.FromBase64String(Data); - return new X509Certificate2(data); - } + public static X509Certificate2 GetX509Certificate2() + { + byte[] data = Convert.FromBase64String(Data); + return new X509Certificate2(data); } } \ No newline at end of file diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs index 0b00a888..b95b2ef1 100644 --- a/src/WireMock.Net/IMapping.cs +++ b/src/WireMock.Net/IMapping.cs @@ -117,7 +117,7 @@ public interface IMapping /// /// The request message. /// The including a new (optional) . - Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage); + Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage); /// /// Gets the RequestMatchResult based on the RequestMessage. diff --git a/src/WireMock.Net/Logging/LogEntry.cs b/src/WireMock.Net/Logging/LogEntry.cs index 885705ba..4be2861c 100644 --- a/src/WireMock.Net/Logging/LogEntry.cs +++ b/src/WireMock.Net/Logging/LogEntry.cs @@ -1,38 +1,37 @@ -using System; +using System; using WireMock.Matchers.Request; -namespace WireMock.Logging +namespace WireMock.Logging; + +/// +/// LogEntry +/// +public class LogEntry : ILogEntry { - /// - /// LogEntry - /// - public class LogEntry : ILogEntry - { - /// - public Guid Guid { get; set; } + /// + public Guid Guid { get; set; } - /// - public IRequestMessage RequestMessage { get; set; } + /// + public IRequestMessage RequestMessage { get; set; } - /// - public IResponseMessage ResponseMessage { get; set; } + /// + public IResponseMessage ResponseMessage { get; set; } - /// - public IRequestMatchResult RequestMatchResult { get; set; } + /// + public IRequestMatchResult RequestMatchResult { get; set; } - /// - public Guid? MappingGuid { get; set; } + /// + public Guid? MappingGuid { get; set; } - /// - public string MappingTitle { get; set; } + /// + public string? MappingTitle { get; set; } - /// - public Guid? PartialMappingGuid { get; set; } + /// + public Guid? PartialMappingGuid { get; set; } - /// - public string PartialMappingTitle { get; set; } + /// + public string? PartialMappingTitle { get; set; } - /// - public IRequestMatchResult PartialMatchResult { get; set; } - } + /// + public IRequestMatchResult PartialMatchResult { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net/Mapping.cs index 108c1244..a981fa80 100644 --- a/src/WireMock.Net/Mapping.cs +++ b/src/WireMock.Net/Mapping.cs @@ -15,14 +15,14 @@ public class Mapping : IMapping /// public Guid Guid { get; } - /// - public string? Title { get; } + /// + public string? Title { get; } - /// - public string? Description { get; } + /// + public string? Description { get; } - /// - public string? Path { get; set; } + /// + public string? Path { get; set; } /// public int Priority { get; } @@ -115,16 +115,16 @@ public class Mapping : IMapping TimeSettings = timeSettings; } - /// - public Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage) - { - return Provider.ProvideResponseAsync(requestMessage, Settings); - } + /// + public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage) + { + return Provider.ProvideResponseAsync(requestMessage, Settings); + } - /// - public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState) - { - var result = new RequestMatchResult(); + /// + public IRequestMatchResult GetRequestMatchResult(IRequestMessage requestMessage, string? nextState) + { + var result = new RequestMatchResult(); RequestMatcher.GetMatchingScore(requestMessage, result); diff --git a/src/WireMock.Net/Matchers/ContentTypeMatcher.cs b/src/WireMock.Net/Matchers/ContentTypeMatcher.cs index a5d21a82..6c45a9f7 100644 --- a/src/WireMock.Net/Matchers/ContentTypeMatcher.cs +++ b/src/WireMock.Net/Matchers/ContentTypeMatcher.cs @@ -18,7 +18,7 @@ public class ContentTypeMatcher : WildcardMatcher /// /// The pattern. /// IgnoreCase (default false) - public ContentTypeMatcher([NotNull] AnyOf pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) + public ContentTypeMatcher(AnyOf pattern, bool ignoreCase = false) : this(new[] { pattern }, ignoreCase) { } @@ -28,7 +28,7 @@ public class ContentTypeMatcher : WildcardMatcher /// The match behaviour. /// The pattern. /// IgnoreCase (default false) - public ContentTypeMatcher(MatchBehaviour matchBehaviour, [NotNull] AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) + public ContentTypeMatcher(MatchBehaviour matchBehaviour, AnyOf pattern, bool ignoreCase = false) : this(matchBehaviour, new[] { pattern }, ignoreCase) { } @@ -57,7 +57,7 @@ public class ContentTypeMatcher : WildcardMatcher /// public override double IsMatch(string? input) { - if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out MediaTypeHeaderValue contentType)) + if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType)) { return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch); } diff --git a/src/WireMock.Net/Matchers/ICSharpCodeMatcher.cs b/src/WireMock.Net/Matchers/ICSharpCodeMatcher.cs index f75e3326..f49646d6 100644 --- a/src/WireMock.Net/Matchers/ICSharpCodeMatcher.cs +++ b/src/WireMock.Net/Matchers/ICSharpCodeMatcher.cs @@ -1,11 +1,10 @@ -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// CSharpCode / CS-Script Matcher +/// +/// +/// +public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher { - /// - /// CSharpCode / CS-Script Matcher - /// - /// - /// - public interface ICSharpCodeMatcher : IObjectMatcher, IStringMatcher - { - } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/IIgnoreCaseMatcher.cs b/src/WireMock.Net/Matchers/IIgnoreCaseMatcher.cs index b809dbd7..3d069b01 100644 --- a/src/WireMock.Net/Matchers/IIgnoreCaseMatcher.cs +++ b/src/WireMock.Net/Matchers/IIgnoreCaseMatcher.cs @@ -1,14 +1,13 @@ -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// IIgnoreCaseMatcher +/// +/// +public interface IIgnoreCaseMatcher : IMatcher { /// - /// IIgnoreCaseMatcher + /// Ignore the case from the pattern. /// - /// - public interface IIgnoreCaseMatcher : IMatcher - { - /// - /// Ignore the case from the pattern. - /// - bool IgnoreCase { get; } - } + bool IgnoreCase { get; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/IMatcher.cs b/src/WireMock.Net/Matchers/IMatcher.cs index 67dde261..5da3a6d7 100644 --- a/src/WireMock.Net/Matchers/IMatcher.cs +++ b/src/WireMock.Net/Matchers/IMatcher.cs @@ -1,23 +1,22 @@ -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// IMatcher +/// +public interface IMatcher { /// - /// IMatcher + /// Gets the name. /// - public interface IMatcher - { - /// - /// Gets the name. - /// - string Name { get; } + string Name { get; } - /// - /// Gets the match behaviour. - /// - MatchBehaviour MatchBehaviour { get; } + /// + /// Gets the match behaviour. + /// + MatchBehaviour MatchBehaviour { get; } - /// - /// Should this matcher throw an exception? - /// - bool ThrowException { get; } - } + /// + /// Should this matcher throw an exception? + /// + bool ThrowException { get; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/IObjectMatcher.cs b/src/WireMock.Net/Matchers/IObjectMatcher.cs index 233782a9..8957b65d 100644 --- a/src/WireMock.Net/Matchers/IObjectMatcher.cs +++ b/src/WireMock.Net/Matchers/IObjectMatcher.cs @@ -1,15 +1,14 @@ -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// IObjectMatcher +/// +public interface IObjectMatcher : IMatcher { /// - /// IObjectMatcher + /// Determines whether the specified input is match. /// - public interface IObjectMatcher : IMatcher - { - /// - /// Determines whether the specified input is match. - /// - /// The input. - /// A value between 0.0 - 1.0 of the similarity. - double IsMatch(object? input); - } + /// The input. + /// A value between 0.0 - 1.0 of the similarity. + double IsMatch(object? input); } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/IValueMatcher.cs b/src/WireMock.Net/Matchers/IValueMatcher.cs index 33151459..c2b8e94e 100644 --- a/src/WireMock.Net/Matchers/IValueMatcher.cs +++ b/src/WireMock.Net/Matchers/IValueMatcher.cs @@ -1,15 +1,14 @@ -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// IValueMatcher +/// +/// +public interface IValueMatcher : IObjectMatcher { /// - /// IValueMatcher + /// Gets the value (can be a string or an object). /// - /// - public interface IValueMatcher : IObjectMatcher - { - /// - /// Gets the value (can be a string or an object). - /// - /// Value - object Value { get; } - } + /// Value + object Value { get; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JmesPathMatcher.cs b/src/WireMock.Net/Matchers/JmesPathMatcher.cs index 84ad7925..511c770b 100644 --- a/src/WireMock.Net/Matchers/JmesPathMatcher.cs +++ b/src/WireMock.Net/Matchers/JmesPathMatcher.cs @@ -6,115 +6,114 @@ using Stef.Validation; using WireMock.Extensions; using WireMock.Models; -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// http://jmespath.org/ +/// +public class JmesPathMatcher : IStringMatcher, IObjectMatcher { + private readonly AnyOf[] _patterns; + + /// + public MatchBehaviour MatchBehaviour { get; } + + /// + public bool ThrowException { get; } + /// - /// http://jmespath.org/ + /// Initializes a new instance of the class. /// - public class JmesPathMatcher : IStringMatcher, IObjectMatcher + /// The patterns. + public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns.ToAnyOfPatterns()) { - private readonly AnyOf[] _patterns; - - /// - public MatchBehaviour MatchBehaviour { get; } - - /// - public bool ThrowException { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public JmesPathMatcher(params string[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns.ToAnyOfPatterns()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The patterns. - public JmesPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Throw an exception when the internal matching fails because of invalid input. - /// The to use. - /// The patterns. - public JmesPathMatcher(bool throwException = false, MatchOperator matchOperator = MatchOperator.Or, params AnyOf[] patterns) : - this(MatchBehaviour.AcceptOnMatch, throwException, matchOperator, patterns) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// Throw an exception when the internal matching fails because of invalid input. - /// The to use. - /// The patterns. - public JmesPathMatcher( - MatchBehaviour matchBehaviour, - bool throwException = false, - MatchOperator matchOperator = MatchOperator.Or, - params AnyOf[] patterns) - { - _patterns = Guard.NotNull(patterns); - MatchBehaviour = matchBehaviour; - ThrowException = throwException; - MatchOperator = matchOperator; - } - - /// - public double IsMatch(string? input) - { - double match = MatchScores.Mismatch; - if (input != null) - { - try - { - var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray(); - match = MatchScores.ToScore(results, MatchOperator); - } - catch (JsonException) - { - if (ThrowException) - { - throw; - } - } - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); - } - - /// - public double IsMatch(object? input) - { - double match = MatchScores.Mismatch; - - // When input is null or byte[], return Mismatch. - if (input != null && !(input is byte[])) - { - string inputAsString = JsonConvert.SerializeObject(input); - return IsMatch(inputAsString); - } - - return MatchBehaviourHelper.Convert(MatchBehaviour, match); - } - - /// - public AnyOf[] GetPatterns() - { - return _patterns; - } - - /// - public MatchOperator MatchOperator { get; } - - /// - public string Name => "JmesPathMatcher"; } + + /// + /// Initializes a new instance of the class. + /// + /// The patterns. + public JmesPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, false, MatchOperator.Or, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. + /// The patterns. + public JmesPathMatcher(bool throwException = false, MatchOperator matchOperator = MatchOperator.Or, params AnyOf[] patterns) : + this(MatchBehaviour.AcceptOnMatch, throwException, matchOperator, patterns) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// Throw an exception when the internal matching fails because of invalid input. + /// The to use. + /// The patterns. + public JmesPathMatcher( + MatchBehaviour matchBehaviour, + bool throwException = false, + MatchOperator matchOperator = MatchOperator.Or, + params AnyOf[] patterns) + { + _patterns = Guard.NotNull(patterns); + MatchBehaviour = matchBehaviour; + ThrowException = throwException; + MatchOperator = matchOperator; + } + + /// + public double IsMatch(string? input) + { + double match = MatchScores.Mismatch; + if (input != null) + { + try + { + var results = _patterns.Select(pattern => bool.Parse(new JmesPath().Transform(input, pattern.GetPattern()))).ToArray(); + match = MatchScores.ToScore(results, MatchOperator); + } + catch (JsonException) + { + if (ThrowException) + { + throw; + } + } + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + + /// + public double IsMatch(object? input) + { + double match = MatchScores.Mismatch; + + // When input is null or byte[], return Mismatch. + if (input != null && !(input is byte[])) + { + string inputAsString = JsonConvert.SerializeObject(input); + return IsMatch(inputAsString); + } + + return MatchBehaviourHelper.Convert(MatchBehaviour, match); + } + + /// + public AnyOf[] GetPatterns() + { + return _patterns; + } + + /// + public MatchOperator MatchOperator { get; } + + /// + public string Name => "JmesPathMatcher"; } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/JsonMatcher.cs b/src/WireMock.Net/Matchers/JsonMatcher.cs index e445ad7f..1f8398cc 100644 --- a/src/WireMock.Net/Matchers/JsonMatcher.cs +++ b/src/WireMock.Net/Matchers/JsonMatcher.cs @@ -119,7 +119,7 @@ public class JsonMatcher : IValueMatcher, IIgnoreCaseMatcher return tokenValue; case string stringValue: - return JsonUtils.Parse(stringValue)!; + return JsonUtils.Parse(stringValue); case IEnumerable enumerableValue: return JArray.FromObject(enumerableValue); diff --git a/src/WireMock.Net/Matchers/LinqMatcher.cs b/src/WireMock.Net/Matchers/LinqMatcher.cs index df69ad55..ec41f79c 100644 --- a/src/WireMock.Net/Matchers/LinqMatcher.cs +++ b/src/WireMock.Net/Matchers/LinqMatcher.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Linq.Dynamic.Core; using AnyOfTypes; @@ -68,7 +69,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher } /// - public double IsMatch(string input) + public double IsMatch(string? input) { double match = MatchScores.Mismatch; @@ -94,7 +95,7 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher } /// - public double IsMatch(object input) + public double IsMatch(object? input) { double match = MatchScores.Mismatch; @@ -105,9 +106,12 @@ public class LinqMatcher : IObjectMatcher, IStringMatcher value = valueAsJObject; break; - default: - value = JObject.FromObject(input); + case { } valueAsObject: + value = JObject.FromObject(valueAsObject); break; + + default: + return MatchScores.Mismatch; } // Convert a single object to a Queryable JObject-list with 1 entry. diff --git a/src/WireMock.Net/Matchers/MatchBehaviour.cs b/src/WireMock.Net/Matchers/MatchBehaviour.cs index 874b88eb..f703ff07 100644 --- a/src/WireMock.Net/Matchers/MatchBehaviour.cs +++ b/src/WireMock.Net/Matchers/MatchBehaviour.cs @@ -1,18 +1,17 @@ -namespace WireMock.Matchers +namespace WireMock.Matchers; + +/// +/// MatchBehaviour +/// +public enum MatchBehaviour { /// - /// MatchBehaviour + /// Accept on match (default) /// - public enum MatchBehaviour - { - /// - /// Accept on match (default) - /// - AcceptOnMatch, + AcceptOnMatch, - /// - /// Reject on match - /// - RejectOnMatch - } + /// + /// Reject on match + /// + RejectOnMatch } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs b/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs index 9468d1fa..f90889e4 100644 --- a/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs +++ b/src/WireMock.Net/Matchers/MatchBehaviourHelper.cs @@ -1,27 +1,26 @@ -namespace WireMock.Matchers -{ - internal static class MatchBehaviourHelper - { - /// - /// Converts the specified match behaviour and match value to a new match value. - /// - /// if AcceptOnMatch --> return match (default) - /// if RejectOnMatch and match = 0.0 --> return 1.0 - /// if RejectOnMatch and match = 0.? --> return 0.0 - /// if RejectOnMatch and match = 1.0 --> return 0.0 - /// - /// - /// The match behaviour. - /// The match. - /// match value - internal static double Convert(MatchBehaviour matchBehaviour, double match) - { - if (matchBehaviour == MatchBehaviour.AcceptOnMatch) - { - return match; - } +namespace WireMock.Matchers; - return match <= MatchScores.Tolerance ? MatchScores.Perfect : MatchScores.Mismatch; +internal static class MatchBehaviourHelper +{ + /// + /// Converts the specified match behaviour and match value to a new match value. + /// + /// if AcceptOnMatch --> return match (default) + /// if RejectOnMatch and match = 0.0 --> return 1.0 + /// if RejectOnMatch and match = 0.? --> return 0.0 + /// if RejectOnMatch and match = 1.0 --> return 0.0 + /// + /// + /// The match behaviour. + /// The match. + /// match value + internal static double Convert(MatchBehaviour matchBehaviour, double match) + { + if (matchBehaviour == MatchBehaviour.AcceptOnMatch) + { + return match; } + + return match <= MatchScores.Tolerance ? MatchScores.Perfect : MatchScores.Mismatch; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/CompositeMatcherType.cs b/src/WireMock.Net/Matchers/Request/CompositeMatcherType.cs index 3c15640f..408ecf97 100644 --- a/src/WireMock.Net/Matchers/Request/CompositeMatcherType.cs +++ b/src/WireMock.Net/Matchers/Request/CompositeMatcherType.cs @@ -1,18 +1,17 @@ -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// CompositeMatcherType +/// +public enum CompositeMatcherType { /// - /// CompositeMatcherType + /// And /// - public enum CompositeMatcherType - { - /// - /// And - /// - And = 0, + And = 0, - /// - /// Or - /// - Or = 1 - } + /// + /// Or + /// + Or = 1 } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs b/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs index 0b78625d..0f516ce5 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMatchResult.cs @@ -1,57 +1,56 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// RequestMatchResult +/// +public class RequestMatchResult : IRequestMatchResult { + /// + public double TotalScore => MatchDetails.Sum(md => md.Score); + + /// + public int TotalNumber => MatchDetails.Count; + + /// + public bool IsPerfectMatch => Math.Abs(TotalScore - TotalNumber) < MatchScores.Tolerance; + + /// + public double AverageTotalScore => TotalNumber == 0 ? 0.0 : TotalScore / TotalNumber; + + /// + public IList MatchDetails { get; } = new List(); + /// - /// RequestMatchResult + /// Adds the score. /// - public class RequestMatchResult : IRequestMatchResult + /// The matcher Type. + /// The score. + /// The score. + public double AddScore(Type matcherType, double score) { - /// - public double TotalScore => MatchDetails.Sum(md => md.Score); + MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score }); - /// - public int TotalNumber => MatchDetails.Count; + return score; + } - /// - public bool IsPerfectMatch => Math.Abs(TotalScore - TotalNumber) < MatchScores.Tolerance; + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// An object to compare with this instance. + /// + /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. + /// + public int CompareTo(object obj) + { + var compareObj = (RequestMatchResult)obj; - /// - public double AverageTotalScore => TotalNumber == 0 ? 0.0 : TotalScore / TotalNumber; + int averageTotalScoreResult = compareObj.AverageTotalScore.CompareTo(AverageTotalScore); - /// - public IList MatchDetails { get; } = new List(); - - /// - /// Adds the score. - /// - /// The matcher Type. - /// The score. - /// The score. - public double AddScore(Type matcherType, double score) - { - MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score }); - - return score; - } - - /// - /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - /// - /// An object to compare with this instance. - /// - /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. - /// - public int CompareTo(object obj) - { - var compareObj = (RequestMatchResult)obj; - - int averageTotalScoreResult = compareObj.AverageTotalScore.CompareTo(AverageTotalScore); - - // In case the score is equal, prefer the one with the most matchers. - return averageTotalScoreResult == 0 ? compareObj.TotalNumber.CompareTo(TotalNumber) : averageTotalScoreResult; - } + // In case the score is equal, prefer the one with the most matchers. + return averageTotalScoreResult == 0 ? compareObj.TotalNumber.CompareTo(TotalNumber) : averageTotalScoreResult; } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs index 1dcca5d8..1c8dfb20 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageCompositeMatcher.cs @@ -3,50 +3,49 @@ using System.Linq; using JetBrains.Annotations; using Stef.Validation; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The composite request matcher. +/// +public abstract class RequestMessageCompositeMatcher : IRequestMatcher { + private readonly CompositeMatcherType _type; + /// - /// The composite request matcher. + /// Gets the request matchers. /// - public abstract class RequestMessageCompositeMatcher : IRequestMatcher + /// + /// The request matchers. + /// + private IEnumerable RequestMatchers { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The request matchers. + /// The CompositeMatcherType type (Defaults to 'And') + protected RequestMessageCompositeMatcher([NotNull] IEnumerable requestMatchers, CompositeMatcherType type = CompositeMatcherType.And) { - private readonly CompositeMatcherType _type; + Guard.NotNull(requestMatchers, nameof(requestMatchers)); - /// - /// Gets the request matchers. - /// - /// - /// The request matchers. - /// - private IEnumerable RequestMatchers { get; } + _type = type; + RequestMatchers = requestMatchers; + } - /// - /// Initializes a new instance of the class. - /// - /// The request matchers. - /// The CompositeMatcherType type (Defaults to 'And') - protected RequestMessageCompositeMatcher([NotNull] IEnumerable requestMatchers, CompositeMatcherType type = CompositeMatcherType.And) + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + if (!RequestMatchers.Any()) { - Guard.NotNull(requestMatchers, nameof(requestMatchers)); - - _type = type; - RequestMatchers = requestMatchers; + return MatchScores.Mismatch; } - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + if (_type == CompositeMatcherType.And) { - if (!RequestMatchers.Any()) - { - return MatchScores.Mismatch; - } - - if (_type == CompositeMatcherType.And) - { - return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult)); - } - - return RequestMatchers.Max(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult)); + return RequestMatchers.Average(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult)); } + + return RequestMatchers.Max(requestMatcher => requestMatcher.GetMatchingScore(requestMessage, requestMatchResult)); } } \ No newline at end of file diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs index e3d56755..f4af9a44 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageCookieMatcher.cs @@ -4,126 +4,125 @@ using System.Linq; using JetBrains.Annotations; using Stef.Validation; -namespace WireMock.Matchers.Request +namespace WireMock.Matchers.Request; + +/// +/// The request cookie matcher. +/// +/// +public class RequestMessageCookieMatcher : IRequestMatcher { + private readonly MatchBehaviour _matchBehaviour; + private readonly bool _ignoreCase; + /// - /// The request cookie matcher. + /// The functions /// - /// - public class RequestMessageCookieMatcher : IRequestMatcher + public Func, bool>[] Funcs { get; } + + /// + /// The name + /// + public string Name { get; } + + /// + /// The matchers. + /// + public IStringMatcher[] Matchers { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The pattern. + /// Ignore the case from the pattern. + /// The match behaviour. + public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, [NotNull] string pattern, bool ignoreCase) { - private readonly MatchBehaviour _matchBehaviour; - private readonly bool _ignoreCase; + Guard.NotNull(name, nameof(name)); + Guard.NotNull(pattern, nameof(pattern)); - /// - /// The functions - /// - public Func, bool>[] Funcs { get; } + _matchBehaviour = matchBehaviour; + _ignoreCase = ignoreCase; + Name = name; + Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) }; + } - /// - /// The name - /// - public string Name { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The name. + /// The patterns. + /// Ignore the case from the pattern. + public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params string[] patterns) : + this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast().ToArray()) + { + Guard.NotNull(patterns, nameof(patterns)); + } - /// - /// The matchers. - /// - public IStringMatcher[] Matchers { get; } + /// + /// Initializes a new instance of the class. + /// + /// The match behaviour. + /// The name. + /// The matchers. + /// Ignore the case from the pattern. + public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers) + { + Guard.NotNull(name, nameof(name)); + Guard.NotNull(matchers, nameof(matchers)); - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The pattern. - /// Ignore the case from the pattern. - /// The match behaviour. - public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, [NotNull] string pattern, bool ignoreCase) + _matchBehaviour = matchBehaviour; + Name = name; + Matchers = matchers; + _ignoreCase = ignoreCase; + } + + /// + /// Initializes a new instance of the class. + /// + /// The funcs. + public RequestMessageCookieMatcher([NotNull] params Func, bool>[] funcs) + { + Guard.NotNull(funcs, nameof(funcs)); + + Funcs = funcs; + } + + /// + public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) + { + double score = IsMatch(requestMessage); + return requestMatchResult.AddScore(GetType(), score); + } + + private double IsMatch(IRequestMessage requestMessage) + { + if (requestMessage.Cookies == null) { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(pattern, nameof(pattern)); - - _matchBehaviour = matchBehaviour; - _ignoreCase = ignoreCase; - Name = name; - Matchers = new IStringMatcher[] { new WildcardMatcher(matchBehaviour, pattern, ignoreCase) }; + return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch); } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The name. - /// The patterns. - /// Ignore the case from the pattern. - public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params string[] patterns) : - this(matchBehaviour, name, ignoreCase, patterns.Select(pattern => new WildcardMatcher(matchBehaviour, pattern, ignoreCase)).Cast().ToArray()) + // Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value + var cookies = !_ignoreCase ? requestMessage.Cookies : new Dictionary(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase); + + if (Funcs != null) { - Guard.NotNull(patterns, nameof(patterns)); + return MatchScores.ToScore(Funcs.Any(f => f(cookies))); } - /// - /// Initializes a new instance of the class. - /// - /// The match behaviour. - /// The name. - /// The matchers. - /// Ignore the case from the pattern. - public RequestMessageCookieMatcher(MatchBehaviour matchBehaviour, [NotNull] string name, bool ignoreCase, [NotNull] params IStringMatcher[] matchers) + if (Matchers == null) { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(matchers, nameof(matchers)); - - _matchBehaviour = matchBehaviour; - Name = name; - Matchers = matchers; - _ignoreCase = ignoreCase; + return MatchScores.Mismatch; } - /// - /// Initializes a new instance of the class. - /// - /// The funcs. - public RequestMessageCookieMatcher([NotNull] params Func, bool>[] funcs) + if (!cookies.ContainsKey(Name)) { - Guard.NotNull(funcs, nameof(funcs)); - - Funcs = funcs; + return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch); } - /// - public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult) - { - double score = IsMatch(requestMessage); - return requestMatchResult.AddScore(GetType(), score); - } - - private double IsMatch(IRequestMessage requestMessage) - { - if (requestMessage.Cookies == null) - { - return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch); - } - - // Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value - var cookies = !_ignoreCase ? requestMessage.Cookies : new Dictionary(requestMessage.Cookies, StringComparer.OrdinalIgnoreCase); - - if (Funcs != null) - { - return MatchScores.ToScore(Funcs.Any(f => f(cookies))); - } - - if (Matchers == null) - { - return MatchScores.Mismatch; - } - - if (!cookies.ContainsKey(Name)) - { - return MatchBehaviourHelper.Convert(_matchBehaviour, MatchScores.Mismatch); - } - - string value = cookies[Name]; - return Matchers.Max(m => m.IsMatch(value)); - } + string value = cookies[Name]; + return Matchers.Max(m => m.IsMatch(value)); } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 74d86088..7a016ab5 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -30,9 +29,9 @@ namespace WireMock.Owin public bool IsStarted { get; private set; } - public List Urls { get; } = new List(); + public List Urls { get; } = new(); - public List Ports { get; } = new List(); + public List Ports { get; } = new(); public Exception RunningException => _runningException; diff --git a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs index 476c232e..cbfd92d7 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs @@ -71,7 +71,6 @@ namespace WireMock.Owin.Mappers { bytes = bytes.Take(bytes.Length / 2).Union(_randomizerBytes.Generate()).ToArray(); } - break; default: @@ -80,11 +79,10 @@ namespace WireMock.Owin.Mappers } var statusCodeType = responseMessage.StatusCode?.GetType(); - switch (statusCodeType) { case Type typeAsIntOrEnum when typeAsIntOrEnum == typeof(int) || typeAsIntOrEnum == typeof(int?) || typeAsIntOrEnum.GetTypeInfo().IsEnum: - response.StatusCode = MapStatusCode((int)responseMessage.StatusCode); + response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!); break; case Type typeAsString when typeAsString == typeof(string): @@ -138,7 +136,7 @@ namespace WireMock.Owin.Mappers return responseMessage.BodyData.BodyAsBytes; case BodyType.File: - return _options.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile); + return _options.FileSystemHandler?.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile); } return null; diff --git a/src/WireMock.Net/Pact/Models/V2/Interaction.cs b/src/WireMock.Net/Pact/Models/V2/Interaction.cs index 9dff35e1..f09dc1cc 100644 --- a/src/WireMock.Net/Pact/Models/V2/Interaction.cs +++ b/src/WireMock.Net/Pact/Models/V2/Interaction.cs @@ -2,11 +2,11 @@ namespace WireMock.Pact.Models.V2; public class Interaction { - public string Description { get; set; } = string.Empty; + public string? Description { get; set; } - public string ProviderState { get; set; } + public string? ProviderState { get; set; } - public PactRequest Request { get; set; } = new PactRequest(); + public PactRequest Request { get; set; } = new(); - public PactResponse Response { get; set; } = new PactResponse(); + public PactResponse Response { get; set; } = new(); } \ No newline at end of file diff --git a/src/WireMock.Net/Plugin/PluginLoader.cs b/src/WireMock.Net/Plugin/PluginLoader.cs index 7479049f..b6f724ac 100644 --- a/src/WireMock.Net/Plugin/PluginLoader.cs +++ b/src/WireMock.Net/Plugin/PluginLoader.cs @@ -1,57 +1,56 @@ -using System; +using System; using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Reflection; -namespace WireMock.Plugin +namespace WireMock.Plugin; + +internal static class PluginLoader { - internal static class PluginLoader + private static readonly ConcurrentDictionary Assemblies = new(); + + public static T Load(params object[] args) where T : class { - private static readonly ConcurrentDictionary Assemblies = new ConcurrentDictionary(); - - public static T Load(params object[] args) where T : class + var foundType = Assemblies.GetOrAdd(typeof(T), (type) => { - var foundType = Assemblies.GetOrAdd(typeof(T), (type) => + var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll"); + + Type? pluginType = null; + foreach (var file in files) { - var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll"); - - Type pluginType = null; - foreach (var file in files) + try { - try + var assembly = Assembly.Load(new AssemblyName { - var assembly = Assembly.Load(new AssemblyName - { - Name = Path.GetFileNameWithoutExtension(file) - }); + Name = Path.GetFileNameWithoutExtension(file) + }); - pluginType = GetImplementationTypeByInterface(assembly); - if (pluginType != null) - { - break; - } - } - catch + pluginType = GetImplementationTypeByInterface(assembly); + if (pluginType != null) { - // no-op: just try next .dll + break; } } - - if (pluginType != null) + catch { - return pluginType; + // no-op: just try next .dll } + } - throw new DllNotFoundException($"No dll found which implements type '{type}'"); - }); + if (pluginType != null) + { + return pluginType; + } - return (T)Activator.CreateInstance(foundType, args); - } + throw new DllNotFoundException($"No dll found which implements type '{type}'"); + }); - private static Type GetImplementationTypeByInterface(Assembly assembly) - { - return assembly.GetTypes().FirstOrDefault(t => typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface); - } + return (T)Activator.CreateInstance(foundType, args); + } + + private static Type? GetImplementationTypeByInterface(Assembly assembly) + { + return assembly.GetTypes().FirstOrDefault(t => typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface); } } \ No newline at end of file diff --git a/src/WireMock.Net/RegularExpressions/RegexExtended.cs b/src/WireMock.Net/RegularExpressions/RegexExtended.cs index e5971d56..72b46cbd 100644 --- a/src/WireMock.Net/RegularExpressions/RegexExtended.cs +++ b/src/WireMock.Net/RegularExpressions/RegexExtended.cs @@ -77,7 +77,7 @@ internal class RegexExtended : Regex /// Pattern to replace token for. private static string ReplaceGuidPattern(string pattern) { - Guard.NotNull(pattern, nameof(pattern)); + Guard.NotNull(pattern); foreach (var tokenPattern in GuidTokenPatterns) { diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index 9c67444f..2470ee0f 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -47,10 +47,10 @@ public class RequestMessage : IRequestMessage public string Method { get; } /// - public IDictionary> Headers { get; } + public IDictionary>? Headers { get; } /// - public IDictionary Cookies { get; } + public IDictionary? Cookies { get; } /// public IDictionary>? Query { get; } diff --git a/src/WireMock.Net/ResponseBuilders/BodyDestinationFormat.cs b/src/WireMock.Net/ResponseBuilders/BodyDestinationFormat.cs index 01f0061f..7300c2c0 100644 --- a/src/WireMock.Net/ResponseBuilders/BodyDestinationFormat.cs +++ b/src/WireMock.Net/ResponseBuilders/BodyDestinationFormat.cs @@ -1,28 +1,27 @@ -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// Defines the BodyDestinationFormat +/// +public static class BodyDestinationFormat { /// - /// Defines the BodyDestinationFormat + /// Same as source (no conversion) /// - public static class BodyDestinationFormat - { - /// - /// Same as source (no conversion) - /// - public const string SameAsSource = "SameAsSource"; + public const string SameAsSource = "SameAsSource"; - /// - /// Convert to string - /// - public const string String = "String"; + /// + /// Convert to string + /// + public const string String = "String"; - /// - /// Convert to bytes - /// - public const string Bytes = "Bytes"; + /// + /// Convert to bytes + /// + public const string Bytes = "Bytes"; - /// - /// Convert to Json object - /// - public const string Json = "Json"; - } + /// + /// Convert to Json object + /// + public const string Json = "Json"; } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/IBodyResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/IBodyResponseBuilder.cs index 19ec4a00..d62898ac 100644 --- a/src/WireMock.Net/ResponseBuilders/IBodyResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IBodyResponseBuilder.cs @@ -2,72 +2,71 @@ using System; using System.Text; using System.Threading.Tasks; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// The BodyResponseBuilder interface. +/// +public interface IBodyResponseBuilder : IFaultResponseBuilder { /// - /// The BodyResponseBuilder interface. + /// WithBody : Create a ... response based on a string. /// - 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); + /// 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 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 a 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 a 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 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 ... 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. - /// Use JSON indented. - /// 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. + /// The body encoding. + /// Use JSON indented. + /// 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); + /// + /// 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); - /// - /// 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); - } + /// + /// 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); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/ICallbackResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/ICallbackResponseBuilder.cs index 1011cbbc..e174116f 100644 --- a/src/WireMock.Net/ResponseBuilders/ICallbackResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/ICallbackResponseBuilder.cs @@ -3,25 +3,24 @@ using System.Threading.Tasks; using JetBrains.Annotations; using WireMock.ResponseProviders; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// The CallbackResponseBuilder interface. +/// +public interface ICallbackResponseBuilder : IResponseProvider { /// - /// The CallbackResponseBuilder interface. + /// The callback builder /// - public interface ICallbackResponseBuilder : IResponseProvider - { - /// - /// The callback builder - /// - /// The . - [PublicAPI] - IResponseBuilder WithCallback([NotNull] Func callbackHandler); + /// The . + [PublicAPI] + IResponseBuilder WithCallback(Func callbackHandler); - /// - /// The async callback builder - /// - /// The . - [PublicAPI] - IResponseBuilder WithCallback([NotNull] 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/ResponseBuilders/IDelayResponseBuilder.cs index 4e03014f..fdc440f1 100644 --- a/src/WireMock.Net/ResponseBuilders/IDelayResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IDelayResponseBuilder.cs @@ -1,32 +1,31 @@ using System; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// The DelayResponseBuilder interface. +/// +public interface IDelayResponseBuilder : ICallbackResponseBuilder { /// - /// The DelayResponseBuilder interface. + /// The delay defined as a . /// - public interface IDelayResponseBuilder : ICallbackResponseBuilder - { - /// - /// The delay defined as a . - /// - /// The TimeSpan to delay. - /// The . - IResponseBuilder WithDelay(TimeSpan delay); + /// The TimeSpan to delay. + /// The . + IResponseBuilder WithDelay(TimeSpan delay); - /// - /// The delay defined as milliseconds. - /// - /// The milliseconds to delay. - /// The . - IResponseBuilder WithDelay(int milliseconds); + /// + /// The delay defined as milliseconds. + /// + /// The milliseconds to delay. + /// The . + IResponseBuilder WithDelay(int milliseconds); - /// - /// Introduce random delay - /// - /// Minimum milliseconds to delay - /// Maximum milliseconds to delay - /// The . - IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000); - } + /// + /// Introduce random delay + /// + /// Minimum milliseconds to delay + /// Maximum milliseconds to delay + /// The . + IResponseBuilder WithRandomDelay(int minimumMilliseconds = 0, int maximumMilliseconds = 60_000); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/IFaultRequestBuilder.cs b/src/WireMock.Net/ResponseBuilders/IFaultRequestBuilder.cs index a0ef3113..2aee085b 100644 --- a/src/WireMock.Net/ResponseBuilders/IFaultRequestBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IFaultRequestBuilder.cs @@ -1,18 +1,17 @@ -using JetBrains.Annotations; +using JetBrains.Annotations; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// The FaultRequestBuilder interface. +/// +public interface IFaultResponseBuilder : ITransformResponseBuilder { /// - /// The FaultRequestBuilder interface. + /// WithBody : Create a fault response. /// - public interface IFaultResponseBuilder : ITransformResponseBuilder - { - /// - /// WithBody : Create a fault response. - /// - /// The FaultType. - /// The percentage when this fault should occur. When null, it's always a fault. - /// A . - IResponseBuilder WithFault(FaultType faultType, [CanBeNull] double? percentage = null); - } + /// The FaultType. + /// The percentage when this fault should occur. When null, it's always a fault. + /// A . + IResponseBuilder WithFault(FaultType faultType, double? percentage = null); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/IHeadersResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/IHeadersResponseBuilder.cs index 0d47eb26..812a70a2 100644 --- a/src/WireMock.Net/ResponseBuilders/IHeadersResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IHeadersResponseBuilder.cs @@ -1,4 +1,3 @@ -using JetBrains.Annotations; using System.Collections.Generic; using WireMock.Types; diff --git a/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs index 13820531..0fd6cd85 100644 --- a/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IProxyResponseBuilder.cs @@ -1,26 +1,25 @@ using JetBrains.Annotations; using WireMock.Settings; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// The ProxyResponseBuilder interface. +/// +public interface IProxyResponseBuilder : IStatusCodeResponseBuilder { /// - /// The ProxyResponseBuilder interface. + /// WithProxy URL using Client X509Certificate2. /// - public interface IProxyResponseBuilder : IStatusCodeResponseBuilder - { - /// - /// WithProxy URL using Client X509Certificate2. - /// - /// The proxy url. - /// The X509Certificate2 file to use for client authentication. - /// A . - IResponseBuilder WithProxy([NotNull] string proxyUrl, [CanBeNull] string clientX509Certificate2ThumbprintOrSubjectName = null); + /// The proxy url. + /// The X509Certificate2 file to use for client authentication. + /// A . + IResponseBuilder WithProxy(string proxyUrl, string? clientX509Certificate2ThumbprintOrSubjectName = null); - /// - /// WithProxy using IProxyAndRecordSettings. - /// - /// The IProxyAndRecordSettings. - /// A . - IResponseBuilder WithProxy([NotNull] ProxyAndRecordSettings settings); - } + /// + /// WithProxy using IProxyAndRecordSettings. + /// + /// The IProxyAndRecordSettings. + /// A . + IResponseBuilder WithProxy([NotNull] ProxyAndRecordSettings settings); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/IResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/IResponseBuilder.cs index b1b13b56..0d531e9c 100644 --- a/src/WireMock.Net/ResponseBuilders/IResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IResponseBuilder.cs @@ -1,9 +1,8 @@ -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// The ResponseBuilder interface. +/// +public interface IResponseBuilder : IProxyResponseBuilder { - /// - /// The ResponseBuilder interface. - /// - public interface IResponseBuilder : IProxyResponseBuilder - { - } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs index e09e9fd1..89a12110 100644 --- a/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs @@ -1,47 +1,46 @@ -using System.Net; +using System.Net; using WireMock.Settings; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// The StatusCodeResponseBuilder interface. +/// +public interface IStatusCodeResponseBuilder : IHeadersResponseBuilder { /// - /// The StatusCodeResponseBuilder interface. + /// The with status code. + /// By default all status codes are allowed, to change this behaviour, see . /// - public interface IStatusCodeResponseBuilder : IHeadersResponseBuilder - { - /// - /// The with status code. - /// By default all status codes are allowed, to change this behaviour, see . - /// - /// The code. - /// The . - IResponseBuilder WithStatusCode(int code); + /// The code. + /// The . + IResponseBuilder WithStatusCode(int code); - /// - /// The with status code. - /// By default all status codes are allowed, to change this behaviour, see . - /// - /// The code. - /// The . - IResponseBuilder WithStatusCode(string code); + /// + /// The with status code. + /// By default all status codes are allowed, to change this behaviour, see . + /// + /// The code. + /// The . + IResponseBuilder WithStatusCode(string code); - /// - /// The with status code. - /// By default all status codes are allowed, to change this behaviour, see . - /// - /// The code. - /// The . - IResponseBuilder WithStatusCode(HttpStatusCode code); + /// + /// The with status code. + /// By default all status codes are allowed, to change this behaviour, see . + /// + /// The code. + /// The . + IResponseBuilder WithStatusCode(HttpStatusCode code); - /// - /// The with Success status code (200). - /// - /// The . - IResponseBuilder WithSuccess(); + /// + /// The with Success status code (200). + /// + /// The . + IResponseBuilder WithSuccess(); - /// - /// The with NotFound status code (404). - /// - /// The . - IResponseBuilder WithNotFound(); - } + /// + /// The with NotFound status code (404). + /// + /// The . + IResponseBuilder WithNotFound(); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs index b903401d..a9da2312 100644 --- a/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs @@ -1,34 +1,33 @@ using WireMock.Types; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +/// +/// The TransformResponseBuilder interface. +/// +public interface ITransformResponseBuilder : IDelayResponseBuilder { /// - /// The TransformResponseBuilder interface. + /// Use the Handlebars.Net ResponseMessage transformer. /// - public interface ITransformResponseBuilder : IDelayResponseBuilder - { - /// - /// Use the Handlebars.Net ResponseMessage transformer. - /// - /// - /// The . - /// - IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile); + /// + /// The . + /// + IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile); - /// - /// Use the Handlebars.Net ResponseMessage transformer. - /// - /// - /// The . - /// - IResponseBuilder WithTransformer(ReplaceNodeOptions options); + /// + /// Use the Handlebars.Net ResponseMessage transformer. + /// + /// + /// The . + /// + IResponseBuilder WithTransformer(ReplaceNodeOptions options); - /// - /// Use a specific ResponseMessage transformer. - /// - /// - /// The . - /// - IResponseBuilder WithTransformer(TransformerType transformerType = TransformerType.Handlebars, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None); - } + /// + /// Use a specific ResponseMessage transformer. + /// + /// + /// The . + /// + IResponseBuilder WithTransformer(TransformerType transformerType = TransformerType.Handlebars, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithCallback.cs b/src/WireMock.Net/ResponseBuilders/Response.WithCallback.cs index c9d9e3de..72ee8195 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.WithCallback.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.WithCallback.cs @@ -1,60 +1,62 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Stef.Validation; -namespace WireMock.ResponseBuilders +namespace WireMock.ResponseBuilders; + +public partial class Response { - 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) { - /// - /// A delegate to execute to generate the response. - /// - public Func Callback { get; private set; } + Guard.NotNull(callbackHandler); - /// - /// A delegate to execute to generate the response async. - /// - public Func> CallbackAsync { get; private set; } + return WithCallbackInternal(true, callbackHandler); + } - /// - /// Defines if the method WithCallback(...) is used. - /// - public bool WithCallbackUsed { get; private set; } + /// + public IResponseBuilder WithCallback(Func> callbackHandler) + { + Guard.NotNull(callbackHandler); - /// - public IResponseBuilder WithCallback(Func callbackHandler) - { - Guard.NotNull(callbackHandler, nameof(callbackHandler)); + return WithCallbackInternal(true, callbackHandler); + } - return WithCallbackInternal(true, callbackHandler); - } + private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func callbackHandler) + { + Guard.NotNull(callbackHandler); - /// - public IResponseBuilder WithCallback(Func> callbackHandler) - { - Guard.NotNull(callbackHandler, nameof(callbackHandler)); + WithCallbackUsed = withCallbackUsed; + Callback = callbackHandler; - return WithCallbackInternal(true, callbackHandler); - } + return this; + } - private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func callbackHandler) - { - Guard.NotNull(callbackHandler, nameof(callbackHandler)); + private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func> callbackHandler) + { + Guard.NotNull(callbackHandler); - WithCallbackUsed = withCallbackUsed; - Callback = callbackHandler; + WithCallbackUsed = withCallbackUsed; + CallbackAsync = callbackHandler; - return this; - } - - private IResponseBuilder WithCallbackInternal(bool withCallbackUsed, Func> callbackHandler) - { - Guard.NotNull(callbackHandler, nameof(callbackHandler)); - - WithCallbackUsed = withCallbackUsed; - CallbackAsync = callbackHandler; - - return this; - } + return this; } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithFault.cs b/src/WireMock.Net/ResponseBuilders/Response.WithFault.cs index b8e511ec..8c078f6e 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.WithFault.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.WithFault.cs @@ -1,14 +1,13 @@ -namespace WireMock.ResponseBuilders -{ - public partial class Response - { - /// - public IResponseBuilder WithFault(FaultType faultType, double? percentage = null) - { - ResponseMessage.FaultType = faultType; - ResponseMessage.FaultPercentage = percentage; +namespace WireMock.ResponseBuilders; - return this; - } +public partial class Response +{ + /// + public IResponseBuilder WithFault(FaultType faultType, double? percentage = null) + { + ResponseMessage.FaultType = faultType; + ResponseMessage.FaultPercentage = percentage; + + return this; } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs b/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs index 72fc5342..51e0fb62 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.WithProxy.cs @@ -7,7 +7,7 @@ namespace WireMock.ResponseBuilders; public partial class Response { - private HttpClient _httpClientForProxy; + private HttpClient? _httpClientForProxy; /// /// The WebProxy settings. diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs index 3ab86580..63ae3fb0 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Stef.Validation; using WireMock.Proxy; -using WireMock.ResponseProviders; using WireMock.Settings; using WireMock.Transformers; using WireMock.Transformers.Handlebars; @@ -18,464 +17,464 @@ using WireMock.Transformers.Scriban; using WireMock.Types; using WireMock.Util; -namespace WireMock.ResponseBuilders +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 Response. + /// The minimum random delay in milliseconds. /// - public partial class Response : IResponseBuilder + public int? MinimumDelayMilliseconds { get; private set; } + + /// + /// The maximum random delay in milliseconds. + /// + public int? MaximumDelayMilliseconds { get; private set; } + + /// + /// The delay + /// + public TimeSpan? Delay { - private static readonly ThreadLocal Random = new ThreadLocal(() => new Random(DateTime.UtcNow.Millisecond)); - - private TimeSpan? _delay; - - /// - /// 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 { - get + if (MinimumDelayMilliseconds != null && MaximumDelayMilliseconds != null) { - if (MinimumDelayMilliseconds != null && MaximumDelayMilliseconds != null) - { - return TimeSpan.FromMilliseconds(Random.Value.Next(MinimumDelayMilliseconds.Value, MaximumDelayMilliseconds.Value)); - } - - return _delay; + return TimeSpan.FromMilliseconds(Random.Value!.Next(MinimumDelayMilliseconds.Value, MaximumDelayMilliseconds.Value)); } - private set => _delay = value; + return _delay; } - /// - /// Gets a value indicating whether [use transformer]. - /// - public bool UseTransformer { get; private set; } + private set => _delay = value; + } - /// - /// Gets the type of the transformer. - /// - public TransformerType TransformerType { get; private set; } + /// + /// Gets a value indicating whether [use transformer]. + /// + public bool UseTransformer { 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 type of the transformer. + /// + public TransformerType TransformerType { get; private set; } - /// - /// Gets the ReplaceNodeOptions to use when transforming a JSON node. - /// - public ReplaceNodeOptions TransformerReplaceNodeOptions { 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 response message. - /// - public ResponseMessage ResponseMessage { get; } + /// + /// Gets the ReplaceNodeOptions to use when transforming a JSON node. + /// + public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; } - /// - /// Creates this instance. - /// - /// ResponseMessage - /// A . - [PublicAPI] - public static IResponseBuilder Create([CanBeNull] ResponseMessage responseMessage = null) + /// + /// 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 WithHeader(string name, params string[] values) + { + Guard.NotNull(name); + + ResponseMessage.AddHeader(name, values); + return this; + } + + /// + public IResponseBuilder WithHeaders(IDictionary headers) + { + Guard.NotNull(headers); + + ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); + return this; + } + + /// + public IResponseBuilder WithHeaders(IDictionary headers) + { + Guard.NotNull(headers); + + ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); + return this; + } + + /// + public IResponseBuilder WithHeaders(IDictionary> headers) + { + Guard.NotNull(headers); + + ResponseMessage.Headers = headers; + return this; + } + + /// + public IResponseBuilder WithBody(Func bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null) + { + Guard.NotNull(bodyFactory, nameof(bodyFactory)); + + return WithCallbackInternal(true, req => new ResponseMessage { - 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([NotNull] Func func) - { - Guard.NotNull(func, nameof(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 WithHeader(string name, params string[] values) - { - Guard.NotNull(name, nameof(name)); - - ResponseMessage.AddHeader(name, values); - return this; - } - - /// - public IResponseBuilder WithHeaders(IDictionary headers) - { - Guard.NotNull(headers, nameof(headers)); - - ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); - return this; - } - - /// - public IResponseBuilder WithHeaders(IDictionary headers) - { - Guard.NotNull(headers, nameof(headers)); - - ResponseMessage.Headers = headers.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); - return this; - } - - /// - public IResponseBuilder WithHeaders(IDictionary> headers) - { - ResponseMessage.Headers = headers; - return this; - } - - /// - public IResponseBuilder WithBody(Func bodyFactory, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null) - { - Guard.NotNull(bodyFactory, nameof(bodyFactory)); - - return WithCallbackInternal(true, req => new ResponseMessage + BodyData = new BodyData { - BodyData = new BodyData - { - DetectedBodyType = BodyType.String, - BodyAsString = bodyFactory(req), - Encoding = encoding ?? Encoding.UTF8 - } - }); - } - - /// - public IResponseBuilder WithBody(Func> bodyFactory, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null) - { - Guard.NotNull(bodyFactory, nameof(bodyFactory)); - - return WithCallbackInternal(true, async req => new ResponseMessage - { - BodyData = new BodyData - { - DetectedBodyType = BodyType.String, - BodyAsString = await bodyFactory(req).ConfigureAwait(false), - Encoding = encoding ?? Encoding.UTF8 - } - }); - } - - /// - public IResponseBuilder WithBody(byte[] body, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null) - { - Guard.NotNull(body, nameof(body)); - - ResponseMessage.BodyDestination = destination; - ResponseMessage.BodyData = new BodyData(); - - switch (destination) - { - case BodyDestinationFormat.String: - var enc = encoding ?? Encoding.UTF8; - ResponseMessage.BodyData.DetectedBodyType = BodyType.String; - ResponseMessage.BodyData.BodyAsString = enc.GetString(body); - ResponseMessage.BodyData.Encoding = enc; - break; - - default: - ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes; - ResponseMessage.BodyData.BodyAsBytes = body; - break; + DetectedBodyType = BodyType.String, + BodyAsString = bodyFactory(req), + Encoding = encoding ?? Encoding.UTF8 } + }); + } - return this; - } + /// + public IResponseBuilder WithBody(Func> bodyFactory, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null) + { + Guard.NotNull(bodyFactory, nameof(bodyFactory)); - /// - public IResponseBuilder WithBodyFromFile(string filename, bool cache = true) + return WithCallbackInternal(true, async req => new ResponseMessage { - Guard.NotNull(filename, nameof(filename)); - - ResponseMessage.BodyData = new BodyData + BodyData = new BodyData { - BodyAsFileIsCached = cache, - BodyAsFile = filename - }; + DetectedBodyType = BodyType.String, + BodyAsString = await bodyFactory(req).ConfigureAwait(false), + Encoding = encoding ?? Encoding.UTF8 + } + }); + } - if (cache && !UseTransformer) - { + /// + public IResponseBuilder WithBody(byte[] body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null) + { + Guard.NotNull(body); + + ResponseMessage.BodyDestination = destination; + ResponseMessage.BodyData = new BodyData(); + + switch (destination) + { + case BodyDestinationFormat.String: + var enc = encoding ?? Encoding.UTF8; + ResponseMessage.BodyData.DetectedBodyType = BodyType.String; + ResponseMessage.BodyData.BodyAsString = enc.GetString(body); + ResponseMessage.BodyData.Encoding = enc; + break; + + default: ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes; + ResponseMessage.BodyData.BodyAsBytes = body; + break; + } + + return this; + } + + /// + public IResponseBuilder WithBodyFromFile(string filename, bool cache = true) + { + Guard.NotNull(filename); + + ResponseMessage.BodyData = new BodyData + { + BodyAsFileIsCached = cache, + BodyAsFile = filename + }; + + if (cache && !UseTransformer) + { + ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes; + } + else + { + ResponseMessage.BodyData.DetectedBodyType = BodyType.File; + } + + return this; + } + + /// + public IResponseBuilder WithBody(string body, string? destination = BodyDestinationFormat.SameAsSource, Encoding? encoding = null) + { + Guard.NotNull(body); + + encoding ??= Encoding.UTF8; + + ResponseMessage.BodyDestination = destination; + ResponseMessage.BodyData = new BodyData + { + Encoding = encoding + }; + + switch (destination) + { + case BodyDestinationFormat.Bytes: + ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes; + ResponseMessage.BodyData.BodyAsBytes = encoding.GetBytes(body); + break; + + case BodyDestinationFormat.Json: + ResponseMessage.BodyData.DetectedBodyType = BodyType.Json; + ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body); + break; + + default: + ResponseMessage.BodyData.DetectedBodyType = BodyType.String; + ResponseMessage.BodyData.BodyAsString = body; + break; + } + + return this; + } + + /// + public IResponseBuilder WithBodyAsJson(object body, Encoding? encoding = null, bool? indented = null) + { + Guard.NotNull(body); + + ResponseMessage.BodyDestination = null; + ResponseMessage.BodyData = new BodyData + { + Encoding = encoding, + DetectedBodyType = BodyType.Json, + BodyAsJson = body, + BodyAsJsonIndented = indented + }; + + return this; + } + + /// + public IResponseBuilder WithBodyAsJson(object body, bool indented) + { + return WithBodyAsJson(body, null, indented); + } + + /// + public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile) + { + return WithTransformer(TransformerType.Handlebars, transformContentFromBodyAsFile); + } + + /// + public IResponseBuilder WithTransformer(ReplaceNodeOptions options) + { + return WithTransformer(TransformerType.Handlebars, false, options); + } + + /// + public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None) + { + UseTransformer = true; + TransformerType = transformerType; + UseTransformerForBodyAsFile = transformContentFromBodyAsFile; + TransformerReplaceNodeOptions = options; + return this; + } + + /// + 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(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( + ProxyAndRecordSettings, + _httpClientForProxy, + requestMessage, + requestMessage.ProxyUrl + ).ConfigureAwait(false); + } + + ResponseMessage responseMessage; + if (!WithCallbackUsed) + { + responseMessage = ResponseMessage; + } + else + { + if (Callback != null) + { + responseMessage = Callback(requestMessage); } else { - ResponseMessage.BodyData.DetectedBodyType = BodyType.File; + responseMessage = await CallbackAsync!(requestMessage).ConfigureAwait(false); } - return this; + // 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; + } } - /// - public IResponseBuilder WithBody(string body, string destination = BodyDestinationFormat.SameAsSource, Encoding encoding = null) + if (UseTransformer) { - Guard.NotNull(body, nameof(body)); - - encoding = encoding ?? Encoding.UTF8; - - ResponseMessage.BodyDestination = destination; - - ResponseMessage.BodyData = new BodyData + ITransformer responseMessageTransformer; + switch (TransformerType) { - Encoding = encoding - }; - - switch (destination) - { - case BodyDestinationFormat.Bytes: - ResponseMessage.BodyData.DetectedBodyType = BodyType.Bytes; - ResponseMessage.BodyData.BodyAsBytes = encoding.GetBytes(body); + case TransformerType.Handlebars: + var factoryHandlebars = new HandlebarsContextFactory(settings.FileSystemHandler, settings.HandlebarsRegistrationCallback); + responseMessageTransformer = new Transformer(factoryHandlebars); break; - case BodyDestinationFormat.Json: - ResponseMessage.BodyData.DetectedBodyType = BodyType.Json; - ResponseMessage.BodyData.BodyAsJson = JsonUtils.DeserializeObject(body); + case TransformerType.Scriban: + case TransformerType.ScribanDotLiquid: + var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType); + responseMessageTransformer = new Transformer(factoryDotLiquid); break; default: - ResponseMessage.BodyData.DetectedBodyType = BodyType.String; - ResponseMessage.BodyData.BodyAsString = body; - break; + throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported."); } - return this; + return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null); } - /// - public IResponseBuilder WithBodyAsJson(object body, Encoding encoding = null, bool? indented = null) + if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true) { - Guard.NotNull(body, nameof(body)); - - ResponseMessage.BodyDestination = null; - ResponseMessage.BodyData = new BodyData - { - Encoding = encoding, - DetectedBodyType = BodyType.Json, - BodyAsJson = body, - BodyAsJsonIndented = indented - }; - - return this; + ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData!.BodyAsFile); } - /// - public IResponseBuilder WithBodyAsJson(object body, bool indented) - { - return WithBodyAsJson(body, null, indented); - } - - /// - public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile) - { - return WithTransformer(TransformerType.Handlebars, transformContentFromBodyAsFile); - } - - /// - public IResponseBuilder WithTransformer(ReplaceNodeOptions options) - { - return WithTransformer(TransformerType.Handlebars, false, options); - } - - /// - public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None) - { - UseTransformer = true; - TransformerType = transformerType; - UseTransformerForBodyAsFile = transformContentFromBodyAsFile; - TransformerReplaceNodeOptions = options; - return this; - } - - /// - 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(IRequestMessage requestMessage, WireMockServerSettings settings) - { - Guard.NotNull(requestMessage, nameof(requestMessage)); - Guard.NotNull(settings, nameof(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( - 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; - } - } - - if (UseTransformer) - { - ITransformer responseMessageTransformer; - switch (TransformerType) - { - case TransformerType.Handlebars: - var factoryHandlebars = new HandlebarsContextFactory(settings.FileSystemHandler, settings.HandlebarsRegistrationCallback); - responseMessageTransformer = new Transformer(factoryHandlebars); - break; - - case TransformerType.Scriban: - case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType); - responseMessageTransformer = new Transformer(factoryDotLiquid); - break; - - default: - throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported."); - } - - return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null); - } - - if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true) - { - ResponseMessage.BodyData.BodyAsBytes = settings.FileSystemHandler.ReadResponseBodyAsFile(responseMessage.BodyData.BodyAsFile); - } - - return (responseMessage, null); - } + return (responseMessage, null); } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseMessage.cs b/src/WireMock.Net/ResponseMessage.cs index 98b3647a..76fc9971 100644 --- a/src/WireMock.Net/ResponseMessage.cs +++ b/src/WireMock.Net/ResponseMessage.cs @@ -7,50 +7,51 @@ using WireMock.Types; using WireMock.Util; using Stef.Validation; -namespace WireMock +namespace WireMock; + +/// +/// The ResponseMessage. +/// +public class ResponseMessage : IResponseMessage { - /// - /// The ResponseMessage. - /// - public class ResponseMessage : IResponseMessage + /// + public IDictionary>? Headers { get; set; } = new Dictionary>(); + + /// + public object? StatusCode { get; set; } + + /// + public string? BodyOriginal { get; set; } + + /// + public string? BodyDestination { get; set; } + + /// + public IBodyData? BodyData { get; set; } + + /// + public FaultType FaultType { get; set; } + + /// + public double? FaultPercentage { get; set; } + + /// + public void AddHeader(string name, string value) { - /// - public IDictionary>? Headers { get; set; } = new Dictionary>(); + Headers ??= new Dictionary>(); + Headers.Add(name, new WireMockList(value)); + } - /// - public object StatusCode { get; set; } + /// + public void AddHeader(string name, params string[] values) + { + Guard.NotNullOrEmpty(values); - /// - public string BodyOriginal { get; set; } + Headers ??= new Dictionary>(); + var newHeaderValues = Headers.TryGetValue(name, out WireMockList? existingValues) + ? values.Union(existingValues).ToArray() + : values; - /// - public string BodyDestination { get; set; } - - /// - public IBodyData? BodyData { get; set; } - - /// - public FaultType FaultType { get; set; } - - /// - public double? FaultPercentage { get; set; } - - /// - public void AddHeader(string name, string value) - { - Headers.Add(name, new WireMockList(value)); - } - - /// - public void AddHeader(string name, params string[] values) - { - Guard.NotNullOrEmpty(values, nameof(values)); - - var newHeaderValues = Headers.TryGetValue(name, out WireMockList existingValues) - ? values.Union(existingValues).ToArray() - : values; - - Headers[name] = new WireMockList(newHeaderValues); - } + Headers[name] = new WireMockList(newHeaderValues); } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseProviders/DynamicAsyncResponseProvider.cs b/src/WireMock.Net/ResponseProviders/DynamicAsyncResponseProvider.cs index 32948141..e2e8f949 100644 --- a/src/WireMock.Net/ResponseProviders/DynamicAsyncResponseProvider.cs +++ b/src/WireMock.Net/ResponseProviders/DynamicAsyncResponseProvider.cs @@ -2,20 +2,19 @@ using System; using System.Threading.Tasks; using WireMock.Settings; -namespace WireMock.ResponseProviders +namespace WireMock.ResponseProviders; + +internal class DynamicAsyncResponseProvider : IResponseProvider { - internal class DynamicAsyncResponseProvider : IResponseProvider + private readonly Func> _responseMessageFunc; + + public DynamicAsyncResponseProvider(Func> responseMessageFunc) { - private readonly Func> _responseMessageFunc; + _responseMessageFunc = responseMessageFunc; + } - public DynamicAsyncResponseProvider(Func> responseMessageFunc) - { - _responseMessageFunc = responseMessageFunc; - } - - public async Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings) - { - return (await _responseMessageFunc(requestMessage).ConfigureAwait(false), null); - } + public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings) + { + return (await _responseMessageFunc(requestMessage).ConfigureAwait(false), null); } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseProviders/DynamicResponseProvider.cs b/src/WireMock.Net/ResponseProviders/DynamicResponseProvider.cs index 8a9deca0..4d2b62ff 100644 --- a/src/WireMock.Net/ResponseProviders/DynamicResponseProvider.cs +++ b/src/WireMock.Net/ResponseProviders/DynamicResponseProvider.cs @@ -2,21 +2,20 @@ using System; using System.Threading.Tasks; using WireMock.Settings; -namespace WireMock.ResponseProviders +namespace WireMock.ResponseProviders; + +internal class DynamicResponseProvider : IResponseProvider { - internal class DynamicResponseProvider : IResponseProvider + private readonly Func _responseMessageFunc; + + public DynamicResponseProvider(Func responseMessageFunc) { - private readonly Func _responseMessageFunc; + _responseMessageFunc = responseMessageFunc; + } - public DynamicResponseProvider(Func responseMessageFunc) - { - _responseMessageFunc = responseMessageFunc; - } - - public Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings) - { - (IResponseMessage responseMessage, IMapping mapping) result = (_responseMessageFunc(requestMessage), null); - return Task.FromResult(result); - } + public Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings) + { + (IResponseMessage responseMessage, IMapping? mapping) result = (_responseMessageFunc(requestMessage), null); + return Task.FromResult(result); } } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseProviders/IResponseProvider.cs b/src/WireMock.Net/ResponseProviders/IResponseProvider.cs index c6e7e58d..49cd6b79 100644 --- a/src/WireMock.Net/ResponseProviders/IResponseProvider.cs +++ b/src/WireMock.Net/ResponseProviders/IResponseProvider.cs @@ -1,22 +1,20 @@ // 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.Threading.Tasks; -using JetBrains.Annotations; using WireMock.Settings; -namespace WireMock.ResponseProviders +namespace WireMock.ResponseProviders; + +/// +/// The Response Provider interface. +/// +public interface IResponseProvider { /// - /// The Response Provider interface. + /// The provide response. /// - public interface IResponseProvider - { - /// - /// The provide response. - /// - /// The request. - /// The WireMockServerSettings. - /// The including a new (optional) . - Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync([NotNull] IRequestMessage requestMessage, [NotNull] WireMockServerSettings settings); - } + /// The request. + /// The WireMockServerSettings. + /// The including a new (optional) . + Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings); } \ No newline at end of file diff --git a/src/WireMock.Net/ResponseProviders/ProxyAsyncResponseProvider.cs b/src/WireMock.Net/ResponseProviders/ProxyAsyncResponseProvider.cs index 020633aa..d221ca74 100644 --- a/src/WireMock.Net/ResponseProviders/ProxyAsyncResponseProvider.cs +++ b/src/WireMock.Net/ResponseProviders/ProxyAsyncResponseProvider.cs @@ -2,22 +2,21 @@ using System; using System.Threading.Tasks; using WireMock.Settings; -namespace WireMock.ResponseProviders +namespace WireMock.ResponseProviders; + +internal class ProxyAsyncResponseProvider : IResponseProvider { - internal class ProxyAsyncResponseProvider : IResponseProvider + private readonly Func> _responseMessageFunc; + private readonly WireMockServerSettings _settings; + + public ProxyAsyncResponseProvider(Func> responseMessageFunc, WireMockServerSettings settings) { - private readonly Func> _responseMessageFunc; - private readonly WireMockServerSettings _settings; + _responseMessageFunc = responseMessageFunc; + _settings = settings; + } - public ProxyAsyncResponseProvider(Func> responseMessageFunc, WireMockServerSettings settings) - { - _responseMessageFunc = responseMessageFunc; - _settings = settings; - } - - public async Task<(IResponseMessage Message, IMapping Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings) - { - return (await _responseMessageFunc(requestMessage, _settings).ConfigureAwait(false), null); - } + public async Task<(IResponseMessage Message, IMapping? Mapping)> ProvideResponseAsync(IRequestMessage requestMessage, WireMockServerSettings settings) + { + return (await _responseMessageFunc(requestMessage, _settings).ConfigureAwait(false), null); } } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/LogEntryMapper.cs b/src/WireMock.Net/Serialization/LogEntryMapper.cs index 9299b4fc..6f9d9c81 100644 --- a/src/WireMock.Net/Serialization/LogEntryMapper.cs +++ b/src/WireMock.Net/Serialization/LogEntryMapper.cs @@ -140,7 +140,7 @@ internal static class LogEntryMapper MatchDetails = matchResult.MatchDetails.Select(md => new { Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty), - Score = md.Score + md.Score } as object).ToList() }; } diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 2ebb08d4..2f8c08d0 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -6,6 +6,7 @@ using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Matchers; using WireMock.Matchers.Request; +using WireMock.Models; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Settings; @@ -116,9 +117,10 @@ internal class MappingConverter mappingModel.Response.Delay = (int?)(response.Delay == Timeout.InfiniteTimeSpan ? TimeSpan.MaxValue.TotalMilliseconds : response.Delay?.TotalMilliseconds); } - if (mapping.Webhooks?.Length == 1) + var nonNullableWebHooks = mapping.Webhooks?.Where(wh => wh != null).ToArray() ?? new IWebhook[0]; + if (nonNullableWebHooks.Length == 1) { - mappingModel.Webhook = WebhookMapper.Map(mapping.Webhooks[0]); + mappingModel.Webhook = WebhookMapper.Map(nonNullableWebHooks[0]); } else if (mapping.Webhooks?.Length > 1) { @@ -209,7 +211,7 @@ internal class MappingConverter break; } - if (response.ResponseMessage.BodyData.Encoding != null && response.ResponseMessage.BodyData.Encoding.WebName != "utf-8") + if (response.ResponseMessage.BodyData?.Encoding != null && response.ResponseMessage.BodyData.Encoding.WebName != "utf-8") { mappingModel.Response.BodyEncoding = new EncodingModel { diff --git a/src/WireMock.Net/Serialization/MappingToFileSaver.cs b/src/WireMock.Net/Serialization/MappingToFileSaver.cs index ad527070..5c4d98f9 100644 --- a/src/WireMock.Net/Serialization/MappingToFileSaver.cs +++ b/src/WireMock.Net/Serialization/MappingToFileSaver.cs @@ -22,10 +22,7 @@ internal class MappingToFileSaver public void SaveMappingToFile(IMapping mapping, string? folder = null) { - if (folder == null) - { - folder = _settings.FileSystemHandler.GetMappingFolder(); - } + folder ??= _settings.FileSystemHandler.GetMappingFolder(); if (!_settings.FileSystemHandler.FolderExists(folder)) { diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index 06ecbb77..7b147fbe 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -208,7 +208,7 @@ internal class MatcherMapper return patternAsStringArray.ToAnyOfPatterns(); } - if (matcher.Patterns?.OfType() is IEnumerable patternsAsStringArray) + if (matcher.Patterns?.OfType() is { } patternsAsStringArray) { return patternsAsStringArray.ToAnyOfPatterns(); } diff --git a/src/WireMock.Net/Serialization/PactMapper.cs b/src/WireMock.Net/Serialization/PactMapper.cs index 0a10ee1a..d288dd3e 100644 --- a/src/WireMock.Net/Serialization/PactMapper.cs +++ b/src/WireMock.Net/Serialization/PactMapper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using DevLab.JmesPath.Interop; using WireMock.Admin.Mappings; using WireMock.Extensions; using WireMock.Matchers; @@ -76,10 +77,25 @@ internal static class PactMapper { Status = MapStatusCode(response.StatusCode), Headers = MapResponseHeaders(response.Headers), - Body = response.BodyAsJson + Body = MapBody(response) }; } + private static object? MapBody(ResponseModel? response) + { + if (response?.BodyAsJson != null) + { + return response.BodyAsJson; + } + + if (response?.Body != null) // In case the body is a string, try to deserialize into object, else just return the string + { + return JsonUtils.TryDeserializeObject(response.Body) ?? response.Body; + } + + return null; + } + private static int MapStatusCode(object? statusCode) { if (statusCode is string statusCodeAsString) @@ -138,13 +154,13 @@ internal static class PactMapper return jsonMatcher?.Pattern; } - private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue) - { - if (matchers != null && matchers.Any() && matchers[0].Pattern is string patternAsString) - { - return patternAsString; - } + //private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue) + //{ + // if (matchers != null && matchers.Any() && matchers[0].Pattern is string patternAsString) + // { + // return patternAsString; + // } - return defaultValue; - } + // return defaultValue; + //} } \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/WebhookMapper.cs b/src/WireMock.Net/Serialization/WebhookMapper.cs index d944e453..ce049e2b 100644 --- a/src/WireMock.Net/Serialization/WebhookMapper.cs +++ b/src/WireMock.Net/Serialization/WebhookMapper.cs @@ -1,112 +1,109 @@ 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 +namespace WireMock.Serialization; + +internal static class WebhookMapper { - internal static class WebhookMapper + public static IWebhook Map(WebhookModel model) { - public static IWebhook Map(WebhookModel model) + var webhook = new Webhook { - var webhook = new Webhook + Request = new WebhookRequest { - Request = new WebhookRequest - { - Url = model.Request.Url, - Method = model.Request.Method, - Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList(x.Value)) ?? new Dictionary>() - } - }; + Url = model.Request.Url, + Method = model.Request.Method, + Headers = model.Request.Headers?.ToDictionary(x => x.Key, x => new WireMockList(x.Value)) ?? new Dictionary>() + } + }; - if (model.Request.UseTransformer == true) + if (model.Request.UseTransformer == true) + { + webhook.Request.UseTransformer = true; + if (!Enum.TryParse(model.Request.TransformerType, out var transformerType)) { - webhook.Request.UseTransformer = true; - if (!Enum.TryParse(model.Request.TransformerType, out var transformerType)) - { - transformerType = TransformerType.Handlebars; - } - webhook.Request.TransformerType = transformerType; + transformerType = TransformerType.Handlebars; + } + webhook.Request.TransformerType = transformerType; - if (!Enum.TryParse(model.Request.TransformerReplaceNodeOptions, out var option)) - { - option = ReplaceNodeOptions.None; - } - - webhook.Request.TransformerReplaceNodeOptions = option; + if (!Enum.TryParse(model.Request.TransformerReplaceNodeOptions, out var option)) + { + option = ReplaceNodeOptions.None; } - IEnumerable contentTypeHeader = null; - if (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; + webhook.Request.TransformerReplaceNodeOptions = option; } - public static WebhookModel Map(IWebhook webhook) + IEnumerable? contentTypeHeader = null; + if (webhook.Request.Headers != null && webhook.Request.Headers.Any(header => string.Equals(header.Key, HttpKnownHeaderNames.ContentType, StringComparison.OrdinalIgnoreCase))) { - if (webhook?.Request == null) - { - return null; - } - - 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() - } - }; - - if (webhook.Request.BodyData != null) - { - switch (webhook.Request.BodyData.DetectedBodyType) - { - case BodyType.String: - model.Request.Body = webhook.Request.BodyData.BodyAsString; - break; - - case BodyType.Json: - model.Request.BodyAsJson = webhook.Request.BodyData.BodyAsJson; - break; - - default: - // Empty - break; - } - } - - return model; + 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() + } + }; + + if (webhook.Request.BodyData != null) + { + switch (webhook.Request.BodyData.DetectedBodyType) + { + case BodyType.String: + 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/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 79ef9f33..7c477175 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -292,7 +292,7 @@ public partial class WireMockServer private IResponseMessage SettingsUpdate(IRequestMessage requestMessage) { - var settings = DeserializeObject(requestMessage)!; + var settings = DeserializeObject(requestMessage); // _settings _settings.AllowBodyForAllHttpMethods = settings.AllowBodyForAllHttpMethods; @@ -571,7 +571,7 @@ public partial class WireMockServer { var requestModel = DeserializeObject(requestMessage); - var request = (Request)InitRequestBuilder(requestModel, false); + var request = (Request)InitRequestBuilder(requestModel, false)!; var dict = new Dictionary(); foreach (var logEntry in LogEntries.Where(le => !le.RequestMessage.Path.StartsWith("/__admin/"))) @@ -625,6 +625,23 @@ public partial class WireMockServer _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. /// @@ -714,31 +731,28 @@ public partial class WireMockServer }; } - private static T? DeserializeObject(IRequestMessage requestMessage) + private static T DeserializeObject(IRequestMessage requestMessage) where T : new() { - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String) + return requestMessage.BodyData?.DetectedBodyType switch { - return JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString); - } + BodyType.String => JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString), - if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json) - { - return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject(); - } + BodyType.Json when requestMessage.BodyData?.BodyAsJson != null => ((JObject)requestMessage.BodyData.BodyAsJson).ToObject()!, - return default(T); + _ => throw new NotSupportedException() + }; } private static T[] DeserializeRequestMessageToArray(IRequestMessage requestMessage) { - if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json) + if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json && requestMessage.BodyData.BodyAsJson != null) { var bodyAsJson = requestMessage.BodyData.BodyAsJson; return DeserializeObjectToArray(bodyAsJson); } - return default(T[]); + throw new NotSupportedException(); } private static T[] DeserializeJsonToArray(string value) @@ -754,6 +768,6 @@ public partial class WireMockServer } var singleResult = ((JObject)value).ToObject(); - return new[] { singleResult }; + return new[] { singleResult! }; } } \ No newline at end of file diff --git a/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs b/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs index 20bf285e..5285a523 100644 --- a/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs +++ b/src/WireMock.Net/Server/WireMockServer.ConvertMapping.cs @@ -275,7 +275,7 @@ public partial class WireMockServer } else { - var headers = JsonUtils.ParseJTokenToObject(entry.Value) ?? new string[0]; + var headers = JsonUtils.ParseJTokenToObject(entry.Value); responseBuilder.WithHeader(entry.Key, headers); } } diff --git a/src/WireMock.Net/Settings/SimpleCommandLineParser.cs b/src/WireMock.Net/Settings/SimpleCommandLineParser.cs index 1e31a7de..f1c61ee9 100644 --- a/src/WireMock.Net/Settings/SimpleCommandLineParser.cs +++ b/src/WireMock.Net/Settings/SimpleCommandLineParser.cs @@ -2,91 +2,105 @@ using System; using System.Collections.Generic; using System.Linq; -namespace WireMock.Settings +namespace WireMock.Settings; + +// Based on http://blog.gauffin.org/2014/12/simple-command-line-parser/ +internal class SimpleCommandLineParser { - // Based on http://blog.gauffin.org/2014/12/simple-command-line-parser/ - internal class SimpleCommandLineParser + private const string Sigil = "--"; + + private IDictionary Arguments { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public void Parse(string[] arguments) { - private const string Sigil = "--"; + string currentName = string.Empty; - private IDictionary Arguments { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + var values = new List(); - public void Parse(string[] arguments) + // Split a single argument on a space character to fix issue (e.g. Azure Service Fabric) when an argument is supplied like "--x abc" or '--x abc' + foreach (string arg in arguments.SelectMany(arg => arg.Split(' '))) { - string currentName = null; - - var values = new List(); - - // Split a single argument on a space character to fix issue (e.g. Azure Service Fabric) when an argument is supplied like "--x abc" or '--x abc' - foreach (string arg in arguments.SelectMany(arg => arg.Split(' '))) + if (arg.StartsWith(Sigil)) { - if (arg.StartsWith(Sigil)) + if (!string.IsNullOrEmpty(currentName)) { - if (!string.IsNullOrEmpty(currentName)) - { - Arguments[currentName] = values.ToArray(); - } + Arguments[currentName] = values.ToArray(); + } - values.Clear(); - currentName = arg.Substring(Sigil.Length); - } - else if (string.IsNullOrEmpty(currentName)) - { - Arguments[arg] = new string[0]; - } - else - { - values.Add(arg); - } + values.Clear(); + currentName = arg.Substring(Sigil.Length); } - - if (!string.IsNullOrEmpty(currentName)) + else if (string.IsNullOrEmpty(currentName)) { - Arguments[currentName] = values.ToArray(); + Arguments[arg] = new string[0]; + } + else + { + values.Add(arg); } } - public bool Contains(string name) + if (!string.IsNullOrEmpty(currentName)) { - return Arguments.ContainsKey(name); - } - - public string[] GetValues(string name, string[] defaultValue = null) - { - return Contains(name) ? Arguments[name] : defaultValue; - } - - public T GetValue(string name, Func func, T defaultValue = default(T)) - { - return Contains(name) ? func(Arguments[name]) : defaultValue; - } - - public bool GetBoolValue(string name, bool defaultValue = false) - { - return GetValue(name, values => - { - string value = values.FirstOrDefault(); - return !string.IsNullOrEmpty(value) ? bool.Parse(value) : defaultValue; - }, defaultValue); - } - - public bool GetBoolSwitchValue(string name) - { - return Contains(name); - } - - public int? GetIntValue(string name, int? defaultValue = null) - { - return GetValue(name, values => - { - string value = values.FirstOrDefault(); - return !string.IsNullOrEmpty(value) ? int.Parse(value) : defaultValue; - }, defaultValue); - } - - public string GetStringValue(string name, string defaultValue = null) - { - return GetValue(name, values => values.FirstOrDefault() ?? defaultValue, defaultValue); + Arguments[currentName] = values.ToArray(); } } + + public bool Contains(string name) + { + return Arguments.ContainsKey(name); + } + + public string[] GetValues(string name, string[] defaultValue) + { + return Contains(name) ? Arguments[name] : defaultValue; + } + + public string[]? GetValues(string name) + { + return Contains(name) ? Arguments[name] : default; + } + + public T GetValue(string name, Func func, T defaultValue) + { + return Contains(name) ? func(Arguments[name]) : defaultValue; + } + + public T? GetValue(string name, Func func) + { + return Contains(name) ? func(Arguments[name]) : default; + } + + public bool GetBoolValue(string name, bool defaultValue = false) + { + return GetValue(name, values => + { + var value = values.FirstOrDefault(); + return !string.IsNullOrEmpty(value) ? bool.Parse(value) : defaultValue; + }, defaultValue); + } + + public bool GetBoolSwitchValue(string name) + { + return Contains(name); + } + + public int? GetIntValue(string name, int? defaultValue = null) + { + return GetValue(name, values => + { + var value = values.FirstOrDefault(); + return !string.IsNullOrEmpty(value) ? int.Parse(value) : defaultValue; + }, defaultValue); + } + + public string GetStringValue(string name, string defaultValue) + { + return GetValue(name, values => values.FirstOrDefault() ?? defaultValue, defaultValue); + } + + public string? GetStringValue(string name) + { + return GetValue(name, values => values.FirstOrDefault()); + } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WebhookSettings.cs b/src/WireMock.Net/Settings/WebhookSettings.cs index 5294fe82..63c6e1c0 100644 --- a/src/WireMock.Net/Settings/WebhookSettings.cs +++ b/src/WireMock.Net/Settings/WebhookSettings.cs @@ -1,9 +1,8 @@ -namespace WireMock.Settings +namespace WireMock.Settings; + +/// +/// WebhookSettings +/// +public class WebhookSettings : HttpClientSettings { - /// - /// WebhookSettings - /// - public class WebhookSettings : HttpClientSettings - { - } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockServerSettings.cs b/src/WireMock.Net/Settings/WireMockServerSettings.cs index 1ad5bfea..0efa82be 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettings.cs @@ -251,5 +251,11 @@ namespace WireMock.Settings /// [PublicAPI, JsonIgnore] public IDictionary>? CustomMatcherMappings { get; set; } + + /// + /// The used when the a JSON response is generated. + /// + [PublicAPI, JsonIgnore] + public JsonSerializerSettings? JsonSerializerSettings { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index 122a6235..47e4f948 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -1,129 +1,129 @@ using System; +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Stef.Validation; using WireMock.Logging; using WireMock.Types; -namespace WireMock.Settings +namespace WireMock.Settings; + +/// +/// A static helper class to parse commandline arguments into WireMockServerSettings. +/// +public static class WireMockServerSettingsParser { /// - /// A static helper class to parse commandline arguments into WireMockServerSettings. + /// Parse commandline arguments into WireMockServerSettings. /// - public static class WireMockServerSettingsParser + /// The commandline arguments + /// The logger (optional, can be null) + /// The parsed settings + [PublicAPI] + public static bool TryParseArguments(string[] args, [NotNullWhen(true)] out WireMockServerSettings? settings, IWireMockLogger? logger = null) { - /// - /// Parse commandline arguments into WireMockServerSettings. - /// - /// The commandline arguments - /// The logger (optional, can be null) - /// The parsed settings - [PublicAPI] - public static bool TryParseArguments([NotNull] string[] args, out WireMockServerSettings settings, [CanBeNull] IWireMockLogger logger = null) + Guard.HasNoNulls(args, nameof(args)); + + var parser = new SimpleCommandLineParser(); + parser.Parse(args); + + if (parser.GetBoolSwitchValue("help")) { - Guard.HasNoNulls(args, nameof(args)); + (logger ?? new WireMockConsoleLogger()).Info("See https://github.com/WireMock-Net/WireMock.Net/wiki/WireMock-commandline-parameters for details on all commandline options."); + settings = null; + return false; + } - var parser = new SimpleCommandLineParser(); - parser.Parse(args); - - if (parser.GetBoolSwitchValue("help")) - { - (logger ?? new WireMockConsoleLogger()).Info("See https://github.com/WireMock-Net/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("AdminPassword"), - AdminUsername = parser.GetStringValue("AdminUsername"), - AllowBodyForAllHttpMethods = parser.GetBoolValue("AllowBodyForAllHttpMethods"), - AllowCSharpCodeMatcher = parser.GetBoolValue("AllowCSharpCodeMatcher"), - AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"), - AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"), - DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"), - HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"), - MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"), - ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"), - RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration"), - SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)), - StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true), - ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"), - UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true), - WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"), - WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"), - }; + settings = new WireMockServerSettings + { + AdminAzureADAudience = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADAudience)), + AdminAzureADTenant = parser.GetStringValue(nameof(WireMockServerSettings.AdminAzureADTenant)), + AdminPassword = parser.GetStringValue("AdminPassword"), + AdminUsername = parser.GetStringValue("AdminUsername"), + AllowBodyForAllHttpMethods = parser.GetBoolValue("AllowBodyForAllHttpMethods"), + AllowCSharpCodeMatcher = parser.GetBoolValue("AllowCSharpCodeMatcher"), + AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue("AllowOnlyDefinedHttpStatusCodeInResponse"), + AllowPartialMapping = parser.GetBoolValue("AllowPartialMapping"), + DisableJsonBodyParsing = parser.GetBoolValue("DisableJsonBodyParsing"), + HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"), + MaxRequestLogCount = parser.GetIntValue("MaxRequestLogCount"), + ReadStaticMappings = parser.GetBoolValue("ReadStaticMappings"), + RequestLogExpirationDuration = parser.GetIntValue("RequestLogExpirationDuration"), + SaveUnmatchedRequests = parser.GetBoolValue(nameof(WireMockServerSettings.SaveUnmatchedRequests)), + StartAdminInterface = parser.GetBoolValue("StartAdminInterface", true), + ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"), + UseRegexExtended = parser.GetBoolValue(nameof(WireMockServerSettings.UseRegexExtended), true), + WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"), + WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"), + }; #if USE_ASPNETCORE - settings.CorsPolicyOptions = parser.GetValue(nameof(WireMockServerSettings.CorsPolicyOptions), values => - { - var value = string.Join(string.Empty, values); - return Enum.TryParse(value, true, out var corsPolicyOptions) ? corsPolicyOptions : CorsPolicyOptions.None; - }); + settings.CorsPolicyOptions = parser.GetValue(nameof(WireMockServerSettings.CorsPolicyOptions), values => + { + var value = string.Join(string.Empty, values); + return Enum.TryParse(value, true, out var corsPolicyOptions) ? corsPolicyOptions : CorsPolicyOptions.None; + }); #endif - if (logger != null) - { - settings.Logger = logger; - } - - if (parser.GetStringValue("WireMockLogger") == "WireMockConsoleLogger") - { - settings.Logger = new WireMockConsoleLogger(); - } - - if (parser.Contains(nameof(WireMockServerSettings.Port))) - { - settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port)); - } - else - { - settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" }); - } - - string proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl"); - if (!string.IsNullOrEmpty(proxyUrl)) - { - settings.ProxyAndRecordSettings = new ProxyAndRecordSettings - { - AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect"), - ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue("ClientX509Certificate2ThumbprintOrSubjectName"), - ExcludedCookies = parser.GetValues("ExcludedCookies"), - ExcludedHeaders = parser.GetValues("ExcludedHeaders"), - // PreferProxyMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.PreferProxyMapping)), - SaveMapping = parser.GetBoolValue("SaveMapping"), - SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern"), - SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), - Url = proxyUrl - }; - - string proxyAddress = parser.GetStringValue("WebProxyAddress"); - if (!string.IsNullOrEmpty(proxyAddress)) - { - settings.ProxyAndRecordSettings.WebProxySettings = new WebProxySettings - { - Address = proxyAddress, - UserName = parser.GetStringValue("WebProxyUserName"), - Password = parser.GetStringValue("WebProxyPassword") - }; - } - } - - 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; - } - - return true; + if (logger != null) + { + settings.Logger = logger; } + + if (parser.GetStringValue("WireMockLogger") == "WireMockConsoleLogger") + { + settings.Logger = new WireMockConsoleLogger(); + } + + if (parser.Contains(nameof(WireMockServerSettings.Port))) + { + settings.Port = parser.GetIntValue(nameof(WireMockServerSettings.Port)); + } + else + { + settings.Urls = parser.GetValues("Urls", new[] { "http://*:9091/" }); + } + + var proxyUrl = parser.GetStringValue("ProxyURL") ?? parser.GetStringValue("ProxyUrl"); + if (!string.IsNullOrEmpty(proxyUrl)) + { + settings.ProxyAndRecordSettings = new ProxyAndRecordSettings + { + AllowAutoRedirect = parser.GetBoolValue("AllowAutoRedirect"), + ClientX509Certificate2ThumbprintOrSubjectName = parser.GetStringValue("ClientX509Certificate2ThumbprintOrSubjectName"), + ExcludedCookies = parser.GetValues("ExcludedCookies"), + ExcludedHeaders = parser.GetValues("ExcludedHeaders"), + // PreferProxyMapping = parser.GetBoolValue(nameof(ProxyAndRecordSettings.PreferProxyMapping)), + SaveMapping = parser.GetBoolValue("SaveMapping"), + SaveMappingForStatusCodePattern = parser.GetStringValue("SaveMappingForStatusCodePattern", "*"), + SaveMappingToFile = parser.GetBoolValue("SaveMappingToFile"), + Url = proxyUrl + }; + + string? proxyAddress = parser.GetStringValue("WebProxyAddress"); + if (!string.IsNullOrEmpty(proxyAddress)) + { + settings.ProxyAndRecordSettings.WebProxySettings = new WebProxySettings + { + Address = proxyAddress, + UserName = parser.GetStringValue("WebProxyUserName"), + Password = parser.GetStringValue("WebProxyPassword") + }; + } + } + + 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; + } + + return true; } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/EnhancedFileSystemWatcher.cs b/src/WireMock.Net/Util/EnhancedFileSystemWatcher.cs index d30cecf8..c4d7a053 100644 --- a/src/WireMock.Net/Util/EnhancedFileSystemWatcher.cs +++ b/src/WireMock.Net/Util/EnhancedFileSystemWatcher.cs @@ -17,7 +17,7 @@ namespace WireMock.Util private const int DefaultWatchInterval = 100; // This Dictionary keeps the track of when an event occurred last for a particular file - private ConcurrentDictionary _lastFileEvent; + private ConcurrentDictionary _lastFileEvent = new(); // Watch Interval in Milliseconds private int _interval; @@ -58,7 +58,7 @@ namespace WireMock.Util /// The interval. public EnhancedFileSystemWatcher(int interval = DefaultWatchInterval) { - Guard.Condition(interval, i => i >= 0, nameof(interval)); + Guard.Condition(interval, i => i >= 0); InitializeMembers(interval); } @@ -68,10 +68,10 @@ namespace WireMock.Util /// /// The directory to monitor, in standard or Universal Naming Convention (UNC) notation. /// The interval. - public EnhancedFileSystemWatcher([NotNull] string path, int interval = DefaultWatchInterval) : base(path) + public EnhancedFileSystemWatcher(string path, int interval = DefaultWatchInterval) : base(path) { - Guard.NotNullOrEmpty(path, nameof(path)); - Guard.Condition(interval, i => i >= 0, nameof(interval)); + Guard.NotNullOrEmpty(path); + Guard.Condition(interval, i => i >= 0); InitializeMembers(interval); } @@ -82,11 +82,11 @@ namespace WireMock.Util /// The directory to monitor, in standard or Universal Naming Convention (UNC) notation. /// The type of files to watch. For example, "*.txt" watches for changes to all text files. /// The interval. - public EnhancedFileSystemWatcher([NotNull] string path, [NotNull] string filter, int interval = DefaultWatchInterval) : base(path, filter) + public EnhancedFileSystemWatcher(string path, string filter, int interval = DefaultWatchInterval) : base(path, filter) { - Guard.NotNullOrEmpty(path, nameof(path)); - Guard.NotNullOrEmpty(filter, nameof(filter)); - Guard.Condition(interval, i => i >= 0, nameof(interval)); + Guard.NotNullOrEmpty(path); + Guard.NotNullOrEmpty(filter); + Guard.Condition(interval, i => i >= 0); InitializeMembers(interval); } @@ -100,22 +100,22 @@ namespace WireMock.Util /// /// Occurs when a file or directory in the specified is changed. /// - public new event FileSystemEventHandler Changed; + public new event FileSystemEventHandler? Changed; /// /// Occurs when a file or directory in the specified is created. /// - public new event FileSystemEventHandler Created; + public new event FileSystemEventHandler? Created; /// /// Occurs when a file or directory in the specified is deleted. /// - public new event FileSystemEventHandler Deleted; + public new event FileSystemEventHandler? Deleted; /// /// Occurs when a file or directory in the specified is renamed. /// - public new event RenamedEventHandler Renamed; + public new event RenamedEventHandler? Renamed; #endregion #region Protected Methods to raise the Events for this class diff --git a/src/WireMock.Net/Util/HttpStatusRangeParser.cs b/src/WireMock.Net/Util/HttpStatusRangeParser.cs index a212f03a..936dccd7 100644 --- a/src/WireMock.Net/Util/HttpStatusRangeParser.cs +++ b/src/WireMock.Net/Util/HttpStatusRangeParser.cs @@ -16,7 +16,7 @@ internal static class HttpStatusRangeParser /// The pattern. (Can be null, in that case it's allowed.) /// The value. /// is invalid. - public static bool IsMatch(string pattern, object httpStatusCode) + public static bool IsMatch(string pattern, object? httpStatusCode) { switch (httpStatusCode) { diff --git a/src/WireMock.Net/Util/JsonUtils.cs b/src/WireMock.Net/Util/JsonUtils.cs index d01cfad5..59b19724 100644 --- a/src/WireMock.Net/Util/JsonUtils.cs +++ b/src/WireMock.Net/Util/JsonUtils.cs @@ -87,9 +87,9 @@ internal static class JsonUtils /// /// A System.String that contains JSON. /// A Newtonsoft.Json.Linq.JToken populated from the string that contains JSON. - public static JToken? Parse(string json) + public static JToken Parse(string json) { - return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); + return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!; } /// @@ -98,9 +98,9 @@ internal static class JsonUtils /// /// A System.String that contains JSON. /// The deserialized object from the JSON string. - public static object? DeserializeObject(string json) + public static object DeserializeObject(string json) { - return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); + return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!; } /// @@ -109,17 +109,35 @@ internal static class JsonUtils /// /// A System.String that contains JSON. /// The deserialized object from the JSON string. - public static T? DeserializeObject(string json) + public static T DeserializeObject(string json) { - return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone); + return JsonConvert.DeserializeObject(json, JsonSerializationConstants.JsonDeserializerSettingsWithDateParsingNone)!; } - public static T? ParseJTokenToObject(object value) + public static T? TryDeserializeObject(string json) { + try + { + return JsonConvert.DeserializeObject(json); + } + catch + { + return default; + } + } + + public static T ParseJTokenToObject(object? value) + { + if (value != null && value.GetType() == typeof(T)) + { + return (T)value; + } + return value switch { - JToken tokenValue => tokenValue.ToObject(), - _ => default + JToken tokenValue => tokenValue.ToObject()!, + + _ => throw new NotSupportedException($"Unable to convert value to {typeof(T)}.") }; } diff --git a/src/WireMock.Net/Util/PathUtils.cs b/src/WireMock.Net/Util/PathUtils.cs index 951c9d47..b3c3e0a1 100644 --- a/src/WireMock.Net/Util/PathUtils.cs +++ b/src/WireMock.Net/Util/PathUtils.cs @@ -1,36 +1,39 @@ -using System.IO; +using System.IO; +using Stef.Validation; -namespace WireMock.Util +namespace WireMock.Util; + +internal static class PathUtils { - internal static class PathUtils + /// + /// Robust handling of the user defined path. + /// Also supports Unix and Windows platforms + /// + /// The path to clean + public static string? CleanPath(string? path) { - /// - /// Robust handling of the user defined path. - /// Also supports Unix and Windows platforms - /// - /// The path to clean - public static string CleanPath(string path) - { - return path?.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); - } + return path?.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); + } - /// - /// Removes leading directory separator chars from the filepath, which could break Path.Combine - /// - /// The path to remove the loading DirectorySeparatorChars - public static string RemoveLeadingDirectorySeparators(string path) - { - return path?.TrimStart(new[] { Path.DirectorySeparatorChar }); - } + /// + /// Removes leading directory separator chars from the filepath, which could break Path.Combine + /// + /// The path to remove the loading DirectorySeparatorChars + public static string? RemoveLeadingDirectorySeparators(string? path) + { + return path?.TrimStart(new[] { Path.DirectorySeparatorChar }); + } - /// - /// Combine two paths - /// - /// The root path - /// The path - public static string Combine(string root, string path) - { - return Path.Combine(root, RemoveLeadingDirectorySeparators(path)); - } + /// + /// Combine two paths + /// + /// The root path + /// The path + public static string Combine(string root, string? path) + { + Guard.NotNull(root); + + var result = RemoveLeadingDirectorySeparators(path); + return result == null ? root : Path.Combine(root, result); } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/SystemUtils.cs b/src/WireMock.Net/Util/SystemUtils.cs index 85182d9f..bc9fe559 100644 --- a/src/WireMock.Net/Util/SystemUtils.cs +++ b/src/WireMock.Net/Util/SystemUtils.cs @@ -4,5 +4,5 @@ namespace WireMock.Util; internal static class SystemUtils { - public static readonly string Version = typeof(SystemUtils).GetTypeInfo().Assembly.GetName().Version.ToString(); + public static readonly string Version = typeof(SystemUtils).GetTypeInfo().Assembly.GetName().Version!.ToString(); } \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index 0f042d25..c4556590 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -140,6 +140,11 @@ + + + + + WireMockServer.cs diff --git a/test/WireMock.Net.Tests/Pact/PactTests.cs b/test/WireMock.Net.Tests/Pact/PactTests.cs index 70234ed5..ba411a13 100644 --- a/test/WireMock.Net.Tests/Pact/PactTests.cs +++ b/test/WireMock.Net.Tests/Pact/PactTests.cs @@ -1,5 +1,9 @@ using System.IO; using System.Net; +using System.Text; +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using WireMock.Matchers; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; @@ -11,7 +15,7 @@ namespace WireMock.Net.Tests.Pact; public class PactTests { [Fact] - public void SavePact_Get_Request() + public void SavePact_Get_Request_And_Response_WithBodyAsJson() { var server = WireMockServer.Start(); server @@ -41,6 +45,62 @@ public class PactTests server.SavePact(Path.Combine("../../../", "Pact", "files"), "pact-get.json"); } + [Fact] + public void SavePact_Get_Request_And_Response_WithBody_StringIsJson() + { + // Act + var server = WireMockServer.Start(); + server + .Given(Request.Create() + .UsingGet() + .WithPath("/tester") + ) + .RespondWith( + Response.Create() + .WithStatusCode(HttpStatusCode.OK) + .WithBody(@"{ foo: ""bar"" }") + ); + + var memoryStream = new MemoryStream(); + server.SavePact(memoryStream); + + var json = Encoding.UTF8.GetString(memoryStream.ToArray()); + var pact = JsonConvert.DeserializeObject(json)!; + + // Assert + pact.Interactions.Should().HaveCount(1); + + var expectedBody = new JObject { { "foo", "bar" } }; + pact.Interactions[0].Response.Body.Should().BeEquivalentTo(expectedBody); + } + + [Fact] + public void SavePact_Get_Request_And_Response_WithBody_StringIsString() + { + // Act + var server = WireMockServer.Start(); + server + .Given(Request.Create() + .UsingGet() + .WithPath("/tester") + ) + .RespondWith( + Response.Create() + .WithStatusCode(HttpStatusCode.OK) + .WithBody("test") + ); + + var memoryStream = new MemoryStream(); + server.SavePact(memoryStream); + + var json = Encoding.UTF8.GetString(memoryStream.ToArray()); + var pact = JsonConvert.DeserializeObject(json)!; + + // Assert + pact.Interactions.Should().HaveCount(1); + pact.Interactions[0].Response.Body.Should().Be("test"); + } + [Fact] public void SavePact_Multiple_Requests() { diff --git a/test/WireMock.Net.Tests/Settings/SimpleCommandLineParserTests.cs b/test/WireMock.Net.Tests/Settings/SimpleCommandLineParserTests.cs index 17ad2690..b6c1d686 100644 --- a/test/WireMock.Net.Tests/Settings/SimpleCommandLineParserTests.cs +++ b/test/WireMock.Net.Tests/Settings/SimpleCommandLineParserTests.cs @@ -2,102 +2,101 @@ using NFluent; using WireMock.Settings; using Xunit; -namespace WireMock.Net.Tests.Settings +namespace WireMock.Net.Tests.Settings; + +public class SimpleCommandLineParserTests { - public class SimpleCommandLineParserTests + private readonly SimpleCommandLineParser _parser; + + public SimpleCommandLineParserTests() { - private readonly SimpleCommandLineParser _parser; + _parser = new SimpleCommandLineParser(); + } - public SimpleCommandLineParserTests() - { - _parser = new SimpleCommandLineParser(); - } + [Fact] + public void SimpleCommandLineParser_Parse_Arguments() + { + // Assign + _parser.Parse(new[] { "--test1", "one", "--test2", "two", "--test3", "three" }); - [Fact] - public void SimpleCommandLineParser_Parse_Arguments() - { - // Assign - _parser.Parse(new[] { "--test1", "one", "--test2", "two", "--test3", "three" }); + // Act + string? value1 = _parser.GetStringValue("test1"); + string? value2 = _parser.GetStringValue("test2"); + string? value3 = _parser.GetStringValue("test3"); - // Act - string value1 = _parser.GetStringValue("test1"); - string value2 = _parser.GetStringValue("test2"); - string value3 = _parser.GetStringValue("test3"); + // Assert + Check.That(value1).IsEqualTo("one"); + Check.That(value2).IsEqualTo("two"); + Check.That(value3).IsEqualTo("three"); + } - // Assert - Check.That(value1).IsEqualTo("one"); - Check.That(value2).IsEqualTo("two"); - Check.That(value3).IsEqualTo("three"); - } + [Fact] + public void SimpleCommandLineParser_Parse_ArgumentsAsCombinedKeyAndValue() + { + // Assign + _parser.Parse(new[] { "--test1 one", "--test2 two", "--test3 three" }); - [Fact] - public void SimpleCommandLineParser_Parse_ArgumentsAsCombinedKeyAndValue() - { - // Assign - _parser.Parse(new[] { "--test1 one", "--test2 two", "--test3 three" }); + // Act + string? value1 = _parser.GetStringValue("test1"); + string? value2 = _parser.GetStringValue("test2"); + string? value3 = _parser.GetStringValue("test3"); - // Act - string value1 = _parser.GetStringValue("test1"); - string value2 = _parser.GetStringValue("test2"); - string value3 = _parser.GetStringValue("test3"); + // Assert + Check.That(value1).IsEqualTo("one"); + Check.That(value2).IsEqualTo("two"); + Check.That(value3).IsEqualTo("three"); + } - // Assert - Check.That(value1).IsEqualTo("one"); - Check.That(value2).IsEqualTo("two"); - Check.That(value3).IsEqualTo("three"); - } + [Fact] + public void SimpleCommandLineParser_Parse_ArgumentsMixed() + { + // Assign + _parser.Parse(new[] { "--test1 one", "--test2", "two", "--test3 three" }); - [Fact] - public void SimpleCommandLineParser_Parse_ArgumentsMixed() - { - // Assign - _parser.Parse(new[] { "--test1 one", "--test2", "two", "--test3 three" }); + // Act + string? value1 = _parser.GetStringValue("test1"); + string? value2 = _parser.GetStringValue("test2"); + string? value3 = _parser.GetStringValue("test3"); - // Act - string value1 = _parser.GetStringValue("test1"); - string value2 = _parser.GetStringValue("test2"); - string value3 = _parser.GetStringValue("test3"); + // Assert + Check.That(value1).IsEqualTo("one"); + Check.That(value2).IsEqualTo("two"); + Check.That(value3).IsEqualTo("three"); + } - // Assert - Check.That(value1).IsEqualTo("one"); - Check.That(value2).IsEqualTo("two"); - Check.That(value3).IsEqualTo("three"); - } + [Fact] + public void SimpleCommandLineParser_Parse_GetBoolValue() + { + // Assign + _parser.Parse(new[] { "'--test1", "false'", "--test2 true" }); - [Fact] - public void SimpleCommandLineParser_Parse_GetBoolValue() - { - // Assign - _parser.Parse(new[] { "'--test1", "false'", "--test2 true" }); + // Act + bool value1 = _parser.GetBoolValue("test1"); + bool value2 = _parser.GetBoolValue("test2"); + bool value3 = _parser.GetBoolValue("test3", true); - // Act - bool value1 = _parser.GetBoolValue("test1"); - bool value2 = _parser.GetBoolValue("test2"); - bool value3 = _parser.GetBoolValue("test3", true); + // Assert + Check.That(value1).IsEqualTo(false); + Check.That(value2).IsEqualTo(true); + Check.That(value3).IsEqualTo(true); + } - // Assert - Check.That(value1).IsEqualTo(false); - Check.That(value2).IsEqualTo(true); - Check.That(value3).IsEqualTo(true); - } + [Fact] + public void SimpleCommandLineParser_Parse_GetIntValue() + { + // Assign + _parser.Parse(new[] { "--test1", "42", "--test2 55" }); - [Fact] - public void SimpleCommandLineParser_Parse_GetIntValue() - { - // Assign - _parser.Parse(new[] { "--test1", "42", "--test2 55" }); + // Act + int? value1 = _parser.GetIntValue("test1"); + int? value2 = _parser.GetIntValue("test2"); + int? value3 = _parser.GetIntValue("test3", 100); + int? value4 = _parser.GetIntValue("test4"); - // Act - int? value1 = _parser.GetIntValue("test1"); - int? value2 = _parser.GetIntValue("test2"); - int? value3 = _parser.GetIntValue("test3", 100); - int? value4 = _parser.GetIntValue("test4"); - - // Assert - Check.That(value1).IsEqualTo(42); - Check.That(value2).IsEqualTo(55); - Check.That(value3).IsEqualTo(100); - Check.That(value4).IsNull(); - } + // Assert + Check.That(value1).IsEqualTo(42); + Check.That(value2).IsEqualTo(55); + Check.That(value3).IsEqualTo(100); + Check.That(value4).IsNull(); } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Util/JsonUtilsTests.cs b/test/WireMock.Net.Tests/Util/JsonUtilsTests.cs index f3433496..600d80d0 100644 --- a/test/WireMock.Net.Tests/Util/JsonUtilsTests.cs +++ b/test/WireMock.Net.Tests/Util/JsonUtilsTests.cs @@ -13,7 +13,7 @@ namespace WireMock.Net.Tests.Util; public class JsonUtilsTests { [Fact] - public void JsonUtils_ParseJTokenToObject() + public void JsonUtils_ParseJTokenToObject_For_String() { // Assign object value = "test"; @@ -22,7 +22,33 @@ public class JsonUtilsTests string result = JsonUtils.ParseJTokenToObject(value); // Assert - Check.That(result).IsEqualTo(default(string)); + result.Should().Be("test"); + } + + [Fact] + public void JsonUtils_ParseJTokenToObject_For_Int() + { + // Assign + object value = 123; + + // Act + var result = JsonUtils.ParseJTokenToObject(value); + + // Assert + result.Should().Be(123); + } + + [Fact] + public void JsonUtils_ParseJTokenToObject_For_Invalid_Throws() + { + // Assign + object value = "{ }"; + + // Act + Action action = () => JsonUtils.ParseJTokenToObject(value); + + // Assert + action.Should().Throw(); } [Fact] diff --git a/test/WireMock.Net.Tests/Util/PathUtilsTests.cs b/test/WireMock.Net.Tests/Util/PathUtilsTests.cs index fe33da8d..9550513b 100644 --- a/test/WireMock.Net.Tests/Util/PathUtilsTests.cs +++ b/test/WireMock.Net.Tests/Util/PathUtilsTests.cs @@ -1,44 +1,43 @@ -using NFluent; +using NFluent; using System.IO; using WireMock.Util; using Xunit; -namespace WireMock.Net.Tests.Util +namespace WireMock.Net.Tests.Util; + +public class PathUtilsTests { - public class PathUtilsTests + [Theory] + [InlineData(@"subdirectory/MyXmlResponse.xml")] + [InlineData(@"subdirectory\MyXmlResponse.xml")] + public void PathUtils_CleanPath(string path) { - [Theory] - [InlineData(@"subdirectory/MyXmlResponse.xml")] - [InlineData(@"subdirectory\MyXmlResponse.xml")] - public void PathUtils_CleanPath(string path) - { - // Act - var cleanPath = PathUtils.CleanPath(path); + // Act + var cleanPath = PathUtils.CleanPath(path); - // Assert - Check.That(cleanPath).Equals("subdirectory" + Path.DirectorySeparatorChar + "MyXmlResponse.xml"); - } + // Assert + Check.That(cleanPath).Equals("subdirectory" + Path.DirectorySeparatorChar + "MyXmlResponse.xml"); + } - [Theory] - [InlineData(null, null)] - [InlineData("", "")] - [InlineData("a", "a")] - [InlineData(@"/", "")] - [InlineData(@"//", "")] - [InlineData(@"//a", "a")] - [InlineData(@"\", "")] - [InlineData(@"\\", "")] - [InlineData(@"\\a", "a")] - public void PathUtils_CleanPath_RemoveLeadingDirectorySeparators(string path, string expected) - { - // Arrange - var cleanPath = PathUtils.CleanPath(path); + [Theory] + [InlineData(null, null)] + [InlineData("", "")] + [InlineData("a", "a")] + [InlineData(@"/", "")] + [InlineData(@"//", "")] + [InlineData(@"//a", "a")] + [InlineData(@"\", "")] + [InlineData(@"\\", "")] + [InlineData(@"\\a", "a")] + public void PathUtils_CleanPath_RemoveLeadingDirectorySeparators(string path, string expected) + { + // Arrange + var cleanPath = PathUtils.CleanPath(path); - // Act - var withoutDirectorySeparators = PathUtils.RemoveLeadingDirectorySeparators(cleanPath); + // Act + var withoutDirectorySeparators = PathUtils.RemoveLeadingDirectorySeparators(cleanPath); - // Assert - Check.That(withoutDirectorySeparators).Equals(expected); - } + // Assert + Check.That(withoutDirectorySeparators).Equals(expected); } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Util/PortUtilsTests.cs b/test/WireMock.Net.Tests/Util/PortUtilsTests.cs index af3c18ef..ebf04918 100644 --- a/test/WireMock.Net.Tests/Util/PortUtilsTests.cs +++ b/test/WireMock.Net.Tests/Util/PortUtilsTests.cs @@ -3,92 +3,92 @@ using NFluent; using WireMock.Util; using Xunit; -namespace WireMock.Net.Tests.Util +namespace WireMock.Net.Tests.Util; + +public class PortUtilsTests { - public class PortUtilsTests + [Fact] + public void PortUtils_TryExtract_InvalidUrl_Returns_False() { - [Fact] - public void PortUtils_TryExtract_InvalidUrl_Returns_False() - { - // Assign - string url = "test"; + // Assign + string url = "test"; - // Act - bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); + // Act + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); - // Assert - result.Should().BeFalse(); - isHttps.Should().BeFalse(); - proto.Should().BeNull(); - host.Should().BeNull(); - port.Should().Be(default(int)); - } + // Assert + result.Should().BeFalse(); + isHttps.Should().BeFalse(); + proto.Should().BeNull(); + host.Should().BeNull(); + port.Should().Be(default(int)); + } - [Fact] - public void PortUtils_TryExtract_UrlIsMissingPort_Returns_False() - { - // Assign - string url = "http://0.0.0.0"; + [Fact] + public void PortUtils_TryExtract_UrlIsMissingPort_Returns_False() + { + // Assign + string url = "http://0.0.0.0"; - // Act - bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); + // Act + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); - // Assert - result.Should().BeFalse(); - isHttps.Should().BeFalse(); - proto.Should().BeNull(); - host.Should().BeNull(); - port.Should().Be(default(int)); - } + // Assert + result.Should().BeFalse(); + isHttps.Should().BeFalse(); + proto.Should().BeNull(); + host.Should().BeNull(); + port.Should().Be(default(int)); + } - [Fact] - public void PortUtils_TryExtract_Http_Returns_True() - { - // Assign - string url = "http://wiremock.net:1234"; + [Fact] + public void PortUtils_TryExtract_Http_Returns_True() + { + // Assign + string url = "http://wiremock.net:1234"; - // Act - bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); + // Act + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); - // Assert - result.Should().BeTrue(); - isHttps.Should().BeFalse(); - proto.Should().Be("http"); - host.Should().Be("wiremock.net"); - port.Should().Be(1234); - } + // Assert + result.Should().BeTrue(); + isHttps.Should().BeFalse(); + proto.Should().Be("http"); + host.Should().Be("wiremock.net"); + port.Should().Be(1234); + } - [Fact] - public void PortUtils_TryExtract_Https_Returns_True() - { - // Assign - string url = "https://wiremock.net:5000"; + [Fact] + public void PortUtils_TryExtract_Https_Returns_True() + { + // Assign + string url = "https://wiremock.net:5000"; - // Act - bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); + // Act + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); - // Assert - result.Should().BeTrue(); - isHttps.Should().BeTrue(); - proto.Should().Be("https"); - host.Should().Be("wiremock.net"); - port.Should().Be(5000); - } + // Assert + result.Should().BeTrue(); + isHttps.Should().BeTrue(); + proto.Should().Be("https"); + host.Should().Be("wiremock.net"); + port.Should().Be(5000); + } - [Fact] - public void PortUtils_TryExtract_Https0_0_0_0_Returns_True() - { - // Assign - string url = "https://0.0.0.0:5000"; + [Fact] + public void PortUtils_TryExtract_Https0_0_0_0_Returns_True() + { + // Assign + string url = "https://0.0.0.0:5000"; - // Act - bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); + // Act + bool result = PortUtils.TryExtract(url, out bool isHttps, out string proto, out string host, out int port); - // Assert - Check.That(result).IsTrue(); - Check.That(proto).IsEqualTo("https"); - Check.That(host).IsEqualTo("0.0.0.0"); - Check.That(port).IsEqualTo(5000); - } + // Assert + result.Should().BeTrue(); + isHttps.Should().BeTrue(); + proto.Should().Be("https"); + host.Should().Be("0.0.0.0"); + port.Should().Be(5000); } } \ No newline at end of file