public void FromVersionId_Known(string versionId)
        {
            var version = CloudEventsSpecVersion.FromVersionId(versionId);

            Assert.NotNull(version);
            Assert.Equal(versionId, version !.VersionId);
        }
예제 #2
0
        /// <summary>
        /// Converts this Kafka message into a CloudEvent object.
        /// </summary>
        /// <param name="message">The Kafka message to convert. Must not be null.</param>
        /// <param name="formatter">The event formatter to use to parse the CloudEvent. Must not be null.</param>
        /// <param name="extensionAttributes">The extension attributes to use when parsing the CloudEvent. May be null.</param>
        /// <returns>A reference to a validated CloudEvent instance.</returns>
        public static CloudEvent ToCloudEvent(this Message <string, byte[]> message,
                                              CloudEventFormatter formatter, IEnumerable <CloudEventAttribute> extensionAttributes)
        {
            Validation.CheckNotNull(message, nameof(message));
            Validation.CheckNotNull(formatter, nameof(formatter));

            if (!IsCloudEvent(message))
            {
                throw new InvalidOperationException();
            }

            var contentType = ExtractContentType(message);

            CloudEvent cloudEvent;

            // Structured mode
            if (MimeUtilities.IsCloudEventsContentType(contentType))
            {
                cloudEvent = formatter.DecodeStructuredModeMessage(message.Value, new ContentType(contentType), extensionAttributes);
            }
            else
            {
                // Binary mode
                if (!(GetHeaderValue(message, SpecVersionKafkaHeader) is byte[] versionIdBytes))
                {
                    throw new ArgumentException("Request is not a CloudEvent");
                }
                string versionId = Encoding.UTF8.GetString(versionIdBytes);
                CloudEventsSpecVersion version = CloudEventsSpecVersion.FromVersionId(versionId)
                                                 ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(message));

                cloudEvent = new CloudEvent(version, extensionAttributes)
                {
                    DataContentType = contentType
                };

                foreach (var header in message.Headers.Where(h => h.Key.StartsWith(KafkaHeaderPrefix)))
                {
                    var attributeName = header.Key.Substring(KafkaHeaderPrefix.Length).ToLowerInvariant();
                    if (attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }
                    // TODO: Is this feasible?
                    var headerValue = header.GetValueBytes();
                    if (headerValue is null)
                    {
                        continue;
                    }
                    string attributeValue = Encoding.UTF8.GetString(headerValue);

                    cloudEvent.SetAttributeFromString(attributeName, attributeValue);
                }
                formatter.DecodeBinaryModeEventData(message.Value, cloudEvent);
            }

            InitPartitioningKey(message, cloudEvent);
            return(Validation.CheckCloudEventArgument(cloudEvent, nameof(message)));
        }
예제 #3
0
        public static CloudEvent ToCloudEvent(this Message <string, byte[]> message,
                                              CloudEventFormatter eventFormatter, IEnumerable <CloudEventAttribute> extensionAttributes)
        {
            if (!IsCloudEvent(message))
            {
                throw new InvalidOperationException();
            }

            var contentType = ExtractContentType(message);

            CloudEvent cloudEvent;

            // Structured mode
            if (contentType?.StartsWith(CloudEvent.MediaType, StringComparison.InvariantCultureIgnoreCase) == true)
            {
                cloudEvent = eventFormatter.DecodeStructuredModeMessage(message.Value, new ContentType(contentType), extensionAttributes);
            }
            else
            {
                // Binary mode
                if (!(GetHeaderValue(message, KafkaCloudEventMessage.SpecVersionKafkaHeader) is byte[] versionIdBytes))
                {
                    throw new ArgumentException("Request is not a CloudEvent");
                }
                string versionId = Encoding.UTF8.GetString(versionIdBytes);
                CloudEventsSpecVersion version = CloudEventsSpecVersion.FromVersionId(versionId);
                if (version is null)
                {
                    throw new ArgumentException($"Unsupported CloudEvents spec version '{versionId}'");
                }

                cloudEvent = new CloudEvent(version, extensionAttributes)
                {
                    Data            = message.Value,
                    DataContentType = contentType
                };
                foreach (var header in message.Headers.Where(h => h.Key.StartsWith(KafkaCloudEventMessage.KafkaHeaderPrefix)))
                {
                    var attributeName = header.Key.Substring(KafkaCloudEventMessage.KafkaHeaderPrefix.Length).ToLowerInvariant();
                    if (attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }
                    // TODO: Is this feasible?
                    var headerValue = header.GetValueBytes();
                    if (headerValue is null)
                    {
                        continue;
                    }
                    string attributeValue = Encoding.UTF8.GetString(headerValue);

                    cloudEvent.SetAttributeFromString(attributeName, attributeValue);
                }
            }

            InitPartitioningKey(message, cloudEvent);
            return(cloudEvent);
        }
예제 #4
0
        public void ParseEventExplicit_ValidStorageBlobCreatedCloudEvent_ShouldSucceed()
        {
            // Arrange
            const CloudEventsSpecVersion cloudEventsVersion = CloudEventsSpecVersion.V0_1;
            const string eventType = "Microsoft.Storage.BlobCreated",
                         source    = "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-account}#blobServices/default/containers/{storage-container}/blobs/{new-file}",
                         eventId   = "173d9985-401e-0075-2497-de268c06ff25",
                         eventTime = "2018-04-28T02:18:47.1281675Z";

            const string api             = "PutBlockList",
                         clientRequestId = "6d79dbfb-0e37-4fc4-981f-442c9ca65760",
                         requestId       = "831e1650-001e-001b-66ab-eeb76e000000",
                         etag            = "0x8D4BCC2E4835CD0",
                         contentType     = "application/octet-stream",
                         blobType        = "BlockBlob",
                         url             = "https://oc2d2817345i60006.blob.core.windows.net/oc2d2817345i200097container/oc2d2817345i20002296blob",
                         sequencer       = "00000000000004420000000000028963",
                         batchId         = "b68529f3-68cd-4744-baa4-3c0498ec19f0";

            const long contentLength = 524_288;

            string rawEvent = EventSamples.AzureBlobStorageCreatedCloudEvent;

            // Act
            EventBatch <Event> eventBatch = EventParser.Parse(rawEvent);

            // Assert
            Assert.NotNull(eventBatch);
            Assert.NotNull(eventBatch.Events);
            CloudEvent cloudEvent = Assert.Single(eventBatch.Events);

            Assert.NotNull(cloudEvent);
            Assert.Equal(cloudEventsVersion, cloudEvent.SpecVersion);
            Assert.Equal(eventType, cloudEvent.Type);
            Assert.Equal(source, cloudEvent.Source.OriginalString);
            Assert.Equal(eventId, cloudEvent.Id);
            Assert.Equal(eventTime, cloudEvent.Time.GetValueOrDefault().ToString("O"));

            var eventPayload = cloudEvent.GetPayload <StorageBlobCreatedEventData>();

            Assert.NotNull(eventPayload);
            Assert.Equal(api, eventPayload.Api);
            Assert.Equal(clientRequestId, eventPayload.ClientRequestId);
            Assert.Equal(requestId, eventPayload.RequestId);
            Assert.Equal(etag, eventPayload.ETag);
            Assert.Equal(contentType, eventPayload.ContentType);
            Assert.Equal(contentLength, eventPayload.ContentLength);
            Assert.Equal(blobType, eventPayload.BlobType);
            Assert.Equal(url, eventPayload.Url);
            Assert.Equal(sequencer, eventPayload.Sequencer);
            Assert.NotNull(eventPayload.StorageDiagnostics);
            var storageDiagnostics = Assert.IsType <JObject>(eventPayload.StorageDiagnostics);

            Assert.Equal(batchId, storageDiagnostics["batchId"]);
        }
        /// <summary>
        /// Converts this HTTP request into a CloudEvent object.
        /// </summary>
        /// <param name="httpRequest">The HTTP request to decode. Must not be null.</param>
        /// <param name="formatter">The event formatter to use to process the request body. Must not be null.</param>
        /// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
        /// <returns>The decoded CloudEvent.</returns>
        /// <exception cref="ArgumentException">The request does not contain a CloudEvent.</exception>
        public static async Task <CloudEvent> ToCloudEventAsync(
            this HttpRequest httpRequest,
            CloudEventFormatter formatter,
            IEnumerable <CloudEventAttribute>?extensionAttributes)
        {
            Validation.CheckNotNull(httpRequest, nameof(httpRequest));
            Validation.CheckNotNull(formatter, nameof(formatter));
            if (HasCloudEventsContentType(httpRequest))
            {
                var contentType = MimeUtilities.CreateContentTypeOrNull(httpRequest.ContentType);
                return(await formatter.DecodeStructuredModeMessageAsync(httpRequest.Body, contentType, extensionAttributes).ConfigureAwait(false));
            }
            else
            {
                var headers = httpRequest.Headers;
                headers.TryGetValue(HttpUtilities.SpecVersionHttpHeader, out var versionId);
                if (versionId.Count == 0)
                {
                    throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(httpRequest));
                }
                var version = CloudEventsSpecVersion.FromVersionId(versionId.FirstOrDefault())
                              ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(httpRequest));

                if (version is null)
                {
                    throw new ArgumentException($"Unsupported CloudEvents spec version '{versionId.First()}'");
                }

                var cloudEvent = new CloudEvent(version, extensionAttributes);
                foreach (var header in headers)
                {
                    string?attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key);
                    if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }
                    string attributeValue = HttpUtilities.DecodeHeaderValue(header.Value.First());

                    cloudEvent.SetAttributeFromString(attributeName, attributeValue);
                }

                cloudEvent.DataContentType = httpRequest.ContentType;
                if (httpRequest.Body is Stream body)
                {
                    ReadOnlyMemory <byte> data = await BinaryDataUtilities.ToReadOnlyMemoryAsync(body).ConfigureAwait(false);

                    formatter.DecodeBinaryModeEventData(data, cloudEvent);
                }
                return(Validation.CheckCloudEventArgument(cloudEvent, nameof(httpRequest)));
            }
        }
        private async static Task <CloudEvent> ToCloudEventAsyncImpl(HttpListenerRequest httpListenerRequest,
                                                                     CloudEventFormatter formatter, IEnumerable <CloudEventAttribute>?extensionAttributes, bool async)
        {
            Validation.CheckNotNull(httpListenerRequest, nameof(httpListenerRequest));
            Validation.CheckNotNull(formatter, nameof(formatter));
            var stream = httpListenerRequest.InputStream;

            if (HasCloudEventsContentType(httpListenerRequest))
            {
                var contentType = MimeUtilities.CreateContentTypeOrNull(httpListenerRequest.ContentType);
                return(async
                    ? await formatter.DecodeStructuredModeMessageAsync(stream, contentType, extensionAttributes).ConfigureAwait(false)
                    : formatter.DecodeStructuredModeMessage(stream, contentType, extensionAttributes));
            }
            else
            {
                string versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader];
                if (versionId is null)
                {
                    throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(httpListenerRequest));
                }
                var version = CloudEventsSpecVersion.FromVersionId(versionId)
                              ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(httpListenerRequest));

                var cloudEvent = new CloudEvent(version, extensionAttributes);
                var headers    = httpListenerRequest.Headers;
                foreach (var key in headers.AllKeys)
                {
                    string?attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key);
                    if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }
                    string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]);
                    cloudEvent.SetAttributeFromString(attributeName, attributeValue);
                }

                // The data content type should not have been set via a "ce-" header; instead,
                // it's in the regular content type.
                cloudEvent.DataContentType = httpListenerRequest.ContentType;

                ReadOnlyMemory <byte> data = async
                    ? await BinaryDataUtilities.ToReadOnlyMemoryAsync(stream).ConfigureAwait(false)
                    : BinaryDataUtilities.ToReadOnlyMemory(stream);

                formatter.DecodeBinaryModeEventData(data, cloudEvent);
                return(Validation.CheckCloudEventArgument(cloudEvent, nameof(httpListenerRequest)));
            }
        }
 /// <summary>
 ///     Publish a raw JSON payload as CloudEvent event.
 /// </summary>
 /// <param name="specVersion">The version of the CloudEvents specification which the event uses.</param>
 /// <param name="eventId">The unique identifier of the event.</param>
 /// <param name="eventType">The type of the event.</param>
 /// <param name="source">The source that identifies the context in which an event happened.</param>
 /// <param name="eventBody">The body of the event.</param>
 public async Task PublishRawCloudEventAsync(
     CloudEventsSpecVersion specVersion,
     string eventId,
     string eventType,
     Uri source,
     string eventBody)
 {
     await PublishRawCloudEventAsync(
         specVersion,
         eventId,
         eventType,
         source,
         eventBody : eventBody,
         eventSubject : "/");
 }
예제 #8
0
        private static async Task <CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent content,
                                                                         CloudEventFormatter formatter, IEnumerable <CloudEventAttribute> extensionAttributes)
        {
            if (HasCloudEventsContentType(content))
            {
                // FIXME: Handle no formatter being specified.
                var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);

                return(await formatter.DecodeStructuredModeMessageAsync(stream, content.Headers.ContentType.ToContentType(), extensionAttributes).ConfigureAwait(false));
            }
            else
            {
                string versionId = headers.Contains(HttpUtilities.SpecVersionHttpHeader)
                    ? headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First()
                    : null;
                if (versionId is null)
                {
                    throw new ArgumentException("Request is not a CloudEvent");
                }
                var version = CloudEventsSpecVersion.FromVersionId(versionId);
                if (version is null)
                {
                    throw new ArgumentException($"Unsupported CloudEvents spec version '{versionId}'");
                }

                var cloudEvent = new CloudEvent(version, extensionAttributes);
                foreach (var header in headers)
                {
                    string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key);
                    if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }
                    string attributeValue = HttpUtilities.DecodeHeaderValue(header.Value.First());

                    cloudEvent.SetAttributeFromString(attributeName, attributeValue);
                }
                if (content is object)
                {
                    // TODO: Should this just be the media type? We probably need to take a full audit of this...
                    cloudEvent.DataContentType = content.Headers?.ContentType?.ToString();
                    var data = await content.ReadAsByteArrayAsync().ConfigureAwait(false);

                    formatter.DecodeBinaryModeEventData(data, cloudEvent);
                }
                return(cloudEvent);
            }
        }
 /// <summary>
 ///     Publish a raw JSON payload as CloudEvent event.
 /// </summary>
 /// <param name="specVersion">The version of the CloudEvents specification which the event uses.</param>
 /// <param name="eventId">The unique identifier of the event.</param>
 /// <param name="eventType">The type of the event.</param>
 /// <param name="source">The source that identifies the context in which an event happened.</param>
 /// <param name="eventBody">The body of the event.</param>
 /// <param name="eventSubject">The value that describes the subject of the event in the context of the event producer.</param>
 public async Task PublishRawCloudEventAsync(
     CloudEventsSpecVersion specVersion,
     string eventId,
     string eventType,
     Uri source,
     string eventBody,
     string eventSubject)
 {
     await PublishRawCloudEventAsync(
         specVersion,
         eventId,
         eventType,
         source,
         eventBody : eventBody,
         eventSubject : eventSubject,
         eventTime : DateTimeOffset.UtcNow);
 }
예제 #10
0
        private static async Task <CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent content,
                                                                         CloudEventFormatter formatter, IEnumerable <CloudEventAttribute>?extensionAttributes, string paramName)
        {
            Validation.CheckNotNull(formatter, nameof(formatter));

            if (HasCloudEventsContentType(content))
            {
                var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);

                return(await formatter.DecodeStructuredModeMessageAsync(stream, MimeUtilities.ToContentType(content.Headers.ContentType), extensionAttributes).ConfigureAwait(false));
            }
            else
            {
                string?versionId = headers.Contains(HttpUtilities.SpecVersionHttpHeader)
                    ? headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First()
                    : null;
                if (versionId is null)
                {
                    throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(paramName));
                }
                var version = CloudEventsSpecVersion.FromVersionId(versionId)
                              ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", paramName);

                var cloudEvent = new CloudEvent(version, extensionAttributes);
                foreach (var header in headers)
                {
                    string?attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key);
                    if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }
                    string attributeValue = HttpUtilities.DecodeHeaderValue(header.Value.First());

                    cloudEvent.SetAttributeFromString(attributeName, attributeValue);
                }
                if (content is object)
                {
                    // TODO: Should this just be the media type? We probably need to take a full audit of this...
                    cloudEvent.DataContentType = content.Headers?.ContentType?.ToString();
                    var data = await content.ReadAsByteArrayAsync().ConfigureAwait(false);

                    formatter.DecodeBinaryModeEventData(data, cloudEvent);
                }
                return(Validation.CheckCloudEventArgument(cloudEvent, paramName));
            }
        }
예제 #11
0
        private CloudEvent DecodeJObject(JObject jObject, IEnumerable <CloudEventAttribute> extensionAttributes = null)
        {
            if (!jObject.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var specVersionToken) ||
                specVersionToken.Type != JTokenType.String)
            {
                throw new ArgumentException($"Structured mode content does not represent a CloudEvent");
            }
            var specVersion = CloudEventsSpecVersion.FromVersionId((string)specVersionToken)
                              ?? throw new ArgumentException($"Unsupported CloudEvents spec version '{(string)specVersionToken}'");

            var cloudEvent = new CloudEvent(specVersion, extensionAttributes);

            PopulateAttributesFromStructuredEvent(cloudEvent, jObject);
            PopulateDataFromStructuredEvent(cloudEvent, jObject);
            // "data" is always the parameter from the public method. It's annoying not to be able to use
            // nameof here, but this will give the appropriate result.
            return(Validation.CheckCloudEventArgument(cloudEvent, "data"));
        }
        /// <summary>
        ///     Publish a raw JSON payload as CloudEvent event.
        /// </summary>
        /// <param name="specVersion">The version of the CloudEvents specification which the event uses.</param>
        /// <param name="eventId">The unique identifier of the event.</param>
        /// <param name="eventType">The type of the event.</param>
        /// <param name="source">The source that identifies the context in which an event happened.</param>
        /// <param name="eventBody">The body of the event.</param>
        /// <param name="eventSubject">The value that describes the subject of the event in the context of the event producer.</param>
        /// <param name="eventTime">The timestamp of when the occurrence happened.</param>
        public async Task PublishRawCloudEventAsync(
            CloudEventsSpecVersion specVersion,
            string eventId,
            string eventType,
            Uri source,
            string eventBody,
            string eventSubject,
            DateTimeOffset eventTime)
        {
            var cloudEvent = new CloudEvent(specVersion, eventType, source, id: eventId, time: eventTime.DateTime)
            {
                Subject         = eventSubject,
                Data            = eventBody,
                DataContentType = new ContentType("application/json")
            };

            await PublishAsync(cloudEvent);
        }
예제 #13
0
        /// <summary>
        /// Converts this listener request into a CloudEvent object, with the given extension attributes.
        /// </summary>
        /// <param name="httpListenerRequest">Listener request</param>
        /// <param name="formatter"></param>
        /// <param name="extensions">List of extension instances</param>
        /// <returns>A CloudEvent instance or 'null' if the request message doesn't hold a CloudEvent</returns>
        public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest,
                                              ICloudEventFormatter formatter, params CloudEventAttribute[] extensionAttributes)
        {
            if (HasCloudEventsContentType(httpListenerRequest))
            {
                // FIXME: Handle no formatter being specified.
                return(formatter.DecodeStructuredEvent(httpListenerRequest.InputStream, extensionAttributes));
            }
            else
            {
                CloudEventsSpecVersion version = CloudEventsSpecVersion.Default;
                if (httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader] is string versionId)
                {
                    version = CloudEventsSpecVersion.FromVersionId(versionId);
                }

                var cloudEvent = new CloudEvent(version, extensionAttributes);
                var headers    = httpListenerRequest.Headers;
                foreach (var key in headers.AllKeys)
                {
                    string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key);
                    if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }
                    string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]);
                    cloudEvent.SetAttributeFromString(attributeName, attributeValue);
                }

                // TODO: Check that this doesn't come through as a header already
                cloudEvent.DataContentType = httpListenerRequest.ContentType;

                // TODO: This is a bit ugly.
                var memoryStream = new MemoryStream();
                httpListenerRequest.InputStream.CopyTo(memoryStream);
                if (memoryStream.Length != 0)
                {
                    cloudEvent.Data = formatter.DecodeData(memoryStream.ToArray(), cloudEvent.DataContentType);
                }
                return(cloudEvent);
            }
        }
예제 #14
0
        private CloudEvent DecodeJObject(JObject jObject, IEnumerable <CloudEventAttribute> extensionAttributes = null)
        {
            if (!jObject.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var specVersionToken) ||
                specVersionToken.Type != JTokenType.String)
            {
                throw new ArgumentException($"Structured mode content does not represent a CloudEvent");
            }
            var specVersion = CloudEventsSpecVersion.FromVersionId((string)specVersionToken);

            if (specVersion is null)
            {
                throw new ArgumentException($"Unsupported CloudEvents spec version '{(string)specVersionToken}'");
            }

            var cloudEvent = new CloudEvent(specVersion, extensionAttributes);

            PopulateAttributesFromStructuredEvent(cloudEvent, jObject);
            PopulateDataFromStructuredEvent(cloudEvent, jObject);
            return(cloudEvent.Validate());
        }
예제 #15
0
        /// <summary>
        /// Converts this listener request into a CloudEvent object, with the given extension attributes.
        /// </summary>
        /// <param name="httpListenerRequest">Listener request</param>
        /// <param name="formatter"></param>
        /// <param name="extensions">List of extension instances</param>
        /// <returns>The CloudEvent corresponding to the given request.</returns>
        /// <exception cref="ArgumentException">The request does not represent a CloudEvent,
        /// or the event's specification version is not supported,
        /// or the event formatter cannot interpret it.</exception>
        public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest,
                                              CloudEventFormatter formatter, params CloudEventAttribute[] extensionAttributes)
        {
            if (HasCloudEventsContentType(httpListenerRequest))
            {
                return(formatter.DecodeStructuredModeMessage(httpListenerRequest.InputStream, MimeUtilities.CreateContentTypeOrNull(httpListenerRequest.ContentType), extensionAttributes));
            }
            else
            {
                string versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader];
                if (versionId is null)
                {
                    throw new ArgumentException("Request is not a CloudEvent");
                }
                var version = CloudEventsSpecVersion.FromVersionId(versionId);
                if (version is null)
                {
                    throw new ArgumentException($"Unsupported CloudEvents spec version '{versionId}'");
                }

                var cloudEvent = new CloudEvent(version, extensionAttributes);
                var headers    = httpListenerRequest.Headers;
                foreach (var key in headers.AllKeys)
                {
                    string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key);
                    if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }
                    string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]);
                    cloudEvent.SetAttributeFromString(attributeName, attributeValue);
                }

                // The data content type should not have been set via a "ce-" header; instead,
                // it's in the regular content type.
                cloudEvent.DataContentType = httpListenerRequest.ContentType;

                formatter.DecodeBinaryModeEventData(BinaryDataUtilities.ToByteArray(httpListenerRequest.InputStream), cloudEvent);
                return(cloudEvent);
            }
        }
예제 #16
0
        // TODO: Override the other methods

        private CloudEvent DecodeJsonElement(JsonElement element, IEnumerable <CloudEventAttribute> extensionAttributes, string paramName)
        {
            if (element.ValueKind != JsonValueKind.Object)
            {
                throw new ArgumentException($"Cannot decode JSON element of kind '{element.ValueKind}' as CloudEvent");
            }

            if (!element.TryGetProperty(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var specVersionProperty) ||
                specVersionProperty.ValueKind != JsonValueKind.String)
            {
                throw new ArgumentException($"Structured mode content does not represent a CloudEvent");
            }
            var specVersion = CloudEventsSpecVersion.FromVersionId(specVersionProperty.GetString())
                              ?? throw new ArgumentException($"Unsupported CloudEvents spec version '{specVersionProperty.GetString()}'");

            var cloudEvent = new CloudEvent(specVersion, extensionAttributes);

            PopulateAttributesFromStructuredEvent(cloudEvent, element);
            PopulateDataFromStructuredEvent(cloudEvent, element);
            return(Validation.CheckCloudEventArgument(cloudEvent, paramName));
        }
        public void SerializedCloudEvent_AsJObject_IsCloudEvent(CloudEventsSpecVersion specVersion)
        {
            // Arrange
            var cloudEvent = new CloudEvent(
                specVersion,
                $"event-type-{Guid.NewGuid()}",
                new Uri("http://test-host"),
                id: $"event-id-{Guid.NewGuid()}")
            {
                Data            = $"event-data-{Guid.NewGuid()}",
                DataContentType = new ContentType("text/plain")
            };
            var jsonFormatter = new JsonEventFormatter();

            byte[]  serialized = jsonFormatter.EncodeStructuredEvent(cloudEvent, out ContentType contentType);
            JObject jObject    = JObject.Parse(Encoding.UTF8.GetString(serialized));

            // Act
            bool isCloudEvent = jObject.IsCloudEvent();

            // Assert
            Assert.True(isCloudEvent, "Serialized CloudEvent object should be evaluated as CloudEvent schema");
        }
예제 #18
0
        public static CloudEvent ToCloudEvent(this Message message,
                                              CloudEventFormatter formatter,
                                              params CloudEventAttribute[] extensionAttributes)
        {
            if (HasCloudEventsContentType(message, out var contentType))
            {
                return(formatter.DecodeStructuredModeMessage(new MemoryStream((byte[])message.Body), new ContentType(contentType), extensionAttributes));
            }
            else
            {
                var propertyMap = message.ApplicationProperties.Map;
                if (!propertyMap.TryGetValue(SpecVersionAmqpHeader, out var versionId) || !(versionId is string versionIdText))
                {
                    throw new ArgumentException("Request is not a CloudEvent");
                }
                var version = CloudEventsSpecVersion.FromVersionId(versionIdText);
                if (version is null)
                {
                    throw new ArgumentException($"Unsupported CloudEvents spec version '{versionIdText}'");
                }

                var cloudEvent = new CloudEvent(version, extensionAttributes)
                {
                    Data            = message.Body,
                    DataContentType = message.Properties.ContentType
                };

                foreach (var property in propertyMap)
                {
                    if (!(property.Key is string key && key.StartsWith(AmqpHeaderPrefix)))
                    {
                        continue;
                    }
                    string attributeName = key.Substring(AmqpHeaderPrefix.Length).ToLowerInvariant();

                    // We've already dealt with the spec version.
                    if (attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }

                    // Timestamps are serialized via DateTime instead of DateTimeOffset.
                    if (property.Value is DateTime dt)
                    {
                        if (dt.Kind != DateTimeKind.Utc)
                        {
                            // This should only happen for MinValue and MaxValue...
                            // just respecify as UTC. (We could add validation that it really
                            // *is* MinValue or MaxValue if we wanted to.)
                            dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
                        }
                        cloudEvent[attributeName] = (DateTimeOffset)dt;
                    }
                    // URIs are serialized as strings, but we need to convert them back to URIs.
                    // It's simplest to let CloudEvent do this for us.
                    else if (property.Value is string text)
                    {
                        cloudEvent.SetAttributeFromString(attributeName, text);
                    }
                    else
                    {
                        cloudEvent[attributeName] = property.Value;
                    }
                }
                return(cloudEvent);
            }
        }
예제 #19
0
        // TODO: If we make this private, we'll have significantly more control over what token types we see.
        // For example, we could turn off date parsing entirely, and we may never get "Uri" tokens either.
        public CloudEvent DecodeJObject(JObject jObject, IEnumerable <CloudEventAttribute> extensionAttributes = null)
        {
            CloudEventsSpecVersion specVersion = CloudEventsSpecVersion.Default;

            if (jObject.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var specVersionToken))
            {
                string versionId = (string)specVersionToken;
                specVersion = CloudEventsSpecVersion.FromVersionId(versionId);
                // TODO: Throw if specVersion is null?
            }

            var cloudEvent = new CloudEvent(specVersion, extensionAttributes);

            foreach (var keyValuePair in jObject)
            {
                var key   = keyValuePair.Key;
                var value = keyValuePair.Value;

                // Skip the spec version attribute, which we've already taken account of.
                if (key == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                {
                    continue;
                }

                // TODO: Is the data_base64 name version-specific?
                if (specVersion == CloudEventsSpecVersion.V1_0 && key == DataBase64)
                {
                    // Handle base64 encoded binaries
                    cloudEvent.Data = Convert.FromBase64String((string)value);
                    continue;
                }
                if (key == Data)
                {
                    // FIXME: Deserialize where appropriate.
                    // Consider whether there are any options here to consider beyond "string" and "object".
                    // (e.g. arrays, numbers etc).
                    // Note: the cast to "object" is important here, otherwise the string branch is implicitly
                    // converted back to JToken...
                    cloudEvent.Data = value.Type == JTokenType.String ? (string)value : (object)value;
                    continue;
                }

                var attribute = cloudEvent.GetAttribute(key);

                // Set the attribute in the event, taking account of mismatches between the type in the JObject
                // and the attribute type as best we can.

                // TODO: This currently performs more conversions than it really should, in the cause of simplicity.
                // We basically need a matrix of "attribute type vs token type" but that's rather complicated.

                string attributeValue = value.Type switch
                {
                    JTokenType.String => (string)value,
                    JTokenType.Date => CloudEventAttributeType.Timestamp.Format((DateTimeOffset)value),
                    JTokenType.Uri => CloudEventAttributeType.UriReference.Format((Uri)value),
                    JTokenType.Null => null, // TODO: Check we want to do this. It's a bit weird.
                    JTokenType.Integer => CloudEventAttributeType.Integer.Format((int)value),
                    _ => throw new ArgumentException($"Invalid token type '{value.Type}' for CloudEvent attribute")
                };

                cloudEvent.SetAttributeFromString(key, attributeValue);
            }

            return(cloudEvent);
        }
예제 #20
0
        /// <summary>
        /// Converts this AMQP message into a CloudEvent object.
        /// </summary>
        /// <param name="message">The AMQP message to convert. Must not be null.</param>
        /// <param name="formatter">The event formatter to use to parse the CloudEvent. Must not be null.</param>
        /// <param name="extensionAttributes">The extension attributes to use when parsing the CloudEvent. May be null.</param>
        /// <returns>A reference to a validated CloudEvent instance.</returns>
        public static CloudEvent ToCloudEvent(
            this Message message,
            CloudEventFormatter formatter,
            IEnumerable <CloudEventAttribute> extensionAttributes)
        {
            Validation.CheckNotNull(message, nameof(message));
            Validation.CheckNotNull(formatter, nameof(formatter));

            if (HasCloudEventsContentType(message, out var contentType))
            {
                return(formatter.DecodeStructuredModeMessage(new MemoryStream((byte[])message.Body), new ContentType(contentType), extensionAttributes));
            }
            else
            {
                var propertyMap = message.ApplicationProperties.Map;
                if (!propertyMap.TryGetValue(SpecVersionAmqpHeader, out var versionId))
                {
                    throw new ArgumentException("Request is not a CloudEvent");
                }

                var version = CloudEventsSpecVersion.FromVersionId(versionId as string)
                              ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(message));

                var cloudEvent = new CloudEvent(version, extensionAttributes)
                {
                    DataContentType = message.Properties.ContentType
                };

                foreach (var property in propertyMap)
                {
                    if (!(property.Key is string key && key.StartsWith(AmqpHeaderPrefix)))
                    {
                        continue;
                    }
                    string attributeName = key.Substring(AmqpHeaderPrefix.Length).ToLowerInvariant();

                    // We've already dealt with the spec version.
                    if (attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                    {
                        continue;
                    }

                    // Timestamps are serialized via DateTime instead of DateTimeOffset.
                    if (property.Value is DateTime dt)
                    {
                        if (dt.Kind != DateTimeKind.Utc)
                        {
                            // This should only happen for MinValue and MaxValue...
                            // just respecify as UTC. (We could add validation that it really
                            // *is* MinValue or MaxValue if we wanted to.)
                            dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
                        }
                        cloudEvent[attributeName] = (DateTimeOffset)dt;
                    }
                    // URIs are serialized as strings, but we need to convert them back to URIs.
                    // It's simplest to let CloudEvent do this for us.
                    else if (property.Value is string text)
                    {
                        cloudEvent.SetAttributeFromString(attributeName, text);
                    }
                    else
                    {
                        cloudEvent[attributeName] = property.Value;
                    }
                }
                // Populate the data after the rest of the CloudEvent
                if (message.BodySection is Data data)
                {
                    // Note: Fetching the Binary property will always retrieve the data. It will
                    // be copied from the Buffer property if necessary.
                    formatter.DecodeBinaryModeEventData(data.Binary, cloudEvent);
                }
                else if (message.BodySection is object)
                {
                    throw new ArgumentException("Binary mode data in AMQP message must be in the application data section");
                }

                return(Validation.CheckCloudEventArgument(cloudEvent, nameof(message)));
            }
        }
 public void FromVersionId_Unknown(string versionId) =>
 Assert.Null(CloudEventsSpecVersion.FromVersionId(versionId));
예제 #22
0
 /// <inheritdoc/>
 public virtual ICloudEventBuilder WithSpecVersion(CloudEventsSpecVersion specVersion)
 {
     this.SpecVersion = specVersion;
     return(this);
 }
        private static CloudEvent BinaryToCloudEvent(
            Message message,
            CloudEventFormatter formatter,
            IEnumerable <CloudEventAttribute>?extensionAttributes
            )
        {
            var propertyMap = message.UserProperties;

            if (!propertyMap.TryGetValue(Constants.SpecVersionPropertyKey, out var versionId))
            {
                throw new ArgumentException("Request is not a CloudEvent", nameof(message));
            }

            var version = CloudEventsSpecVersion.FromVersionId(versionId as string)
                          ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(message));

            var cloudEvent = new CloudEvent(version, extensionAttributes)
            {
                Id = message.MessageId,
                DataContentType = message.ContentType,
            };

            foreach (var property in propertyMap)
            {
                if (!property.Key.StartsWith(Constants.PropertyKeyPrefix))
                {
                    continue;
                }

                var attributeName = property.Key.Substring(Constants.PropertyKeyPrefix.Length).ToLowerInvariant();

                // We've already dealt with the spec version.
                if (attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
                {
                    continue;
                }

                // Timestamps are serialized via DateTime instead of DateTimeOffset.
                if (property.Value is DateTime dt)
                {
                    if (dt.Kind != DateTimeKind.Utc)
                    {
                        // This should only happen for MinValue and MaxValue...
                        // just respecify as UTC. (We could add validation that it really
                        // *is* MinValue or MaxValue if we wanted to.)
                        dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
                    }

                    cloudEvent[attributeName] = (DateTimeOffset)dt;
                }

                // URIs are serialized as strings, but we need to convert them back to URIs.
                // It's simplest to let CloudEvent do this for us.
                else if (property.Value is string text)
                {
                    cloudEvent.SetAttributeFromString(attributeName, text);
                }
                else
                {
                    cloudEvent[attributeName] = property.Value;
                }
            }

            formatter.DecodeBinaryModeEventData(message.Body, cloudEvent);

            Validation.CheckCloudEventArgument(cloudEvent, nameof(message));

            return(cloudEvent);
        }