Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ $RECYCLE.BIN/
.DS_Store
.vs/config/applicationhost.config
.vs/

# VS Code
.vscode
*.code-workspace
.idea/

# Windows Installer files
Expand Down
157 changes: 157 additions & 0 deletions Obvs.Tests/GZippedMessageSerializationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
using System.Text;
using System.IO;

using FakeItEasy;

using Obvs.Types;
using Obvs.Serialization;

using Xunit;

namespace Obvs.Tests {

/// <summary>
/// Test Gzipped message serialization
/// </summary>
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<Stream, object>));
}

[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<TestMessage>(null as Func<System.IO.Stream, TestMessage>));
}


[Fact]
public void Test_GZippedMessageDeserializerNullGzippedSerializerConstruction_Fails() {
Assert.Throws(typeof(ArgumentNullException),
() => new GZippedMessageDeserializer<Obvs.Types.IMessage>(null as GZippedMessageDeserializer<TestMessage>));
}
#endregion

[Fact]
public void Test_GZippedMessageDeserializerGzippedSerializerConstruction_Fails() {
Assert.Throws(typeof(ArgumentException),
() => new GZippedMessageDeserializer<TestMessage>(new GZippedMessageDeserializer<TestMessage>( (Stream stream) => new TestMessage{})));
}

[Fact]
public void Test_GzippedMessageFullSerialization_Succeeds() {
var content = "test";
var wasSerializeActionCalled = false;
Action<Stream, object> 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<Stream, TestMessage> 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<TestMessage>(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<TestMessage>((stream) => new TestMessage{});
Assert.Throws(typeof(ArgumentException), () => messageDeserializer.DeserializeGZipped());
}

[Fact]
public void Test_SerializeGZipped_Success() {
var messageSerializer = A.Fake<IMessageSerializer>();
var gzippedMessageSerializer = messageSerializer.SerializeGZipped();
Assert.NotNull(gzippedMessageSerializer);
}

[Fact]
public void Test_DeserializeGZipped_Success() {
var messageDeserializer = A.Fake<IMessageDeserializer<TestMessage>>();
var gzippedMessageDeserializer = messageDeserializer.DeserializeGZipped<TestMessage>();
Assert.NotNull(gzippedMessageDeserializer);
}
#endregion

}
}
5 changes: 3 additions & 2 deletions Obvs.Tests/Obvs.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
<TargetFramework>net5.0</TargetFramework>
<Company>Christopher Read</Company>
<Copyright>Copyright © Christopher Read 2014</Copyright>
<Authors />
<Authors/>
<IsPackable>false</IsPackable>
<GenerateFullPaths>true</GenerateFullPaths>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="6.2.1" />
Expand All @@ -17,6 +18,6 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Obvs\Obvs.csproj" />
<ProjectReference Include="..\Obvs\Obvs.csproj"/>
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions Obvs/Obvs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<PackageTags>obvs messaging microservice bus messagebus rx reactive servicebus</PackageTags>
<PackageReleaseNotes>New csproj format</PackageReleaseNotes>
<Description>An observable microservice bus .NET library, based on Reactive Extensions. Search 'Obvs' for other transport, serialization, and logging extensions.</Description>
<GenerateFullPaths>true</GenerateFullPaths>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'net472' ">
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="5.0.0" />
Expand Down
72 changes: 72 additions & 0 deletions Obvs/Serialization/GZippedMessageDeserializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.IO;
using System.IO.Compression;

namespace Obvs.Serialization
{

/// <summary>
/// Gzipped message deserializer
/// </summary>
/// <typeparam name="TMessage">Type of message</typeparam>
public class GZippedMessageDeserializer<TMessage> : MessageDeserializerBase<TMessage>
where TMessage : class
{
/// <summary>
/// Message from stream deserializer functor
/// </summary>
private readonly Func<Stream, TMessage> _messageStreamDeserializerFn;

/// <summary>
/// Constructor
/// </summary>
/// <param name="messageStreamDeserializerFn">Message deserializer fn</param>
public GZippedMessageDeserializer(Func<Stream, TMessage> messageStreamDeserializerFn)
{
if (messageStreamDeserializerFn == null) {
throw new ArgumentNullException(nameof(messageStreamDeserializerFn));
}
_messageStreamDeserializerFn = messageStreamDeserializerFn;
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="messageDeserializer">Message deserializer implementation<</param>
public GZippedMessageDeserializer(IMessageDeserializer<TMessage> messageDeserializer) {
if (messageDeserializer == null) {
throw new ArgumentNullException(nameof(messageDeserializer));
}
var gzippedMessageDeserializer = messageDeserializer as GZippedMessageDeserializer<TMessage>;
if (gzippedMessageDeserializer != null) {
throw new ArgumentException("Invalid message deserializer GZippedMessageDeserializer");
}
_messageStreamDeserializerFn = messageDeserializer.Deserialize;
}

/// <inheritdoc />
public override TMessage Deserialize(Stream stream)
{
using (var gzipStream = new GZipStream(stream, CompressionMode.Decompress, true))
{
return _messageStreamDeserializerFn(gzipStream);
}
}
}

public static class GZippedMessageDeserializerExtensions {

/// <summary>
/// Apply GZip decompression to an existing message serializer
/// </summary>
/// <typeparam name="TMessage">Type of message</typeparam>
public static GZippedMessageDeserializer<TMessage> DeserializeGZipped<TMessage>(
this IMessageDeserializer<TMessage> messageDeserializer
) where TMessage : class {
if (messageDeserializer is GZippedMessageDeserializer<TMessage>) {
throw new ArgumentException("Message serializer implementation cannot be GzippedMessageDeserializer");
}
return new GZippedMessageDeserializer<TMessage>(messageDeserializer.Deserialize);
}
}
}
78 changes: 78 additions & 0 deletions Obvs/Serialization/GZippedMessageSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.IO;
using System.IO.Compression;

namespace Obvs.Serialization
{
/// <summary>
/// Gzipped message serializer
/// </summary>
public class GZippedMessageSerializer : IMessageSerializer
{

/// <summary>
/// Message to strem serializer functor
/// </summary>
private readonly Action<Stream, object> _messageStreamSerializerFn;

/// <summary>
/// Compression level
/// </summary>
private readonly CompressionLevel _compressionLevel;

/// <summary>
/// Constructor
/// </summary>
/// <param name="messageStreamSerializerFn">Message stream serializer</param>
/// <param name="compressionLevel">Compression level</param>
public GZippedMessageSerializer(Action<Stream, object> messageStreamSerializerFn, CompressionLevel compressionLevel = CompressionLevel.Optimal)
{
if (messageStreamSerializerFn == null) {
throw new ArgumentNullException(nameof(messageStreamSerializerFn));
}
_messageStreamSerializerFn = messageStreamSerializerFn;
_compressionLevel = compressionLevel;
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="messageSerializer">Message stream serializer</param>
/// <param name="compressionLevel">Compression level</param>
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;
}

/// <inheritdoc />
public void Serialize(Stream stream, object message)
{
using (var gzipStream = new GZipStream(stream, _compressionLevel, true))
{
_messageStreamSerializerFn(gzipStream, message);
}
}
}

public static class GZippedMessageSerializerExtensions {

/// <summary>
/// Apply GZip compression to an existing message serializer
/// </summary>
/// <typeparam name="TMessage">Type of message</typeparam>
public static GZippedMessageSerializer SerializeGZipped(this IMessageSerializer messageSerializer) {
if (messageSerializer is GZippedMessageSerializer) {
throw new ArgumentException("Message serializer implementation cannot be GzippedMessageSerializer");
}
return new GZippedMessageSerializer(messageSerializer.Serialize);
}
}
}
2 changes: 1 addition & 1 deletion Obvs/Serialization/IMessageSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Obvs.Serialization
{
public static class MessageSerializerExtentions
public static class MessageSerializerExtensions
{
public static byte[] Serialize(this IMessageSerializer serializer, object obj)
{
Expand Down