sdk-csharp/src/CloudNative.CloudEvents/CloudEventFormatter.cs

204 lines
14 KiB
C#

// Copyright (c) Cloud Native Foundation.
// Licensed under the Apache 2.0 license.
// See LICENSE file in the project root for full license information.
using CloudNative.CloudEvents.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Mime;
using System.Threading.Tasks;
namespace CloudNative.CloudEvents
{
/// <summary>
/// Performs CloudEvent conversions as part of encoding and decoding messages for protocol bindings.
/// </summary>
/// <remarks>
/// <para>
/// Event formatters are responsible for complete CloudEvent encoding and decoding for structured-mode messages (where
/// all the CloudEvent information is represented within the message body), and data-only encoding and decoding
/// for binary-mode messages (where CloudEvent attributes are represented in message metadata, and the CloudEvent data
/// is represented in the message body).
/// </para>
/// <para>
/// Each event formatter type is responsible for documenting what types of value are acceptable for the <see cref="CloudEvent.Data"/>
/// property in CloudEvents it is asked to encode, and likewise what types of value will be present in the same property
/// when it is asked to decode a message. Event formatters should aim to be as consistent as possible with respect to data handling
/// between structured and binary modes, although this is not always possible as the structured mode representation may contain
/// more hints around how to interpret the data than the binary mode representation. Inconsistencies should be carefully
/// noted so that consumers can write robust code.
/// </para>
/// <para>
/// An event format is often naturally associated with a particular kind of data, but it is not limited to working with
/// that kind. For example, the JSON event format allows JSON data to be stored particularly naturally within the structured-mode
/// message body (which is itself JSON), but it is still able to handle arbitrary binary or text data.
/// </para>
/// </remarks>
public abstract class CloudEventFormatter
{
/// <summary>
/// Decodes a CloudEvent from a structured-mode message body, represented as a read-only memory segment.
/// </summary>
/// <param name="body">The message body (content).</param>
/// <param name="contentType">The content type of the message, or null if no content type is known.
/// Typically this is a content type with a media type of "application/cloudevents"; the additional
/// information such as the charset parameter may be needed in order to decode the message body.</param>
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
/// <returns>The CloudEvent derived from the structured message body.</returns>
public abstract CloudEvent DecodeStructuredModeMessage(ReadOnlyMemory<byte> body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes);
/// <summary>
/// Decodes a CloudEvent from a structured-mode message body, represented as a stream. The default implementation copies the
/// content of the stream into a read-only memory segment before passing it to <see cref="DecodeStructuredModeMessage(ReadOnlyMemory{byte}, ContentType, IEnumerable{CloudEventAttribute})"/>
/// but this can be overridden by event formatters that can decode a stream more efficiently.
/// </summary>
/// <param name="messageBody">The message body (content). Must not be null.</param>
/// <param name="contentType">The content type of the message, or null if no content type is known.
/// Typically this is a content type with a media type of "application/cloudevents"; the additional
/// information such as the charset parameter may be needed in order to decode the message body.</param>
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
/// <returns>The decoded CloudEvent.</returns>
public virtual CloudEvent DecodeStructuredModeMessage(Stream messageBody, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes)
{
var bytes = BinaryDataUtilities.ToReadOnlyMemory(messageBody);
return DecodeStructuredModeMessage(bytes, contentType, extensionAttributes);
}
/// <summary>
/// Asynchronously decodes a CloudEvent from a structured-mode message body, represented as a stream. The default implementation asynchronously copies the
/// content of the stream into a read-only memory segment before passing it to <see cref="DecodeStructuredModeMessage(ReadOnlyMemory{byte}, ContentType, IEnumerable{CloudEventAttribute})"/>
/// but this can be overridden by event formatters that can decode a stream more efficiently.
/// </summary>
/// <param name="body">The message body (content). Must not be null.</param>
/// <param name="contentType">The content type of the message, or null if no content type is known.
/// Typically this is a content type with a media type of "application/cloudevents"; the additional
/// information such as the charset parameter may be needed in order to decode the message body.</param>
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
/// <returns>The CloudEvent derived from the structured message body.</returns>
public virtual async Task<CloudEvent> DecodeStructuredModeMessageAsync(Stream body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes)
{
var bytes = await BinaryDataUtilities.ToReadOnlyMemoryAsync(body).ConfigureAwait(false);
return DecodeStructuredModeMessage(bytes, contentType, extensionAttributes);
}
/// <summary>
/// Encodes a CloudEvent as the body of a structured-mode message.
/// </summary>
/// <param name="cloudEvent">The CloudEvent to encode. Must not be null.</param>
/// <param name="contentType">On successful return, the content type of the structured-mode message body.
/// Must not be null (on return).</param>
/// <returns>The structured-mode representation of the CloudEvent.</returns>
public abstract ReadOnlyMemory<byte> EncodeStructuredModeMessage(CloudEvent cloudEvent, out ContentType contentType);
/// <summary>
/// Decodes the given data obtained from a binary-mode message, populating the <see cref="CloudEvent.Data"/>
/// property of <paramref name="cloudEvent"/>. Other attributes within the CloudEvent may be used to inform
/// the interpretation of the message body. This method is expected to be called after all other aspects of the CloudEvent
/// have been populated.
/// </summary>
/// <param name="body">The message body (content). Must not be null, but may be empty.</param>
/// <param name="cloudEvent">The CloudEvent whose Data property should be populated. Must not be null.</param>
/// <exception cref="ArgumentException">The data in the given CloudEvent cannot be decoded by this
/// event formatter.</exception>
public abstract void DecodeBinaryModeEventData(ReadOnlyMemory<byte> body, CloudEvent cloudEvent);
/// <summary>
/// Encodes the data from <paramref name="cloudEvent"/> in a manner suitable for a binary mode message.
/// </summary>
/// <exception cref="ArgumentException">The data in the given CloudEvent cannot be encoded by this
/// event formatter.</exception>
/// <returns>The binary-mode representation of the CloudEvent.</returns>
public abstract ReadOnlyMemory<byte> EncodeBinaryModeEventData(CloudEvent cloudEvent);
/// <summary>
/// Decodes a collection CloudEvents from a batch-mode message body, represented as a read-only memory segment.
/// </summary>
/// <param name="body">The message body (content).</param>
/// <param name="contentType">The content type of the message, or null if no content type is known.
/// Typically this is a content type with a media type with a prefix of "application/cloudevents-batch"; the additional
/// information such as the charset parameter may be needed in order to decode the message body.</param>
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
/// <returns>The collection of CloudEvents derived from the batch message body.</returns>
public abstract IReadOnlyList<CloudEvent> DecodeBatchModeMessage(ReadOnlyMemory<byte> body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes);
/// <summary>
/// Decodes a collection CloudEvents from a batch-mode message body, represented as a stream. The default implementation copies the
/// content of the stream into a read-only memory segment before passing it to <see cref="DecodeBatchModeMessage(ReadOnlyMemory{byte}, ContentType, IEnumerable{CloudEventAttribute})"/>
/// but this can be overridden by event formatters that can decode a stream more efficiently.
/// </summary>
/// <param name="body">The message body (content). Must not be null.</param>
/// <param name="contentType">The content type of the message, or null if no content type is known.
/// Typically this is a content type with a media type with a prefix of "application/cloudevents"; the additional
/// information such as the charset parameter may be needed in order to decode the message body.</param>
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
/// <returns>The collection of CloudEvents derived from the batch message body.</returns>
public virtual IReadOnlyList<CloudEvent> DecodeBatchModeMessage(Stream body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes)
{
var bytes = BinaryDataUtilities.ToReadOnlyMemory(body);
return DecodeBatchModeMessage(bytes, contentType, extensionAttributes);
}
/// <summary>
/// Asynchronously decodes a collection CloudEvents from a batch-mode message body, represented as a stream. The default implementation asynchronously copies the
/// content of the stream into a read-only memory segment before passing it to <see cref="DecodeBatchModeMessage(ReadOnlyMemory{byte}, ContentType, IEnumerable{CloudEventAttribute})"/>
/// but this can be overridden by event formatters that can decode a stream more efficiently.
/// </summary>
/// <param name="body">The message body (content). Must not be null.</param>
/// <param name="contentType">The content type of the message, or null if no content type is known.
/// Typically this is a content type with a media type with a prefix of "application/cloudevents"; the additional
/// information such as the charset parameter may be needed in order to decode the message body.</param>
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
/// <returns>The collection of CloudEvents derived from the batch message body.</returns>
public virtual async Task<IReadOnlyList<CloudEvent>> DecodeBatchModeMessageAsync(Stream body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes)
{
var bytes = await BinaryDataUtilities.ToReadOnlyMemoryAsync(body).ConfigureAwait(false);
return DecodeBatchModeMessage(bytes, contentType, extensionAttributes);
}
/// <summary>
/// Encodes a sequence of CloudEvents as the body of a message.
/// </summary>
/// <param name="cloudEvents">The CloudEvents to encode. Must not be null.</param>
/// <param name="contentType">On successful return, the content type of the batch message body.
/// Must not be null (on return).</param>
/// <returns>The batch representation of the CloudEvent.</returns>
public abstract ReadOnlyMemory<byte> EncodeBatchModeMessage(IEnumerable<CloudEvent> cloudEvents, out ContentType contentType);
/// <summary>
/// Determines the effective data content type of the given CloudEvent.
/// </summary>
/// <remarks>
/// <para>
/// This implementation validates that <paramref name="cloudEvent"/> is not null,
/// returns the existing <see cref="CloudEvent.DataContentType"/> if that's not null,
/// and otherwise returns null if <see cref="CloudEvent.Data"/> is null or
/// delegates to <see cref="InferDataContentType(object)"/> to infer the data content type
/// from the actual data.
/// </para>
/// <para>
/// Derived classes may override this if additional information is needed from the CloudEvent
/// in order to determine the effective data content type, but most cases can be handled by
/// simply overriding <see cref="InferDataContentType(object)"/>.
/// </para>
/// </remarks>
/// <param name="cloudEvent">The CloudEvent to get or infer the data content type from. Must not be null.</param>
/// <returns>The data content type of the CloudEvent, or null for no data content type.</returns>
public virtual string? GetOrInferDataContentType(CloudEvent cloudEvent)
{
Validation.CheckNotNull(cloudEvent, nameof(cloudEvent));
return cloudEvent.DataContentType is string dataContentType ? dataContentType
: cloudEvent.Data is not object data ? null
: InferDataContentType(data);
}
/// <summary>
/// Infers the effective data content type based on the actual data. This base implementation
/// always returns null, but derived classes may override this method to effectively provide
/// a default data content type based on the in-memory data type.
/// </summary>
/// <param name="data">The data within a CloudEvent. Should not be null.</param>
/// <returns>The inferred content type, or null if no content type is inferred.</returns>
protected virtual string? InferDataContentType(object data) => null;
}
}