public void AsArray_ReturningOriginal() { byte[] original = { 1, 2, 3, 4, 5 }; var segment = new ArraySegment <byte>(original); Assert.Same(original, BinaryDataUtilities.AsArray(segment)); }
/// <inheritdoc /> public override void DecodeBinaryModeEventData(ReadOnlyMemory <byte> body, CloudEvent cloudEvent) { Validation.CheckNotNull(cloudEvent, nameof(cloudEvent)); if (cloudEvent.DataContentType is string dataContentType && dataContentType.StartsWith("text/")) { Encoding encoding = MimeUtilities.GetEncoding(new ContentType(dataContentType)); cloudEvent.Data = BinaryDataUtilities.GetString(body, encoding); }
public void AsArray_FromPartialSegment(int offset) { byte[] original = { 1, 2, 3, 4, 5 }; var segment = new ArraySegment <byte>(original, offset, 4); var actual = BinaryDataUtilities.AsArray(segment); Assert.True(actual.SequenceEqual(segment)); Assert.NotSame(original, actual); }
public void EncodeBinaryModeEventData_TextType_String() { var cloudEvent = new CloudEvent().PopulateRequiredAttributes(); cloudEvent.Data = "some text"; cloudEvent.DataContentType = "text/anything"; var bytes = new JsonEventFormatter().EncodeBinaryModeEventData(cloudEvent); Assert.Equal("some text", BinaryDataUtilities.GetString(bytes, Encoding.UTF8)); }
private static T ParseJsonImpl <T>(ReadOnlyMemory <byte> data) { string text = BinaryDataUtilities.GetString(data, Encoding.UTF8); var serializer = new JsonSerializer { DateParseHandling = DateParseHandling.None }; return(serializer.Deserialize <T>(new JsonTextReader(new StringReader(text))) !); }
private Stream CreateJsonStream() { var cloudEvent = new CloudEvent { Data = new { DataName = "DataValue" } }.PopulateRequiredAttributes(); var bytes = new JsonEventFormatter().EncodeStructuredModeMessage(cloudEvent, out _); return(BinaryDataUtilities.AsStream(bytes)); }
public void EncodeBinaryModeEventData_JsonDataType_JToken() { // This would definitely be an odd thing to do, admittedly... var cloudEvent = new CloudEvent().PopulateRequiredAttributes(); cloudEvent.Data = new JValue(100); cloudEvent.DataContentType = "application/json"; var bytes = new JsonEventFormatter().EncodeBinaryModeEventData(cloudEvent); Assert.Equal("100", BinaryDataUtilities.GetString(bytes, Encoding.UTF8)); }
/// <summary> /// Copies a <see cref="CloudEvent"/> into an <see cref="HttpResponse" />. /// </summary> /// <param name="cloudEvent">The CloudEvent to copy. Must not be null, and must be a valid CloudEvent.</param> /// <param name="destination">The response to copy the CloudEvent to. Must not be null.</param> /// <param name="contentMode">Content mode (structured or binary)</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <returns>A task representing the asynchronous operation.</returns> public static async Task CopyToHttpResponseAsync(this CloudEvent cloudEvent, HttpResponse destination, ContentMode contentMode, CloudEventFormatter formatter) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(destination, nameof(destination)); Validation.CheckNotNull(formatter, nameof(formatter)); ReadOnlyMemory <byte> content; ContentType? contentType; switch (contentMode) { case ContentMode.Structured: content = formatter.EncodeStructuredModeMessage(cloudEvent, out contentType); break; case ContentMode.Binary: content = formatter.EncodeBinaryModeEventData(cloudEvent); contentType = MimeUtilities.CreateContentTypeOrNull(formatter.GetOrInferDataContentType(cloudEvent)); break; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } if (contentType is object) { destination.ContentType = contentType.ToString(); } else if (content.Length != 0) { throw new ArgumentException("The 'datacontenttype' attribute value must be specified", nameof(cloudEvent)); } // Map headers in either mode. // Including the headers in structured mode is optional in the spec (as they're already within the body) but // can be useful. destination.Headers.Add(HttpUtilities.SpecVersionHttpHeader, HttpUtilities.EncodeHeaderValue(cloudEvent.SpecVersion.VersionId)); foreach (var attributeAndValue in cloudEvent.GetPopulatedAttributes()) { var attribute = attributeAndValue.Key; var value = attributeAndValue.Value; // The content type is already handled based on the content mode. if (attribute != cloudEvent.SpecVersion.DataContentTypeAttribute) { string headerValue = HttpUtilities.EncodeHeaderValue(attribute.Format(value)); destination.Headers.Add(HttpUtilities.HttpHeaderPrefix + attribute.Name, headerValue); } } destination.ContentLength = content.Length; await BinaryDataUtilities.CopyToStreamAsync(content, destination.Body).ConfigureAwait(false); }
public void EncodeBinaryModeEventData_NoContentType_LeavesBinaryData() { var cloudEvent = new CloudEvent { Data = SampleBinaryData }.PopulateRequiredAttributes(); // EncodeBinaryModeEventData does *not* implicitly encode binary data as JSON. var data = new JsonEventFormatter().EncodeBinaryModeEventData(cloudEvent); var array = BinaryDataUtilities.AsArray(data); Assert.Equal(array, SampleBinaryData); }
/// <summary> /// Copies a <see cref="CloudEvent"/> batch into the specified <see cref="HttpWebRequest"/>. /// </summary> /// <param name="cloudEvents">CloudEvent batch to copy. Must not be null, and must be a valid CloudEvent.</param> /// <param name="destination">The request to populate. Must not be null.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <returns>A task representing the asynchronous operation.</returns> public static async Task CopyToHttpWebRequestAsync(this IReadOnlyList <CloudEvent> cloudEvents, HttpWebRequest destination, CloudEventFormatter formatter) { Validation.CheckCloudEventBatchArgument(cloudEvents, nameof(cloudEvents)); Validation.CheckNotNull(destination, nameof(destination)); Validation.CheckNotNull(formatter, nameof(formatter)); ReadOnlyMemory <byte> content = formatter.EncodeBatchModeMessage(cloudEvents, out var contentType); destination.ContentType = contentType.ToString(); await BinaryDataUtilities.CopyToStreamAsync(content, destination.GetRequestStream()).ConfigureAwait(false); }
/// <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))); } }
public void EncodeBinaryModeEventData_NoContentType_ConvertsStringToJson() { var cloudEvent = new CloudEvent { Data = "some text" }.PopulateRequiredAttributes(); // EncodeBinaryModeEventData doesn't actually populate the content type of the CloudEvent, // but treat the data as if we'd explicitly specified application/json. var data = new JsonEventFormatter().EncodeBinaryModeEventData(cloudEvent); string text = BinaryDataUtilities.GetString(data, Encoding.UTF8); Assert.Equal("\"some text\"", text); }
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))); } }
public async Task ToCloudEvent_Invalid() { var cloudEvent = new CloudEvent().PopulateRequiredAttributes(); var formatter = new JsonEventFormatter(); var contentBytes = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType); // Remove the required 'id' attribute var obj = JObject.Parse(BinaryDataUtilities.GetString(contentBytes, Encoding.UTF8)); obj.Remove("id"); contentBytes = Encoding.UTF8.GetBytes(obj.ToString()); await Assert.ThrowsAsync <ArgumentException>(() => CreateRequestMessage(contentBytes, contentType).ToCloudEventAsync(formatter)); await Assert.ThrowsAsync <ArgumentException>(() => CreateResponseMessage(contentBytes, contentType).ToCloudEventAsync(formatter)); }
/// <summary> /// Copies a <see cref="CloudEvent"/> batch into an <see cref="HttpListenerResponse" />. /// </summary> /// <param name="cloudEvents">The CloudEvent batch to copy. Must not be null, and must be a valid CloudEvent.</param> /// <param name="destination">The response to copy the CloudEvent to. Must not be null.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <returns>A task representing the asynchronous operation.</returns> public static async Task CopyToHttpListenerResponseAsync(this IReadOnlyList <CloudEvent> cloudEvents, HttpListenerResponse destination, CloudEventFormatter formatter) { Validation.CheckCloudEventBatchArgument(cloudEvents, nameof(cloudEvents)); Validation.CheckNotNull(destination, nameof(destination)); Validation.CheckNotNull(formatter, nameof(formatter)); // TODO: Validate that all events in the batch have the same version? // See https://github.com/cloudevents/spec/issues/807 ReadOnlyMemory <byte> content = formatter.EncodeBatchModeMessage(cloudEvents, out var contentType); destination.ContentType = contentType.ToString(); await BinaryDataUtilities.CopyToStreamAsync(content, destination.OutputStream).ConfigureAwait(false); }
// TODO: Update to a newer version of MQTTNet and support both binary and structured mode? /// <summary> /// Converts a CloudEvent to <see cref="MqttApplicationMessage"/>. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Currently only structured mode is supported.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <param name="topic">The MQTT topic for the message. May be null.</param> public static MqttApplicationMessage ToMqttApplicationMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter, string?topic) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); switch (contentMode) { case ContentMode.Structured: return(new MqttApplicationMessage { Topic = topic, Payload = BinaryDataUtilities.AsArray(formatter.EncodeStructuredModeMessage(cloudEvent, out _)) }); default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } }
public async Task CopyToHttpWebRequestAsync_Batch() { var batch = CreateSampleBatch(); HttpWebRequest request = WebRequest.CreateHttp(ListenerAddress + "ep"); request.Method = "POST"; await batch.CopyToHttpWebRequestAsync(request, new JsonEventFormatter()); var(bytes, contentType) = await SendRequestAsync(request, async context => { var bytes = await BinaryDataUtilities.ToReadOnlyMemoryAsync(context.Request.InputStream); var contentType = context.Request.Headers["Content-Type"]; return(bytes, contentType); }); Assert.Equal(MimeUtilities.BatchMediaType + "+json; charset=utf-8", contentType); var parsedBatch = new JsonEventFormatter().DecodeBatchModeMessage(bytes, MimeUtilities.CreateContentTypeOrNull(contentType), extensionAttributes: null); AssertBatchesEqual(batch, parsedBatch); }
/// <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 a CloudEvent to <see cref="Message"/>. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Structured or binary.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> public static Message ToAmqpMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); var applicationProperties = MapHeaders(cloudEvent); RestrictedDescribed bodySection; Properties properties; switch (contentMode) { case ContentMode.Structured: bodySection = new Data { Binary = BinaryDataUtilities.AsArray(formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType)) }; // TODO: What about the other parts of the content type? properties = new Properties { ContentType = contentType.MediaType }; break; case ContentMode.Binary: bodySection = new Data { Binary = BinaryDataUtilities.AsArray(formatter.EncodeBinaryModeEventData(cloudEvent)) }; properties = new Properties { ContentType = formatter.GetOrInferDataContentType(cloudEvent) }; break; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } return(new Message { ApplicationProperties = applicationProperties, BodySection = bodySection, Properties = properties }); }
/// <summary> /// Converts a CloudEvent to a Kafka message. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Structured or binary.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> public static Message <string?, byte[]> ToKafkaMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); // TODO: Is this appropriate? Why can't we transport a CloudEvent without data in Kafka? Validation.CheckArgument(cloudEvent.Data is object, nameof(cloudEvent), "Only CloudEvents with data can be converted to Kafka messages"); var headers = MapHeaders(cloudEvent); string?key = (string?)cloudEvent[Partitioning.PartitionKeyAttribute]; byte[] value; string?contentTypeHeaderValue; switch (contentMode) { case ContentMode.Structured: value = BinaryDataUtilities.AsArray(formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType)); // TODO: What about the non-media type parts? contentTypeHeaderValue = contentType.MediaType; break; case ContentMode.Binary: value = BinaryDataUtilities.AsArray(formatter.EncodeBinaryModeEventData(cloudEvent)); contentTypeHeaderValue = formatter.GetOrInferDataContentType(cloudEvent); break; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } if (contentTypeHeaderValue is object) { headers.Add(KafkaContentTypeAttributeName, Encoding.UTF8.GetBytes(contentTypeHeaderValue)); } return(new Message <string?, byte[]> { Headers = headers, Value = value, Key = key }); }
/// <summary> /// Converts a CloudEvent to <see cref="Message"/>. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Structured or binary.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <returns>A <see cref="Message"/>.</returns> public static Message ToServiceBusMessage( this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter ) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); Message message; switch (contentMode) { case ContentMode.Structured: message = new Message( BinaryDataUtilities.AsArray(formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType)) ) { ContentType = contentType.MediaType, }; break; case ContentMode.Binary: message = new Message( BinaryDataUtilities.AsArray(formatter.EncodeBinaryModeEventData(cloudEvent)) ) { ContentType = cloudEvent.DataContentType, }; break; default: throw new ArgumentException($"Unsupported content mode: {contentMode}", nameof(contentMode)); } MapHeaders(cloudEvent, message); return(message); }
/// <inheritdoc /> public override CloudEvent DecodeStructuredModeMessage(ReadOnlyMemory <byte> body, ContentType?contentType, IEnumerable <CloudEventAttribute>?extensionAttributes) => DecodeStructuredModeMessageImpl(BinaryDataUtilities.AsStream(body), contentType, extensionAttributes, false).GetAwaiter().GetResult();
private static ReadOnlyMemory <byte> GetContent(HttpResponse response) { response.Body.Position = 0; return(BinaryDataUtilities.ToReadOnlyMemory(response.Body)); }
/// <inheritdoc /> public override IReadOnlyList <CloudEvent> DecodeBatchModeMessage(ReadOnlyMemory <byte> body, ContentType?contentType, IEnumerable <CloudEventAttribute>?extensionAttributes) => DecodeBatchModeMessage(BinaryDataUtilities.AsStream(body), contentType, extensionAttributes);
/// <inheritdoc /> public override void DecodeBinaryModeEventData(ReadOnlyMemory <byte> body, CloudEvent cloudEvent) => cloudEvent.Data = body.Length == 0 ? null : s_jsonParser.Parse <T>(new StreamReader(BinaryDataUtilities.AsStream(body)));
private static HttpRequest CreateRequest(ReadOnlyMemory <byte> content, ContentType contentType) => new DefaultHttpRequest(new DefaultHttpContext()) { ContentType = contentType.ToString(), Body = BinaryDataUtilities.AsStream(content) };