From 56c606e1c41b24c09f6921f5d3f3c899037c427b Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 1 Jul 2026 11:21:56 +0200 Subject: [PATCH] Change the serialization from the WireMockServerSettings in the logging when WireMockServer is started --- examples/WireMock.Net.Console.NET8/Program.cs | 4 +- .../WireMockService.cs | 2 +- .../Settings/WireMockServerSettings.cs | 3 +- .../Settings/WireMockSettingsJsonConverter.cs | 151 ++++++++++++++++++ src/dotnet-WireMock.Net/Program.cs | 2 - .../WireMockServerSettingsJsonTests.cs | 82 ++++++++++ 6 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 src/WireMock.Net.Shared/Settings/WireMockSettingsJsonConverter.cs create mode 100644 test/WireMock.Net.Tests/Settings/WireMockServerSettingsJsonTests.cs diff --git a/examples/WireMock.Net.Console.NET8/Program.cs b/examples/WireMock.Net.Console.NET8/Program.cs index 9ca8b200..36929f77 100644 --- a/examples/WireMock.Net.Console.NET8/Program.cs +++ b/examples/WireMock.Net.Console.NET8/Program.cs @@ -1,8 +1,6 @@ // Copyright © WireMock.Net -using System.IO; using System.Reflection; -using System.Threading.Tasks; using log4net; using log4net.Config; using log4net.Repository; @@ -12,7 +10,7 @@ namespace WireMock.Net.Console.NET8; static class Program { - private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()); + private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly()!); private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); static async Task Main(params string[] args) diff --git a/examples/WireMock.Net.WebApplication.IIS/WireMockService.cs b/examples/WireMock.Net.WebApplication.IIS/WireMockService.cs index 249ddb9b..6c3cf279 100644 --- a/examples/WireMock.Net.WebApplication.IIS/WireMockService.cs +++ b/examples/WireMock.Net.WebApplication.IIS/WireMockService.cs @@ -70,7 +70,7 @@ public class WireMockService : IWireMockService _server = WireMockServer.Start(_settings); - _logger.LogInformation($"WireMock.Net server settings {JsonConvert.SerializeObject(_settings)}"); + _logger.LogInformation("WireMock.Net server settings {Settings}", JsonConvert.SerializeObject(_settings)); } public void Stop() diff --git a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs index e11f630c..4e2df9e3 100644 --- a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs @@ -357,6 +357,7 @@ public class WireMockServerSettings /// Default is . /// [PublicAPI] + [JsonConverter(typeof(WireMockSettingsJsonConverter))] public IJsonConverter DefaultJsonSerializer { get; set; } /// @@ -367,7 +368,7 @@ public class WireMockServerSettings /// Default is . /// [PublicAPI] - [JsonIgnore] + [JsonConverter(typeof(WireMockSettingsJsonConverter))] public IJsonBodyTransformer DefaultJsonBodyTransformer { get; set; } /// diff --git a/src/WireMock.Net.Shared/Settings/WireMockSettingsJsonConverter.cs b/src/WireMock.Net.Shared/Settings/WireMockSettingsJsonConverter.cs new file mode 100644 index 00000000..8d0554aa --- /dev/null +++ b/src/WireMock.Net.Shared/Settings/WireMockSettingsJsonConverter.cs @@ -0,0 +1,151 @@ +// Copyright © WireMock.Net + +using System.Reflection; +using JsonConverter.Abstractions; +using JsonConverter.Newtonsoft.Json; +using JsonConverter.System.Text.Json; +using Newtonsoft.Json; +using WireMock.Transformers; +using NewtonsoftJsonConverterBase = Newtonsoft.Json.JsonConverter; + +namespace WireMock.Settings; + +/// +/// WireMockSettingsJsonConverter serializes and deserializes the special interface-based settings properties as type names. +/// +public class WireMockSettingsJsonConverter : NewtonsoftJsonConverterBase +{ + /// + public override bool CanConvert(Type objectType) + { + return typeof(IJsonConverter).IsAssignableFrom(objectType) || typeof(IJsonBodyTransformer).IsAssignableFrom(objectType); + } + + /// + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + writer.WriteValue(value?.GetType().FullName); + } + + /// + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return existingValue ?? CreateDefaultValue(objectType); + } + + if (reader.TokenType != JsonToken.String) + { + reader.Skip(); + return existingValue ?? CreateDefaultValue(objectType); + } + + var typeName = reader.Value as string; + + if (typeof(IJsonConverter).IsAssignableFrom(objectType)) + { + return CreateJsonConverter(typeName) ?? existingValue ?? new NewtonsoftJsonConverter(); + } + + if (typeof(IJsonBodyTransformer).IsAssignableFrom(objectType)) + { + var settings = ExtractSettings(existingValue as IJsonBodyTransformer); + return settings != null + ? CreateJsonBodyTransformer(typeName, settings) ?? existingValue ?? new NewtonsoftJsonBodyTransformer(settings) + : existingValue; + } + + return existingValue; + } + + private static object? CreateDefaultValue(Type objectType) + { + if (typeof(IJsonConverter).IsAssignableFrom(objectType)) + { + return new NewtonsoftJsonConverter(); + } + + return null; + } + + private static IJsonConverter? CreateJsonConverter(string? typeName) + { + if (string.IsNullOrWhiteSpace(typeName)) + { + return null; + } + + if (typeName == nameof(NewtonsoftJsonConverter) || typeName == typeof(NewtonsoftJsonConverter).FullName) + { + return new NewtonsoftJsonConverter(); + } + + if (typeName == nameof(SystemTextJsonConverter) || typeName == typeof(SystemTextJsonConverter).FullName) + { + return new SystemTextJsonConverter(); + } + + var type = GetType(typeName); + if (type == null || !typeof(IJsonConverter).IsAssignableFrom(type)) + { + return null; + } + + return Activator.CreateInstance(type) as IJsonConverter; + } + + private static IJsonBodyTransformer? CreateJsonBodyTransformer(string? typeName, WireMockServerSettings settings) + { + if (string.IsNullOrWhiteSpace(typeName)) + { + return null; + } + + if (typeName == nameof(NewtonsoftJsonBodyTransformer) || typeName == typeof(NewtonsoftJsonBodyTransformer).FullName) + { + return new NewtonsoftJsonBodyTransformer(settings); + } + + if (typeName == nameof(SystemTextJsonBodyTransformer) || typeName == typeof(SystemTextJsonBodyTransformer).FullName) + { + return new SystemTextJsonBodyTransformer(settings); + } + + var type = GetType(typeName); + if (type == null || !typeof(IJsonBodyTransformer).IsAssignableFrom(type)) + { + return null; + } + + var constructorWithSettings = type.GetConstructor([typeof(WireMockServerSettings)]); + if (constructorWithSettings != null) + { + return constructorWithSettings.Invoke([settings]) as IJsonBodyTransformer; + } + + return Activator.CreateInstance(type) as IJsonBodyTransformer; + } + + private static WireMockServerSettings? ExtractSettings(IJsonBodyTransformer? existingValue) + { + if (existingValue == null) + { + return null; + } + + return existingValue.GetType() + .GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) + .Where(field => typeof(WireMockServerSettings).IsAssignableFrom(field.FieldType)) + .Select(field => field.GetValue(existingValue) as WireMockServerSettings) + .FirstOrDefault(settings => settings != null); + } + + private static Type? GetType(string typeName) + { + return Type.GetType(typeName, throwOnError: false) ?? + AppDomain.CurrentDomain.GetAssemblies() + .Select(assembly => assembly.GetType(typeName, throwOnError: false)) + .FirstOrDefault(foundType => foundType != null); + } +} \ No newline at end of file diff --git a/src/dotnet-WireMock.Net/Program.cs b/src/dotnet-WireMock.Net/Program.cs index 69cb172a..60fac428 100644 --- a/src/dotnet-WireMock.Net/Program.cs +++ b/src/dotnet-WireMock.Net/Program.cs @@ -1,7 +1,5 @@ // Copyright © WireMock.Net -using System; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using WireMock.Logging; using WireMock.Net.StandAlone; diff --git a/test/WireMock.Net.Tests/Settings/WireMockServerSettingsJsonTests.cs b/test/WireMock.Net.Tests/Settings/WireMockServerSettingsJsonTests.cs new file mode 100644 index 00000000..691142fe --- /dev/null +++ b/test/WireMock.Net.Tests/Settings/WireMockServerSettingsJsonTests.cs @@ -0,0 +1,82 @@ +// Copyright © WireMock.Net + +using JsonConverter.Newtonsoft.Json; +using JsonConverter.System.Text.Json; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WireMock.Settings; +using WireMock.Transformers; + +namespace WireMock.Net.Tests.Settings; + +public class WireMockServerSettingsJsonTests +{ + [Fact] + public void NewtonsoftJson_Serialize_WireMockServerSettings_HandlesSpecialProperties_AndKeepsOtherPropertiesAsIs() + { + // Arrange + var settings = new WireMockServerSettings + { + Port = 1234, + AdminPath = "/custom-admin", + StartAdminInterface = true, + DefaultJsonSerializer = new SystemTextJsonConverter() + }; + settings.DefaultJsonBodyTransformer = new SystemTextJsonBodyTransformer(settings); + + // Act + var json = JsonConvert.SerializeObject(settings); + var jsonObject = JObject.Parse(json); + + // Assert + jsonObject[nameof(WireMockServerSettings.Port)]!.Value().Should().Be(1234); + jsonObject[nameof(WireMockServerSettings.AdminPath)]!.Value().Should().Be("/custom-admin"); + jsonObject[nameof(WireMockServerSettings.StartAdminInterface)]!.Value().Should().BeTrue(); + jsonObject[nameof(WireMockServerSettings.DefaultJsonSerializer)]!.Type.Should().Be(JTokenType.String); + jsonObject[nameof(WireMockServerSettings.DefaultJsonSerializer)]!.Value().Should().Be(typeof(SystemTextJsonConverter).FullName); + jsonObject[nameof(WireMockServerSettings.DefaultJsonBodyTransformer)]!.Type.Should().Be(JTokenType.String); + jsonObject[nameof(WireMockServerSettings.DefaultJsonBodyTransformer)]!.Value().Should().Be(typeof(SystemTextJsonBodyTransformer).FullName); + } + + [Fact] + public void NewtonsoftJson_Deserialize_WireMockServerSettings_HandlesSpecialProperties_AndKeepsOtherPropertiesAsIs() + { + // Arrange + var json = + $"{{\"{nameof(WireMockServerSettings.Port)}\":4321," + + $"\"{nameof(WireMockServerSettings.AdminPath)}\":\"/custom-admin\"," + + $"\"{nameof(WireMockServerSettings.StartAdminInterface)}\":true," + + $"\"{nameof(WireMockServerSettings.DefaultJsonSerializer)}\":\"{typeof(SystemTextJsonConverter).FullName}\"," + + $"\"{nameof(WireMockServerSettings.DefaultJsonBodyTransformer)}\":\"{typeof(SystemTextJsonBodyTransformer).FullName}\"}}"; + + // Act + var settings = JsonConvert.DeserializeObject(json); + + // Assert + settings.Should().NotBeNull(); + settings!.Port.Should().Be(4321); + settings.AdminPath.Should().Be("/custom-admin"); + settings.StartAdminInterface.Should().BeTrue(); + settings.DefaultJsonSerializer.Should().BeOfType(); + settings.DefaultJsonBodyTransformer.Should().BeOfType(); + } + + [Fact] + public void NewtonsoftJson_Deserialize_WireMockServerSettings_LegacyObjects_AreIgnored_AndOtherPropertiesStayUntouched() + { + // Arrange + var json = + $"{{\"{nameof(WireMockServerSettings.Port)}\":9876," + + $"\"{nameof(WireMockServerSettings.DefaultJsonSerializer)}\":{{}}," + + $"\"{nameof(WireMockServerSettings.DefaultJsonBodyTransformer)}\":{{}}}}"; + + // Act + var settings = JsonConvert.DeserializeObject(json); + + // Assert + settings.Should().NotBeNull(); + settings!.Port.Should().Be(9876); + settings.DefaultJsonSerializer.Should().BeOfType(); + settings.DefaultJsonBodyTransformer.Should().BeOfType(); + } +} \ No newline at end of file