From 07516dee1e413b2f56895bcfd386c3a147685fe8 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Sun, 30 Jan 2022 10:50:17 +0100 Subject: [PATCH 1/4] Fix trimming runtime error Make CommandLineUtils working with net6.0 trimming. Use explicit constructors known at compile time instead of using Activator.CreateInstance --- .../Validation/ValidationExtensions.cs | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/CommandLineUtils/Validation/ValidationExtensions.cs b/src/CommandLineUtils/Validation/ValidationExtensions.cs index afca5e36..4b7596a6 100644 --- a/src/CommandLineUtils/Validation/ValidationExtensions.cs +++ b/src/CommandLineUtils/Validation/ValidationExtensions.cs @@ -23,8 +23,11 @@ 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 = new RequiredAttribute + { + ErrorMessage = errorMessage, + AllowEmptyStrings = allowEmptyStrings + }; option.Validators.Add(new AttributeValidator(attribute)); return option; } @@ -57,8 +60,11 @@ 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 = new RequiredAttribute + { + ErrorMessage = errorMessage, + AllowEmptyStrings = allowEmptyStrings + }; argument.Validators.Add(new AttributeValidator(attribute)); return argument; } @@ -247,7 +253,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 +263,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 +272,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 +281,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 +290,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 +299,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 +308,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 +317,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 +326,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 +336,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 +346,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 +356,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 +386,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 = new RangeAttribute(minimum, maximum) { ErrorMessage = errorMessage }; builder.Use(new AttributeValidator(attribute)); return builder; } @@ -397,7 +403,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 = new RangeAttribute(minimum, maximum) { ErrorMessage = errorMessage }; builder.Use(new AttributeValidator(attribute)); return builder; } @@ -438,6 +444,16 @@ public static CommandOption OnValidate(this CommandOption option, Func(string? errorMessage, object[]? ctorArgs = null) where T : ValidationAttribute { From 66c863894ed26b48701f9cac6ae8bc365204e7d6 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Mon, 31 Jan 2022 07:24:42 +0100 Subject: [PATCH 2/4] Don't set errorMessage if null --- .../Validation/ValidationExtensions.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/CommandLineUtils/Validation/ValidationExtensions.cs b/src/CommandLineUtils/Validation/ValidationExtensions.cs index 4b7596a6..abfb1937 100644 --- a/src/CommandLineUtils/Validation/ValidationExtensions.cs +++ b/src/CommandLineUtils/Validation/ValidationExtensions.cs @@ -23,11 +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 = new RequiredAttribute + var attribute = AddErrorMessage(new RequiredAttribute { - ErrorMessage = errorMessage, AllowEmptyStrings = allowEmptyStrings - }; + }, errorMessage); option.Validators.Add(new AttributeValidator(attribute)); return option; } @@ -60,11 +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 = new RequiredAttribute + var attribute = AddErrorMessage(new RequiredAttribute { - ErrorMessage = errorMessage, AllowEmptyStrings = allowEmptyStrings - }; + }, errorMessage); argument.Validators.Add(new AttributeValidator(attribute)); return argument; } @@ -386,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 = new RangeAttribute(minimum, maximum) { ErrorMessage = errorMessage }; + var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage ); builder.Use(new AttributeValidator(attribute)); return builder; } @@ -403,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 = new RangeAttribute(minimum, maximum) { ErrorMessage = errorMessage }; + var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage); builder.Use(new AttributeValidator(attribute)); return builder; } @@ -464,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; + } } } From d8cf2e2b9c2ebe66c2b70c12d8618d9087817b21 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Mon, 31 Jan 2022 07:54:57 +0100 Subject: [PATCH 3/4] Remove extra whitespace --- src/CommandLineUtils/Validation/ValidationExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommandLineUtils/Validation/ValidationExtensions.cs b/src/CommandLineUtils/Validation/ValidationExtensions.cs index abfb1937..0df19e2b 100644 --- a/src/CommandLineUtils/Validation/ValidationExtensions.cs +++ b/src/CommandLineUtils/Validation/ValidationExtensions.cs @@ -384,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 = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage ); + var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage); builder.Use(new AttributeValidator(attribute)); return builder; } From aef16d7b06c34bb398e412899507524d2c5812ba Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Mon, 31 Jan 2022 21:25:35 +0100 Subject: [PATCH 4/4] Try to improve coverage --- .../AttributeValidatorTests.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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"); + } + } } }