diff --git a/.autover/changes/56a61b2c-4302-4077-9dc6-3fc3bc60a998.json b/.autover/changes/56a61b2c-4302-4077-9dc6-3fc3bc60a998.json
new file mode 100644
index 00000000..4dba22cc
--- /dev/null
+++ b/.autover/changes/56a61b2c-4302-4077-9dc6-3fc3bc60a998.json
@@ -0,0 +1,11 @@
+{
+ "Projects": [
+ {
+ "Name": "AWS.Messaging",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Update MessageSerializer to store data as actual json. Fixes #168"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/AWS.Messaging/MessageEnvelope.cs b/src/AWS.Messaging/MessageEnvelope.cs
index 557acc7f..25a2db79 100644
--- a/src/AWS.Messaging/MessageEnvelope.cs
+++ b/src/AWS.Messaging/MessageEnvelope.cs
@@ -44,6 +44,12 @@ public abstract class MessageEnvelope
[JsonPropertyName("time")]
public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.MinValue;
+ ///
+ /// The data content type.
+ ///
+ [JsonPropertyName("datacontenttype")]
+ public string? DataContentType { get; set; }
+
///
/// This stores different metadata that is not modeled as a top-level property in MessageEnvelope class.
/// These entries will also be serialized as top-level properties when sending the message, which
diff --git a/src/AWS.Messaging/Serialization/EnvelopeSerializer.cs b/src/AWS.Messaging/Serialization/EnvelopeSerializer.cs
index 29749f43..d2fcc84d 100644
--- a/src/AWS.Messaging/Serialization/EnvelopeSerializer.cs
+++ b/src/AWS.Messaging/Serialization/EnvelopeSerializer.cs
@@ -1,13 +1,12 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Amazon.SQS.Model;
using AWS.Messaging.Configuration;
-using AWS.Messaging.Internal;
using AWS.Messaging.Serialization.Helpers;
+using AWS.Messaging.Internal;
using AWS.Messaging.Services;
using Microsoft.Extensions.Logging;
using AWS.Messaging.Serialization.Parsers;
@@ -103,10 +102,23 @@ public async ValueTask SerializeAsync(MessageEnvelope envelope)
["source"] = envelope.Source?.ToString(),
["specversion"] = envelope.Version,
["type"] = envelope.MessageTypeIdentifier,
- ["time"] = envelope.TimeStamp,
- ["data"] = _messageSerializer.Serialize(message)
+ ["time"] = envelope.TimeStamp
};
+ var messageSerializerResults = _messageSerializer.Serialize(message);
+
+ blob["datacontenttype"] = messageSerializerResults.ContentType;
+
+ if (IsJsonContentType(messageSerializerResults.ContentType))
+ {
+ blob["data"] = JsonNode.Parse(messageSerializerResults.Data);
+ }
+ else
+ {
+ blob["data"] = messageSerializerResults.Data;
+
+ }
+
// Write any Metadata as top-level keys
// This may be useful for any extensions defined in
// https://github.com/cloudevents/spec/tree/main/cloudevents/extensions
@@ -174,6 +186,34 @@ public async ValueTask ConvertToEnvelopeAsync(Message s
}
}
+ private bool IsJsonContentType(string? dataContentType)
+ {
+ if (string.IsNullOrWhiteSpace(dataContentType))
+ {
+ // If dataContentType is not specified, it should be treated as "application/json"
+ return true;
+ }
+
+ // Remove any parameters from the content type
+ var mediaType = dataContentType.Split(';')[0].Trim().ToLower();
+
+ // Check if the media type is "application/json"
+ if (mediaType == "application/json")
+ {
+ return true;
+ }
+
+ // Check if the media subtype is "json" or ends with "+json"
+ var parts = mediaType.Split('/');
+ if (parts.Length == 2)
+ {
+ var subtype = parts[1];
+ return subtype == "json" || subtype.EndsWith("+json");
+ }
+
+ return false;
+ }
+
private (MessageEnvelope Envelope, SubscriberMapping Mapping) DeserializeEnvelope(string envelopeString)
{
using var document = JsonDocument.Parse(envelopeString);
@@ -204,6 +244,7 @@ public async ValueTask ConvertToEnvelopeAsync(Message s
envelope.Version = JsonPropertyHelper.GetRequiredProperty(root, "specversion", element => element.GetString()!);
envelope.MessageTypeIdentifier = JsonPropertyHelper.GetRequiredProperty(root, "type", element => element.GetString()!);
envelope.TimeStamp = JsonPropertyHelper.GetRequiredProperty(root, "time", element => element.GetDateTimeOffset());
+ envelope.DataContentType = JsonPropertyHelper.GetStringProperty(root, "datacontenttype");
// Handle metadata - copy any properties that aren't standard envelope properties
foreach (var property in root.EnumerateObject())
@@ -215,7 +256,10 @@ public async ValueTask ConvertToEnvelopeAsync(Message s
}
// Deserialize the message content using the custom serializer
- var dataContent = JsonPropertyHelper.GetRequiredProperty(root, "data", element => element.GetString()!);
+ var dataContent = JsonPropertyHelper.GetRequiredProperty(root, "data", element =>
+ IsJsonContentType(envelope.DataContentType)
+ ? element.GetRawText()
+ : element.GetString()!);
var message = _messageSerializer.Deserialize(dataContent, subscriberMapping.MessageType);
envelope.SetMessage(message);
diff --git a/src/AWS.Messaging/Serialization/IMessageSerializer.cs b/src/AWS.Messaging/Serialization/IMessageSerializer.cs
index 93f870b6..11e56bae 100644
--- a/src/AWS.Messaging/Serialization/IMessageSerializer.cs
+++ b/src/AWS.Messaging/Serialization/IMessageSerializer.cs
@@ -9,10 +9,11 @@ namespace AWS.Messaging.Serialization;
public interface IMessageSerializer
{
///
- /// Serializes the .NET message object into a string.
+ /// Serializes the .NET message object into a string and specifies the content type of the serialized data.
///
/// The .NET object that will be serialized.
- string Serialize(object message);
+ /// A containing the serialized string and its content type.
+ MessageSerializerResults Serialize(object message);
///
/// Deserializes the raw string message into the .NET type.
diff --git a/src/AWS.Messaging/Serialization/MessageSerializer.cs b/src/AWS.Messaging/Serialization/MessageSerializer.cs
index 6084e1d9..085cfce6 100644
--- a/src/AWS.Messaging/Serialization/MessageSerializer.cs
+++ b/src/AWS.Messaging/Serialization/MessageSerializer.cs
@@ -73,7 +73,7 @@ public object Deserialize(string message, Type deserializedType)
Justification = "Consumers relying on trimming would have been required to call the AddAWSMessageBus overload that takes in JsonSerializerContext that will be used here to avoid the call that requires unreferenced code.")]
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050",
Justification = "Consumers relying on trimming would have been required to call the AddAWSMessageBus overload that takes in JsonSerializerContext that will be used here to avoid the call that requires unreferenced code.")]
- public string Serialize(object message)
+ public MessageSerializerResults Serialize(object message)
{
try
{
@@ -100,7 +100,7 @@ public string Serialize(object message)
_logger.LogTrace("Serialized the message object to a raw string with a content length of {ContentLength}.", jsonString.Length);
}
- return jsonString;
+ return new MessageSerializerResults(jsonString, "application/json");
}
catch (JsonException) when (!_messageConfiguration.LogMessageContent)
{
diff --git a/src/AWS.Messaging/Serialization/MessageSerializerResults.cs b/src/AWS.Messaging/Serialization/MessageSerializerResults.cs
new file mode 100644
index 00000000..510fb215
--- /dev/null
+++ b/src/AWS.Messaging/Serialization/MessageSerializerResults.cs
@@ -0,0 +1,34 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+namespace AWS.Messaging.Serialization;
+
+///
+/// Represents the results of a message serialization operation, containing both the serialized data
+/// and its corresponding content type.
+///
+public class MessageSerializerResults
+{
+ ///
+ /// Initializes a new instance of the MessageSerializerResults class.
+ ///
+ /// The serialized message data as a string.
+ /// The MIME content type of the serialized data.
+ public MessageSerializerResults(string data, string contentType)
+ {
+ Data = data;
+ ContentType = contentType;
+ }
+
+ ///
+ /// Gets or sets the MIME content type of the serialized data.
+ /// Common values include "application/json" or "application/xml".
+ ///
+ public string ContentType { get; }
+
+ ///
+ /// Gets or sets the serialized message data as a string.
+ /// This contains the actual serialized content of the message.
+ ///
+ public string Data { get; }
+}
diff --git a/test/AWS.Messaging.IntegrationTests/EventBridgePublisherTests.cs b/test/AWS.Messaging.IntegrationTests/EventBridgePublisherTests.cs
index 3e9f9196..ae625b50 100644
--- a/test/AWS.Messaging.IntegrationTests/EventBridgePublisherTests.cs
+++ b/test/AWS.Messaging.IntegrationTests/EventBridgePublisherTests.cs
@@ -12,6 +12,8 @@
using Amazon.SQS.Model;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
+using AWS.Messaging.IntegrationTests.Handlers;
+using AWS.Messaging.Serialization;
namespace AWS.Messaging.IntegrationTests;
@@ -105,6 +107,7 @@ await _eventBridgeClient.PutTargetsAsync(new PutTargetsRequest
{
builder.AddEventBridgePublisher(_eventBusArn);
builder.AddMessageSource("/aws/messaging");
+ builder.AddMessageHandler();
});
_serviceProvider = serviceCollection.BuildServiceProvider();
}
@@ -126,23 +129,23 @@ await publisher.PublishAsync(new ChatMessage
var receiveMessageResponse = await _sqsClient.ReceiveMessageAsync(_sqsQueueUrl);
var message = Assert.Single(receiveMessageResponse.Messages);
- // EventBridge adds an external envelope which we need to strip away
- var eventBridgeEnvelope = JsonSerializer.Deserialize(message.Body);
- Assert.NotNull(eventBridgeEnvelope);
+ var envelopeSerializer = _serviceProvider.GetRequiredService();
- Assert.NotNull(eventBridgeEnvelope.Detail);
- var envelope = eventBridgeEnvelope.Detail;
+ // Use the EnvelopeSerializer to convert the message
+ var result = await envelopeSerializer.ConvertToEnvelopeAsync(message);
+ var envelope = result.Envelope as MessageEnvelope;
+
+ Assert.NotNull(envelope);
Assert.False(string.IsNullOrEmpty(envelope.Id));
Assert.Equal("/aws/messaging", envelope.Source.ToString());
Assert.True(envelope.TimeStamp > publishStartTime);
Assert.True(envelope.TimeStamp < publishEndTime);
+ Assert.Equal(typeof(ChatMessage).ToString(), envelope.MessageTypeIdentifier);
- var messageType = Type.GetType(eventBridgeEnvelope.Detail.MessageTypeIdentifier);
- Assert.NotNull(messageType);
-
- var chatMessageObject = JsonSerializer.Deserialize(eventBridgeEnvelope.Detail.Message, messageType);
- var chatMessage = Assert.IsType(chatMessageObject);
- Assert.Equal("Test1", chatMessage.MessageDescription);
+ var chatMessage = envelope.Message;
+ Assert.NotNull(chatMessage);
+ Assert.IsType(chatMessage);
+ Assert.Equal("Test1", chatMessage.MessageDescription);;
}
public async Task DisposeAsync()
diff --git a/test/AWS.Messaging.IntegrationTests/SNSPublisherTests.cs b/test/AWS.Messaging.IntegrationTests/SNSPublisherTests.cs
index ed80dc78..df74c4f6 100644
--- a/test/AWS.Messaging.IntegrationTests/SNSPublisherTests.cs
+++ b/test/AWS.Messaging.IntegrationTests/SNSPublisherTests.cs
@@ -8,6 +8,8 @@
using System.Text.Json;
using Amazon.SimpleNotificationService;
using Amazon.SimpleNotificationService.Model;
+using AWS.Messaging.IntegrationTests.Handlers;
+using AWS.Messaging.Serialization;
namespace AWS.Messaging.IntegrationTests;
@@ -45,6 +47,7 @@ public async Task InitializeAsync()
{
builder.AddSNSPublisher(_snsTopicArn);
builder.AddMessageSource("/aws/messaging");
+ builder.AddMessageHandler();
});
_serviceProvider = serviceCollection.BuildServiceProvider();
}
@@ -70,18 +73,23 @@ await publisher.PublishAsync(new ChatMessage
var snsEnvelope = JsonSerializer.Deserialize(message.Body);
Assert.NotNull(snsEnvelope);
- var envelope = JsonSerializer.Deserialize>(snsEnvelope.Message);
+ // Get the EnvelopeSerializer from the service provider
+ var envelopeSerializer = _serviceProvider.GetRequiredService();
+
+ // Use the EnvelopeSerializer to convert the message
+ var result = await envelopeSerializer.ConvertToEnvelopeAsync(message);
+ var envelope = result.Envelope as MessageEnvelope;
+
Assert.NotNull(envelope);
Assert.False(string.IsNullOrEmpty(envelope.Id));
Assert.Equal("/aws/messaging", envelope.Source.ToString());
Assert.True(envelope.TimeStamp > publishStartTime);
Assert.True(envelope.TimeStamp < publishEndTime);
+ Assert.Equal(typeof(ChatMessage).ToString(), envelope.MessageTypeIdentifier);
- var messageType = Type.GetType(envelope.MessageTypeIdentifier);
- Assert.NotNull(messageType);
-
- var chatMessageObject = JsonSerializer.Deserialize(envelope.Message, messageType);
- var chatMessage = Assert.IsType(chatMessageObject);
+ var chatMessage = envelope.Message;
+ Assert.NotNull(chatMessage);
+ Assert.IsType(chatMessage);
Assert.Equal("Test1", chatMessage.MessageDescription);
}
diff --git a/test/AWS.Messaging.IntegrationTests/SQSPublisherTests.cs b/test/AWS.Messaging.IntegrationTests/SQSPublisherTests.cs
index 04baade8..9792f50f 100644
--- a/test/AWS.Messaging.IntegrationTests/SQSPublisherTests.cs
+++ b/test/AWS.Messaging.IntegrationTests/SQSPublisherTests.cs
@@ -6,6 +6,8 @@
using Microsoft.Extensions.DependencyInjection;
using AWS.Messaging.IntegrationTests.Models;
using System.Text.Json;
+using AWS.Messaging.IntegrationTests.Handlers;
+using AWS.Messaging.Serialization;
namespace AWS.Messaging.IntegrationTests;
@@ -33,6 +35,8 @@ public async Task InitializeAsync()
{
builder.AddSQSPublisher(_sqsQueueUrl);
builder.AddMessageSource("/aws/messaging");
+ builder.AddMessageHandler();
+
});
_serviceProvider = serviceCollection.BuildServiceProvider();
}
@@ -54,19 +58,27 @@ await publisher.PublishAsync(new ChatMessage
var receiveMessageResponse = await _sqsClient.ReceiveMessageAsync(_sqsQueueUrl);
var message = Assert.Single(receiveMessageResponse.Messages);
- var envelope = JsonSerializer.Deserialize>(message.Body);
+ // Get the EnvelopeSerializer from the service provider
+ var envelopeSerializer = _serviceProvider.GetRequiredService();
+
+ // Use the EnvelopeSerializer to convert the message
+ var result = await envelopeSerializer.ConvertToEnvelopeAsync(message);
+ var envelope = result.Envelope as MessageEnvelope;
+
Assert.NotNull(envelope);
Assert.False(string.IsNullOrEmpty(envelope.Id));
Assert.Equal("/aws/messaging", envelope.Source.ToString());
- Assert.True(envelope.TimeStamp > publishStartTime);
+ Assert.True(envelope.TimeStamp > publishStartTime);
Assert.True(envelope.TimeStamp < publishEndTime);
- var messageType = Type.GetType(envelope.MessageTypeIdentifier);
- Assert.NotNull(messageType);
- var chatMessageObject = JsonSerializer.Deserialize(envelope.Message, messageType);
- var chatMessage = Assert.IsType(chatMessageObject);
+ Assert.Equal(typeof(ChatMessage).ToString(), envelope.MessageTypeIdentifier);
+
+ var chatMessage = envelope.Message;
+ Assert.NotNull(chatMessage);
+ Assert.IsType(chatMessage);
Assert.Equal("Test1", chatMessage.MessageDescription);
}
+
public async Task DisposeAsync()
{
try
diff --git a/test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs b/test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs
index 0984d340..ec134dff 100644
--- a/test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs
+++ b/test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs
@@ -44,6 +44,16 @@ public Task HandleAsync(MessageEnvelope messa
}
}
+public class PlainTextHandler : IMessageHandler
+{
+ public Task HandleAsync(MessageEnvelope messageEnvelope, CancellationToken token = default)
+ {
+ // Simple handler implementation for test purposes
+ return Task.FromResult(MessageProcessStatus.Success());
+ }
+}
+
+
///
/// Implements handling for mutiple message types
///
diff --git a/test/AWS.Messaging.UnitTests/SerializationTests/EnvelopeSerializerTests.cs b/test/AWS.Messaging.UnitTests/SerializationTests/EnvelopeSerializerTests.cs
index 22a79d7a..d65164cb 100644
--- a/test/AWS.Messaging.UnitTests/SerializationTests/EnvelopeSerializerTests.cs
+++ b/test/AWS.Messaging.UnitTests/SerializationTests/EnvelopeSerializerTests.cs
@@ -35,6 +35,7 @@ public EnvelopeSerializerTests()
{
builder.AddSQSPublisher("sqsQueueUrl", "addressInfo");
builder.AddMessageHandler("addressInfo");
+ builder.AddMessageHandler("plaintext");
builder.AddMessageSource("/aws/messaging");
});
@@ -119,7 +120,7 @@ public async Task SerializeEnvelope()
// ASSERT
// The \u0022 corresponds to quotation mark (")
- var expectedBlob = "{\"id\":\"id-123\",\"source\":\"/backend/service\",\"specversion\":\"1.0\",\"type\":\"addressInfo\",\"time\":\"2000-12-05T10:30:55+00:00\",\"data\":\"{\\u0022Unit\\u0022:123,\\u0022Street\\u0022:\\u0022Prince St\\u0022,\\u0022ZipCode\\u0022:\\u002200001\\u0022}\"}";
+ var expectedBlob = "{\"id\":\"id-123\",\"source\":\"/backend/service\",\"specversion\":\"1.0\",\"type\":\"addressInfo\",\"time\":\"2000-12-05T10:30:55+00:00\",\"datacontenttype\":\"application/json\",\"data\":{\"Unit\":123,\"Street\":\"Prince St\",\"ZipCode\":\"00001\"}}";
Assert.Equal(expectedBlob, jsonBlob);
}
@@ -272,19 +273,19 @@ public async Task ConvertToEnvelope_With_EventBridgeOuterEnvelope_In_SQSMessageB
var serviceProvider = _serviceCollection.BuildServiceProvider();
var envelopeSerializer = serviceProvider.GetRequiredService();
- var innerMessageEnvelope = new MessageEnvelope
+ var innerMessageEnvelope = new MessageEnvelope
{
Id = "66659d05-e4ff-462f-81c4-09e560e66a5c",
Source = new Uri("/aws/messaging", UriKind.Relative),
Version = "1.0",
MessageTypeIdentifier = "addressInfo",
TimeStamp = _testdate,
- Message = JsonSerializer.Serialize(new AddressInfo
+ Message = new AddressInfo
{
Street = "Prince St",
Unit = 123,
ZipCode = "00001"
- })
+ }
};
var outerMessageEnvelope = new Dictionary
@@ -397,7 +398,7 @@ public async Task SerializationCallbacks_AreCorrectlyInvoked()
var serializedMessage = await envelopeSerializer.SerializeAsync(messageEnvelope);
// ASSERT - Check expected base 64 encoded string
- var expectedserializedMessage = "eyJpZCI6IjEyMyIsInNvdXJjZSI6Ii9hd3MvbWVzc2FnaW5nIiwic3BlY3ZlcnNpb24iOiIxLjAiLCJ0eXBlIjoiYWRkcmVzc0luZm8iLCJ0aW1lIjoiMjAwMC0xMi0wNVQxMDozMDo1NSswMDowMCIsImRhdGEiOiJ7XHUwMDIyVW5pdFx1MDAyMjoxMjMsXHUwMDIyU3RyZWV0XHUwMDIyOlx1MDAyMlByaW5jZSBTdFx1MDAyMixcdTAwMjJaaXBDb2RlXHUwMDIyOlx1MDAyMjAwMDAxXHUwMDIyfSIsIklzLURlbGl2ZXJlZCI6ZmFsc2V9";
+ var expectedserializedMessage = "eyJpZCI6IjEyMyIsInNvdXJjZSI6Ii9hd3MvbWVzc2FnaW5nIiwic3BlY3ZlcnNpb24iOiIxLjAiLCJ0eXBlIjoiYWRkcmVzc0luZm8iLCJ0aW1lIjoiMjAwMC0xMi0wNVQxMDozMDo1NSswMDowMCIsImRhdGFjb250ZW50dHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJkYXRhIjp7IlVuaXQiOjEyMywiU3RyZWV0IjoiUHJpbmNlIFN0IiwiWmlwQ29kZSI6IjAwMDAxIn0sIklzLURlbGl2ZXJlZCI6ZmFsc2V9";
Assert.Equal(expectedserializedMessage, serializedMessage);
// ACT - Convert To Envelope from base 64 Encoded Message
@@ -452,6 +453,15 @@ public async Task SerializeAsync_DataMessageLogging_NoError(bool dataMessageLogg
}
};
+ var serializedContent = JsonSerializer.Serialize(messageEnvelope.Message);
+ var messageSerializeResults = new MessageSerializerResults(serializedContent, "application/json");
+
+
+ // Mock the serializer to return a specific string
+ messageSerializer
+ .Setup(x => x.Serialize(It.IsAny