public void FromVersionId_Known(string versionId) { var version = CloudEventsSpecVersion.FromVersionId(versionId); Assert.NotNull(version); Assert.Equal(versionId, version !.VersionId); }
/// <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))); }
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); }
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 : "/"); }
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); }
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)); } }
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); }
/// <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); } }
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()); }
/// <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); } }
// 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"); }
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); } }
// 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); }
/// <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));
/// <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); }