< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.Transports.SqlServer.SqlServerTransportOptionsValidator
Assembly: AsyncResponse.Transports.SqlServer
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/Transports/AsyncResponse.Transports.SqlServer/SqlServerTransportOptionsValidator.cs
Line coverage
100%
Covered lines: 70
Uncovered lines: 0
Coverable lines: 70
Total lines: 121
Line coverage: 100%
Branch coverage
100%
Covered branches: 50
Total branches: 50
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ValidateCommon(...)100%1616100%
ValidateSubscriber(...)100%22100%
ValidateSubscriber(...)100%1212100%
Required(...)100%22100%
ValidateIdentifier(...)100%44100%
Positive(...)100%22100%
IsIdentifier(...)100%1212100%

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/src/Transports/AsyncResponse.Transports.SqlServer/SqlServerTransportOptionsValidator.cs

#LineLine coverage
 1namespace AsyncResponse.Transports.SqlServer;
 2
 3internal static class SqlServerTransportOptionsValidator
 4{
 5    public static void ValidateCommon(SqlServerAsyncResponseTransportOptions options)
 6    {
 37        Required(options.ConnectionString, nameof(options.ConnectionString));
 38        ValidateIdentifier(options.SchemaName, nameof(options.SchemaName));
 39        ValidateIdentifier(options.MessageTable, nameof(options.MessageTable));
 310        Required(options.WorkerQueue, nameof(options.WorkerQueue));
 311        Required(options.ResponseQueue, nameof(options.ResponseQueue));
 312        Required(options.DeadLetterQueue, nameof(options.DeadLetterQueue));
 313        Required(options.CorrelationIdHeader, nameof(options.CorrelationIdHeader));
 314        Required(options.DefaultReplyTargetName, nameof(options.DefaultReplyTargetName));
 15
 16        // All three logical queues share one table, distinguished only by the queue column. Equal
 17        // names would make subscribers consume each other's rows (or re-consume dead letters).
 318        if (StringComparer.Ordinal.Equals(options.WorkerQueue, options.ResponseQueue)
 319            || StringComparer.Ordinal.Equals(options.WorkerQueue, options.DeadLetterQueue)
 320            || StringComparer.Ordinal.Equals(options.ResponseQueue, options.DeadLetterQueue))
 21        {
 322            throw new InvalidOperationException(
 323                $"{nameof(SqlServerAsyncResponseTransportOptions)}.{nameof(options.WorkerQueue)}, " +
 324                $"{nameof(options.ResponseQueue)}, and {nameof(options.DeadLetterQueue)} must be distinct; they share on
 25        }
 26
 327        if (options.DeadLetterRetention is { } deadLetterRetention && deadLetterRetention <= TimeSpan.Zero)
 328            throw new InvalidOperationException($"{nameof(SqlServerAsyncResponseTransportOptions)}.{nameof(options.DeadL
 29
 330        Positive(options.LockTimeout, nameof(options.LockTimeout));
 331        Positive(options.PublishRetryBaseDelay, nameof(options.PublishRetryBaseDelay));
 332        Positive(options.PublishRetryMaxDelay, nameof(options.PublishRetryMaxDelay));
 333        Positive(options.SubscriberRetryBaseDelay, nameof(options.SubscriberRetryBaseDelay));
 334        Positive(options.SubscriberRetryMaxDelay, nameof(options.SubscriberRetryMaxDelay));
 35
 336        if (options.PublishMaxAttempts <= 0)
 337            throw new InvalidOperationException($"{nameof(SqlServerAsyncResponseTransportOptions)}.{nameof(options.Publi
 338        if (options.PublishRetryBaseDelay > options.PublishRetryMaxDelay)
 339            throw new InvalidOperationException($"{nameof(SqlServerAsyncResponseTransportOptions)}.{nameof(options.Publi
 340        if (options.SubscriberRetryBaseDelay > options.SubscriberRetryMaxDelay)
 341            throw new InvalidOperationException($"{nameof(SqlServerAsyncResponseTransportOptions)}.{nameof(options.Subsc
 342    }
 43
 44    /// <summary>Validates the supplied subscriber options together with the transport-wide shutdown budget.</summary>
 45    public static void ValidateSubscriber(
 46        SqlServerAsyncResponseTransportOptions transportOptions,
 47        SqlServerSubscriberOptions subscriber,
 48        string role)
 49    {
 350        ValidateSubscriber(subscriber, role);
 51
 352        if (subscriber.AckMode is not SqlServerAckMode.AckAfterEnqueue)
 353            return;
 54
 55        // SQL Server subscribers spend only the background drain at shutdown; the polling loop
 56        // stops with the host token and holds no connection that needs a bounded close.
 357        ShutdownBudgetValidator.Validate(
 358            "SQL Server",
 359            $"{nameof(SqlServerAsyncResponseTransportOptions)}.{nameof(transportOptions.HostShutdownTimeout)}",
 360            transportOptions.HostShutdownTimeout,
 361            ($"{nameof(SqlServerSubscriberOptions)}.{nameof(subscriber.BackgroundDrainTimeout)} ({role})", subscriber.Ba
 362    }
 63
 64    public static void ValidateSubscriber(SqlServerSubscriberOptions subscriber, string role)
 65    {
 366        if (subscriber.BatchSize <= 0)
 367            throw new InvalidOperationException($"{nameof(SqlServerSubscriberOptions)}.{nameof(subscriber.BatchSize)} ({
 368        if (subscriber.MaxDeliveryAttempts < 0)
 369            throw new InvalidOperationException($"{nameof(SqlServerSubscriberOptions)}.{nameof(subscriber.MaxDeliveryAtt
 70
 371        Positive(subscriber.RedeliveryDelay, $"{nameof(subscriber.RedeliveryDelay)} ({role})");
 372        Positive(subscriber.EmptyPollDelay, $"{nameof(subscriber.EmptyPollDelay)} ({role})");
 73
 374        switch (subscriber.AckMode)
 75        {
 76            case SqlServerAckMode.AckAfterHandlerCompletes:
 377                return;
 78            case SqlServerAckMode.AckAfterEnqueue:
 379                if (subscriber.BackgroundWorkerCount <= 0)
 380                    throw new InvalidOperationException($"{nameof(SqlServerSubscriberOptions)}.{nameof(subscriber.Backgr
 381                if (subscriber.BackgroundQueueCapacity <= 0)
 382                    throw new InvalidOperationException($"{nameof(SqlServerSubscriberOptions)}.{nameof(subscriber.Backgr
 383                Positive(subscriber.BackgroundDrainTimeout, $"{nameof(subscriber.BackgroundDrainTimeout)} ({role})");
 384                return;
 85            default:
 386                throw new InvalidOperationException($"{nameof(SqlServerSubscriberOptions)}.{nameof(subscriber.AckMode)} 
 87        }
 88    }
 89
 90    public static string Required(string? value, string name)
 391        => !string.IsNullOrWhiteSpace(value)
 392            ? value
 393            : throw new InvalidOperationException($"{nameof(SqlServerAsyncResponseTransportOptions)}.{name} must be conf
 94
 95    public static void ValidateIdentifier(string? value, string name)
 96    {
 397        if (string.IsNullOrWhiteSpace(value))
 398            throw new InvalidOperationException($"{nameof(SqlServerAsyncResponseTransportOptions)}.{name} must be config
 399        if (!IsIdentifier(value))
 3100            throw new InvalidOperationException(
 3101                $"{nameof(SqlServerAsyncResponseTransportOptions)}.{name} '{value}' must be a simple SQL Server identifi
 3102    }
 103
 104    private static void Positive(TimeSpan value, string name)
 105    {
 3106        if (value <= TimeSpan.Zero)
 3107            throw new InvalidOperationException($"{nameof(SqlServerAsyncResponseTransportOptions)}.{name} must be positi
 3108    }
 109
 110    private static bool IsIdentifier(string value)
 111    {
 3112        if (value.Length == 0 || !(char.IsAsciiLetter(value[0]) || value[0] == '_'))
 3113            return false;
 3114        foreach (var c in value)
 115        {
 3116            if (!(char.IsAsciiLetterOrDigit(c) || c == '_'))
 3117                return false;
 118        }
 3119        return true;
 120    }
 121}