Include WireMockOpenApiParser project (#916)

* Fix some nullability warnings for WireMockOpenApiParser

* .

* .

* .

* opt

* FromText

* ab

* .

* private const string AdminOpenApi = "/__admin/openapi";

* fix test

* rnd

* .

* urldetails

* 0

* ,

* .

* tests

* .

* CompressionUtilsTests

* ut

* .
This commit is contained in:
Stef Heyenrath
2023-04-08 21:25:17 +02:00
committed by GitHub
parent 3e24e3452b
commit a8775c3b77
35 changed files with 2284 additions and 295 deletions

View File

@@ -31,13 +31,13 @@ public static class WireMockServerExtensions
/// </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>
/// <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, nameof(server));
Guard.NotNullOrEmpty(path, nameof(path));
Guard.NotNull(server);
Guard.NotNullOrEmpty(path);
var mappings = new WireMockOpenApiParser().FromFile(path, settings, out diagnostic);
@@ -80,9 +80,9 @@ public static class WireMockServerExtensions
/// </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>
/// <param name="settings">Additional settings [optional].</param>
[PublicAPI]
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings settings)
public static IWireMockServer WithMappingFromOpenApiDocument(this IWireMockServer server, OpenApiDocument document, WireMockOpenApiParserSettings? settings = null)
{
Guard.NotNull(server);
Guard.NotNull(document);

View File

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

@@ -31,19 +31,29 @@ internal class OpenApiPathsMapper
_exampleValueGenerator = new ExampleValueGenerator(settings);
}
public IEnumerable<MappingModel> ToMappingModels(OpenApiPaths paths, IList<OpenApiServer> servers)
public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers)
{
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x);
return paths?
.OrderBy(p => p.Key)
.Select(p => MapPath(p.Key, p.Value, servers))
.SelectMany(x => x)
.ToArray() ??
Array.Empty<MappingModel>();
}
private IEnumerable<MappingModel> MapPaths(OpenApiPaths paths, IList<OpenApiServer> servers)
private IReadOnlyList<MappingModel> MapPaths(OpenApiPaths? paths, IList<OpenApiServer> servers)
{
return paths.Select(p => MapPath(p.Key, p.Value, servers)).SelectMany(x => x);
return paths?
.OrderBy(p => p.Key)
.Select(p => MapPath(p.Key, p.Value, servers))
.SelectMany(x => x)
.ToArray() ??
Array.Empty<MappingModel>();
}
private IEnumerable<MappingModel> MapPath(string path, OpenApiPathItem pathItem, IList<OpenApiServer> servers)
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));
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)
@@ -123,7 +133,7 @@ internal class OpenApiPathsMapper
};
}
private bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
private static bool TryGetContent(IDictionary<string, OpenApiMediaType>? contents, [NotNullWhen(true)] out OpenApiMediaType? openApiMediaType, [NotNullWhen(true)] out string? contentType)
{
openApiMediaType = null;
contentType = null;
@@ -305,19 +315,19 @@ internal class OpenApiPathsMapper
return JObject.Parse(outputString.ToString());
}
private IDictionary<string, object?>? MapHeaders(string responseContentType, IDictionary<string, OpenApiHeader> headers)
private IDictionary<string, object>? MapHeaders(string? responseContentType, IDictionary<string, OpenApiHeader>? headers)
{
var mappedHeaders = headers.ToDictionary(
var mappedHeaders = headers?.ToDictionary(
item => item.Key,
_ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern
_ => GetExampleMatcherModel(null, _settings.HeaderPatternToUse).Pattern!
);
if (!string.IsNullOrEmpty(responseContentType))
{
mappedHeaders.TryAdd(HeaderContentType, responseContentType);
mappedHeaders.TryAdd(HeaderContentType, responseContentType!);
}
return mappedHeaders.Keys.Any() ? mappedHeaders : null;
return mappedHeaders?.Keys.Any() == true ? mappedHeaders : null;
}
private IList<ParamModel>? MapQueryParameters(IEnumerable<OpenApiParameter> queryParameters)
@@ -360,9 +370,18 @@ internal class OpenApiPathsMapper
{
return type switch
{
ExampleValueType.Value => new MatcherModel { Name = "ExactMatcher", Pattern = GetExampleValueAsStringForSchemaType(schema), IgnoreCase = _settings.IgnoreCaseExampleValues },
ExampleValueType.Value => new MatcherModel
{
Name = "ExactMatcher",
Pattern = GetExampleValueAsStringForSchemaType(schema),
IgnoreCase = _settings.IgnoreCaseExampleValues
},
_ => new MatcherModel { Name = "WildcardMatcher", Pattern = "*" }
_ => new MatcherModel
{
Name = "WildcardMatcher",
Pattern = "*"
}
};
}

View File

@@ -36,5 +36,5 @@ public class WireMockOpenApiParserExampleValues : IWireMockOpenApiParserExampleV
public virtual string String { get; set; } = "example-string";
/// <inheritdoc />
public virtual OpenApiSchema? Schema { get; set; } = new OpenApiSchema();
public virtual OpenApiSchema? Schema { get; set; } = new();
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Stef.Validation;
@@ -11,116 +10,108 @@ namespace WireMock.Net.OpenApiParser.Utils;
internal class ExampleValueGenerator
{
private readonly WireMockOpenApiParserSettings _settings;
private readonly IWireMockOpenApiParserExampleValues _exampleValues;
public ExampleValueGenerator(WireMockOpenApiParserSettings settings)
{
_settings = Guard.NotNull(settings);
Guard.NotNull(settings);
// Check if user provided an own implementation
if (settings.ExampleValues is null)
{
if (_settings.DynamicExamples)
if (settings.DynamicExamples)
{
_settings.ExampleValues = new WireMockOpenApiParserDynamicExampleValues();
_exampleValues = new WireMockOpenApiParserDynamicExampleValues();
}
else
{
_settings.ExampleValues = new WireMockOpenApiParserExampleValues();
_exampleValues = new WireMockOpenApiParserExampleValues();
}
}
else
{
_exampleValues = settings.ExampleValues;
}
}
public object GetExampleValue(OpenApiSchema? schema)
{
var schemaExample = schema?.Example;
var schemaEnum = GetRandomEnumValue(schema?.Enum);
var schemaEnum = schema?.Enum?.FirstOrDefault();
_settings.ExampleValues.Schema = schema;
_exampleValues.Schema = schema;
switch (schema?.GetSchemaType())
{
case SchemaType.Boolean:
var exampleBoolean = schemaExample as OpenApiBoolean;
return exampleBoolean is null ? _settings.ExampleValues.Boolean : exampleBoolean.Value;
return exampleBoolean?.Value ?? _exampleValues.Boolean;
case SchemaType.Integer:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Int64:
var exampleLong = (OpenApiLong)schemaExample;
var enumLong = (OpenApiLong)schemaEnum;
var valueLongEnumOrExample = enumLong is null ? exampleLong?.Value : enumLong?.Value;
return valueLongEnumOrExample ?? _settings.ExampleValues.Integer;
var exampleLong = schemaExample as OpenApiLong;
var enumLong = schemaEnum as OpenApiLong;
var valueLongEnumOrExample = enumLong?.Value ?? exampleLong?.Value;
return valueLongEnumOrExample ?? _exampleValues.Integer;
default:
var exampleInteger = (OpenApiInteger)schemaExample;
var enumInteger = (OpenApiInteger)schemaEnum;
var valueIntegerEnumOrExample = enumInteger is null ? exampleInteger?.Value : enumInteger?.Value;
return valueIntegerEnumOrExample ?? _settings.ExampleValues.Integer;
var exampleInteger = schemaExample as OpenApiInteger;
var enumInteger = schemaEnum as OpenApiInteger;
var valueIntegerEnumOrExample = enumInteger?.Value ?? exampleInteger?.Value;
return valueIntegerEnumOrExample ?? _exampleValues.Integer;
}
case SchemaType.Number:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Float:
var exampleFloat = (OpenApiFloat)schemaExample;
var enumFloat = (OpenApiFloat)schemaEnum;
var valueFloatEnumOrExample = enumFloat is null ? exampleFloat?.Value : enumFloat?.Value;
return valueFloatEnumOrExample ?? _settings.ExampleValues.Float;
var exampleFloat = schemaExample as OpenApiFloat;
var enumFloat = schemaEnum as OpenApiFloat;
var valueFloatEnumOrExample = enumFloat?.Value ?? exampleFloat?.Value;
return valueFloatEnumOrExample ?? _exampleValues.Float;
default:
var exampleDouble = (OpenApiDouble)schemaExample;
var enumDouble = (OpenApiDouble)schemaEnum;
var valueDoubleEnumOrExample = enumDouble is null ? exampleDouble?.Value : enumDouble?.Value;
return valueDoubleEnumOrExample ?? _settings.ExampleValues.Double;
var exampleDouble = schemaExample as OpenApiDouble;
var enumDouble = schemaEnum as OpenApiDouble;
var valueDoubleEnumOrExample = enumDouble?.Value ?? exampleDouble?.Value;
return valueDoubleEnumOrExample ?? _exampleValues.Double;
}
default:
switch (schema?.GetSchemaFormat())
{
case SchemaFormat.Date:
var exampleDate = (OpenApiDate)schemaExample;
var enumDate = (OpenApiDate)schemaEnum;
var valueDateEnumOrExample = enumDate is null ? exampleDate?.Value : enumDate?.Value;
return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _settings.ExampleValues.Date());
var exampleDate = schemaExample as OpenApiDate;
var enumDate = schemaEnum as OpenApiDate;
var valueDateEnumOrExample = enumDate?.Value ?? exampleDate?.Value;
return DateTimeUtils.ToRfc3339Date(valueDateEnumOrExample ?? _exampleValues.Date());
case SchemaFormat.DateTime:
var exampleDateTime = (OpenApiDateTime)schemaExample;
var enumDateTime = (OpenApiDateTime)schemaEnum;
var valueDateTimeEnumOrExample = enumDateTime is null ? exampleDateTime?.Value : enumDateTime?.Value;
return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _settings.ExampleValues.DateTime());
var exampleDateTime = schemaExample as OpenApiDateTime;
var enumDateTime = schemaEnum as OpenApiDateTime;
var valueDateTimeEnumOrExample = enumDateTime?.Value ?? exampleDateTime?.Value;
return DateTimeUtils.ToRfc3339DateTime(valueDateTimeEnumOrExample?.DateTime ?? _exampleValues.DateTime());
case SchemaFormat.Byte:
var exampleByte = (OpenApiByte)schemaExample;
var enumByte = (OpenApiByte)schemaEnum;
var valueByteEnumOrExample = enumByte is null ? exampleByte?.Value : enumByte?.Value;
return valueByteEnumOrExample ?? _settings.ExampleValues.Bytes;
var exampleByte = schemaExample as OpenApiByte;
var enumByte = schemaEnum as OpenApiByte;
var valueByteEnumOrExample = enumByte?.Value ?? exampleByte?.Value;
return valueByteEnumOrExample ?? _exampleValues.Bytes;
case SchemaFormat.Binary:
var exampleBinary = (OpenApiBinary)schemaExample;
var enumBinary = (OpenApiBinary)schemaEnum;
var valueBinaryEnumOrExample = enumBinary is null ? exampleBinary?.Value : enumBinary?.Value;
return valueBinaryEnumOrExample ?? _settings.ExampleValues.Object;
var exampleBinary = schemaExample as OpenApiBinary;
var enumBinary = schemaEnum as OpenApiBinary;
var valueBinaryEnumOrExample = enumBinary?.Value ?? exampleBinary?.Value;
return valueBinaryEnumOrExample ?? _exampleValues.Object;
default:
var exampleString = (OpenApiString)schemaExample;
var enumString = (OpenApiString)schemaEnum;
var valueStringEnumOrExample = enumString is null ? exampleString?.Value : enumString?.Value;
return valueStringEnumOrExample ?? _settings.ExampleValues.String;
var exampleString = schemaExample as OpenApiString;
var enumString = schemaEnum as OpenApiString;
var valueStringEnumOrExample = enumString?.Value ?? exampleString?.Value;
return valueStringEnumOrExample ?? _exampleValues.String;
}
}
}
private static IOpenApiAny? GetRandomEnumValue(IList<IOpenApiAny>? schemaEnum)
{
if (schemaEnum?.Count > 0)
{
int maxValue = schemaEnum.Count - 1;
int randomEnum = new Random().Next(0, maxValue);
return schemaEnum[randomEnum];
}
return null;
}
}

View File

@@ -4,7 +4,7 @@
<Description>An OpenApi (swagger) parser to generate MappingModel or mapping.json file.</Description>
<TargetFrameworks>net46;netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>wiremock;openapi;OAS;converter;parser;openapiparser</PackageTags>
<PackageTags>wiremock;openapi;OAS;raml;converter;parser;openapiparser</PackageTags>
<ProjectGuid>{D3804228-91F4-4502-9595-39584E5AADAD}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using JetBrains.Annotations;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
@@ -16,18 +17,18 @@ namespace WireMock.Net.OpenApiParser;
/// </summary>
public class WireMockOpenApiParser : IWireMockOpenApiParser
{
private readonly OpenApiStreamReader _reader = new OpenApiStreamReader();
private readonly OpenApiStreamReader _reader = new();
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, out OpenApiDiagnostic)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromFile(string path, out OpenApiDiagnostic diagnostic)
{
return FromFile(path, new WireMockOpenApiParserSettings(), out diagnostic);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromFile(string, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromFile(string path, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
OpenApiDocument document;
if (Path.GetExtension(path).EndsWith("raml", StringComparison.OrdinalIgnoreCase))
@@ -44,24 +45,38 @@ public class WireMockOpenApiParser : IWireMockOpenApiParser
return FromDocument(document, settings);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, out OpenApiDiagnostic)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null)
{
return new OpenApiPathsMapper(settings ?? new WireMockOpenApiParserSettings()).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
}
/// <inheritdoc />
[PublicAPI]
public IReadOnlyList<MappingModel> FromStream(Stream stream, out OpenApiDiagnostic diagnostic)
{
return FromDocument(_reader.Read(stream, out diagnostic));
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromStream(Stream, WireMockOpenApiParserSettings, out OpenApiDiagnostic)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
public IReadOnlyList<MappingModel> FromStream(Stream stream, WireMockOpenApiParserSettings settings, out OpenApiDiagnostic diagnostic)
{
return FromDocument(_reader.Read(stream, out diagnostic), settings);
}
/// <inheritdoc cref="IWireMockOpenApiParser.FromDocument(OpenApiDocument, WireMockOpenApiParserSettings)" />
/// <inheritdoc />
[PublicAPI]
public IEnumerable<MappingModel> FromDocument(OpenApiDocument openApiDocument, WireMockOpenApiParserSettings? settings = null)
public IReadOnlyList<MappingModel> FromText(string text, out OpenApiDiagnostic diagnostic)
{
return new OpenApiPathsMapper(settings).ToMappingModels(openApiDocument.Paths, openApiDocument.Servers);
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);
}
}

View File

@@ -280,4 +280,20 @@ public interface IWireMockAdminApi
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Head("files/{filename}")]
Task FileExistsAsync([Path] string filename, CancellationToken cancellationToken = default);
/// <summary>
/// Convert an OpenApi / RAML document to mappings.
/// </summary>
/// <param name="text">The OpenApi or RAML document as text.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("openapi/convert")]
Task<IReadOnlyList<MappingModel>> OpenApiConvertAsync([Body] string text, CancellationToken cancellationToken = default);
/// <summary>
/// Convert an OpenApi / RAML document to mappings and save these.
/// </summary>
/// <param name="text">The OpenApi or RAML document as text.</param>
/// <param name="cancellationToken">The optional cancellationToken.</param>
[Post("openapi/save")]
Task<StatusModel> OpenApiSaveAsync([Body] string text, CancellationToken cancellationToken = default);
}

View File

@@ -121,7 +121,7 @@ public interface IMapping
/// <summary>
/// Use Fire and Forget for the defined webhook(s). [Optional]
/// </summary>
bool? UseWebhooksFireAndForget { get; set; }
bool? UseWebhooksFireAndForget { get; }
/// <summary>
/// Data Object which can be used when WithTransformer is used.
@@ -130,7 +130,7 @@ public interface IMapping
/// lookup data "1"
/// </example>
/// </summary>
object? Data { get; set; }
object? Data { get; }
/// <summary>
/// ProvideResponseAsync

View File

@@ -67,13 +67,13 @@ public class Mapping : IMapping
public IWebhook[]? Webhooks { get; }
/// <inheritdoc />
public bool? UseWebhooksFireAndForget { get; set; }
public bool? UseWebhooksFireAndForget { get; }
/// <inheritdoc />
public ITimeSettings? TimeSettings { get; }
/// <inheritdoc />
public object? Data { get; set; }
public object? Data { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Mapping"/> class.

View File

@@ -1,51 +1,47 @@
using System;
using System;
using Stef.Validation;
namespace WireMock.Models
namespace WireMock.Models;
/// <summary>
/// UrlDetails
/// </summary>
public class UrlDetails
{
/// <summary>
/// UrlDetails
/// Gets the url (relative).
/// </summary>
public class UrlDetails
public Uri Url { get; }
/// <summary>
/// Gets the AbsoluteUrl.
/// </summary>
public Uri AbsoluteUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(string url) : this(new Uri(url))
{
/// <summary>
/// Gets the url (relative).
/// </summary>
public Uri Url { get; }
}
/// <summary>
/// Gets the AbsoluteUrl.
/// </summary>
public Uri AbsoluteUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(Uri url) : this(url, url)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(string url) : this(new Uri(url))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public UrlDetails(Uri url) : this(url, url)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="absoluteUrl">The absolute URL.</param>
/// <param name="url">The URL (relative).</param>
public UrlDetails(Uri absoluteUrl, Uri url)
{
Guard.NotNull(absoluteUrl, nameof(absoluteUrl));
Guard.NotNull(url, nameof(url));
AbsoluteUrl = absoluteUrl;
Url = url;
}
/// <summary>
/// Initializes a new instance of the <see cref="UrlDetails"/> class.
/// </summary>
/// <param name="absoluteUrl">The absolute URL.</param>
/// <param name="url">The URL (relative).</param>
public UrlDetails(Uri absoluteUrl, Uri url)
{
AbsoluteUrl = Guard.NotNull(absoluteUrl);
Url = Guard.NotNull(url);
}
}

View File

@@ -27,7 +27,7 @@ public interface IProxyResponseBuilder : IStatusCodeResponseBuilder
/// WithProxy using <see cref="X509Certificate2"/>.
/// </summary>
/// <param name="proxyUrl">The proxy url.</param>
/// <param name="certificate"">The X509Certificate2.</param>
/// <param name="certificate">The X509Certificate2.</param>
/// <returns>A <see cref="IResponseBuilder"/>.</returns>
IResponseBuilder WithProxy(string proxyUrl, X509Certificate2 certificate);
}

View File

@@ -54,10 +54,12 @@ internal static class SwaggerMapper
{
operation.Parameters.Add(openApiParameter);
}
foreach (var openApiParameter in MapRequestHeaders(mapping.Request.Headers))
{
operation.Parameters.Add(openApiParameter);
}
foreach (var openApiParameter in MapRequestCookies(mapping.Request.Cookies))
{
operation.Parameters.Add(openApiParameter);
@@ -94,7 +96,7 @@ internal static class SwaggerMapper
return openApiDocument.ToJson(SchemaType.OpenApi3, Formatting.Indented);
}
private static IEnumerable<OpenApiParameter> MapRequestQueryParameters(IList<ParamModel>? queryParameters)
private static IReadOnlyList<OpenApiParameter> MapRequestQueryParameters(IList<ParamModel>? queryParameters)
{
if (queryParameters == null)
{
@@ -146,7 +148,7 @@ internal static class SwaggerMapper
.ToList();
}
private static IEnumerable<OpenApiParameter> MapRequestCookies(IList<CookieModel>? cookies)
private static IReadOnlyList<OpenApiParameter> MapRequestCookies(IList<CookieModel>? cookies)
{
if (cookies == null)
{

View File

@@ -37,6 +37,8 @@ public partial class WireMockServer
private const string AdminRequests = "/__admin/requests";
private const string AdminSettings = "/__admin/settings";
private const string AdminScenarios = "/__admin/scenarios";
private const string AdminOpenApi = "/__admin/openapi";
private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
@@ -113,6 +115,10 @@ public partial class WireMockServer
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileGet));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingHead()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileHead));
Given(Request.Create().WithPath(_adminFilesFilenamePathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(FileDelete));
// __admin/openapi
Given(Request.Create().WithPath($"{AdminOpenApi}/convert").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiConvertToMappings));
Given(Request.Create().WithPath($"{AdminOpenApi}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(OpenApiSaveToMappings));
}
#endregion
@@ -737,7 +743,7 @@ public partial class WireMockServer
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
}
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false)
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false, object? statusCode = null)
{
return new ResponseMessage
{
@@ -746,7 +752,7 @@ public partial class WireMockServer
DetectedBodyType = BodyType.String,
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault)
},
StatusCode = (int)HttpStatusCode.OK,
StatusCode = statusCode ?? (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
};
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings;
@@ -14,7 +15,7 @@ namespace WireMock.Server;
public partial class WireMockServer
{
private void ConvertMappingsAndRegisterAsRespondProvider(MappingModel[] mappingModels, string? path = null)
private void ConvertMappingsAndRegisterAsRespondProvider(IReadOnlyList<MappingModel> mappingModels, string? path = null)
{
var duplicateGuids = mappingModels
.Where(m => m.Guid != null)
@@ -46,7 +47,7 @@ public partial class WireMockServer
}
var respondProvider = Given(requestBuilder, mappingModel.SaveToFile == true);
if (guid != null)
{
respondProvider = respondProvider.WithGuid(guid.Value);
@@ -107,7 +108,10 @@ public partial class WireMockServer
respondProvider = respondProvider.WithWebhook(webhooks);
}
respondProvider.WithWebhookFireAndForget(mappingModel.UseWebhooksFireAndForget ?? false);
if (mappingModel.UseWebhooksFireAndForget == true)
{
respondProvider.WithWebhookFireAndForget(mappingModel.UseWebhooksFireAndForget.Value);
}
var responseBuilder = InitResponseBuilder(mappingModel.Response);
respondProvider.RespondWith(responseBuilder);
@@ -205,11 +209,11 @@ public partial class WireMockServer
{
foreach (var cookieModel in requestModel.Cookies.Where(c => c.Matchers != null))
{
requestBuilder = requestBuilder.WithCookie(
cookieModel.Name,
cookieModel.IgnoreCase == true,
cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch,
cookieModel.Matchers!.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
requestBuilder = requestBuilder.WithCookie(
cookieModel.Name,
cookieModel.IgnoreCase == true,
cookieModel.RejectOnMatch == true ? MatchBehaviour.RejectOnMatch : MatchBehaviour.AcceptOnMatch,
cookieModel.Matchers!.Select(_matcherMapper.Map).OfType<IStringMatcher>().ToArray());
}
}

View File

@@ -0,0 +1,54 @@
#if OPENAPIPARSER
using System;
using System.Linq;
using System.Net;
using WireMock.Net.OpenApiParser;
#endif
namespace WireMock.Server;
public partial class WireMockServer
{
private IResponseMessage OpenApiConvertToMappings(IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body, out var diagnostic);
return diagnostic.Errors.Any() ? ToJson(diagnostic, false, HttpStatusCode.BadRequest) : ToJson(mappingModels);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(e.Message, HttpStatusCode.BadRequest);
}
#else
return ResponseMessageBuilder.Create("Not supported for .NETStandard 1.3 and .NET 4.5.2 or lower.", 400);
#endif
}
private IResponseMessage OpenApiSaveToMappings(IRequestMessage requestMessage)
{
#if OPENAPIPARSER
try
{
var mappingModels = new WireMockOpenApiParser().FromText(requestMessage.Body, out var diagnostic);
if (diagnostic.Errors.Any())
{
return ToJson(diagnostic, false, HttpStatusCode.BadRequest);
}
ConvertMappingsAndRegisterAsRespondProvider(mappingModels);
return ResponseMessageBuilder.Create("OpenApi document converted to Mappings", HttpStatusCode.Created);
}
catch (Exception e)
{
_settings.Logger.Error("HttpStatusCode set to {0} {1}", HttpStatusCode.BadRequest, e);
return ResponseMessageBuilder.Create(e.Message, HttpStatusCode.BadRequest);
}
#else
return ResponseMessageBuilder.Create("Not supported for .NETStandard 1.3 and .NET 4.5.2 or lower.", 400);
#endif
}
}

View File

@@ -108,7 +108,7 @@ internal static class BytesEncodingUtils
return true;
}
if (ch >= 0xc2 && ch <= 0xdf)
if (ch is >= 0xc2 and <= 0xdf)
{
if (position >= length - 2)
{
@@ -145,7 +145,7 @@ internal static class BytesEncodingUtils
return true;
}
if (ch >= 0xe1 && ch <= 0xef)
if (ch is >= 0xe1 and <= 0xef)
{
if (position >= length - 3)
{
@@ -204,7 +204,7 @@ internal static class BytesEncodingUtils
return true;
}
if (ch >= 0xf1 && ch <= 0xf3)
if (ch is >= 0xf1 and <= 0xf3)
{
if (position >= length - 4)
{

View File

@@ -25,7 +25,7 @@ internal static class RegexUtils
return namedGroupsDictionary;
}
public static (bool IsValid, bool Result) MatchRegex(string pattern, string input, bool useRegexExtended = true)
public static (bool IsValid, bool Result) MatchRegex(string? pattern, string input, bool useRegexExtended = true)
{
if (string.IsNullOrEmpty(pattern))
{

View File

@@ -35,15 +35,19 @@
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard1.3' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1'">
<DefineConstants>NETSTANDARD;USE_ASPNETCORE</DefineConstants>
<DefineConstants>$(DefineConstants);NETSTANDARD;USE_ASPNETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1' or '$(TargetFramework)' == 'netcoreapp2.2' or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0'or '$(TargetFramework)' == 'net7.0' ">
<DefineConstants>USE_ASPNETCORE</DefineConstants>
<DefineConstants>$(DefineConstants);USE_ASPNETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net461'">
<DefineConstants>USE_ASPNETCORE;NET46</DefineConstants>
<DefineConstants>$(DefineConstants);USE_ASPNETCORE;NET46</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452'">
<DefineConstants>$(DefineConstants);OPENAPIPARSER</DefineConstants>
</PropertyGroup>
<ItemGroup>
@@ -189,4 +193,8 @@
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
<ProjectReference Include="..\WireMock.Org.Abstractions\WireMock.Org.Abstractions.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard1.3' and '$(TargetFramework)' != 'net451' and '$(TargetFramework)' != 'net452'">
<ProjectReference Include="..\WireMock.Net.OpenApiParser\WireMock.Net.OpenApiParser.csproj" />
</ItemGroup>
</Project>