diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpConnectionScope.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpConnectionScope.cs index 088df469e5e5e..77152894ff93a 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpConnectionScope.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpConnectionScope.cs @@ -174,8 +174,8 @@ public AmqpConnectionScope(Uri serviceEndpoint, private async Task CreateControllerAsync(TimeSpan timeout) { - var timeoutHelper = new TimeoutHelper(timeout, true); - AmqpConnection connection = await ActiveConnection.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); + var stopWatch = ValueStopwatch.StartNew(); + AmqpConnection connection = await ActiveConnection.GetOrCreateAsync(timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); var sessionSettings = new AmqpSessionSettings { Properties = new Fields() }; AmqpSession amqpSession = null; @@ -184,10 +184,10 @@ private async Task CreateControllerAsync(TimeSpan timeout) try { amqpSession = connection.CreateSession(sessionSettings); - await amqpSession.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); + await amqpSession.OpenAsync(timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); - controller = new Controller(amqpSession, timeoutHelper.RemainingTime()); - await controller.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); + controller = new Controller(amqpSession, timeout.CalculateRemaining(stopWatch.GetElapsedTime())); + await controller.OpenAsync(timeout.CalculateRemaining(stopWatch.GetElapsedTime())).ConfigureAwait(false); } catch (Exception exception) { @@ -459,7 +459,7 @@ protected virtual async Task CreateManagementLinkAsync( // Create and open the link. var linkSettings = new AmqpLinkSettings(); - linkSettings.AddProperty(AmqpProperty.Timeout, (uint)timeout.CalculateRemaining(stopWatch.GetElapsedTime()).TotalMilliseconds); + linkSettings.AddProperty(AmqpClientConstants.TimeoutName, (uint)timeout.CalculateRemaining(stopWatch.GetElapsedTime()).TotalMilliseconds); linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, AmqpClientConstants.EntityTypeManagement); entityPath += '/' + AmqpClientConstants.ManagementAddress; @@ -725,7 +725,7 @@ protected virtual async Task CreateSendingLinkAsync( linkSettings.AddProperty(AmqpClientConstants.TransferDestinationAddress, entityPath); } - linkSettings.AddProperty(AmqpProperty.Timeout, (uint)timeout.CalculateRemaining(stopWatch.GetElapsedTime()).TotalMilliseconds); + linkSettings.AddProperty(AmqpClientConstants.TimeoutName, (uint)timeout.CalculateRemaining(stopWatch.GetElapsedTime()).TotalMilliseconds); var link = new SendingAmqpLink(linkSettings); linkSettings.LinkName = $"{ Id };{ connection.Identifier }:{ session.Identifier }:{ link.Identifier }"; @@ -964,9 +964,12 @@ protected virtual async Task RequestAuthorizationUsingCbsAsync( DateTime cbsTokenExpiresAtUtc = DateTime.MaxValue; foreach (string resource in audience) { - cbsTokenExpiresAtUtc = TimeoutHelper.Min( - cbsTokenExpiresAtUtc, - await authLink.SendTokenAsync(TokenProvider, endpoint, resource, resource, requiredClaims, timeout).ConfigureAwait(false)); + DateTime expiresAt = + await authLink.SendTokenAsync(TokenProvider, endpoint, resource, resource, requiredClaims, timeout).ConfigureAwait(false); + if (expiresAt < cbsTokenExpiresAtUtc) + { + cbsTokenExpiresAtUtc = expiresAt; + } } return cbsTokenExpiresAtUtc; } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpMessageConverter.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpMessageConverter.cs index fa889c87f9b57..aa1dcccddd8ac 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpMessageConverter.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpMessageConverter.cs @@ -221,10 +221,7 @@ public static AmqpMessage SBMessageToAmqpMessage(SBMessage sbMessage) public static ServiceBusReceivedMessage AmqpMessageToSBMessage(AmqpMessage amqpMessage, bool isPeeked = false) { - if (amqpMessage == null) - { - throw Fx.Exception.ArgumentNull(nameof(amqpMessage)); - } + Argument.AssertNotNull(amqpMessage, nameof(amqpMessage)); ServiceBusReceivedMessage sbMessage; @@ -567,7 +564,7 @@ internal static bool TryGetAmqpObjectFromNetObject(object netObject, MappingType } else if (mappingType == MappingType.ApplicationProperty) { - throw Fx.Exception.AsError(new SerializationException(Resources.FailedToSerializeUnsupportedType.FormatForUser(netObject.GetType().FullName))); + throw new SerializationException(Resources.FailedToSerializeUnsupportedType.FormatForUser(netObject.GetType().FullName)); } else if (netObject is byte[] netObjectAsByteArray) { @@ -656,7 +653,7 @@ private static bool TryGetNetObjectFromAmqpObject(object amqpObject, MappingType } else if (mappingType == MappingType.ApplicationProperty) { - throw Fx.Exception.AsError(new SerializationException(Resources.FailedToSerializeUnsupportedType.FormatForUser(amqpObject.GetType().FullName))); + throw new SerializationException(Resources.FailedToSerializeUnsupportedType.FormatForUser(amqpObject.GetType().FullName)); } else if (amqpObject is AmqpMap map) { diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpProperty.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpProperty.cs deleted file mode 100644 index 77019059adc5b..0000000000000 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpProperty.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Azure.Amqp; -using Microsoft.Azure.Amqp.Encoding; - -namespace Azure.Messaging.ServiceBus -{ - /// - /// The set of well-known properties associated with an AMQP messages and - /// entities. - /// - /// - internal static class AmqpProperty - { - /// - /// The owner level (a.k.a. epoch) to associate with a link. - /// - /// - public static AmqpSymbol OwnerLevel { get; } = AmqpConstants.Vendor + ":epoch"; - - /// - /// The type of Service Bus entity to associate with a link. - /// - /// - public static AmqpSymbol EntityType { get; } = AmqpConstants.Vendor + ":entity-type"; - - /// - /// The capability for tracking the last event enqueued in a partition, to associate with a link. - /// - /// - public static AmqpSymbol TrackLastEnqueuedEventProperties { get; } = AmqpConstants.Vendor + ":enable-receiver-runtime-metric"; - - /// - /// The timeout to associate with a link. - /// - /// - public static AmqpSymbol Timeout { get; } = AmqpConstants.Vendor + ":timeout"; - - /// - /// The date and time, in UTC, that a message was enqueued. - /// - /// - public static AmqpSymbol EnqueuedTime { get; } = "x-opt-enqueued-time"; - - /// - /// The sequence number assigned to a message. - /// - /// - public static AmqpSymbol SequenceNumber { get; } = "x-opt-sequence-number"; - - /// - /// The offset of a message within a given partition. - /// - /// - public static AmqpSymbol Offset { get; } = "x-opt-offset"; - - /// - /// The partition hashing key used for grouping a batch of events together with the intent of routing to a single partition. - /// - /// - public static AmqpSymbol PartitionKey { get; } = "x-opt-partition-key"; - - /// - /// The message property that identifies the last sequence number enqueued for a partition. - /// - /// - public static AmqpSymbol PartitionLastEnqueuedSequenceNumber { get; } = "last_enqueued_sequence_number"; - - /// - /// The message property that identifies the last offset enqueued for a partition. - /// - /// - public static AmqpSymbol PartitionLastEnqueuedOffset { get; } = "last_enqueued_offset"; - - /// - /// The message property that identifies the last time enqueued for a partition. - /// - /// - public static AmqpSymbol PartitionLastEnqueuedTimeUtc { get; } = "last_enqueued_time_utc"; - - /// - /// The message property that identifies the time that the last enqueued event information was - /// received from the service. - /// - /// - public static AmqpSymbol LastPartitionPropertiesRetrievalTimeUtc { get; } = "runtime_info_retrieval_time_utc"; - - /// - /// The set of descriptors for well-known - /// property types. - /// - /// - public static class Descriptor - { - /// - /// A type annotation for representing a in a message. - /// - /// - public static AmqpSymbol TimeSpan { get; } = AmqpConstants.Vendor + ":timespan"; - - /// - /// A type annotation for representing a in a message. - /// - /// - public static AmqpSymbol Uri { get; } = AmqpConstants.Vendor + ":uri"; - - /// - /// A type annotation for representing a in a message. - /// - /// - public static AmqpSymbol DateTimeOffset { get; } = AmqpConstants.Vendor + ":datetime-offset"; - } - - /// - /// Represents the entity mapping for AMQP properties between the client library and - /// the Service Bus service. - /// - /// - /// - /// WARNING: - /// These values are synchronized between the Service Bus service and the client - /// library. You must consult with the Service Bus service team before making - /// changes, including adding a new member. - /// - /// When adding a new member, remember to always do so before the Unknown - /// member. - /// - /// - public enum Entity - { - Namespace = 4, - EventHub = 7, - ConsumerGroup = 8, - Partition = 9, - Checkpoint = 10, - Unknown = 0x7FFFFFFE - } - - /// - /// Represents the type mapping for AMQP properties between the client library and - /// the Service Bus service. - /// - /// - /// - /// WARNING: - /// These values are synchronized between the Service Bus service and the client - /// library. You must consult with the Service Bus service team before making - /// changes, including adding a new member. - /// - /// When adding a new member, remember to always do so before the Unknown - /// member. - /// - /// - public enum Type - { - Null, - Byte, - SByte, - Char, - Int16, - UInt16, - Int32, - UInt32, - Int64, - UInt64, - Single, - Double, - Decimal, - Boolean, - Guid, - String, - Uri, - DateTime, - DateTimeOffset, - TimeSpan, - Stream, - Unknown - } - } -} diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs index 15160a9c0f4ac..50a8f541e63d2 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs @@ -564,15 +564,8 @@ internal virtual Task DeadLetterInternalAsync( string deadLetterReason, string deadLetterErrorDescription) { - if (deadLetterReason != null && deadLetterReason.Length > Constants.MaxDeadLetterReasonLength) - { - throw new ArgumentOutOfRangeException(nameof(deadLetterReason), string.Format(Resources.MaxPermittedLengthExceeded, Constants.MaxDeadLetterReasonLength)); - } - - if (deadLetterErrorDescription != null && deadLetterErrorDescription.Length > Constants.MaxDeadLetterReasonLength) - { - throw new ArgumentOutOfRangeException(nameof(deadLetterErrorDescription), string.Format(Resources.MaxPermittedLengthExceeded, Constants.MaxDeadLetterReasonLength)); - } + Argument.AssertNotTooLong(deadLetterReason, Constants.MaxDeadLetterReasonLength, nameof(deadLetterReason)); + Argument.AssertNotTooLong(deadLetterErrorDescription, Constants.MaxDeadLetterReasonLength, nameof(deadLetterErrorDescription)); Guid lockTokenGuid = new Guid(lockToken); var lockTokenGuids = new[] { lockTokenGuid }; diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/Argument.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/Argument.cs index 18075184b34fd..7c50faaec7fe7 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/Argument.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/Argument.cs @@ -81,6 +81,24 @@ public static void AssertNotNegative(TimeSpan argumentValue, string argumentName } } + /// + /// Ensures that an argument's value is a positive value, throwing an + /// if that invariant is not met. + /// + /// + /// The value of the argument to verify. + /// The name of the argument being considered. + /// + /// is not positive value. + /// + public static void AssertPositive(TimeSpan argumentValue, string argumentName) + { + if (argumentValue <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(argumentName, $"Argument {argumentName} must be a positive timespan value. The provided value was {argumentValue}."); + } + } + /// /// Ensures that an argument's value is at least as large as a given lower bound, throwing /// if that invariant is not met. diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/OptionsExtensions.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/OptionsExtensions.cs deleted file mode 100644 index ad20667a1e035..0000000000000 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Core/OptionsExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Messaging.ServiceBus.Core -{ - /// - /// The set of extension methods for the - /// class. - /// - /// - internal static class OptionsExtensions - { - /// - /// Creates a new copy of the current , cloning its attributes into a new instance. - /// - /// - /// The instance that this method was invoked on. - /// - /// A new copy of . - /// - public static ServiceBusClientOptions Clone(this ServiceBusClientOptions instance) => - new ServiceBusClientOptions - { - TransportType = instance.TransportType, - Proxy = instance.Proxy - }; - } -} diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/CorrelationFilter.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/CorrelationFilter.cs index 22fa49b015190..f55d808d09e41 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/CorrelationFilter.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/CorrelationFilter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Text; +using Azure.Core; using Azure.Messaging.ServiceBus.Primitives; namespace Azure.Messaging.ServiceBus.Filters @@ -47,11 +48,7 @@ public CorrelationFilter() public CorrelationFilter(string correlationId) : this() { - if (string.IsNullOrWhiteSpace(correlationId)) - { - throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(correlationId)); - } - + Argument.AssertNotNullOrWhiteSpace(correlationId, nameof(correlationId)); CorrelationId = correlationId; } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/RuleDescription.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/RuleDescription.cs index 42a690ca80bef..a3e564fb97cc0 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/RuleDescription.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/RuleDescription.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using Azure.Core; using Azure.Messaging.ServiceBus.Primitives; namespace Azure.Messaging.ServiceBus.Filters @@ -45,7 +46,8 @@ public RuleDescription(string name) /// The filter expression used to match messages. public RuleDescription(string name, Filter filter) { - Filter = filter ?? throw Fx.Exception.ArgumentNull(nameof(filter)); + Argument.AssertNotNull(filter, nameof(filter)); + Filter = filter; Name = name; } @@ -58,7 +60,11 @@ public Filter Filter { get => _filter; - set => _filter = value ?? throw Fx.Exception.ArgumentNull(nameof(Filter)); + set + { + Argument.AssertNotNull(value, nameof(Filter)); + _filter = value; + } } /// diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/RuleDescriptionExtensions.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/RuleDescriptionExtensions.cs index 275ae70a95dc3..2a1a674b02278 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/RuleDescriptionExtensions.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/RuleDescriptionExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; +using Azure.Core; using Azure.Messaging.ServiceBus.Management; using Azure.Messaging.ServiceBus.Primitives; @@ -13,24 +14,14 @@ internal static class RuleDescriptionExtensions { public static void ValidateDescriptionName(this RuleDescription description) { - if (string.IsNullOrWhiteSpace(description.Name)) - { - throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(description.Name)); - } - - if (description.Name.Length > Constants.RuleNameMaximumLength) - { - throw Fx.Exception.ArgumentOutOfRange( - nameof(description.Name), - description.Name, - Resources.EntityNameLengthExceedsLimit.FormatForUser(description.Name, Constants.RuleNameMaximumLength)); - } + Argument.AssertNotNullOrWhiteSpace(description.Name, nameof(description.Name)); + Argument.AssertNotTooLong(description.Name, Constants.RuleNameMaximumLength, nameof(description.Name)); if (description.Name.Contains(Constants.PathDelimiter) || description.Name.Contains(@"\")) { - throw Fx.Exception.Argument( - nameof(description.Name), - Resources.InvalidCharacterInEntityName.FormatForUser(Constants.PathDelimiter, description.Name)); + throw new ArgumentException( + Resources.InvalidCharacterInEntityName.FormatForUser(Constants.PathDelimiter, description.Name), + nameof(description.Name)); } string[] uriSchemeKeys = { "@", "?", "#" }; @@ -38,7 +29,7 @@ public static void ValidateDescriptionName(this RuleDescription description) { if (description.Name.Contains(uriSchemeKey)) { - throw Fx.Exception.Argument( + throw new ArgumentException( nameof(description.Name), Resources.CharacterReservedForUriScheme.FormatForUser(nameof(description.Name), uriSchemeKey)); } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/SqlFilter.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/SqlFilter.cs index 0c5c4f129d1df..4613946b9f5fe 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/SqlFilter.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/SqlFilter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Azure.Core; using Azure.Messaging.ServiceBus.Primitives; namespace Azure.Messaging.ServiceBus.Filters @@ -28,19 +29,8 @@ internal class SqlFilter : Filter /// Max allowed length of sql expression is 1024 chars. public SqlFilter(string sqlExpression) { - if (string.IsNullOrEmpty(sqlExpression)) - { - throw Fx.Exception.ArgumentNull(nameof(sqlExpression)); - } - - if (sqlExpression.Length > Constants.MaximumSqlFilterStatementLength) - { - throw Fx.Exception.Argument( - nameof(sqlExpression), - Resources.SqlFilterStatmentTooLong.FormatForUser( - sqlExpression.Length, - Constants.MaximumSqlFilterStatementLength)); - } + Argument.AssertNotNullOrEmpty(sqlExpression, nameof(sqlExpression)); + Argument.AssertNotTooLong(sqlExpression, Constants.MaximumSqlFilterStatementLength, nameof(sqlExpression)); SqlExpression = sqlExpression; } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/SqlRuleAction.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/SqlRuleAction.cs index dbc49f6bbb6a0..8f26f434b9b70 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/SqlRuleAction.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Filters/SqlRuleAction.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Azure.Core; using Azure.Messaging.ServiceBus.Primitives; namespace Azure.Messaging.ServiceBus.Filters @@ -22,19 +23,8 @@ internal sealed class SqlRuleAction : RuleAction /// Max allowed length of sql expression is 1024 chars. public SqlRuleAction(string sqlExpression) { - if (string.IsNullOrEmpty(sqlExpression)) - { - throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(sqlExpression)); - } - - if (sqlExpression.Length > Constants.MaximumSqlRuleActionStatementLength) - { - throw Fx.Exception.Argument( - nameof(sqlExpression), - Resources.SqlFilterActionStatmentTooLong.FormatForUser( - sqlExpression.Length, - Constants.MaximumSqlRuleActionStatementLength)); - } + Argument.AssertNotNullOrWhiteSpace(sqlExpression, nameof(sqlExpression)); + Argument.AssertNotTooLong(sqlExpression, Constants.MaximumSqlRuleActionStatementLength, nameof(sqlExpression)); SqlExpression = sqlExpression; } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ExceptionUtility.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ExceptionUtility.cs deleted file mode 100644 index 96ab48dddd491..0000000000000 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/ExceptionUtility.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Messaging.ServiceBus.Primitives -{ - using System; - - internal class ExceptionUtility - { - public ArgumentException Argument(string paramName, string message) - { - return new ArgumentException(message, paramName); - } - - public Exception ArgumentNull(string paramName) - { - return new ArgumentNullException(paramName); - } - - public ArgumentException ArgumentNullOrWhiteSpace(string paramName) - { - return this.Argument(paramName, Resources.ArgumentNullOrWhiteSpace.FormatForUser(paramName)); - } - - public ArgumentOutOfRangeException ArgumentOutOfRange(string paramName, object actualValue, string message) - { - return new ArgumentOutOfRangeException(paramName, actualValue, message); - } - - public Exception AsError(Exception exception) - { - return exception; - } - } -} diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/Fx.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/Fx.cs deleted file mode 100644 index 8b6e9e3087965..0000000000000 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/Fx.cs +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Messaging.ServiceBus.Primitives -{ - using System; - using System.Diagnostics; - - internal static class Fx - { - private static ExceptionUtility s_exceptionUtility; - - public static ExceptionUtility Exception - { - get - { - if (s_exceptionUtility == null) - { - s_exceptionUtility = new ExceptionUtility(); - } - - return s_exceptionUtility; - } - } - - public static class Tag - { - public enum CacheAttrition - { - None, - ElementOnTimer, - - // A finalizer/WeakReference based cache, where the elements are held by WeakReferences (or hold an - // inner object by a WeakReference), and the weakly-referenced object has a finalizer which cleans the - // item from the cache. - ElementOnGC, - - // A cache that provides a per-element token, delegate, interface, or other piece of context that can - // be used to remove the element (such as IDisposable). - ElementOnCallback, - - FullPurgeOnTimer, - FullPurgeOnEachAccess, - PartialPurgeOnTimer, - PartialPurgeOnEachAccess, - } - - public enum Location - { - InProcess, - OutOfProcess, - LocalSystem, - LocalOrRemoteSystem, // as in a file that might live on a share - RemoteSystem, - } - - public enum SynchronizationKind - { - LockStatement, - MonitorWait, - MonitorExplicit, - InterlockedNoSpin, - InterlockedWithSpin, - - // Same as LockStatement if the field type is object. - FromFieldType, - } - - [Flags] - public enum BlocksUsing - { - MonitorEnter, - MonitorWait, - ManualResetEvent, - AutoResetEvent, - AsyncResult, - IAsyncResult, - PInvoke, - InputQueue, - ThreadNeutralSemaphore, - PrivatePrimitive, - OtherInternalPrimitive, - OtherFrameworkPrimitive, - OtherInterop, - Other, - - NonBlocking, // For use by non-blocking SynchronizationPrimitives such as IOThreadScheduler - } - - public static class Strings - { - internal const string ExternallyManaged = "externally managed"; - internal const string AppDomain = "AppDomain"; - internal const string DeclaringInstance = "instance of declaring class"; - internal const string Unbounded = "unbounded"; - internal const string Infinite = "infinite"; - } - - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Constructor, - AllowMultiple = true, - Inherited = false)] - [Conditional("CODE_ANALYSIS")] - public sealed class ExternalResourceAttribute : Attribute - { - private readonly Location _location; - private readonly string _description; - - public ExternalResourceAttribute(Location location, string description) - { - this._location = location; - this._description = description; - } - - public Location Location => this._location; - - public string Description => this._description; - } - - [AttributeUsage(AttributeTargets.Field)] - [Conditional("CODE_ANALYSIS")] - public sealed class CacheAttribute : Attribute - { - private readonly Type _elementType; - private readonly CacheAttrition _cacheAttrition; - - public CacheAttribute(Type elementType, CacheAttrition cacheAttrition) - { - this.Scope = Strings.DeclaringInstance; - this.SizeLimit = Strings.Unbounded; - this.Timeout = Strings.Infinite; - - if (elementType == null) - { - throw Fx.Exception.ArgumentNull(nameof(elementType)); - } - - this._elementType = elementType; - this._cacheAttrition = cacheAttrition; - } - - public Type ElementType => this._elementType; - - public CacheAttrition CacheAttrition => this._cacheAttrition; - - public string Scope { get; set; } - - public string SizeLimit { get; set; } - - public string Timeout { get; set; } - } - - [AttributeUsage(AttributeTargets.Field)] - [Conditional("CODE_ANALYSIS")] - public sealed class QueueAttribute : Attribute - { - private readonly Type _elementType; - - public QueueAttribute(Type elementType) - { - this.Scope = Strings.DeclaringInstance; - this.SizeLimit = Strings.Unbounded; - - if (elementType == null) - { - throw Fx.Exception.ArgumentNull(nameof(elementType)); - } - - this._elementType = elementType; - } - - public Type ElementType => this._elementType; - - public string Scope { get; set; } - - public string SizeLimit { get; set; } - - public bool StaleElementsRemovedImmediately { get; set; } - - public bool EnqueueThrowsIfFull { get; set; } - } - - // Set on a class when that class uses lock (this) - acts as though it were on a field - // object this; - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, Inherited = false)] - [Conditional("CODE_ANALYSIS")] - public sealed class SynchronizationObjectAttribute : Attribute - { - public SynchronizationObjectAttribute() - { - this.Blocking = true; - this.Scope = Strings.DeclaringInstance; - this.Kind = SynchronizationKind.FromFieldType; - } - - public bool Blocking { get; set; } - - public string Scope { get; set; } - - public SynchronizationKind Kind { get; set; } - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)] - [Conditional("CODE_ANALYSIS")] - public sealed class SynchronizationPrimitiveAttribute : Attribute - { - private readonly BlocksUsing _blocksUsing; - - public SynchronizationPrimitiveAttribute(BlocksUsing blocksUsing) - { - this._blocksUsing = blocksUsing; - } - - public BlocksUsing BlocksUsing => this._blocksUsing; - - public bool SupportsAsync { get; set; } - - public bool Spins { get; set; } - - public string ReleaseMethod { get; set; } - - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)] - [Conditional("CODE_ANALYSIS")] - public sealed class BlockingAttribute : Attribute - { - public string CancelMethod { get; set; } - - public Type CancelDeclaringType { get; set; } - - public string Conditional { get; set; } - } - - // Sometime a method will call a conditionally-blocking method in such a way that it is guaranteed - // not to block (i.e. the condition can be Asserted false). Such a method can be marked as - // GuaranteeNonBlocking as an assertion that the method doesn't block despite calling a blocking method. - // - // Methods that don't call blocking methods and aren't marked as Blocking are assumed not to block, so - // they do not require this attribute. - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)] - [Conditional("CODE_ANALYSIS")] - public sealed class GuaranteeNonBlockingAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)] - [Conditional("CODE_ANALYSIS")] - public sealed class NonThrowingAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, AllowMultiple = true, Inherited = false)] - [Conditional("CODE_ANALYSIS")] - public class ThrowsAttribute : Attribute - { - private readonly Type _exceptionType; - private readonly string _diagnosis; - - public ThrowsAttribute(Type exceptionType, string diagnosis) - { - if (exceptionType == null) - { - throw Fx.Exception.ArgumentNull(nameof(exceptionType)); - } - if (string.IsNullOrEmpty(diagnosis)) - { - ////throw Fx.Exception.ArgumentNullOrEmpty("diagnosis"); - throw new ArgumentNullException(nameof(diagnosis)); - } - - this._exceptionType = exceptionType; - this._diagnosis = diagnosis; - } - - public Type ExceptionType => this._exceptionType; - - public string Diagnosis => this._diagnosis; - } - - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)] - [Conditional("CODE_ANALYSIS")] - public sealed class InheritThrowsAttribute : Attribute - { - public Type FromDeclaringType { get; set; } - - public string From { get; set; } - } - - [AttributeUsage( - AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | - AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | - AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | - AttributeTargets.Delegate, AllowMultiple = false, - Inherited = false)] - [Conditional("CODE_ANALYSIS")] - public sealed class SecurityNoteAttribute : Attribute - { - public string Critical { get; set; } - - public string Safe { get; set; } - - public string Miscellaneous { get; set; } - } - } - } - } -} diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/TimeoutHelper.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/TimeoutHelper.cs deleted file mode 100644 index c71523a316980..0000000000000 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Primitives/TimeoutHelper.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Messaging.ServiceBus.Primitives -{ - using System; - using System.Diagnostics; - - [DebuggerStepThrough] - internal struct TimeoutHelper - { - private DateTime _deadline; - private bool _deadlineSet; - private TimeSpan _originalTimeout; - - public TimeoutHelper(TimeSpan timeout, bool startTimeout) - { - Debug.Assert(timeout >= TimeSpan.Zero, "timeout must be non-negative"); - - this._originalTimeout = timeout; - this._deadline = DateTime.MaxValue; - this._deadlineSet = (timeout == TimeSpan.MaxValue); - - if (startTimeout && !this._deadlineSet) - { - this.SetDeadline(); - } - } - - public static DateTime Add(DateTime time, TimeSpan timeout) - { - if (timeout >= TimeSpan.Zero && DateTime.MaxValue - time <= timeout) - { - return DateTime.MaxValue; - } - if (timeout <= TimeSpan.Zero && DateTime.MinValue - time >= timeout) - { - return DateTime.MinValue; - } - return time + timeout; - } - - public static DateTime Subtract(DateTime time, TimeSpan timeout) - { - return Add(time, TimeSpan.Zero - timeout); - } - - public static TimeSpan Divide(TimeSpan timeout, int factor) - { - if (timeout == TimeSpan.MaxValue) - { - return TimeSpan.MaxValue; - } - - return Ticks.ToTimeSpan((Ticks.FromTimeSpan(timeout) / factor) + 1); - } - - public static TimeSpan Min(TimeSpan time1, TimeSpan time2) - { - if (time1 <= time2) - { - return time1; - } - - return time2; - } - - public static DateTime Min(DateTime first, DateTime second) - { - if (first <= second) - { - return first; - } - - return second; - } - - public static void ThrowIfNegativeArgument(TimeSpan timeout) - { - ThrowIfNegativeArgument(timeout, nameof(timeout)); - } - - public static void ThrowIfNegativeArgument(TimeSpan timeout, string argumentName) - { - if (timeout < TimeSpan.Zero) - { - throw Fx.Exception.ArgumentOutOfRange(argumentName, timeout, Resources.TimeoutMustBeNonNegative.FormatForUser(argumentName, timeout)); - } - } - - public static void ThrowIfNonPositiveArgument(TimeSpan timeout) - { - ThrowIfNonPositiveArgument(timeout, nameof(timeout)); - } - - public static void ThrowIfNonPositiveArgument(TimeSpan timeout, string argumentName) - { - if (timeout <= TimeSpan.Zero) - { - throw Fx.Exception.ArgumentOutOfRange(argumentName, timeout, Resources.TimeoutMustBePositive.FormatForUser(argumentName, timeout)); - } - } - - public TimeSpan RemainingTime() - { - if (!this._deadlineSet) - { - this.SetDeadline(); - return this._originalTimeout; - } - - if (this._deadline == DateTime.MaxValue) - { - return TimeSpan.MaxValue; - } - - var remaining = this._deadline - DateTime.UtcNow; - if (remaining <= TimeSpan.Zero) - { - return TimeSpan.Zero; - } - - return remaining; - } - - public TimeSpan ElapsedTime() - { - return this._originalTimeout - this.RemainingTime(); - } - - private void SetDeadline() - { - Debug.Assert(!this._deadlineSet, "TimeoutHelper deadline set twice."); - this._deadline = DateTime.UtcNow + this._originalTimeout; - this._deadlineSet = true; - } - } -} diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Processor/ServiceBusProcessorOptions.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Processor/ServiceBusProcessorOptions.cs index 472041ee10755..b352b28bc424b 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Processor/ServiceBusProcessorOptions.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Processor/ServiceBusProcessorOptions.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using Azure.Core; using Azure.Messaging.ServiceBus.Primitives; namespace Azure.Messaging.ServiceBus @@ -26,10 +27,7 @@ public int PrefetchCount } set { - if (value < 0) - { - throw Fx.Exception.ArgumentOutOfRange(nameof(PrefetchCount), value, "Value cannot be less than 0."); - } + Argument.AssertAtLeast(value, 0, nameof(PrefetchCount)); _prefetchCount = value; } } @@ -61,7 +59,7 @@ public TimeSpan MaxAutoLockRenewalDuration set { - TimeoutHelper.ThrowIfNegativeArgument(value, nameof(value)); + Argument.AssertNotNegative(value, nameof(MaxAutoLockRenewalDuration)); _maxAutoRenewDuration = value; } } @@ -79,7 +77,7 @@ public TimeSpan? MaxReceiveWaitTime { if (value.HasValue) { - TimeoutHelper.ThrowIfNegativeArgument(value.Value, nameof(MaxReceiveWaitTime)); + Argument.AssertPositive(value.Value, nameof(MaxReceiveWaitTime)); } _maxReceiveWaitTime = value; @@ -95,11 +93,7 @@ public int MaxConcurrentCalls set { - if (value <= 0) - { - throw new ArgumentOutOfRangeException(Resources.MaxConcurrentCallsMustBeGreaterThanZero.FormatForUser(value)); - } - + Argument.AssertAtLeast(value, 1, nameof(MaxConcurrentCalls)); _maxConcurrentCalls = value; } } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiver.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiver.cs index b3dd2e7e024f4..70a2dd592e2fe 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiver.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiver.cs @@ -149,6 +149,11 @@ public virtual async Task> ReceiveBatchAsync( { Argument.AssertAtLeast(maxMessages, 1, nameof(maxMessages)); Argument.AssertNotClosed(IsDisposed, nameof(ServiceBusReceiver)); + if (maxWaitTime.HasValue) + { + Argument.AssertPositive(maxWaitTime.Value, nameof(maxWaitTime)); + } + cancellationToken.ThrowIfCancellationRequested(); ServiceBusEventSource.Log.ReceiveMessageStart(Identifier, maxMessages); IList messages = null; diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiverOptions.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiverOptions.cs index 887a4807aa3cd..544446c004fe2 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiverOptions.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Receiver/ServiceBusReceiverOptions.cs @@ -27,10 +27,7 @@ public int PrefetchCount } set { - if (value < 0) - { - throw Fx.Exception.ArgumentOutOfRange(nameof(PrefetchCount), value, "Value cannot be less than 0."); - } + Argument.AssertAtLeast(value, 0, nameof(PrefetchCount)); _prefetchCount = value; } } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs index 8129b8bb65fe0..94d64ba27ec59 100755 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.Designer.cs @@ -213,15 +213,6 @@ internal static string DefaultServerBusyException { } } - /// - /// Looks up a localized string similar to The entity path/name '{0}' exceeds the '{1}' character limit.. - /// - internal static string EntityNameLengthExceedsLimit { - get { - return ResourceManager.GetString("EntityNameLengthExceedsLimit", resourceCulture); - } - } - /// /// Looks up a localized string similar to The minimum back off period '{0}' cannot exceed the maximum back off period of '{1}'.. /// @@ -330,15 +321,6 @@ internal static string ListOfLockTokensCannotBeEmpty { } } - /// - /// Looks up a localized string similar to The specified value '{0}' is invalid. "maxConcurrentCalls" must be greater than zero.. - /// - internal static string MaxConcurrentCallsMustBeGreaterThanZero { - get { - return ResourceManager.GetString("MaxConcurrentCallsMustBeGreaterThanZero", resourceCulture); - } - } - /// /// Looks up a localized string similar to The maximum permitted length of {0} was exceeded.. /// @@ -537,15 +519,6 @@ internal static string SqlFilterActionStatmentTooLong { } } - /// - /// Looks up a localized string similar to The length of the filter statement is {0}, which exceeds the maximum length of {1}. - /// - internal static string SqlFilterStatmentTooLong { - get { - return ResourceManager.GetString("SqlFilterStatmentTooLong", resourceCulture); - } - } - /// /// Looks up a localized string similar to Argument {0} must be a non-negative timeout value. The provided value was {1}.. /// diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.resx b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.resx index 7dc6fd958026f..32231807b8d74 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.resx +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/Resources.resx @@ -165,18 +165,12 @@ '{0}' is not a supported type. - - The length of the filter statement is {0}, which exceeds the maximum length of {1} - The length of the filter action statement is {0}, which exceeds the maximum length of {1}. '{0}' contains character '{1}' which is not allowed because it is reserved in the Uri scheme. - - The entity path/name '{0}' exceeds the '{1}' character limit. - The entity name or path contains an invalid character '{0}'. The supplied value is '{1}'. @@ -186,9 +180,6 @@ This operation is only supported for a message receiver in 'PeekLock' receive mode. - - The specified value '{0}' is invalid. "maxConcurrentCalls" must be greater than zero. - A message handler has already been registered. diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/src/ServiceBusMessage.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/src/ServiceBusMessage.cs index b9b6e2808382e..db2d9b5b0cd43 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/src/ServiceBusMessage.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/src/ServiceBusMessage.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Threading; +using System.Xml.Schema; using Azure.Core; using Azure.Messaging.ServiceBus.Primitives; @@ -199,7 +201,7 @@ public TimeSpan TimeToLive set { - TimeoutHelper.ThrowIfNonPositiveArgument(value); + Argument.AssertPositive(value, nameof(TimeToLive)); _timeToLive = value; } } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Processor/ProcessorTests.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Processor/ProcessorTests.cs index 6c951183e3b10..2759b40729c2b 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Processor/ProcessorTests.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Processor/ProcessorTests.cs @@ -115,6 +115,35 @@ public void ProcessorOptionsSetOnClient() Assert.AreEqual(options.MaxReceiveWaitTime, processor.MaxReceiveWaitTime); } + [Test] + public void ProcessorOptionsValidation() + { + var options = new ServiceBusProcessorOptions(); + Assert.That( + () => options.PrefetchCount = -1, + Throws.InstanceOf()); + Assert.That( + () => options.MaxConcurrentCalls = 0, + Throws.InstanceOf()); + Assert.That( + () => options.MaxConcurrentCalls = -1, + Throws.InstanceOf()); + Assert.That( + () => options.MaxAutoLockRenewalDuration = TimeSpan.FromSeconds(-1), + Throws.InstanceOf()); + Assert.That( + () => options.MaxReceiveWaitTime = TimeSpan.FromSeconds(0), + Throws.InstanceOf()); + Assert.That( + () => options.MaxReceiveWaitTime = TimeSpan.FromSeconds(-1), + Throws.InstanceOf()); + + // should not throw + options.PrefetchCount = 0; + options.MaxReceiveWaitTime = TimeSpan.FromSeconds(1); + options.MaxAutoLockRenewalDuration = TimeSpan.FromSeconds(0); + } + [Test] public async Task UserSettledPropertySetCorrectly() { diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Receiver/ReceiverTests.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Receiver/ReceiverTests.cs index 17627bb68c48a..797a6ecf18a36 100644 --- a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Receiver/ReceiverTests.cs +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/Receiver/ReceiverTests.cs @@ -28,5 +28,33 @@ public void ClientProperties() Assert.IsFalse(receiver.IsSessionReceiver); Assert.AreEqual(ReceiveMode.ReceiveAndDelete, receiver.ReceiveMode); } + + [Test] + public void ReceiverOptionsValidation() + { + var options = new ServiceBusReceiverOptions(); + Assert.That( + () => options.PrefetchCount = -1, + Throws.InstanceOf()); + + // should not throw + options.PrefetchCount = 0; + } + + [Test] + public void ReceiveValidatesMaxWaitTime() + { + var account = Encoding.Default.GetString(GetRandomBuffer(12)); + var fullyQualifiedNamespace = new UriBuilder($"{account}.servicebus.windows.net/").Host; + var connString = $"Endpoint=sb://{fullyQualifiedNamespace};SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey={Encoding.Default.GetString(GetRandomBuffer(64))}"; + var client = new ServiceBusClient(connString); + var receiver = client.CreateReceiver("queue"); + Assert.That( + async () => await receiver.ReceiveAsync(TimeSpan.FromSeconds(0)), + Throws.InstanceOf()); + Assert.That( + async () => await receiver.ReceiveAsync(TimeSpan.FromSeconds(-1)), + Throws.InstanceOf()); + } } } diff --git a/sdk/servicebus/Azure.Messaging.ServiceBus/tests/RuleManager/RuleTests.cs b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/RuleManager/RuleTests.cs new file mode 100644 index 0000000000000..d6ca2caff3bc1 --- /dev/null +++ b/sdk/servicebus/Azure.Messaging.ServiceBus/tests/RuleManager/RuleTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using Azure.Messaging.ServiceBus.Filters; +using NUnit.Framework; + +namespace Azure.Messaging.ServiceBus.Tests.RuleManager +{ + public class RuleTests + { + [Test] + public void SqlFilterValidation() + { + Assert.That( + () => new SqlFilter(null), + Throws.InstanceOf()); + Assert.That( + () => new SqlFilter(string.Empty), + Throws.InstanceOf()); + Assert.That( + () => new SqlFilter(new string('a', Constants.MaximumSqlFilterStatementLength + 1)), + Throws.InstanceOf()); + } + + [Test] + public void SqlRuleActionValidation() + { + Assert.That( + () => new SqlRuleAction(null), + Throws.InstanceOf()); + Assert.That( + () => new SqlRuleAction(string.Empty), + Throws.InstanceOf()); + Assert.That( + () => new SqlRuleAction(new string('a', Constants.MaximumSqlRuleActionStatementLength + 1)), + Throws.InstanceOf()); + } + } +}