Skip to content

Commit

Permalink
Use STJ src-gen for ServerInfo and ClientOptions (#106)
Browse files Browse the repository at this point in the history
* Use STJ src-gen for ClientOptions and ServerInfo

* Fix deserialization of ServerInfo

STJ src-gen does not support internal setters. Introduce a public
interface instead to expose a read-only view of ServerInfo and
make ServerInfo internal.

* Remove defaults from src-gen options
  • Loading branch information
jasper-d authored Aug 9, 2023
1 parent 68ea8d9 commit 90ee113
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 93 deletions.
5 changes: 1 addition & 4 deletions src/NATS.Client.Core/Commands/ProtocolWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ public void WriteConnect(ClientOptions options)
WriteConstant(CommandConstants.ConnectWithPadding);

var jsonWriter = new Utf8JsonWriter(_writer);
JsonSerializer.Serialize(jsonWriter, options, new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
});
JsonSerializer.Serialize(jsonWriter, options, JsonContext.Default.ClientOptions);

WriteConstant(CommandConstants.NewLine);
}
Expand Down
50 changes: 50 additions & 0 deletions src/NATS.Client.Core/IServerInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace NATS.Client.Core;

public interface IServerInfo
{
string Id { get; }

string Name { get; }

string Version { get; }

long ProtocolVersion { get; }

string GitCommit { get; }

string GoVersion { get; }

string Host { get; }

int Port { get; }

bool HeadersSupported { get; }

bool AuthRequired { get; }

bool TlsRequired { get; }

bool TlsVerify { get; }

bool TlsAvailable { get; }

int MaxPayload { get; }

bool JetStreamAvailable { get; }

ulong ClientId { get; }

string ClientIp { get; }

string? Nonce { get; }

string? Cluster { get; }

bool ClusterDynamic { get; }

string[]? ClientConnectUrls { get; }

string[]? WebSocketConnectUrls { get; }

bool LameDuckMode { get; }
}
13 changes: 12 additions & 1 deletion src/NATS.Client.Core/Internal/ClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

namespace NATS.Client.Core.Internal;

// SYSLIB1037: The type 'ClientOptions' defines init-only properties,
// deserialization of which is currently not supported in source generation mode
#pragma warning disable SYSLIB1037

// These connections options are serialized and sent to the server.
// https://github.com/nats-io/nats-server/blob/a23b1b7/server/client.go#L536
internal sealed class ClientOptions
{
public ClientOptions(NatsOptions options)
private ClientOptions(NatsOptions options)
{
Name = options.Name;
Echo = options.Echo;
Expand Down Expand Up @@ -86,6 +90,11 @@ public ClientOptions(NatsOptions options)
[JsonPropertyName("no_responders")]
public bool NoResponders { get; init; } = false;

public static ClientOptions Create(NatsOptions options)
{
return new ClientOptions(options);
}

private static string GetAssemblyVersion()
{
var asm = typeof(ClientOptions);
Expand All @@ -107,3 +116,5 @@ private static string GetAssemblyVersion()
return version;
}
}

#pragma warning restore SYSLIB1037
10 changes: 10 additions & 0 deletions src/NATS.Client.Core/Internal/JsonContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;

namespace NATS.Client.Core.Internal;

[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(ServerInfo), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(ClientOptions), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal sealed partial class JsonContext : JsonSerializerContext
{
}
7 changes: 4 additions & 3 deletions src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ private static ServerInfo ParseInfo(in ReadOnlySequence<byte> buffer)
// skip `INFO`
var jsonReader = new Utf8JsonReader(buffer.Slice(5));

var serverInfo = JsonSerializer.Deserialize<ServerInfo>(ref jsonReader) ?? throw new NatsException("Can not parse ServerInfo.");
var serverInfo = JsonSerializer.Deserialize(ref jsonReader, JsonContext.Default.ServerInfo)
?? throw new NatsException("Can not parse ServerInfo.");
return serverInfo;
}

Expand Down Expand Up @@ -404,7 +405,7 @@ private async ValueTask<ReadOnlySequence<byte>> DispatchCommandAsync(int code, R
var newPosition = newBuffer.PositionOf((byte)'\n');

var serverInfo = ParseInfo(newBuffer);
_connection.ServerInfo = serverInfo;
_connection.WritableServerInfo = serverInfo;
_logger.LogInformation("Received ServerInfo: {0}", serverInfo);
_waitForInfoSignal.TrySetResult();
await _infoParsed.ConfigureAwait(false);
Expand All @@ -413,7 +414,7 @@ private async ValueTask<ReadOnlySequence<byte>> DispatchCommandAsync(int code, R
else
{
var serverInfo = ParseInfo(buffer);
_connection.ServerInfo = serverInfo;
_connection.WritableServerInfo = serverInfo;
_logger.LogInformation("Received ServerInfo: {0}", serverInfo);
_waitForInfoSignal.TrySetResult();
await _infoParsed.ConfigureAwait(false);
Expand Down
77 changes: 77 additions & 0 deletions src/NATS.Client.Core/Internal/ServerInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Text.Json.Serialization;

namespace NATS.Client.Core.Internal;

// Defined from `type Info struct` in nats-server
// https://github.com/nats-io/nats-server/blob/a23b1b7/server/server.go#L61
internal sealed record ServerInfo : IServerInfo
{
[JsonPropertyName("server_id")]
public string Id { get; set; } = string.Empty;

[JsonPropertyName("server_name")]
public string Name { get; set; } = string.Empty;

[JsonPropertyName("version")]
public string Version { get; set; } = string.Empty;

[JsonPropertyName("proto")]
public long ProtocolVersion { get; set; }

[JsonPropertyName("git_commit")]
public string GitCommit { get; set; } = string.Empty;

[JsonPropertyName("go")]
public string GoVersion { get; set; } = string.Empty;

[JsonPropertyName("host")]
public string Host { get; set; } = string.Empty;

[JsonPropertyName("port")]
public int Port { get; set; }

[JsonPropertyName("headers")]
public bool HeadersSupported { get; set; }

[JsonPropertyName("auth_required")]
public bool AuthRequired { get; set; }

[JsonPropertyName("tls_required")]
public bool TlsRequired { get; set; }

[JsonPropertyName("tls_verify")]
public bool TlsVerify { get; set; }

[JsonPropertyName("tls_available")]
public bool TlsAvailable { get; set; }

[JsonPropertyName("max_payload")]
public int MaxPayload { get; set; }

[JsonPropertyName("jetstream")]
public bool JetStreamAvailable { get; set; }

[JsonPropertyName("client_id")]
public ulong ClientId { get; set; }

[JsonPropertyName("client_ip")]
public string ClientIp { get; set; } = string.Empty;

[JsonPropertyName("nonce")]
public string? Nonce { get; set; }

[JsonPropertyName("cluster")]
public string? Cluster { get; set; }

[JsonPropertyName("cluster_dynamic")]
public bool ClusterDynamic { get; set; }

[JsonPropertyName("connect_urls")]
public string[]? ClientConnectUrls { get; set; }

[JsonPropertyName("ws_connect_urls")]
public string[]? WebSocketConnectUrls { get; set; }

[JsonPropertyName("ldm")]
public bool LameDuckMode { get; set; }
}
17 changes: 9 additions & 8 deletions src/NATS.Client.Core/NatsConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public partial class NatsConnection : IAsyncDisposable, INatsConnection
public Func<(string Host, int Port), ValueTask<(string Host, int Port)>>? OnConnectingAsync;

internal readonly ConnectionStatsCounter Counter; // allow to call from external sources
internal ServerInfo? WritableServerInfo;
#pragma warning restore SA1401
private readonly object _gate = new object();
private readonly WriterState _writerState;
Expand Down Expand Up @@ -70,7 +71,7 @@ public NatsConnection(NatsOptions options)
InboxPrefix = $"{options.InboxPrefix}.{Guid.NewGuid():n}.";
SubscriptionManager = new SubscriptionManager(this, InboxPrefix);
_logger = options.LoggerFactory.CreateLogger<NatsConnection>();
_clientOptions = new ClientOptions(Options);
_clientOptions = ClientOptions.Create(Options);
HeaderParser = new HeaderParser(options.HeaderEncoding);
}

Expand All @@ -85,7 +86,7 @@ public NatsConnection(NatsOptions options)

public NatsConnectionState ConnectionState { get; private set; }

public ServerInfo? ServerInfo { get; internal set; } // server info is set when received INFO
public IServerInfo? ServerInfo => WritableServerInfo; // server info is set when received INFO

public HeaderParser HeaderParser { get; }

Expand Down Expand Up @@ -328,19 +329,19 @@ private async ValueTask SetupReaderWriterAsync(bool reconnect)
// check to see if we should upgrade to TLS
if (_socket is TcpConnection tcpConnection)
{
if (Options.TlsOptions.Disabled && ServerInfo!.TlsRequired)
if (Options.TlsOptions.Disabled && WritableServerInfo!.TlsRequired)
{
throw new NatsException(
$"Server {_currentConnectUri} requires TLS but TlsOptions.Disabled is set to true");
}

if (Options.TlsOptions.Required && !ServerInfo!.TlsRequired && !ServerInfo.TlsAvailable)
if (Options.TlsOptions.Required && !WritableServerInfo!.TlsRequired && !WritableServerInfo.TlsAvailable)
{
throw new NatsException(
$"Server {_currentConnectUri} does not support TLS but TlsOptions.Disabled is set to true");
}

if (Options.TlsOptions.Required || ServerInfo!.TlsRequired || ServerInfo.TlsAvailable)
if (Options.TlsOptions.Required || WritableServerInfo!.TlsRequired || WritableServerInfo.TlsAvailable)
{
// do TLS upgrade
// if the current URI is not a seed URI and is not a DNS hostname, check the server cert against the
Expand Down Expand Up @@ -376,7 +377,7 @@ private async ValueTask SetupReaderWriterAsync(bool reconnect)
infoParsedSignal.SetResult();

// Authentication
_userCredentials?.Authenticate(_clientOptions, ServerInfo);
_userCredentials?.Authenticate(_clientOptions, WritableServerInfo);

// add CONNECT and PING command to priority lane
_writerState.PriorityCommands.Clear();
Expand Down Expand Up @@ -428,8 +429,8 @@ private async void ReconnectLoop()

var defaultScheme = _currentConnectUri!.Uri.Scheme;
var urls = (Options.NoRandomize
? ServerInfo?.ClientConnectUrls?.Select(x => new NatsUri(x, false, defaultScheme)).Distinct().ToArray()
: ServerInfo?.ClientConnectUrls?.Select(x => new NatsUri(x, false, defaultScheme)).OrderBy(_ => Guid.NewGuid()).Distinct().ToArray())
? WritableServerInfo?.ClientConnectUrls?.Select(x => new NatsUri(x, false, defaultScheme)).Distinct().ToArray()
: WritableServerInfo?.ClientConnectUrls?.Select(x => new NatsUri(x, false, defaultScheme)).OrderBy(_ => Guid.NewGuid()).Distinct().ToArray())
?? Array.Empty<NatsUri>();
if (urls.Length == 0)
urls = Options.GetSeedUris();
Expand Down
77 changes: 0 additions & 77 deletions src/NATS.Client.Core/ServerInfo.cs

This file was deleted.

0 comments on commit 90ee113

Please sign in to comment.