diff --git a/src/CommandLineUtils/Validation/ValidationExtensions.cs b/src/CommandLineUtils/Validation/ValidationExtensions.cs index afca5e36..0df19e2b 100644 --- a/src/CommandLineUtils/Validation/ValidationExtensions.cs +++ b/src/CommandLineUtils/Validation/ValidationExtensions.cs @@ -23,8 +23,10 @@ public static class ValidationExtensions public static CommandOption IsRequired(this CommandOption option, bool allowEmptyStrings = false, string? errorMessage = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters { - var attribute = GetValidationAttr(errorMessage); - attribute.AllowEmptyStrings = allowEmptyStrings; + var attribute = AddErrorMessage(new RequiredAttribute + { + AllowEmptyStrings = allowEmptyStrings + }, errorMessage); option.Validators.Add(new AttributeValidator(attribute)); return option; } @@ -57,8 +59,10 @@ public static CommandOption IsRequired(this CommandOption option, bool public static CommandArgument IsRequired(this CommandArgument argument, bool allowEmptyStrings = false, string? errorMessage = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters { - var attribute = GetValidationAttr(errorMessage); - attribute.AllowEmptyStrings = allowEmptyStrings; + var attribute = AddErrorMessage(new RequiredAttribute + { + AllowEmptyStrings = allowEmptyStrings + }, errorMessage); argument.Validators.Add(new AttributeValidator(attribute)); return argument; } @@ -247,7 +251,7 @@ public static IValidationBuilder Values(this IValidationBuilder builder, bool ig /// The builder. public static IValidationBuilder Values(this IValidationBuilder builder, StringComparison comparer, params string[] allowedValues) { - return builder.Satisfies(ctorArgs: new object[] { comparer, allowedValues }); + return builder.Satisfies(new AllowedValuesAttribute(comparer, allowedValues)); } /// @@ -257,7 +261,7 @@ public static IValidationBuilder Values(this IValidationBuilder builder, StringC /// A custom error message to display. /// The builder. public static IValidationBuilder EmailAddress(this IValidationBuilder builder, string? errorMessage = null) - => builder.Satisfies(errorMessage); + => builder.Satisfies(new EmailAddressAttribute(), errorMessage); /// /// Specifies that values must be a path to a file that already exists. @@ -266,7 +270,7 @@ public static IValidationBuilder EmailAddress(this IValidationBuilder builder, s /// A custom error message to display. /// The builder. public static IValidationBuilder ExistingFile(this IValidationBuilder builder, string? errorMessage = null) - => builder.Satisfies(errorMessage); + => builder.Satisfies(new FileExistsAttribute(), errorMessage); /// /// Specifies that values must be a path to a file that does not already exist. @@ -275,7 +279,7 @@ public static IValidationBuilder ExistingFile(this IValidationBuilder builder, s /// A custom error message to display. /// The builder. public static IValidationBuilder NonExistingFile(this IValidationBuilder builder, string? errorMessage = null) - => builder.Satisfies(errorMessage); + => builder.Satisfies(new FileNotExistsAttribute(), errorMessage); /// /// Specifies that values must be a path to a directory that already exists. @@ -284,7 +288,7 @@ public static IValidationBuilder NonExistingFile(this IValidationBuilder builder /// A custom error message to display. /// The builder. public static IValidationBuilder ExistingDirectory(this IValidationBuilder builder, string? errorMessage = null) - => builder.Satisfies(errorMessage); + => builder.Satisfies(new DirectoryExistsAttribute(), errorMessage); /// /// Specifies that values must be a path to a directory that does not already exist. @@ -293,7 +297,7 @@ public static IValidationBuilder ExistingDirectory(this IValidationBuilder build /// A custom error message to display. /// The builder. public static IValidationBuilder NonExistingDirectory(this IValidationBuilder builder, string? errorMessage = null) - => builder.Satisfies(errorMessage); + => builder.Satisfies(new DirectoryNotExistsAttribute(), errorMessage); /// /// Specifies that values must be a valid file path or directory, and the file path must already exist. @@ -302,7 +306,7 @@ public static IValidationBuilder NonExistingDirectory(this IValidationBuilder bu /// A custom error message to display. /// The builder. public static IValidationBuilder ExistingFileOrDirectory(this IValidationBuilder builder, string? errorMessage = null) - => builder.Satisfies(errorMessage); + => builder.Satisfies(new FileOrDirectoryExistsAttribute(), errorMessage); /// /// Specifies that values must be a valid file path or directory, and the file path must not already exist. @@ -311,7 +315,7 @@ public static IValidationBuilder ExistingFileOrDirectory(this IValidationBuilder /// A custom error message to display. /// The builder. public static IValidationBuilder NonExistingFileOrDirectory(this IValidationBuilder builder, string? errorMessage = null) - => builder.Satisfies(errorMessage); + => builder.Satisfies(new FileOrDirectoryNotExistsAttribute(), errorMessage); /// /// Specifies that values must be legal file paths. @@ -320,7 +324,7 @@ public static IValidationBuilder NonExistingFileOrDirectory(this IValidationBuil /// A custom error message to display. /// The builder. public static IValidationBuilder LegalFilePath(this IValidationBuilder builder, string? errorMessage = null) - => builder.Satisfies(errorMessage); + => builder.Satisfies(new LegalFilePathAttribute(), errorMessage); /// /// Specifies that values must be a string at least characters long. @@ -330,7 +334,7 @@ public static IValidationBuilder LegalFilePath(this IValidationBuilder builder, /// A custom error message to display. /// The builder. public static IValidationBuilder MinLength(this IValidationBuilder builder, int length, string? errorMessage = null) - => builder.Satisfies(errorMessage, length); + => builder.Satisfies(new MinLengthAttribute(length), errorMessage); /// /// Specifies that values must be a string no more than characters long. @@ -340,7 +344,7 @@ public static IValidationBuilder MinLength(this IValidationBuilder builder, int /// A custom error message to display. /// The builder. public static IValidationBuilder MaxLength(this IValidationBuilder builder, int length, string? errorMessage = null) - => builder.Satisfies(errorMessage, length); + => builder.Satisfies(new MaxLengthAttribute(length), errorMessage); /// /// Specifies that values must match a regular expression. @@ -350,7 +354,7 @@ public static IValidationBuilder MaxLength(this IValidationBuilder builder, int /// A custom error message to display. /// The builder. public static IValidationBuilder RegularExpression(this IValidationBuilder builder, string pattern, string? errorMessage = null) - => builder.Satisfies(errorMessage, pattern); + => builder.Satisfies(new RegularExpressionAttribute(pattern), errorMessage); /// /// Specifies that values must satisfy the requirements of the validation attribute of type . @@ -380,7 +384,7 @@ public static IValidationBuilder Satisfies(this IValidationBuilder b public static IValidationBuilder Range(this IValidationBuilder builder, int minimum, int maximum, string? errorMessage = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters { - var attribute = GetValidationAttr(errorMessage, new object[] { minimum, maximum }); + var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage); builder.Use(new AttributeValidator(attribute)); return builder; } @@ -397,7 +401,7 @@ public static IValidationBuilder Range(this IValidationBuilder builder public static IValidationBuilder Range(this IValidationBuilder builder, double minimum, double maximum, string? errorMessage = null) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters { - var attribute = GetValidationAttr(errorMessage, new object[] { minimum, maximum }); + var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage); builder.Use(new AttributeValidator(attribute)); return builder; } @@ -438,6 +442,16 @@ public static CommandOption OnValidate(this CommandOption option, Func(string? errorMessage, object[]? ctorArgs = null) where T : ValidationAttribute { @@ -448,5 +462,15 @@ private static T GetValidationAttr(string? errorMessage, object[]? ctorArgs = } return attribute; } + + private static T AddErrorMessage(T attribute, string? errorMessage) + where T : ValidationAttribute + { + if (errorMessage != null) + { + attribute.ErrorMessage = errorMessage; + } + return attribute; + } } } diff --git a/test/CommandLineUtils.Tests/AttributeValidatorTests.cs b/test/CommandLineUtils.Tests/AttributeValidatorTests.cs index ddbb9f94..3caa4ae4 100644 --- a/test/CommandLineUtils.Tests/AttributeValidatorTests.cs +++ b/test/CommandLineUtils.Tests/AttributeValidatorTests.cs @@ -105,7 +105,7 @@ public OptionBuilderApp(TestConsole testConsole) : base(testConsole) { Option("-e|--email", "Email", CommandOptionType.SingleValue) - .Accepts().EmailAddress(); + .Accepts().EmailAddress("Invalid Email"); Option("-n|--name", "Name", CommandOptionType.SingleValue) .Accepts().MinLength(1); @@ -115,6 +115,9 @@ public OptionBuilderApp(TestConsole testConsole) Option("-r|--regex", "Regex", CommandOptionType.SingleValue) .Accepts().RegularExpression("^abc.*"); + + Option("-m|--mode", "Mode", CommandOptionType.SingleValue) + .Accepts().Satisfies("With an error message from model validation"); } } @@ -132,6 +135,9 @@ private class OptionApp [Option, RegularExpression("^abc.*")] public string? Regex { get; } + [Option, ModeValidation] + public string? Mode { get; } + private void OnExecute() { } } @@ -149,6 +155,8 @@ private void OnExecute() { } [InlineData(new[] { "-a", "abcdefghijk" }, 1)] [InlineData(new[] { "-r", "abcdefghijk" }, 0)] [InlineData(new[] { "-r", "xyz" }, 1)] + [InlineData(new[] { "-m", "xyz" }, 1)] + [InlineData(new[] { "-m", "mode" }, 0)] public void ValidatesAttributesOnOption(string[] args, int exitCode) { Assert.Equal(exitCode, CommandLineApplication.Execute(new TestConsole(_output), args)); @@ -179,5 +187,14 @@ public override bool IsValid(object value) && app.Arg1 != null && app.Arg1.Contains("good") && app.Arg2 != null && app.Arg2.Contains("good"); } + + [AttributeUsage(AttributeTargets.Property)] + private sealed class ModeValidationAttribute : ValidationAttribute + { + public override bool IsValid(object value) + { + return value is string text && text.Contains("mode"); + } + } } }