internal CloudEvent ConvertToCloudEvent(Request request, CloudEventFormatter formatter) { MaybeReshapeData(request); var context = request.Context; var resource = context.Resource; var service = ValidateNotNullOrEmpty(resource.Service ?? _defaultService, "service"); var resourceName = ValidateNotNullOrEmpty(resource.Name, "resource name"); var(source, subject) = ConvertSourceAndSubject(service, resourceName, request.Data); var evt = new CloudEvent { Type = _cloudEventType, Source = source, Id = context.Id, Time = context.Timestamp, DataContentType = JsonContentType, Subject = subject }; var bytes = JsonSerializer.SerializeToUtf8Bytes(request.Data); formatter.DecodeBinaryModeEventData(bytes, evt); return(evt); }
internal CloudEvent ConvertToCloudEvent(Request request, CloudEventFormatter formatter) { MaybeReshapeData(request); var context = request.Context; var resource = context.Resource; // Default the service name if necessary resource.Service = ValidateNotNullOrEmpty(resource.Service ?? _defaultService, "service"); ValidateNotNullOrEmpty(resource.Name, "resource name"); var evt = new CloudEvent { Type = _cloudEventType, Id = context.Id, Time = context.Timestamp, DataContentType = JsonContentType }; PopulateAttributes(request, evt); var bytes = JsonSerializer.SerializeToUtf8Bytes(request.Data); formatter.DecodeBinaryModeEventData(bytes, evt); return(evt); }
/// <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))); }
/// <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))); } }
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); } }
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)); } }
/// <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); } }
/// <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))); } }
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); }