diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs index 602d0cb1..ea5e628e 100644 --- a/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs @@ -89,4 +89,11 @@ public class SettingsModel /// Don't save the response-string in the LogEntry when WithBody(Func{IRequestMessage, string}) or WithBody(Func{IRequestMessage, Task{string}}) is used. (default set to false). /// public bool? DoNotSaveDynamicResponseInLogEntry { get; set; } + + /// + /// See . + /// + /// Default value = "All". + /// + public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { 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 ca80ec89..f747f7c5 100644 --- a/src/WireMock.Net.Abstractions/IRequestMessage.cs +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -28,7 +28,7 @@ public interface IRequestMessage /// /// The ProxyUrl (if a proxy is used). /// - string ProxyUrl { get; set; } + string? ProxyUrl { get; set; } /// /// Gets the DateTime. diff --git a/src/WireMock.Net.Abstractions/Types/QueryParameterMultipleValueSupport.cs b/src/WireMock.Net.Abstractions/Types/QueryParameterMultipleValueSupport.cs new file mode 100644 index 00000000..b8fe260e --- /dev/null +++ b/src/WireMock.Net.Abstractions/Types/QueryParameterMultipleValueSupport.cs @@ -0,0 +1,28 @@ +using System; + +namespace WireMock.Types; + +[Flags] +public enum QueryParameterMultipleValueSupport +{ + // Support none + None = 0x0, + + // Support "&" as multi-value-separator --> "key=value&key=anotherValue" + Ampersand = 0x1, + + // Support ";" as multi-value-separator --> "key=value;key=anotherValue" + SemiColon = 0x2, + + // Support "," as multi-value-separator --> "key=1,2" + Comma = 0x4, + + // Support "&" and ";" as multi-value-separator --> "key=value&key=anotherValue" and also "key=value;key=anotherValue" + AmpersandAndSemiColon = Ampersand | SemiColon, + + // Support "&" and ";" as multi-value-separator + NoComma = AmpersandAndSemiColon, + + // Support all multi-value-separators ("&" and ";" and ",") + All = Ampersand | SemiColon | Comma +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs index e9924ed1..d81e7aa3 100644 --- a/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs +++ b/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs @@ -1,21 +1,20 @@ #if NET46 || NETSTANDARD2_0 using System.Collections.Generic; -namespace WireMock.Net.OpenApiParser.Extensions +namespace WireMock.Net.OpenApiParser.Extensions; + +internal static class DictionaryExtensions { - internal static class DictionaryExtensions + public static bool TryAdd(this Dictionary? dictionary, TKey key, TValue value) { - public static bool TryAdd(this Dictionary dictionary, TKey key, TValue value) + if (dictionary is null || dictionary.ContainsKey(key)) { - if (dictionary is null || dictionary.ContainsKey(key)) - { - return false; - } - - dictionary[key] = value; - - return true; + return false; } + + dictionary[key] = value; + + return true; } } -#endif +#endif \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs index 2307f7fb..405a282e 100644 --- a/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs +++ b/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs @@ -1,92 +1,91 @@ -using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using WireMock.Net.OpenApiParser.Types; -namespace WireMock.Net.OpenApiParser.Extensions +namespace WireMock.Net.OpenApiParser.Extensions; + +internal static class OpenApiSchemaExtensions { - internal static class OpenApiSchemaExtensions + /// + /// https://stackoverflow.com/questions/48111459/how-to-define-a-property-that-can-be-string-or-null-in-openapi-swagger + /// + public static bool TryGetXNullable(this OpenApiSchema schema, out bool value) { - /// - /// https://stackoverflow.com/questions/48111459/how-to-define-a-property-that-can-be-string-or-null-in-openapi-swagger - /// - public static bool TryGetXNullable(this OpenApiSchema schema, out bool value) + value = false; + + if (schema.Extensions.TryGetValue("x-nullable", out IOpenApiExtension e) && e is OpenApiBoolean openApiBoolean) { - value = false; - - if (schema.Extensions.TryGetValue("x-nullable", out IOpenApiExtension e) && e is OpenApiBoolean openApiBoolean) - { - value = openApiBoolean.Value; - return true; - } - - return false; + value = openApiBoolean.Value; + return true; } - public static SchemaType GetSchemaType(this OpenApiSchema schema) + return false; + } + + public static SchemaType GetSchemaType(this OpenApiSchema? schema) + { + switch (schema?.Type) { - switch (schema?.Type) - { - case "object": - return SchemaType.Object; + case "object": + return SchemaType.Object; - case "array": - return SchemaType.Array; + case "array": + return SchemaType.Array; - case "integer": - return SchemaType.Integer; + case "integer": + return SchemaType.Integer; - case "number": - return SchemaType.Number; + case "number": + return SchemaType.Number; - case "boolean": - return SchemaType.Boolean; + case "boolean": + return SchemaType.Boolean; - case "string": - return SchemaType.String; + case "string": + return SchemaType.String; - case "file": - return SchemaType.File; + case "file": + return SchemaType.File; - default: - return SchemaType.Unknown; - } + default: + return SchemaType.Unknown; } + } - public static SchemaFormat GetSchemaFormat(this OpenApiSchema schema) + public static SchemaFormat GetSchemaFormat(this OpenApiSchema? schema) + { + switch (schema?.Format) { - switch (schema?.Format) - { - case "float": - return SchemaFormat.Float; + case "float": + return SchemaFormat.Float; - case "double": - return SchemaFormat.Double; + case "double": + return SchemaFormat.Double; - case "int32": - return SchemaFormat.Int32; + case "int32": + return SchemaFormat.Int32; - case "int64": - return SchemaFormat.Int64; + case "int64": + return SchemaFormat.Int64; - case "date": - return SchemaFormat.Date; + case "date": + return SchemaFormat.Date; - case "date-time": - return SchemaFormat.DateTime; + case "date-time": + return SchemaFormat.DateTime; - case "password": - return SchemaFormat.Password; + case "password": + return SchemaFormat.Password; - case "byte": - return SchemaFormat.Byte; + case "byte": + return SchemaFormat.Byte; - case "binary": - return SchemaFormat.Binary; + case "binary": + return SchemaFormat.Binary; - default: - return SchemaFormat.Undefined; - } + default: + return SchemaFormat.Undefined; } } } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs index d195c36c..f174ca18 100644 --- a/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs +++ b/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs @@ -1,97 +1,94 @@ -using System.IO; +using System.IO; using System.Linq; -using System.Runtime.InteropServices.ComTypes; using JetBrains.Annotations; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; -using SharpYaml.Model; using Stef.Validation; using WireMock.Net.OpenApiParser.Settings; using WireMock.Server; -namespace WireMock.Net.OpenApiParser.Extensions +namespace WireMock.Net.OpenApiParser.Extensions; + +/// +/// Some extension methods for . +/// +public static class WireMockServerExtensions { /// - /// Some extension methods for . + /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. /// - public static class WireMockServerExtensions + /// The WireMockServer instance + /// Path containing OpenAPI file to parse and use the mappings. + /// Returns diagnostic object containing errors detected during parsing + [PublicAPI] + public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic) { - /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. - /// - /// The WireMockServer instance - /// Path containing OpenAPI file to parse and use the mappings. - /// Returns diagnostic object containing errors detected during parsing - [PublicAPI] - public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic) - { - return WithMappingFromOpenApiFile(server, path, null, out diagnostic); - } + return WithMappingFromOpenApiFile(server, path, new WireMockOpenApiParserSettings(), out diagnostic); + } - /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. - /// - /// The WireMockServer instance - /// Path containing OpenAPI file to parse and use the mappings. - /// Returns diagnostic object containing errors detected during parsing - /// Additional settings - [PublicAPI] - public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) - { - Guard.NotNull(server, nameof(server)); - Guard.NotNullOrEmpty(path, nameof(path)); + /// + /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. + /// + /// The WireMockServer instance + /// Path containing OpenAPI file to parse and use the mappings. + /// Returns diagnostic object containing errors detected during parsing + /// Additional settings + [PublicAPI] + public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) + { + Guard.NotNull(server, nameof(server)); + Guard.NotNullOrEmpty(path, nameof(path)); - var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic); + var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic); - return server.WithMapping(mappings.ToArray()); - } + return server.WithMapping(mappings.ToArray()); + } - /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. - /// - /// The WireMockServer instance - /// Stream containing OpenAPI description to parse and use the mappings. - /// Returns diagnostic object containing errors detected during parsing - [PublicAPI] - public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic) - { - return WithMappingFromOpenApiStream(server, stream, null, out diagnostic); - } + /// + /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. + /// + /// The WireMockServer instance + /// Stream containing OpenAPI description to parse and use the mappings. + /// Returns diagnostic object containing errors detected during parsing + [PublicAPI] + public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic) + { + return WithMappingFromOpenApiStream(server, stream, new WireMockOpenApiParserSettings(), out diagnostic); + } - /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. - /// - /// The WireMockServer instance - /// Stream containing OpenAPI description to parse and use the mappings. - /// Additional settings - /// Returns diagnostic object containing errors detected during parsing - [PublicAPI] - public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) - { - Guard.NotNull(server, nameof(server)); - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(settings, nameof(settings)); + /// + /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. + /// + /// The WireMockServer instance + /// Stream containing OpenAPI description to parse and use the mappings. + /// Additional settings + /// Returns diagnostic object containing errors detected during parsing + [PublicAPI] + public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) + { + Guard.NotNull(server); + Guard.NotNull(stream); + Guard.NotNull(settings); - var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic); + var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic); - return server.WithMapping(mappings.ToArray()); - } + return server.WithMapping(mappings.ToArray()); + } - /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 document. - /// - /// The WireMockServer instance - /// The OpenAPI document to use as mappings. - /// Additional settings [optional] - [PublicAPI] - public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings = null) - { - Guard.NotNull(server, nameof(server)); - Guard.NotNull(document, nameof(document)); + /// + /// Register the mappings via an OpenAPI (swagger) V2 or V3 document. + /// + /// The WireMockServer instance + /// The OpenAPI document to use as mappings. + /// Additional settings [optional] + [PublicAPI] + public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings) + { + Guard.NotNull(server); + Guard.NotNull(document); - var mappings = new WireMockOpenApiParser().FromDocument(document, settings); + var mappings = new WireMockOpenApiParser().FromDocument(document, settings); - return server.WithMapping(mappings.ToArray()); - } + return server.WithMapping(mappings.ToArray()); } } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs b/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs index 660f890d..a22e144b 100644 --- a/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs +++ b/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; @@ -35,7 +35,7 @@ namespace WireMock.Net.OpenApiParser /// The source OpenApiDocument /// Additional settings [optional] /// MappingModel - IEnumerable FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings settings = null); + IEnumerable FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null); /// /// Generate from a . diff --git a/src/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs b/src/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs index 8b1e73b6..ae1701bc 100644 --- a/src/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs +++ b/src/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Microsoft.OpenApi; @@ -15,371 +16,365 @@ using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Utils; -namespace WireMock.Net.OpenApiParser.Mappers +namespace WireMock.Net.OpenApiParser.Mappers; + +internal class OpenApiPathsMapper { - internal class OpenApiPathsMapper + private const string HeaderContentType = "Content-Type"; + + private readonly WireMockOpenApiParserSettings _settings; + private readonly ExampleValueGenerator _exampleValueGenerator; + + public OpenApiPathsMapper(WireMockOpenApiParserSettings settings) { - private const string HeaderContentType = "Content-Type"; + _settings = Guard.NotNull(settings); + _exampleValueGenerator = new ExampleValueGenerator(settings); + } - private readonly WireMockOpenApiParserSettings _settings; - private readonly ExampleValueGenerator _exampleValueGenerator; + public IEnumerable ToMappingModels(OpenApiPaths paths, IList servers) + { + return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); + } - public OpenApiPathsMapper(WireMockOpenApiParserSettings settings) + private IEnumerable MapPaths(OpenApiPaths paths, IList servers) + { + return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); + } + + private IEnumerable MapPath(string path, OpenApiPathItem pathItem, IList servers) + { + return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)); + } + + private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList servers) + { + var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query); + var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path); + var headers = operation.Parameters.Where(p => p.In == ParameterLocation.Header); + + var response = operation.Responses.FirstOrDefault(); + + TryGetContent(response.Value?.Content, out OpenApiMediaType? responseContent, out string? responseContentType); + var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema; + var responseExample = responseContent?.Example; + var responseSchemaExample = responseContent?.Schema?.Example; + + var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) : + responseSchemaExample != null ? MapOpenApiAnyToJToken(responseSchemaExample) : + MapSchemaToObject(responseSchema); + + var requestBodyModel = new BodyModel(); + if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required) { - _settings = Guard.NotNull(settings, nameof(settings)); - _exampleValueGenerator = new ExampleValueGenerator(settings); + var request = operation.RequestBody.Content; + TryGetContent(request, out OpenApiMediaType? requestContent, out _); + + var requestBodySchema = operation.RequestBody.Content.First().Value?.Schema; + var requestBodyExample = requestContent!.Example; + var requestBodySchemaExample = requestContent.Schema?.Example; + + var requestBodyMapped = requestBodyExample != null ? MapOpenApiAnyToJToken(requestBodyExample) : + requestBodySchemaExample != null ? MapOpenApiAnyToJToken(requestBodySchemaExample) : + MapSchemaToObject(requestBodySchema); + + requestBodyModel = MapRequestBody(requestBodyMapped); } - public IEnumerable ToMappingModels(OpenApiPaths paths, IList servers) + if (!int.TryParse(response.Key, out var httpStatusCode)) { - return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); + httpStatusCode = 200; } - private IEnumerable MapPaths(OpenApiPaths paths, IList servers) + return new MappingModel { - return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x); - } - - private IEnumerable MapPath(string path, OpenApiPathItem pathItem, IList servers) - { - return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)); - } - - private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList servers) - { - var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query); - var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path); - var headers = operation.Parameters.Where(p => p.In == ParameterLocation.Header); - - var response = operation.Responses.FirstOrDefault(); - - TryGetContent(response.Value?.Content, out OpenApiMediaType responseContent, out string responseContentType); - var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema; - var responseExample = responseContent?.Example; - var responseSchemaExample = responseContent?.Schema?.Example; - - var body = responseExample != null ? MapOpenApiAnyToJToken(responseExample) : - responseSchemaExample != null ? MapOpenApiAnyToJToken(responseSchemaExample) : - MapSchemaToObject(responseSchema); - - var requestBodyModel = new BodyModel(); - if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required) + Guid = Guid.NewGuid(), + Request = new RequestModel { - var request = operation.RequestBody.Content; - TryGetContent(request, out OpenApiMediaType requestContent, out string requestContentType); - - var requestBodySchema = operation.RequestBody.Content.First().Value?.Schema; - var requestBodyExample = requestContent.Example; - var requestBodySchemaExample = requestContent.Schema?.Example; - - var requestBodyMapped = requestBodyExample != null ? MapOpenApiAnyToJToken(requestBodyExample) : - requestBodySchemaExample != null ? MapOpenApiAnyToJToken(requestBodySchemaExample) : - MapSchemaToObject(requestBodySchema); - - requestBodyModel = MapRequestBody(requestBodyMapped); + Methods = new[] { httpMethod }, + Path = MapBasePath(servers) + MapPathWithParameters(path, pathParameters), + Params = MapQueryParameters(queryParameters), + Headers = MapRequestHeaders(headers), + Body = requestBodyModel + }, + Response = new ResponseModel + { + StatusCode = httpStatusCode, + Headers = MapHeaders(responseContentType, response.Value?.Headers), + BodyAsJson = body } + }; + } - if (!int.TryParse(response.Key, out var httpStatusCode)) + private BodyModel? MapRequestBody(object? requestBody) + { + if (requestBody == null) + { + return null; + } + + return new BodyModel + { + Matcher = new MatcherModel { - httpStatusCode = 200; + Name = "JsonMatcher", + Pattern = JsonConvert.SerializeObject(requestBody, Formatting.Indented), + IgnoreCase = _settings.RequestBodyIgnoreCase } + }; + } - return new MappingModel - { - Guid = Guid.NewGuid(), - Request = new RequestModel + private bool TryGetContent(IDictionary? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType) + { + openApiMediaType = null; + contentType = null; + + if (contents == null || contents.Values.Count == 0) + { + return false; + } + + if (contents.TryGetValue("application/json", out var content)) + { + openApiMediaType = content; + contentType = "application/json"; + } + else + { + var first = contents.FirstOrDefault(); + openApiMediaType = first.Value; + contentType = first.Key; + } + + return true; + } + + private object? MapSchemaToObject(OpenApiSchema? schema, string? name = null) + { + if (schema == null) + { + return null; + } + + switch (schema.GetSchemaType()) + { + case SchemaType.Array: + var jArray = new JArray(); + for (int i = 0; i < _settings.NumberOfArrayItems; i++) { - Methods = new[] { httpMethod }, - Path = MapBasePath(servers) + MapPathWithParameters(path, pathParameters), - Params = MapQueryParameters(queryParameters), - Headers = MapRequestHeaders(headers), - Body = requestBodyModel - }, - Response = new ResponseModel - { - StatusCode = httpStatusCode, - Headers = MapHeaders(responseContentType, response.Value?.Headers), - BodyAsJson = body - } - }; - } - - private BodyModel MapRequestBody(object requestBody) - { - if (requestBody == null) - { - return null; - } - - return new BodyModel - { - Matcher = new MatcherModel - { - Name = "JsonMatcher", - Pattern = JsonConvert.SerializeObject(requestBody, Formatting.Indented), - IgnoreCase = _settings.RequestBodyIgnoreCase - } - }; - } - - private bool TryGetContent(IDictionary contents, out OpenApiMediaType openApiMediaType, out string contentType) - { - openApiMediaType = null; - contentType = null; - - if (contents == null || contents.Values.Count == 0) - { - return false; - } - - if (contents.TryGetValue("application/json", out var content)) - { - openApiMediaType = content; - contentType = "application/json"; - } - else - { - var first = contents.FirstOrDefault(); - openApiMediaType = first.Value; - contentType = first.Key; - } - - return true; - } - - private object MapSchemaToObject(OpenApiSchema schema, string name = null) - { - if (schema == null) - { - return null; - } - - switch (schema.GetSchemaType()) - { - case SchemaType.Array: - var jArray = new JArray(); - for (int i = 0; i < _settings.NumberOfArrayItems; i++) + if (schema.Items.Properties.Count > 0) { - if (schema.Items.Properties.Count > 0) + var arrayItem = new JObject(); + foreach (var property in schema.Items.Properties) { - var arrayItem = new JObject(); - foreach (var property in schema.Items.Properties) + var objectValue = MapSchemaToObject(property.Value, property.Key); + if (objectValue is JProperty jp) { - var objectValue = MapSchemaToObject(property.Value, property.Key); - if (objectValue is JProperty jp) - { - arrayItem.Add(jp); - } - else - { - arrayItem.Add(new JProperty(property.Key, objectValue)); - } + arrayItem.Add(jp); } - - jArray.Add(arrayItem); - } - else - { - jArray.Add(MapSchemaToObject(schema.Items, name)); - } - } - - if (schema.AllOf.Count > 0) - { - jArray.Add(MapSchemaAllOfToObject(schema)); - } - - return jArray; - - case SchemaType.Boolean: - case SchemaType.Integer: - case SchemaType.Number: - case SchemaType.String: - return _exampleValueGenerator.GetExampleValue(schema); - - case SchemaType.Object: - var propertyAsJObject = new JObject(); - foreach (var schemaProperty in schema.Properties) - { - propertyAsJObject.Add(MapPropertyAsJObject(schemaProperty.Value, schemaProperty.Key)); - } - if (schema.AllOf.Count > 0) - { - foreach (var property in schema.AllOf) - { - foreach (var item in property.Properties) + else { - propertyAsJObject.Add(MapPropertyAsJObject(item.Value, item.Key)); + arrayItem.Add(new JProperty(property.Key, objectValue)); } } + + jArray.Add(arrayItem); + } + else + { + jArray.Add(MapSchemaToObject(schema.Items, name)); } - - return name != null ? new JProperty(name, propertyAsJObject) : (JToken)propertyAsJObject; - - default: - return null; - } - } - - private JObject MapSchemaAllOfToObject(OpenApiSchema schema) - { - var arrayItem = new JObject(); - foreach (var property in schema.AllOf) - { - foreach (var item in property.Properties) - { - arrayItem.Add(MapPropertyAsJObject(item.Value, item.Key)); } - } - return arrayItem; - } - private object MapPropertyAsJObject(OpenApiSchema openApiSchema, string key) - { - if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array) - { - var mapped = MapSchemaToObject(openApiSchema, key); - if (mapped is JProperty jp) + if (schema.AllOf.Count > 0) { - return jp; + jArray.Add(MapSchemaAllOfToObject(schema)); } - else + + return jArray; + + case SchemaType.Boolean: + case SchemaType.Integer: + case SchemaType.Number: + case SchemaType.String: + return _exampleValueGenerator.GetExampleValue(schema); + + case SchemaType.Object: + var propertyAsJObject = new JObject(); + foreach (var schemaProperty in schema.Properties) { - return new JProperty(key, mapped); + propertyAsJObject.Add(MapPropertyAsJObject(schemaProperty.Value, schemaProperty.Key)); } - } - else + + if (schema.AllOf.Count > 0) + { + foreach (var property in schema.AllOf) + { + foreach (var item in property.Properties) + { + propertyAsJObject.Add(MapPropertyAsJObject(item.Value, item.Key)); + } + } + } + + return name != null ? new JProperty(name, propertyAsJObject) : propertyAsJObject; + + default: + return null; + } + } + + private JObject MapSchemaAllOfToObject(OpenApiSchema schema) + { + var arrayItem = new JObject(); + foreach (var property in schema.AllOf) + { + foreach (var item in property.Properties) { - // bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x); - return new JProperty(key, _exampleValueGenerator.GetExampleValue(openApiSchema)); + arrayItem.Add(MapPropertyAsJObject(item.Value, item.Key)); } } + return arrayItem; + } - private string MapPathWithParameters(string path, IEnumerable parameters) + private object MapPropertyAsJObject(OpenApiSchema openApiSchema, string key) + { + if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array) { - if (parameters == null) + var mapped = MapSchemaToObject(openApiSchema, key); + if (mapped is JProperty jp) { - return path; + return jp; } - string newPath = path; - foreach (var parameter in parameters) - { - var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse); - newPath = newPath.Replace($"{{{parameter.Name}}}", exampleMatcherModel.Pattern as string); - } - - return newPath; + return new JProperty(key, mapped); } - private string MapBasePath(IList servers) + // bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x); + return new JProperty(key, _exampleValueGenerator.GetExampleValue(openApiSchema)); + } + + private string MapPathWithParameters(string path, IEnumerable? parameters) + { + if (parameters == null) { - if (servers == null || servers.Count == 0) - { - return string.Empty; - } + return path; + } - OpenApiServer server = servers.First(); - if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult)) - { - return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString(); - } + string newPath = path; + foreach (var parameter in parameters) + { + var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse); + newPath = newPath.Replace($"{{{parameter.Name}}}", exampleMatcherModel.Pattern as string); + } + return newPath; + } + + private string MapBasePath(IList? servers) + { + if (servers == null || servers.Count == 0) + { return string.Empty; } - private JToken MapOpenApiAnyToJToken(IOpenApiAny any) + OpenApiServer server = servers.First(); + if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult)) { - if (any == null) - { - return null; - } - - using var outputString = new StringWriter(); - var writer = new OpenApiJsonWriter(outputString); - any.Write(writer, OpenApiSpecVersion.OpenApi3_0); - - if (any.AnyType == AnyType.Array) - { - return JArray.Parse(outputString.ToString()); - } - else - { - return JObject.Parse(outputString.ToString()); - } + return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString(); } - private IDictionary MapHeaders(string responseContentType, IDictionary headers) + return string.Empty; + } + + private JToken? MapOpenApiAnyToJToken(IOpenApiAny? any) + { + if (any == null) { - var mappedHeaders = headers.ToDictionary( - item => item.Key, - item => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern - ); - - if (!string.IsNullOrEmpty(responseContentType)) - { - mappedHeaders.TryAdd(HeaderContentType, responseContentType); - } - - return mappedHeaders.Keys.Any() ? mappedHeaders : null; + return null; } - private IList MapQueryParameters(IEnumerable queryParameters) + using var outputString = new StringWriter(); + var writer = new OpenApiJsonWriter(outputString); + any.Write(writer, OpenApiSpecVersion.OpenApi3_0); + + if (any.AnyType == AnyType.Array) { - var list = queryParameters - .Where(req => req.Required) - .Select(qp => new ParamModel + return JArray.Parse(outputString.ToString()); + } + + return JObject.Parse(outputString.ToString()); + } + + private IDictionary? MapHeaders(string responseContentType, IDictionary headers) + { + var mappedHeaders = headers.ToDictionary( + item => item.Key, + _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern + ); + + if (!string.IsNullOrEmpty(responseContentType)) + { + mappedHeaders.TryAdd(HeaderContentType, responseContentType); + } + + return mappedHeaders.Keys.Any() ? mappedHeaders : null; + } + + private IList? MapQueryParameters(IEnumerable queryParameters) + { + var list = queryParameters + .Where(req => req.Required) + .Select(qp => new ParamModel + { + Name = qp.Name, + IgnoreCase = _settings.QueryParameterPatternIgnoreCase, + Matchers = new[] { - Name = qp.Name, - IgnoreCase = _settings.QueryParameterPatternIgnoreCase, - Matchers = new[] - { - GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse) - } - }) - .ToList(); + GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse) + } + }) + .ToList(); - return list.Any() ? list : null; - } + return list.Any() ? list : null; + } - private IList MapRequestHeaders(IEnumerable headers) - { - var list = headers - .Where(req => req.Required) - .Select(qp => new HeaderModel + private IList? MapRequestHeaders(IEnumerable headers) + { + var list = headers + .Where(req => req.Required) + .Select(qp => new HeaderModel + { + Name = qp.Name, + IgnoreCase = _settings.HeaderPatternIgnoreCase, + Matchers = new[] { - Name = qp.Name, - IgnoreCase = _settings.HeaderPatternIgnoreCase, - Matchers = new[] - { - GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse) - } - }) - .ToList(); + GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse) + } + }) + .ToList(); - return list.Any() ? list : null; - } + return list.Any() ? list : null; + } - private MatcherModel GetExampleMatcherModel(OpenApiSchema schema, ExampleValueType type) + private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType type) + { + return type switch { - return type switch - { - ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues }, + ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues }, - _ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" } - }; - } + _ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" } + }; + } - private string GetExampleValueAsStringForSchemaType(OpenApiSchema schema) + private string GetExampleValueAsStringForSchemaType(OpenApiSchema? schema) + { + var value = _exampleValueGenerator.GetExampleValue(schema); + + return value switch { - var value = _exampleValueGenerator.GetExampleValue(schema); + string valueAsString => valueAsString, - return value switch - { - string valueAsString => valueAsString, - - _ => value.ToString(), - }; - } + _ => value.ToString(), + }; } } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs b/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs index cef841f1..be65c13f 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs @@ -1,58 +1,60 @@ -using System; +using System; using Microsoft.OpenApi.Models; -namespace WireMock.Net.OpenApiParser.Settings -{ - /// - /// A interface defining the example values to use for the different types. - /// - public interface IWireMockOpenApiParserExampleValues - { - /// - /// An example value for a Boolean. - /// - bool Boolean { get; set; } - /// - /// An example value for an Integer. - /// - int Integer { get; set; } - /// - /// An example value for a Float. - /// - float Float { get; set; } - /// - /// An example value for a Double. - /// - double Double { get; set; } - - /// - /// An example value for a Date. - /// - Func Date { get; set; } - - /// - /// An example value for a DateTime. - /// - Func DateTime { get; set; } - - /// - /// An example value for Bytes. - /// - byte[] Bytes { get; set; } - - /// - /// An example value for a Object. - /// - object Object { get; set; } - - /// - /// An example value for a String. - /// - string String { get; set; } - - /// - /// OpenApi Schema to generate dynamic examples more accurate - /// - OpenApiSchema Schema { get; set; } - } +namespace WireMock.Net.OpenApiParser.Settings; + +/// +/// A interface defining the example values to use for the different types. +/// +public interface IWireMockOpenApiParserExampleValues +{ + /// + /// An example value for a Boolean. + /// + bool Boolean { get; set; } + + /// + /// An example value for an Integer. + /// + int Integer { get; set; } + + /// + /// An example value for a Float. + /// + float Float { get; set; } + + /// + /// An example value for a Double. + /// + double Double { get; set; } + + /// + /// An example value for a Date. + /// + Func Date { get; set; } + + /// + /// An example value for a DateTime. + /// + Func DateTime { get; set; } + + /// + /// An example value for Bytes. + /// + byte[] Bytes { get; set; } + + /// + /// An example value for a Object. + /// + object Object { get; set; } + + /// + /// An example value for a String. + /// + string String { get; set; } + + /// + /// OpenApi Schema to generate dynamic examples more accurate + /// + OpenApiSchema? Schema { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs index 7856b887..1524fed3 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs @@ -1,34 +1,42 @@ -using System; +using System; using Microsoft.OpenApi.Models; -using RandomDataGenerator.FieldOptions; -using RandomDataGenerator.Randomizers; - -namespace WireMock.Net.OpenApiParser.Settings -{ - /// - /// A class defining the random example values to use for the different types. - /// - public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues - { - /// - public virtual bool Boolean { get { return RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true; } set { } } - /// - public virtual int Integer { get { return RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42; } set { } } - /// - public virtual float Float { get { return RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; } set { } } - /// - public virtual double Double { get { return RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; } set { } } - /// - public virtual Func Date { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; } set { } } - /// - public virtual Func DateTime { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; } set { } } - /// - public virtual byte[] Bytes { get { return RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate(); } set { } } - /// - public virtual object Object { get; set; } = "example-object"; - /// - public virtual string String { get { return RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; } set { } } - /// - public virtual OpenApiSchema Schema { get; set; } - } +using RandomDataGenerator.FieldOptions; +using RandomDataGenerator.Randomizers; + +namespace WireMock.Net.OpenApiParser.Settings; + +/// +/// A class defining the random example values to use for the different types. +/// +public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues +{ + /// + public virtual bool Boolean { get => RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true; set { } } + + /// + public virtual int Integer { get => RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42; set { } } + + /// + public virtual float Float { get => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; set { } } + + /// + public virtual double Double { get => RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; set { } } + + /// + public virtual Func Date { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; } set { } } + + /// + public virtual Func DateTime { get { return () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; } set { } } + + /// + public virtual byte[] Bytes { get => RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate(); set { } } + + /// + public virtual object Object { get; set; } = "example-object"; + + /// + public virtual string String { get => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; set { } } + + /// + public virtual OpenApiSchema? Schema { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs index ece47d02..6b3af509 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs @@ -1,32 +1,40 @@ -using System; +using System; using Microsoft.OpenApi.Models; -namespace WireMock.Net.OpenApiParser.Settings -{ - /// - /// A class defining the example values to use for the different types. - /// - public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues - { - /// - public virtual bool Boolean { get; set; } = true; - /// - public virtual int Integer { get; set; } = 42; - /// - public virtual float Float { get; set; } = 4.2f; - /// - public virtual double Double { get; set; } = 4.2d; - /// - public virtual Func Date { get; set; } = () => System.DateTime.UtcNow.Date; - /// - public virtual Func DateTime { get; set; } = () => System.DateTime.UtcNow; - /// - public virtual byte[] Bytes { get; set; } = { 48, 49, 50 }; - /// - public virtual object Object { get; set; } = "example-object"; - /// - public virtual string String { get; set; } = "example-string"; - /// - public virtual OpenApiSchema Schema { get; set; } = new OpenApiSchema(); - } +namespace WireMock.Net.OpenApiParser.Settings; + +/// +/// A class defining the example values to use for the different types. +/// +public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues +{ + /// + public virtual bool Boolean { get; set; } = true; + + /// + public virtual int Integer { get; set; } = 42; + + /// + public virtual float Float { get; set; } = 4.2f; + + /// + public virtual double Double { get; set; } = 4.2d; + + /// + public virtual Func Date { get; set; } = () => System.DateTime.UtcNow.Date; + + /// + public virtual Func DateTime { get; set; } = () => System.DateTime.UtcNow; + + /// + public virtual byte[] Bytes { get; set; } = { 48, 49, 50 }; + + /// + public virtual object Object { get; set; } = "example-object"; + + /// + public virtual string String { get; set; } = "example-string"; + + /// + public virtual OpenApiSchema? Schema { get; set; } = new OpenApiSchema(); } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserSettings.cs b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserSettings.cs index 1839f6c9..cb91f846 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserSettings.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserSettings.cs @@ -1,64 +1,63 @@ using WireMock.Net.OpenApiParser.Types; -namespace WireMock.Net.OpenApiParser.Settings +namespace WireMock.Net.OpenApiParser.Settings; + +/// +/// The WireMockOpenApiParser Settings +/// +public class WireMockOpenApiParserSettings { /// - /// The WireMockOpenApiParser Settings + /// The number of array items to generate (default is 3). /// - public class WireMockOpenApiParserSettings - { - /// - /// The number of array items to generate (default is 3). - /// - public int NumberOfArrayItems { get; set; } = 3; + public int NumberOfArrayItems { get; set; } = 3; - /// - /// The example value type to use when generating a Path - /// - public ExampleValueType PathPatternToUse { get; set; } = ExampleValueType.Value; + /// + /// The example value type to use when generating a Path + /// + public ExampleValueType PathPatternToUse { get; set; } = ExampleValueType.Value; - /// - /// The example value type to use when generating a Header - /// - public ExampleValueType HeaderPatternToUse { get; set; } = ExampleValueType.Value; + /// + /// The example value type to use when generating a Header + /// + public ExampleValueType HeaderPatternToUse { get; set; } = ExampleValueType.Value; - /// - /// The example value type to use when generating a Query Parameter - /// - public ExampleValueType QueryParameterPatternToUse { get; set; } = ExampleValueType.Value; + /// + /// The example value type to use when generating a Query Parameter + /// + public ExampleValueType QueryParameterPatternToUse { get; set; } = ExampleValueType.Value; - /// - /// The example values to use. - /// - /// Default implementations are: - /// - - /// - - /// - public IWireMockOpenApiParserExampleValues ExampleValues { get; set; } + /// + /// The example values to use. + /// + /// Default implementations are: + /// - + /// - + /// + public IWireMockOpenApiParserExampleValues? ExampleValues { get; set; } - /// - /// Is a Header match case insensitive? (default is true). - /// - public bool HeaderPatternIgnoreCase { get; set; } = true; + /// + /// Is a Header match case insensitive? (default is true). + /// + public bool HeaderPatternIgnoreCase { get; set; } = true; - /// - /// Is a Query Parameter match case insensitive? (default is true). - /// - public bool QueryParameterPatternIgnoreCase { get; set; } = true; + /// + /// Is a Query Parameter match case insensitive? (default is true). + /// + public bool QueryParameterPatternIgnoreCase { get; set; } = true; - /// - /// Is a Request Body match case insensitive? (default is true). - /// - public bool RequestBodyIgnoreCase { get; set; } = true; + /// + /// Is a Request Body match case insensitive? (default is true). + /// + public bool RequestBodyIgnoreCase { get; set; } = true; - /// - /// Is a ExampleValue match case insensitive? (default is true). - /// - public bool IgnoreCaseExampleValues { get; set; } = true; + /// + /// Is a ExampleValue match case insensitive? (default is true). + /// + public bool IgnoreCaseExampleValues { get; set; } = true; - /// - /// Are examples generated dynamically? (default is false). - /// - public bool DynamicExamples { get; set; } = false; - } + /// + /// Are examples generated dynamically? (default is false). + /// + public bool DynamicExamples { get; set; } = false; } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Types/ExampleValueType.cs b/src/WireMock.Net.OpenApiParser/Types/ExampleValueType.cs index 19d63170..aabbfca9 100644 --- a/src/WireMock.Net.OpenApiParser/Types/ExampleValueType.cs +++ b/src/WireMock.Net.OpenApiParser/Types/ExampleValueType.cs @@ -1,20 +1,19 @@ -namespace WireMock.Net.OpenApiParser.Types +namespace WireMock.Net.OpenApiParser.Types; + +/// +/// The example value to use +/// +public enum ExampleValueType { /// - /// The example value to use + /// 1. Use a generated example value based on the SchemaType (default). + /// 2. If there is no example value defined in the schema, + /// then the will be used (custom, fixed or dynamic). /// - public enum ExampleValueType - { - /// - /// 1. Use a generated example value based on the SchemaType (default). - /// 2. If there is no example value defined in the schema, - /// then the will be used (custom, fixed or dynamic). - /// - Value, + Value, - /// - /// Just use a Wildcard (*) character. - /// - Wildcard - } + /// + /// Just use a Wildcard (*) character. + /// + Wildcard } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs b/src/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs index 20e6b017..0924ed24 100644 --- a/src/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs +++ b/src/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs @@ -1,25 +1,24 @@ -namespace WireMock.Net.OpenApiParser.Types +namespace WireMock.Net.OpenApiParser.Types; + +internal enum SchemaFormat { - internal enum SchemaFormat - { - Float, + Float, - Double, + Double, - Int32, + Int32, - Int64, + Int64, - Date, + Date, - DateTime, + DateTime, - Password, + Password, - Byte, + Byte, - Binary, + Binary, - Undefined - } + Undefined } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Types/SchemaType.cs b/src/WireMock.Net.OpenApiParser/Types/SchemaType.cs index f6152347..7ccb46bf 100644 --- a/src/WireMock.Net.OpenApiParser/Types/SchemaType.cs +++ b/src/WireMock.Net.OpenApiParser/Types/SchemaType.cs @@ -1,21 +1,20 @@ -namespace WireMock.Net.OpenApiParser.Types +namespace WireMock.Net.OpenApiParser.Types; + +internal enum SchemaType { - internal enum SchemaType - { - Object, + Object, - Array, + Array, - String, + String, - Integer, + Integer, - Number, + Number, - Boolean, + Boolean, - File, + File, - Unknown - } + Unknown } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs b/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs index d7ef7435..dbd5d52c 100644 --- a/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs +++ b/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs @@ -1,18 +1,17 @@ -using System; +using System; using System.Globalization; -namespace WireMock.Net.OpenApiParser.Utils -{ - internal static class DateTimeUtils - { - public static string ToRfc3339DateTime(DateTime dateTime) - { - return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo); - } +namespace WireMock.Net.OpenApiParser.Utils; - public static string ToRfc3339Date(DateTime dateTime) - { - return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo); - } +internal static class DateTimeUtils +{ + public static string ToRfc3339DateTime(DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo); + } + + public static string ToRfc3339Date(DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo); } } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs b/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs index 55aad19c..2bc15d1a 100644 --- a/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs +++ b/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs @@ -7,121 +7,120 @@ using WireMock.Net.OpenApiParser.Extensions; using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Types; -namespace WireMock.Net.OpenApiParser.Utils +namespace WireMock.Net.OpenApiParser.Utils; + +internal class ExampleValueGenerator { - internal class ExampleValueGenerator + private readonly WireMockOpenApiParserSettings _settings; + + public ExampleValueGenerator(WireMockOpenApiParserSettings settings) { - private readonly WireMockOpenApiParserSettings _settings; + _settings = Guard.NotNull(settings); - public ExampleValueGenerator(WireMockOpenApiParserSettings settings) + // Check if user provided an own implementation + if (settings.ExampleValues is null) { - _settings = Guard.NotNull(settings, nameof(settings)); - - // Check if user provided an own implementation - if (settings.ExampleValues is null) + if (_settings.DynamicExamples) { - if (_settings.DynamicExamples) - { - _settings.ExampleValues = new WireMockOpenApiParserDynamicExampleValues(); - } - else - { - _settings.ExampleValues = new WireMockOpenApiParserExampleValues(); - } + _settings.ExampleValues = new WireMockOpenApiParserDynamicExampleValues(); } - } - - public object GetExampleValue(OpenApiSchema schema) - { - var schemaExample = schema?.Example; - var schemaEnum = GetRandomEnumValue(schema?.Enum); - - _settings.ExampleValues.Schema = schema; - - switch (schema?.GetSchemaType()) + else { - case SchemaType.Boolean: - var exampleBoolean = (OpenApiBoolean)schemaExample; - return exampleBoolean is null ? _settings.ExampleValues.Boolean : exampleBoolean.Value; - - case SchemaType.Integer: - switch (schema?.GetSchemaFormat()) - { - case SchemaFormat.Int64: - var exampleLong = (OpenApiLong)schemaExample; - var enumLong = (OpenApiLong)schemaEnum; - var valueLongEnumOrExample = enumLong is null ? exampleLong?.Value : enumLong?.Value; - return valueLongEnumOrExample ?? _settings.ExampleValues.Integer; - - default: - var exampleInteger = (OpenApiInteger)schemaExample; - var enumInteger = (OpenApiInteger)schemaEnum; - var valueIntegerEnumOrExample = enumInteger is null ? exampleInteger?.Value : enumInteger?.Value; - return valueIntegerEnumOrExample ?? _settings.ExampleValues.Integer; - } - - case SchemaType.Number: - switch (schema?.GetSchemaFormat()) - { - case SchemaFormat.Float: - var exampleFloat = (OpenApiFloat)schemaExample; - var enumFloat = (OpenApiFloat)schemaEnum; - var valueFloatEnumOrExample = enumFloat is null ? exampleFloat?.Value : enumFloat?.Value; - return valueFloatEnumOrExample ?? _settings.ExampleValues.Float; - - default: - var exampleDouble = (OpenApiDouble)schemaExample; - var enumDouble = (OpenApiDouble)schemaEnum; - var valueDoubleEnumOrExample = enumDouble is null ? exampleDouble?.Value : enumDouble?.Value; - return valueDoubleEnumOrExample ?? _settings.ExampleValues.Double; - } - - default: - switch (schema?.GetSchemaFormat()) - { - case SchemaFormat.Date: - var exampleDate = (OpenApiDate)schemaExample; - var enumDate = (OpenApiDate)schemaEnum; - var valueDateEnumOrExample = enumDate is null ? exampleDate?.Value : enumDate?.Value; - return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _settings.ExampleValues.Date()); - - case SchemaFormat.DateTime: - var exampleDateTime = (OpenApiDateTime)schemaExample; - var enumDateTime = (OpenApiDateTime)schemaEnum; - var valueDateTimeEnumOrExample = enumDateTime is null ? exampleDateTime?.Value : enumDateTime?.Value; - return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _settings.ExampleValues.DateTime()); - - case SchemaFormat.Byte: - var exampleByte = (OpenApiByte)schemaExample; - var enumByte = (OpenApiByte)schemaEnum; - var valueByteEnumOrExample = enumByte is null ? exampleByte?.Value : enumByte?.Value; - return valueByteEnumOrExample ?? _settings.ExampleValues.Bytes; - - case SchemaFormat.Binary: - var exampleBinary = (OpenApiBinary)schemaExample; - var enumBinary = (OpenApiBinary)schemaEnum; - var valueBinaryEnumOrExample = enumBinary is null ? exampleBinary?.Value : enumBinary?.Value; - return valueBinaryEnumOrExample ?? _settings.ExampleValues.Object; - - default: - var exampleString = (OpenApiString)schemaExample; - var enumString = (OpenApiString)schemaEnum; - var valueStringEnumOrExample = enumString is null ? exampleString?.Value : enumString?.Value; - return valueStringEnumOrExample ?? _settings.ExampleValues.String; - } + _settings.ExampleValues = new WireMockOpenApiParserExampleValues(); } } - - private static IOpenApiAny GetRandomEnumValue(IList schemaEnum) - { - if (schemaEnum?.Count > 0) - { - int maxValue = schemaEnum.Count - 1; - int randomEnum = new Random().Next(0, maxValue); - return schemaEnum[randomEnum]; - } - - return null; - } + } + + public object GetExampleValue(OpenApiSchema? schema) + { + var schemaExample = schema?.Example; + var schemaEnum = GetRandomEnumValue(schema?.Enum); + + _settings.ExampleValues.Schema = schema; + + switch (schema?.GetSchemaType()) + { + case SchemaType.Boolean: + var exampleBoolean = schemaExample as OpenApiBoolean; + return exampleBoolean is null ? _settings.ExampleValues.Boolean : exampleBoolean.Value; + + case SchemaType.Integer: + switch (schema?.GetSchemaFormat()) + { + case SchemaFormat.Int64: + var exampleLong = (OpenApiLong)schemaExample; + var enumLong = (OpenApiLong)schemaEnum; + var valueLongEnumOrExample = enumLong is null ? exampleLong?.Value : enumLong?.Value; + return valueLongEnumOrExample ?? _settings.ExampleValues.Integer; + + default: + var exampleInteger = (OpenApiInteger)schemaExample; + var enumInteger = (OpenApiInteger)schemaEnum; + var valueIntegerEnumOrExample = enumInteger is null ? exampleInteger?.Value : enumInteger?.Value; + return valueIntegerEnumOrExample ?? _settings.ExampleValues.Integer; + } + + case SchemaType.Number: + switch (schema?.GetSchemaFormat()) + { + case SchemaFormat.Float: + var exampleFloat = (OpenApiFloat)schemaExample; + var enumFloat = (OpenApiFloat)schemaEnum; + var valueFloatEnumOrExample = enumFloat is null ? exampleFloat?.Value : enumFloat?.Value; + return valueFloatEnumOrExample ?? _settings.ExampleValues.Float; + + default: + var exampleDouble = (OpenApiDouble)schemaExample; + var enumDouble = (OpenApiDouble)schemaEnum; + var valueDoubleEnumOrExample = enumDouble is null ? exampleDouble?.Value : enumDouble?.Value; + return valueDoubleEnumOrExample ?? _settings.ExampleValues.Double; + } + + default: + switch (schema?.GetSchemaFormat()) + { + case SchemaFormat.Date: + var exampleDate = (OpenApiDate)schemaExample; + var enumDate = (OpenApiDate)schemaEnum; + var valueDateEnumOrExample = enumDate is null ? exampleDate?.Value : enumDate?.Value; + return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _settings.ExampleValues.Date()); + + case SchemaFormat.DateTime: + var exampleDateTime = (OpenApiDateTime)schemaExample; + var enumDateTime = (OpenApiDateTime)schemaEnum; + var valueDateTimeEnumOrExample = enumDateTime is null ? exampleDateTime?.Value : enumDateTime?.Value; + return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _settings.ExampleValues.DateTime()); + + case SchemaFormat.Byte: + var exampleByte = (OpenApiByte)schemaExample; + var enumByte = (OpenApiByte)schemaEnum; + var valueByteEnumOrExample = enumByte is null ? exampleByte?.Value : enumByte?.Value; + return valueByteEnumOrExample ?? _settings.ExampleValues.Bytes; + + case SchemaFormat.Binary: + var exampleBinary = (OpenApiBinary)schemaExample; + var enumBinary = (OpenApiBinary)schemaEnum; + var valueBinaryEnumOrExample = enumBinary is null ? exampleBinary?.Value : enumBinary?.Value; + return valueBinaryEnumOrExample ?? _settings.ExampleValues.Object; + + default: + var exampleString = (OpenApiString)schemaExample; + var enumString = (OpenApiString)schemaEnum; + var valueStringEnumOrExample = enumString is null ? exampleString?.Value : enumString?.Value; + return valueStringEnumOrExample ?? _settings.ExampleValues.String; + } + } + } + + private static IOpenApiAny? GetRandomEnumValue(IList? schemaEnum) + { + if (schemaEnum?.Count > 0) + { + int maxValue = schemaEnum.Count - 1; + int randomEnum = new Random().Next(0, maxValue); + return schemaEnum[randomEnum]; + } + + return null; } } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj index 82b29165..93fdc3c9 100644 --- a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj +++ b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj @@ -12,7 +12,7 @@ ../WireMock.Net/WireMock.Net.snk true MIT - 8.0 + 10 @@ -22,6 +22,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs b/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs index 297c6033..e9ba071c 100644 --- a/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs +++ b/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using JetBrains.Annotations; @@ -9,60 +9,59 @@ using WireMock.Admin.Mappings; using WireMock.Net.OpenApiParser.Mappers; using WireMock.Net.OpenApiParser.Settings; -namespace WireMock.Net.OpenApiParser +namespace WireMock.Net.OpenApiParser; + +/// +/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels. +/// +public class WireMockOpenApiParser : IWireMockOpenApiParser { - /// - /// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels. - /// - public class WireMockOpenApiParser : IWireMockOpenApiParser + private readonly OpenApiStreamReader _reader = new OpenApiStreamReader(); + + /// + [PublicAPI] + public IEnumerable FromFile(string path, out OpenApiDiagnostic diagnostic) { - private readonly OpenApiStreamReader _reader = new OpenApiStreamReader(); + return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic); + } - /// - [PublicAPI] - public IEnumerable FromFile(string path, out OpenApiDiagnostic diagnostic) + /// + [PublicAPI] + public IEnumerable FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) + { + OpenApiDocument document; + if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase)) { - return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic); + diagnostic = new OpenApiDiagnostic(); + document = new RamlConverter().ConvertToOpenApiDocument(path); + } + else + { + var reader = new OpenApiStreamReader(); + document = reader.Read(File.OpenRead(path), out diagnostic); } - /// - [PublicAPI] - public IEnumerable FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) - { - OpenApiDocument document; - if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase)) - { - diagnostic = new OpenApiDiagnostic(); - document = new RamlConverter().ConvertToOpenApiDocument(path); - } - else - { - var reader = new OpenApiStreamReader(); - document = reader.Read(File.OpenRead(path), out diagnostic); - } + return FromDocument(document, settings); + } - return FromDocument(document, settings); - } + /// + [PublicAPI] + public IEnumerable FromStream(Stream stream, out OpenApiDiagnostic diagnostic) + { + return FromDocument(_reader.Read(stream, out diagnostic)); + } - /// - [PublicAPI] - public IEnumerable FromStream(Stream stream, out OpenApiDiagnostic diagnostic) - { - return FromDocument(_reader.Read(stream, out diagnostic)); - } + /// + [PublicAPI] + public IEnumerable FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) + { + return FromDocument(_reader.Read(stream, out diagnostic), settings); + } - /// - [PublicAPI] - public IEnumerable FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) - { - return FromDocument(_reader.Read(stream, out diagnostic), settings); - } - - /// - [PublicAPI] - public IEnumerable FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings settings = null) - { - return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers); - } + /// + [PublicAPI] + public IEnumerable FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null) + { + return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers); } } \ No newline at end of file diff --git a/src/WireMock.Net.StandAlone/StandAloneApp.cs b/src/WireMock.Net.StandAlone/StandAloneApp.cs index 07ab2c40..e2272978 100644 --- a/src/WireMock.Net.StandAlone/StandAloneApp.cs +++ b/src/WireMock.Net.StandAlone/StandAloneApp.cs @@ -15,7 +15,7 @@ namespace WireMock.Net.StandAlone; /// public static class StandAloneApp { - private static readonly string Version = typeof(StandAloneApp).GetTypeInfo().Assembly.GetName().Version.ToString(); + private static readonly string Version = typeof(StandAloneApp).GetTypeInfo().Assembly.GetName().Version!.ToString(); /// /// Start WireMock.Net standalone Server based on the WireMockServerSettings. diff --git a/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs index 929e3994..493df3e2 100644 --- a/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs @@ -3,13 +3,13 @@ using System.Collections.Concurrent; using WireMock.Handlers; using WireMock.Logging; using WireMock.Matchers; +using WireMock.Types; using WireMock.Util; #if !USE_ASPNETCORE using Owin; #else using IAppBuilder = Microsoft.AspNetCore.Builder.IApplicationBuilder; using Microsoft.Extensions.DependencyInjection; -using WireMock.Types; #endif namespace WireMock.Owin; @@ -70,5 +70,7 @@ internal interface IWireMockMiddlewareOptions bool? SaveUnmatchedRequests { get; set; } - public bool? DoNotSaveDynamicResponseInLogEntry { get; set; } + bool? DoNotSaveDynamicResponseInLogEntry { get; set; } + + QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs index 1b6c920f..c26c2f78 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs @@ -81,11 +81,11 @@ namespace WireMock.Owin.Mappers var statusCodeType = responseMessage.StatusCode?.GetType(); switch (statusCodeType) { - case Type typeAsIntOrEnum when typeAsIntOrEnum == typeof(int) || typeAsIntOrEnum == typeof(int?) || typeAsIntOrEnum.GetTypeInfo().IsEnum: + case { } typeAsIntOrEnum when typeAsIntOrEnum == typeof(int) || typeAsIntOrEnum == typeof(int?) || typeAsIntOrEnum.GetTypeInfo().IsEnum: response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!); break; - case Type typeAsString when typeAsString == typeof(string): + case { } typeAsString when typeAsString == typeof(string): // Note: this case will also match on null int.TryParse(responseMessage.StatusCode as string, out int result); response.StatusCode = MapStatusCode(result); @@ -130,7 +130,7 @@ namespace WireMock.Owin.Mappers switch (responseMessage.BodyData?.DetectedBodyType) { case BodyType.String: - return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString); + return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!); case BodyType.Json: var formatting = responseMessage.BodyData.BodyAsJsonIndented == true @@ -143,7 +143,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; @@ -161,7 +161,7 @@ namespace WireMock.Owin.Mappers }); // Set other headers - foreach (var item in responseMessage.Headers) + foreach (var item in responseMessage.Headers!) { var headerName = item.Key; var value = item.Value; diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs index 72cbe3cf..1d2c4a0e 100644 --- a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs @@ -87,4 +87,7 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions /// public bool? DoNotSaveDynamicResponseInLogEntry { get; set; } + + /// + public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; } } \ No newline at end of file diff --git a/src/WireMock.Net/RequestMessage.cs b/src/WireMock.Net/RequestMessage.cs index 2470ee0f..dd44bb3f 100644 --- a/src/WireMock.Net/RequestMessage.cs +++ b/src/WireMock.Net/RequestMessage.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net; using Stef.Validation; using WireMock.Models; +using WireMock.Owin; using WireMock.Types; using WireMock.Util; @@ -26,7 +27,7 @@ public class RequestMessage : IRequestMessage public string AbsoluteUrl { get; } /// - public string ProxyUrl { get; set; } + public string? ProxyUrl { get; set; } /// public DateTime DateTime { get; set; } @@ -91,16 +92,36 @@ public class RequestMessage : IRequestMessage /// public string Origin { get; } + /// + /// Used for Unit Testing + /// + public RequestMessage( + UrlDetails urlDetails, + string method, + string clientIP, + IBodyData? bodyData = null, + IDictionary? headers = null, + IDictionary? cookies = null) : this(null, urlDetails, method, clientIP, bodyData, headers, cookies) + { + } + /// /// Initializes a new instance of the class. /// + /// The. /// The original url details. /// The HTTP method. /// The client IP Address. /// The BodyData. /// The headers. /// The cookies. - public RequestMessage(UrlDetails urlDetails, string method, string clientIP, IBodyData? bodyData = null, IDictionary? headers = null, IDictionary? cookies = null) + internal RequestMessage( + IWireMockMiddlewareOptions? options, + UrlDetails urlDetails, string method, + string clientIP, + IBodyData? bodyData = null, + IDictionary? headers = null, + IDictionary? cookies = null) { Guard.NotNull(urlDetails, nameof(urlDetails)); Guard.NotNull(method, nameof(method)); @@ -134,7 +155,7 @@ public class RequestMessage : IRequestMessage Headers = headers?.ToDictionary(header => header.Key, header => new WireMockList(header.Value)); Cookies = cookies; RawQuery = urlDetails.Url.Query; - Query = QueryStringParser.Parse(RawQuery); + Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport); } /// diff --git a/src/WireMock.Net/Server/IRespondWithAProvider.cs b/src/WireMock.Net/Server/IRespondWithAProvider.cs index b73d3389..55751738 100644 --- a/src/WireMock.Net/Server/IRespondWithAProvider.cs +++ b/src/WireMock.Net/Server/IRespondWithAProvider.cs @@ -125,9 +125,9 @@ public interface IRespondWithAProvider /// /// Support FireAndForget for any configured Webhooks /// - /// + /// /// - IRespondWithAProvider WithWebhookFireAndForget(bool UseWebhooksFireAndForget); + IRespondWithAProvider WithWebhookFireAndForget(bool useWebhooksFireAndForget); /// /// Add a Webhook to call after the response has been generated. @@ -141,7 +141,7 @@ public interface IRespondWithAProvider /// The . IRespondWithAProvider WithWebhook( string url, - string? method = "post", + string method = "post", IDictionary>? headers = null, string? body = null, bool useTransformer = true, @@ -160,7 +160,7 @@ public interface IRespondWithAProvider /// The . IRespondWithAProvider WithWebhook( string url, - string? method = "post", + string method = "post", IDictionary>? headers = null, object? body = null, bool useTransformer = true, diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net/Server/RespondWithAProvider.cs index d2125bee..25d12286 100644 --- a/src/WireMock.Net/Server/RespondWithAProvider.cs +++ b/src/WireMock.Net/Server/RespondWithAProvider.cs @@ -30,7 +30,7 @@ internal class RespondWithAProvider : IRespondWithAProvider private readonly WireMockServerSettings _settings; private readonly bool _saveToFile; - private bool _useWebhookFireAndForget = false; + private bool _useWebhookFireAndForget; public Guid Guid { get; private set; } = Guid.NewGuid(); diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 8fd41124..89a11626 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -223,6 +223,7 @@ public partial class WireMockServer WatchStaticMappingsInSubdirectories = _settings.WatchStaticMappingsInSubdirectories, HostingScheme = _settings.HostingScheme, DoNotSaveDynamicResponseInLogEntry = _settings.DoNotSaveDynamicResponseInLogEntry, + QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport, #if USE_ASPNETCORE CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString() @@ -252,6 +253,7 @@ public partial class WireMockServer _settings.WatchStaticMappings = settings.WatchStaticMappings; _settings.WatchStaticMappingsInSubdirectories = settings.WatchStaticMappingsInSubdirectories; _settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry; + _settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport; InitSettings(_settings); diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net/Server/WireMockServer.cs index 085cc073..52e80ef0 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net/Server/WireMockServer.cs @@ -296,6 +296,7 @@ public partial class WireMockServer : IWireMockServer _options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously; _options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; _options.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry; + _options.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport; if (settings.CustomCertificateDefined) { diff --git a/src/WireMock.Net/Settings/WireMockServerSettings.cs b/src/WireMock.Net/Settings/WireMockServerSettings.cs index 4b525fd2..7422046a 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettings.cs @@ -258,6 +258,14 @@ namespace WireMock.Settings [PublicAPI] public bool? DoNotSaveDynamicResponseInLogEntry { get; set; } + /// + /// See . + /// + /// Default value = "All". + /// + [PublicAPI] + public QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; } + /// /// Custom matcher mappings for static mappings /// diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index f9b77879..7b917d77 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Stef.Validation; @@ -55,7 +54,8 @@ public static class WireMockServerSettingsParser WatchStaticMappings = parser.GetBoolValue("WatchStaticMappings"), WatchStaticMappingsInSubdirectories = parser.GetBoolValue("WatchStaticMappingsInSubdirectories"), HostingScheme = parser.GetEnumValue(nameof(WireMockServerSettings.HostingScheme)), - DoNotSaveDynamicResponseInLogEntry = parser.GetBoolValue(nameof(WireMockServerSettings.DoNotSaveDynamicResponseInLogEntry)) + DoNotSaveDynamicResponseInLogEntry = parser.GetBoolValue(nameof(WireMockServerSettings.DoNotSaveDynamicResponseInLogEntry)), + QueryParameterMultipleValueSupport = parser.GetEnumValue(nameof(WireMockServerSettings.QueryParameterMultipleValueSupport)) }; #if USE_ASPNETCORE diff --git a/src/WireMock.Net/Util/QueryStringParser.cs b/src/WireMock.Net/Util/QueryStringParser.cs index 293fcd57..8e0e5f14 100644 --- a/src/WireMock.Net/Util/QueryStringParser.cs +++ b/src/WireMock.Net/Util/QueryStringParser.cs @@ -11,25 +11,41 @@ namespace WireMock.Util; /// internal static class QueryStringParser { - public static IDictionary> Parse(string queryString) + private static readonly Dictionary> Empty = new(); + + public static IDictionary> Parse(string? queryString, QueryParameterMultipleValueSupport? support = null) { if (string.IsNullOrEmpty(queryString)) { - return new Dictionary>(); + return Empty; } + var queryParameterMultipleValueSupport = support ?? QueryParameterMultipleValueSupport.All; + string[] JoinParts(string[] parts) { if (parts.Length > 1) { - return parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); // support "?key=1,2" + return queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.Comma) ? + parts[1].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) : // Support "?key=1,2" + new[] { parts[1] }; } return new string[0]; } - return queryString.TrimStart('?') - .Split(new[] { '&', ';' }, StringSplitOptions.RemoveEmptyEntries) // Support "?key=value;key=anotherValue" and "?key=value&key=anotherValue" + var splitOn = new List(); + if (queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.Ampersand)) + { + splitOn.Add("&"); // Support "?key=value&key=anotherValue" + } + if (queryParameterMultipleValueSupport.HasFlag(QueryParameterMultipleValueSupport.SemiColon)) + { + splitOn.Add(";"); // Support "?key=value;key=anotherValue" + } + + return queryString!.TrimStart('?') + .Split(splitOn.ToArray(), StringSplitOptions.RemoveEmptyEntries) .Select(parameter => parameter.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries)) .GroupBy(parts => parts[0], JoinParts) .ToDictionary(grouping => grouping.Key, grouping => new WireMockList(grouping.SelectMany(x => x).Select(WebUtility.UrlDecode))); diff --git a/test/WireMock.Net.Tests/RequestTests.cs b/test/WireMock.Net.Tests/RequestTests.cs index 9d4cd716..00e604c7 100644 --- a/test/WireMock.Net.Tests/RequestTests.cs +++ b/test/WireMock.Net.Tests/RequestTests.cs @@ -1,243 +1,261 @@ -using NFluent; +using NFluent; using System.Collections.Generic; using WireMock.Matchers.Request; using WireMock.Models; +using WireMock.Owin; using WireMock.RequestBuilders; using WireMock.Types; using WireMock.Util; using Xunit; -namespace WireMock.Net.Tests +namespace WireMock.Net.Tests; + +public class RequestTests { - public class RequestTests + private const string ClientIp = "::1"; + + [Fact] + public void Should_exclude_requests_matching_given_http_method_but_not_url() { - private const string ClientIp = "::1"; + // given + var spec = Request.Create().WithPath("/bar").UsingPut(); - [Fact] - public void Should_exclude_requests_matching_given_http_method_but_not_url() + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); + } + + [Fact] + public void Should_exclude_requests_not_matching_given_headers() + { + // given + var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "tatata"); + + // when + var body = new BodyData { - // given - var spec = Request.Create().WithPath("/bar").UsingPut(); + BodyAsString = "whatever", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "tata" } } }); - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } + [Fact] + public void Should_exclude_requests_not_matching_given_headers_ignorecase() + { + // given + var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "abc", false); - [Fact] - public void Should_exclude_requests_not_matching_given_headers() + // when + var body = new BodyData { - // given - var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "tatata"); + BodyAsString = "whatever", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "ABC" } } }); - // when - var body = new BodyData - { - BodyAsString = "whatever", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "tata" } } }); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } + [Fact] + public void Should_specify_requests_matching_given_header_prefix() + { + // given + var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "tata*"); - [Fact] - public void Should_exclude_requests_not_matching_given_headers_ignorecase() + // when + var body = new BodyData { - // given - var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "abc", false); + BodyAsString = "whatever", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "TaTa" } } }); - // when - var body = new BodyData - { - BodyAsString = "whatever", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "ABC" } } }); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } + [Fact] + public void Should_specify_requests_matching_given_wildcard_header() + { + // given + var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "*"); - [Fact] - public void Should_specify_requests_matching_given_header_prefix() + // when + var body = new BodyData { - // given - var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "tata*"); + BodyAsString = "whatever", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "TaTa" } } }); - // when - var body = new BodyData - { - BodyAsString = "whatever", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "TaTa" } } }); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } + [Fact] + public void Should_specify_requests_not_matching_given_wildcard_header2() + { + // given + var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "*"); - [Fact] - public void Should_specify_requests_matching_given_wildcard_header() + // when + var body = new BodyData { - // given - var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "*"); + BodyAsString = "whatever", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-tata", new[] { "ToTo" } } }); - // when - var body = new BodyData - { - BodyAsString = "whatever", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "TaTa" } } }); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(0.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } + [Fact] + public void Should_specify_requests_matching_given_wildcard_header_rejectonmatch() + { + // given + var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "*", WireMock.Matchers.MatchBehaviour.RejectOnMatch); - [Fact] - public void Should_specify_requests_not_matching_given_wildcard_header2() + // when + var body = new BodyData { - // given - var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "*"); + BodyAsString = "whatever", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-tata", new[] { "ToTo" } } }); - // when - var body = new BodyData - { - BodyAsString = "whatever", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-tata", new[] { "ToTo" } } }); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(0.0); - } + [Fact] + public void Should_specify_requests_not_matching_given_wildcard_header_rejectonmatch() + { + // given + var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "*", WireMock.Matchers.MatchBehaviour.RejectOnMatch); - [Fact] - public void Should_specify_requests_matching_given_wildcard_header_rejectonmatch() + // when + var body = new BodyData { - // given - var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "*", WireMock.Matchers.MatchBehaviour.RejectOnMatch); + BodyAsString = "whatever", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "TaTa" } } }); - // when - var body = new BodyData - { - BodyAsString = "whatever", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-tata", new[] { "ToTo" } } }); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(0.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } + [Fact] + public void Should_specify_requests_matching_given_body() + { + // given + var spec = Request.Create().UsingAnyMethod().WithBody("Hello world!"); - [Fact] - public void Should_specify_requests_not_matching_given_wildcard_header_rejectonmatch() + // when + var body = new BodyData { - // given - var spec = Request.Create().UsingAnyMethod().WithHeader("X-toto", "*", WireMock.Matchers.MatchBehaviour.RejectOnMatch); + BodyAsString = "Hello world!", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); - // when - var body = new BodyData - { - BodyAsString = "whatever", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "TaTa" } } }); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(0.0); - } + [Fact] + public void Should_exclude_requests_not_matching_given_body() + { + // given + var spec = Request.Create().UsingAnyMethod().WithBody(" Hello world! "); - [Fact] - public void Should_specify_requests_matching_given_body() + // when + var body = new BodyData { - // given - var spec = Request.Create().UsingAnyMethod().WithBody("Hello world!"); + BodyAsString = "xxx", + DetectedBodyType = BodyType.String + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "tata" } } }); - // when - var body = new BodyData - { - BodyAsString = "Hello world!", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body); + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); + } - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } + [Fact] + public void Should_specify_requests_matching_given_param() + { + // given + var spec = Request.Create().WithParam("bar", "1", "2"); - [Fact] - public void Should_exclude_requests_not_matching_given_body() + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo?bar=1&bar=2"), "PUT", ClientIp); + + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } + + [Fact] + public void Should_specify_requests_matching_given_param_WithComma() + { + // given + var options = new WireMockMiddlewareOptions { - // given - var spec = Request.Create().UsingAnyMethod().WithBody(" Hello world! "); + QueryParameterMultipleValueSupport = QueryParameterMultipleValueSupport.NoComma + }; + var spec = Request.Create().WithParam("$filter", "startswith(name,'testName')"); - // when - var body = new BodyData - { - BodyAsString = "xxx", - DetectedBodyType = BodyType.String - }; - var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "PUT", ClientIp, body, new Dictionary { { "X-toto", new[] { "tata" } } }); + // when + var request = new RequestMessage(options, new UrlDetails("http://localhost/?$filter=startswith(name,'testName')"), "PUT", ClientIp); - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } - [Fact] - public void Should_specify_requests_matching_given_param() - { - // given - var spec = Request.Create().WithParam("bar", "1", "2"); + [Fact] + public void Should_specify_requests_matching_given_param_func() + { + // given + var spec = Request.Create().UsingAnyMethod().WithParam(p => p.ContainsKey("bar")); - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo?bar=1&bar=2"), "PUT", ClientIp); + // when + var request = new RequestMessage(new UrlDetails("http://localhost/foo?bar=1&bar=2"), "PUT", ClientIp); - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); + } - [Fact] - public void Should_specify_requests_matching_given_param_func() - { - // given - var spec = Request.Create().UsingAnyMethod().WithParam(p => p.ContainsKey("bar")); + [Fact] + public void Should_exclude_requests_not_matching_given_params() + { + // given + var spec = Request.Create().WithParam("bar", "1"); - // when - var request = new RequestMessage(new UrlDetails("http://localhost/foo?bar=1&bar=2"), "PUT", ClientIp); + // when + var request = new RequestMessage(new UrlDetails("http://localhost/test=7"), "PUT", ClientIp); - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - - [Fact] - public void Should_exclude_requests_not_matching_given_params() - { - // given - var spec = Request.Create().WithParam("bar", "1"); - - // when - var request = new RequestMessage(new UrlDetails("http://localhost/test=7"), "PUT", ClientIp); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); - } + // then + var requestMatchResult = new RequestMatchResult(); + Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsNotEqualTo(1.0); } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs b/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs index 14b3a0ff..c273c297 100644 --- a/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs +++ b/test/WireMock.Net.Tests/Util/QueryStringParserTests.cs @@ -1,267 +1,308 @@ -using FluentAssertions; +using FluentAssertions; using System.Collections.Generic; using WireMock.Types; using WireMock.Util; using Xunit; -namespace WireMock.Net.Tests.Util +namespace WireMock.Net.Tests.Util; + +public class QueryStringParserTests { - public class QueryStringParserTests + [Fact] + public void Parse_WithNullString() { - [Fact] - public void Parse_WithNullString() - { - // Assign - string query = null; + // Assign + string? query = null; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Should().Equal(new Dictionary>()); - } + // Assert + result.Should().Equal(new Dictionary>()); + } - [Fact] - public void Parse_WithEmptyString() - { - // Assign - string query = ""; + [Fact] + public void Parse_WithEmptyString() + { + // Assign + string query = ""; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Should().Equal(new Dictionary>()); - } + // Assert + result.Should().Equal(new Dictionary>()); + } - [Fact] - public void Parse_WithQuestionMark() - { - // Assign - string query = "?"; + [Fact] + public void Parse_WithQuestionMark() + { + // Assign + string query = "?"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Should().Equal(new Dictionary>()); - } + // Assert + result.Should().Equal(new Dictionary>()); + } - [Fact] - public void Parse_With1Param() - { - // Assign - string query = "?key=bla/blub.xml"; + [Fact] + public void Parse_With1Param() + { + // Assign + string query = "?key=bla/blub.xml"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["key"].Should().Equal(new WireMockList("bla/blub.xml")); - } + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("bla/blub.xml")); + } - [Fact] - public void Parse_With2Params() - { - // Assign - string query = "?x=1&y=2"; + [Fact] + public void Parse_With2Params() + { + // Assign + string query = "?x=1&y=2"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(2); - result["x"].Should().Equal(new WireMockList("1")); - result["y"].Should().Equal(new WireMockList("2")); - } + // Assert + result.Count.Should().Be(2); + result["x"].Should().Equal(new WireMockList("1")); + result["y"].Should().Equal(new WireMockList("2")); + } - [Fact] - public void Parse_With1ParamNoValue() - { - // Assign - string query = "?empty"; + [Fact] + public void Parse_With1ParamNoValue() + { + // Assign + string query = "?empty"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["empty"].Should().Equal(new WireMockList()); - } + // Assert + result.Count.Should().Be(1); + result["empty"].Should().Equal(new WireMockList()); + } - [Fact] - public void Parse_With1ParamNoValueWithEqualSign() - { - // Assign - string query = "?empty="; + [Fact] + public void Parse_With1ParamNoValueWithEqualSign() + { + // Assign + string query = "?empty="; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["empty"].Should().Equal(new WireMockList()); - } + // Assert + result.Count.Should().Be(1); + result["empty"].Should().Equal(new WireMockList()); + } - [Fact] - public void Parse_With1ParamAndJustAndSign() - { - // Assign - string query = "?key=1&"; + [Fact] + public void Parse_With1ParamAndJustAndSign() + { + // Assign + string query = "?key=1&"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["key"].Should().Equal(new WireMockList("1")); - } + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("1")); + } - [Fact] - public void Parse_With2ParamsAndWhereOneHasAQuestion() - { - // Assign - string query = "?key=value?&b=c"; + [Fact] + public void Parse_With2ParamsAndWhereOneHasAQuestion() + { + // Assign + string query = "?key=value?&b=c"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(2); - result["key"].Should().Equal(new WireMockList("value?")); - result["b"].Should().Equal(new WireMockList("c")); - } + // Assert + result.Count.Should().Be(2); + result["key"].Should().Equal(new WireMockList("value?")); + result["b"].Should().Equal(new WireMockList("c")); + } - [Fact] - public void Parse_With1ParamWithEqualSign() - { - // Assign - string query = "?key=value=what"; + [Fact] + public void Parse_With1ParamWithEqualSign() + { + // Assign + string query = "?key=value=what"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["key"].Should().Equal(new WireMockList("value=what")); - } + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("value=what")); + } - [Fact] - public void Parse_With1ParamWithTwoEqualSigns() - { - // Assign - string query = "?key=value==what"; + [Fact] + public void Parse_With1ParamWithTwoEqualSigns() + { + // Assign + string query = "?key=value==what"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["key"].Should().Equal(new WireMockList("value==what")); - } + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("value==what")); + } - [Fact] - public void Parse_WithMultipleParamWithSameKeySeparatedBySemiColon() - { - // Assign - string query = "?key=value;key=anotherValue"; + [Fact] + public void Parse_WithMultipleParamWithSameKeySeparatedBySemiColon() + { + // Assign + string query = "?key=value;key=anotherValue"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["key"].Should().Equal(new WireMockList(new[] { "value", "anotherValue" })); - } + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("value", "anotherValue")); + } - [Fact] - public void Parse_With1ParamContainingComma() - { - // Assign - string query = "?key=1,2&key=3"; + [Fact] + public void Parse_WithMultipleParamWithSameKeySeparatedByAmp() + { + // Assign + string query = "?key=1&key=2"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["key"].Should().Equal(new WireMockList(new[] { "1", "2", "3" })); - } + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("1", "2")); + } - [Fact] - public void Parse_With1ParamContainingEscapedAnd() - { - // Assign - string query = "?winkel=C%26A"; + [Fact] + public void Parse_With1ParamContainingComma_When_SupportMultiValueUsingComma_Is_True() + { + // Assign + string query = "?key=1,2,3"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["winkel"].Should().Equal(new WireMockList(new[] { "C&A" })); - } + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("1", "2", "3")); + } - [Fact] - public void Parse_With1ParamContainingParentheses() - { - // Assign - string query = "?Transaction=(123)"; + [Fact] + public void Parse_With1ParamContainingCommaAndAmpCombined_When_SupportMultiValueUsingComma_Is_Comma() + { + // Assign + string query = "?key=1,2&key=3"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["Transaction"].Should().Equal(new WireMockList(new[] { "(123)" })); - } + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("1", "2", "3")); + } - [Fact] - public void Parse_WithMultipleParamWithSameKey() - { - // Assign - string query = "?key=value&key=anotherValue"; + [Fact] + public void Parse_With1ParamContainingComma_SupportMultiValueUsingComma_Is_AmpersandAndSemiColon() + { + // Assign + string query = "?$filter=startswith(name,'testName')"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query, QueryParameterMultipleValueSupport.AmpersandAndSemiColon); - // Assert - result.Count.Should().Be(1); - result["key"].Should().Equal(new WireMockList(new[] { "value", "anotherValue" })); - } + // Assert + result.Count.Should().Be(1); + result["$filter"].Should().Equal(new WireMockList("startswith(name,'testName')")); + } - [Fact] - public void Parse_With1ParamContainingSpacesAndEqualSign() - { - // Assign - string query = "?q=SELECT Id from User where username='user@gmail.com'"; + [Fact] + public void Parse_With1ParamContainingEscapedAnd() + { + // Assign + string query = "?winkel=C%26A"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(1); - result["q"].Should().Equal(new WireMockList("SELECT Id from User where username='user@gmail.com'")); - } + // Assert + result.Count.Should().Be(1); + result["winkel"].Should().Equal(new WireMockList("C&A")); + } - [Fact] - public void Parse_WithComplex() - { - // Assign - string query = "?q=energy+edge&rls=com.microsoft:en-au&ie=UTF-8&oe=UTF-8&startIndex=&startPage=1%22"; + [Fact] + public void Parse_With1ParamContainingParentheses() + { + // Assign + string query = "?Transaction=(123)"; - // Act - var result = QueryStringParser.Parse(query); + // Act + var result = QueryStringParser.Parse(query); - // Assert - result.Count.Should().Be(6); - result["q"].Should().Equal(new WireMockList("energy edge")); - result["rls"].Should().Equal(new WireMockList("com.microsoft:en-au")); - result["ie"].Should().Equal(new WireMockList("UTF-8")); - result["oe"].Should().Equal(new WireMockList("UTF-8")); - result["startIndex"].Should().Equal(new WireMockList()); - result["startPage"].Should().Equal(new WireMockList("1\"")); - } + // Assert + result.Count.Should().Be(1); + result["Transaction"].Should().Equal(new WireMockList("(123)")); + } + + [Fact] + public void Parse_WithMultipleParamWithSameKey() + { + // Assign + string query = "?key=value&key=anotherValue"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["key"].Should().Equal(new WireMockList("value", "anotherValue")); + } + + [Fact] + public void Parse_With1ParamContainingSpacesAndEqualSign() + { + // Assign + string query = "?q=SELECT Id from User where username='user@gmail.com'"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(1); + result["q"].Should().Equal(new WireMockList("SELECT Id from User where username='user@gmail.com'")); + } + + [Fact] + public void Parse_WithComplex() + { + // Assign + string query = "?q=energy+edge&rls=com.microsoft:en-au&ie=UTF-8&oe=UTF-8&startIndex=&startPage=1%22"; + + // Act + var result = QueryStringParser.Parse(query); + + // Assert + result.Count.Should().Be(6); + result["q"].Should().Equal(new WireMockList("energy edge")); + result["rls"].Should().Equal(new WireMockList("com.microsoft:en-au")); + result["ie"].Should().Equal(new WireMockList("UTF-8")); + result["oe"].Should().Equal(new WireMockList("UTF-8")); + result["startIndex"].Should().Equal(new WireMockList()); + result["startPage"].Should().Equal(new WireMockList("1\"")); } } \ No newline at end of file