Skip to content

Commit

Permalink
ServiceProvider callback for NATS DI configuration; (#619)
Browse files Browse the repository at this point in the history
* ServiceProvider callback for NATS DI configuration;

* Recursion fix;

* Build fixes

* Build fixes

* Build fixes

* dotnet format

---------

Co-authored-by: Ziya Suzen <[email protected]>
  • Loading branch information
william-liebert and mtmk authored Sep 10, 2024
1 parent 71dc676 commit dc5fa62
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 21 deletions.
59 changes: 38 additions & 21 deletions src/NATS.Extensions.Microsoft.DependencyInjection/NatsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ namespace NATS.Extensions.Microsoft.DependencyInjection;
public class NatsBuilder
{
private readonly IServiceCollection _services;

private int _poolSize = 1;
private Func<NatsOpts, NatsOpts>? _configureOpts;
private Action<NatsConnection>? _configureConnection;
private Func<IServiceProvider, NatsOpts, NatsOpts>? _configureOpts;
private Action<IServiceProvider, NatsConnection>? _configureConnection;
private object? _diKey = null;

public NatsBuilder(IServiceCollection services)
Expand All @@ -20,43 +21,59 @@ public NatsBuilder(IServiceCollection services)
public NatsBuilder WithPoolSize(int size)
{
_poolSize = Math.Max(size, 1);

return this;
}

public NatsBuilder ConfigureOptions(Func<NatsOpts, NatsOpts> optsFactory)
public NatsBuilder ConfigureOptions(Func<NatsOpts, NatsOpts> optsFactory) =>
ConfigureOptions((_, opts) => optsFactory(opts));

public NatsBuilder ConfigureOptions(Func<IServiceProvider, NatsOpts, NatsOpts> optsFactory)
{
var previousFactory = _configureOpts;
_configureOpts = opts =>
var configure = _configureOpts;
_configureOpts = (serviceProvider, opts) =>
{
// Apply the previous configurator if it exists.
if (previousFactory != null)
{
opts = previousFactory(opts);
}
opts = configure?.Invoke(serviceProvider, opts) ?? opts;

// Then apply the new configurator.
return optsFactory(opts);
return optsFactory(serviceProvider, opts);
};

return this;
}

public NatsBuilder ConfigureConnection(Action<NatsConnection> connectionOpts)
public NatsBuilder ConfigureConnection(Action<NatsConnection> configureConnection) =>
ConfigureConnection((_, con) => configureConnection(con));

public NatsBuilder ConfigureConnection(Action<IServiceProvider, NatsConnection> configureConnection)
{
_configureConnection = connectionOpts;
var configure = _configureConnection;
_configureConnection = (serviceProvider, connection) =>
{
configure?.Invoke(serviceProvider, connection);

configureConnection(serviceProvider, connection);
};

return this;
}

public NatsBuilder AddJsonSerialization(JsonSerializerContext context)
=> ConfigureOptions(opts =>
public NatsBuilder AddJsonSerialization(JsonSerializerContext context) =>
AddJsonSerialization(_ => context);

public NatsBuilder AddJsonSerialization(Func<IServiceProvider, JsonSerializerContext> contextFactory)
=> ConfigureOptions((serviceProvider, opts) =>
{
var jsonRegistry = new NatsJsonContextSerializerRegistry(context);
var context = contextFactory(serviceProvider);
NatsJsonContextSerializerRegistry jsonRegistry = new(context);

return opts with { SerializerRegistry = jsonRegistry };
});

#if NET8_0_OR_GREATER
public NatsBuilder WithKey(object key)
{
_diKey = key;

return this;
}
#endif
Expand Down Expand Up @@ -117,18 +134,18 @@ private static NatsConnection PooledConnectionFactory(IServiceProvider provider,
private NatsConnectionPool PoolFactory(IServiceProvider provider, object? diKey = null)
{
var options = NatsOpts.Default with { LoggerFactory = provider.GetRequiredService<ILoggerFactory>() };
options = _configureOpts?.Invoke(options) ?? options;
options = _configureOpts?.Invoke(provider, options) ?? options;

return new NatsConnectionPool(_poolSize, options, _configureConnection ?? (_ => { }));
return new NatsConnectionPool(_poolSize, options, con => _configureConnection?.Invoke(provider, con));
}

private NatsConnection SingleConnectionFactory(IServiceProvider provider, object? diKey = null)
{
var options = NatsOpts.Default with { LoggerFactory = provider.GetRequiredService<ILoggerFactory>() };
options = _configureOpts?.Invoke(options) ?? options;
options = _configureOpts?.Invoke(provider, options) ?? options;

var conn = new NatsConnection(options);
_configureConnection?.Invoke(conn);
_configureConnection?.Invoke(provider, conn);

return conn;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace NATS.Extensions.Microsoft.DependencyInjection.Tests;

internal interface IMyResolvedService
{
string GetValue();
}

internal class MyResolvedService(string value) : IMyResolvedService
{
public string GetValue() => value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,57 @@ public Task AddNatsClient_ConfigureOptionsSetsUrl()
return Task.CompletedTask;
}

[Fact]
public Task AddNatsClient_ConfigureOptionsSetsUrlResolvesServices()
{
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
services.AddSingleton<IMyResolvedService>(new MyResolvedService("url-set"));
services.AddNatsClient(nats => nats
.ConfigureOptions((_, opts) => opts) // Add multiple to test chaining
.ConfigureOptions((serviceProvider, opts) =>
{
opts = opts with
{
Url = serviceProvider.GetRequiredService<IMyResolvedService>().GetValue(),
};

return opts;
}));

var provider = services.BuildServiceProvider();
var nats = provider.GetRequiredService<INatsConnection>();

Assert.Equal("url-set", nats.Opts.Url);

return Task.CompletedTask;
}

[Fact]
public async Task AddNatsClient_ConfigureConnectionResolvesServices()
{
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
services.AddSingleton<IMyResolvedService>(new MyResolvedService("url-set"));
services.AddNatsClient(nats => nats
.ConfigureConnection((_, _) => { }) // Add multiple to test chaining
.ConfigureConnection((serviceProvider, conn) =>
{
conn.OnConnectingAsync = async instance =>

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (v2.9)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (v2.9)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (v2.9)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (v2.9)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (v2.9)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (v2.9)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (v2.9)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (v2.9)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (latest)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (latest)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (latest)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (latest)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (latest)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (latest)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (latest)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (latest)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (main)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (main)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (main)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / dotnet (main)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (main)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (main)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (main)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 119 in tests/NATS.Extensions.Microsoft.DependencyInjection.Tests/NatsHostingExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / memory test (main)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
var resolved = serviceProvider.GetRequiredService<IMyResolvedService>().GetValue();

return (resolved, instance.Port);
};
}));

var provider = services.BuildServiceProvider();
var nats = provider.GetRequiredService<NatsConnection>();

(var host, var _) = await nats.OnConnectingAsync!((Host: "host", Port: 123));
Assert.Equal("url-set", host);
}

#if NET8_0_OR_GREATER
[Fact]
public void AddNats_RegistersKeyedNatsConnection_WhenKeyIsProvided()
Expand Down

0 comments on commit dc5fa62

Please sign in to comment.