Move implementation utility methods into their own namespace
- Preconditions is renamed to Validation - Validation is public, allowing simplified validation in implementation classes - CloudEvent.ValidateForConversion is now Validation.CheckCloudEventArgument - All the MimeUtilities methods are non-extension methods (to avoid Intellisense suggesting them) - BinaryDataUtilities is now public (it's much less of an issue once it's in a namespace that other users are somewhat discouraged from using) - Documentation explains the purpose of the namespace, albeit briefly Signed-off-by: Jon Skeet <jonskeet@google.com>
This commit is contained in:
parent
00391089d2
commit
41f639d44b
|
@ -4,3 +4,15 @@ This directory contains documentation on:
|
|||
|
||||
- Using the SDK as a consumer
|
||||
- Implementing new event formats and protocol bindings
|
||||
|
||||
## Implementation utility classes
|
||||
|
||||
The `CloudNative.CloudEvents.Core` namespace contains utility
|
||||
classes which are generally helpful when implementing [protocol
|
||||
bindings](bindings.md) or [event formatters](formatters.md) but are
|
||||
not expected to be used by code which only creates or consumes
|
||||
CloudEvents.
|
||||
|
||||
The classes in this namespace are static classes, but the methods
|
||||
are deliberately not extension methods. This avoids the methods from
|
||||
being suggested to non-implementation code.
|
||||
|
|
|
@ -95,7 +95,7 @@ following pseudo-code as structure:
|
|||
- If the message contains content, call the
|
||||
`formatter.DecodeBinaryModeEventData` method to populate the
|
||||
`CloudEvent.Data` property appropriately.
|
||||
- Return the result of `CloudEvent.ValidateForConversion` which
|
||||
- Return the result of `Validation.CheckCloudEventArgument` which
|
||||
will validate the event, and either return the original reference if
|
||||
the event is valid, or throw an appropriate `ArgumentException`
|
||||
otherwise.
|
||||
|
@ -155,9 +155,10 @@ The conversion should follow the following steps of pseudo-code:
|
|||
|
||||
- Parameter validation (which may be completed in any order):
|
||||
- `cloudEvent` and `formatter` should be non-null
|
||||
(`CheckCloudEventArgument` will validate this for the `cloudEvent` parameter)
|
||||
- In a `CopyTo...` method, `destination` should be non-null
|
||||
- The `contentMode` should be a known, supported value
|
||||
- Call `cloudEvent.ValidateForConversion(nameof(cloudEvent))`
|
||||
- Call `Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent))`
|
||||
for validation of the original CloudEvent.
|
||||
- For structured mode encoding:
|
||||
- Call `formatter.EncodeStructuredModeMessage` to encode
|
||||
|
|
|
@ -73,10 +73,10 @@ being non-null, and additionally perform CloudEvent validation on:
|
|||
- The `CloudEvent` accepted in `EncodeBinaryModeEventData` or
|
||||
`EncodeStructuredModeMessage`
|
||||
|
||||
Validation should be performed using the `ValidateForConversion`
|
||||
Validation should be performed using the `Validation.CheckCloudEventArgument`
|
||||
method, so that an appropriate `ArgumentException` is thrown.
|
||||
|
||||
The formatter should *not* perform validation on the
|
||||
`CloudEvent` accepted in `DecodeBinaryModeEventData`. This is
|
||||
typically called by a protocol binding which should perform
|
||||
validation itself later.
|
||||
The formatter should *not* perform validation on the `CloudEvent`
|
||||
accepted in `DecodeBinaryModeEventData`, beyond asserting that the
|
||||
argument is not null. This is typically called by a protocol binding
|
||||
which should perform validation itself later.
|
|
@ -5,6 +5,7 @@
|
|||
using Amqp;
|
||||
using Amqp.Framing;
|
||||
using Amqp.Types;
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -50,8 +51,8 @@ namespace CloudNative.CloudEvents.Amqp
|
|||
CloudEventFormatter formatter,
|
||||
IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
message = message ?? throw new ArgumentNullException(nameof(message));
|
||||
formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||
Validation.CheckNotNull(message, nameof(message));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
if (HasCloudEventsContentType(message, out var contentType))
|
||||
{
|
||||
|
@ -122,7 +123,7 @@ namespace CloudNative.CloudEvents.Amqp
|
|||
throw new ArgumentException("Binary mode data in AMQP message must be in the application data section");
|
||||
}
|
||||
|
||||
return cloudEvent.ValidateForConversion(nameof(message));
|
||||
return Validation.CheckCloudEventArgument(cloudEvent, nameof(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,9 +142,8 @@ namespace CloudNative.CloudEvents.Amqp
|
|||
/// <param name="formatter">The formatter to use within the conversion. Must not be null.</param>
|
||||
public static Message ToAmqpMessage(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));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
var applicationProperties = MapHeaders(cloudEvent);
|
||||
RestrictedDescribed bodySection;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System;
|
||||
|
@ -30,15 +31,8 @@ namespace CloudNative.CloudEvents
|
|||
|
||||
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (encoding == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
Validation.CheckNotNull(context, nameof(context));
|
||||
Validation.CheckNotNull(encoding, nameof(encoding));
|
||||
|
||||
var request = context.HttpContext.Request;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using CloudNative.CloudEvents.Http;
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -79,13 +80,10 @@ namespace CloudNative.CloudEvents
|
|||
cloudEvent.DataContentType = httpRequest.ContentType;
|
||||
if (httpRequest.Body is Stream body)
|
||||
{
|
||||
// TODO: This is a bit ugly. We have code in BinaryDataUtilities to handle this, but
|
||||
// we'd rather not expose it...
|
||||
var memoryStream = new MemoryStream();
|
||||
await body.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||
formatter.DecodeBinaryModeEventData(memoryStream.ToArray(), cloudEvent);
|
||||
byte[] data = await BinaryDataUtilities.ToByteArrayAsync(body).ConfigureAwait(false);
|
||||
formatter.DecodeBinaryModeEventData(data, cloudEvent);
|
||||
}
|
||||
return cloudEvent.ValidateForConversion(nameof(httpRequest));
|
||||
return Validation.CheckCloudEventArgument(cloudEvent, nameof(httpRequest));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using Avro;
|
||||
using Avro.Generic;
|
||||
using Avro.IO;
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -53,7 +54,7 @@ namespace CloudNative.CloudEvents
|
|||
|
||||
public override CloudEvent DecodeStructuredModeMessage(Stream data, ContentType contentType, IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
Validation.CheckNotNull(data, nameof(data));
|
||||
|
||||
var decoder = new BinaryDecoder(data);
|
||||
var rawEvent = avroReader.Read<GenericRecord>(null, decoder);
|
||||
|
@ -62,7 +63,7 @@ namespace CloudNative.CloudEvents
|
|||
|
||||
public override CloudEvent DecodeStructuredModeMessage(byte[] data, ContentType contentType, IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
Validation.CheckNotNull(data, nameof(data));
|
||||
return DecodeStructuredModeMessage(new MemoryStream(data), contentType, extensionAttributes);
|
||||
}
|
||||
|
||||
|
@ -119,13 +120,12 @@ namespace CloudNative.CloudEvents
|
|||
}
|
||||
}
|
||||
|
||||
return cloudEvent.ValidateForConversion(nameof(record));
|
||||
return Validation.CheckCloudEventArgument(cloudEvent, nameof(record));
|
||||
}
|
||||
|
||||
public override byte[] EncodeStructuredModeMessage(CloudEvent cloudEvent, out ContentType contentType)
|
||||
{
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
|
||||
contentType = new ContentType(CloudEvent.MediaType + MediaTypeSuffix);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using CloudNative.CloudEvents.Extensions;
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using Confluent.Kafka;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -50,8 +51,8 @@ namespace CloudNative.CloudEvents.Kafka
|
|||
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));
|
||||
Validation.CheckNotNull(message, nameof(message));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
if (!IsCloudEvent(message))
|
||||
{
|
||||
|
@ -104,7 +105,7 @@ namespace CloudNative.CloudEvents.Kafka
|
|||
}
|
||||
|
||||
InitPartitioningKey(message, cloudEvent);
|
||||
return cloudEvent.ValidateForConversion(nameof(message));
|
||||
return Validation.CheckCloudEventArgument(cloudEvent, nameof(message));
|
||||
}
|
||||
|
||||
private static string ExtractContentType(Message<string, byte[]> message)
|
||||
|
@ -133,15 +134,11 @@ namespace CloudNative.CloudEvents.Kafka
|
|||
/// <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)
|
||||
{
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
// TODO: Is this appropriate? Why can't we transport a CloudEvent without data in Kafka?
|
||||
if (cloudEvent.Data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cloudEvent.Data));
|
||||
}
|
||||
Validation.CheckArgument(cloudEvent.Data is object, nameof(cloudEvent), "Only CloudEvents with data can be converted to Kafka messages");
|
||||
var headers = MapHeaders(cloudEvent, formatter);
|
||||
string key = (string) cloudEvent[Partitioning.PartitionKeyAttribute];
|
||||
byte[] value;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using MQTTnet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -34,8 +35,8 @@ namespace CloudNative.CloudEvents.Mqtt
|
|||
public static CloudEvent ToCloudEvent(this MqttApplicationMessage message,
|
||||
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
message = message ?? throw new ArgumentNullException(nameof(message));
|
||||
formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
Validation.CheckNotNull(message, nameof(message));
|
||||
|
||||
// TODO: Determine if there's a sensible content type we should apply.
|
||||
return formatter.DecodeStructuredModeMessage(message.Payload, contentType: null, extensionAttributes);
|
||||
|
@ -51,9 +52,8 @@ namespace CloudNative.CloudEvents.Mqtt
|
|||
/// <param name="topic">The MQTT topic for the message. May be null.</param>
|
||||
public static MqttApplicationMessage ToMqttApplicationMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter, string topic)
|
||||
{
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
switch (contentMode)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
|
@ -112,23 +113,23 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
/// </summary>
|
||||
public JsonEventFormatter(JsonSerializer serializer)
|
||||
{
|
||||
Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
|
||||
Serializer = Validation.CheckNotNull(serializer, nameof(serializer));
|
||||
}
|
||||
|
||||
public override async Task<CloudEvent> DecodeStructuredModeMessageAsync(Stream data, ContentType contentType, IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
Validation.CheckNotNull(data, nameof(data));
|
||||
|
||||
var jsonReader = CreateJsonReader(data, contentType.GetEncoding());
|
||||
var jsonReader = CreateJsonReader(data, MimeUtilities.GetEncoding(contentType));
|
||||
var jObject = await JObject.LoadAsync(jsonReader).ConfigureAwait(false);
|
||||
return DecodeJObject(jObject, extensionAttributes);
|
||||
}
|
||||
|
||||
public override CloudEvent DecodeStructuredModeMessage(Stream data, ContentType contentType, IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
Validation.CheckNotNull(data, nameof(data));
|
||||
|
||||
var jsonReader = CreateJsonReader(data, contentType.GetEncoding());
|
||||
var jsonReader = CreateJsonReader(data, MimeUtilities.GetEncoding(contentType));
|
||||
var jObject = JObject.Load(jsonReader);
|
||||
return DecodeJObject(jObject, extensionAttributes);
|
||||
}
|
||||
|
@ -151,7 +152,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
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 cloudEvent.ValidateForConversion("data");
|
||||
return Validation.CheckCloudEventArgument(cloudEvent, "data");
|
||||
}
|
||||
|
||||
private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JObject jObject)
|
||||
|
@ -306,8 +307,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
|
||||
public override byte[] EncodeStructuredModeMessage(CloudEvent cloudEvent, out ContentType contentType)
|
||||
{
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
|
||||
contentType = new ContentType("application/cloudevents+json")
|
||||
{
|
||||
|
@ -389,8 +389,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
|
||||
public override byte[] EncodeBinaryModeEventData(CloudEvent cloudEvent)
|
||||
{
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
|
||||
if (cloudEvent.Data is null)
|
||||
{
|
||||
|
@ -405,11 +404,11 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
// without a preamble (or rewrite StreamWriter...)
|
||||
var stringWriter = new StringWriter();
|
||||
Serializer.Serialize(stringWriter, cloudEvent.Data);
|
||||
return contentType.GetEncoding().GetBytes(stringWriter.ToString());
|
||||
return MimeUtilities.GetEncoding(contentType).GetBytes(stringWriter.ToString());
|
||||
}
|
||||
if (contentType.MediaType.StartsWith("text/") && cloudEvent.Data is string text)
|
||||
{
|
||||
return contentType.GetEncoding().GetBytes(text);
|
||||
return MimeUtilities.GetEncoding(contentType).GetBytes(text);
|
||||
}
|
||||
if (cloudEvent.Data is byte[] bytes)
|
||||
{
|
||||
|
@ -420,12 +419,12 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
|
||||
public override void DecodeBinaryModeEventData(byte[] value, CloudEvent cloudEvent)
|
||||
{
|
||||
value = value ?? throw new ArgumentNullException(nameof(value));
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
Validation.CheckNotNull(value, nameof(value));
|
||||
Validation.CheckNotNull(cloudEvent, nameof(cloudEvent));
|
||||
|
||||
ContentType contentType = new ContentType(cloudEvent.DataContentType ?? JsonMediaType);
|
||||
|
||||
Encoding encoding = contentType.GetEncoding();
|
||||
Encoding encoding = MimeUtilities.GetEncoding(contentType);
|
||||
|
||||
if (contentType.MediaType == JsonMediaType)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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;
|
||||
|
@ -119,8 +120,9 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
|
||||
private async Task<CloudEvent> DecodeStructuredModeMessageImpl(Stream data, ContentType contentType, IEnumerable<CloudEventAttribute> extensionAttributes, bool async)
|
||||
{
|
||||
data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
var encoding = contentType.GetEncoding();
|
||||
Validation.CheckNotNull(data, nameof(data));
|
||||
|
||||
var encoding = MimeUtilities.GetEncoding(contentType);
|
||||
JsonDocument document;
|
||||
if (encoding is UTF8Encoding)
|
||||
{
|
||||
|
@ -157,7 +159,7 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
PopulateDataFromStructuredEvent(cloudEvent, document);
|
||||
// "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 cloudEvent.ValidateForConversion("data");
|
||||
return Validation.CheckCloudEventArgument(cloudEvent, "data");
|
||||
}
|
||||
|
||||
private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JsonDocument document)
|
||||
|
@ -321,8 +323,7 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
|
||||
public override byte[] EncodeStructuredModeMessage(CloudEvent cloudEvent, out ContentType contentType)
|
||||
{
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
|
||||
contentType = new ContentType("application/cloudevents+json")
|
||||
{
|
||||
|
@ -404,8 +405,7 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
|
||||
public override byte[] EncodeBinaryModeEventData(CloudEvent cloudEvent)
|
||||
{
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
|
||||
if (cloudEvent.Data is null)
|
||||
{
|
||||
|
@ -414,19 +414,19 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
ContentType contentType = new ContentType(cloudEvent.DataContentType ?? JsonMediaType);
|
||||
if (contentType.MediaType == JsonMediaType)
|
||||
{
|
||||
var encoding = contentType.GetEncoding();
|
||||
var encoding = MimeUtilities.GetEncoding(contentType);
|
||||
if (encoding is UTF8Encoding)
|
||||
{
|
||||
return JsonSerializer.SerializeToUtf8Bytes(cloudEvent.Data, SerializerOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
return contentType.GetEncoding().GetBytes(JsonSerializer.Serialize(cloudEvent.Data, SerializerOptions));
|
||||
return MimeUtilities.GetEncoding(contentType).GetBytes(JsonSerializer.Serialize(cloudEvent.Data, SerializerOptions));
|
||||
}
|
||||
}
|
||||
if (contentType.MediaType.StartsWith("text/") && cloudEvent.Data is string text)
|
||||
{
|
||||
return contentType.GetEncoding().GetBytes(text);
|
||||
return MimeUtilities.GetEncoding(contentType).GetBytes(text);
|
||||
}
|
||||
if (cloudEvent.Data is byte[] bytes)
|
||||
{
|
||||
|
@ -437,12 +437,12 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
|
||||
public override void DecodeBinaryModeEventData(byte[] value, CloudEvent cloudEvent)
|
||||
{
|
||||
value = value ?? throw new ArgumentNullException(nameof(value));
|
||||
cloudEvent = cloudEvent ?? throw new ArgumentNullException(nameof(cloudEvent));
|
||||
Validation.CheckNotNull(value, nameof(value));
|
||||
Validation.CheckNotNull(cloudEvent, nameof(cloudEvent));
|
||||
|
||||
ContentType contentType = new ContentType(cloudEvent.DataContentType ?? JsonMediaType);
|
||||
|
||||
Encoding encoding = contentType.GetEncoding();
|
||||
Encoding encoding = MimeUtilities.GetEncoding(contentType);
|
||||
|
||||
if (contentType.MediaType == JsonMediaType)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Linq;
|
||||
|
@ -62,33 +63,32 @@ namespace CloudNative.CloudEvents
|
|||
/// to an empty sequence.</param>
|
||||
public CloudEvent(CloudEventsSpecVersion specVersion, IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
// TODO: Validate that all attributes are extension attributes.
|
||||
// TODO: Work out how to be more efficient, e.g. not creating a dictionary at all if there are no
|
||||
// extension attributes.
|
||||
SpecVersion = Preconditions.CheckNotNull(specVersion, nameof(specVersion));
|
||||
SpecVersion = Validation.CheckNotNull(specVersion, nameof(specVersion));
|
||||
if (extensionAttributes is object)
|
||||
{
|
||||
foreach (var extension in extensionAttributes)
|
||||
{
|
||||
Preconditions.CheckArgument(
|
||||
Validation.CheckArgument(
|
||||
extension is object,
|
||||
nameof(extensionAttributes),
|
||||
"Extension attribute collection cannot contain null elements");
|
||||
Preconditions.CheckArgument(
|
||||
Validation.CheckArgument(
|
||||
extension.Name != CloudEventsSpecVersion.SpecVersionAttributeName,
|
||||
nameof(extensionAttributes),
|
||||
"The 'specversion' attribute cannot be specified as an extension attribute");
|
||||
Preconditions.CheckArgument(
|
||||
Validation.CheckArgument(
|
||||
SpecVersion.GetAttributeByName(extension.Name) is null,
|
||||
nameof(extensionAttributes),
|
||||
"'{0}' cannot be specified as the name of an extension attribute; it is already a context attribute",
|
||||
extension.Name);
|
||||
Preconditions.CheckArgument(
|
||||
Validation.CheckArgument(
|
||||
extension.IsExtension,
|
||||
nameof(extensionAttributes),
|
||||
"'{0}' is not an extension attribute",
|
||||
extension.Name);
|
||||
Preconditions.CheckArgument(
|
||||
Validation.CheckArgument(
|
||||
!this.extensionAttributes.ContainsKey(extension.Name),
|
||||
nameof(extensionAttributes),
|
||||
"'{0}' cannot be specified more than once as an extension attribute");
|
||||
|
@ -123,8 +123,8 @@ namespace CloudNative.CloudEvents
|
|||
{
|
||||
get
|
||||
{
|
||||
Preconditions.CheckNotNull(attribute, nameof(attribute));
|
||||
Preconditions.CheckArgument(attribute.Name != CloudEventsSpecVersion.SpecVersionAttributeName, nameof(attribute), Strings.ErrorCannotIndexBySpecVersionAttribute);
|
||||
Validation.CheckNotNull(attribute, nameof(attribute));
|
||||
Validation.CheckArgument(attribute.Name != CloudEventsSpecVersion.SpecVersionAttributeName, nameof(attribute), Strings.ErrorCannotIndexBySpecVersionAttribute);
|
||||
|
||||
// TODO: Is this validation definitely useful? It does mean we never return something
|
||||
// that's invalid for the attribute, which is potentially good...
|
||||
|
@ -137,14 +137,14 @@ namespace CloudNative.CloudEvents
|
|||
}
|
||||
set
|
||||
{
|
||||
Preconditions.CheckNotNull(attribute, nameof(attribute));
|
||||
Preconditions.CheckArgument(attribute.Name != CloudEventsSpecVersion.SpecVersionAttributeName, nameof(attribute), Strings.ErrorCannotIndexBySpecVersionAttribute);
|
||||
Validation.CheckNotNull(attribute, nameof(attribute));
|
||||
Validation.CheckArgument(attribute.Name != CloudEventsSpecVersion.SpecVersionAttributeName, nameof(attribute), Strings.ErrorCannotIndexBySpecVersionAttribute);
|
||||
|
||||
string name = attribute.Name;
|
||||
var knownAttribute = GetAttribute(name);
|
||||
|
||||
// TODO: Are we happy to add the extension in even if the value is null?
|
||||
Preconditions.CheckArgument(knownAttribute is object || attribute.IsExtension,
|
||||
Validation.CheckArgument(knownAttribute is object || attribute.IsExtension,
|
||||
nameof(attribute),
|
||||
"Cannot add an unknown non-extension attribute to an event.");
|
||||
|
||||
|
@ -178,14 +178,14 @@ namespace CloudNative.CloudEvents
|
|||
get
|
||||
{
|
||||
// TODO: Validate the attribute name is valid (e.g. not upper case)? Seems overkill.
|
||||
Preconditions.CheckNotNull(attributeName, nameof(attributeName));
|
||||
Preconditions.CheckArgument(attributeName != CloudEventsSpecVersion.SpecVersionAttributeName, nameof(attributeName), Strings.ErrorCannotIndexBySpecVersionAttribute);
|
||||
return attributeValues.GetValueOrDefault(Preconditions.CheckNotNull(attributeName, nameof(attributeName)));
|
||||
Validation.CheckNotNull(attributeName, nameof(attributeName));
|
||||
Validation.CheckArgument(attributeName != CloudEventsSpecVersion.SpecVersionAttributeName, nameof(attributeName), Strings.ErrorCannotIndexBySpecVersionAttribute);
|
||||
return attributeValues.GetValueOrDefault(Validation.CheckNotNull(attributeName, nameof(attributeName)));
|
||||
}
|
||||
set
|
||||
{
|
||||
Preconditions.CheckNotNull(attributeName, nameof(attributeName));
|
||||
Preconditions.CheckArgument(attributeName != CloudEventsSpecVersion.SpecVersionAttributeName, nameof(attributeName), Strings.ErrorCannotIndexBySpecVersionAttribute);
|
||||
Validation.CheckNotNull(attributeName, nameof(attributeName));
|
||||
Validation.CheckArgument(attributeName != CloudEventsSpecVersion.SpecVersionAttributeName, nameof(attributeName), Strings.ErrorCannotIndexBySpecVersionAttribute);
|
||||
|
||||
var knownAttribute = GetAttribute(attributeName);
|
||||
|
||||
|
@ -193,7 +193,7 @@ namespace CloudNative.CloudEvents
|
|||
// (It's a simple way of populating extensions after the fact...)
|
||||
if (knownAttribute is null)
|
||||
{
|
||||
Preconditions.CheckArgument(value is null || value is string,
|
||||
Validation.CheckArgument(value is null || value is string,
|
||||
nameof(value), "Cannot assign value of type {0} to unknown attribute '{1}'",
|
||||
value.GetType(), attributeName);
|
||||
knownAttribute = CloudEventAttribute.CreateExtension(attributeName, CloudEventAttributeType.String);
|
||||
|
@ -352,8 +352,8 @@ namespace CloudNative.CloudEvents
|
|||
/// <param name="value">The value of the attribute to set. Must not be null.</param>
|
||||
public void SetAttributeFromString(string name, string value)
|
||||
{
|
||||
Preconditions.CheckNotNull(name, nameof(name));
|
||||
Preconditions.CheckNotNull(value, nameof(value));
|
||||
Validation.CheckNotNull(name, nameof(name));
|
||||
Validation.CheckNotNull(value, nameof(value));
|
||||
|
||||
var attribute = GetAttribute(name);
|
||||
if (attribute is null)
|
||||
|
@ -385,29 +385,6 @@ namespace CloudNative.CloudEvents
|
|||
throw new InvalidOperationException($"Missing required attributes: {joinedMissing}");
|
||||
}
|
||||
|
||||
// TODO: consider moving this to an extension method in an implementation helper namespace?
|
||||
|
||||
/// <summary>
|
||||
/// Validates that this CloudEvent is valid in the same way as <see cref="IsValid"/>,
|
||||
/// but throwing an <see cref="ArgumentException"/> using the given parameter name
|
||||
/// if the event is invalid. This is typically used within protocol bindings or event formatters
|
||||
/// as the last step in decoding an event, or as the first step when encoding an event.
|
||||
/// </summary>
|
||||
/// <param name="paramName">The parameter name to use in the exception if the event is invalid.
|
||||
/// May be null.</param>
|
||||
/// <exception cref="ArgumentException">The event is invalid.</exception>
|
||||
/// <returns>A reference to the same object, for simplicity of method chaining.</returns>
|
||||
public CloudEvent ValidateForConversion(string paramName)
|
||||
{
|
||||
if (IsValid)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
var missing = SpecVersion.RequiredAttributes.Where(attr => this[attr] is null).ToList();
|
||||
string joinedMissing = string.Join(", ", missing);
|
||||
throw new ArgumentException($"CloudEvent is missing required attributes: {joinedMissing}", paramName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this CloudEvent is valid, i.e. whether all required attributes have
|
||||
/// values.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using System;
|
||||
|
||||
namespace CloudNative.CloudEvents
|
||||
|
@ -44,7 +45,7 @@ namespace CloudNative.CloudEvents
|
|||
// TODO: Have a "mode" of Required/Optional/Extension?
|
||||
|
||||
private CloudEventAttribute(string name, CloudEventAttributeType type, bool required, bool extension, Action<object> validator) =>
|
||||
(Name, Type, IsRequired, IsExtension, this.validator) = (ValidateName(name), Preconditions.CheckNotNull(type, nameof(type)), required, extension, validator);
|
||||
(Name, Type, IsRequired, IsExtension, this.validator) = (ValidateName(name), Validation.CheckNotNull(type, nameof(type)), required, extension, validator);
|
||||
|
||||
internal static CloudEventAttribute CreateRequired(string name, CloudEventAttributeType type, Action<object> validator) =>
|
||||
new CloudEventAttribute(name, type, required: true, extension: false, validator: validator);
|
||||
|
@ -87,7 +88,7 @@ namespace CloudNative.CloudEvents
|
|||
/// <returns><paramref name="name"/>, for convenience.</returns>
|
||||
internal static string ValidateName(string name)
|
||||
{
|
||||
Preconditions.CheckNotNull(name, nameof(name));
|
||||
Validation.CheckNotNull(name, nameof(name));
|
||||
if (name.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Attribute names must be non-empty", nameof(name));
|
||||
|
@ -106,7 +107,7 @@ namespace CloudNative.CloudEvents
|
|||
|
||||
public object Parse(string text)
|
||||
{
|
||||
Preconditions.CheckNotNull(text, nameof(text));
|
||||
Validation.CheckNotNull(text, nameof(text));
|
||||
object value;
|
||||
// By wrapping every exception here, we always get an
|
||||
// ArgumentException (other than the ArgumentNullException above) and have the name in the message.
|
||||
|
@ -132,7 +133,7 @@ namespace CloudNative.CloudEvents
|
|||
/// <returns>The value, for simple method chaining.</returns>
|
||||
public object Validate(object value)
|
||||
{
|
||||
Preconditions.CheckNotNull(value, nameof(value));
|
||||
Validation.CheckNotNull(value, nameof(value));
|
||||
// By wrapping every exception, whether from the type or the custom validator, we always get an
|
||||
// ArgumentException (other than the ArgumentNullException above) and have the name in the message.
|
||||
try
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Globalization;
|
||||
|
||||
|
@ -98,7 +99,7 @@ namespace CloudNative.CloudEvents
|
|||
{
|
||||
}
|
||||
|
||||
public override sealed object Parse(string value) => ParseImpl(Preconditions.CheckNotNull(value, nameof(value)));
|
||||
public override sealed object Parse(string value) => ParseImpl(Validation.CheckNotNull(value, nameof(value)));
|
||||
|
||||
public override sealed string Format(object value)
|
||||
{
|
||||
|
@ -109,13 +110,13 @@ namespace CloudNative.CloudEvents
|
|||
|
||||
public override sealed void Validate(object value)
|
||||
{
|
||||
Preconditions.CheckNotNull(value, nameof(value));
|
||||
Validation.CheckNotNull(value, nameof(value));
|
||||
if (!ClrType.IsInstanceOfType(value))
|
||||
{
|
||||
throw new ArgumentException($"Value of type {value.GetType()} is incompatible with expected type {ClrType}", nameof(value));
|
||||
}
|
||||
|
||||
ValidateImpl((T)Preconditions.CheckNotNull(value, nameof(value)));
|
||||
ValidateImpl((T)Validation.CheckNotNull(value, nameof(value)));
|
||||
}
|
||||
|
||||
protected abstract T ParseImpl(string value);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Linq;
|
||||
|
@ -60,7 +61,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
/// <returns>true, if the response is a CloudEvent</returns>
|
||||
public static bool IsCloudEvent(this HttpRequestMessage httpRequestMessage)
|
||||
{
|
||||
Preconditions.CheckNotNull(httpRequestMessage, nameof(httpRequestMessage));
|
||||
Validation.CheckNotNull(httpRequestMessage, nameof(httpRequestMessage));
|
||||
return HasCloudEventsContentType(httpRequestMessage.Content) ||
|
||||
httpRequestMessage.Headers.Contains(HttpUtilities.SpecVersionHttpHeader);
|
||||
}
|
||||
|
@ -72,7 +73,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
/// <returns>true, if the response is a CloudEvent</returns>
|
||||
public static bool IsCloudEvent(this HttpResponseMessage httpResponseMessage)
|
||||
{
|
||||
Preconditions.CheckNotNull(httpResponseMessage, nameof(httpResponseMessage));
|
||||
Validation.CheckNotNull(httpResponseMessage, nameof(httpResponseMessage));
|
||||
return HasCloudEventsContentType(httpResponseMessage.Content) ||
|
||||
httpResponseMessage.Headers.Contains(HttpUtilities.SpecVersionHttpHeader);
|
||||
}
|
||||
|
@ -109,7 +110,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
CloudEventFormatter formatter,
|
||||
IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
Preconditions.CheckNotNull(httpResponseMessage, nameof(httpResponseMessage));
|
||||
Validation.CheckNotNull(httpResponseMessage, nameof(httpResponseMessage));
|
||||
return ToCloudEventInternalAsync(httpResponseMessage.Headers, httpResponseMessage.Content, formatter, extensionAttributes, nameof(httpResponseMessage));
|
||||
}
|
||||
|
||||
|
@ -138,19 +139,19 @@ namespace CloudNative.CloudEvents.Http
|
|||
CloudEventFormatter formatter,
|
||||
IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
Preconditions.CheckNotNull(httpRequestMessage, nameof(httpRequestMessage));
|
||||
Validation.CheckNotNull(httpRequestMessage, nameof(httpRequestMessage));
|
||||
return ToCloudEventInternalAsync(httpRequestMessage.Headers, httpRequestMessage.Content, formatter, extensionAttributes, nameof(httpRequestMessage));
|
||||
}
|
||||
|
||||
private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent content,
|
||||
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute> extensionAttributes, string paramName)
|
||||
{
|
||||
Preconditions.CheckNotNull(formatter, nameof(formatter));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
if (HasCloudEventsContentType(content))
|
||||
{
|
||||
var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
return await formatter.DecodeStructuredModeMessageAsync(stream, content.Headers.ContentType.ToContentType(), extensionAttributes).ConfigureAwait(false);
|
||||
return await formatter.DecodeStructuredModeMessageAsync(stream, MimeUtilities.ToContentType(content.Headers.ContentType), extensionAttributes).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -179,7 +180,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
var data = await content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
formatter.DecodeBinaryModeEventData(data, cloudEvent);
|
||||
}
|
||||
return cloudEvent.ValidateForConversion(paramName);
|
||||
return Validation.CheckCloudEventArgument(cloudEvent, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Net.Http;
|
||||
using System.Net.Mime;
|
||||
|
@ -21,9 +22,8 @@ namespace CloudNative.CloudEvents.Http
|
|||
/// <param name="formatter">The formatter to use within the conversion. Must not be null.</param>
|
||||
public static HttpContent ToHttpContent(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter)
|
||||
{
|
||||
Preconditions.CheckNotNull(cloudEvent, nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
Preconditions.CheckNotNull(formatter, nameof(formatter));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
byte[] content;
|
||||
// The content type to include in the ContentType header - may be the data content type, or the formatter's content type.
|
||||
|
@ -43,7 +43,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
var ret = new ByteArrayContent(content);
|
||||
if (contentType is object)
|
||||
{
|
||||
ret.Headers.ContentType = contentType.ToMediaTypeHeaderValue();
|
||||
ret.Headers.ContentType = MimeUtilities.ToMediaTypeHeaderValue(contentType);
|
||||
}
|
||||
else if (content.Length != 0)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Net;
|
||||
|
@ -27,10 +28,9 @@ namespace CloudNative.CloudEvents.Http
|
|||
public static Task CopyToHttpListenerResponseAsync(this CloudEvent cloudEvent, HttpListenerResponse destination,
|
||||
ContentMode contentMode, CloudEventFormatter formatter)
|
||||
{
|
||||
Preconditions.CheckNotNull(cloudEvent, nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
Preconditions.CheckNotNull(destination, nameof(destination));
|
||||
Preconditions.CheckNotNull(formatter, nameof(formatter));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
Validation.CheckNotNull(destination, nameof(destination));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
byte[] content;
|
||||
ContentType contentType;
|
||||
|
@ -142,8 +142,8 @@ namespace CloudNative.CloudEvents.Http
|
|||
public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest,
|
||||
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute> extensionAttributes)
|
||||
{
|
||||
Preconditions.CheckNotNull(httpListenerRequest, nameof(httpListenerRequest));
|
||||
Preconditions.CheckNotNull(formatter, nameof(formatter));
|
||||
Validation.CheckNotNull(httpListenerRequest, nameof(httpListenerRequest));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
if (HasCloudEventsContentType(httpListenerRequest))
|
||||
{
|
||||
|
@ -176,7 +176,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
cloudEvent.DataContentType = httpListenerRequest.ContentType;
|
||||
|
||||
formatter.DecodeBinaryModeEventData(BinaryDataUtilities.ToByteArray(httpListenerRequest.InputStream), cloudEvent);
|
||||
return cloudEvent.ValidateForConversion(nameof(httpListenerRequest));
|
||||
return Validation.CheckCloudEventArgument(cloudEvent, nameof(httpListenerRequest));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Net;
|
||||
using System.Net.Mime;
|
||||
|
@ -28,10 +29,9 @@ namespace CloudNative.CloudEvents.Http
|
|||
public static async Task CopyToHttpWebRequestAsync(this CloudEvent cloudEvent, HttpWebRequest destination,
|
||||
ContentMode contentMode, CloudEventFormatter formatter)
|
||||
{
|
||||
Preconditions.CheckNotNull(cloudEvent, nameof(cloudEvent));
|
||||
cloudEvent.ValidateForConversion(nameof(cloudEvent));
|
||||
Preconditions.CheckNotNull(destination, nameof(destination));
|
||||
Preconditions.CheckNotNull(formatter, nameof(formatter));
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
Validation.CheckNotNull(destination, nameof(destination));
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
||||
byte[] content;
|
||||
// The content type to include in the ContentType header - may be the data content type, or the formatter's content type.
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CloudNative.CloudEvents
|
||||
namespace CloudNative.CloudEvents.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Utilities methods for dealing with binary data, converting between
|
||||
/// streams, arrays, Memory{T} etc.
|
||||
/// </summary>
|
||||
internal static class BinaryDataUtilities
|
||||
public static class BinaryDataUtilities
|
||||
{
|
||||
internal async static Task<byte[]> ToByteArrayAsync(Stream stream)
|
||||
public async static Task<byte[]> ToByteArrayAsync(Stream stream)
|
||||
{
|
||||
// TODO: Optimize if it's already a MemoryStream?
|
||||
var memory = new MemoryStream();
|
||||
|
@ -21,7 +21,7 @@ namespace CloudNative.CloudEvents
|
|||
return memory.ToArray();
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArray(Stream stream)
|
||||
public static byte[] ToByteArray(Stream stream)
|
||||
{
|
||||
var memory = new MemoryStream();
|
||||
stream.CopyTo(memory);
|
|
@ -6,13 +6,13 @@ using System.Net.Http.Headers;
|
|||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
|
||||
namespace CloudNative.CloudEvents
|
||||
namespace CloudNative.CloudEvents.Core
|
||||
{
|
||||
// TODO: Consider this name and namespace carefully. It really does need to be public, as all the event formatters are elsewhere.
|
||||
// But it's not ideal...
|
||||
|
||||
/// <summary>
|
||||
/// Utility and extension methods around MIME.
|
||||
/// Utility methods around MIME.
|
||||
/// </summary>
|
||||
public static class MimeUtilities
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ namespace CloudNative.CloudEvents
|
|||
/// <param name="contentType">The content type, or null if no content type is known.</param>
|
||||
/// <returns>An encoding suitable for the charset specified in <paramref name="contentType"/>,
|
||||
/// or UTF-8 if no charset has been specified.</returns>
|
||||
public static Encoding GetEncoding(this ContentType contentType) =>
|
||||
public static Encoding GetEncoding(ContentType contentType) =>
|
||||
contentType?.CharSet is string charSet ? Encoding.GetEncoding(charSet) : Encoding.UTF8;
|
||||
|
||||
/// <summary>
|
||||
|
@ -31,7 +31,7 @@ namespace CloudNative.CloudEvents
|
|||
/// </summary>
|
||||
/// <param name="headerValue">The header value to convert. May be null.</param>
|
||||
/// <returns>The converted content type, or null if <paramref name="headerValue"/> is null.</returns>
|
||||
public static ContentType ToContentType(this MediaTypeHeaderValue headerValue) =>
|
||||
public static ContentType ToContentType(MediaTypeHeaderValue headerValue) =>
|
||||
headerValue is null ? null : new ContentType(headerValue.ToString());
|
||||
|
||||
/// <summary>
|
||||
|
@ -39,7 +39,7 @@ namespace CloudNative.CloudEvents
|
|||
/// </summary>
|
||||
/// <param name="contentType">The content type to convert. May be null.</param>
|
||||
/// <returns>The converted media type header value, or null if <paramref name="contentType"/> is null.</returns>
|
||||
public static MediaTypeHeaderValue ToMediaTypeHeaderValue(this ContentType contentType)
|
||||
public static MediaTypeHeaderValue ToMediaTypeHeaderValue(ContentType contentType)
|
||||
{
|
||||
if (contentType is null)
|
||||
{
|
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2021 Cloud Native Foundation.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace CloudNative.CloudEvents.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Validation methods which are typically convenient for implementers of CloudEvent formatters
|
||||
/// and protocol bindings.
|
||||
/// </summary>
|
||||
public static class Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates that the given reference is non-null.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the value to check</typeparam>
|
||||
/// <param name="value">The reference to check for nullity</param>
|
||||
/// <param name="paramName">The parameter name to use in the exception if <paramref name="value"/> is null.
|
||||
/// May be null.</param>
|
||||
/// <returns>The value of <paramref name="value"/>, for convenient method chaining or assignment.</returns>
|
||||
public static T CheckNotNull<T>(T value, string paramName) where T : class =>
|
||||
value ?? throw new ArgumentNullException(paramName);
|
||||
|
||||
/// <summary>
|
||||
/// Validates an argument-dependent condition, throwing an exception if the check fails.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to validate; this method will throw an <see cref="ArgumentException"/> if this is false.</param>
|
||||
/// <param name="paramName">The name of the parameter being validated. May be null.</param>
|
||||
/// <param name="message">The message to use in the exception, if one is thrown.</param>
|
||||
public static void CheckArgument(bool condition, string paramName, string message)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new ArgumentException(message, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an argument-dependent condition, throwing an exception if the check fails.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to validate; this method will throw an <see cref="ArgumentException"/> if this is false.</param>
|
||||
/// <param name="paramName">The name of the parameter being validated. May be null.</param>
|
||||
/// <param name="messageFormat">The string format to use in the exception message, if one is thrown.</param>
|
||||
/// <param name="arg1">The first argument in the string format.</param>
|
||||
public static void CheckArgument(bool condition, string paramName, string messageFormat,
|
||||
object arg1)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new ArgumentException(string.Format(messageFormat, arg1), paramName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an argument-dependent condition, throwing an exception if the check fails.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to validate; this method will throw an <see cref="ArgumentException"/> if this is false.</param>
|
||||
/// <param name="paramName">The name of the parameter being validated. May be null.</param>
|
||||
/// <param name="messageFormat">The string format to use in the exception message, if one is thrown.</param>
|
||||
/// <param name="arg1">The first argument in the string format.</param>
|
||||
/// <param name="arg2">The first argument in the string format.</param>
|
||||
public static void CheckArgument(bool condition, string paramName, string messageFormat,
|
||||
object arg1, object arg2)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new ArgumentException(string.Format(messageFormat, arg1, arg2), paramName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the specified CloudEvent is valid in the same way as <see cref="CloudEvent.IsValid"/>,
|
||||
/// but throwing an <see cref="ArgumentException"/> using the given parameter name
|
||||
/// if the event is invalid. This is typically used within protocol bindings or event formatters
|
||||
/// as the last step in decoding an event, or as the first step when encoding an event.
|
||||
/// </summary
|
||||
/// <param name="cloudEvent">The event to validate.
|
||||
/// <param name="paramName">The parameter name to use in the exception if <paramref name="cloudEvent"/> is null or invalid.
|
||||
/// May be null.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="cloudEvent"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The event is invalid.</exception>
|
||||
/// <returns>A reference to the same object, for simplicity of method chaining.</returns>
|
||||
public static CloudEvent CheckCloudEventArgument(CloudEvent cloudEvent, string paramName)
|
||||
{
|
||||
CheckNotNull(cloudEvent, paramName);
|
||||
if (cloudEvent.IsValid)
|
||||
{
|
||||
return cloudEvent;
|
||||
}
|
||||
var missing = cloudEvent.SpecVersion.RequiredAttributes.Where(attr => cloudEvent[attr] is null).ToList();
|
||||
string joinedMissing = string.Join(", ", missing);
|
||||
throw new ArgumentException($"CloudEvent is missing required attributes: {joinedMissing}", paramName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright 2021 Cloud Native Foundation.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace CloudNative.CloudEvents
|
||||
{
|
||||
/// <summary>
|
||||
/// Convenient precondition methods.
|
||||
/// </summary>
|
||||
internal static class Preconditions
|
||||
{
|
||||
internal static T CheckNotNull<T>(T value, string paramName) where T : class =>
|
||||
value ?? throw new ArgumentNullException(paramName);
|
||||
|
||||
internal static void CheckArgument(bool condition, string paramName, string message)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new ArgumentException(message, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CheckArgument(bool condition, string paramName, string messageFormat,
|
||||
object arg1)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new ArgumentException(string.Format(messageFormat, arg1), paramName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CheckArgument(bool condition, string paramName, string messageFormat,
|
||||
object arg1, object arg2)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new ArgumentException(string.Format(messageFormat, arg1, arg2), paramName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Globalization;
|
||||
|
||||
|
@ -49,10 +50,8 @@ namespace CloudNative.CloudEvents
|
|||
public static bool TryParse(string input, out DateTimeOffset result)
|
||||
{
|
||||
// TODO: Check this and add a test
|
||||
if (input is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
Validation.CheckNotNull(input, nameof(input));
|
||||
|
||||
if (input.Length < MinLength) // "yyyy-MM-ddTHH:mm:ssZ" is the shortest possible value.
|
||||
{
|
||||
result = default;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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.Linq;
|
||||
using System.Net.Mime;
|
||||
|
@ -112,7 +113,7 @@ namespace CloudNative.CloudEvents.UnitTests
|
|||
Assert.Contains(CloudEventsSpecVersion.Default.SourceAttribute.Name, exception1.Message);
|
||||
Assert.DoesNotContain(CloudEventsSpecVersion.Default.TypeAttribute.Name, exception1.Message);
|
||||
|
||||
var exception2 = Assert.Throws<ArgumentException>(() => cloudEvent.ValidateForConversion("param"));
|
||||
var exception2 = Assert.Throws<ArgumentException>(() => Validation.CheckCloudEventArgument(cloudEvent, "param"));
|
||||
Assert.Equal("param", exception2.ParamName);
|
||||
Assert.Contains(CloudEventsSpecVersion.Default.IdAttribute.Name, exception1.Message);
|
||||
Assert.Contains(CloudEventsSpecVersion.Default.SourceAttribute.Name, exception1.Message);
|
||||
|
@ -130,7 +131,7 @@ namespace CloudNative.CloudEvents.UnitTests
|
|||
};
|
||||
Assert.True(cloudEvent.IsValid);
|
||||
Assert.Same(cloudEvent, cloudEvent.Validate());
|
||||
Assert.Same(cloudEvent, cloudEvent.ValidateForConversion("param"));
|
||||
Assert.Same(cloudEvent, Validation.CheckCloudEventArgument(cloudEvent, "param"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -8,7 +8,7 @@ using System.Net.Mime;
|
|||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace CloudNative.CloudEvents.UnitTests.Http
|
||||
namespace CloudNative.CloudEvents.Core.UnitTests
|
||||
{
|
||||
public class MimeUtilitiesTest
|
||||
{
|
||||
|
@ -21,9 +21,9 @@ namespace CloudNative.CloudEvents.UnitTests.Http
|
|||
public void ContentTypeConversions(string text)
|
||||
{
|
||||
var originalContentType = new ContentType(text);
|
||||
var header = originalContentType.ToMediaTypeHeaderValue();
|
||||
var header = MimeUtilities.ToMediaTypeHeaderValue(originalContentType);
|
||||
AssertEqualParts(text, header.ToString());
|
||||
var convertedContentType = header.ToContentType();
|
||||
var convertedContentType = MimeUtilities.ToContentType(header);
|
||||
AssertEqualParts(originalContentType.ToString(), convertedContentType.ToString());
|
||||
|
||||
// Conversions can end up reordering the parameters. In reality we're only
|
||||
|
@ -40,8 +40,8 @@ namespace CloudNative.CloudEvents.UnitTests.Http
|
|||
[Fact]
|
||||
public void ContentTypeConversions_Null()
|
||||
{
|
||||
Assert.Null(default(ContentType).ToMediaTypeHeaderValue());
|
||||
Assert.Null(default(MediaTypeHeaderValue).ToContentType());
|
||||
Assert.Null(MimeUtilities.ToMediaTypeHeaderValue(default(ContentType)));
|
||||
Assert.Null(MimeUtilities.ToContentType(default(MediaTypeHeaderValue)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -50,7 +50,7 @@ namespace CloudNative.CloudEvents.UnitTests.Http
|
|||
public void ContentTypeGetEncoding(string charSet)
|
||||
{
|
||||
var contentType = new ContentType($"text/plain; charset={charSet}");
|
||||
Encoding encoding = contentType.GetEncoding();
|
||||
Encoding encoding = MimeUtilities.GetEncoding(contentType);
|
||||
Assert.Equal(charSet, encoding.WebName);
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ namespace CloudNative.CloudEvents.UnitTests.Http
|
|||
public void ContentTypeGetEncoding_NoContentType()
|
||||
{
|
||||
ContentType contentType = null;
|
||||
Encoding encoding = contentType.GetEncoding();
|
||||
Encoding encoding = MimeUtilities.GetEncoding(contentType);
|
||||
Assert.Equal(Encoding.UTF8, encoding);
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ namespace CloudNative.CloudEvents.UnitTests.Http
|
|||
public void ContentTypeGetEncoding_NoCharSet()
|
||||
{
|
||||
ContentType contentType = new ContentType("text/plain");
|
||||
Encoding encoding = contentType.GetEncoding();
|
||||
Encoding encoding = MimeUtilities.GetEncoding(contentType);
|
||||
Assert.Equal(Encoding.UTF8, encoding);
|
||||
}
|
||||
|
Loading…
Reference in New Issue