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

Information
Class: AsyncResponse.Transports.Kafka.KafkaTransportOptionsValidator
Assembly: AsyncResponse.Transports.Kafka
File(s): /_/src/Transports/AsyncResponse.Transports.Kafka/KafkaTransportOptionsValidator.cs
Line coverage
100%
Covered lines: 64
Uncovered lines: 0
Coverable lines: 64
Total lines: 126
Line coverage: 100%
Branch coverage
100%
Covered branches: 44
Total branches: 44
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Required(...)100%22100%
.cctor()100%11100%
EnsureMaxPollInterval(...)100%44100%
EnsureIntMilliseconds(...)100%44100%
EnsureDeadLetterNotLive(...)100%44100%
ValidateCommon(...)100%3030100%

File(s)

/_/src/Transports/AsyncResponse.Transports.Kafka/KafkaTransportOptionsValidator.cs

#LineLine coverage
 1namespace AsyncResponse.Transports.Kafka;
 2
 3internal static class KafkaTransportOptionsValidator
 4{
 5    /// <summary>Validates the supplied options.</summary>
 6    public static string Required(string? value, string name)
 133607        => !string.IsNullOrWhiteSpace(value)
 133608            ? value
 133609            : throw new InvalidOperationException($"{nameof(KafkaAsyncResponseTransportOptions)}.{name} must be configur
 10
 11    /// <summary>
 12    /// librdkafka's allowed range for its interval settings (<c>auto.commit.interval.ms</c>,
 13    /// <c>max.poll.interval.ms</c>) tops out at 86,400,000 ms; a larger value fails consumer
 14    /// CONSTRUCTION inside the subscriber loop, not validation.
 15    /// </summary>
 616    private static readonly TimeSpan MaxLibrdkafkaInterval = TimeSpan.FromDays(1);
 17
 18    /// <summary>Validates a subscriber's <c>max.poll.interval.ms</c> against the librdkafka range.</summary>
 19    internal static void EnsureMaxPollInterval(TimeSpan value, string optionsName, string name)
 20    {
 98221        if (value <= TimeSpan.Zero || value > MaxLibrdkafkaInterval)
 622            throw new InvalidOperationException(
 623                $"{optionsName}.{name} must be positive and at most 1 day " +
 624                "(the librdkafka max.poll.interval.ms range; larger values fail consumer construction).");
 97625    }
 26
 27    /// <summary>
 28    /// The Kafka client passes timeouts to librdkafka as 32-bit millisecond values
 29    /// (<c>Flush(TimeSpan)</c>, <c>Consume(TimeSpan)</c>, admin request timeouts); anything above
 30    /// int.MaxValue ms (~24.8 days) overflows there mid-operation.
 31    /// </summary>
 32    internal static void EnsureIntMilliseconds(TimeSpan value, string optionsName, string name)
 33    {
 367634        if (value <= TimeSpan.Zero || value.TotalMilliseconds > int.MaxValue)
 1035            throw new InvalidOperationException(
 1036                $"{optionsName}.{name} must be positive and at most {TimeSpan.FromMilliseconds(int.MaxValue).TotalDays:0
 1037                "(the Kafka client passes it to librdkafka as a 32-bit millisecond value).");
 366638    }
 39
 40    private static void EnsureDeadLetterNotLive(string deadLetterTopic, KafkaTransportTopicSchema topics)
 41    {
 337242        if (StringComparer.Ordinal.Equals(deadLetterTopic, topics.WorkerTopic) ||
 337243            StringComparer.Ordinal.Equals(deadLetterTopic, topics.ResponseTopic))
 44        {
 445            throw new InvalidOperationException(
 446                $"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(KafkaAsyncResponseTransportOptions.DeadLetterTopi
 447                $"'{deadLetterTopic}', which is a live transport topic; dead-lettered messages republished into a consum
 448                "are re-consumed instead of parked.");
 49        }
 336850    }
 51
 52    /// <summary>Validates the supplied options.</summary>
 53    public static void ValidateCommon(KafkaAsyncResponseTransportOptions options)
 54    {
 171455        _ = Required(options.BootstrapServers, nameof(options.BootstrapServers));
 170856        _ = Required(options.TopicPrefix, nameof(options.TopicPrefix));
 170657        _ = Required(options.WorkerConsumerGroup, nameof(options.WorkerConsumerGroup));
 170458        _ = Required(options.ResponseConsumerGroup, nameof(options.ResponseConsumerGroup));
 170259        _ = Required(options.CorrelationIdHeader, nameof(options.CorrelationIdHeader));
 170060        _ = Required(options.DefaultReplyTargetName, nameof(options.DefaultReplyTargetName));
 61
 169862        if (options.DeadLetterEnabled && string.IsNullOrWhiteSpace(options.DeadLetterTopic))
 168863            _ = Required(options.DeadLetterTopicSuffix, nameof(options.DeadLetterTopicSuffix));
 64
 65        // Worker and response subscribers run distinct consumer groups, so a shared topic feeds
 66        // every message to BOTH: job envelopes complete real waiters through the response ingress
 67        // (the correlation-id header is stamped and trusted) while responses churn through the
 68        // worker dispatcher into the dead-letter topic. Compare the RESOLVED names, as the
 69        // queue-based siblings do, so an explicit value colliding with the other role's derived
 70        // default is caught too.
 169671        var topics = new KafkaTransportTopicSchema(options);
 169672        if (StringComparer.Ordinal.Equals(topics.WorkerTopic, topics.ResponseTopic))
 73        {
 474            throw new InvalidOperationException(
 475                $"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.WorkerTopic)} and " +
 476                $"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.ResponseTopic)} must resolve to distinct 
 477                $"(both resolve to '{topics.WorkerTopic}') so worker and response subscribers do not consume each other'
 78        }
 79
 169280        if (options.DeadLetterEnabled)
 81        {
 82            // A dead-letter topic aimed at a LIVE transport topic re-feeds poison to a subscriber:
 83            // its own source topic loops the message forever, the other role's topic cross-routes
 84            // it. Derived names ({source}{suffix}) cannot collide with their source, but an
 85            // explicit DeadLetterTopic can.
 168886            EnsureDeadLetterNotLive(topics.DeadLetterTopicFor(topics.WorkerTopic), topics);
 168487            EnsureDeadLetterNotLive(topics.DeadLetterTopicFor(topics.ResponseTopic), topics);
 88        }
 89
 168890        if (options.OffsetCommitInterval <= TimeSpan.Zero || options.OffsetCommitInterval > MaxLibrdkafkaInterval)
 491            throw new InvalidOperationException(
 492                $"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.OffsetCommitInterval)} must be positive a
 493                "(the librdkafka auto.commit.interval.ms range; larger values fail consumer construction).");
 168494        EnsureIntMilliseconds(options.OperationTimeout, nameof(KafkaAsyncResponseTransportOptions), nameof(options.Opera
 168095        AsyncResponseChannelOptions.EnsureTimerBacked(options.PublishRetryBaseDelay, nameof(KafkaAsyncResponseTransportO
 167896        AsyncResponseChannelOptions.EnsureTimerBacked(options.PublishRetryMaxDelay, nameof(KafkaAsyncResponseTransportOp
 167897        AsyncResponseChannelOptions.EnsureTimerBacked(options.SubscriberRetryBaseDelay, nameof(KafkaAsyncResponseTranspo
 167898        AsyncResponseChannelOptions.EnsureTimerBacked(options.SubscriberRetryMaxDelay, nameof(KafkaAsyncResponseTranspor
 99
 1678100        if (options.PublishMaxAttempts <= 0)
 2101            throw new InvalidOperationException($"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.PublishMa
 102
 1676103        if (options.PublishRetryBaseDelay > options.PublishRetryMaxDelay)
 104        {
 2105            throw new InvalidOperationException(
 2106                $"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.PublishRetryBaseDelay)} cannot exceed " +
 2107                $"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.PublishRetryMaxDelay)}.");
 108        }
 109
 1674110        if (options.SubscriberRetryBaseDelay > options.SubscriberRetryMaxDelay)
 111        {
 2112            throw new InvalidOperationException(
 2113                $"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.SubscriberRetryBaseDelay)} cannot exceed 
 2114                $"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.SubscriberRetryMaxDelay)}.");
 115        }
 116
 1672117        if (options.TopicNumPartitions is not -1 and <= 0)
 4118            throw new InvalidOperationException($"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.TopicNumP
 119
 1668120        if (options.TopicReplicationFactor is not (-1) and <= 0)
 2121            throw new InvalidOperationException($"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.TopicRepl
 122
 1666123        if (options.HostShutdownTimeout is { } hostShutdownTimeout && hostShutdownTimeout <= TimeSpan.Zero)
 2124            throw new InvalidOperationException($"{nameof(KafkaAsyncResponseTransportOptions)}.{nameof(options.HostShutd
 1664125    }
 126}