diff --git a/src/JustSaying/Messaging/MessageSerialization/MessageAttributeParser.cs b/src/JustSaying/Messaging/MessageSerialization/MessageAttributeParser.cs new file mode 100644 index 000000000..c47cf9b94 --- /dev/null +++ b/src/JustSaying/Messaging/MessageSerialization/MessageAttributeParser.cs @@ -0,0 +1,18 @@ +namespace JustSaying.Messaging.MessageSerialization; + +internal static class MessageAttributeParser +{ + public static MessageAttributeValue Parse(string dataType, string dataValue) + { + // Check for a prefix instead of an exact match as SQS supports custom-type labels, or example, "Binary.gif". + // See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html#sqs-message-attributes. + bool isBinary = dataType?.StartsWith("Binary", StringComparison.Ordinal) is true; + + return new() + { + DataType = dataType, + StringValue = !isBinary ? dataValue : null, + BinaryValue = isBinary ? Convert.FromBase64String(dataValue) : null + }; + } +} diff --git a/src/JustSaying/Messaging/MessageSerialization/NewtonsoftSerializer.cs b/src/JustSaying/Messaging/MessageSerialization/NewtonsoftSerializer.cs index 68c5b5d25..8ceeece1c 100644 --- a/src/JustSaying/Messaging/MessageSerialization/NewtonsoftSerializer.cs +++ b/src/JustSaying/Messaging/MessageSerialization/NewtonsoftSerializer.cs @@ -17,10 +17,10 @@ public NewtonsoftSerializer() public NewtonsoftSerializer(JsonSerializerSettings settings) { settings ??= new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore, - Converters = new JsonConverter[] { new Newtonsoft.Json.Converters.StringEnumConverter() } - }; + { + NullValueHandling = NullValueHandling.Ignore, + Converters = [new Newtonsoft.Json.Converters.StringEnumConverter()] + }; _settings = settings; } @@ -57,33 +57,30 @@ public MessageAttributes GetMessageAttributes(string message) return new MessageAttributes(); } - var dict = new Dictionary(); + var attributes = new Dictionary(); - foreach (var prop in props) + foreach (var property in props) { - var propData = prop.Value; - if (propData == null) continue; - - var dataType = propData["Type"].ToString(); - var dataValue = propData["Value"].ToString(); + if (property.Value is not { } propertyValue) + { + continue; + } - var isString = dataType == "String"; + var dataType = propertyValue["Type"].ToString(); + var dataValue = propertyValue["Value"].ToString(); - var mav = new MessageAttributeValue - { - DataType = dataType, - StringValue = isString ? dataValue : null, - BinaryValue = !isString ? Convert.FromBase64String(dataValue) : null - }; - dict.Add(prop.Name, mav); + attributes.Add(property.Name, MessageAttributeParser.Parse(dataType, dataValue)); } - return new MessageAttributes(dict); + return new MessageAttributes(attributes); } public string GetMessageSubject(string sqsMessage) { - if (string.IsNullOrWhiteSpace(sqsMessage)) return string.Empty; + if (string.IsNullOrWhiteSpace(sqsMessage)) + { + return string.Empty; + } var body = JObject.Parse(sqsMessage); return body.Value("Subject") ?? string.Empty; diff --git a/src/JustSaying/Messaging/MessageSerialization/SystemTextJsonSerializer.cs b/src/JustSaying/Messaging/MessageSerialization/SystemTextJsonSerializer.cs index 9d124fcba..7a2d04daa 100644 --- a/src/JustSaying/Messaging/MessageSerialization/SystemTextJsonSerializer.cs +++ b/src/JustSaying/Messaging/MessageSerialization/SystemTextJsonSerializer.cs @@ -66,19 +66,12 @@ public MessageAttributes GetMessageAttributes(string message) } var attributes = new Dictionary(); - foreach(var obj in attributesElement.EnumerateObject()) + foreach (var property in attributesElement.EnumerateObject()) { - var dataType = obj.Value.GetProperty("Type").GetString(); - var dataValue = obj.Value.GetProperty("Value").GetString(); + var dataType = property.Value.GetProperty("Type").GetString(); + var dataValue = property.Value.GetProperty("Value").GetString(); - var isString = dataType == "String"; - - attributes.Add(obj.Name, new MessageAttributeValue() - { - DataType = dataType, - StringValue = isString ? dataValue : null, - BinaryValue = !isString ? Convert.FromBase64String(dataValue) : null - }); + attributes.Add(property.Name, MessageAttributeParser.Parse(dataType, dataValue)); } return new MessageAttributes(attributes); diff --git a/tests/JustSaying.UnitTests/Messaging/Serialization/Newtonsoft/WhenDeserializingMessage.cs b/tests/JustSaying.UnitTests/Messaging/Serialization/Newtonsoft/WhenDeserializingMessage.cs new file mode 100644 index 000000000..c1a7b0339 --- /dev/null +++ b/tests/JustSaying.UnitTests/Messaging/Serialization/Newtonsoft/WhenDeserializingMessage.cs @@ -0,0 +1,123 @@ +using JustSaying.Messaging.MessageSerialization; +using JustSaying.Models; + +namespace JustSaying.UnitTests.Messaging.Serialization.Newtonsoft; + +public class WhenDeserializingMessage : XBehaviourTest +{ + protected override MessageSerializationRegister CreateSystemUnderTest() => + new( + new NonGenericMessageSubjectProvider(), + new NewtonsoftSerializationFactory()); + + protected override void Given() + { + RecordAnyExceptionsThrown(); + } + + protected override void WhenAction() + { + SystemUnderTest.AddSerializer(); + } + + [Fact] + public void DeserializesMessage() + { + // Arrange + var body = + """ + { + "Subject": "CustomMessage", + "Message":"{\"Custom\":\"Text\"}" + } + """; + + // Act + var actual = SystemUnderTest.DeserializeMessage(body); + + // Assert + actual.ShouldNotBeNull(); + actual.Message.ShouldNotBeNull(); + actual.MessageAttributes.ShouldNotBeNull(); + + var message = actual.Message.ShouldBeOfType(); + + message.Custom.ShouldBe("Text"); + } + + [Fact] + public void DeserializesMessageWithMessageAttributes() + { + // Arrange + var body = + """ + { + "Subject": "CustomMessage", + "Message":"{\"Custom\":\"Text\"}", + "MessageAttributes": { + "Text": { + "Type": "String", + "Value": "foo" + }, + "Integer": { + "Type": "Number", + "Value": "42" + }, + "BinaryData": { + "Type": "Binary", + "Value": "SnVzdCBFYXQgVGFrZWF3YXkuY29t" + }, + "CustomBinaryData": { + "Type": "Binary.jet", + "Value": "SnVzdFNheWluZw==" + } + } + } + """; + + // Act + var actual = SystemUnderTest.DeserializeMessage(body); + + // Assert + actual.ShouldNotBeNull(); + actual.Message.ShouldNotBeNull(); + + var message = actual.Message.ShouldBeOfType(); + message.Custom.ShouldBe("Text"); + + actual.MessageAttributes.ShouldNotBeNull(); + + var attribute = actual.MessageAttributes.Get("Text"); + + attribute.ShouldNotBeNull(); + attribute.DataType.ShouldBe("String"); + attribute.StringValue.ShouldBe("foo"); + attribute.BinaryValue.ShouldBeNull(); + + attribute = actual.MessageAttributes.Get("Integer"); + + attribute.ShouldNotBeNull(); + attribute.DataType.ShouldBe("Number"); + attribute.StringValue.ShouldBe("42"); + attribute.BinaryValue.ShouldBeNull(); + + attribute = actual.MessageAttributes.Get("BinaryData"); + + attribute.ShouldNotBeNull(); + attribute.DataType.ShouldBe("Binary"); + attribute.StringValue.ShouldBeNull(); + attribute.BinaryValue.ShouldBe([.. "Just Eat Takeaway.com"u8]); + + attribute = actual.MessageAttributes.Get("CustomBinaryData"); + + attribute.ShouldNotBeNull(); + attribute.DataType.ShouldBe("Binary.jet"); + attribute.StringValue.ShouldBeNull(); + attribute.BinaryValue.ShouldBe([.. "JustSaying"u8]); + } + + private sealed class CustomMessage : Message + { + public string Custom { get; set; } + } +} diff --git a/tests/JustSaying.UnitTests/Messaging/Serialization/SystemTextJson/WhenDeserializingMessage.cs b/tests/JustSaying.UnitTests/Messaging/Serialization/SystemTextJson/WhenDeserializingMessage.cs new file mode 100644 index 000000000..b7258a55c --- /dev/null +++ b/tests/JustSaying.UnitTests/Messaging/Serialization/SystemTextJson/WhenDeserializingMessage.cs @@ -0,0 +1,123 @@ +using JustSaying.Messaging.MessageSerialization; +using JustSaying.Models; + +namespace JustSaying.UnitTests.Messaging.Serialization.SystemTextJson; + +public class WhenDeserializingMessage : XBehaviourTest +{ + protected override MessageSerializationRegister CreateSystemUnderTest() => + new( + new NonGenericMessageSubjectProvider(), + new SystemTextJsonSerializationFactory()); + + protected override void Given() + { + RecordAnyExceptionsThrown(); + } + + protected override void WhenAction() + { + SystemUnderTest.AddSerializer(); + } + + [Fact] + public void DeserializesMessage() + { + // Arrange + var body = + """ + { + "Subject": "CustomMessage", + "Message":"{\"Custom\":\"Text\"}" + } + """; + + // Act + var actual = SystemUnderTest.DeserializeMessage(body); + + // Assert + actual.ShouldNotBeNull(); + actual.Message.ShouldNotBeNull(); + actual.MessageAttributes.ShouldNotBeNull(); + + var message = actual.Message.ShouldBeOfType(); + + message.Custom.ShouldBe("Text"); + } + + [Fact] + public void DeserializesMessageWithMessageAttributes() + { + // Arrange + var body = + """ + { + "Subject": "CustomMessage", + "Message":"{\"Custom\":\"Text\"}", + "MessageAttributes": { + "Text": { + "Type": "String", + "Value": "foo" + }, + "Integer": { + "Type": "Number", + "Value": "42" + }, + "BinaryData": { + "Type": "Binary", + "Value": "SnVzdCBFYXQgVGFrZWF3YXkuY29t" + }, + "CustomBinaryData": { + "Type": "Binary.jet", + "Value": "SnVzdFNheWluZw==" + } + } + } + """; + + // Act + var actual = SystemUnderTest.DeserializeMessage(body); + + // Assert + actual.ShouldNotBeNull(); + actual.Message.ShouldNotBeNull(); + + var message = actual.Message.ShouldBeOfType(); + message.Custom.ShouldBe("Text"); + + actual.MessageAttributes.ShouldNotBeNull(); + + var attribute = actual.MessageAttributes.Get("Text"); + + attribute.ShouldNotBeNull(); + attribute.DataType.ShouldBe("String"); + attribute.StringValue.ShouldBe("foo"); + attribute.BinaryValue.ShouldBeNull(); + + attribute = actual.MessageAttributes.Get("Integer"); + + attribute.ShouldNotBeNull(); + attribute.DataType.ShouldBe("Number"); + attribute.StringValue.ShouldBe("42"); + attribute.BinaryValue.ShouldBeNull(); + + attribute = actual.MessageAttributes.Get("BinaryData"); + + attribute.ShouldNotBeNull(); + attribute.DataType.ShouldBe("Binary"); + attribute.StringValue.ShouldBeNull(); + attribute.BinaryValue.ShouldBe([.. "Just Eat Takeaway.com"u8]); + + attribute = actual.MessageAttributes.Get("CustomBinaryData"); + + attribute.ShouldNotBeNull(); + attribute.DataType.ShouldBe("Binary.jet"); + attribute.StringValue.ShouldBeNull(); + attribute.BinaryValue.ShouldBe([.. "JustSaying"u8]); + } + + private sealed class CustomMessage : Message + { + public string Custom { get; set; } + } +}