From fa901b646f9b194498c5d94456c2bdd7dab05680 Mon Sep 17 00:00:00 2001 From: Staffan Gustafsson Date: Thu, 20 Apr 2017 20:31:57 +0200 Subject: [PATCH] Add ErrorMessage property to ValidatePattern, ValidateSet and ValidateScript attributes (#2728) This makes it possibe to write for example [ValidatePattern('[A-Z]:', ErrorMessage='The Drive should be specified as a single letter followed by a colon, for example "D:"')] [string] $Drive, The element being validated is also passed, so {0} can be used in the custom error message --- .../engine/Attributes.cs | 45 ++++++++++++++- .../engine/parser/Compiler.cs | 4 ++ .../Scripting/ParameterBinding.Tests.ps1 | 56 +++++++++++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index d0ae9f601..189fc6032 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1113,6 +1113,17 @@ namespace System.Management.Automation /// public RegexOptions Options { set; get; } = RegexOptions.IgnoreCase; + /// + /// Gets or sets the custom error message pattern that is displayed to the user. + /// + /// The text representation of the object being validated and the validating regex is passed as + /// the first and second formatting parameters to the ErrorMessage formatting pattern. + /// + /// [ValidatePattern("\s+", ErrorMessage="The text '{0}' did not pass validation of regex '{1}'")] + /// + /// + public string ErrorMessage { get; set; } + /// /// Validates that each parameter argument matches the RegexPattern /// @@ -1136,8 +1147,9 @@ namespace System.Management.Automation Match match = regex.Match(objectString); if (!match.Success) { + var errorMessageFormat = String.IsNullOrEmpty(ErrorMessage) ? Metadata.ValidatePatternFailure : ErrorMessage; throw new ValidationMetadataException("ValidatePatternFailure", - null, Metadata.ValidatePatternFailure, + null, errorMessageFormat, objectString, RegexPattern); } } @@ -1163,6 +1175,18 @@ namespace System.Management.Automation /// public sealed class ValidateScriptAttribute : ValidateEnumeratedArgumentsAttribute { + /// + /// Gets or sets the custom error message that is displayed to the user. + /// + /// The item being validated and the validating scriptblock is passed as the first and second + /// formatting argument. + /// + /// + /// [ValidateScript("$_ % 2", ErrorMessage = "The item '{0}' did not pass validation of script '{1}'")] + /// + /// + public string ErrorMessage { get; set; } + /// /// Gets the scriptblock to be used in the validation /// @@ -1193,8 +1217,9 @@ namespace System.Management.Automation if (!LanguagePrimitives.IsTrue(result)) { + var errorMessageFormat = String.IsNullOrEmpty(ErrorMessage) ? Metadata.ValidateScriptFailure : ErrorMessage; throw new ValidationMetadataException("ValidateScriptFailure", - null, Metadata.ValidateScriptFailure, + null, errorMessageFormat, element, ScriptBlock); } } @@ -1336,6 +1361,18 @@ namespace System.Management.Automation { private string[] _validValues; + /// + /// Gets or sets the custom error message that is displayed to the user + /// + /// The item being validated and a text representation of the validation set + /// is passed as the first and second formatting argument to the ErrorMessage formatting pattern. + /// + /// + /// [ValidateSet("A","B","C", ErrorMessage="The item '{0}' is not part of the set '{1}'.") + /// + /// + public string ErrorMessage { get; set; } + /// /// Gets a flag specifying if we should ignore the case when performing string comparison. The /// default is true. @@ -1386,8 +1423,10 @@ namespace System.Management.Automation return; } } + + var errorMessageFormat = String.IsNullOrEmpty(ErrorMessage) ? Metadata.ValidateSetFailure : ErrorMessage; throw new ValidationMetadataException("ValidateSetFailure", null, - Metadata.ValidateSetFailure, + errorMessageFormat, element.ToString(), SetAsString()); } diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 91244543e..d24520080 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -1313,6 +1313,10 @@ namespace System.Management.Automation.Language { result.IgnoreCase = s_attrArgToBoolConverter.Target(s_attrArgToBoolConverter, argValue); } + else if (argumentName.Equals("ErrorMessage", StringComparison.OrdinalIgnoreCase)) + { + result.ErrorMessage = argValue.ToString(); + } else { throw InterpreterError.NewInterpreterException(namedArg, typeof(RuntimeException), namedArg.Extent, diff --git a/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 b/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 index 4f3d2dbf2..6f3795211 100644 --- a/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 +++ b/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 @@ -412,6 +412,62 @@ { get-fooh } | Should not throw } + + It "ValidateScript can use custom ErrorMessage" { + function get-fooi { + [CmdletBinding()] + param([ValidateScript({$_ -gt 2}, ErrorMessage = "Item '{0}' failed '{1}' validation")] $p) + $p + } + $errMsg = '' + try + { + get-fooi -p 2 + } + catch + { + $errMsg = $_.Exception.Message + } + $errMsg | Should Be "Cannot validate argument on parameter 'p'. Item '2' failed '`$_ -gt 2' validation" + } + + It "ValidatePattern can use custom ErrorMessage" { + function get-fooj + { + [CmdletBinding()] + param([ValidatePattern("\s+", ErrorMessage = "Item '{0}' failed '{1}' regex")] $p) + $p + } + $errMsg = '' + try + { + get-fooj -p 2 + } + catch + { + $errMsg = $_.Exception.Message + } + $errMsg | Should Be "Cannot validate argument on parameter 'p'. Item '2' failed '\s+' regex" + } + + It "ValidateSet can use custom ErrorMessage" { + function get-fook + { + param([ValidateSet('A', 'B', 'C', IgnoreCase=$false, ErrorMessage="Item '{0}' is not in '{1}'")] $p) + } + $errMsg = '' + try + { + get-fook -p 2 + } + catch + { + $errMsg = $_.Exception.Message + } + $set = 'A','B','C' -join [Globalization.CultureInfo]::CurrentUICulture.TextInfo.ListSeparator + $errMsg | Should Be "Cannot validate argument on parameter 'p'. Item '2' is not in '$set'" + } + } #known issue 2069