mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-03-31 06:03:03 +02:00
Update REST Admin interface to support "Get Mapping(s) as C# Code" (#878)
* Add /__admin/mappings/code endpoint * api * fix * . * fix * . * . * .
This commit is contained in:
@@ -7,4 +7,5 @@ internal static class WireMockConstants
|
||||
public const int ProxyPriority = -2_000_000;
|
||||
|
||||
public const string ContentTypeJson = "application/json";
|
||||
public const string ContentTypeTextPlain = "text/plain";
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using WireMock.Admin.Mappings;
|
||||
using WireMock.Matchers.Request;
|
||||
using WireMock.Server;
|
||||
using WireMock.Types;
|
||||
|
||||
namespace WireMock;
|
||||
|
||||
@@ -40,4 +42,19 @@ public interface IMappingBuilder
|
||||
/// </summary>
|
||||
/// <param name="folder">The folder to write the files to.</param>
|
||||
void SaveMappingsToFolder(string folder);
|
||||
|
||||
/// <summary>
|
||||
/// Get the C# code for a mapping.
|
||||
/// </summary>
|
||||
/// <param name="guid">The Mapping Guid.</param>
|
||||
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
|
||||
/// <returns>C# code (null in case the mapping is not found)</returns>
|
||||
string? ToCSharpCode(Guid guid, MappingConverterType converterType);
|
||||
|
||||
/// <summary>
|
||||
/// Get the C# code for all mappings.
|
||||
/// </summary>
|
||||
/// <param name="converterType">The <see cref="MappingConverterType"/></param>
|
||||
/// <returns>C# code</returns>
|
||||
public string ToCSharpCode(MappingConverterType converterType);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Stef.Validation;
|
||||
using WireMock.Admin.Mappings;
|
||||
@@ -7,6 +9,7 @@ using WireMock.Owin;
|
||||
using WireMock.Serialization;
|
||||
using WireMock.Server;
|
||||
using WireMock.Settings;
|
||||
using WireMock.Types;
|
||||
using WireMock.Util;
|
||||
|
||||
namespace WireMock;
|
||||
@@ -66,10 +69,12 @@ public class MappingBuilder : IMappingBuilder
|
||||
/// <inheritdoc />
|
||||
public MappingModel[] GetMappings()
|
||||
{
|
||||
return _options.Mappings.Values.ToArray()
|
||||
.Where(m => !m.IsAdminInterface)
|
||||
.Select(_mappingConverter.ToMappingModel)
|
||||
.ToArray();
|
||||
return GetMappingsInternal().Select(_mappingConverter.ToMappingModel).ToArray();
|
||||
}
|
||||
|
||||
internal IMapping[] GetMappingsInternal()
|
||||
{
|
||||
return _options.Mappings.Values.ToArray().Where(m => !m.IsAdminInterface).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -78,6 +83,37 @@ public class MappingBuilder : IMappingBuilder
|
||||
return ToJson(GetMappings());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? ToCSharpCode(Guid guid, MappingConverterType converterType)
|
||||
{
|
||||
var mapping = GetMappingsInternal().FirstOrDefault(m => m.Guid == guid);
|
||||
if (mapping is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var settings = new MappingConverterSettings { AddStart = true, ConverterType = converterType };
|
||||
return _mappingConverter.ToCSharpCode(mapping, settings);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ToCSharpCode(MappingConverterType converterType)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
bool addStart = true;
|
||||
foreach (var mapping in GetMappingsInternal())
|
||||
{
|
||||
sb.AppendLine(_mappingConverter.ToCSharpCode(mapping, new MappingConverterSettings { AddStart = addStart, ConverterType = converterType }));
|
||||
|
||||
if (addStart)
|
||||
{
|
||||
addStart = false;
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SaveMappingsToFile(string path)
|
||||
{
|
||||
|
||||
@@ -56,9 +56,12 @@ public class RequestMessage : IRequestMessage
|
||||
/// <inheritdoc cref="IRequestMessage.Cookies" />
|
||||
public IDictionary<string, string>? Cookies { get; }
|
||||
|
||||
/// <inheritdoc cref="IRequestMessage.Query" />
|
||||
/// <inheritdoc />
|
||||
public IDictionary<string, WireMockList<string>>? Query { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDictionary<string, WireMockList<string>>? QueryIgnoreCase { get; }
|
||||
|
||||
/// <inheritdoc cref="IRequestMessage.RawQuery" />
|
||||
public string RawQuery { get; }
|
||||
|
||||
@@ -171,6 +174,7 @@ public class RequestMessage : IRequestMessage
|
||||
Cookies = cookies;
|
||||
RawQuery = urlDetails.Url.Query;
|
||||
Query = QueryStringParser.Parse(RawQuery, options?.QueryParameterMultipleValueSupport);
|
||||
QueryIgnoreCase = new Dictionary<string, WireMockList<string>>(Query, StringComparer.OrdinalIgnoreCase);
|
||||
#if USE_ASPNETCORE
|
||||
ClientCertificate = clientCertificate;
|
||||
#endif
|
||||
|
||||
@@ -33,7 +33,7 @@ internal class MappingConverter
|
||||
settings ??= new MappingConverterSettings();
|
||||
|
||||
var request = (Request)mapping.RequestMatcher;
|
||||
var response = (Response)mapping.Provider;
|
||||
var response = (Response) mapping.Provider;
|
||||
|
||||
var clientIPMatcher = request.GetRequestMessageMatcher<RequestMessageClientIPMatcher>();
|
||||
var pathMatcher = request.GetRequestMessageMatcher<RequestMessagePathMatcher>();
|
||||
|
||||
@@ -31,7 +31,7 @@ internal class RespondWithAProvider : IRespondWithAProvider
|
||||
private readonly IDateTimeUtils _dateTimeUtils;
|
||||
private readonly bool _saveToFile;
|
||||
|
||||
private bool _useWebhookFireAndForget;
|
||||
private bool? _useWebhookFireAndForget;
|
||||
|
||||
public Guid Guid { get; private set; }
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ public partial class WireMockServer
|
||||
private const int EnhancedFileSystemWatcherTimeoutMs = 1000;
|
||||
private const string AdminFiles = "/__admin/files";
|
||||
private const string AdminMappings = "/__admin/mappings";
|
||||
private const string AdminMappingsCode = "/__admin/mappings/code";
|
||||
private const string AdminMappingsWireMockOrg = "/__admin/mappings/wiremock.org";
|
||||
private const string AdminRequests = "/__admin/requests";
|
||||
private const string AdminSettings = "/__admin/settings";
|
||||
@@ -41,6 +42,7 @@ public partial class WireMockServer
|
||||
private static readonly Guid ProxyMappingGuid = new("e59914fd-782e-428e-91c1-4810ffb86567");
|
||||
private static readonly RegexMatcher AdminRequestContentTypeJson = new ContentTypeMatcher(WireMockConstants.ContentTypeJson, true);
|
||||
private static 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 static readonly RegexMatcher AdminMappingsCodeGuidPathMatcher = new(@"^\/__admin\/mappings\/code\/([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 static 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})$");
|
||||
private static readonly RegexMatcher AdminScenariosNameMatcher = new(@"^\/__admin\/scenarios\/.+$");
|
||||
private static readonly RegexMatcher AdminScenariosNameWithResetMatcher = new(@"^\/__admin\/scenarios\/.+\/reset$");
|
||||
@@ -57,9 +59,14 @@ public partial class WireMockServer
|
||||
// __admin/mappings
|
||||
Given(Request.Create().WithPath(AdminMappings).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsGet));
|
||||
Given(Request.Create().WithPath(AdminMappings).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPost));
|
||||
Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg));
|
||||
Given(Request.Create().WithPath(AdminMappings).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsDelete));
|
||||
|
||||
// __admin/mappings/code
|
||||
Given(Request.Create().WithPath(AdminMappingsCode).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsCodeGet));
|
||||
|
||||
// __admin/mappings/wiremock.org
|
||||
Given(Request.Create().WithPath(AdminMappingsWireMockOrg).UsingPost().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsPostWireMockOrg));
|
||||
|
||||
// __admin/mappings/reset
|
||||
Given(Request.Create().WithPath(AdminMappings + "/reset").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsReset));
|
||||
|
||||
@@ -68,6 +75,9 @@ public partial class WireMockServer
|
||||
Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingPut().WithHeader(HttpKnownHeaderNames.ContentType, AdminRequestContentTypeJson)).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingPut));
|
||||
Given(Request.Create().WithPath(AdminMappingsGuidPathMatcher).UsingDelete()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingDelete));
|
||||
|
||||
// __admin/mappings/code/{guid}
|
||||
Given(Request.Create().WithPath(AdminMappingsCodeGuidPathMatcher).UsingGet()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingCodeGet));
|
||||
|
||||
// __admin/mappings/save
|
||||
Given(Request.Create().WithPath($"{AdminMappings}/save").UsingPost()).AtPriority(WireMockConstants.AdminPriority).RespondWith(new DynamicResponseProvider(MappingsSave));
|
||||
|
||||
@@ -286,13 +296,11 @@ public partial class WireMockServer
|
||||
#region Mapping/{guid}
|
||||
private IResponseMessage MappingGet(IRequestMessage requestMessage)
|
||||
{
|
||||
Guid guid = ParseGuidFromRequestMessage(requestMessage);
|
||||
var mapping = Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid);
|
||||
|
||||
var mapping = FindMappingByGuid(requestMessage);
|
||||
if (mapping == null)
|
||||
{
|
||||
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
|
||||
return ResponseMessageBuilder.Create("Mapping not found", 404);
|
||||
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
var model = _mappingConverter.ToMappingModel(mapping);
|
||||
@@ -300,31 +308,71 @@ public partial class WireMockServer
|
||||
return ToJson(model);
|
||||
}
|
||||
|
||||
private IResponseMessage MappingCodeGet(IRequestMessage requestMessage)
|
||||
{
|
||||
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
|
||||
{
|
||||
var code = _mappingBuilder.ToCSharpCode(guid, GetMappingConverterType(requestMessage));
|
||||
if (code is null)
|
||||
{
|
||||
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
|
||||
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
return ToResponseMessage(code);
|
||||
}
|
||||
|
||||
_settings.Logger.Warn("HttpStatusCode set to 400");
|
||||
return ResponseMessageBuilder.Create("GUID is missing", HttpStatusCode.BadRequest);
|
||||
}
|
||||
|
||||
private static MappingConverterType GetMappingConverterType(IRequestMessage requestMessage)
|
||||
{
|
||||
var mappingConverterType = MappingConverterType.Server;
|
||||
|
||||
if (requestMessage.QueryIgnoreCase?.TryGetValue(nameof(MappingConverterType), out var values) == true &&
|
||||
Enum.TryParse(values.FirstOrDefault(), true, out MappingConverterType parsed))
|
||||
{
|
||||
mappingConverterType = parsed;
|
||||
}
|
||||
|
||||
return mappingConverterType;
|
||||
}
|
||||
|
||||
private IMapping? FindMappingByGuid(IRequestMessage requestMessage)
|
||||
{
|
||||
return TryParseGuidFromRequestMessage(requestMessage, out var guid) ? Mappings.FirstOrDefault(m => !m.IsAdminInterface && m.Guid == guid) : null;
|
||||
}
|
||||
|
||||
private IResponseMessage MappingPut(IRequestMessage requestMessage)
|
||||
{
|
||||
Guid guid = ParseGuidFromRequestMessage(requestMessage);
|
||||
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
|
||||
{
|
||||
var mappingModel = DeserializeObject<MappingModel>(requestMessage);
|
||||
var guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
|
||||
|
||||
var mappingModel = DeserializeObject<MappingModel>(requestMessage);
|
||||
Guid? guidFromPut = ConvertMappingAndRegisterAsRespondProvider(mappingModel, guid);
|
||||
return ResponseMessageBuilder.Create("Mapping added or updated", HttpStatusCode.OK, guidFromPut);
|
||||
}
|
||||
|
||||
return ResponseMessageBuilder.Create("Mapping added or updated", HttpStatusCode.OK, guidFromPut);
|
||||
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
|
||||
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
private IResponseMessage MappingDelete(IRequestMessage requestMessage)
|
||||
{
|
||||
Guid guid = ParseGuidFromRequestMessage(requestMessage);
|
||||
|
||||
if (DeleteMapping(guid))
|
||||
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteMapping(guid))
|
||||
{
|
||||
return ResponseMessageBuilder.Create("Mapping removed", HttpStatusCode.OK, guid);
|
||||
}
|
||||
|
||||
_settings.Logger.Warn("HttpStatusCode set to 404 : Mapping not found");
|
||||
return ResponseMessageBuilder.Create("Mapping not found", HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
private static Guid ParseGuidFromRequestMessage(IRequestMessage requestMessage)
|
||||
private static bool TryParseGuidFromRequestMessage(IRequestMessage requestMessage, out Guid guid)
|
||||
{
|
||||
return Guid.Parse(requestMessage.Path.Substring(AdminMappings.Length + 1));
|
||||
var lastPart = requestMessage.Path.Split('/').LastOrDefault();
|
||||
return Guid.TryParse(lastPart, out guid);
|
||||
}
|
||||
#endregion Mapping/{guid}
|
||||
|
||||
@@ -360,6 +408,15 @@ public partial class WireMockServer
|
||||
return ToJson(ToMappingModels());
|
||||
}
|
||||
|
||||
private IResponseMessage MappingsCodeGet(IRequestMessage requestMessage)
|
||||
{
|
||||
var converterType = GetMappingConverterType(requestMessage);
|
||||
|
||||
var code = _mappingBuilder.ToCSharpCode(converterType);
|
||||
|
||||
return ToResponseMessage(code);
|
||||
}
|
||||
|
||||
private IResponseMessage MappingsPost(IRequestMessage requestMessage)
|
||||
{
|
||||
try
|
||||
@@ -464,30 +521,29 @@ public partial class WireMockServer
|
||||
#region Request/{guid}
|
||||
private IResponseMessage RequestGet(IRequestMessage requestMessage)
|
||||
{
|
||||
Guid guid = ParseGuidFromRequestMessage(requestMessage);
|
||||
var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
|
||||
|
||||
if (entry == null)
|
||||
if (TryParseGuidFromRequestMessage(requestMessage, out var guid))
|
||||
{
|
||||
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
|
||||
return ResponseMessageBuilder.Create("Request not found", 404);
|
||||
var entry = LogEntries.FirstOrDefault(r => !r.RequestMessage.Path.StartsWith("/__admin/") && r.Guid == guid);
|
||||
if (entry is { })
|
||||
{
|
||||
var model = new LogEntryMapper(_options).Map(entry);
|
||||
return ToJson(model);
|
||||
}
|
||||
}
|
||||
|
||||
var model = new LogEntryMapper(_options).Map(entry);
|
||||
|
||||
return ToJson(model);
|
||||
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
|
||||
return ResponseMessageBuilder.Create("Request not found", HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
private IResponseMessage RequestDelete(IRequestMessage requestMessage)
|
||||
{
|
||||
Guid guid = ParseGuidFromRequestMessage(requestMessage);
|
||||
|
||||
if (DeleteLogEntry(guid))
|
||||
if (TryParseGuidFromRequestMessage(requestMessage, out var guid) && DeleteLogEntry(guid))
|
||||
{
|
||||
return ResponseMessageBuilder.Create("Request removed");
|
||||
}
|
||||
|
||||
return ResponseMessageBuilder.Create("Request not found", 404);
|
||||
_settings.Logger.Warn("HttpStatusCode set to 404 : Request not found");
|
||||
return ResponseMessageBuilder.Create("Request not found", HttpStatusCode.NotFound);
|
||||
}
|
||||
#endregion Request/{guid}
|
||||
|
||||
@@ -564,7 +620,7 @@ public partial class WireMockServer
|
||||
|
||||
return ResetScenario(name) ?
|
||||
ResponseMessageBuilder.Create("Scenario reset") :
|
||||
ResponseMessageBuilder.Create($"No scenario found by name '{name}'.", 404);
|
||||
ResponseMessageBuilder.Create($"No scenario found by name '{name}'.", HttpStatusCode.NotFound);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -687,6 +743,20 @@ public partial class WireMockServer
|
||||
};
|
||||
}
|
||||
|
||||
private static ResponseMessage ToResponseMessage(string text)
|
||||
{
|
||||
return new ResponseMessage
|
||||
{
|
||||
BodyData = new BodyData
|
||||
{
|
||||
DetectedBodyType = BodyType.String,
|
||||
BodyAsString = text
|
||||
},
|
||||
StatusCode = (int)HttpStatusCode.OK,
|
||||
Headers = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string>(WireMockConstants.ContentTypeTextPlain) } }
|
||||
};
|
||||
}
|
||||
|
||||
private static T DeserializeObject<T>(IRequestMessage requestMessage) where T : new()
|
||||
{
|
||||
return requestMessage.BodyData?.DetectedBodyType switch
|
||||
|
||||
@@ -540,6 +540,20 @@ public partial class WireMockServer : IWireMockServer
|
||||
return _mappingBuilder.Given(requestMatcher, saveToFile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[PublicAPI]
|
||||
public string? MappingToCSharpCode(Guid guid, MappingConverterType converterType)
|
||||
{
|
||||
return _mappingBuilder.ToCSharpCode(guid, converterType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[PublicAPI]
|
||||
public string MappingsToCSharpCode(MappingConverterType converterType)
|
||||
{
|
||||
return _mappingBuilder.ToCSharpCode(converterType);
|
||||
}
|
||||
|
||||
private void InitSettings(WireMockServerSettings settings)
|
||||
{
|
||||
if (settings.AllowBodyForAllHttpMethods == true)
|
||||
|
||||
Reference in New Issue
Block a user