Skip to content

Commit

Permalink
Implement public JObject conversions in JsonEventFormatter
Browse files Browse the repository at this point in the history
The method names are consistent with the ones in ProtobufEventFormatter.
Currently batch conversion is not supported, but we can add that later if requested.

Signed-off-by: Jon Skeet <[email protected]>
  • Loading branch information
jskeet committed Aug 17, 2022
1 parent aa7429d commit df81c7b
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 7 deletions.
34 changes: 27 additions & 7 deletions src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public override async Task<CloudEvent> DecodeStructuredModeMessageAsync(Stream b

var jsonReader = CreateJsonReader(body, MimeUtilities.GetEncoding(contentType));
var jObject = await JObject.LoadAsync(jsonReader).ConfigureAwait(false);
return DecodeJObject(jObject, extensionAttributes);
return DecodeJObject(jObject, extensionAttributes, nameof(body));
}

/// <inheritdoc />
Expand All @@ -146,7 +146,7 @@ public override CloudEvent DecodeStructuredModeMessage(Stream body, ContentType?

var jsonReader = CreateJsonReader(body, MimeUtilities.GetEncoding(contentType));
var jObject = JObject.Load(jsonReader);
return DecodeJObject(jObject, extensionAttributes);
return DecodeJObject(jObject, extensionAttributes, nameof(body));
}

/// <inheritdoc />
Expand Down Expand Up @@ -177,14 +177,23 @@ public override IReadOnlyList<CloudEvent> DecodeBatchModeMessage(Stream body, Co
public override IReadOnlyList<CloudEvent> DecodeBatchModeMessage(ReadOnlyMemory<byte> body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes) =>
DecodeBatchModeMessage(BinaryDataUtilities.AsStream(body), contentType, extensionAttributes);

/// <summary>
/// Converts the given <see cref="JObject"/> into a <see cref="CloudEvent"/>.
/// </summary>
/// <param name="jObject">The JSON representation of a CloudEvent. Must not be null.</param>
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
/// <returns>The SDK representation of the CloudEvent.</returns>
public CloudEvent ConvertFromJObject(JObject jObject, IEnumerable<CloudEventAttribute>? extensionAttributes) =>
DecodeJObject(Validation.CheckNotNull(jObject, nameof(jObject)), extensionAttributes, nameof(jObject));

private IReadOnlyList<CloudEvent> DecodeJArray(JArray jArray, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
{
List<CloudEvent> events = new List<CloudEvent>(jArray.Count);
foreach (var token in jArray)
{
if (token is JObject obj)
{
events.Add(DecodeJObject(obj, extensionAttributes));
events.Add(DecodeJObject(obj, extensionAttributes, paramName));
}
else
{
Expand All @@ -194,7 +203,7 @@ private IReadOnlyList<CloudEvent> DecodeJArray(JArray jArray, IEnumerable<CloudE
return events;
}

private CloudEvent DecodeJObject(JObject jObject, IEnumerable<CloudEventAttribute>? extensionAttributes)
private CloudEvent DecodeJObject(JObject jObject, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
{
if (!jObject.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var specVersionToken)
|| specVersionToken.Type != JTokenType.String)
Expand All @@ -207,9 +216,7 @@ private CloudEvent DecodeJObject(JObject jObject, IEnumerable<CloudEventAttribut
var cloudEvent = new CloudEvent(specVersion, extensionAttributes);
PopulateAttributesFromStructuredEvent(cloudEvent, jObject);
PopulateDataFromStructuredEvent(cloudEvent, jObject);
// "data" is always the parameter from the public method. It's annoying not to be able to use
// nameof here, but this will give the appropriate result.
return Validation.CheckCloudEventArgument(cloudEvent, "data");
return Validation.CheckCloudEventArgument(cloudEvent, paramName);
}

private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JObject jObject)
Expand Down Expand Up @@ -395,6 +402,19 @@ public override ReadOnlyMemory<byte> EncodeStructuredModeMessage(CloudEvent clou
return stream.ToArray();
}

/// <summary>
/// Converts the given <see cref="CloudEvent"/> to a <see cref="JObject"/> containing the structured mode JSON format representation
/// of the event.
/// </summary>
/// <param name="cloudEvent">The event to convert. Must not be null.</param>
/// <returns>A <see cref="JObject"/> containing the structured mode JSON format representation of the event.</returns>
public JObject ConvertToJObject(CloudEvent cloudEvent)
{
var writer = new JTokenWriter();
WriteCloudEventForBatchOrStructuredMode(writer, cloudEvent);
return (JObject) writer.Token!;
}

/// <inheritdoc />
public override ReadOnlyMemory<byte> EncodeBatchModeMessage(IEnumerable<CloudEvent> cloudEvents, out ContentType contentType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,55 @@ public void EncodeStructured_IndentationSettings()
Assert.Equal(expected, json);
}

// Effectively smoke tests for LINQ to JSON conversions; these piggy-back on the same implementation
// as the rest of the code, so we don't need to test exhaustively.

[Fact]
public void ConvertToJObject()
{
var cloudEvent = new CloudEvent
{
Data = SampleBinaryData
}.PopulateRequiredAttributes();

JObject obj = new JsonEventFormatter().ConvertToJObject(cloudEvent);
var asserter = new JTokenAsserter
{
{ "data_base64", JTokenType.String, SampleBinaryDataBase64 },
{ "id", JTokenType.String, "test-id" },
{ "source", JTokenType.String, "//test" },
{ "specversion", JTokenType.String, "1.0" },
{ "type", JTokenType.String, "test-type" },
};
asserter.AssertProperties(obj, assertCount: true);
}

[Fact]
public void ConvertFromJObject()
{
var obj = new JObject
{
["specversion"] = "1.0",
["type"] = "test-type",
["id"] = "test-id",
["data"] = "text", // Just so that it's reasonable to have a DataContentType,
["datacontenttype"] = "text/plain",
["dataschema"] = "https://data-schema",
["subject"] = "event-subject",
["source"] = "//event-source",
["time"] = SampleTimestampText
};
var cloudEvent = new JsonEventFormatter().ConvertFromJObject(obj, extensionAttributes: null);
Assert.Equal(CloudEventsSpecVersion.V1_0, cloudEvent.SpecVersion);
Assert.Equal("test-type", cloudEvent.Type);
Assert.Equal("test-id", cloudEvent.Id);
Assert.Equal("text/plain", cloudEvent.DataContentType);
Assert.Equal(new Uri("https://data-schema"), cloudEvent.DataSchema);
Assert.Equal("event-subject", cloudEvent.Subject);
Assert.Equal(new Uri("//event-source", UriKind.RelativeOrAbsolute), cloudEvent.Source);
AssertTimestampsEqual(SampleTimestamp, cloudEvent.Time);
}

// Utility methods
private static object? DecodeBinaryModeEventData(byte[] bytes, string contentType)
{
Expand Down

0 comments on commit df81c7b

Please sign in to comment.