feat: Make the Kafka protocol bindings follow conventions
Signed-off-by: Jon Skeet <jonskeet@google.com>
This commit is contained in:
parent
450ba23e96
commit
17d943302a
|
@ -6,13 +6,15 @@ using CloudNative.CloudEvents.Extensions;
|
||||||
using Confluent.Kafka;
|
using Confluent.Kafka;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace CloudNative.CloudEvents.Kafka
|
namespace CloudNative.CloudEvents.Kafka
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods to convert between CloudEvents and Kafka messages.
|
||||||
|
/// </summary>
|
||||||
public static class KafkaClientExtensions
|
public static class KafkaClientExtensions
|
||||||
{
|
{
|
||||||
private const string KafkaHeaderPrefix = "ce_";
|
private const string KafkaHeaderPrefix = "ce_";
|
||||||
|
@ -26,13 +28,31 @@ namespace CloudNative.CloudEvents.Kafka
|
||||||
GetHeaderValue(message, SpecVersionKafkaHeader) is object ||
|
GetHeaderValue(message, SpecVersionKafkaHeader) is object ||
|
||||||
(ExtractContentType(message)?.StartsWith(CloudEvent.MediaType, StringComparison.InvariantCultureIgnoreCase) == true);
|
(ExtractContentType(message)?.StartsWith(CloudEvent.MediaType, StringComparison.InvariantCultureIgnoreCase) == true);
|
||||||
|
|
||||||
public static CloudEvent ToCloudEvent(this Message<string, byte[]> message,
|
|
||||||
CloudEventFormatter eventFormatter, params CloudEventAttribute[] extensionAttributes) =>
|
|
||||||
ToCloudEvent(message, eventFormatter, (IEnumerable<CloudEventAttribute>) extensionAttributes);
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts this Kafka message into a CloudEvent object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The Kafka message to convert. Must not be null.</param>
|
||||||
|
/// <param name="formatter">The event formatter to use to parse the CloudEvent. Must not be null.</param>
|
||||||
|
/// <param name="extensionAttributes">The extension attributes to use when parsing the CloudEvent. May be null.</param>
|
||||||
|
/// <returns>A reference to a validated CloudEvent instance.</returns>
|
||||||
public static CloudEvent ToCloudEvent(this Message<string, byte[]> message,
|
public static CloudEvent ToCloudEvent(this Message<string, byte[]> message,
|
||||||
CloudEventFormatter eventFormatter, IEnumerable<CloudEventAttribute> extensionAttributes)
|
CloudEventFormatter formatter, params CloudEventAttribute[] extensionAttributes) =>
|
||||||
|
ToCloudEvent(message, formatter, (IEnumerable<CloudEventAttribute>) extensionAttributes);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts this Kafka message into a CloudEvent object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The Kafka message to convert. Must not be null.</param>
|
||||||
|
/// <param name="formatter">The event formatter to use to parse the CloudEvent. Must not be null.</param>
|
||||||
|
/// <param name="extensionAttributes">The extension attributes to use when parsing the CloudEvent. May be null.</param>
|
||||||
|
/// <returns>A reference to a validated CloudEvent instance.</returns>
|
||||||
|
public static CloudEvent ToCloudEvent(this Message<string, byte[]> message,
|
||||||
|
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||||
{
|
{
|
||||||
|
message = message ?? throw new ArgumentNullException(nameof(message));
|
||||||
|
formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||||
|
|
||||||
if (!IsCloudEvent(message))
|
if (!IsCloudEvent(message))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
|
@ -45,7 +65,7 @@ namespace CloudNative.CloudEvents.Kafka
|
||||||
// Structured mode
|
// Structured mode
|
||||||
if (contentType?.StartsWith(CloudEvent.MediaType, StringComparison.InvariantCultureIgnoreCase) == true)
|
if (contentType?.StartsWith(CloudEvent.MediaType, StringComparison.InvariantCultureIgnoreCase) == true)
|
||||||
{
|
{
|
||||||
cloudEvent = eventFormatter.DecodeStructuredModeMessage(message.Value, new ContentType(contentType), extensionAttributes);
|
cloudEvent = formatter.DecodeStructuredModeMessage(message.Value, new ContentType(contentType), extensionAttributes);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -55,11 +75,8 @@ namespace CloudNative.CloudEvents.Kafka
|
||||||
throw new ArgumentException("Request is not a CloudEvent");
|
throw new ArgumentException("Request is not a CloudEvent");
|
||||||
}
|
}
|
||||||
string versionId = Encoding.UTF8.GetString(versionIdBytes);
|
string versionId = Encoding.UTF8.GetString(versionIdBytes);
|
||||||
CloudEventsSpecVersion version = CloudEventsSpecVersion.FromVersionId(versionId);
|
CloudEventsSpecVersion version = CloudEventsSpecVersion.FromVersionId(versionId)
|
||||||
if (version is null)
|
?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(message));
|
||||||
{
|
|
||||||
throw new ArgumentException($"Unsupported CloudEvents spec version '{versionId}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
cloudEvent = new CloudEvent(version, extensionAttributes)
|
cloudEvent = new CloudEvent(version, extensionAttributes)
|
||||||
{
|
{
|
||||||
|
@ -83,11 +100,11 @@ namespace CloudNative.CloudEvents.Kafka
|
||||||
|
|
||||||
cloudEvent.SetAttributeFromString(attributeName, attributeValue);
|
cloudEvent.SetAttributeFromString(attributeName, attributeValue);
|
||||||
}
|
}
|
||||||
eventFormatter.DecodeBinaryModeEventData(message.Value, cloudEvent);
|
formatter.DecodeBinaryModeEventData(message.Value, cloudEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
InitPartitioningKey(message, cloudEvent);
|
InitPartitioningKey(message, cloudEvent);
|
||||||
return cloudEvent;
|
return cloudEvent.ValidateForConversion(nameof(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ExtractContentType(Message<string, byte[]> message)
|
private static string ExtractContentType(Message<string, byte[]> message)
|
||||||
|
@ -108,8 +125,18 @@ namespace CloudNative.CloudEvents.Kafka
|
||||||
message.Headers.FirstOrDefault(x => string.Equals(x.Key, headerName, StringComparison.InvariantCultureIgnoreCase))
|
message.Headers.FirstOrDefault(x => string.Equals(x.Key, headerName, StringComparison.InvariantCultureIgnoreCase))
|
||||||
?.GetValueBytes();
|
?.GetValueBytes();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a CloudEvent to <see cref="Message"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param>
|
||||||
|
/// <param name="contentMode">Content mode. Structured or binary.</param>
|
||||||
|
/// <param name="formatter">The formatter to use within the conversion. Must not be null.</param>
|
||||||
public static Message<string, byte[]> ToKafkaMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter)
|
public static Message<string, byte[]> ToKafkaMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter)
|
||||||
{
|
{
|
||||||
|
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||||
|
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||||
|
formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||||
|
|
||||||
// TODO: Is this appropriate? Why can't we transport a CloudEvent without data in Kafka?
|
// TODO: Is this appropriate? Why can't we transport a CloudEvent without data in Kafka?
|
||||||
if (cloudEvent.Data == null)
|
if (cloudEvent.Data == null)
|
||||||
{
|
{
|
||||||
|
@ -120,16 +147,19 @@ namespace CloudNative.CloudEvents.Kafka
|
||||||
byte[] value;
|
byte[] value;
|
||||||
string contentTypeHeaderValue;
|
string contentTypeHeaderValue;
|
||||||
|
|
||||||
if (contentMode == ContentMode.Structured)
|
switch (contentMode)
|
||||||
{
|
{
|
||||||
value = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType);
|
case ContentMode.Structured:
|
||||||
// TODO: What about the non-media type parts?
|
value = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType);
|
||||||
contentTypeHeaderValue = contentType.MediaType;
|
// TODO: What about the non-media type parts?
|
||||||
}
|
contentTypeHeaderValue = contentType.MediaType;
|
||||||
else
|
break;
|
||||||
{
|
case ContentMode.Binary:
|
||||||
value = formatter.EncodeBinaryModeEventData(cloudEvent);
|
value = formatter.EncodeBinaryModeEventData(cloudEvent);
|
||||||
contentTypeHeaderValue = cloudEvent.DataContentType;
|
contentTypeHeaderValue = cloudEvent.DataContentType;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}");
|
||||||
}
|
}
|
||||||
if (contentTypeHeaderValue is object)
|
if (contentTypeHeaderValue is object)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue