From 2bd22cac3fa8175d83a4feaa75bc2e935cb33c40 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Wed, 17 Jun 2026 13:46:58 +0100 Subject: [PATCH] abstractions: move socket connection interfaces from Core Move INatsSocketConnection and INatsTlsUpgradeableSocketConnection to NATS.Client.Abstractions so custom transport implementers can depend on the contract without referencing Core, same rationale as the serialization interfaces already there. Both types are BCL-only. Keep the NATS.Client.Core namespace on the moved types and add TypeForwardedTo in Core to preserve source and binary compatibility. The obsolete ISocketConnection and INatsSocketConnectionFactory stay in Core; the factory takes NatsOpts so it cannot move without dragging Core options along. Add Microsoft.Bcl.AsyncInterfaces to the Abstractions netstandard2.0 group for IAsyncDisposable and ValueTask, which are not in that target's BCL. --- .../INatsSocketConnection.cs | 43 +++++++++++++++++++ .../NATS.Client.Abstractions.csproj | 1 + src/NATS.Client.Core/INatsSocketConnection.cs | 42 ------------------ src/NATS.Client.Core/TypeForwarders.cs | 2 + tests/NATS.Client.CheckAbi/Program.cs | 14 ++++++ .../MySocketConnection.cs | 30 +++++++++++++ 6 files changed, 90 insertions(+), 42 deletions(-) create mode 100644 src/NATS.Client.Abstractions/INatsSocketConnection.cs create mode 100644 tests/NATS.Client.CheckAbiTransientLib/MySocketConnection.cs diff --git a/src/NATS.Client.Abstractions/INatsSocketConnection.cs b/src/NATS.Client.Abstractions/INatsSocketConnection.cs new file mode 100644 index 000000000..c71570000 --- /dev/null +++ b/src/NATS.Client.Abstractions/INatsSocketConnection.cs @@ -0,0 +1,43 @@ +using System.Net.Sockets; + +namespace NATS.Client.Core; + +/// +/// Represents a socket connection to a NATS server. +/// +/// +/// This interface defines the contract for low-level socket connections to NATS servers, +/// providing methods for sending and receiving data and disposing the socket. +/// Disposing should attempt to perform graceful shutdown of the socket. +/// +public interface INatsSocketConnection : IAsyncDisposable +{ + /// + /// Sends data asynchronously over the connection. + /// + /// The buffer containing the data to send. + /// A task representing the asynchronous send operation with the number of bytes sent. + ValueTask SendAsync(ReadOnlyMemory buffer); + + /// + /// Receives data asynchronously from the connection. + /// + /// The buffer to store the received data. + /// A task representing the asynchronous receive operation with the number of bytes received. + ValueTask ReceiveAsync(Memory buffer); +} + +/// +/// Represents a NATS socket connection that can be upgraded to use TLS. +/// +/// +/// This interface extends to provide access to the underlying +/// socket that is used to create a SslStream. +/// +public interface INatsTlsUpgradeableSocketConnection : INatsSocketConnection +{ + /// + /// Gets the underlying Socket instance for the connection. + /// + Socket Socket { get; } +} diff --git a/src/NATS.Client.Abstractions/NATS.Client.Abstractions.csproj b/src/NATS.Client.Abstractions/NATS.Client.Abstractions.csproj index e4e38515b..2fb09c6ff 100644 --- a/src/NATS.Client.Abstractions/NATS.Client.Abstractions.csproj +++ b/src/NATS.Client.Abstractions/NATS.Client.Abstractions.csproj @@ -17,6 +17,7 @@ + diff --git a/src/NATS.Client.Core/INatsSocketConnection.cs b/src/NATS.Client.Core/INatsSocketConnection.cs index 87386dc76..95f0861e9 100644 --- a/src/NATS.Client.Core/INatsSocketConnection.cs +++ b/src/NATS.Client.Core/INatsSocketConnection.cs @@ -1,32 +1,5 @@ -using System.Net.Sockets; - namespace NATS.Client.Core; -/// -/// Represents a socket connection to a NATS server. -/// -/// -/// This interface defines the contract for low-level socket connections to NATS servers, -/// providing methods for sending and receiving data and disposing the socket. -/// Disposing should attempt to perform graceful shutdown of the socket. -/// -public interface INatsSocketConnection : IAsyncDisposable -{ - /// - /// Sends data asynchronously over the connection. - /// - /// The buffer containing the data to send. - /// A task representing the asynchronous send operation with the number of bytes sent. - ValueTask SendAsync(ReadOnlyMemory buffer); - - /// - /// Receives data asynchronously from the connection. - /// - /// The buffer to store the received data. - /// A task representing the asynchronous receive operation with the number of bytes received. - ValueTask ReceiveAsync(Memory buffer); -} - /// /// Obsolete, use instead /// @@ -39,18 +12,3 @@ public interface ISocketConnection : INatsSocketConnection ValueTask AbortConnectionAsync(CancellationToken cancellationToken); } - -/// -/// Represents a NATS socket connection that can be upgraded to use TLS. -/// -/// -/// This interface extends to provide access to the underlying -/// socket that is used to create a SslStream. -/// -public interface INatsTlsUpgradeableSocketConnection : INatsSocketConnection -{ - /// - /// Gets the underlying Socket instance for the connection. - /// - Socket Socket { get; } -} diff --git a/src/NATS.Client.Core/TypeForwarders.cs b/src/NATS.Client.Core/TypeForwarders.cs index a565921ee..a9ff5cb35 100644 --- a/src/NATS.Client.Core/TypeForwarders.cs +++ b/src/NATS.Client.Core/TypeForwarders.cs @@ -5,3 +5,5 @@ [assembly: TypeForwardedTo(typeof(INatsSerialize<>))] [assembly: TypeForwardedTo(typeof(INatsDeserialize<>))] [assembly: TypeForwardedTo(typeof(INatsSerializerRegistry))] +[assembly: TypeForwardedTo(typeof(INatsSocketConnection))] +[assembly: TypeForwardedTo(typeof(INatsTlsUpgradeableSocketConnection))] diff --git a/tests/NATS.Client.CheckAbi/Program.cs b/tests/NATS.Client.CheckAbi/Program.cs index 433b42ed0..ed3b4ece6 100644 --- a/tests/NATS.Client.CheckAbi/Program.cs +++ b/tests/NATS.Client.CheckAbi/Program.cs @@ -23,10 +23,14 @@ void Check(string name, string expected, string actual) Check("INatsSerializeWithContext<>", "NATS.Client.Abstractions", typeof(INatsSerializeWithContext<>).Assembly.GetName().Name!); Check("INatsDeserializeWithContext<>", "NATS.Client.Abstractions", typeof(INatsDeserializeWithContext<>).Assembly.GetName().Name!); Check("NatsMsgContext", "NATS.Client.Abstractions", typeof(NatsMsgContext).Assembly.GetName().Name!); +Check("INatsSocketConnection", "NATS.Client.Abstractions", typeof(INatsSocketConnection).Assembly.GetName().Name!); +Check("INatsTlsUpgradeableSocketConnection", "NATS.Client.Abstractions", typeof(INatsTlsUpgradeableSocketConnection).Assembly.GetName().Name!); Console.WriteLine(); Console.WriteLine("Types as seen by TransientLib (compiled against 2.6.0):"); Check("INatsSerialize<> (transient)", "NATS.Client.Abstractions", MySerializer.GetSerializerInterfaceAssembly()); +Check("INatsSocketConnection (transient)", "NATS.Client.Abstractions", MySocketConnection.GetSocketConnectionInterfaceAssembly()); +Check("INatsTlsUpgradeableSocketConnection (transient)", "NATS.Client.Abstractions", MySocketConnection.GetTlsUpgradeableInterfaceAssembly()); Console.WriteLine(); Console.WriteLine("Assembly versions at runtime:"); @@ -54,6 +58,16 @@ void Check(string name, string expected, string actual) Console.WriteLine(); +Console.WriteLine("Testing TransientLib.MySocketConnection (compiled against 2.6.0):"); +INatsSocketConnection connection = new MySocketConnection(); +var sent = await connection.SendAsync(new ReadOnlyMemory(new byte[7])); +Console.WriteLine($" SendAsync reported {sent} bytes"); +if (sent != 7) + errors.Add($"SendAsync failed: got {sent}"); +await connection.DisposeAsync(); + +Console.WriteLine(); + if (errors.Count > 0) { Console.Error.WriteLine($"FAILED: {errors.Count} error(s):"); diff --git a/tests/NATS.Client.CheckAbiTransientLib/MySocketConnection.cs b/tests/NATS.Client.CheckAbiTransientLib/MySocketConnection.cs new file mode 100644 index 000000000..43bab5812 --- /dev/null +++ b/tests/NATS.Client.CheckAbiTransientLib/MySocketConnection.cs @@ -0,0 +1,30 @@ +using System.Net.Sockets; +using NATS.Client.Core; + +namespace NATS.Client.CheckAbiTransientLib; + +/// +/// Compiled against NATS.Net 2.6.0 where INatsSocketConnection and +/// INatsTlsUpgradeableSocketConnection live in NATS.Client.Core. When used with a newer +/// build, type forwarding should redirect both to NATS.Client.Abstractions. +/// +public class MySocketConnection : INatsTlsUpgradeableSocketConnection +{ + public Socket Socket => throw new NotSupportedException(); + + public static string GetSocketConnectionInterfaceAssembly() + { + return typeof(INatsSocketConnection).Assembly.GetName().Name!; + } + + public static string GetTlsUpgradeableInterfaceAssembly() + { + return typeof(INatsTlsUpgradeableSocketConnection).Assembly.GetName().Name!; + } + + public ValueTask SendAsync(ReadOnlyMemory buffer) => new(buffer.Length); + + public ValueTask ReceiveAsync(Memory buffer) => new(0); + + public ValueTask DisposeAsync() => default; +}