private CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable <CloudEventAttribute> extensionAttributes)
        {
            if (!record.TryGetValue(AttributeName, out var attrObj))
            {
                throw new ArgumentException($"Record has no '{AttributeName}' field");
            }
            IDictionary <string, object> recordAttributes = (IDictionary <string, object>)attrObj;

            if (!recordAttributes.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var versionId) ||
                !(versionId is string versionIdString))
            {
                throw new ArgumentException("Specification version attribute is missing");
            }
            CloudEventsSpecVersion version = CloudEventsSpecVersion.FromVersionId(versionIdString);

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

            var cloudEvent = new CloudEvent(version, extensionAttributes);

            cloudEvent.Data = record.TryGetValue(DataName, out var data) ? data : null;

            foreach (var keyValuePair in recordAttributes)
            {
                string key   = keyValuePair.Key;
                object value = keyValuePair.Value;
                if (value is null)
                {
                    continue;
                }

                if (key == CloudEventsSpecVersion.SpecVersionAttribute.Name || key == DataName)
                {
                    continue;
                }

                // The Avro schema allows the value to be a Boolean, integer, string or bytes.
                // Timestamps and URIs are represented as strings, so we just use SetAttributeFromString to handle those.
                // TODO: This does mean that any extensions of these types must have been registered beforehand.
                if (value is bool || value is int || value is byte[])
                {
                    cloudEvent[key] = value;
                }
                else if (value is string)
                {
                    cloudEvent.SetAttributeFromString(key, (string)value);
                }
                else
                {
                    throw new ArgumentException($"Invalid value type from Avro record: {value.GetType()}");
                }
            }

            return(Validation.CheckCloudEventArgument(cloudEvent, nameof(record)));
        }
Example #2
0
        public CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable <CloudEventAttribute> extensionAttributes)
        {
            if (!record.TryGetValue("attribute", out var attrObj))
            {
                return(null);
            }
            IDictionary <string, object> recordAttributes = (IDictionary <string, object>)attrObj;

            CloudEventsSpecVersion specVersion = CloudEventsSpecVersion.Default;

            if (recordAttributes.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var versionId) &&
                versionId is string versionIdString)
            {
                specVersion = CloudEventsSpecVersion.FromVersionId(versionIdString);
            }
            var cloudEvent = new CloudEvent(specVersion, extensionAttributes);

            cloudEvent.Data = record.TryGetValue(DataName, out var data) ? data : null;

            foreach (var keyValuePair in recordAttributes)
            {
                string key   = keyValuePair.Key;
                object value = keyValuePair.Value;
                if (value is null)
                {
                    continue;
                }

                if (key == CloudEventsSpecVersion.SpecVersionAttribute.Name || key == DataName)
                {
                    continue;
                }

                // The Avro schema allows the value to be a Boolean, integer, string or bytes.
                // Timestamps and URIs are represented as strings, so we just use SetAttributeFromString to handle those.
                if (value is bool || value is int || value is byte[])
                {
                    cloudEvent[key] = value;
                }
                else if (value is string)
                {
                    cloudEvent.SetAttributeFromString(key, (string)value);
                }
                else
                {
                    throw new ArgumentException($"Invalid value type from Avro record: {value.GetType()}");
                }
            }

            return(cloudEvent);
        }
        /// <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="extensions">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 ValueTask <CloudEvent> ReadCloudEventAsync(this HttpRequest httpRequest,
                                                                       CloudEventFormatter formatter,
                                                                       params CloudEventAttribute[] extensionAttributes)
        {
            if (HasCloudEventsContentType(httpRequest))
            {
                // TODO: Handle formatter being null
                return(await formatter.DecodeStructuredModeMessageAsync(httpRequest.Body, MimeUtilities.CreateContentTypeOrNull(httpRequest.ContentType), extensionAttributes).ConfigureAwait(false));
            }
            else
            {
                var headers = httpRequest.Headers;
                if (!headers.TryGetValue(HttpUtilities.SpecVersionHttpHeader, out var versionId))
                {
                    throw new ArgumentException("Request is not a CloudEvent");
                }
                var version = CloudEventsSpecVersion.FromVersionId(versionId.First());
                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)
                {
                    // 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);
                }
                return(cloudEvent);
            }
        }
        /// <summary>
        /// Converts this HTTP request into a CloudEvent object, with the given extensions,
        /// overriding the formatter.
        /// </summary>
        /// <param name="httpRequest">HTTP request</param>
        /// <param name="formatter">The event formatter to use to process the request body.</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 async ValueTask <CloudEvent> ReadCloudEventAsync(this HttpRequest httpRequest,
                                                                       ICloudEventFormatter formatter,
                                                                       params CloudEventAttribute[] extensionAttributes)
        {
            if (HasCloudEventsContentType(httpRequest))
            {
                // TODO: Handle formatter being null
                return(await formatter.DecodeStructuredEventAsync(httpRequest.Body, extensionAttributes).ConfigureAwait(false));
            }
            else
            {
                var headers = httpRequest.Headers;
                CloudEventsSpecVersion version = CloudEventsSpecVersion.Default;
                if (headers.TryGetValue(HttpUtilities.SpecVersionHttpHeader, out var values))
                {
                    string versionId = values.First();
                    version = CloudEventsSpecVersion.FromVersionId(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);
                }

                cloudEvent.DataContentType = httpRequest.ContentType;
                if (httpRequest.Body is Stream body)
                {
                    // TODO: This is a bit ugly.
                    var memoryStream = new MemoryStream();
                    await body.CopyToAsync(memoryStream).ConfigureAwait(false);

                    if (memoryStream.Length != 0)
                    {
                        cloudEvent.Data = formatter.DecodeData(memoryStream.ToArray(), cloudEvent.DataContentType);
                    }
                }
                return(cloudEvent);
            }
        }