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

Information
Class: AsyncResponse.Transports.PostgreSQL.PostgreSqlTransportOptionsValidator
Assembly: AsyncResponse.Transports.PostgreSQL
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/Transports/AsyncResponse.Transports.PostgreSQL/PostgreSqlTransportOptionsValidator.cs
Line coverage
100%
Covered lines: 72
Uncovered lines: 0
Coverable lines: 72
Total lines: 123
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.PostgreSQL/PostgreSqlTransportOptionsValidator.cs

#LineLine coverage
 1namespace AsyncResponse.Transports.PostgreSQL;
 2
 3internal static class PostgreSqlTransportOptionsValidator
 4{
 5    public static void ValidateCommon(PostgreSqlAsyncResponseTransportOptions options)
 6    {
 37        ValidateIdentifier(options.SchemaName, nameof(options.SchemaName));
 38        ValidateIdentifier(options.MessageTable, nameof(options.MessageTable));
 39        ValidateIdentifier(options.NotificationChannel, nameof(options.NotificationChannel));
 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(PostgreSqlAsyncResponseTransportOptions)}.{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(PostgreSqlAsyncResponseTransportOptions)}.{nameof(options.Dead
 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));
 335        Positive(options.ShutdownTimeout, nameof(options.ShutdownTimeout));
 36
 337        if (options.PublishMaxAttempts <= 0)
 338            throw new InvalidOperationException($"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{nameof(options.Publ
 339        if (options.PublishRetryBaseDelay > options.PublishRetryMaxDelay)
 340            throw new InvalidOperationException($"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{nameof(options.Publ
 341        if (options.SubscriberRetryBaseDelay > options.SubscriberRetryMaxDelay)
 342            throw new InvalidOperationException($"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{nameof(options.Subs
 343    }
 44
 45    /// <summary>Validates the supplied subscriber options together with the transport-wide shutdown budget.</summary>
 46    public static void ValidateSubscriber(
 47        PostgreSqlAsyncResponseTransportOptions transportOptions,
 48        PostgreSqlSubscriberOptions subscriber,
 49        string role)
 50    {
 351        ValidateSubscriber(subscriber, role);
 52
 353        if (subscriber.AckMode is not PostgreSqlAckMode.AckAfterEnqueue)
 354            return;
 55
 56        // PostgreSQL spends the background drain plus the LISTEN-task join (ShutdownTimeout)
 57        // at shutdown; both must fit inside the host budget or ACKed work is truncated.
 358        ShutdownBudgetValidator.Validate(
 359            "PostgreSQL",
 360            $"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{nameof(transportOptions.HostShutdownTimeout)}",
 361            transportOptions.HostShutdownTimeout,
 362            ($"{nameof(PostgreSqlSubscriberOptions)}.{nameof(subscriber.BackgroundDrainTimeout)} ({role})", subscriber.B
 363            ($"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{nameof(transportOptions.ShutdownTimeout)}", transportO
 364    }
 65
 66    public static void ValidateSubscriber(PostgreSqlSubscriberOptions subscriber, string role)
 67    {
 368        if (subscriber.BatchSize <= 0)
 369            throw new InvalidOperationException($"{nameof(PostgreSqlSubscriberOptions)}.{nameof(subscriber.BatchSize)} (
 370        if (subscriber.MaxDeliveryAttempts < 0)
 371            throw new InvalidOperationException($"{nameof(PostgreSqlSubscriberOptions)}.{nameof(subscriber.MaxDeliveryAt
 72
 373        Positive(subscriber.RedeliveryDelay, $"{nameof(subscriber.RedeliveryDelay)} ({role})");
 374        Positive(subscriber.EmptyPollDelay, $"{nameof(subscriber.EmptyPollDelay)} ({role})");
 75
 376        switch (subscriber.AckMode)
 77        {
 78            case PostgreSqlAckMode.AckAfterHandlerCompletes:
 379                return;
 80            case PostgreSqlAckMode.AckAfterEnqueue:
 381                if (subscriber.BackgroundWorkerCount <= 0)
 382                    throw new InvalidOperationException($"{nameof(PostgreSqlSubscriberOptions)}.{nameof(subscriber.Backg
 383                if (subscriber.BackgroundQueueCapacity <= 0)
 384                    throw new InvalidOperationException($"{nameof(PostgreSqlSubscriberOptions)}.{nameof(subscriber.Backg
 385                Positive(subscriber.BackgroundDrainTimeout, $"{nameof(subscriber.BackgroundDrainTimeout)} ({role})");
 386                return;
 87            default:
 388                throw new InvalidOperationException($"{nameof(PostgreSqlSubscriberOptions)}.{nameof(subscriber.AckMode)}
 89        }
 90    }
 91
 92    public static string Required(string? value, string name)
 393        => !string.IsNullOrWhiteSpace(value)
 394            ? value
 395            : throw new InvalidOperationException($"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{name} must be con
 96
 97    public static void ValidateIdentifier(string? value, string name)
 98    {
 399        if (string.IsNullOrWhiteSpace(value))
 3100            throw new InvalidOperationException($"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{name} must be confi
 3101        if (!IsIdentifier(value))
 3102            throw new InvalidOperationException(
 3103                $"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{name} '{value}' must be a simple PostgreSQL identif
 3104    }
 105
 106    private static void Positive(TimeSpan value, string name)
 107    {
 3108        if (value <= TimeSpan.Zero)
 3109            throw new InvalidOperationException($"{nameof(PostgreSqlAsyncResponseTransportOptions)}.{name} must be posit
 3110    }
 111
 112    private static bool IsIdentifier(string value)
 113    {
 3114        if (value.Length == 0 || !(char.IsAsciiLetter(value[0]) || value[0] == '_'))
 3115            return false;
 3116        foreach (var c in value)
 117        {
 3118            if (!(char.IsAsciiLetterOrDigit(c) || c == '_'))
 3119                return false;
 120        }
 3121        return true;
 122    }
 123}