From 98492228d7d3a734a81c88121d1854b20157ce18 Mon Sep 17 00:00:00 2001 From: Fernando Almeida Date: Sat, 22 Dec 2018 23:42:40 +0000 Subject: [PATCH 1/2] Added support for a meta gzipped message serializer to allow the compression of uncompressed serializers. Created extension methods to ease the creation of gzipped serialization from existing implementations of (de)serializers. Created unit tests to verify expected functionality. Revised .gitignore to exclude VS code specific files. Upgraded package dependencies to the extent possibly without revising target frameworks. --- .gitignore | 4 + Obvs.Tests/GZippedMessageSerializationTest.cs | 157 ++++++++++++++++++ Obvs.Tests/Obvs.Tests.csproj | 20 ++- Obvs/Obvs.csproj | 3 +- .../GZippedMessageDeserializer.cs | 73 ++++++++ .../Serialization/GZippedMessageSerializer.cs | 79 +++++++++ Obvs/Serialization/IMessageSerializer.cs | 2 +- 7 files changed, 327 insertions(+), 11 deletions(-) create mode 100644 Obvs.Tests/GZippedMessageSerializationTest.cs create mode 100644 Obvs/Serialization/GZippedMessageDeserializer.cs create mode 100644 Obvs/Serialization/GZippedMessageSerializer.cs diff --git a/.gitignore b/.gitignore index 9aa29db..2cc2c08 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,7 @@ $RECYCLE.BIN/ packages/ .vs/config/applicationhost.config .vs/ + +# VS Code +.vscode +*.code-workspace diff --git a/Obvs.Tests/GZippedMessageSerializationTest.cs b/Obvs.Tests/GZippedMessageSerializationTest.cs new file mode 100644 index 0000000..beaebeb --- /dev/null +++ b/Obvs.Tests/GZippedMessageSerializationTest.cs @@ -0,0 +1,157 @@ +using System; +using System.Text; +using System.IO; + +using Moq; + +using Obvs.Types; +using Obvs.Serialization; + +using Xunit; + +namespace Obvs.Tests { + + /// + /// Test Gzipped message serialization + /// + public class GZippedMessageSerializationTest { + + public class TestMessage: IMessage { + public string Content {get; set;} = null; + + public override bool Equals(object obj) + { + // + // See the full list of guidelines at + // http://go.microsoft.com/fwlink/?LinkID=85237 + // and also the guidance for operator== at + // http://go.microsoft.com/fwlink/?LinkId=85238 + // + + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + return this.Content.Equals(((TestMessage) obj).Content); + } + + // override object.GetHashCode + public override int GetHashCode() + { + return Content.GetHashCode(); + } + + } + + #region "Serialization tests" + [Fact] + public void Test_GZippedMessageSerializerNullActionConstruction_Fails() { + Assert.Throws(typeof(ArgumentNullException), + () => new GZippedMessageSerializer(null as Action)); + } + + [Fact] + public void Test_GZippedMessageSerializerNullGzippedSerializerConstruction_Fails() { + Assert.Throws(typeof(ArgumentNullException), + () => new GZippedMessageSerializer(null as GZippedMessageSerializer)); + } + + [Fact] + public void Test_GZippedMessageSerializerGzippedSerializerConstruction_Fails() { + Assert.Throws(typeof(ArgumentException), + () => new GZippedMessageSerializer(new GZippedMessageSerializer( (Stream stream, object obj) => {}))); + } + + #endregion + + #region "Deserialization tests" + + [Fact] + public void Test_GZippedMessageDeserializerNullActionConstruction_Fails() { + Assert.Throws( + typeof(ArgumentNullException), + () => new GZippedMessageDeserializer(null as Func)); + } + + + [Fact] + public void Test_GZippedMessageDeserializerNullGzippedSerializerConstruction_Fails() { + Assert.Throws(typeof(ArgumentNullException), + () => new GZippedMessageDeserializer(null as GZippedMessageDeserializer)); + } + #endregion + + [Fact] + public void Test_GZippedMessageDeserializerGzippedSerializerConstruction_Fails() { + Assert.Throws(typeof(ArgumentException), + () => new GZippedMessageDeserializer(new GZippedMessageDeserializer( (Stream stream) => new TestMessage{}))); + } + + [Fact] + public void Test_GzippedMessageFullSerialization_Succeeds() { + var content = "test"; + var wasSerializeActionCalled = false; + Action serializeAction = (stream, obj) => { + wasSerializeActionCalled = true; + var message = obj as TestMessage; + var contentBytes = Encoding.UTF8.GetBytes(message.Content); + stream.Write(contentBytes, 0, contentBytes.Length); + }; + var messageSerializer = new GZippedMessageSerializer(serializeAction); + + var wasDeserializeFuncCalled = false; + Func deserializeFunction = stream => { + wasDeserializeFuncCalled = true; + var buffer = new byte[Encoding.UTF8.GetByteCount(content)]; + var numBytesRead = stream.Read(buffer, 0, buffer.Length); + var deserializedMessage = new TestMessage { + Content = Encoding.UTF8.GetString(buffer) + }; + return deserializedMessage; + }; + var messageDeserializer = new GZippedMessageDeserializer(deserializeFunction); + + var testMessage = new TestMessage { + Content = content + }; + var messageStream = new MemoryStream(); + messageSerializer.Serialize(messageStream, testMessage); + Assert.True(wasSerializeActionCalled); + messageStream.Position = 0; + var deserializedTestMessage = messageDeserializer.Deserialize(messageStream); + Assert.True(wasDeserializeFuncCalled); + + Assert.Equal(testMessage, deserializedTestMessage); + } + + #region "IMessage serializer extensions" + [Fact] + public void Test_SerializeGZipped_Fails() { + var messageSerializer = new GZippedMessageSerializer((stream, obj) => {}); + Assert.Throws(typeof(ArgumentException), () => messageSerializer.SerializeGZipped()); + } + + [Fact] + public void Test_DeserializeGZipped_Fails() { + var messageDeserializer = new GZippedMessageDeserializer((stream) => new TestMessage{}); + Assert.Throws(typeof(ArgumentException), () => messageDeserializer.DeserializeGZipped()); + } + + [Fact] + public void Test_SerializeGZipped_Success() { + var messageSerializerMock = new Mock(); + var gzippedMessageSerializer = messageSerializerMock.Object.SerializeGZipped(); + Assert.NotNull(gzippedMessageSerializer); + } + + [Fact] + public void Test_DeserializeGZipped_Success() { + var messageDeserializerMock = new Mock>(); + var gzippedMessageDeserializer = messageDeserializerMock.Object.DeserializeGZipped(); + Assert.NotNull(gzippedMessageDeserializer); + } + #endregion + + } +} \ No newline at end of file diff --git a/Obvs.Tests/Obvs.Tests.csproj b/Obvs.Tests/Obvs.Tests.csproj index d357150..5a3c2e7 100644 --- a/Obvs.Tests/Obvs.Tests.csproj +++ b/Obvs.Tests/Obvs.Tests.csproj @@ -1,19 +1,21 @@ - - + + netcoreapp2.0 Christopher Read Copyright © Christopher Read 2014 - + false + true - - - - - + + + + + + - + \ No newline at end of file diff --git a/Obvs/Obvs.csproj b/Obvs/Obvs.csproj index 66ff5b9..12fb42e 100644 --- a/Obvs/Obvs.csproj +++ b/Obvs/Obvs.csproj @@ -10,13 +10,14 @@ obvs messaging microservice bus messagebus rx reactive servicebus New csproj format An observable microservice bus .NET library, based on Reactive Extensions. Search 'Obvs' for other transport, serialization, and logging extensions. + true - + diff --git a/Obvs/Serialization/GZippedMessageDeserializer.cs b/Obvs/Serialization/GZippedMessageDeserializer.cs new file mode 100644 index 0000000..b5d3a2a --- /dev/null +++ b/Obvs/Serialization/GZippedMessageDeserializer.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using System.IO.Compression; + +namespace Obvs.Serialization +{ + + /// + /// Gzipped message deserializer + /// + /// Type of message + public class GZippedMessageDeserializer : MessageDeserializerBase + where TMessage : class + { + /// + /// Message from stream deserializer functor + /// + private readonly Func _messageStreamDeserializerFn; + + /// + /// Constructor + /// + /// Message deserializer fn + public GZippedMessageDeserializer(Func messageStreamDeserializerFn) + { + if (messageStreamDeserializerFn == null) { + throw new ArgumentNullException(nameof(messageStreamDeserializerFn)); + } + _messageStreamDeserializerFn = messageStreamDeserializerFn; + } + + /// + /// Constructor + /// + /// Message deserializer implementation< + public GZippedMessageDeserializer(IMessageDeserializer messageDeserializer) { + if (messageDeserializer == null) { + throw new ArgumentNullException(nameof(messageDeserializer)); + } + var gzippedMessageDeserializer = messageDeserializer as GZippedMessageDeserializer; + if (gzippedMessageDeserializer != null) { + throw new ArgumentException("Invalid message deserializer GZippedMessageDeserializer"); + } + _messageStreamDeserializerFn = messageDeserializer.Deserialize; + } + + /// + public override TMessage Deserialize(Stream stream) + { + using (var gzipStream = new GZipStream(stream, CompressionMode.Decompress, true)) + { + return _messageStreamDeserializerFn(gzipStream); + } + } + } + + public static class GZippedMessageDeserializerExtensions { + + /// + /// Apply GZip decompression to an existing message serializer + /// + /// Type of message + public static GZippedMessageDeserializer DeserializeGZipped( + this IMessageDeserializer messageDeserializer + ) where TMessage : class { + var gzippedMessageSerializer = messageDeserializer as GZippedMessageDeserializer; + if (gzippedMessageSerializer != null) { + throw new ArgumentException("Message serializer implementation cannot be GzippedMessageSerializer"); + } + return new GZippedMessageDeserializer(messageDeserializer.Deserialize); + } + } +} \ No newline at end of file diff --git a/Obvs/Serialization/GZippedMessageSerializer.cs b/Obvs/Serialization/GZippedMessageSerializer.cs new file mode 100644 index 0000000..d96ef02 --- /dev/null +++ b/Obvs/Serialization/GZippedMessageSerializer.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using System.IO.Compression; + +namespace Obvs.Serialization +{ + /// + /// Gzipped message serializer + /// + public class GZippedMessageSerializer : IMessageSerializer + { + + /// + /// Message to strem serializer functor + /// + private readonly Action _messageStreamSerializerFn; + + /// + /// Compression level + /// + private readonly CompressionLevel _compressionLevel; + + /// + /// Constructor + /// + /// Message stream serializer + /// Compression level + public GZippedMessageSerializer(Action messageStreamSerializerFn, CompressionLevel compressionLevel = CompressionLevel.Optimal) + { + if (messageStreamSerializerFn == null) { + throw new ArgumentNullException(nameof(messageStreamSerializerFn)); + } + _messageStreamSerializerFn = messageStreamSerializerFn; + _compressionLevel = compressionLevel; + } + + /// + /// Constructor + /// + /// Message stream serializer + /// Compression level + public GZippedMessageSerializer(IMessageSerializer messageSerializer, CompressionLevel compressionLevel = CompressionLevel.Optimal) + { + if (messageSerializer == null) { + throw new ArgumentNullException(nameof(messageSerializer)); + } + var gzippedMessageSerializer = messageSerializer as GZippedMessageSerializer; + if (gzippedMessageSerializer != null) { + throw new ArgumentException("Invalid message deserializer GZippedMessageSerializer"); + } + _messageStreamSerializerFn = messageSerializer.Serialize; + _compressionLevel = compressionLevel; + } + + /// + public void Serialize(Stream stream, object message) + { + using (var gzipStream = new GZipStream(stream, CompressionLevel.Fastest, true)) + { + _messageStreamSerializerFn(gzipStream, message); + } + } + } + + public static class GZippedMessageSerializerExtensions { + + /// + /// Apply GZip compression to an existing message serializer + /// + /// Type of message + public static GZippedMessageSerializer SerializeGZipped(this IMessageSerializer messageSerializer) { + var gzippedMessageSerializer = messageSerializer as GZippedMessageSerializer; + if (gzippedMessageSerializer != null) { + throw new ArgumentException("Message serializer implementation cannot be GzippedMessageSerializer"); + } + return new GZippedMessageSerializer(messageSerializer.Serialize); + } + } +} \ No newline at end of file diff --git a/Obvs/Serialization/IMessageSerializer.cs b/Obvs/Serialization/IMessageSerializer.cs index 7d92f6a..24fef17 100644 --- a/Obvs/Serialization/IMessageSerializer.cs +++ b/Obvs/Serialization/IMessageSerializer.cs @@ -2,7 +2,7 @@ namespace Obvs.Serialization { - public static class MessageSerializerExtentions + public static class MessageSerializerExtensions { public static byte[] Serialize(this IMessageSerializer serializer, object obj) { From 627b6af90de4c17553dec82dfd1416a9f4fcb726 Mon Sep 17 00:00:00 2001 From: Fernando Almeida Date: Tue, 1 Jan 2019 23:56:47 +0000 Subject: [PATCH 2/2] Removed Moq dependency from Obvs.Tests to continue to favor FakeItEasy. Revised Gzipped message serializer and deserializer extension methods sanity check for improved readability. Revised GZippedMessageSerializer to make use of the instance compression level passed upon construction. --- Obvs.Tests/GZippedMessageSerializationTest.cs | 10 +++++----- Obvs.Tests/Obvs.Tests.csproj | 1 - Obvs/Serialization/GZippedMessageDeserializer.cs | 5 ++--- Obvs/Serialization/GZippedMessageSerializer.cs | 5 ++--- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Obvs.Tests/GZippedMessageSerializationTest.cs b/Obvs.Tests/GZippedMessageSerializationTest.cs index beaebeb..fbde0c1 100644 --- a/Obvs.Tests/GZippedMessageSerializationTest.cs +++ b/Obvs.Tests/GZippedMessageSerializationTest.cs @@ -2,7 +2,7 @@ using System.Text; using System.IO; -using Moq; +using FakeItEasy; using Obvs.Types; using Obvs.Serialization; @@ -140,15 +140,15 @@ public void Test_DeserializeGZipped_Fails() { [Fact] public void Test_SerializeGZipped_Success() { - var messageSerializerMock = new Mock(); - var gzippedMessageSerializer = messageSerializerMock.Object.SerializeGZipped(); + var messageSerializer = A.Fake(); + var gzippedMessageSerializer = messageSerializer.SerializeGZipped(); Assert.NotNull(gzippedMessageSerializer); } [Fact] public void Test_DeserializeGZipped_Success() { - var messageDeserializerMock = new Mock>(); - var gzippedMessageDeserializer = messageDeserializerMock.Object.DeserializeGZipped(); + var messageDeserializer = A.Fake>(); + var gzippedMessageDeserializer = messageDeserializer.DeserializeGZipped(); Assert.NotNull(gzippedMessageDeserializer); } #endregion diff --git a/Obvs.Tests/Obvs.Tests.csproj b/Obvs.Tests/Obvs.Tests.csproj index 5a3c2e7..6d03efe 100644 --- a/Obvs.Tests/Obvs.Tests.csproj +++ b/Obvs.Tests/Obvs.Tests.csproj @@ -13,7 +13,6 @@ - diff --git a/Obvs/Serialization/GZippedMessageDeserializer.cs b/Obvs/Serialization/GZippedMessageDeserializer.cs index b5d3a2a..098bb60 100644 --- a/Obvs/Serialization/GZippedMessageDeserializer.cs +++ b/Obvs/Serialization/GZippedMessageDeserializer.cs @@ -63,9 +63,8 @@ public static class GZippedMessageDeserializerExtensions { public static GZippedMessageDeserializer DeserializeGZipped( this IMessageDeserializer messageDeserializer ) where TMessage : class { - var gzippedMessageSerializer = messageDeserializer as GZippedMessageDeserializer; - if (gzippedMessageSerializer != null) { - throw new ArgumentException("Message serializer implementation cannot be GzippedMessageSerializer"); + if (messageDeserializer is GZippedMessageDeserializer) { + throw new ArgumentException("Message serializer implementation cannot be GzippedMessageDeserializer"); } return new GZippedMessageDeserializer(messageDeserializer.Deserialize); } diff --git a/Obvs/Serialization/GZippedMessageSerializer.cs b/Obvs/Serialization/GZippedMessageSerializer.cs index d96ef02..94b13ea 100644 --- a/Obvs/Serialization/GZippedMessageSerializer.cs +++ b/Obvs/Serialization/GZippedMessageSerializer.cs @@ -55,7 +55,7 @@ public GZippedMessageSerializer(IMessageSerializer messageSerializer, Compressio /// public void Serialize(Stream stream, object message) { - using (var gzipStream = new GZipStream(stream, CompressionLevel.Fastest, true)) + using (var gzipStream = new GZipStream(stream, _compressionLevel, true)) { _messageStreamSerializerFn(gzipStream, message); } @@ -69,8 +69,7 @@ public static class GZippedMessageSerializerExtensions { /// /// Type of message public static GZippedMessageSerializer SerializeGZipped(this IMessageSerializer messageSerializer) { - var gzippedMessageSerializer = messageSerializer as GZippedMessageSerializer; - if (gzippedMessageSerializer != null) { + if (messageSerializer is GZippedMessageSerializer) { throw new ArgumentException("Message serializer implementation cannot be GzippedMessageSerializer"); } return new GZippedMessageSerializer(messageSerializer.Serialize);