feat: add IValidateOptions and ValidateOnStart for existing options classes - #676
Merged
samtrion merged 5 commits intoAug 3, 2026
Merged
Conversation
…lasses Add IValidateOptions<T> validators for TimeoutRequestInterceptorOptions, QueryCachingOptions, OutboxOptions, OutboxProcessorOptions, AzureServiceBusTransportOptions, RabbitMqTransportOptions, and DaprMessageTransportOptions, each registered with AddOptions<T>().ValidateOnStart() so misconfiguration is caught at startup instead of at first use. The AzureServiceBus transport's imperative ValidateOptions() check is replaced by AzureServiceBusTransportOptionsValidator with equivalent behavior, now surfaced as an OptionsValidationException instead of an InvalidOperationException. LoggingInterceptorOptions is intentionally left untouched: it already has a validator but is out of scope for this change. SQLiteOutboxOptions does not exist as a distinct type; the SQLite provider reuses the shared OutboxOptions, which is already covered by OutboxOptionsValidator (TableName not empty). ConnectionString remains unvalidated there since it is legitimately null for EF Core-based outbox usage.
Contributor
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
SonarAnalyzer flagged the explanatory comment as commented-out code because it contained a code-like fragment; reworded in prose only.
4 tasks
…ons classes (#677) * feat: bind options from configuration via IConfigureOptions Add IConfigureOptions<TOptions> implementations that bind LoggingInterceptorOptions, TimeoutRequestInterceptorOptions, QueryCachingOptions, OutboxOptions, OutboxProcessorOptions, AzureServiceBusTransportOptions, RabbitMqTransportOptions, and DaprMessageTransportOptions from documented Pulse:* configuration sections, registered inside the respective existing Add*/Use* extension methods so IConfiguration-backed values are validated at startup by the #238 validators. AddRequestTimeout only applies its explicit globalTimeout parameter when a value is provided, so a configuration-bound GlobalTimeout is no longer unconditionally overwritten by the method's default null argument. * fix(rabbitmq): register IConfiguration in channel pool resolution test Resolving IRabbitMqChannelPool now requires IConfiguration to be resolvable, since RabbitMqTransportOptionsConfiguration (added by the IConfigureOptions binding work) depends on it.
samtrion
added a commit
that referenced
this pull request
Aug 3, 2026
) * feat(rabbitmq): pool RabbitMQ channels in RabbitMqMessageTransport Replace the single lazily-created, publish-serialized channel in RabbitMqMessageTransport with a pooled IRabbitMqChannelPool / RabbitMqChannelPool backed by a ConcurrentQueue of idle channels and a SemaphoreSlim capped at the new RabbitMqTransportOptions.MaxChannelPoolSize (default 10). SendAsync rents a channel per call; SendBatchAsync rents a single channel for the whole batch and publishes sequentially on it, since only one thread ever touches that channel. Both always return the channel in a finally block. IsHealthyAsync now delegates to the pool. The channel pool is registered as a singleton via TryAddSingleton in UseRabbitMqTransport so repeated calls do not duplicate it. * fix(rabbitmq): suppress false-positive S5034 on per-iteration ValueTask conversion RentAsync_ConcurrentCalls_AreCappedAtMaxChannelPoolSize converts a fresh ValueTask returned by RentAsync to a Task exactly once per loop iteration, but SonarAnalyzer's cross-iteration analysis cannot tell the instances apart and flags a false double-consumption. * test(rabbitmq): cover channel-creation failure and pool resolution paths Adds coverage for RentAsync releasing its rental slot when channel creation fails, and for resolving IRabbitMqChannelPool from a built service provider, raising patch coverage to the required threshold. * feat: add IValidateOptions and ValidateOnStart for existing options classes (#676) * feat: add IValidateOptions and ValidateOnStart for existing options classes Add IValidateOptions<T> validators for TimeoutRequestInterceptorOptions, QueryCachingOptions, OutboxOptions, OutboxProcessorOptions, AzureServiceBusTransportOptions, RabbitMqTransportOptions, and DaprMessageTransportOptions, each registered with AddOptions<T>().ValidateOnStart() so misconfiguration is caught at startup instead of at first use. The AzureServiceBus transport's imperative ValidateOptions() check is replaced by AzureServiceBusTransportOptionsValidator with equivalent behavior, now surfaced as an OptionsValidationException instead of an InvalidOperationException. LoggingInterceptorOptions is intentionally left untouched: it already has a validator but is out of scope for this change. SQLiteOutboxOptions does not exist as a distinct type; the SQLite provider reuses the shared OutboxOptions, which is already covered by OutboxOptionsValidator (TableName not empty). ConnectionString remains unvalidated there since it is legitimately null for EF Core-based outbox usage. * fix(outbox): reword test comment to avoid false-positive S125 match SonarAnalyzer flagged the explanatory comment as commented-out code because it contained a code-like fragment; reworded in prose only. * feat: IConfigureOptions with IConfiguration binding for existing options classes (#677) * feat: bind options from configuration via IConfigureOptions Add IConfigureOptions<TOptions> implementations that bind LoggingInterceptorOptions, TimeoutRequestInterceptorOptions, QueryCachingOptions, OutboxOptions, OutboxProcessorOptions, AzureServiceBusTransportOptions, RabbitMqTransportOptions, and DaprMessageTransportOptions from documented Pulse:* configuration sections, registered inside the respective existing Add*/Use* extension methods so IConfiguration-backed values are validated at startup by the #238 validators. AddRequestTimeout only applies its explicit globalTimeout parameter when a value is provided, so a configuration-bound GlobalTimeout is no longer unconditionally overwritten by the method's default null argument. * fix(rabbitmq): register IConfiguration in channel pool resolution test Resolving IRabbitMqChannelPool now requires IConfiguration to be resolvable, since RabbitMqTransportOptionsConfiguration (added by the IConfigureOptions binding work) depends on it. * fix(rabbitmq): register IConfiguration in channel pool resolution test Resolving IOptions<RabbitMqTransportOptions> now requires IConfiguration to be resolvable, since RabbitMqTransportOptionsConfiguration depends on it; the raw ServiceCollection built by this integration test did not register one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
IValidateOptions<T>validators for the following options classes, each registered with the fail-fast pattern (AddOptions<T>().ValidateOnStart()+TryAddEnumerable(... IValidateOptions<T> ...)) in their respective existingAdd*/Use*extension method:TimeoutRequestInterceptorOptions(AddRequestTimeout) —GlobalTimeoutmust be null or >TimeSpan.Zero.QueryCachingOptions(AddQueryCaching) —DefaultExpirymust be null or >TimeSpan.Zero.OutboxOptions(AddOutbox) —TableNamemust not be null/empty/whitespace.OutboxProcessorOptions(AddOutbox) —BatchSize > 0,PollingInterval > 0,MaxRetryCount >= 0,ProcessingTimeout > 0; whenEnableExponentialBackoffis true, alsoBackoffMultiplier > 1.0,BaseRetryDelay > 0,MaxRetryDelay >= BaseRetryDelay.AzureServiceBusTransportOptions(UseAzureServiceBusTransport) — eitherConnectionStringorFullyQualifiedNamespacemust be set.RabbitMqTransportOptions(UseRabbitMqTransport) —ExchangeNamenot empty;MaxChannelPoolSize >= 1(the latter property was added by feat: RabbitMQ channel pooling in RabbitMqMessageTransport #241).DaprMessageTransportOptions(UseDaprTransport) —PubSubNamenot empty.Each validator lives next to its options class, following the existing
{OptionsClassName}Validatornaming convention used byLoggingInterceptorOptionsValidator.Deviations from the issue text
LoggingInterceptorOptionsis intentionally left untouched. It already has a validator (LoggingInterceptorOptionsValidator) but registration doesn't call.ValidateOnStart(). This class was explicitly called out as out of scope for this change.AzureServiceBusTransportOptions: the transport previously validated its options imperatively atServiceBusClient-creation time via a private staticValidateOptionsmethod, throwingInvalidOperationException. This is replaced byAzureServiceBusTransportOptionsValidatorregistered via the options pipeline. Behavior is equivalent (same required-field check, still throws at first options resolution / at host startup withValidateOnStart), but the thrown exception type changes toMicrosoft.Extensions.Options.OptionsValidationException. Existing tests were updated to expect the new exception type.SQLiteOutboxOptionsdoes not exist as a distinct type in the current codebase — the SQLite provider (src/NetEvolve.Pulse.SQLite/Outbox/OutboxOptionsExtensions.cs) reuses the sharedOutboxOptions. This row from the issue is covered by theOutboxOptionsValidatoradded forOutboxOptions(TableNamenot empty).OutboxOptions.ConnectionStringis intentionally not validated as required, since it is legitimatelynullfor EF Core-based outbox usage (only ADO.NET-based providers need it) — making it mandatory would break existing valid configurations.Dependency
This branch is based on
feature/241-rabbitmq-channel-pool(not yet merged to main), sinceRabbitMqTransportOptions.MaxChannelPoolSize— validated here — was introduced there. The diff shown in this PR is scoped to the #238-specific changes on top of that branch.Notes
Because
IValidateOptions<T>runs validation whenever the options are resolved (viaIOptions<T>/IOptionsMonitor<T>), not only through theValidateOnStart()eager startup check, a couple of pre-existing tests that resolved default (and now invalid) option values needed small updates to assert the resultingOptionsValidationExceptioninstead of the previous default values / exception types.Closes #238
Test plan
dotnet build Pulse.slnxsucceeds with no errors.csharpier format .run before commit.OutboxExtensionsTests,RabbitMqExtensionsTests,AzureServiceBusExtensionsTests, etc.) — 557 tests, all passing.ValidateOnStart's effect end-to-end: resolvingIOptions<OutboxOptions>(and similarly for RabbitMQ/AzureServiceBus) with an invalid configuration throwsOptionsValidationExceptionwhen the service provider builds the options.