diff --git a/WireMock.Net Solution.sln b/WireMock.Net Solution.sln index d7c71918..56b43bb5 100644 --- a/WireMock.Net Solution.sln +++ b/WireMock.Net Solution.sln @@ -128,7 +128,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Middleware.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.AwesomeAssertions", "src\WireMock.Net.AwesomeAssertions\WireMock.Net.AwesomeAssertions.csproj", "{7753670F-7C7F-44BF-8BC7-08325588E60C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenApiParser", "src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj", "{D3804228-91F4-4502-9595-39584E5AADAD}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src-preview", "src-preview", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenApiParser", "src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj", "{E5B03EEF-822C-4295-952B-4479AD30082B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -304,10 +306,10 @@ Global {7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|Any CPU.Build.0 = Debug|Any CPU {7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|Any CPU.ActiveCfg = Release|Any CPU {7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|Any CPU.Build.0 = Release|Any CPU - {D3804228-91F4-4502-9595-39584E5AADAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3804228-91F4-4502-9595-39584E5AADAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3804228-91F4-4502-9595-39584E5AADAD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3804228-91F4-4502-9595-39584E5AADAD}.Release|Any CPU.Build.0 = Release|Any CPU + {E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5B03EEF-822C-4295-952B-4479AD30082B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5B03EEF-822C-4295-952B-4479AD30082B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -357,7 +359,7 @@ Global {6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE} = {0BB8B634-407A-4610-A91F-11586990767A} {A5FEF4F7-7DA2-4962-89A8-16BA942886E5} = {0BB8B634-407A-4610-A91F-11586990767A} {7753670F-7C7F-44BF-8BC7-08325588E60C} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} - {D3804228-91F4-4502-9595-39584E5AADAD} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} + {E5B03EEF-822C-4295-952B-4479AD30082B} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/DictionaryExtensions.cs b/src-old/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs similarity index 91% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/DictionaryExtensions.cs rename to src-old/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs index a819a1f9..8034318e 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/DictionaryExtensions.cs +++ b/src-old/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs @@ -1,6 +1,6 @@ // Copyright © WireMock.Net -#if NET46 || NET47 || NETSTANDARD2_0 +#if NET46 || NETSTANDARD2_0 using System.Collections.Generic; namespace WireMock.Net.OpenApiParser.Extensions; diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/OpenApiSchemaExtensions.cs b/src-old/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs similarity index 53% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/OpenApiSchemaExtensions.cs rename to src-old/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs index 02f7a49b..efd4bfcd 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/OpenApiSchemaExtensions.cs +++ b/src-old/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs @@ -1,53 +1,60 @@ // Copyright © WireMock.Net using System.Linq; -using System.Text.Json; using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Models.Interfaces; using WireMock.Net.OpenApiParser.Types; namespace WireMock.Net.OpenApiParser.Extensions; internal static class OpenApiSchemaExtensions { - public static bool TryGetXNullable(this IOpenApiSchema 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 != null && schema.Extensions.TryGetValue(OpenApiConstants.NullableExtension, out var nullExtRawValue) && nullExtRawValue is OpenApiAny { Node: { } jsonNode }) + if (schema.Extensions.TryGetValue("x-nullable", out var e) && e is OpenApiBoolean openApiBoolean) { - value = jsonNode.GetValueKind() == JsonValueKind.True; + value = openApiBoolean.Value; return true; } return false; } - public static JsonSchemaType? GetSchemaType(this IOpenApiSchema? schema, out bool isNullable) + public static SchemaType GetSchemaType(this OpenApiSchema? schema) { - isNullable = false; - if (schema == null) { - return null; + return SchemaType.Unknown; } if (schema.Type == null) { - if (schema.AllOf?.Any() == true || schema.AnyOf?.Any() == true) + if (schema.AllOf.Any() || schema.AnyOf.Any()) { - return JsonSchemaType.Object; + return SchemaType.Object; } } - isNullable = (schema.Type | JsonSchemaType.Null) == JsonSchemaType.Null || (schema.TryGetXNullable(out var xNullable) && xNullable); - - // Removes the Null flag from the schema.Type, ensuring the returned value represents a non-nullable type. - return schema.Type & ~JsonSchemaType.Null; + return schema.Type switch + { + "object" => SchemaType.Object, + "array" => SchemaType.Array, + "integer" => SchemaType.Integer, + "number" => SchemaType.Number, + "boolean" => SchemaType.Boolean, + "string" => SchemaType.String, + "file" => SchemaType.File, + _ => SchemaType.Unknown + }; } - public static SchemaFormat GetSchemaFormat(this IOpenApiSchema? schema) + public static SchemaFormat GetSchemaFormat(this OpenApiSchema? schema) { switch (schema?.Format) { diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/WireMockServerExtensions.cs b/src-old/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs similarity index 90% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/WireMockServerExtensions.cs rename to src-old/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs index 8d8aee7f..494d4f37 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Extensions/WireMockServerExtensions.cs +++ b/src-old/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs @@ -4,7 +4,7 @@ using System.IO; using System.Linq; using JetBrains.Annotations; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; using Stef.Validation; using WireMock.Net.OpenApiParser.Settings; using WireMock.Server; @@ -17,7 +17,7 @@ namespace WireMock.Net.OpenApiParser.Extensions; public static class WireMockServerExtensions { /// - /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file. + /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. /// /// The WireMockServer instance /// Path containing OpenAPI file to parse and use the mappings. @@ -29,7 +29,7 @@ public static class WireMockServerExtensions } /// - /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file. + /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. /// /// The WireMockServer instance /// Path containing OpenAPI file to parse and use the mappings. @@ -47,7 +47,7 @@ public static class WireMockServerExtensions } /// - /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream. + /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. /// /// The WireMockServer instance /// Stream containing OpenAPI description to parse and use the mappings. @@ -59,7 +59,7 @@ public static class WireMockServerExtensions } /// - /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream. + /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. /// /// The WireMockServer instance /// Stream containing OpenAPI description to parse and use the mappings. @@ -78,7 +78,7 @@ public static class WireMockServerExtensions } /// - /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 document. + /// Register the mappings via an OpenAPI (swagger) V2 or V3 document. /// /// The WireMockServer instance /// The OpenAPI document to use as mappings. diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/IWireMockOpenApiParser.cs b/src-old/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs similarity index 97% rename from src-preview/WireMock.Net.OpenApiParser.Preview/IWireMockOpenApiParser.cs rename to src-old/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs index 4e978739..e3b2b728 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/IWireMockOpenApiParser.cs +++ b/src-old/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Readers; using WireMock.Admin.Mappings; using WireMock.Net.OpenApiParser.Settings; @@ -17,7 +17,7 @@ public interface IWireMockOpenApiParser /// /// Generate from a file-path. /// - /// The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file. + /// The path to read the OpenApi/Swagger/V2/V3 or Raml file. /// OpenApiDiagnostic output /// MappingModel IReadOnlyList FromFile(string path, out OpenApiDiagnostic diagnostic); @@ -25,7 +25,7 @@ public interface IWireMockOpenApiParser /// /// Generate from a file-path. /// - /// The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file. + /// The path to read the OpenApi/Swagger/V2/V3 or Raml file. /// Additional settings /// OpenApiDiagnostic output /// MappingModel diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Mappers/OpenApiPathsMapper.cs b/src-old/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs similarity index 53% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Mappers/OpenApiPathsMapper.cs rename to src-old/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs index e48766e0..6056aecb 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Mappers/OpenApiPathsMapper.cs +++ b/src-old/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs @@ -3,19 +3,20 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; -using System.Text.Json; -using System.Text.Json.Nodes; +using Microsoft.OpenApi; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Writers; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Net.OpenApiParser.Extensions; using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Utils; -using SystemTextJsonSerializer = System.Text.Json.JsonSerializer; namespace WireMock.Net.OpenApiParser.Mappers; @@ -38,40 +39,56 @@ internal class OpenApiPathsMapper .OrderBy(p => p.Key) .Select(p => MapPath(p.Key, p.Value, servers)) .SelectMany(x => x) - .ToArray() ?? []; + .ToArray() ?? + Array.Empty(); } - private IReadOnlyList MapPath(string path, IOpenApiPathItem pathItem, IList servers) + private IReadOnlyList MapPaths(OpenApiPaths? paths, IList servers) { - return pathItem.Operations?.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray() ?? []; + return paths? + .OrderBy(p => p.Key) + .Select(p => MapPath(p.Key, p.Value, servers)) + .SelectMany(x => x) + .ToArray() ?? + Array.Empty(); + } + + private IReadOnlyList MapPath(string path, OpenApiPathItem pathItem, IList servers) + { + return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray(); } 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 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() ?? new KeyValuePair(); + var response = operation.Responses.FirstOrDefault(); - TryGetContent(response.Value?.Content, out OpenApiMediaType? responseContent, out var responseContentType); + 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 responseBody = responseExample ?? responseSchemaExample ?? MapSchemaToObject(responseSchema); + 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) { var request = operation.RequestBody.Content; - TryGetContent(request, out var requestContent, out _); + 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 ?? requestBodySchemaExample ?? MapSchemaToObject(requestBodySchema); + var requestBodyMapped = requestBodyExample != null ? MapOpenApiAnyToJToken(requestBodyExample) : + requestBodySchemaExample != null ? MapOpenApiAnyToJToken(requestBodySchemaExample) : + MapSchemaToObject(requestBodySchema); + requestBodyModel = MapRequestBody(requestBodyMapped); } @@ -95,12 +112,12 @@ internal class OpenApiPathsMapper { StatusCode = httpStatusCode, Headers = MapHeaders(responseContentType, response.Value?.Headers), - BodyAsJson = responseBody != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(responseBody)) : null + BodyAsJson = body } }; } - private BodyModel? MapRequestBody(JsonNode? requestBody) + private BodyModel? MapRequestBody(object? requestBody) { if (requestBody == null) { @@ -112,7 +129,7 @@ internal class OpenApiPathsMapper Matcher = new MatcherModel { Name = "JsonMatcher", - Pattern = SystemTextJsonSerializer.Serialize(requestBody, new JsonSerializerOptions { WriteIndented = true }), + Pattern = JsonConvert.SerializeObject(requestBody, Formatting.Indented), IgnoreCase = _settings.RequestBodyIgnoreCase } }; @@ -143,103 +160,117 @@ internal class OpenApiPathsMapper return true; } - private JsonNode? MapSchemaToObject(IOpenApiSchema? schema) + private object? MapSchemaToObject(OpenApiSchema? schema, string? name = null) { if (schema == null) { return null; } - switch (schema.GetSchemaType(out _)) + switch (schema.GetSchemaType()) { - case JsonSchemaType.Array: - var array = new JsonArray(); - for (var i = 0; i < _settings.NumberOfArrayItems; i++) + 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 item = new JsonObject(); + var arrayItem = new JObject(); foreach (var property in schema.Items.Properties) { - item[property.Key] = MapSchemaToObject(property.Value); + var objectValue = MapSchemaToObject(property.Value, property.Key); + if (objectValue is JProperty jp) + { + arrayItem.Add(jp); + } + else + { + arrayItem.Add(new JProperty(property.Key, objectValue)); + } } - array.Add(item); + jArray.Add(arrayItem); } else { - var arrayItem = MapSchemaToObject(schema.Items); - array.Add(arrayItem); + var arrayItem = MapSchemaToObject(schema.Items, name: null); // Set name to null to force JObject instead of JProperty + jArray.Add(arrayItem); } } - if (schema.AllOf?.Count > 0) + if (schema.AllOf.Count > 0) { - array.Add(MapSchemaAllOfToObject(schema)); + jArray.Add(MapSchemaAllOfToObject(schema)); } - return array; + return jArray; - case JsonSchemaType.Boolean: - case JsonSchemaType.Integer: - case JsonSchemaType.Number: - case JsonSchemaType.String: + case SchemaType.Boolean: + case SchemaType.Integer: + case SchemaType.Number: + case SchemaType.String: return _exampleValueGenerator.GetExampleValue(schema); - case JsonSchemaType.Object: - var propertyAsJsonObject = new JsonObject(); - foreach (var schemaProperty in schema.Properties ?? new Dictionary()) + case SchemaType.Object: + var propertyAsJObject = new JObject(); + foreach (var schemaProperty in schema.Properties) { - propertyAsJsonObject[schemaProperty.Key] = MapPropertyAsJsonNode(schemaProperty.Value); + propertyAsJObject.Add(MapPropertyAsJObject(schemaProperty.Value, schemaProperty.Key)); } - if (schema.AllOf?.Count > 0) + if (schema.AllOf.Count > 0) { - foreach (var group in schema.AllOf.SelectMany(p => p.Properties ?? new Dictionary()).GroupBy(x => x.Key)) + foreach (var group in schema.AllOf.SelectMany(p => p.Properties).GroupBy(x => x.Key)) { - propertyAsJsonObject[group.Key] = MapPropertyAsJsonNode(group.First().Value); + propertyAsJObject.Add(MapPropertyAsJObject(group.First().Value, group.Key)); } } - return propertyAsJsonObject; + return name != null ? new JProperty(name, propertyAsJObject) : propertyAsJObject; default: return null; } } - private JsonObject MapSchemaAllOfToObject(IOpenApiSchema schema) + private JObject MapSchemaAllOfToObject(OpenApiSchema schema) { - var arrayItem = new JsonObject(); - foreach (var property in schema.AllOf ?? []) + var arrayItem = new JObject(); + foreach (var property in schema.AllOf) { - foreach (var item in property.Properties ?? new Dictionary()) + foreach (var item in property.Properties) { - arrayItem[item.Key] = MapPropertyAsJsonNode(item.Value); + arrayItem.Add(MapPropertyAsJObject(item.Value, item.Key)); } } return arrayItem; } - private JsonNode? MapPropertyAsJsonNode(IOpenApiSchema openApiSchema) + private object MapPropertyAsJObject(OpenApiSchema openApiSchema, string key) { - var schemaType = openApiSchema.GetSchemaType(out _); - if (schemaType is JsonSchemaType.Object or JsonSchemaType.Array) + if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array) { - return MapSchemaToObject(openApiSchema); + var mapped = MapSchemaToObject(openApiSchema, key); + if (mapped is JProperty jp) + { + return jp; + } + + return new JProperty(key, mapped); } - return _exampleValueGenerator.GetExampleValue(openApiSchema); + // bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x); + return new JProperty(key, _exampleValueGenerator.GetExampleValue(openApiSchema)); } - private string MapPathWithParameters(string path, IEnumerable? parameters) + private string MapPathWithParameters(string path, IEnumerable? parameters) { if (parameters == null) { return path; } - var newPath = path; + string newPath = path; foreach (var parameter in parameters) { var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse); @@ -249,56 +280,93 @@ internal class OpenApiPathsMapper return newPath; } - private IDictionary? MapHeaders(string? responseContentType, IDictionary? headers) + private string MapBasePath(IList? servers) { - var mappedHeaders = headers? - .ToDictionary(item => item.Key, _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!) ?? new Dictionary(); + if (servers == null || servers.Count == 0) + { + return string.Empty; + } + + OpenApiServer server = servers.First(); + if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult)) + { + return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString(); + } + + return string.Empty; + } + + private JToken? MapOpenApiAnyToJToken(IOpenApiAny? any) + { + 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()); + } + + return JObject.Parse(outputString.ToString()); + } + + private IDictionary? MapHeaders(string? responseContentType, IDictionary? headers) + { + var mappedHeaders = headers?.ToDictionary( + item => item.Key, + _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern! + ) ?? new Dictionary(); if (!string.IsNullOrEmpty(responseContentType)) { - mappedHeaders.TryAdd(HeaderContentType, responseContentType); + mappedHeaders.TryAdd(HeaderContentType, responseContentType!); } return mappedHeaders.Keys.Any() ? mappedHeaders : null; } - private IList? MapQueryParameters(IEnumerable queryParameters) + private IList? MapQueryParameters(IEnumerable queryParameters) { var list = queryParameters .Where(req => req.Required) .Select(qp => new ParamModel { - Name = qp.Name ?? string.Empty, + Name = qp.Name, IgnoreCase = _settings.QueryParameterPatternIgnoreCase, - Matchers = - [ + Matchers = new[] + { GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse) - ] + } }) .ToList(); return list.Any() ? list : null; } - private IList? MapRequestHeaders(IEnumerable headers) + private IList? MapRequestHeaders(IEnumerable headers) { var list = headers .Where(req => req.Required) .Select(qp => new HeaderModel { - Name = qp.Name ?? string.Empty, + Name = qp.Name, IgnoreCase = _settings.HeaderPatternIgnoreCase, - Matchers = - [ + Matchers = new[] + { GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse) - ] + } }) .ToList(); return list.Any() ? list : null; } - private MatcherModel GetExampleMatcherModel(IOpenApiSchema? schema, ExampleValueType type) + private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType type) { return type switch { @@ -317,31 +385,15 @@ internal class OpenApiPathsMapper }; } - private string GetExampleValueAsStringForSchemaType(IOpenApiSchema? schema) + private string GetExampleValueAsStringForSchemaType(OpenApiSchema? schema) { var value = _exampleValueGenerator.GetExampleValue(schema); - if (value.GetValueKind() == JsonValueKind.String) + return value switch { - return value.GetValue(); - } + string valueAsString => valueAsString, - return value.ToString(); - } - - private static string MapBasePath(IList? servers) - { - var server = servers?.FirstOrDefault(); - if (server == null) - { - return string.Empty; - } - - if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out var uriResult)) - { - return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString(); - } - - return string.Empty; + _ => value.ToString(), + }; } } \ No newline at end of file diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Properties/AssemblyInfo.cs b/src-old/WireMock.Net.OpenApiParser/Properties/AssemblyInfo.cs similarity index 100% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Properties/AssemblyInfo.cs rename to src-old/WireMock.Net.OpenApiParser/Properties/AssemblyInfo.cs diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Settings/IWireMockOpenApiParserExampleValues.cs b/src-old/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs similarity index 83% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Settings/IWireMockOpenApiParserExampleValues.cs rename to src-old/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs index 5d335144..eecfed7e 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Settings/IWireMockOpenApiParserExampleValues.cs +++ b/src-old/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs @@ -1,12 +1,12 @@ // Copyright © WireMock.Net using System; -using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models; namespace WireMock.Net.OpenApiParser.Settings; /// -/// An interface defining the example values to use for the different types. +/// A interface defining the example values to use for the different types. /// public interface IWireMockOpenApiParserExampleValues { @@ -26,9 +26,9 @@ public interface IWireMockOpenApiParserExampleValues float Float { get; } /// - /// An example value for a Decimal. + /// An example value for a Double. /// - decimal Decimal { get; } + double Double { get; } /// /// An example value for a Date. @@ -58,5 +58,5 @@ public interface IWireMockOpenApiParserExampleValues /// /// OpenApi Schema to generate dynamic examples more accurate /// - IOpenApiSchema? Schema { get; set; } + OpenApiSchema? Schema { get; set; } } \ No newline at end of file diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Settings/WireMockOpenApiParserDynamicExampleValues.cs b/src-old/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs similarity index 66% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Settings/WireMockOpenApiParserDynamicExampleValues.cs rename to src-old/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs index 194762c5..46b2c766 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Settings/WireMockOpenApiParserDynamicExampleValues.cs +++ b/src-old/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs @@ -1,7 +1,7 @@ // Copyright © WireMock.Net using System; -using Microsoft.OpenApi.Models.Interfaces; +using Microsoft.OpenApi.Models; using RandomDataGenerator.FieldOptions; using RandomDataGenerator.Randomizers; @@ -22,7 +22,7 @@ public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserE public virtual float Float => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; /// - public virtual decimal Decimal => SafeConvertFloatToDecimal(RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f); + public virtual double Double => RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; /// public virtual Func Date => () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; @@ -40,20 +40,5 @@ public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserE public virtual string String => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; /// - public virtual IOpenApiSchema? Schema { get; set; } - - /// - /// Safely converts a float to a decimal, ensuring the value stays within the bounds of a decimal. - /// - /// The float value to convert. - /// A decimal value within the valid range of a decimal. - private static decimal SafeConvertFloatToDecimal(float value) - { - return value switch - { - < (float)decimal.MinValue => decimal.MinValue, - > (float)decimal.MaxValue => decimal.MaxValue, - _ => (decimal)value - }; - } + public virtual OpenApiSchema? Schema { get; set; } } \ No newline at end of file diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Settings/WireMockOpenApiParserExampleValues.cs b/src-old/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs similarity index 80% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Settings/WireMockOpenApiParserExampleValues.cs rename to src-old/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs index 3b3c6fea..99665cf0 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Settings/WireMockOpenApiParserExampleValues.cs +++ b/src-old/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs @@ -2,7 +2,6 @@ using System; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Models.Interfaces; namespace WireMock.Net.OpenApiParser.Settings; @@ -21,7 +20,7 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV public virtual float Float => 4.2f; /// - public virtual decimal Decimal => 4.2m; + public virtual double Double => 4.2d; /// public virtual Func Date { get; } = () => System.DateTime.UtcNow.Date; @@ -30,7 +29,7 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV public virtual Func DateTime { get; } = () => System.DateTime.UtcNow; /// - public virtual byte[] Bytes { get; } = [48, 49, 50]; + public virtual byte[] Bytes { get; } = { 48, 49, 50 }; /// public virtual object Object => "example-object"; @@ -39,5 +38,5 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV public virtual string String => "example-string"; /// - public virtual IOpenApiSchema? Schema { get; set; } = new OpenApiSchema(); + public virtual OpenApiSchema? Schema { get; set; } = new(); } \ No newline at end of file diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Settings/WireMockOpenApiParserSettings.cs b/src-old/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserSettings.cs similarity index 100% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Settings/WireMockOpenApiParserSettings.cs rename to src-old/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserSettings.cs diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Types/ExampleValueType.cs b/src-old/WireMock.Net.OpenApiParser/Types/ExampleValueType.cs similarity index 100% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Types/ExampleValueType.cs rename to src-old/WireMock.Net.OpenApiParser/Types/ExampleValueType.cs diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Types/SchemaFormat.cs b/src-old/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs similarity index 100% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Types/SchemaFormat.cs rename to src-old/WireMock.Net.OpenApiParser/Types/SchemaFormat.cs diff --git a/src/WireMock.Net.OpenApiParser/Types/SchemaType.cs b/src-old/WireMock.Net.OpenApiParser/Types/SchemaType.cs similarity index 100% rename from src/WireMock.Net.OpenApiParser/Types/SchemaType.cs rename to src-old/WireMock.Net.OpenApiParser/Types/SchemaType.cs diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Utils/DateTimeUtils.cs b/src-old/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs similarity index 50% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Utils/DateTimeUtils.cs rename to src-old/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs index e3178549..c0108295 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Utils/DateTimeUtils.cs +++ b/src-old/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs @@ -7,16 +7,13 @@ namespace WireMock.Net.OpenApiParser.Utils; internal static class DateTimeUtils { - private const string DateFormat = "yyyy-MM-dd"; - private const string DateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.fffzzz"; - public static string ToRfc3339DateTime(DateTime dateTime) { - return dateTime.ToString(DateTimeFormat, DateTimeFormatInfo.InvariantInfo); + return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo); } public static string ToRfc3339Date(DateTime dateTime) { - return dateTime.ToString(DateFormat, DateTimeFormatInfo.InvariantInfo); + return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo); } } \ No newline at end of file diff --git a/src-old/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs b/src-old/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs new file mode 100644 index 00000000..4039a358 --- /dev/null +++ b/src-old/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs @@ -0,0 +1,119 @@ +// Copyright © WireMock.Net + +using System.Linq; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Stef.Validation; +using WireMock.Net.OpenApiParser.Extensions; +using WireMock.Net.OpenApiParser.Settings; +using WireMock.Net.OpenApiParser.Types; + +namespace WireMock.Net.OpenApiParser.Utils; + +internal class ExampleValueGenerator +{ + private readonly IWireMockOpenApiParserExampleValues _exampleValues; + + public ExampleValueGenerator(WireMockOpenApiParserSettings settings) + { + Guard.NotNull(settings); + + // Check if user provided an own implementation + if (settings.ExampleValues is null) + { + if (settings.DynamicExamples) + { + _exampleValues = new WireMockOpenApiParserDynamicExampleValues(); + } + else + { + _exampleValues = new WireMockOpenApiParserExampleValues(); + } + } + else + { + _exampleValues = settings.ExampleValues; + } + } + + public object GetExampleValue(OpenApiSchema? schema) + { + var schemaExample = schema?.Example; + var schemaEnum = schema?.Enum?.FirstOrDefault(); + + _exampleValues.Schema = schema; + + switch (schema?.GetSchemaType()) + { + case SchemaType.Boolean: + var exampleBoolean = schemaExample as OpenApiBoolean; + return exampleBoolean?.Value ?? _exampleValues.Boolean; + + case SchemaType.Integer: + switch (schema?.GetSchemaFormat()) + { + case SchemaFormat.Int64: + var exampleLong = schemaExample as OpenApiLong; + var enumLong = schemaEnum as OpenApiLong; + var valueLongEnumOrExample = enumLong?.Value ?? exampleLong?.Value; + return valueLongEnumOrExample ?? _exampleValues.Integer; + + default: + var exampleInteger = schemaExample as OpenApiInteger; + var enumInteger = schemaEnum as OpenApiInteger; + var valueIntegerEnumOrExample = enumInteger?.Value ?? exampleInteger?.Value; + return valueIntegerEnumOrExample ?? _exampleValues.Integer; + } + + case SchemaType.Number: + switch (schema?.GetSchemaFormat()) + { + case SchemaFormat.Float: + var exampleFloat = schemaExample as OpenApiFloat; + var enumFloat = schemaEnum as OpenApiFloat; + var valueFloatEnumOrExample = enumFloat?.Value ?? exampleFloat?.Value; + return valueFloatEnumOrExample ?? _exampleValues.Float; + + default: + var exampleDouble = schemaExample as OpenApiDouble; + var enumDouble = schemaEnum as OpenApiDouble; + var valueDoubleEnumOrExample = enumDouble?.Value ?? exampleDouble?.Value; + return valueDoubleEnumOrExample ?? _exampleValues.Double; + } + + default: + switch (schema?.GetSchemaFormat()) + { + case SchemaFormat.Date: + var exampleDate = schemaExample as OpenApiDate; + var enumDate = schemaEnum as OpenApiDate; + var valueDateEnumOrExample = enumDate?.Value ?? exampleDate?.Value; + return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _exampleValues.Date()); + + case SchemaFormat.DateTime: + var exampleDateTime = schemaExample as OpenApiDateTime; + var enumDateTime = schemaEnum as OpenApiDateTime; + var valueDateTimeEnumOrExample = enumDateTime?.Value ?? exampleDateTime?.Value; + return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _exampleValues.DateTime()); + + case SchemaFormat.Byte: + var exampleByte = schemaExample as OpenApiByte; + var enumByte = schemaEnum as OpenApiByte; + var valueByteEnumOrExample = enumByte?.Value ?? exampleByte?.Value; + return valueByteEnumOrExample ?? _exampleValues.Bytes; + + case SchemaFormat.Binary: + var exampleBinary = schemaExample as OpenApiBinary; + var enumBinary = schemaEnum as OpenApiBinary; + var valueBinaryEnumOrExample = enumBinary?.Value ?? exampleBinary?.Value; + return valueBinaryEnumOrExample ?? _exampleValues.Object; + + default: + var exampleString = schemaExample as OpenApiString; + var enumString = schemaEnum as OpenApiString; + var valueStringEnumOrExample = enumString?.Value ?? exampleString?.Value; + return valueStringEnumOrExample ?? _exampleValues.String; + } + } + } +} \ No newline at end of file diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Utils/PathUtils.cs b/src-old/WireMock.Net.OpenApiParser/Utils/PathUtils.cs similarity index 100% rename from src-preview/WireMock.Net.OpenApiParser.Preview/Utils/PathUtils.cs rename to src-old/WireMock.Net.OpenApiParser/Utils/PathUtils.cs diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/WireMock.Net.OpenApiParser.Preview.csproj b/src-old/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj similarity index 86% rename from src-preview/WireMock.Net.OpenApiParser.Preview/WireMock.Net.OpenApiParser.Preview.csproj rename to src-old/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj index faed3da0..9fac3c76 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/WireMock.Net.OpenApiParser.Preview.csproj +++ b/src-old/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj @@ -2,10 +2,10 @@ An OpenApi (swagger) parser to generate MappingModel or mapping.json file. - net47;netstandard2.0;netstandard2.1;net8.0 + net46;netstandard2.0;netstandard2.1 true wiremock;openapi;OAS;raml;converter;parser;openapiparser - {E5B03EEF-822C-4295-952B-4479AD30082B} + {D3804228-91F4-4502-9595-39584E5AADAD} true ../WireMock.Net/WireMock.Net.ruleset true @@ -14,19 +14,18 @@ MIT - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/WireMockOpenApiParser.cs b/src-old/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs similarity index 69% rename from src-preview/WireMock.Net.OpenApiParser.Preview/WireMockOpenApiParser.cs rename to src-old/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs index 849da722..c027eed9 100644 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/WireMockOpenApiParser.cs +++ b/src-old/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs @@ -6,8 +6,7 @@ using System.IO; using System.Text; using JetBrains.Annotations; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Reader; -using Microsoft.OpenApi.YamlReader; +using Microsoft.OpenApi.Readers; using RamlToOpenApiConverter; using WireMock.Admin.Mappings; using WireMock.Net.OpenApiParser.Mappers; @@ -20,7 +19,7 @@ namespace WireMock.Net.OpenApiParser; /// public class WireMockOpenApiParser : IWireMockOpenApiParser { - private static readonly OpenApiReaderSettings ReaderSettings = new(); + private readonly OpenApiStreamReader _reader = new(); /// [PublicAPI] @@ -41,7 +40,8 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser } else { - document = Read(File.OpenRead(path), out diagnostic); + var reader = new OpenApiStreamReader(); + document = reader.Read(File.OpenRead(path), out diagnostic); } return FromDocument(document, settings); @@ -51,21 +51,21 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser [PublicAPI] public IReadOnlyList FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null) { - return new OpenApiPathsMapper(settings ?? new WireMockOpenApiParserSettings()).ToMappingModels(document.Paths, document.Servers ?? []); + return new OpenApiPathsMapper(settings ?? new WireMockOpenApiParserSettings()).ToMappingModels(document.Paths, document.Servers); } /// [PublicAPI] public IReadOnlyList FromStream(Stream stream, out OpenApiDiagnostic diagnostic) { - return FromDocument(Read(stream, out diagnostic)); + return FromDocument(_reader.Read(stream, out diagnostic)); } /// [PublicAPI] public IReadOnlyList FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) { - return FromDocument(Read(stream, out diagnostic), settings); + return FromDocument(_reader.Read(stream, out diagnostic), settings); } /// @@ -81,27 +81,4 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser { return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), settings, out diagnostic); } - - private static OpenApiDocument Read(Stream stream, out OpenApiDiagnostic diagnostic) - { - var reader = new OpenApiYamlReader(); - - if (stream is not MemoryStream memoryStream) - { - memoryStream = ReadStreamIntoMemoryStream(stream); - } - - var result = reader.Read(memoryStream, ReaderSettings); - - diagnostic = result.Diagnostic ?? new OpenApiDiagnostic(); - return result.Document ?? throw new InvalidOperationException("The document is null."); - } - - private static MemoryStream ReadStreamIntoMemoryStream(Stream stream) - { - var memoryStream = new MemoryStream(); - stream.CopyTo(memoryStream); - memoryStream.Position = 0; - return memoryStream; - } } \ No newline at end of file diff --git a/src-preview/WireMock.Net.OpenApiParser.Preview/Utils/ExampleValueGenerator.cs b/src-preview/WireMock.Net.OpenApiParser.Preview/Utils/ExampleValueGenerator.cs deleted file mode 100644 index 153d4c67..00000000 --- a/src-preview/WireMock.Net.OpenApiParser.Preview/Utils/ExampleValueGenerator.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright © WireMock.Net - -using System; -using System.Linq; -using System.Text.Json.Nodes; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Models.Interfaces; -using Stef.Validation; -using WireMock.Net.OpenApiParser.Extensions; -using WireMock.Net.OpenApiParser.Settings; -using WireMock.Net.OpenApiParser.Types; - -namespace WireMock.Net.OpenApiParser.Utils; - -internal class ExampleValueGenerator -{ - private readonly IWireMockOpenApiParserExampleValues _exampleValues; - - public ExampleValueGenerator(WireMockOpenApiParserSettings settings) - { - Guard.NotNull(settings); - - // Check if user provided an own implementation - if (settings.ExampleValues is null) - { - if (settings.DynamicExamples) - { - _exampleValues = new WireMockOpenApiParserDynamicExampleValues(); - } - else - { - _exampleValues = new WireMockOpenApiParserExampleValues(); - } - } - else - { - _exampleValues = settings.ExampleValues; - } - } - - public JsonNode GetExampleValue(IOpenApiSchema? schema) - { - var schemaExample = schema?.Example; - var schemaEnum = schema?.Enum?.FirstOrDefault(); - - _exampleValues.Schema = schema; - - switch (schema?.GetSchemaType(out _)) - { - case JsonSchemaType.Boolean: - var exampleBoolean = schemaExample?.GetValue(); - return exampleBoolean ?? _exampleValues.Boolean; - - case JsonSchemaType.Integer: - var exampleInteger = schemaExample?.GetValue(); - var enumInteger = schemaEnum?.GetValue(); - var valueIntegerEnumOrExample = enumInteger ?? exampleInteger; - return valueIntegerEnumOrExample ?? _exampleValues.Integer; - - case JsonSchemaType.Number: - switch (schema.GetSchemaFormat()) - { - case SchemaFormat.Float: - var exampleFloat = schemaExample?.GetValue(); - var enumFloat = schemaEnum?.GetValue(); - var valueFloatEnumOrExample = enumFloat ?? exampleFloat; - return valueFloatEnumOrExample ?? _exampleValues.Float; - - default: - var exampleDecimal = schemaExample?.GetValue(); - var enumDecimal = schemaEnum?.GetValue(); - var valueDecimalEnumOrExample = enumDecimal ?? exampleDecimal; - return valueDecimalEnumOrExample ?? _exampleValues.Decimal; - } - - default: - switch (schema?.GetSchemaFormat()) - { - case SchemaFormat.Date: - var exampleDate = schemaExample?.GetValue(); - var enumDate = schemaEnum?.GetValue(); - var valueDateEnumOrExample = enumDate ?? exampleDate; - return valueDateEnumOrExample ?? DateTimeUtils.ToRfc3339Date(_exampleValues.Date()); - - case SchemaFormat.DateTime: - var exampleDateTime = schemaExample?.GetValue(); - var enumDateTime = schemaEnum?.GetValue(); - var valueDateTimeEnumOrExample = enumDateTime ?? exampleDateTime; - return valueDateTimeEnumOrExample ?? DateTimeUtils.ToRfc3339DateTime(_exampleValues.DateTime()); - - case SchemaFormat.Byte: - var exampleByte = schemaExample?.GetValue(); - var enumByte = schemaEnum?.GetValue(); - var valueByteEnumOrExample = enumByte ?? exampleByte; - return Convert.ToBase64String(valueByteEnumOrExample ?? _exampleValues.Bytes); - - default: - var exampleString = schemaExample?.GetValue(); - var enumString = schemaEnum?.GetValue(); - var valueStringEnumOrExample = enumString ?? exampleString; - return valueStringEnumOrExample ?? _exampleValues.String; - } - } - } -} \ 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 8034318e..a819a1f9 100644 --- a/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs +++ b/src/WireMock.Net.OpenApiParser/Extensions/DictionaryExtensions.cs @@ -1,6 +1,6 @@ // Copyright © WireMock.Net -#if NET46 || NETSTANDARD2_0 +#if NET46 || NET47 || NETSTANDARD2_0 using System.Collections.Generic; namespace WireMock.Net.OpenApiParser.Extensions; diff --git a/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs index efd4bfcd..51da19f3 100644 --- a/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs +++ b/src/WireMock.Net.OpenApiParser/Extensions/OpenApiSchemaExtensions.cs @@ -1,60 +1,62 @@ // Copyright © WireMock.Net +using System.Collections.Generic; using System.Linq; -using Microsoft.OpenApi.Any; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using WireMock.Net.OpenApiParser.Types; namespace WireMock.Net.OpenApiParser.Extensions; 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) + public static bool TryGetXNullable(this IOpenApiSchema schema, out bool value) { value = false; - if (schema.Extensions.TryGetValue("x-nullable", out var e) && e is OpenApiBoolean openApiBoolean) +#pragma warning disable S3011 + var extensionsProperty = schema.GetType().GetProperty("Extensions", BindingFlags.Instance | BindingFlags.NonPublic); +#pragma warning restore S3011 + if (extensionsProperty?.GetValue(schema) is Dictionary extensions && extensions.TryGetValue("x-nullable", out var nullExtRawValue)) { - value = openApiBoolean.Value; - return true; + var nodeProperty = nullExtRawValue.GetType().GetProperty("Node", BindingFlags.Instance | BindingFlags.Public); + if (nodeProperty?.GetValue(nullExtRawValue) is JsonNode jsonNode) + { + return jsonNode.GetValueKind() == JsonValueKind.True; + } } return false; } - public static SchemaType GetSchemaType(this OpenApiSchema? schema) + public static JsonSchemaType? GetSchemaType(this IOpenApiSchema? schema, out bool isNullable) { + isNullable = false; + if (schema == null) { - return SchemaType.Unknown; + return null; } if (schema.Type == null) { - if (schema.AllOf.Any() || schema.AnyOf.Any()) + if (schema.AllOf?.Any() == true || schema.AnyOf?.Any() == true) { - return SchemaType.Object; + return JsonSchemaType.Object; } } - return schema.Type switch - { - "object" => SchemaType.Object, - "array" => SchemaType.Array, - "integer" => SchemaType.Integer, - "number" => SchemaType.Number, - "boolean" => SchemaType.Boolean, - "string" => SchemaType.String, - "file" => SchemaType.File, - _ => SchemaType.Unknown - }; + isNullable = (schema.Type | JsonSchemaType.Null) == JsonSchemaType.Null || (schema.TryGetXNullable(out var xNullable) && xNullable); + + // Removes the Null flag from the schema.Type, ensuring the returned value represents a non-nullable type. + return schema.Type & ~JsonSchemaType.Null; } - public static SchemaFormat GetSchemaFormat(this OpenApiSchema? schema) + public static SchemaFormat GetSchemaFormat(this IOpenApiSchema? schema) { switch (schema?.Format) { diff --git a/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs b/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs index 494d4f37..12297f65 100644 --- a/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs +++ b/src/WireMock.Net.OpenApiParser/Extensions/WireMockServerExtensions.cs @@ -3,9 +3,8 @@ using System.IO; using System.Linq; using JetBrains.Annotations; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers; using Stef.Validation; +using WireMock.Net.OpenApiParser.Models; using WireMock.Net.OpenApiParser.Settings; using WireMock.Server; @@ -17,7 +16,7 @@ namespace WireMock.Net.OpenApiParser.Extensions; public static class WireMockServerExtensions { /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. + /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file. /// /// The WireMockServer instance /// Path containing OpenAPI file to parse and use the mappings. @@ -29,7 +28,7 @@ public static class WireMockServerExtensions } /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 file. + /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file. /// /// The WireMockServer instance /// Path containing OpenAPI file to parse and use the mappings. @@ -47,7 +46,7 @@ public static class WireMockServerExtensions } /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. + /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream. /// /// The WireMockServer instance /// Stream containing OpenAPI description to parse and use the mappings. @@ -59,7 +58,7 @@ public static class WireMockServerExtensions } /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream. + /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream. /// /// The WireMockServer instance /// Stream containing OpenAPI description to parse and use the mappings. @@ -78,13 +77,13 @@ public static class WireMockServerExtensions } /// - /// Register the mappings via an OpenAPI (swagger) V2 or V3 document. + /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 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) + public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, object document, WireMockOpenApiParserSettings? settings = null) { Guard.NotNull(server); Guard.NotNull(document); diff --git a/src/WireMock.Net.OpenApiParser/ILRepack.targets b/src/WireMock.Net.OpenApiParser/ILRepack.targets new file mode 100644 index 00000000..ae5e5336 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/ILRepack.targets @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs b/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs index e3b2b728..62f1588b 100644 --- a/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs +++ b/src/WireMock.Net.OpenApiParser/IWireMockOpenApiParser.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.IO; -using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers; using WireMock.Admin.Mappings; +using WireMock.Net.OpenApiParser.Models; using WireMock.Net.OpenApiParser.Settings; namespace WireMock.Net.OpenApiParser; @@ -17,7 +16,7 @@ public interface IWireMockOpenApiParser /// /// Generate from a file-path. /// - /// The path to read the OpenApi/Swagger/V2/V3 or Raml file. + /// The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file. /// OpenApiDiagnostic output /// MappingModel IReadOnlyList FromFile(string path, out OpenApiDiagnostic diagnostic); @@ -25,19 +24,19 @@ public interface IWireMockOpenApiParser /// /// Generate from a file-path. /// - /// The path to read the OpenApi/Swagger/V2/V3 or Raml file. + /// The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file. /// Additional settings /// OpenApiDiagnostic output /// MappingModel IReadOnlyList FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic); /// - /// Generate from an . + /// Generate from an Microsoft.OpenApi.Models.OpenApiDocument. /// /// The source OpenApiDocument /// Additional settings [optional] /// MappingModel - IReadOnlyList FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null); + IReadOnlyList FromDocument(object 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 6056aecb..fae8b50e 100644 --- a/src/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs +++ b/src/WireMock.Net.OpenApiParser/Mappers/OpenApiPathsMapper.cs @@ -3,20 +3,19 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; -using Microsoft.OpenApi; -using Microsoft.OpenApi.Any; +using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Models.Interfaces; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Net.OpenApiParser.Extensions; using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Utils; +using SystemTextJsonSerializer = System.Text.Json.JsonSerializer; namespace WireMock.Net.OpenApiParser.Mappers; @@ -39,56 +38,40 @@ internal class OpenApiPathsMapper .OrderBy(p => p.Key) .Select(p => MapPath(p.Key, p.Value, servers)) .SelectMany(x => x) - .ToArray() ?? - Array.Empty(); + .ToArray() ?? []; } - private IReadOnlyList MapPaths(OpenApiPaths? paths, IList servers) + private IReadOnlyList MapPath(string path, IOpenApiPathItem pathItem, IList servers) { - return paths? - .OrderBy(p => p.Key) - .Select(p => MapPath(p.Key, p.Value, servers)) - .SelectMany(x => x) - .ToArray() ?? - Array.Empty(); - } - - private IReadOnlyList MapPath(string path, OpenApiPathItem pathItem, IList servers) - { - return pathItem.Operations.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray(); + return pathItem.Operations?.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray() ?? []; } 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 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(); + var response = operation.Responses?.FirstOrDefault() ?? new KeyValuePair(); - TryGetContent(response.Value?.Content, out OpenApiMediaType? responseContent, out string? responseContentType); + TryGetContent(response.Value?.Content, out OpenApiMediaType? responseContent, out var 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 responseBody = responseExample ?? responseSchemaExample ?? MapSchemaToObject(responseSchema); var requestBodyModel = new BodyModel(); if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required) { var request = operation.RequestBody.Content; - TryGetContent(request, out OpenApiMediaType? requestContent, out _); + TryGetContent(request, out var 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); - + var requestBodyMapped = requestBodyExample ?? requestBodySchemaExample ?? MapSchemaToObject(requestBodySchema); requestBodyModel = MapRequestBody(requestBodyMapped); } @@ -112,12 +95,12 @@ internal class OpenApiPathsMapper { StatusCode = httpStatusCode, Headers = MapHeaders(responseContentType, response.Value?.Headers), - BodyAsJson = body + BodyAsJson = responseBody != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(responseBody)) : null } }; } - private BodyModel? MapRequestBody(object? requestBody) + private BodyModel? MapRequestBody(JsonNode? requestBody) { if (requestBody == null) { @@ -129,7 +112,7 @@ internal class OpenApiPathsMapper Matcher = new MatcherModel { Name = "JsonMatcher", - Pattern = JsonConvert.SerializeObject(requestBody, Formatting.Indented), + Pattern = SystemTextJsonSerializer.Serialize(requestBody, new JsonSerializerOptions { WriteIndented = true }), IgnoreCase = _settings.RequestBodyIgnoreCase } }; @@ -160,117 +143,103 @@ internal class OpenApiPathsMapper return true; } - private object? MapSchemaToObject(OpenApiSchema? schema, string? name = null) + private JsonNode? MapSchemaToObject(IOpenApiSchema? schema) { if (schema == null) { return null; } - switch (schema.GetSchemaType()) + switch (schema.GetSchemaType(out _)) { - case SchemaType.Array: - var jArray = new JArray(); - for (int i = 0; i < _settings.NumberOfArrayItems; i++) + case JsonSchemaType.Array: + var array = new JsonArray(); + for (var i = 0; i < _settings.NumberOfArrayItems; i++) { - if (schema.Items.Properties.Count > 0) + if (schema.Items?.Properties?.Count > 0) { - var arrayItem = new JObject(); + var item = new JsonObject(); foreach (var property in schema.Items.Properties) { - var objectValue = MapSchemaToObject(property.Value, property.Key); - if (objectValue is JProperty jp) - { - arrayItem.Add(jp); - } - else - { - arrayItem.Add(new JProperty(property.Key, objectValue)); - } + item[property.Key] = MapSchemaToObject(property.Value); } - jArray.Add(arrayItem); + array.Add(item); } else { - var arrayItem = MapSchemaToObject(schema.Items, name: null); // Set name to null to force JObject instead of JProperty - jArray.Add(arrayItem); + var arrayItem = MapSchemaToObject(schema.Items); + array.Add(arrayItem); } } - if (schema.AllOf.Count > 0) + if (schema.AllOf?.Count > 0) { - jArray.Add(MapSchemaAllOfToObject(schema)); + array.Add(MapSchemaAllOfToObject(schema)); } - return jArray; + return array; - case SchemaType.Boolean: - case SchemaType.Integer: - case SchemaType.Number: - case SchemaType.String: + case JsonSchemaType.Boolean: + case JsonSchemaType.Integer: + case JsonSchemaType.Number: + case JsonSchemaType.String: return _exampleValueGenerator.GetExampleValue(schema); - case SchemaType.Object: - var propertyAsJObject = new JObject(); - foreach (var schemaProperty in schema.Properties) + case JsonSchemaType.Object: + var propertyAsJsonObject = new JsonObject(); + foreach (var schemaProperty in schema.Properties ?? new Dictionary()) { - propertyAsJObject.Add(MapPropertyAsJObject(schemaProperty.Value, schemaProperty.Key)); + propertyAsJsonObject[schemaProperty.Key] = MapPropertyAsJsonNode(schemaProperty.Value); } - if (schema.AllOf.Count > 0) + if (schema.AllOf?.Count > 0) { - foreach (var group in schema.AllOf.SelectMany(p => p.Properties).GroupBy(x => x.Key)) + foreach (var group in schema.AllOf.SelectMany(p => p.Properties ?? new Dictionary()).GroupBy(x => x.Key)) { - propertyAsJObject.Add(MapPropertyAsJObject(group.First().Value, group.Key)); + propertyAsJsonObject[group.Key] = MapPropertyAsJsonNode(group.First().Value); } } - return name != null ? new JProperty(name, propertyAsJObject) : propertyAsJObject; + return propertyAsJsonObject; default: return null; } } - private JObject MapSchemaAllOfToObject(OpenApiSchema schema) + private JsonObject MapSchemaAllOfToObject(IOpenApiSchema schema) { - var arrayItem = new JObject(); - foreach (var property in schema.AllOf) + var arrayItem = new JsonObject(); + foreach (var property in schema.AllOf ?? []) { - foreach (var item in property.Properties) + foreach (var item in property.Properties ?? new Dictionary()) { - arrayItem.Add(MapPropertyAsJObject(item.Value, item.Key)); + arrayItem[item.Key] = MapPropertyAsJsonNode(item.Value); } } return arrayItem; } - private object MapPropertyAsJObject(OpenApiSchema openApiSchema, string key) + private JsonNode? MapPropertyAsJsonNode(IOpenApiSchema openApiSchema) { - if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array) + var schemaType = openApiSchema.GetSchemaType(out _); + if (schemaType is JsonSchemaType.Object or JsonSchemaType.Array) { - var mapped = MapSchemaToObject(openApiSchema, key); - if (mapped is JProperty jp) - { - return jp; - } - - return new JProperty(key, mapped); + return MapSchemaToObject(openApiSchema); } - // bool propertyIsNullable = openApiSchema.Nullable || (openApiSchema.TryGetXNullable(out bool x) && x); - return new JProperty(key, _exampleValueGenerator.GetExampleValue(openApiSchema)); + return _exampleValueGenerator.GetExampleValue(openApiSchema); } - private string MapPathWithParameters(string path, IEnumerable? parameters) + private string MapPathWithParameters(string path, IEnumerable? parameters) { if (parameters == null) { return path; } - string newPath = path; + var newPath = path; foreach (var parameter in parameters) { var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse); @@ -280,93 +249,56 @@ internal class OpenApiPathsMapper return newPath; } - private string MapBasePath(IList? servers) + private IDictionary? MapHeaders(string? responseContentType, IDictionary? headers) { - if (servers == null || servers.Count == 0) - { - return string.Empty; - } - - OpenApiServer server = servers.First(); - if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out Uri uriResult)) - { - return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString(); - } - - return string.Empty; - } - - private JToken? MapOpenApiAnyToJToken(IOpenApiAny? any) - { - 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()); - } - - return JObject.Parse(outputString.ToString()); - } - - private IDictionary? MapHeaders(string? responseContentType, IDictionary? headers) - { - var mappedHeaders = headers?.ToDictionary( - item => item.Key, - _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern! - ) ?? new Dictionary(); + var mappedHeaders = headers? + .ToDictionary(item => item.Key, _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!) ?? new Dictionary(); if (!string.IsNullOrEmpty(responseContentType)) { - mappedHeaders.TryAdd(HeaderContentType, responseContentType!); + mappedHeaders.TryAdd(HeaderContentType, responseContentType); } return mappedHeaders.Keys.Any() ? mappedHeaders : null; } - private IList? MapQueryParameters(IEnumerable queryParameters) + private IList? MapQueryParameters(IEnumerable queryParameters) { var list = queryParameters .Where(req => req.Required) .Select(qp => new ParamModel { - Name = qp.Name, + Name = qp.Name ?? string.Empty, IgnoreCase = _settings.QueryParameterPatternIgnoreCase, - Matchers = new[] - { + Matchers = + [ GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse) - } + ] }) .ToList(); return list.Any() ? list : null; } - private IList? MapRequestHeaders(IEnumerable headers) + private IList? MapRequestHeaders(IEnumerable headers) { var list = headers .Where(req => req.Required) .Select(qp => new HeaderModel { - Name = qp.Name, + Name = qp.Name ?? string.Empty, IgnoreCase = _settings.HeaderPatternIgnoreCase, - Matchers = new[] - { + Matchers = + [ GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse) - } + ] }) .ToList(); return list.Any() ? list : null; } - private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType type) + private MatcherModel GetExampleMatcherModel(IOpenApiSchema? schema, ExampleValueType type) { return type switch { @@ -385,15 +317,31 @@ internal class OpenApiPathsMapper }; } - private string GetExampleValueAsStringForSchemaType(OpenApiSchema? schema) + private string GetExampleValueAsStringForSchemaType(IOpenApiSchema? schema) { var value = _exampleValueGenerator.GetExampleValue(schema); - return value switch + if (value.GetValueKind() == JsonValueKind.String) { - string valueAsString => valueAsString, + return value.GetValue(); + } - _ => value.ToString(), - }; + return value.ToString(); + } + + private static string MapBasePath(IList? servers) + { + var server = servers?.FirstOrDefault(); + if (server == null) + { + return string.Empty; + } + + if (Uri.TryCreate(server.Url, UriKind.RelativeOrAbsolute, out var uriResult)) + { + return uriResult.IsAbsoluteUri ? uriResult.AbsolutePath : uriResult.ToString(); + } + + return string.Empty; } } \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Models/OpenApiDiagnostic.cs b/src/WireMock.Net.OpenApiParser/Models/OpenApiDiagnostic.cs new file mode 100644 index 00000000..97473e60 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Models/OpenApiDiagnostic.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using RamlToOpenApiConverter; + +namespace WireMock.Net.OpenApiParser.Models; + +/// +/// Object containing all diagnostic information related to Open API parsing. +/// +public class OpenApiDiagnostic +{ + /// + /// List of all errors. + /// + public List Errors { get; set; } = []; + + /// + /// List of all warnings + /// + public List Warnings { get; set; } = []; + + /// + /// Open API specification version of the document parsed. + /// + public OpenApiSpecificationVersion SpecificationVersion { get; set; } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Models/OpenApiError.cs b/src/WireMock.Net.OpenApiParser/Models/OpenApiError.cs new file mode 100644 index 00000000..b3d8bbe0 --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Models/OpenApiError.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace WireMock.Net.OpenApiParser.Models; + +/// +/// Error related to the Open API Document. +/// +public class OpenApiError +{ + /// + /// Initializes the class. + /// + public OpenApiError(string? pointer, string message) + { + Pointer = pointer; + Message = message; + } + + /// + /// Initializes a copy of an object + /// + public OpenApiError(OpenApiError error) + { + Pointer = error.Pointer; + Message = error.Message; + } + + /// + /// Message explaining the error. + /// + public string Message { get; set; } + + /// + /// Pointer to the location of the error. + /// + public string? Pointer { get; set; } + + /// + /// Gets the string representation of . + /// + public override string ToString() + { + return Message + (!string.IsNullOrEmpty(Pointer) ? " [" + Pointer + "]" : ""); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/Models/OpenApiMapper.cs b/src/WireMock.Net.OpenApiParser/Models/OpenApiMapper.cs new file mode 100644 index 00000000..9d84712e --- /dev/null +++ b/src/WireMock.Net.OpenApiParser/Models/OpenApiMapper.cs @@ -0,0 +1,25 @@ +// Copyright © WireMock.Net + +using System.Linq; +using RamlToOpenApiConverter; +using MicrosoftOpenApiDiagnostic = Microsoft.OpenApi.Reader.OpenApiDiagnostic; + +namespace WireMock.Net.OpenApiParser.Models; + +internal static class OpenApiMapper +{ + internal static OpenApiDiagnostic? Map(MicrosoftOpenApiDiagnostic? openApiDiagnostic) + { + if (openApiDiagnostic == null) + { + return null; + } + + return new OpenApiDiagnostic + { + Errors = openApiDiagnostic.Errors.Select(e => new OpenApiError(e.Pointer, e.Message)).ToList(), + Warnings = openApiDiagnostic.Warnings.Select(e => new OpenApiError(e.Pointer, e.Message)).ToList(), + SpecificationVersion = (OpenApiSpecificationVersion)openApiDiagnostic.SpecificationVersion + }; + } +} \ 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 eecfed7e..5d335144 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/IWireMockOpenApiParserExampleValues.cs @@ -1,12 +1,12 @@ // Copyright © WireMock.Net using System; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; namespace WireMock.Net.OpenApiParser.Settings; /// -/// A interface defining the example values to use for the different types. +/// An interface defining the example values to use for the different types. /// public interface IWireMockOpenApiParserExampleValues { @@ -26,9 +26,9 @@ public interface IWireMockOpenApiParserExampleValues float Float { get; } /// - /// An example value for a Double. + /// An example value for a Decimal. /// - double Double { get; } + decimal Decimal { get; } /// /// An example value for a Date. @@ -58,5 +58,5 @@ public interface IWireMockOpenApiParserExampleValues /// /// OpenApi Schema to generate dynamic examples more accurate /// - OpenApiSchema? Schema { get; set; } + IOpenApiSchema? 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 46b2c766..194762c5 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserDynamicExampleValues.cs @@ -1,7 +1,7 @@ // Copyright © WireMock.Net using System; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using RandomDataGenerator.FieldOptions; using RandomDataGenerator.Randomizers; @@ -22,7 +22,7 @@ public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserE public virtual float Float => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; /// - public virtual double Double => RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d; + public virtual decimal Decimal => SafeConvertFloatToDecimal(RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f); /// public virtual Func Date => () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; @@ -40,5 +40,20 @@ public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserE public virtual string String => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; /// - public virtual OpenApiSchema? Schema { get; set; } + public virtual IOpenApiSchema? Schema { get; set; } + + /// + /// Safely converts a float to a decimal, ensuring the value stays within the bounds of a decimal. + /// + /// The float value to convert. + /// A decimal value within the valid range of a decimal. + private static decimal SafeConvertFloatToDecimal(float value) + { + return value switch + { + < (float)decimal.MinValue => decimal.MinValue, + > (float)decimal.MaxValue => decimal.MaxValue, + _ => (decimal)value + }; + } } \ 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 99665cf0..3b3c6fea 100644 --- a/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs +++ b/src/WireMock.Net.OpenApiParser/Settings/WireMockOpenApiParserExampleValues.cs @@ -2,6 +2,7 @@ using System; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; namespace WireMock.Net.OpenApiParser.Settings; @@ -20,7 +21,7 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV public virtual float Float => 4.2f; /// - public virtual double Double => 4.2d; + public virtual decimal Decimal => 4.2m; /// public virtual Func Date { get; } = () => System.DateTime.UtcNow.Date; @@ -29,7 +30,7 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV public virtual Func DateTime { get; } = () => System.DateTime.UtcNow; /// - public virtual byte[] Bytes { get; } = { 48, 49, 50 }; + public virtual byte[] Bytes { get; } = [48, 49, 50]; /// public virtual object Object => "example-object"; @@ -38,5 +39,5 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV public virtual string String => "example-string"; /// - public virtual OpenApiSchema? Schema { get; set; } = new(); + public virtual IOpenApiSchema? Schema { get; set; } = new OpenApiSchema(); } \ 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 c0108295..e3178549 100644 --- a/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs +++ b/src/WireMock.Net.OpenApiParser/Utils/DateTimeUtils.cs @@ -7,13 +7,16 @@ namespace WireMock.Net.OpenApiParser.Utils; internal static class DateTimeUtils { + private const string DateFormat = "yyyy-MM-dd"; + private const string DateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.fffzzz"; + public static string ToRfc3339DateTime(DateTime dateTime) { - return dateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo); + return dateTime.ToString(DateTimeFormat, DateTimeFormatInfo.InvariantInfo); } public static string ToRfc3339Date(DateTime dateTime) { - return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo); + return dateTime.ToString(DateFormat, 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 4039a358..153d4c67 100644 --- a/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs +++ b/src/WireMock.Net.OpenApiParser/Utils/ExampleValueGenerator.cs @@ -1,8 +1,10 @@ // Copyright © WireMock.Net +using System; using System.Linq; -using Microsoft.OpenApi.Any; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models.Interfaces; using Stef.Validation; using WireMock.Net.OpenApiParser.Extensions; using WireMock.Net.OpenApiParser.Settings; @@ -36,82 +38,66 @@ internal class ExampleValueGenerator } } - public object GetExampleValue(OpenApiSchema? schema) + public JsonNode GetExampleValue(IOpenApiSchema? schema) { var schemaExample = schema?.Example; var schemaEnum = schema?.Enum?.FirstOrDefault(); _exampleValues.Schema = schema; - switch (schema?.GetSchemaType()) + switch (schema?.GetSchemaType(out _)) { - case SchemaType.Boolean: - var exampleBoolean = schemaExample as OpenApiBoolean; - return exampleBoolean?.Value ?? _exampleValues.Boolean; + case JsonSchemaType.Boolean: + var exampleBoolean = schemaExample?.GetValue(); + return exampleBoolean ?? _exampleValues.Boolean; - case SchemaType.Integer: - switch (schema?.GetSchemaFormat()) - { - case SchemaFormat.Int64: - var exampleLong = schemaExample as OpenApiLong; - var enumLong = schemaEnum as OpenApiLong; - var valueLongEnumOrExample = enumLong?.Value ?? exampleLong?.Value; - return valueLongEnumOrExample ?? _exampleValues.Integer; + case JsonSchemaType.Integer: + var exampleInteger = schemaExample?.GetValue(); + var enumInteger = schemaEnum?.GetValue(); + var valueIntegerEnumOrExample = enumInteger ?? exampleInteger; + return valueIntegerEnumOrExample ?? _exampleValues.Integer; - default: - var exampleInteger = schemaExample as OpenApiInteger; - var enumInteger = schemaEnum as OpenApiInteger; - var valueIntegerEnumOrExample = enumInteger?.Value ?? exampleInteger?.Value; - return valueIntegerEnumOrExample ?? _exampleValues.Integer; - } - - case SchemaType.Number: - switch (schema?.GetSchemaFormat()) + case JsonSchemaType.Number: + switch (schema.GetSchemaFormat()) { case SchemaFormat.Float: - var exampleFloat = schemaExample as OpenApiFloat; - var enumFloat = schemaEnum as OpenApiFloat; - var valueFloatEnumOrExample = enumFloat?.Value ?? exampleFloat?.Value; + var exampleFloat = schemaExample?.GetValue(); + var enumFloat = schemaEnum?.GetValue(); + var valueFloatEnumOrExample = enumFloat ?? exampleFloat; return valueFloatEnumOrExample ?? _exampleValues.Float; default: - var exampleDouble = schemaExample as OpenApiDouble; - var enumDouble = schemaEnum as OpenApiDouble; - var valueDoubleEnumOrExample = enumDouble?.Value ?? exampleDouble?.Value; - return valueDoubleEnumOrExample ?? _exampleValues.Double; + var exampleDecimal = schemaExample?.GetValue(); + var enumDecimal = schemaEnum?.GetValue(); + var valueDecimalEnumOrExample = enumDecimal ?? exampleDecimal; + return valueDecimalEnumOrExample ?? _exampleValues.Decimal; } default: switch (schema?.GetSchemaFormat()) { case SchemaFormat.Date: - var exampleDate = schemaExample as OpenApiDate; - var enumDate = schemaEnum as OpenApiDate; - var valueDateEnumOrExample = enumDate?.Value ?? exampleDate?.Value; - return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _exampleValues.Date()); + var exampleDate = schemaExample?.GetValue(); + var enumDate = schemaEnum?.GetValue(); + var valueDateEnumOrExample = enumDate ?? exampleDate; + return valueDateEnumOrExample ?? DateTimeUtils.ToRfc3339Date(_exampleValues.Date()); case SchemaFormat.DateTime: - var exampleDateTime = schemaExample as OpenApiDateTime; - var enumDateTime = schemaEnum as OpenApiDateTime; - var valueDateTimeEnumOrExample = enumDateTime?.Value ?? exampleDateTime?.Value; - return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _exampleValues.DateTime()); + var exampleDateTime = schemaExample?.GetValue(); + var enumDateTime = schemaEnum?.GetValue(); + var valueDateTimeEnumOrExample = enumDateTime ?? exampleDateTime; + return valueDateTimeEnumOrExample ?? DateTimeUtils.ToRfc3339DateTime(_exampleValues.DateTime()); case SchemaFormat.Byte: - var exampleByte = schemaExample as OpenApiByte; - var enumByte = schemaEnum as OpenApiByte; - var valueByteEnumOrExample = enumByte?.Value ?? exampleByte?.Value; - return valueByteEnumOrExample ?? _exampleValues.Bytes; - - case SchemaFormat.Binary: - var exampleBinary = schemaExample as OpenApiBinary; - var enumBinary = schemaEnum as OpenApiBinary; - var valueBinaryEnumOrExample = enumBinary?.Value ?? exampleBinary?.Value; - return valueBinaryEnumOrExample ?? _exampleValues.Object; + var exampleByte = schemaExample?.GetValue(); + var enumByte = schemaEnum?.GetValue(); + var valueByteEnumOrExample = enumByte ?? exampleByte; + return Convert.ToBase64String(valueByteEnumOrExample ?? _exampleValues.Bytes); default: - var exampleString = schemaExample as OpenApiString; - var enumString = schemaEnum as OpenApiString; - var valueStringEnumOrExample = enumString?.Value ?? exampleString?.Value; + var exampleString = schemaExample?.GetValue(); + var enumString = schemaEnum?.GetValue(); + var valueStringEnumOrExample = enumString ?? exampleString; return valueStringEnumOrExample ?? _exampleValues.String; } } diff --git a/src/WireMock.Net.OpenApiParser/Utils/PathUtils.cs b/src/WireMock.Net.OpenApiParser/Utils/PathUtils.cs index c46b3923..f91681e1 100644 --- a/src/WireMock.Net.OpenApiParser/Utils/PathUtils.cs +++ b/src/WireMock.Net.OpenApiParser/Utils/PathUtils.cs @@ -1,5 +1,7 @@ // Copyright © WireMock.Net +using System.Text; + namespace WireMock.Net.OpenApiParser.Utils; internal static class PathUtils @@ -11,17 +13,17 @@ internal static class PathUtils return string.Empty; } - var result = paths[0].Trim().TrimEnd('/'); + var result = new StringBuilder(paths[0].Trim().TrimEnd('/')); - for (int i = 1; i < paths.Length; i++) + for (var i = 1; i < paths.Length; i++) { var nextPath = paths[i].Trim().TrimStart('/').TrimEnd('/'); if (!string.IsNullOrEmpty(nextPath)) { - result += '/' + nextPath; + result.Append('/').Append(nextPath); } } - return result; + return result.ToString(); } } \ 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 9fac3c76..299cb27a 100644 --- a/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj +++ b/src/WireMock.Net.OpenApiParser/WireMock.Net.OpenApiParser.csproj @@ -1,16 +1,16 @@ - + An OpenApi (swagger) parser to generate MappingModel or mapping.json file. - net46;netstandard2.0;netstandard2.1 + netstandard2.0;net8.0 true wiremock;openapi;OAS;raml;converter;parser;openapiparser - {D3804228-91F4-4502-9595-39584E5AADAD} + {E5B03EEF-822C-4295-952B-4479AD30082B} true ../WireMock.Net/WireMock.Net.ruleset - true + MIT @@ -20,18 +20,31 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs b/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs index c027eed9..c4f16dcd 100644 --- a/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs +++ b/src/WireMock.Net.OpenApiParser/WireMockOpenApiParser.cs @@ -6,20 +6,35 @@ using System.IO; using System.Text; using JetBrains.Annotations; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.YamlReader; using RamlToOpenApiConverter; using WireMock.Admin.Mappings; using WireMock.Net.OpenApiParser.Mappers; +using WireMock.Net.OpenApiParser.Models; using WireMock.Net.OpenApiParser.Settings; +using OpenApiDiagnostic = WireMock.Net.OpenApiParser.Models.OpenApiDiagnostic; namespace WireMock.Net.OpenApiParser; /// -/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels. +/// Parse a OpenApi/Swagger/V2/V3/V3.1 to WireMock.Net MappingModels. /// public class WireMockOpenApiParser : IWireMockOpenApiParser { - private readonly OpenApiStreamReader _reader = new(); + private readonly OpenApiReaderSettings _readerSettings; + + /// + /// Initializes a new instance of the class. + /// + public WireMockOpenApiParser() + { + _readerSettings = new OpenApiReaderSettings(); + _readerSettings.AddMicrosoftExtensionParsers(); + _readerSettings.AddJsonReader(); + _readerSettings.TryAddReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + _readerSettings.TryAddReader(OpenApiConstants.Yml, new OpenApiYamlReader()); + } /// [PublicAPI] @@ -40,8 +55,7 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser } else { - var reader = new OpenApiStreamReader(); - document = reader.Read(File.OpenRead(path), out diagnostic); + document = Read(File.OpenRead(path), out diagnostic); } return FromDocument(document, settings); @@ -49,23 +63,25 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser /// [PublicAPI] - public IReadOnlyList FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null) + public IReadOnlyList FromDocument(object document, WireMockOpenApiParserSettings? settings = null) { - return new OpenApiPathsMapper(settings ?? new WireMockOpenApiParserSettings()).ToMappingModels(document.Paths, document.Servers); + var openApiDocument = document as OpenApiDocument ?? throw new ArgumentException("The document should be a Microsoft.OpenApi.Models.OpenApiDocument", nameof(document)); + + return new OpenApiPathsMapper(settings ?? new WireMockOpenApiParserSettings()).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers ?? []); } /// [PublicAPI] public IReadOnlyList FromStream(Stream stream, out OpenApiDiagnostic diagnostic) { - return FromDocument(_reader.Read(stream, out diagnostic)); + return FromDocument(Read(stream, out diagnostic)); } /// [PublicAPI] public IReadOnlyList FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) { - return FromDocument(_reader.Read(stream, out diagnostic), settings); + return FromDocument(Read(stream, out diagnostic), settings); } /// @@ -81,4 +97,25 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser { return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), settings, out diagnostic); } + + private OpenApiDocument Read(Stream stream, out OpenApiDiagnostic diagnostic) + { + if (stream is not MemoryStream memoryStream) + { + memoryStream = ReadStreamIntoMemoryStream(stream); + } + + var result = OpenApiDocument.Load(memoryStream, settings: _readerSettings); + + diagnostic = OpenApiMapper.Map(result.Diagnostic) ?? new OpenApiDiagnostic(); + return result.Document ?? throw new InvalidOperationException("The document is null."); + } + + private static MemoryStream ReadStreamIntoMemoryStream(Stream stream) + { + var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + memoryStream.Position = 0; + return memoryStream; + } } \ No newline at end of file diff --git a/src/WireMock.Net/Json/DynamicPropertyWithValue.cs b/src/WireMock.Net/Json/DynamicPropertyWithValue.cs deleted file mode 100644 index 21b0b23d..00000000 --- a/src/WireMock.Net/Json/DynamicPropertyWithValue.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright © WireMock.Net - -// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq - -using System.Linq.Dynamic.Core; - -namespace WireMock.Json; - -internal class DynamicPropertyWithValue : DynamicProperty -{ - public object? Value { get; } - - public DynamicPropertyWithValue(string name, object? value) : base(name, value?.GetType() ?? typeof(object)) - { - Value = value; - } -} \ No newline at end of file diff --git a/src/WireMock.Net/Json/FloatBehavior.cs b/src/WireMock.Net/Json/FloatBehavior.cs index 13653338..0172a899 100644 --- a/src/WireMock.Net/Json/FloatBehavior.cs +++ b/src/WireMock.Net/Json/FloatBehavior.cs @@ -5,7 +5,7 @@ namespace WireMock.Json; /// -/// Enum to define how to convert an Float in the Json Object. +/// Enum to define how to convert a Float in the Json Object. /// internal enum FloatBehavior { diff --git a/src/WireMock.Net/Json/JObjectExtensions.cs b/src/WireMock.Net/Json/JObjectExtensions.cs deleted file mode 100644 index 203ceb09..00000000 --- a/src/WireMock.Net/Json/JObjectExtensions.cs +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright © WireMock.Net - -// Copied from https://github.com/Handlebars-Net/Handlebars.Net.Helpers/blob/master/src/Handlebars.Net.Helpers.DynamicLinq which is copied from https://github.com/StefH/JsonConverter - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Dynamic.Core; -using System.Reflection; -using Newtonsoft.Json.Linq; - -namespace WireMock.Json; - -internal static class JObjectExtensions -{ - private class JTokenResolvers : Dictionary>; - - private static readonly JTokenResolvers Resolvers = new() - { - { JTokenType.Array, ConvertJTokenArray }, - { JTokenType.Boolean, (jToken, _) => jToken.Value() }, - { JTokenType.Bytes, (jToken, _) => jToken.Value() }, - { JTokenType.Date, (jToken, _) => jToken.Value() }, - { JTokenType.Float, ConvertJTokenFloat }, - { JTokenType.Guid, (jToken, _) => jToken.Value() }, - { JTokenType.Integer, ConvertJTokenInteger }, - { JTokenType.None, (_, _) => null }, - { JTokenType.Null, (_, _) => null }, - { JTokenType.Object, ConvertJObject }, - { JTokenType.Property, ConvertJTokenProperty }, - { JTokenType.String, (jToken, _) => jToken.Value() }, - { JTokenType.TimeSpan, (jToken, _) => jToken.Value() }, - { JTokenType.Undefined, (_, _) => null }, - { JTokenType.Uri, (o, _) => o.Value() }, - }; - - internal static DynamicClass? ToDynamicJsonClass(this JObject? src, DynamicJsonClassOptions? options = null) - { - if (src == null) - { - return null; - } - - var dynamicPropertyWithValues = new List(); - - foreach (var prop in src.Properties()) - { - var value = Resolvers[prop.Type](prop.Value, options); - if (value != null) - { - dynamicPropertyWithValues.Add(new DynamicPropertyWithValue(prop.Name, value)); - } - } - - return CreateInstance(dynamicPropertyWithValues); - } - - internal static IEnumerable ToDynamicClassArray(this JArray? src, DynamicJsonClassOptions? options = null) - { - if (src == null) - { - return EmptyArray.Value; - } - - return ConvertJTokenArray(src, options); - } - - private static object? ConvertJObject(JToken arg, DynamicJsonClassOptions? options = null) - { - if (arg is JObject asJObject) - { - return asJObject.ToDynamicJsonClass(options); - } - - return GetResolverFor(arg)(arg, options); - } - - private static object PassThrough(JToken arg, DynamicJsonClassOptions? options) - { - return arg; - } - - private static Func GetResolverFor(JToken arg) - { - return Resolvers.TryGetValue(arg.Type, out var result) ? result : PassThrough; - } - - private static object ConvertJTokenFloat(JToken arg, DynamicJsonClassOptions? options = null) - { - if (arg.Type != JTokenType.Float) - { - throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to double or float."); - } - - if (options?.FloatConvertBehavior == FloatBehavior.UseFloat) - { - try - { - return arg.Value(); - } - catch - { - return arg.Value(); - } - } - - if (options?.FloatConvertBehavior == FloatBehavior.UseDecimal) - { - try - { - return arg.Value(); - } - catch - { - return arg.Value(); - } - } - - - return arg.Value(); - } - - private static object ConvertJTokenInteger(JToken arg, DynamicJsonClassOptions? options = null) - { - if (arg.Type != JTokenType.Integer) - { - throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to long or int."); - } - - var longValue = arg.Value(); - - if (options is null || options.IntegerConvertBehavior == IntegerBehavior.UseInt) - { - if (longValue is >= int.MinValue and <= int.MaxValue) - { - return Convert.ToInt32(longValue); - } - } - - return longValue; - } - - private static object? ConvertJTokenProperty(JToken arg, DynamicJsonClassOptions? options = null) - { - var resolver = GetResolverFor(arg); - if (resolver is null) - { - throw new InvalidOperationException($"Unable to handle {nameof(JToken)} of type: {arg.Type}."); - } - - return resolver(arg, options); - } - - private static IEnumerable ConvertJTokenArray(JToken arg, DynamicJsonClassOptions? options = null) - { - if (arg is not JArray array) - { - throw new InvalidOperationException($"Unable to convert {nameof(JToken)} of type: {arg.Type} to {nameof(JArray)}."); - } - - var result = new List(); - foreach (var item in array) - { - result.Add(ConvertJObject(item)); - } - - var distinctType = FindSameTypeOf(result); - return distinctType == null ? result.ToArray() : ConvertToTypedArray(result, distinctType); - } - - private static Type? FindSameTypeOf(IEnumerable src) - { - var types = src.Select(o => o?.GetType()).Distinct().OfType().ToArray(); - return types.Length == 1 ? types[0] : null; - } - - private static IEnumerable ConvertToTypedArray(IEnumerable src, Type newType) - { - var method = ConvertToTypedArrayGenericMethod.MakeGenericMethod(newType); - return (IEnumerable)method.Invoke(null, [src])!; - } - - private static readonly MethodInfo ConvertToTypedArrayGenericMethod = typeof(JObjectExtensions).GetMethod(nameof(ConvertToTypedArrayGeneric), BindingFlags.NonPublic | BindingFlags.Static)!; - - private static T[] ConvertToTypedArrayGeneric(IEnumerable src) - { - return src.Cast().ToArray(); - } - - public static DynamicClass CreateInstance(IList dynamicPropertiesWithValue, bool createParameterCtor = true) - { - var type = DynamicClassFactory.CreateType(dynamicPropertiesWithValue.Cast().ToArray(), createParameterCtor); - var dynamicClass = (DynamicClass)Activator.CreateInstance(type)!; - foreach (var dynamicPropertyWithValue in dynamicPropertiesWithValue.Where(p => p.Value != null)) - { - dynamicClass.SetDynamicPropertyValue(dynamicPropertyWithValue.Name, dynamicPropertyWithValue.Value!); - } - - return dynamicClass; - } -} \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index 5f6ea747..20c45881 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -1,4 +1,4 @@ - + Lightweight Http Mocking Server for .Net, inspired by WireMock from the Java landscape. WireMock.Net @@ -46,7 +46,7 @@ $(DefineConstants);USE_ASPNETCORE;NET46 - + $(DefineConstants);OPENAPIPARSER @@ -59,8 +59,6 @@ - - @@ -205,7 +203,7 @@ - + \ No newline at end of file diff --git a/test/WireMock.Net.Tests/OpenApiParser/WireMockOpenApiParserTests.FromText_ShouldReturnMappings.verified.txt b/test/WireMock.Net.Tests/OpenApiParser/WireMockOpenApiParserTests.FromText_ShouldReturnMappings.verified.txt index 7190423d..382d8455 100644 --- a/test/WireMock.Net.Tests/OpenApiParser/WireMockOpenApiParserTests.FromText_ShouldReturnMappings.verified.txt +++ b/test/WireMock.Net.Tests/OpenApiParser/WireMockOpenApiParserTests.FromText_ShouldReturnMappings.verified.txt @@ -334,7 +334,7 @@ "processingTerminalId": "example-string", "order": { "orderId": "example-string", - "dateTime": "2024-06-19T12:34:56.000+00:00", + "dateTime": "2024-06-19T12:34:56.000\u002B00:00", "description": "example-string", "amount": 42, "currency": "AED", @@ -1787,7 +1787,7 @@ "processingTerminalId": "example-string", "order": { "orderId": "example-string", - "dateTime": "2024-06-19T12:34:56.000+00:00", + "dateTime": "2024-06-19T12:34:56.000\u002B00:00", "description": "example-string", "amount": 42, "currency": "AED" @@ -2714,7 +2714,7 @@ "processingTerminalId": "example-string", "order": { "orderId": "example-string", - "dateTime": "2024-06-19T12:34:56.000+00:00", + "dateTime": "2024-06-19T12:34:56.000\u002B00:00", "description": "example-string", "amount": 42, "currency": "AED", @@ -2863,7 +2863,7 @@ "processingTerminalId": "example-string", "order": { "orderId": "example-string", - "dateTime": "2024-06-19T12:34:56.000+00:00", + "dateTime": "2024-06-19T12:34:56.000\u002B00:00", "description": "example-string", "amount": 42, "currency": "AED" @@ -6893,7 +6893,7 @@ "operator": "example-string", "order": { "orderId": "example-string", - "dateTime": "2024-06-19T12:34:56.000+00:00", + "dateTime": "2024-06-19T12:34:56.000\u002B00:00", "description": "example-string", "amount": 42, "currency": "AED", @@ -7093,7 +7093,7 @@ "offlineProcessing": { "operation": "offlineDecline", "approvalCode": "example-string", - "dateTime": "2024-06-19T12:34:56.000+00:00" + "dateTime": "2024-06-19T12:34:56.000\u002B00:00" }, "autoCapture": true, "processAsSale": true @@ -11349,7 +11349,7 @@ { "op": "replace", "path": "/a/b/c", - "value": "420" + "value": 420 }, { "op": "move", @@ -11827,7 +11827,7 @@ { "op": "replace", "path": "/a/b/c", - "value": "420" + "value": 420 }, { "op": "move", @@ -12541,7 +12541,7 @@ { "op": "replace", "path": "/a/b/c", - "value": "420" + "value": 420 }, { "op": "move", @@ -12986,7 +12986,7 @@ "operator": "example-string", "order": { "orderId": "example-string", - "dateTime": "2024-06-19T12:34:56.000+00:00", + "dateTime": "2024-06-19T12:34:56.000\u002B00:00", "description": "example-string", "amount": 42, "currency": "AED", diff --git a/test/WireMock.Net.Tests/OpenApiParser/WireMockOpenApiParserTests.cs b/test/WireMock.Net.Tests/OpenApiParser/WireMockOpenApiParserTests.cs index 59da2970..2afddcaa 100644 --- a/test/WireMock.Net.Tests/OpenApiParser/WireMockOpenApiParserTests.cs +++ b/test/WireMock.Net.Tests/OpenApiParser/WireMockOpenApiParserTests.cs @@ -23,7 +23,7 @@ public class WireMockOpenApiParserTests _exampleValuesMock.SetupGet(e => e.Boolean).Returns(true); _exampleValuesMock.SetupGet(e => e.Integer).Returns(42); _exampleValuesMock.SetupGet(e => e.Float).Returns(1.1f); - _exampleValuesMock.SetupGet(e => e.Double).Returns(2.2); + _exampleValuesMock.SetupGet(e => e.Decimal).Returns(2.2m); _exampleValuesMock.SetupGet(e => e.String).Returns("example-string"); _exampleValuesMock.SetupGet(e => e.Object).Returns("example-object"); _exampleValuesMock.SetupGet(e => e.Bytes).Returns("Stef"u8.ToArray());