Fixed #8: Forwards compatibility of enums
This commit is contained in:
parent
d37e593b78
commit
45c090d00d
90
Tapeti/Default/FallbackStringEnumConverter.cs
Normal file
90
Tapeti/Default/FallbackStringEnumConverter.cs
Normal 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<>);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user