diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 5ac315d2ae..3250e626b9 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -899,6 +899,31 @@ namespace System.Management.Automation } } + /// + /// Predefined range kind to use with ValidateRangeAttribute. + /// + public enum ValidateRangeKind + { + /// + /// Range is greater than 0. + /// + Positive, + + /// + /// Range is greater than or equal to 0. + /// + NonNegative, + + /// + /// Range is less than 0. + /// + Negative, + + /// + /// Range is less than or equal to 0. + /// + NonPositive + } /// /// Validates that each parameter argument falls in the range /// specified by MinRange and MaxRange @@ -926,6 +951,8 @@ namespace System.Management.Automation /// private Type _promotedType; + ValidateRangeKind? _rangeKind; + /// /// Validates that each parameter argument falls in the range /// specified by MinRange and MaxRange @@ -951,37 +978,14 @@ namespace System.Management.Automation { element = o.BaseObject; } - - // minRange and maxRange have the same type, so we just need - // to compare to one of them - if (element.GetType() != _promotedType) + + if (_rangeKind.HasValue) { - object resultValue; - if (LanguagePrimitives.TryConvertTo(element, _promotedType, out resultValue)) - { - element = resultValue; - } - else - { - throw new ValidationMetadataException("ValidationRangeElementType", - null, Metadata.ValidateRangeElementType, - element.GetType().Name, MinRange.GetType().Name); - } + ValidateRange(element, (ValidateRangeKind)_rangeKind); } - - // They are the same type and are all IComparable, so this should not throw - if (_minComparable.CompareTo(element) > 0) + else { - throw new ValidationMetadataException("ValidateRangeTooSmall", - null, Metadata.ValidateRangeSmallerThanMinRangeFailure, - element.ToString(), MinRange.ToString()); - } - - if (_maxComparable.CompareTo(element) < 0) - { - throw new ValidationMetadataException("ValidateRangeTooBig", - null, Metadata.ValidateRangeGreaterThanMaxRangeFailure, - element.ToString(), MaxRange.ToString()); + ValidateRange(element); } } @@ -1058,6 +1062,139 @@ namespace System.Management.Automation MaxRange = maxRange; } + /// + /// Initializes a new instance of the ValidateRangeAttribute class + /// this constructor uses a predefined ranged + /// + public ValidateRangeAttribute(ValidateRangeKind kind) : base() + { + _rangeKind = kind; + } + + private void ValidateRange(object element, ValidateRangeKind rangeKind) + { + Type commonType = GetCommonType(typeof(int),element.GetType()); + if (commonType == null) + { + throw new ValidationMetadataException( + "ValidationRangeElementType", + null, + Metadata.ValidateRangeElementType, + element.GetType().Name, + typeof(int).Name); + } + + object resultValue; + IComparable dynamicZero = 0; + + if (LanguagePrimitives.TryConvertTo(element, commonType, out resultValue)) + { + element = resultValue; + + if (LanguagePrimitives.TryConvertTo(0, commonType, out resultValue)) + { + dynamicZero = (IComparable)resultValue; + } + } + else + { + throw new ValidationMetadataException( + "ValidationRangeElementType", + null, + Metadata.ValidateRangeElementType, + element.GetType().Name, + commonType.Name); + } + + switch (rangeKind) + { + case ValidateRangeKind.Positive: + if (dynamicZero.CompareTo(element) >= 0) + { + throw new ValidationMetadataException( + "ValidateRangePositiveFailure", + null, + Metadata.ValidateRangePositiveFailure, + element.ToString()); + } + break; + case ValidateRangeKind.NonNegative: + if (dynamicZero.CompareTo(element) > 0) + { + throw new ValidationMetadataException( + "ValidateRangeNonNegativeFailure", + null, + Metadata.ValidateRangeNonNegativeFailure, + element.ToString()); + } + break; + case ValidateRangeKind.Negative: + if (dynamicZero.CompareTo(element) <= 0) + { + throw new ValidationMetadataException( + "ValidateRangeNegativeFailure", + null, + Metadata.ValidateRangeNegativeFailure, + element.ToString()); + } + break; + case ValidateRangeKind.NonPositive: + if (dynamicZero.CompareTo(element) < 0) + { + throw new ValidationMetadataException( + "ValidateRangeNonPositiveFailure", + null, + Metadata.ValidateRangeNonPositiveFailure, + element.ToString()); + } + break; + } + } + + private void ValidateRange(object element) + { + // MinRange and maxRange have the same type, so we just need + // to compare to one of them. + if (element.GetType() != _promotedType) + { + object resultValue; + if (LanguagePrimitives.TryConvertTo(element, _promotedType, out resultValue)) + { + element = resultValue; + } + else + { + throw new ValidationMetadataException( + "ValidationRangeElementType", + null, + Metadata.ValidateRangeElementType, + element.GetType().Name, + MinRange.GetType().Name); + } + } + + // They are the same type and are all IComparable, so this should not throw + if (_minComparable.CompareTo(element) > 0) + { + throw new ValidationMetadataException( + "ValidateRangeTooSmall", + null, + Metadata.ValidateRangeSmallerThanMinRangeFailure, + element.ToString(), + MinRange.ToString()); + } + + if (_maxComparable.CompareTo(element) < 0) + { + throw new ValidationMetadataException( + "ValidateRangeTooBig", + null, + Metadata.ValidateRangeGreaterThanMaxRangeFailure, + element.ToString(), + MaxRange.ToString()); + } + } + private static Type GetCommonType(Type minType, Type maxType) { Type resultType = null; diff --git a/src/System.Management.Automation/resources/Metadata.resx b/src/System.Management.Automation/resources/Metadata.resx index aad8f90208..762f830677 100644 --- a/src/System.Management.Automation/resources/Metadata.resx +++ b/src/System.Management.Automation/resources/Metadata.resx @@ -123,6 +123,18 @@ The argument cannot be validated because its type "{0}" is not the same type ({1}) as the maximum and minimum limits of the parameter. Make sure the argument is of type {1} and then try the command again. + + The argument "{0}" cannot be validated because its value is not greater than zero. + + + The argument "{0}" cannot be validated because its value is not greater than or equal to zero. + + + The argument "{0}" cannot be validated because its value is not less than zero. + + + The argument "{0}" cannot be validated because its value is not less than or equal to zero. + The specified minimum range ({0}) cannot be accepted because it is not the same type as the specified maximum range ({1}). Update the ValidateRange attribute for the parameter. diff --git a/src/System.Management.Automation/utils/MetadataExceptions.cs b/src/System.Management.Automation/utils/MetadataExceptions.cs index 733bf12766..69e4dad6fc 100644 --- a/src/System.Management.Automation/utils/MetadataExceptions.cs +++ b/src/System.Management.Automation/utils/MetadataExceptions.cs @@ -72,6 +72,10 @@ namespace System.Management.Automation public class ValidationMetadataException : MetadataException { internal const string ValidateRangeElementType = "ValidateRangeElementType"; + internal const string ValidateRangePositiveFailure = "ValidateRangePositiveFailure"; + internal const string ValidateRangeNonNegativeFailure = "ValidateRangeNonNegativeFailure"; + internal const string ValidateRangeNegativeFailure = "ValidateRangeNegativeFailure"; + internal const string ValidateRangeNonPositiveFailure = "ValidateRangeNonPositiveFailure"; internal const string ValidateRangeMinRangeMaxRangeType = "ValidateRangeMinRangeMaxRangeType"; internal const string ValidateRangeNotIComparable = "ValidateRangeNotIComparable"; internal const string ValidateRangeMaxRangeSmallerThanMinRange = "ValidateRangeMaxRangeSmallerThanMinRange"; diff --git a/test/powershell/engine/ValidateAttributes.Tests.ps1 b/test/powershell/engine/ValidateAttributes.Tests.ps1 index 42989b3c45..92a04c1497 100644 --- a/test/powershell/engine/ValidateAttributes.Tests.ps1 +++ b/test/powershell/engine/ValidateAttributes.Tests.ps1 @@ -3,26 +3,261 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { Context "ValidateCount" { BeforeAll { $testCases = @( - @{ sb = { function Local:foo { param([ValidateCount(-1,2)] [string[]] $bar) }; foo }; FullyQualifiedErrorId = "ExceptionConstructingAttribute"; InnerErrorId = "" } - @{ sb = { function Local:foo { param([ValidateCount(1,-1)] [string[]] $bar) }; foo }; FullyQualifiedErrorId = "ExceptionConstructingAttribute"; InnerErrorId = "" } - @{ sb = { function Local:foo { param([ValidateCount(2, 1)] [string[]] $bar) }; foo }; FullyQualifiedErrorId = "ValidateRangeMaxLengthSmallerThanMinLength"; InnerErrorId = "" } - @{ sb = { function Local:foo { param([ValidateCount(2, 2)] [string[]] $bar) }; foo 1 }; FullyQualifiedErrorId = "ParameterArgumentValidationError,foo"; InnerErrorId = "ValidateCountExactFailure" } - @{ sb = { function Local:foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1 }; FullyQualifiedErrorId = "ParameterArgumentValidationError,foo"; InnerErrorId = "ValidateCountMinMaxFailure" } - @{ sb = { function Local:foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1,2,3,4 }; FullyQualifiedErrorId = "ParameterArgumentValidationError,foo"; InnerErrorId = "ValidateCountMinMaxFailure" } + @{ + ScriptBlock = { function foo { param([ValidateCount(-1,2)] [string[]] $bar) }; foo } + FullyQualifiedErrorId = "ExceptionConstructingAttribute" + InnerErrorId = "" + } + @{ + ScriptBlock = { function foo { param([ValidateCount(1,-1)] [string[]] $bar) }; foo } + FullyQualifiedErrorId = "ExceptionConstructingAttribute" + InnerErrorId = "" + } + @{ + ScriptBlock = { function foo { param([ValidateCount(2, 1)] [string[]] $bar) }; foo } + FullyQualifiedErrorId = "ValidateRangeMaxLengthSmallerThanMinLength" + InnerErrorId = "" + } + @{ + ScriptBlock = { function foo { param([ValidateCount(2, 2)] [string[]] $bar) }; foo 1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateCountExactFailure" + } + @{ + ScriptBlock = { function foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateCountMinMaxFailure" + } + @{ + ScriptBlock = { function foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1,2,3,4 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateCountMinMaxFailure" + } ) } It 'Exception: :' -TestCases $testCases { - param($sb, $FullyQualifiedErrorId, $InnerErrorId) + param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) - $sb | ShouldBeErrorId $FullyQualifiedErrorId + $ScriptBlock | ShouldBeErrorId $FullyQualifiedErrorId if ($InnerErrorId) { $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should Be $InnerErrorId } } It 'No Exception: valid argument count' { - { function Local:foo { param([ValidateCount(2, 4)] [string[]] $bar) }; foo 1,2,3,4 } | Should Not Throw + { function foo { param([ValidateCount(2, 4)] [string[]] $bar) }; foo 1,2,3,4 } | Should Not Throw + } + } + + Context "ValidateRange - ParameterConstuctors" { + BeforeAll { + $testCases = @( + @{ + ScriptBlock = { function foo { param([ValidateRange('xPositive')] $bar) }; foo } + FullyQualifiedErrorId = "ExceptionConstructingAttribute" + InnerErrorId = "SubstringDisambiguationEnumParseThrewAnException" + } + @{ + ScriptBlock = { function foo { param([ValidateRange(2,1)] [int] $bar) }; foo } + FullyQualifiedErrorId = "MaxRangeSmallerThanMinRange" + InnerErrorId = "" + } + @{ + ScriptBlock = { function foo { param([ValidateRange("one",10)] $bar) }; foo } + FullyQualifiedErrorId = "MinRangeNotTheSameTypeOfMaxRange" + InnerErrorId = "" + } + @{ + ScriptBlock = { function foo { param([ValidateRange(1,"two")] $bar) }; foo } + FullyQualifiedErrorId = "MinRangeNotTheSameTypeOfMaxRange" + InnerErrorId = "" + } + ) + } + + It 'Exception: :' -TestCases $testCases { + param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) + + $ScriptBlock | ShouldBeErrorId $FullyQualifiedErrorId + if ($InnerErrorId) { + $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should Be $InnerErrorId + } + } + } + Context "ValidateRange - User Defined Range"{ + BeforeAll { + $testCases = @( + @{ + ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo -1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateRangeTooSmall" + } + @{ + ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo 11 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateRangeTooBig" + } + @{ + ScriptBlock = { function foo { param([ValidateRange(1,10)] $bar) }; foo "one" } + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidationRangeElementType" + } + ) + + $validTestCases = @( + @{ + ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo 5 } + } + ) + } + + It 'Exception: :' -TestCases $testCases { + param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) + + $ScriptBlock | ShouldBeErrorId $FullyQualifiedErrorId + if ($InnerErrorId) { + $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should Be $InnerErrorId + } + } + + It 'No Exception: value within range' -TestCases $validTestCases { + param($ScriptBlock) + $ScriptBlock | Should Not Throw + } + } + + Context "ValidateRange - Predefined Range" { + BeforeAll { + $testCases = @( + @{ + ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo -1 } + RangeType = "Positive" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateRangePositiveFailure" + } + @{ + ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo 0 } + RangeType = "Positive" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateRangePositiveFailure" + } + @{ + ScriptBlock = { function foo { param([ValidateRange("Positive")] $bar) }; foo "one" } + RangeType = "Positive" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "" + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo -1 } + RangeType = "NonNegative" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateRangeNonNegativeFailure" + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonNegative')] $bar) }; foo "one" } + RangeType = "NonNegative" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "" + } + @{ + ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo 1 } + RangeType = "Negative" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateRangeNegativeFailure" + } + @{ + ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo 0 } + RangeType = "Negative" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateRangeNegativeFailure" + } + @{ + ScriptBlock = { function foo { param([ValidateRange('Negative')] $bar) }; foo "one" } + RangeType = "Negative" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "" + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonPositive')] $bar) }; foo 1 } + RangeType = "NonPositive" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "ValidateRangeNonPositiveFailure" + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonPositive')] $bar) }; foo "one" } + RangeType = "NonPositive" + FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + InnerErrorId = "" + } + ) + + $validTestCases = @( + @{ + ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo 15 } + RangeType = "Positive" + TestValue = 15 + } + @{ + ScriptBlock = { function foo { param([ValidateRange("Positive")] [double]$bar) }; foo ([double]::MaxValue) }; + RangeType = "Positive" + TestValue = [double]::MaxValue + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo 0 } + RangeType = "NonNegative" + TestValue = 0 + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo 15 } + RangeType = "NonNegative" + TestValue = 15 + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [double]$bar) }; foo ([double]::MaxValue) }; + RangeType = "NonNegative" + TestValue = [double]::MaxValue + } + @{ + ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo -15 } + RangeType = "Negative" + TestValue = -15 + } + @{ + ScriptBlock = { function foo { param([ValidateRange('Negative')] [double]$bar) }; foo ([double]::MinValue) }; + TestValue = [double]::MinValue + RangeType = "Negative" + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [int] $bar) }; foo 0 } + RangeType = "NonPositive" + TestValue = 0 + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [int] $bar) }; foo -15 } + RangeType = "NonPositive" + TestValue = -15 + } + @{ + ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [double]$bar) }; foo ([double]::MinValue) } + RangeType = "NonPositive" + TestValue = [double]::MinValue + } + ) + } + + It 'Exception: :, RangeType: ' -TestCases $testCases { + param($ScriptBlock, $RangeType, $FullyQualifiedErrorId, $InnerErrorId) + + $ScriptBlock | ShouldBeErrorId $FullyQualifiedErrorId + if ($InnerErrorId) { + $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should Be $InnerErrorId + } + } + + It 'No Exception: RangeType: - argument ""' -TestCases $validTestCases { + param($ScriptBlock, $RangeType, $testValue) + $ScriptBlock | Should Not Throw } } }