Revert changes to WireMock.Net.OpenApiParser (#1289)

* Revert changes to WireMock.Net.OpenApiParser

* revert
This commit is contained in:
Stef Heyenrath
2025-05-02 07:18:35 +02:00
committed by GitHub
parent e7310fbc7b
commit cf0dcf5855
36 changed files with 1909 additions and 644 deletions

View File

@@ -46,8 +46,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.Net452
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.RequestLogTest", "examples\WireMock.Net.Console.RequestLogTest\WireMock.Net.Console.RequestLogTest.csproj", "{A9D039B9-7509-4CF1-9EFD-87EB82998575}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Console.RequestLogTest", "examples\WireMock.Net.Console.RequestLogTest\WireMock.Net.Console.RequestLogTest.csproj", "{A9D039B9-7509-4CF1-9EFD-87EB82998575}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.OpenApiParser", "src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj", "{D3804228-91F4-4502-9595-39584E5AADAD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.OpenApiParser.ConsoleApp", "examples\WireMock.Net.OpenApiParser.ConsoleApp\WireMock.Net.OpenApiParser.ConsoleApp.csproj", "{5C09FB93-1535-4F92-AF26-21E8A061EE4A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.OpenApiParser.ConsoleApp", "examples\WireMock.Net.OpenApiParser.ConsoleApp\WireMock.Net.OpenApiParser.ConsoleApp.csproj", "{5C09FB93-1535-4F92-AF26-21E8A061EE4A}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.FluentAssertions", "src\WireMock.Net.FluentAssertions\WireMock.Net.FluentAssertions.csproj", "{B6269AAC-170A-4346-8B9A-579DED3D9A95}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.FluentAssertions", "src\WireMock.Net.FluentAssertions\WireMock.Net.FluentAssertions.csproj", "{B6269AAC-170A-4346-8B9A-579DED3D9A95}"
@@ -130,6 +128,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WireMock.Net.Middleware.Tes
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.AwesomeAssertions", "src\WireMock.Net.AwesomeAssertions\WireMock.Net.AwesomeAssertions.csproj", "{7753670F-7C7F-44BF-8BC7-08325588E60C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.AwesomeAssertions", "src\WireMock.Net.AwesomeAssertions\WireMock.Net.AwesomeAssertions.csproj", "{7753670F-7C7F-44BF-8BC7-08325588E60C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenApiParser", "src\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj", "{D3804228-91F4-4502-9595-39584E5AADAD}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -176,10 +176,6 @@ Global
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.Build.0 = Debug|Any CPU {A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|Any CPU.ActiveCfg = Release|Any CPU {A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|Any CPU.Build.0 = Release|Any CPU {A9D039B9-7509-4CF1-9EFD-87EB82998575}.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
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.Build.0 = Debug|Any CPU {5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -308,6 +304,10 @@ Global
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Release|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|Any CPU.Build.0 = 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
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -323,7 +323,6 @@ Global
{7F0B2446-0363-4720-AF46-F47F83B557DC} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {7F0B2446-0363-4720-AF46-F47F83B557DC} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{668F689E-57B4-422E-8846-C0FF643CA268} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {668F689E-57B4-422E-8846-C0FF643CA268} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{A9D039B9-7509-4CF1-9EFD-87EB82998575} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {A9D039B9-7509-4CF1-9EFD-87EB82998575} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{D3804228-91F4-4502-9595-39584E5AADAD} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{5C09FB93-1535-4F92-AF26-21E8A061EE4A} = {985E0ADB-D4B4-473A-AA40-567E279B7946} {5C09FB93-1535-4F92-AF26-21E8A061EE4A} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{B6269AAC-170A-4346-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {B6269AAC-170A-4346-8B9A-579DED3D9A95} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{40BF24B5-12E6-4610-9489-138798632E28} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {40BF24B5-12E6-4610-9489-138798632E28} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
@@ -358,6 +357,7 @@ Global
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE} = {0BB8B634-407A-4610-A91F-11586990767A} {6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE} = {0BB8B634-407A-4610-A91F-11586990767A}
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5} = {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} {7753670F-7C7F-44BF-8BC7-08325588E60C} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{D3804228-91F4-4502-9595-39584E5AADAD} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,22 @@
// Copyright © WireMock.Net
#if NET46 || NET47 || NETSTANDARD2_0
using System.Collections.Generic;
namespace WireMock.Net.OpenApiParser.Extensions;
internal static class DictionaryExtensions
{
public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue>? dictionary, TKey key, TValue value)
{
if (dictionary is null || dictionary.ContainsKey(key))
{
return false;
}
dictionary[key] = value;
return true;
}
}
#endif

View File

@@ -0,0 +1,85 @@
// Copyright © WireMock.Net
using System.Linq;
using System.Text.Json;
using Microsoft.OpenApi.Any;
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)
{
value = false;
if (schema.Extensions != null && schema.Extensions.TryGetValue(OpenApiConstants.NullableExtension, out var nullExtRawValue) && nullExtRawValue is OpenApiAny { Node: { } jsonNode })
{
value = jsonNode.GetValueKind() == JsonValueKind.True;
return true;
}
return false;
}
public static JsonSchemaType? GetSchemaType(this IOpenApiSchema? schema, out bool isNullable)
{
isNullable = false;
if (schema == null)
{
return null;
}
if (schema.Type == null)
{
if (schema.AllOf?.Any() == true || schema.AnyOf?.Any() == true)
{
return JsonSchemaType.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;
}
public static SchemaFormat GetSchemaFormat(this IOpenApiSchema? schema)
{
switch (schema?.Format)
{
case "float":
return SchemaFormat.Float;
case "double":
return SchemaFormat.Double;
case "int32":
return SchemaFormat.Int32;
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;
}
}
}

View File

@@ -0,0 +1,96 @@
// Copyright © WireMock.Net
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Reader;
using Stef.Validation;
using WireMock.Net.OpenApiParser.Settings;
using WireMock.Server;
namespace WireMock.Net.OpenApiParser.Extensions;
/// <summary>
/// Some extension methods for <see cref="IWireMockServer"/>.
/// </summary>
public static class WireMockServerExtensions
{
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic)
{
return WithMappingFromOpenApiFile(server, path, new WireMockOpenApiParserSettings(), out diagnostic);
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="path">Path containing OpenAPI file to parse and use the mappings.</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
Guard.NotNull(server);
Guard.NotNullOrEmpty(path);
var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic);
return server.WithMapping(mappings.ToArray());
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic)
{
return WithMappingFromOpenApiStream(server, stream, new WireMockOpenApiParserSettings(), out diagnostic);
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="stream">Stream containing OpenAPI description to parse and use the mappings.</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
Guard.NotNull(server);
Guard.NotNull(stream);
Guard.NotNull(settings);
var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic);
return server.WithMapping(mappings.ToArray());
}
/// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 document.
/// </summary>
/// <param name="server">The WireMockServer instance</param>
/// <param name="document">The OpenAPI document to use as mappings.</param>
/// <param name="settings">Additional settings [optional].</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings? settings = null)
{
Guard.NotNull(server);
Guard.NotNull(document);
var mappings = new WireMockOpenApiParser().FromDocument(document, settings);
return server.WithMapping(mappings.ToArray());
}
}

View File

@@ -0,0 +1,75 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.IO;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Reader;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser;
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// </summary>
public interface IWireMockOpenApiParser
{
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// </summary>
/// <param name="path">The path to read the OpenApi/Swagger/V2/V3/V31 or Raml file.</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// </summary>
/// <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="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>.
/// </summary>
/// <param name="document">The source OpenApiDocument</param>
/// <param name="settings">Additional settings [optional]</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null);
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </summary>
/// <param name="stream">The source stream</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// </summary>
/// <param name="text">The source text</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic);
/// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// </summary>
/// <param name="text">The source text</param>
/// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
}

View File

@@ -0,0 +1,347 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Newtonsoft.Json;
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;
internal class OpenApiPathsMapper
{
private const string HeaderContentType = "Content-Type";
private readonly WireMockOpenApiParserSettings _settings;
private readonly ExampleValueGenerator _exampleValueGenerator;
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{
_settings = Guard.NotNull(settings);
_exampleValueGenerator = new ExampleValueGenerator(settings);
}
public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers)
{
return paths?
.OrderBy(p => p.Key)
.Select(p => MapPath(p.Key, p.Value, servers))
.SelectMany(x => x)
.ToArray() ?? [];
}
private IReadOnlyList<MappingModel> MapPath(string path, IOpenApiPathItem pathItem, IList<OpenApiServer> 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<OpenApiServer> servers)
{
var queryParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Query) ?? [];
var pathParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Path) ?? [];
var headers = operation.Parameters?.Where(p => p.In == ParameterLocation.Header) ?? [];
var response = operation?.Responses?.FirstOrDefault() ?? new KeyValuePair<string, IOpenApiResponse>();
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 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 var 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);
requestBodyModel = MapRequestBody(requestBodyMapped);
}
if (!int.TryParse(response.Key, out var httpStatusCode))
{
httpStatusCode = 200;
}
return new MappingModel
{
Guid = Guid.NewGuid(),
Request = new RequestModel
{
Methods = [httpMethod],
Path = PathUtils.Combine(MapBasePath(servers), MapPathWithParameters(path, pathParameters)),
Params = MapQueryParameters(queryParameters),
Headers = MapRequestHeaders(headers),
Body = requestBodyModel
},
Response = new ResponseModel
{
StatusCode = httpStatusCode,
Headers = MapHeaders(responseContentType, response.Value?.Headers),
BodyAsJson = responseBody != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(responseBody)) : null
}
};
}
private BodyModel? MapRequestBody(JsonNode? requestBody)
{
if (requestBody == null)
{
return null;
}
return new BodyModel
{
Matcher = new MatcherModel
{
Name = "JsonMatcher",
Pattern = SystemTextJsonSerializer.Serialize(requestBody, new JsonSerializerOptions { WriteIndented = true }),
IgnoreCase = _settings.RequestBodyIgnoreCase
}
};
}
private static bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
{
openApiMediaType = null;
contentType = null;
if (contents == null || contents.Values.Count == 0)
{
return false;
}
if (contents.TryGetValue("application/json", out var content))
{
openApiMediaType = content;
contentType = "application/json";
}
else
{
var first = contents.FirstOrDefault();
openApiMediaType = first.Value;
contentType = first.Key;
}
return true;
}
private JsonNode? MapSchemaToObject(IOpenApiSchema? schema)
{
if (schema == null)
{
return null;
}
switch (schema.GetSchemaType(out _))
{
case JsonSchemaType.Array:
var array = new JsonArray();
for (var i = 0; i < _settings.NumberOfArrayItems; i++)
{
if (schema.Items?.Properties?.Count > 0)
{
var item = new JsonObject();
foreach (var property in schema.Items.Properties)
{
item[property.Key] = MapSchemaToObject(property.Value);
}
array.Add(item);
}
else
{
var arrayItem = MapSchemaToObject(schema.Items);
array.Add(arrayItem);
}
}
if (schema.AllOf?.Count > 0)
{
array.Add(MapSchemaAllOfToObject(schema));
}
return array;
case JsonSchemaType.Boolean:
case JsonSchemaType.Integer:
case JsonSchemaType.Number:
case JsonSchemaType.String:
return _exampleValueGenerator.GetExampleValue(schema);
case JsonSchemaType.Object:
var propertyAsJsonObject = new JsonObject();
foreach (var schemaProperty in schema.Properties ?? new Dictionary<string, IOpenApiSchema>())
{
propertyAsJsonObject[schemaProperty.Key] = MapPropertyAsJsonNode(schemaProperty.Value);
}
if (schema.AllOf?.Count > 0)
{
foreach (var group in schema.AllOf.SelectMany(p => p.Properties ?? new Dictionary<string, IOpenApiSchema>()).GroupBy(x => x.Key))
{
propertyAsJsonObject[group.Key] = MapPropertyAsJsonNode(group.First().Value);
}
}
return propertyAsJsonObject;
default:
return null;
}
}
private JsonObject MapSchemaAllOfToObject(IOpenApiSchema schema)
{
var arrayItem = new JsonObject();
foreach (var property in schema.AllOf ?? [])
{
foreach (var item in property.Properties ?? new Dictionary<string, IOpenApiSchema>())
{
arrayItem[item.Key] = MapPropertyAsJsonNode(item.Value);
}
}
return arrayItem;
}
private JsonNode? MapPropertyAsJsonNode(IOpenApiSchema openApiSchema)
{
var schemaType = openApiSchema.GetSchemaType(out _);
if (schemaType is JsonSchemaType.Object or JsonSchemaType.Array)
{
return MapSchemaToObject(openApiSchema);
}
return _exampleValueGenerator.GetExampleValue(openApiSchema);
}
private string MapPathWithParameters(string path, IEnumerable<IOpenApiParameter>? parameters)
{
if (parameters == null)
{
return path;
}
var newPath = path;
foreach (var parameter in parameters)
{
var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse);
newPath = newPath.Replace($"{{{parameter.Name}}}", exampleMatcherModel.Pattern as string);
}
return newPath;
}
private IDictionary<string, object>? MapHeaders(string? responseContentType, IDictionary<string, IOpenApiHeader>? headers)
{
var mappedHeaders = headers?
.ToDictionary(item => item.Key, _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!) ?? new Dictionary<string, object>();
if (!string.IsNullOrEmpty(responseContentType))
{
mappedHeaders.TryAdd(HeaderContentType, responseContentType);
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
}
private IList<ParamModel>? MapQueryParameters(IEnumerable<IOpenApiParameter> queryParameters)
{
var list = queryParameters
.Where(req => req.Required)
.Select(qp => new ParamModel
{
Name = qp.Name ?? string.Empty,
IgnoreCase = _settings.QueryParameterPatternIgnoreCase,
Matchers =
[
GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse)
]
})
.ToList();
return list.Any() ? list : null;
}
private IList<HeaderModel>? MapRequestHeaders(IEnumerable<IOpenApiParameter> headers)
{
var list = headers
.Where(req => req.Required)
.Select(qp => new HeaderModel
{
Name = qp.Name ?? string.Empty,
IgnoreCase = _settings.HeaderPatternIgnoreCase,
Matchers =
[
GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse)
]
})
.ToList();
return list.Any() ? list : null;
}
private MatcherModel GetExampleMatcherModel(IOpenApiSchema? schema, ExampleValueType type)
{
return type switch
{
ExampleValueType.Value => new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetExampleValueAsStringForSchemaType(schema),
IgnoreCase = _settings.IgnoreCaseExampleValues
},
_ => new MatcherModel
{
Name = "WildcardMatcher",
Pattern = "*"
}
};
}
private string GetExampleValueAsStringForSchemaType(IOpenApiSchema? schema)
{
var value = _exampleValueGenerator.GetExampleValue(schema);
if (value.GetValueKind() == JsonValueKind.String)
{
return value.GetValue<string>();
}
return value.ToString();
}
private static string MapBasePath(IList<OpenApiServer>? 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;
}
}

View File

@@ -0,0 +1,5 @@
// Copyright © WireMock.Net
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]

View File

@@ -0,0 +1,62 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi.Models.Interfaces;
namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// An interface defining the example values to use for the different types.
/// </summary>
public interface IWireMockOpenApiParserExampleValues
{
/// <summary>
/// An example value for a Boolean.
/// </summary>
bool Boolean { get; }
/// <summary>
/// An example value for an Integer.
/// </summary>
int Integer { get; }
/// <summary>
/// An example value for a Float.
/// </summary>
float Float { get; }
/// <summary>
/// An example value for a Decimal.
/// </summary>
decimal Decimal { get; }
/// <summary>
/// An example value for a Date.
/// </summary>
Func<DateTime> Date { get; }
/// <summary>
/// An example value for a DateTime.
/// </summary>
Func<DateTime> DateTime { get; }
/// <summary>
/// An example value for Bytes.
/// </summary>
byte[] Bytes { get; }
/// <summary>
/// An example value for a Object.
/// </summary>
object Object { get; }
/// <summary>
/// An example value for a String.
/// </summary>
string String { get; }
/// <summary>
/// OpenApi Schema to generate dynamic examples more accurate
/// </summary>
IOpenApiSchema? Schema { get; set; }
}

View File

@@ -0,0 +1,59 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi.Models.Interfaces;
using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers;
namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// A class defining the random example values to use for the different types.
/// </summary>
public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues
{
/// <inheritdoc />
public virtual bool Boolean => RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true;
/// <inheritdoc />
public virtual int Integer => RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42;
/// <inheritdoc />
public virtual float Float => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f;
/// <inheritdoc />
public virtual decimal Decimal => SafeConvertFloatToDecimal(RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f);
/// <inheritdoc />
public virtual Func<DateTime> Date => () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date;
/// <inheritdoc />
public virtual Func<DateTime> DateTime => () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow;
/// <inheritdoc />
public virtual byte[] Bytes => RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate();
/// <inheritdoc />
public virtual object Object => "example-object";
/// <inheritdoc />
public virtual string String => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string";
/// <inheritdoc />
public virtual IOpenApiSchema? Schema { get; set; }
/// <summary>
/// Safely converts a float to a decimal, ensuring the value stays within the bounds of a decimal.
/// </summary>
/// <param name="value">The float value to convert.</param>
/// <returns>A decimal value within the valid range of a decimal.</returns>
private static decimal SafeConvertFloatToDecimal(float value)
{
return value switch
{
< (float)decimal.MinValue => decimal.MinValue,
> (float)decimal.MaxValue => decimal.MaxValue,
_ => (decimal)value
};
}
}

View File

@@ -0,0 +1,43 @@
// Copyright © WireMock.Net
using System;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// A class defining the example values to use for the different types.
/// </summary>
public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues
{
/// <inheritdoc />
public virtual bool Boolean => true;
/// <inheritdoc />
public virtual int Integer => 42;
/// <inheritdoc />
public virtual float Float => 4.2f;
/// <inheritdoc />
public virtual decimal Decimal => 4.2m;
/// <inheritdoc />
public virtual Func<DateTime> Date { get; } = () => System.DateTime.UtcNow.Date;
/// <inheritdoc />
public virtual Func<DateTime> DateTime { get; } = () => System.DateTime.UtcNow;
/// <inheritdoc />
public virtual byte[] Bytes { get; } = [48, 49, 50];
/// <inheritdoc />
public virtual object Object => "example-object";
/// <inheritdoc />
public virtual string String => "example-string";
/// <inheritdoc />
public virtual IOpenApiSchema? Schema { get; set; } = new OpenApiSchema();
}

View File

@@ -0,0 +1,73 @@
// Copyright © WireMock.Net
using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// The WireMockOpenApiParser Settings
/// </summary>
public class WireMockOpenApiParserSettings
{
/// <summary>
/// The number of array items to generate (default is 3).
/// </summary>
public int NumberOfArrayItems { get; set; } = 3;
/// <summary>
/// The example value type to use when generating a Path
/// </summary>
public ExampleValueType PathPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary>
/// The example value type to use when generating a Header
/// </summary>
public ExampleValueType HeaderPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary>
/// The example value type to use when generating a Query Parameter
/// </summary>
public ExampleValueType QueryParameterPatternToUse { get; set; } = ExampleValueType.Value;
/// <summary>
/// The example values to use.
///
/// Default implementations are:
/// - <see cref="WireMockOpenApiParserExampleValues"/>
/// - <see cref="WireMockOpenApiParserDynamicExampleValues"/>
/// </summary>
public IWireMockOpenApiParserExampleValues? ExampleValues { get; set; }
/// <summary>
/// Is a Header match case-insensitive?
///
/// Default is <c>true</c>.
/// </summary>
public bool HeaderPatternIgnoreCase { get; set; } = true;
/// <summary>
/// Is a Query Parameter match case-insensitive?
///
/// Default is <c>true</c>.
/// </summary>
public bool QueryParameterPatternIgnoreCase { get; set; } = true;
/// <summary>
/// Is a Request Body match case-insensitive?
///
/// Default is <c>true</c>.
/// </summary>
public bool RequestBodyIgnoreCase { get; set; } = true;
/// <summary>
/// Is a ExampleValue match case-insensitive?
///
/// Default is <c>true</c>.
/// </summary>
public bool IgnoreCaseExampleValues { get; set; } = true;
/// <summary>
/// Are examples generated dynamically?
/// </summary>
public bool DynamicExamples { get; set; }
}

View File

@@ -0,0 +1,21 @@
// Copyright © WireMock.Net
namespace WireMock.Net.OpenApiParser.Types;
/// <summary>
/// The example value to use
/// </summary>
public enum ExampleValueType
{
/// <summary>
/// 1. Use a generated example value based on the SchemaType (default).
/// 2. If there is no example value defined in the schema,
/// then the <see cref="Settings.IWireMockOpenApiParserExampleValues"/> will be used (custom, fixed or dynamic).
/// </summary>
Value,
/// <summary>
/// Just use a Wildcard (*) character.
/// </summary>
Wildcard
}

View File

@@ -0,0 +1,26 @@
// Copyright © WireMock.Net
namespace WireMock.Net.OpenApiParser.Types;
internal enum SchemaFormat
{
Float,
Double,
Int32,
Int64,
Date,
DateTime,
Password,
Byte,
Binary,
Undefined
}

View File

@@ -0,0 +1,22 @@
// Copyright © WireMock.Net
using System;
using System.Globalization;
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);
}
public static string ToRfc3339Date(DateTime dateTime)
{
return dateTime.ToString(DateFormat, DateTimeFormatInfo.InvariantInfo);
}
}

View File

@@ -0,0 +1,105 @@
// 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<bool>();
return exampleBoolean ?? _exampleValues.Boolean;
case JsonSchemaType.Integer:
var exampleInteger = schemaExample?.GetValue<decimal>();
var enumInteger = schemaEnum?.GetValue<decimal>();
var valueIntegerEnumOrExample = enumInteger ?? exampleInteger;
return valueIntegerEnumOrExample ?? _exampleValues.Integer;
case JsonSchemaType.Number:
switch (schema.GetSchemaFormat())
{
case SchemaFormat.Float:
var exampleFloat = schemaExample?.GetValue<float>();
var enumFloat = schemaEnum?.GetValue<float>();
var valueFloatEnumOrExample = enumFloat ?? exampleFloat;
return valueFloatEnumOrExample ?? _exampleValues.Float;
default:
var exampleDecimal = schemaExample?.GetValue<decimal>();
var enumDecimal = schemaEnum?.GetValue<decimal>();
var valueDecimalEnumOrExample = enumDecimal ?? exampleDecimal;
return valueDecimalEnumOrExample ?? _exampleValues.Decimal;
}
default:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Date:
var exampleDate = schemaExample?.GetValue<string>();
var enumDate = schemaEnum?.GetValue<string>();
var valueDateEnumOrExample = enumDate ?? exampleDate;
return valueDateEnumOrExample ?? DateTimeUtils.ToRfc3339Date(_exampleValues.Date());
case SchemaFormat.DateTime:
var exampleDateTime = schemaExample?.GetValue<string>();
var enumDateTime = schemaEnum?.GetValue<string>();
var valueDateTimeEnumOrExample = enumDateTime ?? exampleDateTime;
return valueDateTimeEnumOrExample ?? DateTimeUtils.ToRfc3339DateTime(_exampleValues.DateTime());
case SchemaFormat.Byte:
var exampleByte = schemaExample?.GetValue<byte[]>();
var enumByte = schemaEnum?.GetValue<byte[]>();
var valueByteEnumOrExample = enumByte ?? exampleByte;
return Convert.ToBase64String(valueByteEnumOrExample ?? _exampleValues.Bytes);
default:
var exampleString = schemaExample?.GetValue<string>();
var enumString = schemaEnum?.GetValue<string>();
var valueStringEnumOrExample = enumString ?? exampleString;
return valueStringEnumOrExample ?? _exampleValues.String;
}
}
}
}

View File

@@ -0,0 +1,27 @@
// Copyright © WireMock.Net
namespace WireMock.Net.OpenApiParser.Utils;
internal static class PathUtils
{
internal static string Combine(params string[] paths)
{
if (paths.Length == 0)
{
return string.Empty;
}
var result = paths[0].Trim().TrimEnd('/');
for (int i = 1; i < paths.Length; i++)
{
var nextPath = paths[i].Trim().TrimStart('/').TrimEnd('/');
if (!string.IsNullOrEmpty(nextPath))
{
result += '/' + nextPath;
}
}
return result;
}
}

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description>
<TargetFrameworks>net47;netstandard2.0;netstandard2.1;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;openapi;OAS;raml;converter;parser;openapiparser</PackageTags>
<ProjectGuid>{E5B03EEF-822C-4295-952B-4479AD30082B}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="RamlToOpenApiConverter" Version="0.7.0" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.18" />
<PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,107 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.YamlReader;
using RamlToOpenApiConverter;
using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Mappers;
using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser;
/// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels.
/// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser
{
private static readonly OpenApiReaderSettings ReaderSettings = new();
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
{
diagnostic = new OpenApiDiagnostic();
document = new RamlConverter().ConvertToOpenApiDocument(path);
}
else
{
document = Read(File.OpenRead(path), out diagnostic);
}
return FromDocument(document, settings);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null)
{
return new OpenApiPathsMapper(settings ?? new WireMockOpenApiParserSettings()).ToMappingModels(document.Paths, document.Servers ?? []);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{
return FromDocument(Read(stream, out diagnostic));
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
return FromDocument(Read(stream, out diagnostic), settings);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic)
{
return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), out diagnostic);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
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;
}
}

View File

@@ -1,6 +1,6 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
#if NET46 || NET47 || NETSTANDARD2_0 #if NET46 || NETSTANDARD2_0
using System.Collections.Generic; using System.Collections.Generic;
namespace WireMock.Net.OpenApiParser.Extensions; namespace WireMock.Net.OpenApiParser.Extensions;

View File

@@ -1,53 +1,60 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Linq; using System.Linq;
using System.Text.Json;
using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Extensions; namespace WireMock.Net.OpenApiParser.Extensions;
internal static class OpenApiSchemaExtensions internal static class OpenApiSchemaExtensions
{ {
public static bool TryGetXNullable(this IOpenApiSchema schema, out bool value) /// <summary>
/// https://stackoverflow.com/questions/48111459/how-to-define-a-property-that-can-be-string-or-null-in-openapi-swagger
/// </summary>
public static bool TryGetXNullable(this OpenApiSchema schema, out bool value)
{ {
value = false; 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 true;
} }
return false; return false;
} }
public static JsonSchemaType? GetSchemaType(this IOpenApiSchema? schema, out bool isNullable) public static SchemaType GetSchemaType(this OpenApiSchema? schema)
{ {
isNullable = false;
if (schema == null) if (schema == null)
{ {
return null; return SchemaType.Unknown;
} }
if (schema.Type == null) 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); return schema.Type switch
{
// Removes the Null flag from the schema.Type, ensuring the returned value represents a non-nullable type. "object" => SchemaType.Object,
return schema.Type & ~JsonSchemaType.Null; "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) switch (schema?.Format)
{ {

View File

@@ -1,96 +1,96 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers;
using Stef.Validation; using Stef.Validation;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
using WireMock.Server; using WireMock.Server;
namespace WireMock.Net.OpenApiParser.Extensions; namespace WireMock.Net.OpenApiParser.Extensions;
/// <summary> /// <summary>
/// Some extension methods for <see cref="IWireMockServer"/>. /// Some extension methods for <see cref="IWireMockServer"/>.
/// </summary> /// </summary>
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 or V3 file.
/// </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>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic) public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, out OpenApiDiagnostic diagnostic)
{ {
return WithMappingFromOpenApiFile(server, path, new WireMockOpenApiParserSettings(), out diagnostic); return WithMappingFromOpenApiFile(server, path, new WireMockOpenApiParserSettings(), out diagnostic);
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 file. /// Register the mappings via an OpenAPI (swagger) V2 or V3 file.
/// </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>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public static IWireMockServer WithMappingFromOpenApiFile(this IWireMockServer server, string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
Guard.NotNull(server); Guard.NotNull(server);
Guard.NotNullOrEmpty(path); Guard.NotNullOrEmpty(path);
var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic); var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic);
return server.WithMapping(mappings.ToArray()); return server.WithMapping(mappings.ToArray());
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream. /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream.
/// </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>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic) public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, out OpenApiDiagnostic diagnostic)
{ {
return WithMappingFromOpenApiStream(server, stream, new WireMockOpenApiParserSettings(), out diagnostic); return WithMappingFromOpenApiStream(server, stream, new WireMockOpenApiParserSettings(), out diagnostic);
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 stream. /// Register the mappings via an OpenAPI (swagger) V2 or V3 stream.
/// </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>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public static IWireMockServer WithMappingFromOpenApiStream(this IWireMockServer server, Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
Guard.NotNull(server); Guard.NotNull(server);
Guard.NotNull(stream); Guard.NotNull(stream);
Guard.NotNull(settings); Guard.NotNull(settings);
var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic); var mappings = new WireMockOpenApiParser().FromStream(stream, settings, out diagnostic);
return server.WithMapping(mappings.ToArray()); return server.WithMapping(mappings.ToArray());
} }
/// <summary> /// <summary>
/// Register the mappings via an OpenAPI (swagger) V2/V3/V3.1 document. /// Register the mappings via an OpenAPI (swagger) V2 or V3 document.
/// </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>
/// <param name="settings">Additional settings [optional].</param> /// <param name="settings">Additional settings [optional].</param>
[PublicAPI] [PublicAPI]
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings? settings = null) public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings? settings = null)
{ {
Guard.NotNull(server); Guard.NotNull(server);
Guard.NotNull(document); Guard.NotNull(document);
var mappings = new WireMockOpenApiParser().FromDocument(document, settings); var mappings = new WireMockOpenApiParser().FromDocument(document, settings);
return server.WithMapping(mappings.ToArray()); return server.WithMapping(mappings.ToArray());
} }
} }

View File

@@ -1,75 +1,75 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser; namespace WireMock.Net.OpenApiParser;
/// <summary> /// <summary>
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels. /// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock MappingModels.
/// </summary> /// </summary>
public interface IWireMockOpenApiParser public interface IWireMockOpenApiParser
{ {
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// </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 or Raml file.</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic);
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a file-path.
/// </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 or Raml file.</param>
/// <param name="settings">Additional settings</param> /// <param name="settings">Additional settings</param>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from an <seealso cref="OpenApiDocument"/>.
/// </summary> /// </summary>
/// <param name="document">The source OpenApiDocument</param> /// <param name="document">The source OpenApiDocument</param>
/// <param name="settings">Additional settings [optional]</param> /// <param name="settings">Additional settings [optional]</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null); IReadOnlyList<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null);
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </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>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic);
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="Stream"/>.
/// </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>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// </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>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic);
/// <summary> /// <summary>
/// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>. /// Generate <see cref="IReadOnlyList{MappingModel}"/> from a <seealso cref="string"/>.
/// </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>
/// <param name="diagnostic">OpenApiDiagnostic output</param> /// <param name="diagnostic">OpenApiDiagnostic output</param>
/// <returns>MappingModel</returns> /// <returns>MappingModel</returns>
IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic); IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic);
} }

View File

@@ -3,19 +3,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using Microsoft.OpenApi;
using System.Text.Json.Nodes; using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Writers;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Net.OpenApiParser.Extensions; using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Types;
using WireMock.Net.OpenApiParser.Utils; using WireMock.Net.OpenApiParser.Utils;
using SystemTextJsonSerializer = System.Text.Json.JsonSerializer;
namespace WireMock.Net.OpenApiParser.Mappers; namespace WireMock.Net.OpenApiParser.Mappers;
@@ -38,40 +39,56 @@ internal class OpenApiPathsMapper
.OrderBy(p => p.Key) .OrderBy(p => p.Key)
.Select(p => MapPath(p.Key, p.Value, servers)) .Select(p => MapPath(p.Key, p.Value, servers))
.SelectMany(x => x) .SelectMany(x => x)
.ToArray() ?? []; .ToArray() ??
Array.Empty<MappingModel>();
} }
private IReadOnlyList<MappingModel> MapPath(string path, IOpenApiPathItem pathItem, IList<OpenApiServer> servers) private IReadOnlyList<MappingModel> MapPaths(OpenApiPaths? paths, IList<OpenApiServer> 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<MappingModel>();
}
private IReadOnlyList<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> 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<OpenApiServer> servers) private MappingModel MapOperationToMappingModel(string path, string httpMethod, OpenApiOperation operation, IList<OpenApiServer> servers)
{ {
var queryParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Query) ?? []; var queryParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Query);
var pathParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Path) ?? []; var pathParameters = operation.Parameters.Where(p => p.In == ParameterLocation.Path);
var headers = operation.Parameters?.Where(p => p.In == ParameterLocation.Header) ?? []; var headers = operation.Parameters.Where(p => p.In == ParameterLocation.Header);
var response = operation?.Responses?.FirstOrDefault() ?? new KeyValuePair<string, IOpenApiResponse>(); 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 responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example; var responseExample = responseContent?.Example;
var responseSchemaExample = responseContent?.Schema?.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(); var requestBodyModel = new BodyModel();
if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required) if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required)
{ {
var request = operation.RequestBody.Content; 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 requestBodySchema = operation.RequestBody.Content.First().Value?.Schema;
var requestBodyExample = requestContent!.Example; var requestBodyExample = requestContent!.Example;
var requestBodySchemaExample = requestContent.Schema?.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); requestBodyModel = MapRequestBody(requestBodyMapped);
} }
@@ -95,12 +112,12 @@ internal class OpenApiPathsMapper
{ {
StatusCode = httpStatusCode, StatusCode = httpStatusCode,
Headers = MapHeaders(responseContentType, response.Value?.Headers), 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) if (requestBody == null)
{ {
@@ -112,7 +129,7 @@ internal class OpenApiPathsMapper
Matcher = new MatcherModel Matcher = new MatcherModel
{ {
Name = "JsonMatcher", Name = "JsonMatcher",
Pattern = SystemTextJsonSerializer.Serialize(requestBody, new JsonSerializerOptions { WriteIndented = true }), Pattern = JsonConvert.SerializeObject(requestBody, Formatting.Indented),
IgnoreCase = _settings.RequestBodyIgnoreCase IgnoreCase = _settings.RequestBodyIgnoreCase
} }
}; };
@@ -143,103 +160,117 @@ internal class OpenApiPathsMapper
return true; return true;
} }
private JsonNode? MapSchemaToObject(IOpenApiSchema? schema) private object? MapSchemaToObject(OpenApiSchema? schema, string? name = null)
{ {
if (schema == null) if (schema == null)
{ {
return null; return null;
} }
switch (schema.GetSchemaType(out _)) switch (schema.GetSchemaType())
{ {
case JsonSchemaType.Array: case SchemaType.Array:
var array = new JsonArray(); var jArray = new JArray();
for (var i = 0; i < _settings.NumberOfArrayItems; i++) 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) 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 else
{ {
var arrayItem = MapSchemaToObject(schema.Items); var arrayItem = MapSchemaToObject(schema.Items, name: null); // Set name to null to force JObject instead of JProperty
array.Add(arrayItem); 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 SchemaType.Boolean:
case JsonSchemaType.Integer: case SchemaType.Integer:
case JsonSchemaType.Number: case SchemaType.Number:
case JsonSchemaType.String: case SchemaType.String:
return _exampleValueGenerator.GetExampleValue(schema); return _exampleValueGenerator.GetExampleValue(schema);
case JsonSchemaType.Object: case SchemaType.Object:
var propertyAsJsonObject = new JsonObject(); var propertyAsJObject = new JObject();
foreach (var schemaProperty in schema.Properties ?? new Dictionary<string, IOpenApiSchema>()) 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<string, IOpenApiSchema>()).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: default:
return null; return null;
} }
} }
private JsonObject MapSchemaAllOfToObject(IOpenApiSchema schema) private JObject MapSchemaAllOfToObject(OpenApiSchema schema)
{ {
var arrayItem = new JsonObject(); var arrayItem = new JObject();
foreach (var property in schema.AllOf ?? []) foreach (var property in schema.AllOf)
{ {
foreach (var item in property.Properties ?? new Dictionary<string, IOpenApiSchema>()) foreach (var item in property.Properties)
{ {
arrayItem[item.Key] = MapPropertyAsJsonNode(item.Value); arrayItem.Add(MapPropertyAsJObject(item.Value, item.Key));
} }
} }
return arrayItem; return arrayItem;
} }
private JsonNode? MapPropertyAsJsonNode(IOpenApiSchema openApiSchema) private object MapPropertyAsJObject(OpenApiSchema openApiSchema, string key)
{ {
var schemaType = openApiSchema.GetSchemaType(out _); if (openApiSchema.GetSchemaType() == SchemaType.Object || openApiSchema.GetSchemaType() == SchemaType.Array)
if (schemaType is JsonSchemaType.Object or JsonSchemaType.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<IOpenApiParameter>? parameters) private string MapPathWithParameters(string path, IEnumerable<OpenApiParameter>? parameters)
{ {
if (parameters == null) if (parameters == null)
{ {
return path; return path;
} }
var newPath = path; string newPath = path;
foreach (var parameter in parameters) foreach (var parameter in parameters)
{ {
var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse); var exampleMatcherModel = GetExampleMatcherModel(parameter.Schema, _settings.PathPatternToUse);
@@ -249,56 +280,93 @@ internal class OpenApiPathsMapper
return newPath; return newPath;
} }
private IDictionary<string, object>? MapHeaders(string? responseContentType, IDictionary<string, IOpenApiHeader>? headers) private string MapBasePath(IList<OpenApiServer>? servers)
{ {
var mappedHeaders = headers? if (servers == null || servers.Count == 0)
.ToDictionary(item => item.Key, _ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!) ?? new Dictionary<string, object>(); {
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<string, object>? MapHeaders(string? responseContentType, IDictionary<string, OpenApiHeader>? headers)
{
var mappedHeaders = headers?.ToDictionary(
item => item.Key,
_ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!
) ?? new Dictionary<string, object>();
if (!string.IsNullOrEmpty(responseContentType)) if (!string.IsNullOrEmpty(responseContentType))
{ {
mappedHeaders.TryAdd(HeaderContentType, responseContentType); mappedHeaders.TryAdd(HeaderContentType, responseContentType!);
} }
return mappedHeaders.Keys.Any() ? mappedHeaders : null; return mappedHeaders.Keys.Any() ? mappedHeaders : null;
} }
private IList<ParamModel>? MapQueryParameters(IEnumerable<IOpenApiParameter> queryParameters) private IList<ParamModel>? MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
{ {
var list = queryParameters var list = queryParameters
.Where(req => req.Required) .Where(req => req.Required)
.Select(qp => new ParamModel .Select(qp => new ParamModel
{ {
Name = qp.Name ?? string.Empty, Name = qp.Name,
IgnoreCase = _settings.QueryParameterPatternIgnoreCase, IgnoreCase = _settings.QueryParameterPatternIgnoreCase,
Matchers = Matchers = new[]
[ {
GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse) GetExampleMatcherModel(qp.Schema, _settings.QueryParameterPatternToUse)
] }
}) })
.ToList(); .ToList();
return list.Any() ? list : null; return list.Any() ? list : null;
} }
private IList<HeaderModel>? MapRequestHeaders(IEnumerable<IOpenApiParameter> headers) private IList<HeaderModel>? MapRequestHeaders(IEnumerable<OpenApiParameter> headers)
{ {
var list = headers var list = headers
.Where(req => req.Required) .Where(req => req.Required)
.Select(qp => new HeaderModel .Select(qp => new HeaderModel
{ {
Name = qp.Name ?? string.Empty, Name = qp.Name,
IgnoreCase = _settings.HeaderPatternIgnoreCase, IgnoreCase = _settings.HeaderPatternIgnoreCase,
Matchers = Matchers = new[]
[ {
GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse) GetExampleMatcherModel(qp.Schema, _settings.HeaderPatternToUse)
] }
}) })
.ToList(); .ToList();
return list.Any() ? list : null; return list.Any() ? list : null;
} }
private MatcherModel GetExampleMatcherModel(IOpenApiSchema? schema, ExampleValueType type) private MatcherModel GetExampleMatcherModel(OpenApiSchema? schema, ExampleValueType type)
{ {
return type switch 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); var value = _exampleValueGenerator.GetExampleValue(schema);
if (value.GetValueKind() == JsonValueKind.String) return value switch
{ {
return value.GetValue<string>(); string valueAsString => valueAsString,
}
return value.ToString(); _ => value.ToString(),
} };
private static string MapBasePath(IList<OpenApiServer>? 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;
} }
} }

View File

@@ -1,62 +1,62 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Models;
namespace WireMock.Net.OpenApiParser.Settings; namespace WireMock.Net.OpenApiParser.Settings;
/// <summary> /// <summary>
/// A interface defining the example values to use for the different types. /// A interface defining the example values to use for the different types.
/// </summary> /// </summary>
public interface IWireMockOpenApiParserExampleValues public interface IWireMockOpenApiParserExampleValues
{ {
/// <summary> /// <summary>
/// An example value for a Boolean. /// An example value for a Boolean.
/// </summary> /// </summary>
bool Boolean { get; } bool Boolean { get; }
/// <summary> /// <summary>
/// An example value for an Integer. /// An example value for an Integer.
/// </summary> /// </summary>
int Integer { get; } int Integer { get; }
/// <summary> /// <summary>
/// An example value for a Float. /// An example value for a Float.
/// </summary> /// </summary>
float Float { get; } float Float { get; }
/// <summary> /// <summary>
/// An example value for a Decimal. /// An example value for a Double.
/// </summary> /// </summary>
decimal Decimal { get; } double Double { get; }
/// <summary> /// <summary>
/// An example value for a Date. /// An example value for a Date.
/// </summary> /// </summary>
Func<DateTime> Date { get; } Func<DateTime> Date { get; }
/// <summary> /// <summary>
/// An example value for a DateTime. /// An example value for a DateTime.
/// </summary> /// </summary>
Func<DateTime> DateTime { get; } Func<DateTime> DateTime { get; }
/// <summary> /// <summary>
/// An example value for Bytes. /// An example value for Bytes.
/// </summary> /// </summary>
byte[] Bytes { get; } byte[] Bytes { get; }
/// <summary> /// <summary>
/// An example value for a Object. /// An example value for a Object.
/// </summary> /// </summary>
object Object { get; } object Object { get; }
/// <summary> /// <summary>
/// An example value for a String. /// An example value for a String.
/// </summary> /// </summary>
string String { get; } string String { get; }
/// <summary> /// <summary>
/// OpenApi Schema to generate dynamic examples more accurate /// OpenApi Schema to generate dynamic examples more accurate
/// </summary> /// </summary>
IOpenApiSchema? Schema { get; set; } OpenApiSchema? Schema { get; set; }
} }

View File

@@ -1,59 +1,44 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi.Models;
using RandomDataGenerator.FieldOptions; using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers; using RandomDataGenerator.Randomizers;
namespace WireMock.Net.OpenApiParser.Settings; namespace WireMock.Net.OpenApiParser.Settings;
/// <summary> /// <summary>
/// A class defining the random example values to use for the different types. /// A class defining the random example values to use for the different types.
/// </summary> /// </summary>
public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues public class WireMockOpenApiParserDynamicExampleValues : IWireMockOpenApiParserExampleValues
{ {
/// <inheritdoc /> /// <inheritdoc />
public virtual bool Boolean => RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true; public virtual bool Boolean => RandomizerFactory.GetRandomizer(new FieldOptionsBoolean()).Generate() ?? true;
/// <inheritdoc /> /// <inheritdoc />
public virtual int Integer => RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42; public virtual int Integer => RandomizerFactory.GetRandomizer(new FieldOptionsInteger()).Generate() ?? 42;
/// <inheritdoc /> /// <inheritdoc />
public virtual float Float => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f; public virtual float Float => RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f;
/// <inheritdoc /> /// <inheritdoc />
public virtual decimal Decimal => SafeConvertFloatToDecimal(RandomizerFactory.GetRandomizer(new FieldOptionsFloat()).Generate() ?? 4.2f); public virtual double Double => RandomizerFactory.GetRandomizer(new FieldOptionsDouble()).Generate() ?? 4.2d;
/// <inheritdoc /> /// <inheritdoc />
public virtual Func<DateTime> Date => () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date; public virtual Func<DateTime> Date => () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow.Date;
/// <inheritdoc /> /// <inheritdoc />
public virtual Func<DateTime> DateTime => () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow; public virtual Func<DateTime> DateTime => () => RandomizerFactory.GetRandomizer(new FieldOptionsDateTime()).Generate() ?? System.DateTime.UtcNow;
/// <inheritdoc /> /// <inheritdoc />
public virtual byte[] Bytes => RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate(); public virtual byte[] Bytes => RandomizerFactory.GetRandomizer(new FieldOptionsBytes()).Generate();
/// <inheritdoc /> /// <inheritdoc />
public virtual object Object => "example-object"; public virtual object Object => "example-object";
/// <inheritdoc /> /// <inheritdoc />
public virtual string String => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string"; public virtual string String => RandomizerFactory.GetRandomizer(new FieldOptionsTextRegex { Pattern = @"^[0-9]{2}[A-Z]{5}[0-9]{2}" }).Generate() ?? "example-string";
/// <inheritdoc /> /// <inheritdoc />
public virtual IOpenApiSchema? Schema { get; set; } public virtual OpenApiSchema? Schema { get; set; }
/// <summary>
/// Safely converts a float to a decimal, ensuring the value stays within the bounds of a decimal.
/// </summary>
/// <param name="value">The float value to convert.</param>
/// <returns>A decimal value within the valid range of a decimal.</returns>
private static decimal SafeConvertFloatToDecimal(float value)
{
return value switch
{
< (float)decimal.MinValue => decimal.MinValue,
> (float)decimal.MaxValue => decimal.MaxValue,
_ => (decimal)value
};
}
} }

View File

@@ -1,43 +1,42 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
namespace WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser.Settings;
/// <summary>
/// <summary> /// A class defining the example values to use for the different types.
/// A class defining the example values to use for the different types. /// </summary>
/// </summary> public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues
public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleValues {
{ /// <inheritdoc />
/// <inheritdoc /> public virtual bool Boolean => true;
public virtual bool Boolean => true;
/// <inheritdoc />
/// <inheritdoc /> public virtual int Integer => 42;
public virtual int Integer => 42;
/// <inheritdoc />
/// <inheritdoc /> public virtual float Float => 4.2f;
public virtual float Float => 4.2f;
/// <inheritdoc />
/// <inheritdoc /> public virtual double Double => 4.2d;
public virtual decimal Decimal => 4.2m;
/// <inheritdoc />
/// <inheritdoc /> public virtual Func<DateTime> Date { get; } = () => System.DateTime.UtcNow.Date;
public virtual Func<DateTime> Date { get; } = () => System.DateTime.UtcNow.Date;
/// <inheritdoc />
/// <inheritdoc /> public virtual Func<DateTime> DateTime { get; } = () => System.DateTime.UtcNow;
public virtual Func<DateTime> DateTime { get; } = () => System.DateTime.UtcNow;
/// <inheritdoc />
/// <inheritdoc /> public virtual byte[] Bytes { get; } = { 48, 49, 50 };
public virtual byte[] Bytes { get; } = [48, 49, 50];
/// <inheritdoc />
/// <inheritdoc /> public virtual object Object => "example-object";
public virtual object Object => "example-object";
/// <inheritdoc />
/// <inheritdoc /> public virtual string String => "example-string";
public virtual string String => "example-string";
/// <inheritdoc />
/// <inheritdoc /> public virtual OpenApiSchema? Schema { get; set; } = new();
public virtual IOpenApiSchema? Schema { get; set; } = new OpenApiSchema();
} }

View File

@@ -0,0 +1,22 @@
// Copyright © WireMock.Net
namespace WireMock.Net.OpenApiParser.Types;
internal enum SchemaType
{
Object,
Array,
String,
Integer,
Number,
Boolean,
File,
Unknown
}

View File

@@ -7,16 +7,13 @@ namespace WireMock.Net.OpenApiParser.Utils;
internal static class DateTimeUtils 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) 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) public static string ToRfc3339Date(DateTime dateTime)
{ {
return dateTime.ToString(DateFormat, DateTimeFormatInfo.InvariantInfo); return dateTime.ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo);
} }
} }

View File

@@ -1,10 +1,8 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System;
using System.Linq; using System.Linq;
using System.Text.Json.Nodes; using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Stef.Validation; using Stef.Validation;
using WireMock.Net.OpenApiParser.Extensions; using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
@@ -38,66 +36,82 @@ internal class ExampleValueGenerator
} }
} }
public JsonNode GetExampleValue(IOpenApiSchema? schema) public object GetExampleValue(OpenApiSchema? schema)
{ {
var schemaExample = schema?.Example; var schemaExample = schema?.Example;
var schemaEnum = schema?.Enum?.FirstOrDefault(); var schemaEnum = schema?.Enum?.FirstOrDefault();
_exampleValues.Schema = schema; _exampleValues.Schema = schema;
switch (schema?.GetSchemaType(out _)) switch (schema?.GetSchemaType())
{ {
case JsonSchemaType.Boolean: case SchemaType.Boolean:
var exampleBoolean = schemaExample?.GetValue<bool>(); var exampleBoolean = schemaExample as OpenApiBoolean;
return exampleBoolean ?? _exampleValues.Boolean; return exampleBoolean?.Value ?? _exampleValues.Boolean;
case JsonSchemaType.Integer: case SchemaType.Integer:
var exampleInteger = schemaExample?.GetValue<decimal>(); switch (schema?.GetSchemaFormat())
var enumInteger = schemaEnum?.GetValue<decimal>(); {
var valueIntegerEnumOrExample = enumInteger ?? exampleInteger; case SchemaFormat.Int64:
return valueIntegerEnumOrExample ?? _exampleValues.Integer; var exampleLong = schemaExample as OpenApiLong;
var enumLong = schemaEnum as OpenApiLong;
var valueLongEnumOrExample = enumLong?.Value ?? exampleLong?.Value;
return valueLongEnumOrExample ?? _exampleValues.Integer;
case JsonSchemaType.Number: default:
switch (schema.GetSchemaFormat()) 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: case SchemaFormat.Float:
var exampleFloat = schemaExample?.GetValue<float>(); var exampleFloat = schemaExample as OpenApiFloat;
var enumFloat = schemaEnum?.GetValue<float>(); var enumFloat = schemaEnum as OpenApiFloat;
var valueFloatEnumOrExample = enumFloat ?? exampleFloat; var valueFloatEnumOrExample = enumFloat?.Value ?? exampleFloat?.Value;
return valueFloatEnumOrExample ?? _exampleValues.Float; return valueFloatEnumOrExample ?? _exampleValues.Float;
default: default:
var exampleDecimal = schemaExample?.GetValue<decimal>(); var exampleDouble = schemaExample as OpenApiDouble;
var enumDecimal = schemaEnum?.GetValue<decimal>(); var enumDouble = schemaEnum as OpenApiDouble;
var valueDecimalEnumOrExample = enumDecimal ?? exampleDecimal; var valueDoubleEnumOrExample = enumDouble?.Value ?? exampleDouble?.Value;
return valueDecimalEnumOrExample ?? _exampleValues.Decimal; return valueDoubleEnumOrExample ?? _exampleValues.Double;
} }
default: default:
switch (schema?.GetSchemaFormat()) switch (schema?.GetSchemaFormat())
{ {
case SchemaFormat.Date: case SchemaFormat.Date:
var exampleDate = schemaExample?.GetValue<string>(); var exampleDate = schemaExample as OpenApiDate;
var enumDate = schemaEnum?.GetValue<string>(); var enumDate = schemaEnum as OpenApiDate;
var valueDateEnumOrExample = enumDate ?? exampleDate; var valueDateEnumOrExample = enumDate?.Value ?? exampleDate?.Value;
return valueDateEnumOrExample ?? DateTimeUtils.ToRfc3339Date(_exampleValues.Date()); return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _exampleValues.Date());
case SchemaFormat.DateTime: case SchemaFormat.DateTime:
var exampleDateTime = schemaExample?.GetValue<string>(); var exampleDateTime = schemaExample as OpenApiDateTime;
var enumDateTime = schemaEnum?.GetValue<string>(); var enumDateTime = schemaEnum as OpenApiDateTime;
var valueDateTimeEnumOrExample = enumDateTime ?? exampleDateTime; var valueDateTimeEnumOrExample = enumDateTime?.Value ?? exampleDateTime?.Value;
return valueDateTimeEnumOrExample ?? DateTimeUtils.ToRfc3339DateTime(_exampleValues.DateTime()); return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _exampleValues.DateTime());
case SchemaFormat.Byte: case SchemaFormat.Byte:
var exampleByte = schemaExample?.GetValue<byte[]>(); var exampleByte = schemaExample as OpenApiByte;
var enumByte = schemaEnum?.GetValue<byte[]>(); var enumByte = schemaEnum as OpenApiByte;
var valueByteEnumOrExample = enumByte ?? exampleByte; var valueByteEnumOrExample = enumByte?.Value ?? exampleByte?.Value;
return Convert.ToBase64String(valueByteEnumOrExample ?? _exampleValues.Bytes); 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: default:
var exampleString = schemaExample?.GetValue<string>(); var exampleString = schemaExample as OpenApiString;
var enumString = schemaEnum?.GetValue<string>(); var enumString = schemaEnum as OpenApiString;
var valueStringEnumOrExample = enumString ?? exampleString; var valueStringEnumOrExample = enumString?.Value ?? exampleString?.Value;
return valueStringEnumOrExample ?? _exampleValues.String; return valueStringEnumOrExample ?? _exampleValues.String;
} }
} }

View File

@@ -1,36 +1,37 @@
<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>
<TargetFrameworks>net47;netstandard2.0;netstandard2.1;net8.0</TargetFrameworks> <TargetFrameworks>net46;netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;openapi;OAS;raml;converter;parser;openapiparser</PackageTags> <PackageTags>wiremock;openapi;OAS;raml;converter;parser;openapiparser</PackageTags>
<ProjectGuid>{D3804228-91F4-4502-9595-39584E5AADAD}</ProjectGuid> <ProjectGuid>{D3804228-91F4-4502-9595-39584E5AADAD}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl> <PublishRepositoryUrl>true</PublishRepositoryUrl>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign> <PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <PropertyGroup Condition="'$(Configuration)' == 'Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Nullable" Version="1.3.1"> <PackageReference Include="Microsoft.OpenApi.Readers" Version="1.2.3" />
<PrivateAssets>all</PrivateAssets> <PackageReference Include="Nullable" Version="1.3.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="RamlToOpenApiConverter" Version="0.7.0" /> </PackageReference>
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.18" /> <PackageReference Include="RamlToOpenApiConverter" Version="0.6.1" />
<PackageReference Include="Stef.Validation" Version="0.1.1" /> <PackageReference Include="RandomDataGenerator.Net" Version="1.0.18" />
</ItemGroup> <PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" /> <ItemGroup>
</ItemGroup> <ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,107 +1,84 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers;
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.Settings;
using WireMock.Net.OpenApiParser.Settings;
namespace WireMock.Net.OpenApiParser;
namespace WireMock.Net.OpenApiParser;
/// <summary>
/// <summary> /// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels.
/// Parse a OpenApi/Swagger/V2/V3 or Raml to WireMock.Net MappingModels. /// </summary>
/// </summary> public class WireMockOpenApiParser : IWireMockOpenApiParser
public class WireMockOpenApiParser : IWireMockOpenApiParser {
{ private readonly OpenApiStreamReader _reader = new();
private static readonly OpenApiReaderSettings ReaderSettings = new();
/// <inheritdoc />
/// <inheritdoc /> [PublicAPI]
[PublicAPI] public IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic) {
{ return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic);
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic); }
}
/// <inheritdoc />
/// <inheritdoc /> [PublicAPI]
[PublicAPI] public IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) {
{ OpenApiDocument document;
OpenApiDocument document; if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase)) {
{ diagnostic = new OpenApiDiagnostic();
diagnostic = new OpenApiDiagnostic(); document = new RamlConverter().ConvertToOpenApiDocument(path);
document = new RamlConverter().ConvertToOpenApiDocument(path); }
} else
else {
{ var reader = new OpenApiStreamReader();
document = Read(File.OpenRead(path), out diagnostic); document = reader.Read(File.OpenRead(path), out diagnostic);
} }
return FromDocument(document, settings); return FromDocument(document, settings);
} }
/// <inheritdoc /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IReadOnlyList<MappingModel> FromDocument(OpenApiDocument document, WireMockOpenApiParserSettings? settings = null) public IReadOnlyList<MappingModel> 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);
} }
/// <inheritdoc /> /// <inheritdoc />
[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)); return FromDocument(_reader.Read(stream, out diagnostic));
} }
/// <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); return FromDocument(_reader.Read(stream, out diagnostic), settings);
} }
/// <inheritdoc /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic) public IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic)
{ {
return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), out diagnostic); return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), out diagnostic);
} }
/// <inheritdoc /> /// <inheritdoc />
[PublicAPI] [PublicAPI]
public IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic) public IReadOnlyList<MappingModel> FromText(string text, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{ {
return FromStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), settings, out diagnostic); 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;
}
} }

View File

@@ -25,7 +25,7 @@ internal static class TypeLoader
return foundType; return foundType;
} }
throw new DllNotFoundException($"No dll found which implements Interface '{key}'."); throw new DllNotFoundException($"No dll found which implements interface '{key}'.");
}); });
return (TInterface)Activator.CreateInstance(pluginType, args)!; return (TInterface)Activator.CreateInstance(pluginType, args)!;

View File

@@ -46,7 +46,7 @@
<DefineConstants>$(DefineConstants);USE_ASPNETCORE;NET46</DefineConstants> <DefineConstants>$(DefineConstants);USE_ASPNETCORE;NET46</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'"> <PropertyGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452'">
<DefineConstants>$(DefineConstants);OPENAPIPARSER</DefineConstants> <DefineConstants>$(DefineConstants);OPENAPIPARSER</DefineConstants>
</PropertyGroup> </PropertyGroup>
@@ -205,7 +205,7 @@
<ProjectReference Include="..\WireMock.Org.Abstractions\WireMock.Org.Abstractions.csproj" /> <ProjectReference Include="..\WireMock.Org.Abstractions\WireMock.Org.Abstractions.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452' and '$(TargetFramework)' != 'net46' and '$(TargetFramework)' != 'net461'"> <ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452'">
<ProjectReference Include="..\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" /> <ProjectReference Include="..\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -334,7 +334,7 @@
"processingTerminalId": "example-string", "processingTerminalId": "example-string",
"order": { "order": {
"orderId": "example-string", "orderId": "example-string",
"dateTime": "2024-06-19T12:34:56.000\u002B00:00", "dateTime": "2024-06-19T12:34:56.000+00:00",
"description": "example-string", "description": "example-string",
"amount": 42, "amount": 42,
"currency": "AED", "currency": "AED",
@@ -1787,7 +1787,7 @@
"processingTerminalId": "example-string", "processingTerminalId": "example-string",
"order": { "order": {
"orderId": "example-string", "orderId": "example-string",
"dateTime": "2024-06-19T12:34:56.000\u002B00:00", "dateTime": "2024-06-19T12:34:56.000+00:00",
"description": "example-string", "description": "example-string",
"amount": 42, "amount": 42,
"currency": "AED" "currency": "AED"
@@ -2714,7 +2714,7 @@
"processingTerminalId": "example-string", "processingTerminalId": "example-string",
"order": { "order": {
"orderId": "example-string", "orderId": "example-string",
"dateTime": "2024-06-19T12:34:56.000\u002B00:00", "dateTime": "2024-06-19T12:34:56.000+00:00",
"description": "example-string", "description": "example-string",
"amount": 42, "amount": 42,
"currency": "AED", "currency": "AED",
@@ -2863,7 +2863,7 @@
"processingTerminalId": "example-string", "processingTerminalId": "example-string",
"order": { "order": {
"orderId": "example-string", "orderId": "example-string",
"dateTime": "2024-06-19T12:34:56.000\u002B00:00", "dateTime": "2024-06-19T12:34:56.000+00:00",
"description": "example-string", "description": "example-string",
"amount": 42, "amount": 42,
"currency": "AED" "currency": "AED"
@@ -6893,7 +6893,7 @@
"operator": "example-string", "operator": "example-string",
"order": { "order": {
"orderId": "example-string", "orderId": "example-string",
"dateTime": "2024-06-19T12:34:56.000\u002B00:00", "dateTime": "2024-06-19T12:34:56.000+00:00",
"description": "example-string", "description": "example-string",
"amount": 42, "amount": 42,
"currency": "AED", "currency": "AED",
@@ -7093,7 +7093,7 @@
"offlineProcessing": { "offlineProcessing": {
"operation": "offlineDecline", "operation": "offlineDecline",
"approvalCode": "example-string", "approvalCode": "example-string",
"dateTime": "2024-06-19T12:34:56.000\u002B00:00" "dateTime": "2024-06-19T12:34:56.000+00:00"
}, },
"autoCapture": true, "autoCapture": true,
"processAsSale": true "processAsSale": true
@@ -11349,7 +11349,7 @@
{ {
"op": "replace", "op": "replace",
"path": "/a/b/c", "path": "/a/b/c",
"value": 420 "value": "420"
}, },
{ {
"op": "move", "op": "move",
@@ -11827,7 +11827,7 @@
{ {
"op": "replace", "op": "replace",
"path": "/a/b/c", "path": "/a/b/c",
"value": 420 "value": "420"
}, },
{ {
"op": "move", "op": "move",
@@ -12541,7 +12541,7 @@
{ {
"op": "replace", "op": "replace",
"path": "/a/b/c", "path": "/a/b/c",
"value": 420 "value": "420"
}, },
{ {
"op": "move", "op": "move",
@@ -12986,7 +12986,7 @@
"operator": "example-string", "operator": "example-string",
"order": { "order": {
"orderId": "example-string", "orderId": "example-string",
"dateTime": "2024-06-19T12:34:56.000\u002B00:00", "dateTime": "2024-06-19T12:34:56.000+00:00",
"description": "example-string", "description": "example-string",
"amount": 42, "amount": 42,
"currency": "AED", "currency": "AED",

View File

@@ -23,7 +23,7 @@ public class WireMockOpenApiParserTests
_exampleValuesMock.SetupGet(e => e.Boolean).Returns(true); _exampleValuesMock.SetupGet(e => e.Boolean).Returns(true);
_exampleValuesMock.SetupGet(e => e.Integer).Returns(42); _exampleValuesMock.SetupGet(e => e.Integer).Returns(42);
_exampleValuesMock.SetupGet(e => e.Float).Returns(1.1f); _exampleValuesMock.SetupGet(e => e.Float).Returns(1.1f);
_exampleValuesMock.SetupGet(e => e.Decimal).Returns(2.2m); _exampleValuesMock.SetupGet(e => e.Double).Returns(2.2);
_exampleValuesMock.SetupGet(e => e.String).Returns("example-string"); _exampleValuesMock.SetupGet(e => e.String).Returns("example-string");
_exampleValuesMock.SetupGet(e => e.Object).Returns("example-object"); _exampleValuesMock.SetupGet(e => e.Object).Returns("example-object");
_exampleValuesMock.SetupGet(e => e.Bytes).Returns("Stef"u8.ToArray()); _exampleValuesMock.SetupGet(e => e.Bytes).Returns("Stef"u8.ToArray());