public void CreateV0_1() { var cloudEvent = new CloudEvent(CloudEventsSpecVersion.V0_1, "com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["comexampleextension2"] = new { othervalue = 5 }; Assert.Equal(CloudEventsSpecVersion.V0_1, cloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", cloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); Assert.Equal("A234-1234-1234", cloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), cloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), cloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", cloudEvent.Data); var attr = cloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); Assert.Equal(5, (int)((dynamic)attr["comexampleextension2"]).othervalue); }
public void CreateBaseEvent2() { var cloudEvent = new CloudEvent( "com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123"), "A234-1234-1234", sampleTimestamp) { DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["comexampleextension2"] = new { othervalue = 5 }; Assert.Equal(CloudEventsSpecVersion.Default, cloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", cloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); Assert.Equal("A234-1234-1234", cloudEvent.Id); AssertTimestampsEqual("2018-04-05T17:31:00Z", cloudEvent.Time.Value); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), cloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", cloudEvent.Data); var attr = cloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); Assert.Equal(5, (int)((dynamic)attr["comexampleextension2"]).othervalue); }
public void CreateV0_2ConvertToV1_0() { var cloudEvent = new CloudEvent(CloudEventsSpecVersion.V0_2, "com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = sampleTimestamp, DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; cloudEvent = cloudEvent.WithSpecVersion(CloudEventsSpecVersion.V1_0); Assert.Equal(CloudEventsSpecVersion.V1_0, cloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", cloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), cloudEvent.Source); Assert.Equal("A234-1234-1234", cloudEvent.Id); AssertTimestampsEqual("2018-04-05T17:31:00Z", cloudEvent.Time.Value); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), cloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", cloudEvent.Data); var attr = cloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); }
public async Task Handle(CloudEvent <Result> cloudEvent) { _logger.LogDebug("Handling new event {CloudEvent}", cloudEvent); try { // Get the result from the decrypted attributes var encryptedKey = cloudEvent.GetAttributes().Single(x => x.Key == EncryptedKeyCloudEventExtension.EncryptedKeyExtension).Value.ToString(); var encryptedResult = cloudEvent.GetAttributes().Single(x => x.Key == EncryptedResultCloudEventExtension.EncryptedResultExtension).Value .ToString(); // In the future we may get multiple results, one for each supported platform List <Result> results; try { var plainResultsString = _decryptor.Decrypt(encryptedResult, encryptedKey); results = JsonConvert.DeserializeObject <List <Result> >(plainResultsString); } catch (Exception e) { _logger.LogError(e, "Failed to decrypt result from encrypted key {EncryptedKey} and encrypted result {EncryptedResult}", encryptedKey, encryptedResult); throw new Exception("Failed to decrypt result", e); } _logger.LogDebug("Decrypted results OK. Received results for {PlatformCount} different platforms", results.Count); // Save each result separately foreach (var result in results) { var fileName = result.ToFileName(); _logger.LogDebug("Generated file name {FileName} from result", fileName); // Store score and details as metadata to allow easier search in the future var(sanitizedMetadata, originalMetadata) = GetMetadata(result); var resultFileName = await SaveResult(fileName, JsonConvert.SerializeObject(result, Formatting.Indented), sanitizedMetadata); await SaveMetadata(fileName, originalMetadata, resultFileName); } } catch (Exception e) { _logger.LogError(e, "Failed to handle event"); throw; } }
/// <inheritdoc/> public virtual CloudEvent Build() { CloudEvent e = new CloudEvent(this.SpecVersion, this.Type, this.Source, this.Id, this.Time, this.Extensions.ToArray()) { Data = this.Data, DataContentType = this.DataContentType }; if (!string.IsNullOrWhiteSpace(this.Subject)) { e.Subject = this.Subject; } if (this.Attributes != null) { IDictionary <string, object> eventAttributes = e.GetAttributes(); foreach (KeyValuePair <string, object> kvp in this.Attributes) { if (!eventAttributes.ContainsKey(kvp.Key)) { eventAttributes[kvp.Key] = kvp.Value; } } } return(e); }
public static CloudEvent ToCloudEvent(this Message <string, byte[]> message, ICloudEventFormatter eventFormatter = null, params ICloudEventExtension[] extensions) { if (!IsCloudEvent(message)) { throw new InvalidOperationException(); } var contentType = ExtractContentType(message); CloudEvent cloudEvent; if (!string.IsNullOrEmpty(contentType) && contentType.StartsWith(CloudEvent.MediaType, StringComparison.InvariantCultureIgnoreCase)) { // structured mode if (eventFormatter == null) { if (contentType.EndsWith(JsonEventFormatter.MediaTypeSuffix, StringComparison.InvariantCultureIgnoreCase)) { eventFormatter = _jsonFormatter; } else { throw new InvalidOperationException("Not supported CloudEvents media formatter."); } } cloudEvent = _jsonFormatter.DecodeStructuredEvent(message.Value, extensions); } else { // binary mode var specVersion = ExtractVersion(message); cloudEvent = new CloudEvent(specVersion, extensions); var attributes = cloudEvent.GetAttributes(); var cloudEventHeaders = message.Headers.Where(h => h.Key.StartsWith(KafkaCloudEventMessage.KafkaHeaderPerfix)); foreach (var header in cloudEventHeaders) { if (string.Equals(header.Key, SpecVersionKafkaHeader1, StringComparison.InvariantCultureIgnoreCase) || string.Equals(header.Key, SpecVersionKafkaHeader2, StringComparison.InvariantCultureIgnoreCase)) { continue; } var attributeName = header.Key.Substring(KafkaCloudEventMessage.KafkaHeaderPerfix.Length); attributes.Add(attributeName, eventFormatter.DecodeAttribute(specVersion, attributeName, header.GetValueBytes(), extensions)); } cloudEvent.DataContentType = contentType != null ? new ContentType(contentType) : null; cloudEvent.Data = message.Value; } InitPartitioningKey(message, cloudEvent); return(cloudEvent); }
private void MapHeaders(CloudEvent cloudEvent) { var ignoreKeys = new List <string>(3) { CloudEventAttributes.DataAttributeName(cloudEvent.SpecVersion), CloudEventAttributes.IdAttributeName(cloudEvent.SpecVersion), CloudEventAttributes.DataContentTypeAttributeName(cloudEvent.SpecVersion), }; foreach (var attribute in cloudEvent.GetAttributes()) { if (!ignoreKeys.Contains(attribute.Key)) { var key = Constants.PropertyKeyPrefix + attribute.Key; switch (attribute.Value) { case Uri uri: UserProperties.Add(key, uri.ToString()); break; default: UserProperties.Add(key, attribute.Value); break; } } } }
/// <inheritdoc /> protected override void Dispatch(CloudEvent e) { _logger.LogTrace("dispatching..."); Spigot.AfterReceive?.Invoke(e); var message = new EventArrived <T> { EventData = Spigot.Serializer.Deserialize <T>(e.Data as byte[]), Context = new Context { Headers = e.GetAttributes(), Sender = new Sender(e.Source) } }; _logger.LogTrace($"Received {e.Type} message from stream with id {e.Id}"); try { HandleMessage(message); } catch (Exception exx) { _logger.LogCritical(exx, $"User code threw an exception."); } }
public byte[] EncodeStructuredEvent(CloudEvent cloudEvent, out ContentType contentType) { if (cloudEvent == null) { throw new ArgumentNullException(nameof(cloudEvent)); } contentType = new ContentType("application/cloudevents+json") { CharSet = Encoding.UTF8.WebName, }; var jObject = new JObject(); var attributes = cloudEvent.GetAttributes(); foreach (var keyValuePair in attributes) { if (keyValuePair.Value == null) { continue; } if (keyValuePair.Value is ContentType contentTypeValue && !string.IsNullOrEmpty(contentTypeValue.MediaType)) { jObject[keyValuePair.Key] = contentTypeValue.ToString(); }
/// <summary> /// Represent this model as a <see cref="CloudEvent"/> or <c>null</c>. /// </summary> public CloudEvent AsCloudEvent( Uri source = null, CloudEventsSpecVersion?specVersion = null, IEnumerable <ICloudEventExtension> extensions = null) { var version = specVersion.GetValueOrDefault(CloudEventsSpecVersion.Default); if (Attributes.Count > 0) { var cloudEvent = new CloudEvent(version, extensions); IDictionary <string, object> attributes = cloudEvent.GetAttributes(); foreach (KeyValuePair <string, object> keyValuePair in Attributes) { attributes[keyValuePair.Key] = keyValuePair.Value; } return(cloudEvent); } else { return(new CloudEvent( version, EventType, Source ?? source, Subject ?? String.Empty, Id, EventTime.DateTime, extensions?.ToArray()) { Data = Data, DataContentType = new ContentType("application/json") }); } }
public async Task Controller_WithValidCloudEvent_DeserializesUsingPipeline(string contentType) { // Arrange var expectedExtensionKey = "comexampleextension1"; var expectedExtensionValue = Guid.NewGuid().ToString(); var cloudEvent = new CloudEvent("test-type-æøå", new Uri("urn:integration-tests")) { Id = Guid.NewGuid().ToString(), }; var attrs = cloudEvent.GetAttributes(); attrs[expectedExtensionKey] = expectedExtensionValue; var content = new CloudEventContent(cloudEvent, ContentMode.Structured, new JsonEventFormatter()); content.Headers.ContentType = new MediaTypeHeaderValue(contentType); // Act var result = await _factory.CreateClient().PostAsync("/api/events/receive", content); // Assert Assert.Equal(HttpStatusCode.OK, result.StatusCode); Assert.Contains(cloudEvent.Id, await result.Content.ReadAsStringAsync()); Assert.Contains(cloudEvent.Type, await result.Content.ReadAsStringAsync()); Assert.Contains($"\"{expectedExtensionKey}\":\"{expectedExtensionValue}\"", await result.Content.ReadAsStringAsync()); }
public async Task HandleAsync(CloudEvent cloudEvent, CancellationToken cancellationToken) { // This is an example of how CloudEvents are unfortunately inconsistent when it // comes to Data. Is it "pre-serialized" or not? We prevent parsing date/time values, // so that we can get as close to the original JSON as possible. var settings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None }; cloudEvent.Data = JsonConvert.DeserializeObject <JObject>((string)cloudEvent.Data, settings); // Work around https://github.com/cloudevents/sdk-csharp/issues/59 var attributes = cloudEvent.GetAttributes(); var attributeKeys = attributes.Keys.ToList(); foreach (var key in attributeKeys) { var lowerKey = key.ToLowerInvariant(); var value = attributes[key]; attributes.Remove(key); attributes[lowerKey] = value; } // Write out a structured JSON representation of the CloudEvent var formatter = new JsonEventFormatter(); var bytes = formatter.EncodeStructuredEvent(cloudEvent, out var contentType); await File.WriteAllBytesAsync("function_output.json", bytes); }
async Task HttpStructuredClientReceiveTest() { string ctx = Guid.NewGuid().ToString(); pendingRequests.TryAdd(ctx, async context => { try { var cloudEvent = new CloudEvent("com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["utf8examplevalue"] = "æøå"; await context.Response.CopyFromAsync(cloudEvent, ContentMode.Structured, new JsonEventFormatter()); context.Response.StatusCode = (int)HttpStatusCode.OK; } catch (Exception e) { using (var sw = new StreamWriter(context.Response.OutputStream)) { sw.Write(e.ToString()); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } context.Response.Close(); }); var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Add(testContextHeader, ctx); var result = await httpClient.GetAsync(new Uri(listenerAddress + "ep")); Assert.Equal(HttpStatusCode.OK, result.StatusCode); Assert.True(result.IsCloudEvent()); var receivedCloudEvent = result.ToCloudEvent(); Assert.Equal(CloudEventsSpecVersion.V1_0, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), receivedCloudEvent.Source); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), receivedCloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", receivedCloudEvent.Data); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); Assert.Equal("æøå", (string)attr["utf8examplevalue"]); }
private void ServiceBusMessageTest(Func <CloudEvent, ServiceBusCloudEventMessage> event2message, ContentMode contentMode) { var data = "<much wow=\"xml\"/>"; var cloudEvent = new CloudEvent( CloudEventsSpecVersion.V1_0, "com.github.pull.create", source: new Uri("https://github.com/cloudevents/spec/pull"), subject: "123") { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = contentMode == ContentMode.Structured ? (object)data : (object)Encoding.UTF8.GetBytes(data), }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["comexampleextension2"] = new { othervalue = 5 }; var message = event2message(cloudEvent); message.IsCloudEvent().Should().BeTrue(); var clonedMessage = message.Clone(); clonedMessage.IsCloudEvent().Should().BeTrue(); var receivedCloudEvent = clonedMessage.ToCloudEvent(); receivedCloudEvent.SpecVersion.Should().Be(CloudEventsSpecVersion.Default); receivedCloudEvent.Type.Should().Be("com.github.pull.create"); receivedCloudEvent.Source.Should().Be(new Uri("https://github.com/cloudevents/spec/pull")); receivedCloudEvent.Subject.Should().Be("123"); receivedCloudEvent.Id.Should().Be("A234-1234-1234"); receivedCloudEvent.Time.Should().NotBeNull(); receivedCloudEvent.Time !.Value.ToUniversalTime().Should().Be(DateTime.Parse("2018-04-05T17:31:00Z", CultureInfo.InvariantCulture).ToUniversalTime()); receivedCloudEvent.DataContentType.Should().Be(new ContentType(MediaTypeNames.Text.Xml)); if (contentMode == ContentMode.Structured) { receivedCloudEvent.Data.Should().Be(data); } else { Encoding.UTF8.GetString((byte[])receivedCloudEvent.Data).Should().Be(data); } var receivedAttrs = receivedCloudEvent.GetAttributes(); ((string)receivedAttrs["comexampleextension1"]).Should().Be("value"); ((int)((dynamic)receivedAttrs["comexampleextension2"]).othervalue).Should().Be(5); }
public async Task MqttSendTest() { var jsonEventFormatter = new JsonEventFormatter(); var cloudEvent = new CloudEvent("com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), ContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["comexampleextension2"] = new { othervalue = 5 }; var client = new MqttFactory().CreateMqttClient(); var options = new MqttClientOptionsBuilder() .WithClientId("Client1") .WithTcpServer("localhost", 52355) .WithCleanSession() .Build(); TaskCompletionSource <CloudEvent> tcs = new TaskCompletionSource <CloudEvent>(); await client.ConnectAsync(options); client.ApplicationMessageReceived += (sender, args) => { tcs.SetResult(args.ApplicationMessage.ToCloudEvent(jsonEventFormatter)); }; var result = await client.SubscribeAsync("abc"); await client.PublishAsync(new MqttCloudEventMessage(cloudEvent, new JsonEventFormatter()) { Topic = "abc" }); var receivedCloudEvent = await tcs.Task; Assert.Equal(CloudEventsSpecVersion.V0_2, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), receivedCloudEvent.Source); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), receivedCloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.ContentType); Assert.Equal("<much wow=\"xml\"/>", receivedCloudEvent.Data); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); Assert.Equal(5, (int)((dynamic)attr["comexampleextension2"]).othervalue); }
public void KafkaBinaryMessageTest() { // Kafka doesn't provide any way to get to the message transport level to do the test properly // and it doesn't have an embedded version of a server for .Net so the lowest we can get is // the `Message<T, K>` var jsonEventFormatter = new JsonEventFormatter(); var cloudEvent = new CloudEvent("com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123"), extensions: new PartitioningExtension()) { Id = "A234-1234-1234", Time = new DateTimeOffset(2018, 4, 5, 17, 31, 0, TimeSpan.Zero), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = Encoding.UTF8.GetBytes("<much wow=\"xml\"/>") }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; cloudEvent.Extension <PartitioningExtension>().PartitioningKeyValue = "hello much wow"; var message = new KafkaCloudEventMessage(cloudEvent, ContentMode.Binary, new JsonEventFormatter()); Assert.True(message.IsCloudEvent()); // using serialization to create fully independent copy thus simulating message transport // real transport will work in a similar way var serialized = JsonConvert.SerializeObject(message, new HeaderConverter()); var settings = new JsonSerializerSettings { Converters = { new HeadersConverter(), new HeaderConverter() } }; var messageCopy = JsonConvert.DeserializeObject <Message <string, byte[]> >(serialized, settings); Assert.True(messageCopy.IsCloudEvent()); var receivedCloudEvent = messageCopy.ToCloudEvent(jsonEventFormatter, new PartitioningExtension()); Assert.Equal(CloudEventsSpecVersion.Default, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), receivedCloudEvent.Source); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); AssertTimestampsEqual("2018-04-05T17:31:00Z", receivedCloudEvent.Time.Value); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.DataContentType); Assert.Equal(Encoding.UTF8.GetBytes("<much wow=\"xml\"/>"), receivedCloudEvent.Data); Assert.Equal("hello much wow", receivedCloudEvent.Extension <PartitioningExtension>().PartitioningKeyValue); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); }
/// <inheritdoc/> public void Attach(CloudEvent cloudEvent) { IDictionary <string, object> eventAttributes = cloudEvent.GetAttributes(); if (this._Attributes == eventAttributes) { return; } foreach (KeyValuePair <string, object> attribute in this._Attributes) { eventAttributes[attribute.Key] = attribute.Value; } this._Attributes = eventAttributes; }
public void KafkaStructuredMessageTest() { // Kafka doesn't provide any way to get to the message transport level to do the test properly // and it doesn't have an embedded version of a server for .Net so the lowest we can get is // the `Message<T, K>` var jsonEventFormatter = new JsonEventFormatter(); var cloudEvent = new CloudEvent(CloudEventsSpecVersion.V1_0, "com.github.pull.create", source: new Uri("https://github.com/cloudevents/spec/pull"), subject: "123") { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; var message = new KafkaCloudEventMessage(cloudEvent, ContentMode.Structured, new JsonEventFormatter()); Assert.True(message.IsCloudEvent()); // using serialization to create fully independent copy thus simulating message transport // real transport will work in a similar way var serialized = JsonConvert.SerializeObject(message, new HeaderConverter()); var messageCopy = JsonConvert.DeserializeObject <Message <string, byte[]> >(serialized, new HeadersConverter(), new HeaderConverter()); Assert.True(messageCopy.IsCloudEvent()); var receivedCloudEvent = messageCopy.ToCloudEvent(jsonEventFormatter); Assert.Equal(CloudEventsSpecVersion.Default, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull"), receivedCloudEvent.Source); Assert.Equal("123", receivedCloudEvent.Subject); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), receivedCloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", receivedCloudEvent.Data); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); }
private void MapHeaders(CloudEvent cloudEvent, ICloudEventFormatter formatter) { foreach (var attr in cloudEvent.GetAttributes()) { if (string.Equals(attr.Key, CloudEventAttributes.DataAttributeName(cloudEvent.SpecVersion)) || string.Equals(attr.Key, CloudEventAttributes.DataContentTypeAttributeName(cloudEvent.SpecVersion)) || string.Equals(attr.Key, PartitioningExtension.PartitioningKeyAttributeName)) { continue; } Headers.Add(KafkaHeaderPerfix + attr.Key, formatter.EncodeAttribute(cloudEvent.SpecVersion, attr.Key, attr.Value, cloudEvent.Extensions.Values)); } }
/// <summary> /// Creates a new <see cref="ICloudEventBuilder"/> from the specified <see cref="CloudEvent"/> /// </summary> /// <param name="e">The <see cref="CloudEvent"/> to create a new <see cref="ICloudEventBuilder"/> for</param> /// <returns>A new <see cref="ICloudEventBuilder"/> based on the specified <see cref="CloudEvent"/></returns> public static CloudEventBuilder FromEvent(CloudEvent e) { CloudEventBuilder builder = new CloudEventBuilder(); builder.WithSpecVersion(e.SpecVersion) .WithType(e.Type) .WithSubject(e.Subject) .WithSource(e.Source) .WithId(e.Id) .WithTime(e.Time) .WithData(e.Data, e.DataContentType) .WithDataSchema(e.DataSchema) .WithAttributes(e.GetAttributes()); return(builder); }
public void AmqpStructuredMessageTest() { // the AMQPNetLite library is factored such // that we don't need to do a wire test here var jsonEventFormatter = new JsonEventFormatter(); var cloudEvent = new CloudEvent(CloudEventsSpecVersion.V0_3, "com.github.pull.create", source: new Uri("https://github.com/cloudevents/spec/pull"), subject: "123") { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["comexampleextension2"] = new { othervalue = 5 }; var message = new AmqpCloudEventMessage(cloudEvent, ContentMode.Structured, new JsonEventFormatter()); Assert.True(message.IsCloudEvent()); var encodedAmqpMessage = message.Encode(); var message1 = Message.Decode(encodedAmqpMessage); Assert.True(message1.IsCloudEvent()); var receivedCloudEvent = message1.ToCloudEvent(); Assert.Equal(CloudEventsSpecVersion.Default, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull"), receivedCloudEvent.Source); Assert.Equal("123", receivedCloudEvent.Subject); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), receivedCloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", receivedCloudEvent.Data); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); Assert.Equal(5, (int)((dynamic)attr["comexampleextension2"]).othervalue); }
public void AmqpBinaryMessageTest() { // the AMQPNetLite library is factored such // that we don't need to do a wire test here var jsonEventFormatter = new JsonEventFormatter(); var cloudEvent = new CloudEvent("com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = new DateTimeOffset(2018, 4, 5, 17, 31, 0, TimeSpan.Zero), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; var message = new AmqpCloudEventMessage(cloudEvent, ContentMode.Binary, new JsonEventFormatter()); Assert.True(message.IsCloudEvent()); var encodedAmqpMessage = message.Encode(); var message1 = Message.Decode(encodedAmqpMessage); Assert.True(message1.IsCloudEvent()); var receivedCloudEvent = message1.ToCloudEvent(); Assert.Equal(CloudEventsSpecVersion.Default, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), receivedCloudEvent.Source); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); AssertTimestampsEqual("2018-04-05T17:31:00Z", receivedCloudEvent.Time.Value); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", receivedCloudEvent.Data); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); }
private static CloudEvent BinaryToCloudEvent(Message message, ICloudEventExtension[] extensions) { var specVersion = GetCloudEventsSpecVersion(message); var cloudEventType = GetAttribute(message, CloudEventAttributes.TypeAttributeName(specVersion)); var cloudEventSource = new Uri(GetAttribute(message, CloudEventAttributes.SourceAttributeName(specVersion))); var cloudEvent = new CloudEvent(specVersion, cloudEventType, cloudEventSource, id: message.MessageId, extensions: extensions); var attributes = cloudEvent.GetAttributes(); foreach (var property in message.UserProperties) { if (property.Key.StartsWith(Constants.PropertyKeyPrefix, StringComparison.InvariantCultureIgnoreCase)) { #pragma warning disable CA1308 // Normalize strings to uppercase var key = property.Key.Substring(Constants.PropertyKeyPrefix.Length).ToLowerInvariant(); #pragma warning restore CA1308 // Normalize strings to uppercase attributes[key] = property.Value; } } cloudEvent.DataContentType = message.ContentType == null ? null : new ContentType(message.ContentType); cloudEvent.Data = message.Body; return(cloudEvent); }
async Task HttpStructuredWebRequestSendTest() { var cloudEvent = new CloudEvent("com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["utf8examplevalue"] = "æøå"; string ctx = Guid.NewGuid().ToString(); HttpWebRequest httpWebRequest = WebRequest.CreateHttp(listenerAddress + "ep"); httpWebRequest.Method = "POST"; await httpWebRequest.CopyFromAsync(cloudEvent, ContentMode.Structured, new JsonEventFormatter()); httpWebRequest.Headers.Add(testContextHeader, ctx); pendingRequests.TryAdd(ctx, context => { try { // Structured events do not contain any CloudEvent HTTP headers. Assert.Empty(context.Request.Headers.AllKeys.Where(key => key.StartsWith("ce-"))); var receivedCloudEvent = context.Request.ToCloudEvent(new JsonEventFormatter()); Assert.Equal(CloudEventsSpecVersion.V1_0, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), receivedCloudEvent.Source); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), receivedCloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", receivedCloudEvent.Data); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); Assert.Equal("æøå", (string)attr["utf8examplevalue"]); context.Response.StatusCode = (int)HttpStatusCode.NoContent; } catch (Exception e) { using (var sw = new StreamWriter(context.Response.OutputStream)) { sw.Write(e.ToString()); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } context.Response.Close(); return(Task.CompletedTask); }); var result = (HttpWebResponse)await httpWebRequest.GetResponseAsync(); if (result.StatusCode != HttpStatusCode.NoContent) { throw new InvalidOperationException(result.StatusCode.ToString()); } }
async Task HttpStructuredClientSendTest() { var cloudEvent = new CloudEvent("com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["utf8examplevalue"] = "æøå"; string ctx = Guid.NewGuid().ToString(); var content = new CloudEventContent(cloudEvent, ContentMode.Structured, new JsonEventFormatter()); content.Headers.Add(testContextHeader, ctx); pendingRequests.TryAdd(ctx, context => { try { // Structured events contain a copy of the CloudEvent attributes as HTTP headers. var headers = context.Request.Headers; Assert.Equal("1.0", headers["ce-specversion"]); Assert.Equal("com.github.pull.create", headers["ce-type"]); Assert.Equal("https://github.com/cloudevents/spec/pull/123", headers["ce-source"]); Assert.Equal("A234-1234-1234", headers["ce-id"]); Assert.Equal("2018-04-05T17:31:00Z", headers["ce-time"]); // Note that datacontenttype is mapped in this case, but would not be included in binary mode. Assert.Equal("text/xml", headers["ce-datacontenttype"]); Assert.Equal("application/cloudevents+json", context.Request.ContentType); Assert.Equal("value", headers["ce-comexampleextension1"]); // The non-ASCII attribute value should have been URL-encoded using UTF-8 for the header. Assert.Equal("%C3%A6%C3%B8%C3%A5", headers["ce-utf8examplevalue"]); var receivedCloudEvent = context.Request.ToCloudEvent(new JsonEventFormatter()); Assert.Equal(CloudEventsSpecVersion.V1_0, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), receivedCloudEvent.Source); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), receivedCloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.DataContentType); Assert.Equal("<much wow=\"xml\"/>", receivedCloudEvent.Data); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); Assert.Equal("æøå", (string)attr["utf8examplevalue"]); context.Response.StatusCode = (int)HttpStatusCode.NoContent; } catch (Exception e) { using (var sw = new StreamWriter(context.Response.OutputStream)) { sw.Write(e.ToString()); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } context.Response.Close(); return(Task.CompletedTask); }); var httpClient = new HttpClient(); var result = (await httpClient.PostAsync(new Uri(listenerAddress + "ep"), content)); if (result.StatusCode != HttpStatusCode.NoContent) { throw new InvalidOperationException(result.Content.ReadAsStringAsync().GetAwaiter().GetResult()); } }
async Task HttpStructuredClientSendTest() { var cloudEvent = new CloudEvent("com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), ContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["comexampleextension2"] = new { othervalue = 5 }; string ctx = Guid.NewGuid().ToString(); var content = new CloudEventContent(cloudEvent, ContentMode.Structured, new JsonEventFormatter()); content.Headers.Add(testContextHeader, ctx); pendingRequests.TryAdd(ctx, async context => { try { var receivedCloudEvent = context.Request.ToCloudEvent(new JsonEventFormatter()); Assert.Equal(CloudEventsSpecVersion.V0_2, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), receivedCloudEvent.Source); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), receivedCloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.ContentType); Assert.Equal("<much wow=\"xml\"/>", receivedCloudEvent.Data); var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); Assert.Equal(5, (int)((dynamic)attr["comexampleextension2"]).othervalue); context.Response.StatusCode = (int)HttpStatusCode.NoContent; } catch (Exception e) { using (var sw = new StreamWriter(context.Response.OutputStream)) { sw.Write(e.ToString()); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } context.Response.Close(); }); var httpClient = new HttpClient(); var result = (await httpClient.PostAsync(new Uri(listenerAddress + "ep"), content)); if (result.StatusCode != HttpStatusCode.NoContent) { throw new InvalidOperationException(result.Content.ReadAsStringAsync().GetAwaiter().GetResult()); } }
async Task HttpBinaryClientSendTest() { var cloudEvent = new CloudEvent("com.github.pull.create", new Uri("https://github.com/cloudevents/spec/pull/123")) { Id = "A234-1234-1234", Time = new DateTime(2018, 4, 5, 17, 31, 0, DateTimeKind.Utc), DataContentType = new ContentType(MediaTypeNames.Text.Xml), Data = "<much wow=\"xml\"/>" }; var attrs = cloudEvent.GetAttributes(); attrs["comexampleextension1"] = "value"; attrs["utf8examplevalue"] = "æøå"; string ctx = Guid.NewGuid().ToString(); var content = new CloudEventContent(cloudEvent, ContentMode.Binary, new JsonEventFormatter()); content.Headers.Add(testContextHeader, ctx); pendingRequests.TryAdd(ctx, context => { try { Assert.True(context.Request.IsCloudEvent()); var receivedCloudEvent = context.Request.ToCloudEvent(new JsonEventFormatter()); Assert.Equal(CloudEventsSpecVersion.V1_0, receivedCloudEvent.SpecVersion); Assert.Equal("com.github.pull.create", receivedCloudEvent.Type); Assert.Equal(new Uri("https://github.com/cloudevents/spec/pull/123"), receivedCloudEvent.Source); Assert.Equal("A234-1234-1234", receivedCloudEvent.Id); Assert.Equal(DateTime.Parse("2018-04-05T17:31:00Z").ToUniversalTime(), receivedCloudEvent.Time.Value.ToUniversalTime()); Assert.Equal(new ContentType(MediaTypeNames.Text.Xml), receivedCloudEvent.DataContentType); // The non-ASCII attribute value should have been URL-encoded using UTF-8 for the header. Assert.True(content.Headers.TryGetValues("ce-utf8examplevalue", out var utf8ExampleValues)); Assert.Equal("%C3%A6%C3%B8%C3%A5", utf8ExampleValues.Single()); using (var sr = new StreamReader((Stream)receivedCloudEvent.Data)) { Assert.Equal("<much wow=\"xml\"/>", sr.ReadToEnd()); } var attr = receivedCloudEvent.GetAttributes(); Assert.Equal("value", (string)attr["comexampleextension1"]); // The non-ASCII attribute value should have been correctly URL-decoded. Assert.Equal("æøå", (string)attr["utf8examplevalue"]); context.Response.StatusCode = (int)HttpStatusCode.NoContent; } catch (Exception e) { using (var sw = new StreamWriter(context.Response.OutputStream)) { sw.Write(e.ToString()); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } context.Response.Close(); return(Task.CompletedTask); }); var httpClient = new HttpClient(); var result = (await httpClient.PostAsync(new Uri(listenerAddress + "ep"), content)); if (result.StatusCode != HttpStatusCode.NoContent) { throw new InvalidOperationException(result.Content.ReadAsStringAsync().GetAwaiter().GetResult()); } }
public static CloudEvent ToCloudEvent(this Message message, ICloudEventFormatter formatter = null, params ICloudEventExtension[] extensions) { string contentType = message.Properties.ContentType?.ToString(); if (contentType != null && contentType.StartsWith(CloudEvent.MediaType, StringComparison.InvariantCultureIgnoreCase)) { // handle structured mode if (formatter == null) { // if we didn't get a formatter, pick one if (contentType.EndsWith(JsonEventFormatter.MediaTypeSuffix, StringComparison.InvariantCultureIgnoreCase)) { formatter = jsonFormatter; } else { throw new InvalidOperationException("Unsupported CloudEvents encoding"); } } return(formatter.DecodeStructuredEvent(new MemoryStream((byte[])message.Body), extensions)); } else { var specVersion = message.ApplicationProperties.Map.ContainsKey(SpecVersionAmqpHeader1) ? CloudEventsSpecVersion.V0_1 : message.ApplicationProperties.Map.ContainsKey(SpecVersionAmqpHeader2) ? (message.ApplicationProperties.Map[SpecVersionAmqpHeader2] as string == "0.2" ? CloudEventsSpecVersion.V0_2 : (message.ApplicationProperties.Map[SpecVersionAmqpHeader2] as string == "0.3" ? CloudEventsSpecVersion.V0_3 : CloudEventsSpecVersion.Default)) : CloudEventsSpecVersion.Default; var cloudEvent = new CloudEvent(specVersion, extensions); var attributes = cloudEvent.GetAttributes(); foreach (var prop in message.ApplicationProperties.Map) { if (prop.Key is string && ((string)prop.Key).StartsWith(AmqpHeaderPrefix, StringComparison.InvariantCultureIgnoreCase)) { if (cloudEvent.SpecVersion != CloudEventsSpecVersion.V1_0 && prop.Value is Map) { IDictionary <string, object> exp = new ExpandoObject(); foreach (var props in (Map)prop.Value) { exp[props.Key.ToString()] = props.Value; } attributes[((string)prop.Key).Substring(AmqpHeaderPrefix.Length).ToLowerInvariant()] = exp; } else { attributes[((string)prop.Key).Substring(AmqpHeaderPrefix.Length).ToLowerInvariant()] = prop.Value; } } } cloudEvent.DataContentType = message.Properties.ContentType != null ? new ContentType(message.Properties.ContentType) : null; cloudEvent.Data = message.Body; return(cloudEvent); } }
public static async Task <HttpResponseMessage> Madlibs( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequestMessage req, ILogger log) { try { // opt into push if needed if (req.IsWebHookValidationRequest()) { return(await req.HandleAsWebHookValidationRequest(null, null)); } if (!req.Headers.Contains("X-Callback-URL")) { // thanks, but we can't respond without an address log.LogInformation("Didn't find 'X-Callback-URL' header."); return(new HttpResponseMessage(HttpStatusCode.BadRequest)); } string callbackUrl = req.Headers.GetValues("X-Callback-URL").FirstOrDefault(); CloudEvent receivedCloudEvent = req.ToCloudEvent(); CloudEvent raisedCloudEvent = null; log.LogInformation($"Processing {receivedCloudEvent.SpecVersion} with {receivedCloudEvent.Type}"); log.LogInformation($"Callback to {callbackUrl}"); switch (receivedCloudEvent.Type) { case "word.found.noun": raisedCloudEvent = new CloudEvent("word.picked.noun", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Nouns[rnd.Next(Words.All.Nouns.Length)] } }; break; case "word.found.verb": raisedCloudEvent = new CloudEvent("word.picked.verb", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Verbs[rnd.Next(Words.All.Verbs.Length)] } }; break; case "word.found.exclamation": raisedCloudEvent = new CloudEvent("word.picked.exlamation", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Exclamations[rnd.Next(Words.All.Exclamations.Length)] } }; break; case "word.found.adverb": raisedCloudEvent = new CloudEvent("word.picked.adverb", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Adverbs[rnd.Next(Words.All.Adverbs.Length)] } }; break; case "word.found.pluralnoun": raisedCloudEvent = new CloudEvent("word.picked.pluralnoun", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Pluralnouns[rnd.Next(Words.All.Pluralnouns.Length)] } }; break; case "word.found.adjective": raisedCloudEvent = new CloudEvent("word.picked.adjective", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Adjectives[rnd.Next(Words.All.Adjectives.Length)] } }; break; case "word.found.color": raisedCloudEvent = new CloudEvent("word.picked.color", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Colors[rnd.Next(Words.All.Colors.Length)] } }; break; case "word.found.name": raisedCloudEvent = new CloudEvent("word.picked.name", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Names[rnd.Next(Words.All.Names.Length)] } }; break; case "word.found.animal": raisedCloudEvent = new CloudEvent("word.picked.animal", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Animals[rnd.Next(Words.All.Animals.Length)] } }; break; case "word.found.verbing": raisedCloudEvent = new CloudEvent("word.picked.verbing", new Uri(SourceIdentifier)) { ContentType = Json, Data = new { word = Words.All.Verbings[rnd.Next(Words.All.Verbings.Length)] } }; break; default: return(new HttpResponseMessage(HttpStatusCode.NoContent)); } raisedCloudEvent.GetAttributes().Add("relatedid", receivedCloudEvent.Id); HttpClient client = new HttpClient(); var result = await client.PostAsync(callbackUrl, new CloudEventContent(raisedCloudEvent, rnd.Next(2) == 0 ? ContentMode.Binary : ContentMode.Structured, new JsonEventFormatter())); log.LogInformation($"Callback result {result.StatusCode}"); return(new HttpResponseMessage(result.StatusCode)); } catch (Exception e) { log.LogInformation($"Exception while processing {e.ToString()}"); throw; } }
public ActionResult <IEnumerable <string> > ReceiveCloudEvent([FromBody] CloudEvent cloudEvent) { return(Ok($"Received event with ID {cloudEvent.Id}, attributes: {JsonConvert.SerializeObject(cloudEvent.GetAttributes())}")); }