Skip to content

Commit c76f866

Browse files
committed
new edits for binary data handling
remove binary logic update SerializeEnvelope test add field to message envelope instead all tests pass add tests update tests refactor remove unused code
1 parent 6af4726 commit c76f866

File tree

5 files changed

+388
-34
lines changed

5 files changed

+388
-34
lines changed

src/AWS.Messaging/AWS.Messaging.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@
4848
<None Include="..\..\README.md" Pack="true" PackagePath="" />
4949
</ItemGroup>
5050

51-
</Project>
51+
</Project>

src/AWS.Messaging/MessageEnvelope.cs

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public abstract class MessageEnvelope
4444
[JsonPropertyName("time")]
4545
public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.MinValue;
4646

47+
/// <summary>
48+
/// The data content type.
49+
/// </summary>
50+
[JsonPropertyName("datacontenttype")]
51+
public string? DataContentType { get; set; } = "application/json";
52+
4753
/// <summary>
4854
/// This stores different metadata that is not modeled as a top-level property in MessageEnvelope class.
4955
/// These entries will also be serialized as top-level properties when sending the message, which

src/AWS.Messaging/Serialization/EnvelopeSerializer.cs

+128-11
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ public async ValueTask<MessageEnvelope<T>> CreateEnvelopeAsync<T>(T message)
6767
Version = CLOUD_EVENT_SPEC_VERSION,
6868
MessageTypeIdentifier = publisherMapping.MessageTypeIdentifier,
6969
TimeStamp = timeStamp,
70-
Message = message
70+
Message = message,
71+
// DataContentType = "" // TODO
7172
};
7273
}
7374

@@ -91,10 +92,12 @@ public async ValueTask<string> SerializeAsync<T>(MessageEnvelope<T> envelope)
9192
["source"] = envelope.Source?.ToString(),
9293
["specversion"] = envelope.Version,
9394
["type"] = envelope.MessageTypeIdentifier,
94-
["time"] = envelope.TimeStamp,
95-
["data"] = _messageSerializer.Serialize(message)
95+
["time"] = envelope.TimeStamp
9696
};
9797

98+
// See https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/json-format.md#31-handling-of-data for more details.
99+
SerializeData(message, blob, envelope.DataContentType);
100+
98101
// Write any Metadata as top-level keys
99102
// This may be useful for any extensions defined in
100103
// https://github.com/cloudevents/spec/tree/main/cloudevents/extensions
@@ -107,17 +110,17 @@ public async ValueTask<string> SerializeAsync<T>(MessageEnvelope<T> envelope)
107110
}
108111

109112
var jsonString = blob.ToJsonString();
110-
var serializedMessage = await InvokePostSerializationCallback(jsonString);
113+
var finalSerializedMessage = await InvokePostSerializationCallback(jsonString);
111114

112115
if (_messageConfiguration.LogMessageContent)
113116
{
114-
_logger.LogTrace("Serialized the MessageEnvelope object as the following raw string:\n{SerializedMessage}", serializedMessage);
117+
_logger.LogTrace("Serialized the MessageEnvelope object as the following raw string:\n{SerializedMessage}", finalSerializedMessage );
115118
}
116119
else
117120
{
118121
_logger.LogTrace("Serialized the MessageEnvelope object to a raw string");
119122
}
120-
return serializedMessage;
123+
return finalSerializedMessage ;
121124
}
122125
catch (JsonException) when (!_messageConfiguration.LogMessageContent)
123126
{
@@ -131,6 +134,16 @@ public async ValueTask<string> SerializeAsync<T>(MessageEnvelope<T> envelope)
131134
}
132135
}
133136

137+
private string ExtractDataContent(JsonElement dataElement, string? dataContentType)
138+
{
139+
return IsJsonContentType(dataContentType)
140+
? dataElement.ValueKind == JsonValueKind.String
141+
? dataElement.GetString()!
142+
: dataElement.GetRawText()
143+
: dataElement.GetString()
144+
?? throw new InvalidDataException("Data must be a string for non-JSON content type");
145+
}
146+
134147
/// <inheritdoc/>
135148
public async ValueTask<ConvertToEnvelopeResult> ConvertToEnvelopeAsync(Message sqsMessage)
136149
{
@@ -143,6 +156,7 @@ public async ValueTask<ConvertToEnvelopeResult> ConvertToEnvelopeAsync(Message s
143156
var subscriberMapping = GetAndValidateSubscriberMapping(parsedResult.Envelope.MessageTypeIdentifier);
144157
var deserializedMessage = DeserializeDataContent(
145158
parsedResult.Envelope.Message!,
159+
parsedResult.Envelope.DataContentType,
146160
subscriberMapping.MessageType);
147161

148162
// Create and populate final envelope
@@ -174,11 +188,35 @@ public async ValueTask<ConvertToEnvelopeResult> ConvertToEnvelopeAsync(Message s
174188
private async Task<ParsedEnvelopeResult> ParseMessageEnvelope(Message sqsMessage)
175189
{
176190
sqsMessage.Body = await InvokePreDeserializationCallback(sqsMessage.Body);
177-
var messageEnvelopeConfiguration = GetMessageEnvelopeConfiguration(sqsMessage);
178-
var intermediateEnvelope = JsonSerializer.Deserialize<MessageEnvelope<string>>(messageEnvelopeConfiguration.MessageEnvelopeBody!)!;
191+
var config = GetMessageEnvelopeConfiguration(sqsMessage);
192+
193+
using var document = JsonDocument.Parse(config.MessageEnvelopeBody!);
194+
var root = document.RootElement;
195+
196+
if (!root.TryGetProperty("data", out var dataElement))
197+
{
198+
throw new InvalidDataException("Message envelope is missing required 'data' field");
199+
}
200+
201+
string? dataContentType = root.TryGetProperty("datacontenttype", out var contentTypeElement)
202+
? contentTypeElement.GetString()
203+
: "application/json";
204+
205+
string dataContent = ExtractDataContent(dataElement, dataContentType);
206+
207+
// Create intermediate envelope with all properties
208+
var envelopeJson = new JsonObject();
209+
foreach (var property in root.EnumerateObject())
210+
{
211+
envelopeJson[property.Name] = property.Name == "data"
212+
? JsonValue.Create(dataContent)
213+
: JsonNode.Parse(property.Value.GetRawText());
214+
}
215+
216+
var intermediateEnvelope = JsonSerializer.Deserialize<MessageEnvelope<string>>(envelopeJson.ToJsonString())!;
179217
ValidateMessageEnvelope(intermediateEnvelope);
180218

181-
return new ParsedEnvelopeResult(intermediateEnvelope, messageEnvelopeConfiguration);
219+
return new ParsedEnvelopeResult(intermediateEnvelope, config);
182220
}
183221

184222
private MessageEnvelope CreateFinalEnvelope(
@@ -204,6 +242,7 @@ private MessageEnvelope CreateFinalEnvelope(
204242
finalEnvelope.MessageTypeIdentifier = intermediateEnvelope.MessageTypeIdentifier;
205243
finalEnvelope.TimeStamp = intermediateEnvelope.TimeStamp;
206244
finalEnvelope.Metadata = intermediateEnvelope.Metadata;
245+
finalEnvelope.DataContentType = intermediateEnvelope.DataContentType;
207246
finalEnvelope.SQSMetadata = config.SQSMetadata;
208247
finalEnvelope.SNSMetadata = config.SNSMetadata;
209248
finalEnvelope.EventBridgeMetadata = config.EventBridgeMetadata;
@@ -232,9 +271,87 @@ private SubscriberMapping GetAndValidateSubscriberMapping(string messageTypeIden
232271
return subscriberMapping;
233272
}
234273

235-
private object DeserializeDataContent(string dataContent, Type messageType)
274+
private object DeserializeDataContent(string dataContent, string? dataContentType, Type messageType)
236275
{
237-
return _messageSerializer.Deserialize(dataContent, messageType);
276+
if (IsJsonContentType(dataContentType))
277+
{
278+
return _messageSerializer.Deserialize(dataContent, messageType);
279+
}
280+
281+
if (messageType == typeof(string))
282+
{
283+
return dataContent;
284+
}
285+
286+
throw new InvalidOperationException(
287+
$"Cannot deserialize non-JSON content type {dataContentType} to type {messageType}");
288+
}
289+
290+
private void SerializeData<T>(T message, JsonObject blob, string? dataContentType)
291+
{
292+
if (message == null)
293+
{
294+
throw new ArgumentNullException("The underlying application message cannot be null");
295+
}
296+
297+
// Serialize the message
298+
var serializedMessage = _messageSerializer.Serialize(message);
299+
300+
// Determine if the serialized message is valid JSON
301+
// Wed do this because _messageSerializer is injected and there is no guarantee that it serializes to json.
302+
bool isJson = IsValidJson(serializedMessage);
303+
blob["datacontenttype"] = dataContentType;
304+
305+
if (IsJsonContentType(dataContentType))
306+
{
307+
if (isJson)
308+
{
309+
// If it's valid JSON, parse it to maintain structure
310+
blob["data"] = JsonNode.Parse(serializedMessage);
311+
}
312+
else
313+
{
314+
// If it's not valid JSON but content type indicates JSON,
315+
// log warning and store as string
316+
_logger.LogWarning("Data was serialized as non-JSON, but datacontenttype indicates JSON format. Storing as string.");
317+
blob["data"] = serializedMessage;
318+
}
319+
}
320+
else
321+
{
322+
// For non-JSON content types, store as string
323+
blob["data"] = serializedMessage;
324+
}
325+
}
326+
327+
private bool IsJsonContentType(string? contentType)
328+
{
329+
if (string.IsNullOrEmpty(contentType))
330+
{
331+
// If datacontenttype is unspecified, treat as application/json
332+
return true;
333+
}
334+
335+
// Strip any parameters (anything after ';')
336+
var mediaType = contentType.Split(';')[0].Trim().ToLowerInvariant();
337+
338+
return mediaType.EndsWith("/json") || // Matches */json
339+
(mediaType.Contains('/') && mediaType.EndsWith("+json")); // Matches */*+json
340+
}
341+
342+
private bool IsValidJson(string strInput)
343+
{
344+
if (string.IsNullOrWhiteSpace(strInput)) return false;
345+
346+
try
347+
{
348+
JsonDocument.Parse(strInput);
349+
return true;
350+
}
351+
catch (JsonException)
352+
{
353+
return false;
354+
}
238355
}
239356

240357
private void ValidateMessageEnvelope<T>(MessageEnvelope<T>? messageEnvelope)

test/AWS.Messaging.UnitTests/MessageHandlers/Handlers.cs

+8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public Task<MessageProcessStatus> HandleAsync(MessageEnvelope<AddressInfo> messa
2828
}
2929
}
3030

31+
public class PlainTextHandler : IMessageHandler<string>
32+
{
33+
public Task<MessageProcessStatus> HandleAsync(MessageEnvelope<string> messageEnvelope, CancellationToken token = default)
34+
{
35+
return Task.FromResult(MessageProcessStatus.Success());
36+
}
37+
}
38+
3139
public class ChatMessageHandlerWithDependencies : IMessageHandler<ChatMessage>
3240
{
3341
private readonly IDependentThing _thingDoer;

0 commit comments

Comments
 (0)