Swagger support (#749)

* r

* fix

* sw

* x

* s

* .

* .

* .

* CreateTypeFromJObject

* .

* .

* f

* c

* .

* .

* .

* .

* .

* .

* ok

* ,

* .

* .

* .

* .

* n

* pact

* fix

* schema

* null

* fluent

* r

* -p

* .

* .

* refs

* .
This commit is contained in:
Stef Heyenrath
2022-05-13 22:01:46 +02:00
committed by GitHub
parent 0d8b3b1438
commit 5e301fd74b
45 changed files with 2371 additions and 1123 deletions

View File

@@ -35,7 +35,6 @@ namespace WireMock.Server;
public partial class WireMockServer
{
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
private const string ContentTypeJson = "application/json";
private const string AdminFiles = "/__admin/files";
private const string AdminMappings = "/__admin/mappings";
private const string AdminMappingsWireMockOrg = "/__admin/mappings/wiremock.org";
@@ -45,7 +44,7 @@ public partial class WireMockServer
private const string QueryParamReloadStaticMappings = "reloadStaticMappings";
private readonly Guid _proxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(ContentTypeJson, true);
private readonly RegexMatcher _adminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
private readonly RegexMatcher _adminMappingsGuidPathMatcher = new(@"^\/__admin\/mappings\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
private readonly RegexMatcher _adminRequestsGuidPathMatcher = new(@"^\/__admin\/requests\/([0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12})$");
@@ -73,7 +72,10 @@ public partial class WireMockServer
Given(Request.Create().WithPath(_adminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
// __admin/mappings/save
Given(Request.Create().WithPath(AdminMappings + "/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave));
Given(Request.Create().WithPath($"{AdminMappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave));
// __admin/mappings/swagger
Given(Request.Create().WithPath($"{AdminMappings}/swagger").UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(SwaggerGet));
// __admin/requests
Given(Request.Create().WithPath(AdminRequests).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(RequestsGet));
@@ -148,7 +150,7 @@ public partial class WireMockServer
/// <inheritdoc cref="IWireMockServer.WatchStaticMappings" />
[PublicAPI]
public void WatchStaticMappings([CanBeNull] string folder = null)
public void WatchStaticMappings(string? folder = null)
{
if (folder == null)
{
@@ -379,6 +381,20 @@ public partial class WireMockServer
#endregion Mapping/{guid}
#region Mappings
private IResponseMessage SwaggerGet(IRequestMessage requestMessage)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = SwaggerMapper.ToSwagger(this)
},
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
};
}
private IResponseMessage MappingsSave(IRequestMessage requestMessage)
{
SaveStaticMappings();
@@ -667,6 +683,19 @@ public partial class WireMockServer
}
#endregion
#region Pact
/// <summary>
/// Save the mappings as a Pact Json file V2.
/// </summary>
/// <param name="folder">The folder to save the pact file.</param>
/// <param name="filename">The filename for the .json file [optional].</param>
[PublicAPI]
public void SavePact(string folder, string? filename = null)
{
var (filenameUpdated, bytes) = PactMapper.ToPact(this, filename);
_settings.FileSystemHandler.WriteFile(folder, filenameUpdated, bytes);
}
/// <summary>
/// This stores details about the consumer of the interaction.
/// </summary>
@@ -688,7 +717,7 @@ public partial class WireMockServer
Provider = provider;
return this;
}
#endregion
private IRequestBuilder? InitRequestBuilder(RequestModel requestModel, bool pathOrUrlRequired)
{
IRequestBuilder requestBuilder = Request.Create();
@@ -904,68 +933,6 @@ public partial class WireMockServer
return responseBuilder;
}
private ResponseMessage ToJson<T>(T result, bool keepNullValues = false)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault)
},
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(ContentTypeJson) } }
};
}
private Encoding? ToEncoding(EncodingModel? encodingModel)
{
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
}
private T? DeserializeObject<T>(IRequestMessage requestMessage)
{
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String)
{
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString);
}
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json)
{
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>();
}
return default(T);
}
private T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
{
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json)
{
var bodyAsJson = requestMessage.BodyData.BodyAsJson;
return DeserializeObjectToArray<T>(bodyAsJson);
}
return default(T[]);
}
private T[] DeserializeObjectToArray<T>(object value)
{
if (value is JArray jArray)
{
return jArray.ToObject<T[]>();
}
var singleResult = ((JObject)value).ToObject<T>();
return new[] { singleResult };
}
private T[] DeserializeJsonToArray<T>(string value)
{
return DeserializeObjectToArray<T>(JsonUtils.DeserializeObject(value));
}
private void DisposeEnhancedFileSystemWatcher()
{
if (_enhancedFileSystemWatcher != null)
@@ -1012,4 +979,66 @@ public partial class WireMockServer
DeleteMapping(args.FullPath);
}
}
private static Encoding? ToEncoding(EncodingModel? encodingModel)
{
return encodingModel != null ? Encoding.GetEncoding(encodingModel.CodePage) : null;
}
private static ResponseMessage ToJson<T>(T result, bool keepNullValues = false)
{
return new ResponseMessage
{
BodyData = new BodyData
{
DetectedBodyType = BodyType.String,
BodyAsString = JsonConvert.SerializeObject(result, keepNullValues ? JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues : JsonSerializationConstants.JsonSerializerSettingsDefault)
},
StatusCode = (int)HttpStatusCode.OK,
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeJson) } }
};
}
private static T? DeserializeObject<T>(IRequestMessage requestMessage)
{
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.String)
{
return JsonUtils.DeserializeObject<T>(requestMessage.BodyData.BodyAsString);
}
if (requestMessage?.BodyData?.DetectedBodyType == BodyType.Json)
{
return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject<T>();
}
return default(T);
}
private static T[] DeserializeRequestMessageToArray<T>(IRequestMessage requestMessage)
{
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json)
{
var bodyAsJson = requestMessage.BodyData.BodyAsJson;
return DeserializeObjectToArray<T>(bodyAsJson);
}
return default(T[]);
}
private static T[] DeserializeJsonToArray<T>(string value)
{
return DeserializeObjectToArray<T>(JsonUtils.DeserializeObject(value));
}
private static T[] DeserializeObjectToArray<T>(object value)
{
if (value is JArray jArray)
{
return jArray.ToObject<T[]>();
}
var singleResult = ((JObject)value).ToObject<T>();
return new[] { singleResult };
}
}

View File

@@ -5,87 +5,89 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using JetBrains.Annotations;
using Stef.Validation;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Matchers.Request;
namespace WireMock.Server
namespace WireMock.Server;
public partial class WireMockServer
{
public partial class WireMockServer
/// <inheritdoc cref="IWireMockServer.LogEntriesChanged" />
[PublicAPI]
public event NotifyCollectionChangedEventHandler LogEntriesChanged
{
/// <inheritdoc cref="IWireMockServer.LogEntriesChanged" />
[PublicAPI]
public event NotifyCollectionChangedEventHandler LogEntriesChanged
add
{
add
_options.LogEntries.CollectionChanged += (sender, eventRecordArgs) =>
{
_options.LogEntries.CollectionChanged += (sender, eventRecordArgs) =>
try
{
try
{
value(sender, eventRecordArgs);
}
catch (Exception exception)
{
_options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message);
}
};
}
remove => _options.LogEntries.CollectionChanged -= value;
}
/// <inheritdoc cref="IWireMockServer.LogEntries" />
[PublicAPI]
public IEnumerable<ILogEntry> LogEntries => new ReadOnlyCollection<LogEntry>(_options.LogEntries.ToList());
/// <summary>
/// The search log-entries based on matchers.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IEnumerable"/>.</returns>
[PublicAPI]
public IEnumerable<LogEntry> FindLogEntries([NotNull] params IRequestMatcher[] matchers)
{
var results = new Dictionary<LogEntry, RequestMatchResult>();
foreach (var log in _options.LogEntries.ToList())
{
var requestMatchResult = new RequestMatchResult();
foreach (var matcher in matchers)
{
matcher.GetMatchingScore(log.RequestMessage, requestMatchResult);
value(sender, eventRecordArgs);
}
if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect)
catch (Exception exception)
{
results.Add(log, requestMatchResult);
_options.Logger.Error("Error calling the LogEntriesChanged event handler: {0}", exception.Message);
}
}
return new ReadOnlyCollection<LogEntry>(results.OrderBy(x => x.Value).Select(x => x.Key).ToList());
};
}
/// <inheritdoc cref="IWireMockServer.ResetLogEntries" />
[PublicAPI]
public void ResetLogEntries()
{
_options.LogEntries.Clear();
}
remove => _options.LogEntries.CollectionChanged -= value;
}
/// <inheritdoc cref="IWireMockServer.DeleteLogEntry" />
[PublicAPI]
public bool DeleteLogEntry(Guid guid)
/// <inheritdoc cref="IWireMockServer.LogEntries" />
[PublicAPI]
public IEnumerable<ILogEntry> LogEntries => new ReadOnlyCollection<LogEntry>(_options.LogEntries.ToList());
/// <summary>
/// The search log-entries based on matchers.
/// </summary>
/// <param name="matchers">The matchers.</param>
/// <returns>The <see cref="IEnumerable"/>.</returns>
[PublicAPI]
public IEnumerable<LogEntry> FindLogEntries(params IRequestMatcher[] matchers)
{
Guard.NotNull(matchers);
var results = new Dictionary<LogEntry, RequestMatchResult>();
foreach (var log in _options.LogEntries.ToList())
{
// Check a LogEntry exists with the same GUID, if so, remove it.
var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid);
if (existing != null)
var requestMatchResult = new RequestMatchResult();
foreach (var matcher in matchers)
{
_options.LogEntries.Remove(existing);
return true;
matcher.GetMatchingScore(log.RequestMessage, requestMatchResult);
}
return false;
if (requestMatchResult.AverageTotalScore > MatchScores.AlmostPerfect)
{
results.Add(log, requestMatchResult);
}
}
return new ReadOnlyCollection<LogEntry>(results.OrderBy(x => x.Value).Select(x => x.Key).ToList());
}
/// <inheritdoc cref="IWireMockServer.ResetLogEntries" />
[PublicAPI]
public void ResetLogEntries()
{
_options.LogEntries.Clear();
}
/// <inheritdoc cref="IWireMockServer.DeleteLogEntry" />
[PublicAPI]
public bool DeleteLogEntry(Guid guid)
{
// Check a LogEntry exists with the same GUID, if so, remove it.
var existing = _options.LogEntries.ToList().FirstOrDefault(m => m.Guid == guid);
if (existing != null)
{
_options.LogEntries.Remove(existing);
return true;
}
return false;
}
}

View File

@@ -1,167 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Admin.Mappings;
using WireMock.Pact.Models.V2;
using WireMock.Util;
namespace WireMock.Server;
public partial class WireMockServer
{
private const string DefaultPath = "/";
private const string DefaultMethod = "GET";
private const int DefaultStatus = 200;
private const string DefaultConsumer = "Default Consumer";
private const string DefaultProvider = "Default Provider";
/// <summary>
/// Save the mappings as a Pact Json file V2.
/// </summary>
/// <param name="folder">The folder to save the pact file.</param>
/// <param name="filename">The filename for the .json file [optional].</param>
public void SavePact(string folder, string? filename = null)
{
var consumer = Consumer ?? DefaultConsumer;
var provider = Provider ?? DefaultProvider;
filename ??= $"{consumer} - {provider}.json";
var pact = new Pact.Models.V2.Pact
{
Consumer = new Pacticipant { Name = consumer },
Provider = new Pacticipant { Name = provider }
};
foreach (var mapping in MappingModels)
{
var interaction = new Interaction
{
Description = mapping.Description,
ProviderState = mapping.Title,
Request = MapRequest(mapping.Request),
Response = MapResponse(mapping.Response)
};
pact.Interactions.Add(interaction);
}
var bytes = JsonUtils.SerializeAsPactFile(pact);
_settings.FileSystemHandler.WriteFile(folder, filename, bytes);
}
private static Request MapRequest(RequestModel request)
{
string path;
switch (request.Path)
{
case string pathAsString:
path = pathAsString;
break;
case PathModel pathModel:
path = GetPatternAsStringFromMatchers(pathModel.Matchers, DefaultPath);
break;
default:
path = DefaultPath;
break;
}
return new Request
{
Method = request.Methods?.FirstOrDefault() ?? DefaultMethod,
Path = path,
Query = MapQueryParameters(request.Params),
Headers = MapRequestHeaders(request.Headers),
Body = MapBody(request.Body)
};
}
private static Response MapResponse(ResponseModel? response)
{
if (response == null)
{
return new Response();
}
return new Response
{
Status = MapStatusCode(response.StatusCode),
Headers = MapResponseHeaders(response.Headers),
Body = response.BodyAsJson
};
}
private static int MapStatusCode(object? statusCode)
{
if (statusCode is string statusCodeAsString)
{
return int.TryParse(statusCodeAsString, out var statusCodeAsInt) ? statusCodeAsInt : DefaultStatus;
}
if (statusCode != null)
{
// Convert to Int32 because Newtonsoft deserializes an 'object' with a number value to a long.
return Convert.ToInt32(statusCode);
}
return DefaultStatus;
}
private static string? MapQueryParameters(IList<ParamModel>? queryParameters)
{
if (queryParameters == null)
{
return null;
}
var values = queryParameters
.Where(qp => qp.Matchers != null && qp.Matchers.Any() && qp.Matchers[0].Pattern is string)
.Select(param => $"{Uri.EscapeDataString(param.Name)}={Uri.EscapeDataString((string)param.Matchers![0].Pattern)}");
return string.Join("&", values);
}
private static IDictionary<string, string>? MapRequestHeaders(IList<HeaderModel>? headers)
{
if (headers == null)
{
return null;
}
var validHeaders = headers.Where(h => h.Matchers != null && h.Matchers.Any() && h.Matchers[0].Pattern is string);
return validHeaders.ToDictionary(x => x.Name, y => (string)y.Matchers![0].Pattern);
}
private static IDictionary<string, string>? MapResponseHeaders(IDictionary<string, object>? headers)
{
if (headers == null)
{
return null;
}
var validHeaders = headers.Where(h => h.Value is string);
return validHeaders.ToDictionary(x => x.Key, y => (string)y.Value);
}
private static object? MapBody(BodyModel? body)
{
if (body == null || body.Matcher.Name != "JsonMatcher")
{
return null;
}
return body.Matcher.Pattern;
}
private static string GetPatternAsStringFromMatchers(MatcherModel[]? matchers, string defaultValue)
{
if (matchers != null && matchers.Any() && matchers[0].Pattern is string patternAsString)
{
return patternAsString;
}
return defaultValue;
}
}