diff --git a/NClient.sln b/NClient.sln index bf29e7e9c..cb239b085 100644 --- a/NClient.sln +++ b/NClient.sln @@ -93,6 +93,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NClient.Providers.Api.Rest" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NClient.Providers.Api.Rest.Tests", "tests\NClient.Providers\NClient.Providers.Api.Rest.Tests\NClient.Providers.Api.Rest.Tests.csproj", "{27521108-9C67-44AE-885C-3BE343421955}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NClient.Providers.Serialization.Protobuf.ProtobufNet", "src\NClient.Providers\NClient.Providers.Serialization.Protobuf.ProtobufNet\NClient.Providers.Serialization.Protobuf.ProtobufNet.csproj", "{EC0A387A-AAEE-44EF-8AB0-6EF2D745E772}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests", "tests\NClient.Providers\NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests\NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests.csproj", "{B10DCE0F-7BF1-4D0C-AAFD-F4D1D0CBD4A3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -279,6 +283,18 @@ Global {27521108-9C67-44AE-885C-3BE343421955}.Release|Any CPU.ActiveCfg = Release|Any CPU {27521108-9C67-44AE-885C-3BE343421955}.Release|Any CPU.Build.0 = Release|Any CPU {27521108-9C67-44AE-885C-3BE343421955}.TestRelease|Any CPU.ActiveCfg = Release|Any CPU + {EC0A387A-AAEE-44EF-8AB0-6EF2D745E772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC0A387A-AAEE-44EF-8AB0-6EF2D745E772}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC0A387A-AAEE-44EF-8AB0-6EF2D745E772}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC0A387A-AAEE-44EF-8AB0-6EF2D745E772}.Release|Any CPU.Build.0 = Release|Any CPU + {EC0A387A-AAEE-44EF-8AB0-6EF2D745E772}.TestRelease|Any CPU.ActiveCfg = Debug|Any CPU + {EC0A387A-AAEE-44EF-8AB0-6EF2D745E772}.TestRelease|Any CPU.Build.0 = Debug|Any CPU + {B10DCE0F-7BF1-4D0C-AAFD-F4D1D0CBD4A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B10DCE0F-7BF1-4D0C-AAFD-F4D1D0CBD4A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B10DCE0F-7BF1-4D0C-AAFD-F4D1D0CBD4A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B10DCE0F-7BF1-4D0C-AAFD-F4D1D0CBD4A3}.Release|Any CPU.Build.0 = Release|Any CPU + {B10DCE0F-7BF1-4D0C-AAFD-F4D1D0CBD4A3}.TestRelease|Any CPU.ActiveCfg = Debug|Any CPU + {B10DCE0F-7BF1-4D0C-AAFD-F4D1D0CBD4A3}.TestRelease|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -318,6 +334,8 @@ Global {FA3D9C32-CC55-4D39-8F49-5FC3040EBD78} = {B1BCB5AE-6A5E-49F7-A00A-6B2931DF628C} {0BB4D666-9C18-4F71-A0A3-88DCC275139D} = {DCB997FD-29F4-48D9-8348-093761DA42E7} {27521108-9C67-44AE-885C-3BE343421955} = {B1BCB5AE-6A5E-49F7-A00A-6B2931DF628C} + {EC0A387A-AAEE-44EF-8AB0-6EF2D745E772} = {DCB997FD-29F4-48D9-8348-093761DA42E7} + {B10DCE0F-7BF1-4D0C-AAFD-F4D1D0CBD4A3} = {B1BCB5AE-6A5E-49F7-A00A-6B2931DF628C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {244A4C58-8FA7-496C-ABCF-72F333149A20} diff --git a/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/Extensions/ProtobufSerializationExtensions.cs b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/Extensions/ProtobufSerializationExtensions.cs new file mode 100644 index 000000000..b98fbb1c7 --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/Extensions/ProtobufSerializationExtensions.cs @@ -0,0 +1,80 @@ +using NClient.Common.Helpers; +using NClient.Providers.Serialization; +using NClient.Providers.Serialization.Protobuf.ProtobufNet; + +// ReSharper disable once CheckNamespace +namespace NClient +{ + public static class ProtobufSerializationExtensions + { + /// + /// Sets ProtobufNet based used to create instance of . + /// + /// + /// + /// + /// + /// + public static INClientOptionalBuilder WithProtobufNetSerialization( + this INClientOptionalBuilder optionalBuilder) + where TClient : class + { + Ensure.IsNotNull(optionalBuilder, nameof(optionalBuilder)); + + return optionalBuilder.WithCustomSerialization(new ProtobufSerializerProvider()); + } + + /// + /// Sets ProtobufNet based used to create instance of . + /// + /// + /// The settings to be used with . + /// + /// + /// + /// + public static INClientOptionalBuilder WithProtobufNetSerialization( + this INClientOptionalBuilder optionalBuilder, + ProtobufSerializerSettings protobufSerializerSettings) + where TClient : class + { + Ensure.IsNotNull(optionalBuilder, nameof(optionalBuilder)); + Ensure.IsNotNull(protobufSerializerSettings, nameof(protobufSerializerSettings)); + + return optionalBuilder.WithCustomSerialization(new ProtobufSerializerProvider(protobufSerializerSettings)); + } + + /// + /// Sets ProtobufNet based used to create instance of . + /// + /// + /// + /// + /// + public static INClientFactoryOptionalBuilder WithProtobufNetSerialization( + this INClientFactoryOptionalBuilder optionalBuilder) + { + Ensure.IsNotNull(optionalBuilder, nameof(optionalBuilder)); + + return optionalBuilder.WithCustomSerialization(new ProtobufSerializerProvider()); + } + + /// + /// Sets ProtobufNet based used to create instance of . + /// + /// + /// The settings to be used with . + /// + /// + /// + public static INClientFactoryOptionalBuilder WithProtobufNetSerialization( + this INClientFactoryOptionalBuilder optionalBuilder, + ProtobufSerializerSettings protobufSerializerSettings) + { + Ensure.IsNotNull(optionalBuilder, nameof(optionalBuilder)); + Ensure.IsNotNull(protobufSerializerSettings, nameof(protobufSerializerSettings)); + + return optionalBuilder.WithCustomSerialization(new ProtobufSerializerProvider(protobufSerializerSettings)); + } + } +} diff --git a/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/Extensions/UsingProtobufSerializationExtensions.cs b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/Extensions/UsingProtobufSerializationExtensions.cs new file mode 100644 index 000000000..1c691c231 --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/Extensions/UsingProtobufSerializationExtensions.cs @@ -0,0 +1,68 @@ +using NClient.Common.Helpers; +using NClient.Providers.Serialization; +using NClient.Providers.Serialization.Protobuf.ProtobufNet; + +// ReSharper disable once CheckNamespace +namespace NClient +{ + public static class UsingProtobufSerializationExtensions + { + /// + /// Sets ProtobufNet based used to create instance of . + /// + /// + public static INClientOptionalBuilder UsingProtobufNetSerialization( + this INClientSerializationBuilder serializationBuilder) + where TClient : class + { + Ensure.IsNotNull(serializationBuilder, nameof(serializationBuilder)); + + return serializationBuilder.UsingCustomSerializer(new ProtobufSerializerProvider()); + } + + /// + /// Sets ProtobufNet based used to create instance of . + /// + /// + /// The settings to be used with . + public static INClientOptionalBuilder UsingProtobufNetSerialization( + this INClientSerializationBuilder serializationBuilder, + ProtobufSerializerSettings protobufSerializerSettings) + where TClient : class + { + Ensure.IsNotNull(serializationBuilder, nameof(serializationBuilder)); + Ensure.IsNotNull(protobufSerializerSettings, nameof(protobufSerializerSettings)); + + return serializationBuilder + .UsingCustomSerializer(new ProtobufSerializerProvider(protobufSerializerSettings)); + } + + /// + /// Sets ProtobufNet based used to create instance of . + /// + /// + public static INClientFactoryOptionalBuilder UsingProtobufNetSerialization( + this INClientFactorySerializationBuilder serializationBuilder) + { + Ensure.IsNotNull(serializationBuilder, nameof(serializationBuilder)); + + return serializationBuilder.UsingCustomSerializer(new ProtobufSerializerProvider()); + } + + /// + /// Sets ProtobufNet based used to create instance of . + /// + /// + /// The settings to be used with . + public static INClientFactoryOptionalBuilder UsingProtobufNetSerialization( + this INClientFactorySerializationBuilder serializationBuilder, + ProtobufSerializerSettings protobufSerializerSettings) + { + Ensure.IsNotNull(serializationBuilder, nameof(serializationBuilder)); + Ensure.IsNotNull(protobufSerializerSettings, nameof(protobufSerializerSettings)); + + return serializationBuilder + .UsingCustomSerializer(new ProtobufSerializerProvider(protobufSerializerSettings)); + } + } +} diff --git a/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/NClient.Providers.Serialization.Protobuf.ProtobufNet.csproj b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/NClient.Providers.Serialization.Protobuf.ProtobufNet.csproj new file mode 100644 index 000000000..42cc0b27e --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/NClient.Providers.Serialization.Protobuf.ProtobufNet.csproj @@ -0,0 +1,52 @@ + + + + + $(AssemblyName) + $(VersionPrefix) + $(VersionSuffix) + logo.png + https://nclient.github.io + https://github.com/nclient/nclient + git + NClient;NClient.Providers;NClient.Providers.Serialization;Protobuf;Google;protobuf-net + The provider that allows you to use the Protobuf serializer. + kingmidas74 + LICENSE + true + + + + netstandard2.0;netstandard2.1 + 9.0 + enable + true + true + Debug;Release + AnyCPU + $(SolutionDir)/bin/src/$(Configuration)/$(AssemblyName) + + + + + + + + + + + + + + Common\Ensure.cs + + + Common\Converters.cs + + + + + + + + \ No newline at end of file diff --git a/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializer.cs b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializer.cs new file mode 100644 index 000000000..7162fe062 --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializer.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using NClient.Common.Helpers; +using ProtoBuf; + +namespace NClient.Providers.Serialization.Protobuf.ProtobufNet +{ + public class ProtobufSerializer : ISerializer + { + private readonly ProtobufSerializerSettings _protobufSerializerSettings; + + public string ContentType { get; } = String.Empty; + + public ProtobufSerializer(ProtobufSerializerSettings protobufSerializerSettings) + { + Ensure.IsNotNull(protobufSerializerSettings, nameof(protobufSerializerSettings)); + Ensure.IsNotNullOrEmpty(protobufSerializerSettings.ContentTypeHeader, nameof(protobufSerializerSettings.ContentTypeHeader)); + + _protobufSerializerSettings = protobufSerializerSettings; + ContentType = _protobufSerializerSettings.ContentTypeHeader; + } + + public object? Deserialize(string source, Type returnType) + { + Ensure.IsNotNull(source, nameof(source)); + Ensure.IsNotNull(returnType, nameof(returnType)); + + var bytes = Converters.GetBytes(source); + + using var memoryStream = new MemoryStream(bytes); + return Serializer.Deserialize(returnType, memoryStream); + } + + public string Serialize(T? value) + { + Ensure.IsNotNull(value, nameof(value)); + + using var memoryStream = new MemoryStream(); + Serializer.Serialize(memoryStream, value); + + return Converters.GetString(memoryStream.ToArray()); + } + } +} diff --git a/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializerProvider.cs b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializerProvider.cs new file mode 100644 index 000000000..ffafe993a --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializerProvider.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Logging; +using NClient.Common.Helpers; + +namespace NClient.Providers.Serialization.Protobuf.ProtobufNet +{ + public class ProtobufSerializerProvider : ISerializerProvider + { + private readonly ProtobufSerializerSettings _protobufSerializerSettings; + + /// + /// Creates the Protobuf serializer provider. + /// + public ProtobufSerializerProvider() + { + _protobufSerializerSettings = new ProtobufSerializerSettings("application/proto"); + } + + /// + /// Creates the Newtonsoft.Json based serializer provider. + /// + /// The settings to be used with . + public ProtobufSerializerProvider(ProtobufSerializerSettings protobufSerializerSettings) + { + Ensure.IsNotNull(protobufSerializerSettings, nameof(protobufSerializerSettings)); + + _protobufSerializerSettings = protobufSerializerSettings; + } + + public ISerializer Create(ILogger? logger) + { + return new ProtobufSerializer(_protobufSerializerSettings); + } + } +} diff --git a/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializerSettings.cs b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializerSettings.cs new file mode 100644 index 000000000..a196fb708 --- /dev/null +++ b/src/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet/ProtobufSerializerSettings.cs @@ -0,0 +1,16 @@ +using NClient.Common.Helpers; + +namespace NClient.Providers.Serialization.Protobuf.ProtobufNet +{ + public record ProtobufSerializerSettings + { + public string ContentTypeHeader { get; } = string.Empty; + + public ProtobufSerializerSettings(string contentTypeHeader) + { + Ensure.IsNotNullOrEmpty(contentTypeHeader, nameof(contentTypeHeader)); + + ContentTypeHeader = contentTypeHeader; + } + } +} diff --git a/src/NClient/NClient.Common/Helpers/Converters.cs b/src/NClient/NClient.Common/Helpers/Converters.cs new file mode 100644 index 000000000..36b7c36dd --- /dev/null +++ b/src/NClient/NClient.Common/Helpers/Converters.cs @@ -0,0 +1,21 @@ +using System; + +namespace NClient.Common.Helpers +{ + internal static class Converters + { + public static string GetString(byte[] source) + { + var chars = new char[source.Length]; + Buffer.BlockCopy(source, 0, chars, 0, source.Length); + return new string(chars); + } + + public static byte[] GetBytes(string source) + { + byte[] result = new byte[source.Length]; + Buffer.BlockCopy(source.ToCharArray(), 0, result, 0, result.Length); + return result; + } + } +} diff --git a/tests/NClient.Packages/NClient.Packages.Tests/NClient.Packages.Tests.csproj b/tests/NClient.Packages/NClient.Packages.Tests/NClient.Packages.Tests.csproj index 22cce9601..ace9b2784 100644 --- a/tests/NClient.Packages/NClient.Packages.Tests/NClient.Packages.Tests.csproj +++ b/tests/NClient.Packages/NClient.Packages.Tests/NClient.Packages.Tests.csproj @@ -28,6 +28,7 @@ + diff --git a/tests/NClient.Packages/NClient.Packages.Tests/VersionTest.cs b/tests/NClient.Packages/NClient.Packages.Tests/VersionTest.cs index 9755d3dde..80692fcf8 100644 --- a/tests/NClient.Packages/NClient.Packages.Tests/VersionTest.cs +++ b/tests/NClient.Packages/NClient.Packages.Tests/VersionTest.cs @@ -56,6 +56,10 @@ public void NClient_Providers_Serialization_Json_System() => public void NClient_Providers_Serialization_Json_Newtonsoft() => PackagesVersionProvider.GetCurrent("NClient.Providers.Serialization.Json.Newtonsoft").Should().Be(PackagesVersionProvider.GetNew()); + [Test] + public void NClient_Providers_Serialization_Protobuf_ProtobufNet() => + PackagesVersionProvider.GetCurrent("NClient.Providers.Serialization.Protobuf.ProtobufNet").Should().Be(PackagesVersionProvider.GetNew()); + [Test] public void NClient_Providers_Serialization_Xml_System() => PackagesVersionProvider.GetCurrent("NClient.Providers.Serialization.Xml.System").Should().Be(PackagesVersionProvider.GetNew()); diff --git a/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/Models/NotProto.cs b/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/Models/NotProto.cs new file mode 100644 index 000000000..7fe21ef6a --- /dev/null +++ b/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/Models/NotProto.cs @@ -0,0 +1,14 @@ +namespace NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests.Models +{ + public readonly struct NotProto + { + public readonly int X; + public readonly int Y; + + public NotProto(int x, int y) + { + X = x; + Y = y; + } + } +} diff --git a/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/Models/Point.cs b/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/Models/Point.cs new file mode 100644 index 000000000..c0b350bd2 --- /dev/null +++ b/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/Models/Point.cs @@ -0,0 +1,13 @@ +using ProtoBuf; + +namespace NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests.Models +{ + [ProtoContract] + public struct Point + { + [ProtoMember(1)] + public int X { get; set; } + [ProtoMember(2)] + public int Y { get; set; } + } +} diff --git a/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests.csproj b/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests.csproj new file mode 100644 index 000000000..8a5e36059 --- /dev/null +++ b/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests.csproj @@ -0,0 +1,33 @@ + + + + net462;net472;net48;net5.0;net6.0;netcoreapp3.1 + 9.0 + enable + true + true + false + Debug;Release + AnyCPU + $(SolutionDir)/bin/test/$(Configuration)/$(AssemblyName) + + + + + + + + + + + + + + + + + Common\Converters.cs + + + + diff --git a/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/ProtobufSerializerTest.cs b/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/ProtobufSerializerTest.cs new file mode 100644 index 000000000..91a318eb4 --- /dev/null +++ b/tests/NClient.Providers/NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests/ProtobufSerializerTest.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections; +using System.IO; +using FluentAssertions; +using NClient.Common.Helpers; +using NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests.Models; +using NUnit.Framework; +using ProtoBuf; + +namespace NClient.Providers.Serialization.Protobuf.ProtobufNet.Tests +{ + [Parallelizable] + public class ProtobufSerializerTest + { + public static readonly IEnumerable SerializationValidTestCases = new[] + { + new TestCaseData(new Point{ X = 1, Y = 2 }, new Point{ X = 1, Y = 2 }, typeof(Point)), + new TestCaseData(new Point{ X = 2, Y = 2 }, new Point{ X = 2, Y = 2 }, typeof(Point)) + }; + + public static readonly IEnumerable DeserializationValidTestCases = new[] + { + new TestCaseData(new Point{ X = 1, Y = 2 }, new Point{ X = 1, Y = 2 }, typeof(Point)) + }; + + public static readonly IEnumerable NotValidTestCases = new[] + { + new TestCaseData(new NotProto(1, 2), new NotProto(1, 2), typeof(NotProto)) + }; + + [TestCaseSource(nameof(SerializationValidTestCases))] + public void Serialize_ValidValues_NotThrow(object obj, object expectedResult, Type type) + { + var serializer = new ProtobufSerializerProvider().Create(logger: null); + + var serializerString = serializer.Serialize(obj); + byte[] bytes = Converters.GetBytes(serializerString); + + var actualResult = Serializer.Deserialize(type, new MemoryStream(bytes)); + + actualResult.Should().BeEquivalentTo(expectedResult); + } + + [Test] + public void Serialize_Null_ThrowArgumentNullException() + { + var serializer = new ProtobufSerializerProvider().Create(logger: null); + + serializer + .Invoking(x => x.Serialize((int?) null)) + .Should() + .ThrowExactly(); + } + + [TestCaseSource(nameof(DeserializationValidTestCases))] + public void Deserialize_ValidValues_NotThrow(object sourceObject, object expectedResult, Type type) + { + var serializer = new ProtobufSerializerProvider().Create(logger: null); + + using var memoryStream = new MemoryStream(); + Serializer.Serialize(memoryStream, sourceObject); + + var serializedBytes = memoryStream.ToArray(); + + var serializedString = Converters.GetString(serializedBytes); + + var actualResult = serializer.Deserialize(serializedString, type); + + actualResult.Should().BeEquivalentTo(expectedResult); + } + + [Test] + public void Deserialize_Null_ThrowArgumentNullException() + { + var serializer = new ProtobufSerializerProvider().Create(logger: null); + + serializer + .Invoking(x => x.Deserialize(null!, typeof(string))) + .Should() + .ThrowExactly(); + } + } +}