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:
Jon Skeet 2021-03-25 11:14:14 +00:00 committed by Jon Skeet
parent 00391089d2
commit 41f639d44b
26 changed files with 251 additions and 214 deletions

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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)
{

View File

@ -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));
}
}

View File

@ -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.

View File

@ -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);

View File

@ -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)
{

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;

View File

@ -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]

View File

@ -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);
}