Skip to content

Commit

Permalink
Extend message attribute handling
Browse files Browse the repository at this point in the history
Handle message attributes of type `Number` or with a custom suffix label.
Resolves #1556.
  • Loading branch information
martincostello committed Nov 6, 2024
1 parent 56b8877 commit 9482817
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -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
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -57,33 +57,30 @@ public MessageAttributes GetMessageAttributes(string message)
return new MessageAttributes();
}

var dict = new Dictionary<string, MessageAttributeValue>();
var attributes = new Dictionary<string, MessageAttributeValue>();

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<string>("Subject") ?? string.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,12 @@ public MessageAttributes GetMessageAttributes(string message)
}

var attributes = new Dictionary<string, MessageAttributeValue>();
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using JustSaying.Messaging.MessageSerialization;
using JustSaying.Models;

namespace JustSaying.UnitTests.Messaging.Serialization.Newtonsoft;

public class WhenDeserializingMessage : XBehaviourTest<MessageSerializationRegister>
{
protected override MessageSerializationRegister CreateSystemUnderTest() =>
new(
new NonGenericMessageSubjectProvider(),
new NewtonsoftSerializationFactory());

protected override void Given()
{
RecordAnyExceptionsThrown();
}

protected override void WhenAction()
{
SystemUnderTest.AddSerializer<CustomMessage>();
}

[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<CustomMessage>();

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<CustomMessage>();
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; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using JustSaying.Messaging.MessageSerialization;
using JustSaying.Models;

namespace JustSaying.UnitTests.Messaging.Serialization.SystemTextJson;

public class WhenDeserializingMessage : XBehaviourTest<MessageSerializationRegister>
{
protected override MessageSerializationRegister CreateSystemUnderTest() =>
new(
new NonGenericMessageSubjectProvider(),
new SystemTextJsonSerializationFactory());

protected override void Given()
{
RecordAnyExceptionsThrown();
}

protected override void WhenAction()
{
SystemUnderTest.AddSerializer<CustomMessage>();
}

[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<CustomMessage>();

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<CustomMessage>();
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; }
}
}

0 comments on commit 9482817

Please sign in to comment.