Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
syntax = "proto3";

package Wolverine.Protobuf.Tests;

option csharp_namespace = "Wolverine.Protobuf.Tests.Messages";

message TestProtobufMessage {
string Name = 1;
}
10 changes: 10 additions & 0 deletions src/Extensions/Wolverine.Protobuf.Tests/ProtobufMessageHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Wolverine.Protobuf.Tests.Messages;

namespace Wolverine.Protobuf.Tests;

public class ProtobufMessageHandler
{
public void Handle(TestProtobufMessage message)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="GitHubActionsTestLogger" Version="2.4.1" PrivateAssets="All" />
<PackageReference Include="Google.Protobuf" Version="3.31.1" />
<PackageReference Include="Grpc.Core" Version="2.46.6" />
<PackageReference Include="Grpc.Tools" Version="2.72.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="protobuf-net.BuildTools" Version="3.2.52">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Testing\Wolverine.ComplianceTests\Wolverine.ComplianceTests.csproj" />
<ProjectReference Include="..\Wolverine.Protobuf\Wolverine.Protobuf.csproj" />
</ItemGroup>

<ItemGroup>
<Protobuf Include="Messages\**\*.proto" GrpcServices="Server" ProtoRoot="Messages" />
</ItemGroup>

<ItemGroup>
<Content Include="$(SolutionDir)xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

</Project>
65 changes: 65 additions & 0 deletions src/Extensions/Wolverine.Protobuf.Tests/end_to_end.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma warning disable IDE0058
using Microsoft.Extensions.Hosting;
using Shouldly;
using Wolverine.Protobuf.Tests.Messages;
using Wolverine.Tracking;
using Wolverine.Transports.Tcp;
using Wolverine.Util;
using Xunit;

namespace Wolverine.Protobuf.Tests;

public class end_to_end : IAsyncLifetime
{
private IHost _publishingHost;
private IHost _receivingHost;

[Fact]
public async Task end_to_end_message_send_using_protobuf()
{
// Arrange
var messageNameContent = "Message test";

// Act
var actual = await _publishingHost
.TrackActivity()
.AlsoTrack(_receivingHost)
.SendMessageAndWaitAsync(new TestProtobufMessage { Name = messageNameContent });

// Assert
actual.Received.SingleMessage<TestProtobufMessage>()
.ShouldNotBeNull()
.ShouldBeOfType<TestProtobufMessage>()
.Name.ShouldBe(messageNameContent);
}

#region Test setup

public async Task InitializeAsync()
{
var receivingTcpPort = PortFinder.GetAvailablePort();

_publishingHost = await Host.CreateDefaultBuilder().UseWolverine(opts =>
{
opts.UseProtobufSerialization();
opts.PublishMessage<TestProtobufMessage>().ToPort(receivingTcpPort);
opts.ApplicationAssembly = typeof(TestProtobufMessage).Assembly;

}).StartAsync();

_receivingHost = await Host.CreateDefaultBuilder().UseWolverine(opts =>
{
opts.UseProtobufSerialization();
opts.ListenAtPort(receivingTcpPort);
opts.ApplicationAssembly = typeof(TestProtobufMessage).Assembly;
}).StartAsync();
}

public async Task DisposeAsync()
{
await _receivingHost.StopAsync();
await _publishingHost.StopAsync();
}

#endregion
}
3 changes: 3 additions & 0 deletions src/Extensions/Wolverine.Protobuf/AssemblyAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Wolverine.Protobuf.Tests")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Google.Protobuf;

using Wolverine.Runtime.Serialization;

namespace Wolverine.Protobuf.Internal;

internal class ProtobufMessageSerializer(ProtobufSerializerOptions options) : IMessageSerializer
{
public string ContentType => "binary/protobuf";

public object ReadFromData(Type messageType, Envelope envelope)
{
if (envelope?.Data == null || envelope.Data.Length == 0)
{
throw new ArgumentNullException(nameof(envelope), "Envelope data cannot be null or empty");
}

if (!typeof(Google.Protobuf.IMessage).IsAssignableFrom(messageType))
{
throw new ArgumentException($"Type {messageType.FullName} must implement {nameof(Google.Protobuf.IMessage)}", nameof(messageType));
}

var instance = Activator.CreateInstance(messageType);

if (instance is not Google.Protobuf.IMessage protobufMessage)
{
throw new ArgumentException($"Type {messageType.FullName} must implement {nameof(Google.Protobuf.IMessage)}", nameof(messageType));
}

return protobufMessage.Descriptor.Parser.ParseFrom(envelope.Data);
}

public object ReadFromData(byte[] data)
{
throw new NotSupportedException("Protobuf deserialization requires message type information. Use the ReadFromData(Type, Envelope) overload instead.");
}

public byte[] Write(Envelope envelope)
{
if (envelope.Message is null)
{
throw new NullReferenceException("Envelope message is empty");
}

return WriteMessage(envelope.Message);
}

public byte[] WriteMessage(object message)
{
if (message is not Google.Protobuf.IMessage protobufMessage)
{
throw new ArgumentException($"Message must implement {nameof(Google.Protobuf.IMessage)}", nameof(message));
}

using (var stream = new MemoryStream())
{
protobufMessage.WriteTo(stream);
return stream.ToArray();
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Wolverine.Protobuf;

public class ProtobufSerializerOptions
{
public static readonly ProtobufSerializerOptions Standard = new ProtobufSerializerOptions();
}
16 changes: 16 additions & 0 deletions src/Extensions/Wolverine.Protobuf/Wolverine.Protobuf.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Protobuf serialization for Wolverine Applications</Description>
<PackageId>WolverineFx.Protobuf</PackageId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.31.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Wolverine\Wolverine.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Wolverine.Configuration;
using Wolverine.Protobuf.Internal;

namespace Wolverine.Protobuf;

public static class WolverineProtobufSerializationExtensions
{
/// <summary>
/// Make Protobuf the default serializer for this application
/// </summary>
/// <param name="options"></param>
/// <param name="configuration"></param>
public static void UseProtobufSerialization(this WolverineOptions options,
Action<ProtobufSerializerOptions>? configuration = null)
{
var serializerOptions = ProtobufSerializerOptions.Standard;

configuration?.Invoke(serializerOptions);

var serializer = new ProtobufMessageSerializer(serializerOptions);

options.DefaultSerializer = serializer;
}

/// <summary>
/// Apply Protobuf serialization for just this endpoint
/// </summary>
/// <param name="listener"></param>
/// b
/// <param name="configuration"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T UseProtobufSerialization<T>(this T endpoint,
Action<ProtobufSerializerOptions>? configuration = null) where T : IEndpointConfiguration<T>
{
var serializerOptions = ProtobufSerializerOptions.Standard;

configuration?.Invoke(serializerOptions);

var serializer = new ProtobufMessageSerializer(serializerOptions);
endpoint.DefaultSerializer(serializer);
return endpoint;
}
}
Loading
Loading