Upgrade dependency RamlToOpenApiConverter to version 0.40.0 (#1505)

* Upgrade dependency RamlToOpenApiConverter to version 0.40.0

* fix code comment

* .

* Potential fix for pull request finding 'Constant condition'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Potential fix for pull request finding 'Constant condition'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* fix

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
This commit is contained in:
Stef Heyenrath
2026-09-10 20:28:43 +02:00
committed by GitHub
co-authored by Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
parent 84f4109a7c
commit f0969e9618
15 changed files with 158 additions and 227 deletions
@@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="3.7.0" /> <!--<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="3.7.0" />-->
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" />
@@ -1,10 +1,13 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
#if NETSTANDARD2_0
using System.Diagnostics.CodeAnalysis;
namespace System.Collections.Generic; namespace System.Collections.Generic;
internal static class DictionaryExtensions internal static class DictionaryExtensions
{ {
#if NETSTANDARD2_0
public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue>? dictionary, TKey key, TValue value) public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue>? dictionary, TKey key, TValue value)
{ {
if (dictionary is null || dictionary.ContainsKey(key)) if (dictionary is null || dictionary.ContainsKey(key))
@@ -16,5 +19,29 @@ internal static class DictionaryExtensions
return true; return true;
} }
}
#endif #endif
public static bool TryGetFirstValue<TKey, TValue>(this IDictionary<TKey, TValue>? dictionary, out TValue? value)
{
if (dictionary != null && dictionary.Count > 0)
{
value = dictionary.First().Value;
return true;
}
value = default;
return false;
}
public static bool TryGetFirstOrDefault<TKey, TValue>(this IDictionary<TKey, TValue>? dictionary, out KeyValuePair<TKey, TValue> value)
{
if (dictionary != null && dictionary.Count > 0)
{
value = dictionary.First();
return true;
}
value = default;
return false;
}
}
@@ -1,7 +1,5 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
@@ -56,37 +54,25 @@ internal static class OpenApiSchemaExtensions
public static SchemaFormat GetSchemaFormat(this IOpenApiSchema? schema) public static SchemaFormat GetSchemaFormat(this IOpenApiSchema? schema)
{ {
switch (schema?.Format) return (schema?.Format) switch
{ {
case "float": "float" => SchemaFormat.Float,
return SchemaFormat.Float; "double" => SchemaFormat.Double,
"int32" => SchemaFormat.Int32,
"int64" => SchemaFormat.Int64,
"date" => SchemaFormat.Date,
"date-time" => SchemaFormat.DateTime,
"password" => SchemaFormat.Password,
"byte" => SchemaFormat.Byte,
"binary" => SchemaFormat.Binary,
_ => SchemaFormat.Undefined,
};
}
case "double": internal static JsonNode? FindFirstExample(this IOpenApiSchema? schema)
return SchemaFormat.Double; {
#pragma warning disable CS0618 // Type or member is obsolete
case "int32": return schema?.Examples?.FirstOrDefault() ?? schema?.Example;
return SchemaFormat.Int32; #pragma warning restore CS0618 // Type or member is obsolete
case "int64":
return SchemaFormat.Int64;
case "date":
return SchemaFormat.Date;
case "date-time":
return SchemaFormat.DateTime;
case "password":
return SchemaFormat.Password;
case "byte":
return SchemaFormat.Byte;
case "binary":
return SchemaFormat.Binary;
default:
return SchemaFormat.Undefined;
}
} }
} }
@@ -1,10 +1,8 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.IO;
using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.OpenApi.Reader;
using Stef.Validation; using Stef.Validation;
using WireMock.Net.OpenApiParser.Models;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
using WireMock.Server; using WireMock.Server;
@@ -16,7 +14,8 @@ namespace WireMock.Net.OpenApiParser.Extensions;
public static class WireMockServerExtensions public static class WireMockServerExtensions
{ {
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file. /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 file.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param> /// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
@@ -28,7 +27,8 @@ public static class WireMockServerExtensions
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file. /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 file.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param> /// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
@@ -46,7 +46,8 @@ public static class WireMockServerExtensions
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream. /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 stream.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param> /// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
@@ -58,7 +59,8 @@ public static class WireMockServerExtensions
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream. /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 stream.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param> /// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
@@ -77,7 +79,8 @@ public static class WireMockServerExtensions
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 document. /// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1/V3.2 document.
/// In case of an error, no mappings are registered and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="server">The WireMockServer instance</param> /// <param name="server">The WireMockServer instance</param>
/// <param name="document">The OpenAPI document to use as mappings.</param> /// <param name="document">The OpenAPI document to use as mappings.</param>
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- See also https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task/issues/26 -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ILRepacker" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<ItemGroup>
<InputAssemblies Include="$(OutputPath)WireMock.Net.OpenApiParser.dll" />
<InputAssemblies Include="@(ReferencePathWithRefAssemblies)" Condition="'%(filename)' == 'Microsoft.OpenApi.YamlReader'" />
<InputAssemblies Include="@(ReferencePathWithRefAssemblies)" Condition="'%(filename)' == 'Microsoft.OpenApi'" />
<LibraryPath Include="%(ReferencePathWithRefAssemblies.RelativeDir)" />
</ItemGroup>
<ItemGroup>
<DoNotInternalizeAssemblies Include="WireMock.Net.OpenApiParser" />
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
RenameInternalized="true"
InternalizeExclude="@(DoNotInternalizeAssemblies)"
InputAssemblies="@(InputAssemblies)"
LibraryPath="@(LibraryPath)"
TargetKind="Dll"
KeyFile="../../src/WireMock.Net/WireMock.Net.snk"
OutputFile="$(OutputPath)$(AssemblyName).dll"
/>
</Target>
</Project>
@@ -1,9 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Collections.Generic; using Microsoft.OpenApi.Reader;
using System.IO;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Models;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser; namespace WireMock.Net.OpenApiParser;
@@ -15,6 +13,7 @@ public interface IWireMockOpenApiParser
{ {
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file.</param> /// <param name="path">The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file.</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
@@ -23,6 +22,7 @@ public interface IWireMockOpenApiParser
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file.</param> /// <param name="path">The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file.</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
@@ -40,6 +40,7 @@ public interface IWireMockOpenApiParser
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="stream">The source stream</param> /// <param name="stream">The source stream</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
@@ -48,6 +49,7 @@ public interface IWireMockOpenApiParser
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="stream">The source stream</param> /// <param name="stream">The source stream</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
@@ -57,6 +59,7 @@ public interface IWireMockOpenApiParser
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="text">The source text</param> /// <param name="text">The source text</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
@@ -65,6 +68,7 @@ public interface IWireMockOpenApiParser
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// In case of an error, an empty list is returned and <see cref="OpenApiDiagnostic"/> contains the error details.
/// </summary> /// </summary>
/// <param name="text">The source text</param> /// <param name="text">The source text</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
@@ -53,7 +53,7 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
Headers = MapRequestHeaders(requestHeaders), Headers = MapRequestHeaders(requestHeaders),
Body = GetRequestBodyModel(operation.RequestBody) Body = GetRequestBodyModel(operation.RequestBody)
}, },
Response = GetResponseModel(operation.Responses?.FirstOrDefault()) Response = operation.Responses?.TryGetFirstOrDefault(out var firstResponse) == true ? GetResponseModel(firstResponse) : new ResponseModel { StatusCode = 200 }
}; };
} }
@@ -70,8 +70,7 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
var requestExample = requestContent?.Example; var requestExample = requestContent?.Example;
var requestExamples = requestContent?.Examples; var requestExamples = requestContent?.Examples;
var requestSchemaExample = requestContent?.Schema?.Example; var requestSchemaExample = requestContent?.Schema.FindFirstExample();
var requestSchemaExamples = requestContent?.Schema?.Examples;
JsonNode? request; JsonNode? request;
if (requestExample != null) if (requestExample != null)
@@ -86,10 +85,6 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{ {
request = requestExamples.FirstOrDefault().Value.Value; request = requestExamples.FirstOrDefault().Value.Value;
} }
else if (requestSchemaExamples != null)
{
request = requestSchemaExamples.FirstOrDefault();
}
else else
{ {
var requestSchema = content?.FirstOrDefault().Value.Schema; var requestSchema = content?.FirstOrDefault().Value.Schema;
@@ -99,16 +94,15 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
return MapRequestBody(request) ?? new BodyModel(); return MapRequestBody(request) ?? new BodyModel();
} }
private ResponseModel GetResponseModel(KeyValuePair<string, IOpenApiResponse>? openApiResponse) private ResponseModel GetResponseModel(KeyValuePair<string, IOpenApiResponse> openApiResponse)
{ {
var content = openApiResponse?.Value.Content; var content = openApiResponse.Value.Content;
TryGetContent(content, out var responseContent, out var contentType); TryGetContent(content, out var responseContent, out var contentType);
var responseExample = responseContent?.Example; var responseExample = responseContent?.Example;
var responseExamples = responseContent?.Examples; var responseExamples = responseContent?.Examples;
var responseSchemaExample = responseContent?.Schema?.Example; var responseSchemaExample = responseContent?.Schema.FindFirstExample();
var responseSchemaExamples = responseContent?.Schema?.Examples;
JsonNode? response; JsonNode? response;
if (responseExample != null) if (responseExample != null)
@@ -119,24 +113,19 @@ internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{ {
response = responseSchemaExample; response = responseSchemaExample;
} }
else if (responseExamples != null) else if (responseExamples.TryGetFirstValue(out var firstResponseFromExamples))
{ {
response = responseExamples.FirstOrDefault().Value.Value; response = firstResponseFromExamples?.Value;
}
else if (responseSchemaExamples != null)
{
response = responseSchemaExamples.FirstOrDefault();
} }
else else
{ {
var responseSchema = content?.FirstOrDefault().Value?.Schema; response = content.TryGetFirstValue(out var firstContent) ? MapSchemaToObject(firstContent?.Schema) : null;
response = MapSchemaToObject(responseSchema);
} }
return new ResponseModel return new ResponseModel
{ {
StatusCode = int.TryParse(openApiResponse?.Key, out var httpStatusCode) ? httpStatusCode : 200, StatusCode = int.TryParse(openApiResponse.Key, out var httpStatusCode) ? httpStatusCode : 200,
Headers = MapHeaders(contentType, openApiResponse?.Value.Headers), Headers = MapHeaders(contentType, openApiResponse.Value.Headers),
BodyAsJson = response != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(response)) : null BodyAsJson = response != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(response)) : null
}; };
} }
@@ -1,28 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using RamlToOpenApiConverter;
namespace WireMock.Net.OpenApiParser.Models;
/// <summary>
/// Object containing all diagnostic information related to Open API parsing.
/// </summary>
public class OpenApiDiagnostic
{
/// <summary>
/// List of all errors.
/// </summary>
public List<OpenApiError> Errors { get; set; } = [];
/// <summary>
/// List of all warnings
/// </summary>
public List<OpenApiError> Warnings { get; set; } = [];
/// <summary>
/// Open API specification version of the document parsed.
/// </summary>
public OpenApiSpecificationVersion SpecificationVersion { get; set; }
}
@@ -1,46 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
namespace WireMock.Net.OpenApiParser.Models;
/// <summary>
/// Error related to the Open API Document.
/// </summary>
public class OpenApiError
{
/// <summary>
/// Initializes the <see cref="OpenApiError"/> class.
/// </summary>
public OpenApiError(string? pointer, string message)
{
Pointer = pointer;
Message = message;
}
/// <summary>
/// Initializes a copy of an <see cref="OpenApiError"/> object
/// </summary>
public OpenApiError(OpenApiError error)
{
Pointer = error.Pointer;
Message = error.Message;
}
/// <summary>
/// Message explaining the error.
/// </summary>
public string Message { get; set; }
/// <summary>
/// Pointer to the location of the error.
/// </summary>
public string? Pointer { get; set; }
/// <summary>
/// Gets the string representation of <see cref="OpenApiError"/>.
/// </summary>
public override string ToString()
{
return Message + (!string.IsNullOrEmpty(Pointer) ? " [" + Pointer + "]" : "");
}
}
@@ -1,25 +0,0 @@
// 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
};
}
}
@@ -39,7 +39,7 @@ internal class ExampleValueGenerator
public JsonNode GetExampleValue(IOpenApiSchema? schema) public JsonNode GetExampleValue(IOpenApiSchema? schema)
{ {
var schemaExample = schema?.Example; var schemaExample = schema?.FindFirstExample();
var schemaEnum = schema?.Enum?.FirstOrDefault(); var schemaEnum = schema?.Enum?.FirstOrDefault();
_exampleValues.Schema = schema; _exampleValues.Schema = schema;
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description> <Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description>
@@ -40,23 +40,11 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.21.0" /> <PackageReference Include="RamlToOpenApiConverter" Version="0.40.0" />
<PackageReference Include="YamlDotNet" Version="18.1.0" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.19.1" /> <PackageReference Include="RandomDataGenerator.Net" Version="1.0.19.1" />
<PackageReference Include="Stef.Validation" Version="0.3.0" /> <PackageReference Include="Stef.Validation" Version="0.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="3.7.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.43" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi" Version="3.7.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="3.7.0" PrivateAssets="All" />
<PackageReference Include="SharpYaml" Version="2.1.5" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup> </ItemGroup>
@@ -1,8 +1,6 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.OpenApi; using Microsoft.OpenApi;
@@ -11,14 +9,12 @@ using Microsoft.OpenApi.YamlReader;
using RamlToOpenApiConverter; using RamlToOpenApiConverter;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Mappers; using WireMock.Net.OpenApiParser.Mappers;
using WireMock.Net.OpenApiParser.Models;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
using OpenApiDiagnostic = WireMock.Net.OpenApiParser.Models.OpenApiDiagnostic;
namespace WireMock.Net.OpenApiParser; namespace WireMock.Net.OpenApiParser;
/// <summary> /// <summary>
/// Parse a OpenApi/Swagger/V2/V3/V3.1 to WireMock.Net MappingModels. /// Parse a OpenApi/Swagger/V2/V3/V3.1/V3.2 to WireMock.Net MappingModels.
/// </summary> /// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser public class WireMockOpenApiParser : IWireMockOpenApiParser
{ {
@@ -32,8 +28,15 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
_readerSettings = new OpenApiReaderSettings(); _readerSettings = new OpenApiReaderSettings();
_readerSettings.AddMicrosoftExtensionParsers(); _readerSettings.AddMicrosoftExtensionParsers();
_readerSettings.AddJsonReader(); _readerSettings.AddJsonReader();
_readerSettings.TryAddReader(OpenApiConstants.Yaml, new OpenApiYamlReader());
_readerSettings.TryAddReader(OpenApiConstants.Yml, new OpenApiYamlReader()); var openApiYamlReaderSettings = new OpenApiYamlReaderSettings
{
MaxAliasExpansionNodeCount = 1_000_000
};
var openApiYamlReader = new OpenApiYamlReader(openApiYamlReaderSettings);
_readerSettings.TryAddReader(OpenApiConstants.Yaml, openApiYamlReader);
_readerSettings.TryAddReader(OpenApiConstants.Yml, openApiYamlReader);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -55,7 +58,12 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
} }
else else
{ {
document = Read(File.OpenRead(path), out diagnostic); if (!TryRead(File.OpenRead(path), out var documentFromYaml, out diagnostic))
{
return [];
}
document = documentFromYaml;
} }
return FromDocument(document, settings); return FromDocument(document, settings);
@@ -74,14 +82,24 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
[PublicAPI] [PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic) public IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{ {
return FromDocument(Read(stream, out diagnostic)); if (TryRead(stream, out var openApiDocument, out diagnostic))
{
return FromDocument(openApiDocument);
}
return [];
} }
/// <inheritdoc /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
return FromDocument(Read(stream, out diagnostic), settings); if (TryRead(stream, out var openApiDocument, out diagnostic))
{
return FromDocument(openApiDocument, settings);
}
return [];
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -98,7 +116,7 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), settings, out diagnostic); return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), settings, out diagnostic);
} }
private OpenApiDocument Read(Stream stream, out OpenApiDiagnostic diagnostic) private bool TryRead(Stream stream, [NotNullWhen(true)] out OpenApiDocument? openApiDocument, out OpenApiDiagnostic diagnostic)
{ {
if (stream is not MemoryStream memoryStream) if (stream is not MemoryStream memoryStream)
{ {
@@ -107,8 +125,9 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
var result = OpenApiDocument.Load(memoryStream, settings: _readerSettings); var result = OpenApiDocument.Load(memoryStream, settings: _readerSettings);
diagnostic = OpenApiMapper.Map(result.Diagnostic) ?? new OpenApiDiagnostic(); diagnostic = result.Diagnostic ?? new OpenApiDiagnostic();
return result.Document ?? throw new InvalidOperationException("The document is null."); openApiDocument = result.Document;
return openApiDocument != null && !diagnostic.Errors.Any();
} }
private static MemoryStream ReadStreamIntoMemoryStream(Stream stream) private static MemoryStream ReadStreamIntoMemoryStream(Stream stream)
@@ -36,12 +36,38 @@ public class WireMockOpenApiParserTests
var openApiDocument = File.ReadAllText(Path.Combine("OpenApiParser", "payroc-openapi-spec.yaml")); var openApiDocument = File.ReadAllText(Path.Combine("OpenApiParser", "payroc-openapi-spec.yaml"));
// Act // Act
var mappings = _sut.FromText(openApiDocument, settings, out _); var mappings = _sut.FromText(openApiDocument, settings, out var diagnostic);
// Assert
mappings.Should().NotBeEmpty();
diagnostic.Should().NotBeNull();
diagnostic.Errors.Should().BeEmpty();
// Verify // Verify
await Verify(mappings); await Verify(mappings);
} }
[Fact]
public void FromText_UsingInvalidYaml_ShouldReturnEmptyMappingsWithDiagnostic()
{
// Arrange
var settings = new WireMockOpenApiParserSettings
{
ExampleValues = _exampleValuesMock.Object
};
var openApiDocument = File.ReadAllText(Path.Combine("OpenApiParser", "invalid.yaml"));
// Act
var mappings = _sut.FromText(openApiDocument, settings, out var diagnostic);
// Assert
mappings.Should().BeEmpty();
diagnostic.Should().NotBeNull();
diagnostic.Errors.Should().HaveCount(1);
diagnostic.Errors[0].Message.Should().Be("Responses must contain at least one response");
}
[Fact] [Fact]
public async Task FromText_UsingJson_WithPlainTextExample_ShouldReturnMappings() public async Task FromText_UsingJson_WithPlainTextExample_ShouldReturnMappings()
{ {
@@ -54,7 +80,12 @@ public class WireMockOpenApiParserTests
var openApiDocument = File.ReadAllText(Path.Combine("OpenApiParser", "oas-content-example.json")); var openApiDocument = File.ReadAllText(Path.Combine("OpenApiParser", "oas-content-example.json"));
// Act // Act
var mappings = _sut.FromText(openApiDocument, settings, out _); var mappings = _sut.FromText(openApiDocument, settings, out var diagnostic);
// Assert
mappings.Should().NotBeEmpty();
diagnostic.Should().NotBeNull();
diagnostic.Errors.Should().BeEmpty();
// Verify // Verify
await Verify(mappings); await Verify(mappings);
@@ -0,0 +1,12 @@
openapi: 3.2.0
info:
version: '1'
title: Invalid API
description: Invalid
paths:
/funding-recipients:
summary: Create and manage funding recipients.
post:
tags:
- Funding recipients