1
0
mirror of synced 2024-12-22 17:23:07 +01:00

Fixed #8: Forwards compatibility of enums

This commit is contained in:
Mark van Renswoude 2019-01-28 11:30:24 +01:00
parent d37e593b78
commit 45c090d00d
3 changed files with 114 additions and 6 deletions

View File

@ -0,0 +1,90 @@
using System;
using System.Diagnostics;
using Newtonsoft.Json;
namespace Tapeti.Default
{
/// <summary>
/// Converts an <see cref="Enum"/> to and from its name string value. If an unknown string value is encountered
/// it will translate to 0xDEADBEEF (-559038737) so it can be gracefully handled.
/// If you copy this value as-is to another message and try to send it, this converter will throw an exception.
///
/// This converter is far simpler than the default StringEnumConverter, it assumes both sides use the same
/// enum and therefore skips the naming strategy.
/// </summary>
public class FallbackStringEnumConverter : JsonConverter
{
private readonly int invalidEnumValue;
public FallbackStringEnumConverter()
{
unchecked { invalidEnumValue = (int)0xDEADBEEF; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
if ((int) value == invalidEnumValue)
throw new ArgumentException("Enum value was an unknown string value in an incoming message and can not be published in an outgoing message as-is");
var outputValue = Enum.GetName(value.GetType(), value);
writer.WriteValue(outputValue);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var isNullable = IsNullableType(objectType);
if (reader.TokenType == JsonToken.Null)
{
if (!isNullable)
throw new JsonSerializationException($"Cannot convert null value to {objectType}");
return null;
}
var actualType = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType;
Debug.Assert(actualType != null, nameof(actualType) + " != null");
if (reader.TokenType != JsonToken.String)
throw new JsonSerializationException($"Unexpected token {reader.TokenType} when parsing enum");
var enumText = reader.Value.ToString();
if (enumText == string.Empty && isNullable)
return null;
try
{
return Enum.Parse(actualType, enumText);
}
catch (ArgumentException)
{
return Enum.ToObject(actualType, invalidEnumValue);
}
}
public override bool CanConvert(Type objectType)
{
var actualType = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType;
return actualType?.IsEnum ?? false;
}
private static bool IsNullableType(Type t)
{
if (t == null)
throw new ArgumentNullException(nameof(t));
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
}
}

View File

@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using RabbitMQ.Client;
namespace Tapeti.Default
@ -25,7 +24,7 @@ namespace Tapeti.Default
NullValueHandling = NullValueHandling.Ignore
};
serializerSettings.Converters.Add(new StringEnumConverter());
serializerSettings.Converters.Add(new FallbackStringEnumConverter());
}
@ -52,7 +51,7 @@ namespace Tapeti.Default
throw new ArgumentException($"{ClassTypeHeader} header not present");
var messageType = deserializedTypeNames.GetOrAdd(Encoding.UTF8.GetString((byte[])typeName), DeserializeTypeName);
return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(body), messageType);
return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(body), messageType, serializerSettings);
}

View File

@ -80,12 +80,16 @@ namespace Test
return flowProvider.YieldWithParallelRequest()
.AddRequestSync<PoloConfirmationRequestMessage, PoloConfirmationResponseMessage>(new PoloConfirmationRequestMessage
{
StoredInState = StateTestGuid
StoredInState = StateTestGuid,
EnumValue = TestEnum.Value1,
}, HandlePoloConfirmationResponse1)
.AddRequestSync<PoloConfirmationRequestMessage, PoloConfirmationResponseMessage>(new PoloConfirmationRequestMessage
{
StoredInState = StateTestGuid
StoredInState = StateTestGuid,
EnumValue = TestEnum.Value2,
OptionalEnumValue = TestEnum.Value1
}, HandlePoloConfirmationResponse2)
.YieldSync(ContinuePoloConfirmation);
@ -127,7 +131,9 @@ namespace Test
return new PoloConfirmationResponseMessage
{
ShouldMatchState = message.StoredInState
ShouldMatchState = message.StoredInState,
EnumValue = message.EnumValue,
OptionalEnumValue = message.OptionalEnumValue
};
}
@ -141,6 +147,13 @@ namespace Test
}
public enum TestEnum
{
Value1,
Value2
}
[Request(Response = typeof(PoloMessage))]
public class MarcoMessage
{
@ -157,6 +170,9 @@ namespace Test
{
[Required]
public Guid StoredInState { get; set; }
public TestEnum EnumValue;
public TestEnum? OptionalEnumValue;
}
@ -164,5 +180,8 @@ namespace Test
{
[Required]
public Guid ShouldMatchState { get; set; }
public TestEnum EnumValue;
public TestEnum? OptionalEnumValue;
}
}