diff --git a/Tapeti/Default/FallbackStringEnumConverter.cs b/Tapeti/Default/FallbackStringEnumConverter.cs
new file mode 100644
index 0000000..d4098c3
--- /dev/null
+++ b/Tapeti/Default/FallbackStringEnumConverter.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Diagnostics;
+using Newtonsoft.Json;
+
+namespace Tapeti.Default
+{
+ ///
+ /// Converts an 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.
+ ///
+ 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<>);
+ }
+ }
+}
diff --git a/Tapeti/Default/JsonMessageSerializer.cs b/Tapeti/Default/JsonMessageSerializer.cs
index 2aee24f..9cee002 100644
--- a/Tapeti/Default/JsonMessageSerializer.cs
+++ b/Tapeti/Default/JsonMessageSerializer.cs
@@ -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);
}
diff --git a/Test/MarcoController.cs b/Test/MarcoController.cs
index 4ced01e..3368f59 100644
--- a/Test/MarcoController.cs
+++ b/Test/MarcoController.cs
@@ -80,12 +80,16 @@ namespace Test
return flowProvider.YieldWithParallelRequest()
.AddRequestSync(new PoloConfirmationRequestMessage
{
- StoredInState = StateTestGuid
+ StoredInState = StateTestGuid,
+ EnumValue = TestEnum.Value1,
+
}, HandlePoloConfirmationResponse1)
.AddRequestSync(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;
}
}