private CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable <CloudEventAttribute> extensionAttributes) { if (!record.TryGetValue(AttributeName, out var attrObj)) { throw new ArgumentException($"Record has no '{AttributeName}' field"); } IDictionary <string, object> recordAttributes = (IDictionary <string, object>)attrObj; if (!recordAttributes.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var versionId) || !(versionId is string versionIdString)) { throw new ArgumentException("Specification version attribute is missing"); } CloudEventsSpecVersion version = CloudEventsSpecVersion.FromVersionId(versionIdString); if (version is null) { throw new ArgumentException($"Unsupported CloudEvents spec version '{versionIdString}'"); } var cloudEvent = new CloudEvent(version, extensionAttributes); cloudEvent.Data = record.TryGetValue(DataName, out var data) ? data : null; foreach (var keyValuePair in recordAttributes) { string key = keyValuePair.Key; object value = keyValuePair.Value; if (value is null) { continue; } if (key == CloudEventsSpecVersion.SpecVersionAttribute.Name || key == DataName) { continue; } // The Avro schema allows the value to be a Boolean, integer, string or bytes. // Timestamps and URIs are represented as strings, so we just use SetAttributeFromString to handle those. // TODO: This does mean that any extensions of these types must have been registered beforehand. if (value is bool || value is int || value is byte[]) { cloudEvent[key] = value; } else if (value is string) { cloudEvent.SetAttributeFromString(key, (string)value); } else { throw new ArgumentException($"Invalid value type from Avro record: {value.GetType()}"); } } return(Validation.CheckCloudEventArgument(cloudEvent, nameof(record))); }
public CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable <CloudEventAttribute> extensionAttributes) { if (!record.TryGetValue("attribute", out var attrObj)) { return(null); } IDictionary <string, object> recordAttributes = (IDictionary <string, object>)attrObj; CloudEventsSpecVersion specVersion = CloudEventsSpecVersion.Default; if (recordAttributes.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var versionId) && versionId is string versionIdString) { specVersion = CloudEventsSpecVersion.FromVersionId(versionIdString); } var cloudEvent = new CloudEvent(specVersion, extensionAttributes); cloudEvent.Data = record.TryGetValue(DataName, out var data) ? data : null; foreach (var keyValuePair in recordAttributes) { string key = keyValuePair.Key; object value = keyValuePair.Value; if (value is null) { continue; } if (key == CloudEventsSpecVersion.SpecVersionAttribute.Name || key == DataName) { continue; } // The Avro schema allows the value to be a Boolean, integer, string or bytes. // Timestamps and URIs are represented as strings, so we just use SetAttributeFromString to handle those. if (value is bool || value is int || value is byte[]) { cloudEvent[key] = value; } else if (value is string) { cloudEvent.SetAttributeFromString(key, (string)value); } else { throw new ArgumentException($"Invalid value type from Avro record: {value.GetType()}"); } } return(cloudEvent); }
/// <summary> /// Converts this HTTP request into a CloudEvent object. /// </summary> /// <param name="httpRequest">The HTTP request to decode. Must not be null.</param> /// <param name="formatter">The event formatter to use to process the request body. Must not be null.</param> /// <param name="extensions">The extension attributes to use when populating the CloudEvent. May be null.</param> /// <returns>The decoded CloudEvent.</returns> /// <exception cref="ArgumentException">The request does not contain a CloudEvent.</exception> public static async ValueTask <CloudEvent> ReadCloudEventAsync(this HttpRequest httpRequest, CloudEventFormatter formatter, params CloudEventAttribute[] extensionAttributes) { if (HasCloudEventsContentType(httpRequest)) { // TODO: Handle formatter being null return(await formatter.DecodeStructuredModeMessageAsync(httpRequest.Body, MimeUtilities.CreateContentTypeOrNull(httpRequest.ContentType), extensionAttributes).ConfigureAwait(false)); } else { var headers = httpRequest.Headers; if (!headers.TryGetValue(HttpUtilities.SpecVersionHttpHeader, out var versionId)) { throw new ArgumentException("Request is not a CloudEvent"); } var version = CloudEventsSpecVersion.FromVersionId(versionId.First()); if (version is null) { throw new ArgumentException($"Unsupported CloudEvents spec version '{versionId.First()}'"); } var cloudEvent = new CloudEvent(version, extensionAttributes); foreach (var header in headers) { string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key); if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name) { continue; } string attributeValue = HttpUtilities.DecodeHeaderValue(header.Value.First()); cloudEvent.SetAttributeFromString(attributeName, attributeValue); } cloudEvent.DataContentType = httpRequest.ContentType; if (httpRequest.Body is Stream body) { // TODO: This is a bit ugly. We have code in BinaryDataUtilities to handle this, but // we'd rather not expose it... var memoryStream = new MemoryStream(); await body.CopyToAsync(memoryStream).ConfigureAwait(false); formatter.DecodeBinaryModeEventData(memoryStream.ToArray(), cloudEvent); } return(cloudEvent); } }
/// <summary> /// Converts this HTTP request into a CloudEvent object, with the given extensions, /// overriding the formatter. /// </summary> /// <param name="httpRequest">HTTP request</param> /// <param name="formatter">The event formatter to use to process the request body.</param> /// <param name="extensions">List of extension instances</param> /// <returns>A CloudEvent instance or 'null' if the request message doesn't hold a CloudEvent</returns> public static async ValueTask <CloudEvent> ReadCloudEventAsync(this HttpRequest httpRequest, ICloudEventFormatter formatter, params CloudEventAttribute[] extensionAttributes) { if (HasCloudEventsContentType(httpRequest)) { // TODO: Handle formatter being null return(await formatter.DecodeStructuredEventAsync(httpRequest.Body, extensionAttributes).ConfigureAwait(false)); } else { var headers = httpRequest.Headers; CloudEventsSpecVersion version = CloudEventsSpecVersion.Default; if (headers.TryGetValue(HttpUtilities.SpecVersionHttpHeader, out var values)) { string versionId = values.First(); version = CloudEventsSpecVersion.FromVersionId(versionId); } var cloudEvent = new CloudEvent(version, extensionAttributes); foreach (var header in headers) { string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key); if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name) { continue; } string attributeValue = HttpUtilities.DecodeHeaderValue(header.Value.First()); cloudEvent.SetAttributeFromString(attributeName, attributeValue); } cloudEvent.DataContentType = httpRequest.ContentType; if (httpRequest.Body is Stream body) { // TODO: This is a bit ugly. var memoryStream = new MemoryStream(); await body.CopyToAsync(memoryStream).ConfigureAwait(false); if (memoryStream.Length != 0) { cloudEvent.Data = formatter.DecodeData(memoryStream.ToArray(), cloudEvent.DataContentType); } } return(cloudEvent); } }
public async Task SetsTraceParentExtension(bool inclTraceparent, bool inclTracestate) { var mockTransport = new MockTransport(new MockResponse(200)); var options = new EventGridPublisherClientOptions { Transport = mockTransport }; EventGridPublisherClient client = new EventGridPublisherClient( new Uri("http://localHost"), new AzureKeyCredential("fakeKey"), options); var activity = new Activity($"{nameof(EventGridPublisherClient)}.{nameof(EventGridPublisherClient.SendEvents)}"); activity.SetW3CFormat(); activity.Start(); List <CloudEvent> inputEvents = new List <CloudEvent>(); for (int i = 0; i < 10; i++) { var cloudEvent = new CloudEvent { Subject = "record", Source = new Uri("http://localHost"), Id = Guid.NewGuid().ToString(), Time = DateTime.Now, Type = "test" }; if (inclTraceparent && inclTracestate && i % 2 == 0) { cloudEvent.SetAttributeFromString("traceparent", "traceparentValue"); } if (inclTracestate && i % 2 == 0) { cloudEvent.SetAttributeFromString("tracestate", "param:value"); } inputEvents.Add(cloudEvent); } await client.SendCloudNativeCloudEventsAsync(inputEvents); activity.Stop(); List <CloudEvent> endEvents = DeserializeRequest(mockTransport.SingleRequest); IEnumerator <CloudEvent> inputEnum = inputEvents.GetEnumerator(); foreach (CloudEvent cloudEvent in endEvents) { inputEnum.MoveNext(); var inputAttributes = inputEnum.Current.GetPopulatedAttributes().Select(pair => pair.Key.Name).ToList(); if (inputAttributes.Contains(TraceParentHeaderName) && inputAttributes.Contains(TraceStateHeaderName)) { Assert.AreEqual( inputEnum.Current[TraceParentHeaderName], cloudEvent[TraceParentHeaderName]); Assert.AreEqual( inputEnum.Current[TraceStateHeaderName], cloudEvent[TraceStateHeaderName]); } else if (inputAttributes.Contains(TraceParentHeaderName)) { Assert.AreEqual( inputEnum.Current[TraceParentHeaderName], cloudEvent[TraceParentHeaderName]); } else if (inputAttributes.Contains(TraceStateHeaderName)) { Assert.AreEqual( inputEnum.Current[TraceStateHeaderName], cloudEvent[TraceStateHeaderName]); } else { Assert.AreEqual( activity.Id, cloudEvent[TraceParentHeaderName]); } } }