Skip to content

Commit

Permalink
refactor: use explicit constructors on validation attributes to worka…
Browse files Browse the repository at this point in the history
…round .NET 6 runtime trimming error (#491)
  • Loading branch information
xoofx committed Feb 1, 2022
1 parent 3352423 commit 33b4ece
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 19 deletions.
60 changes: 42 additions & 18 deletions src/CommandLineUtils/Validation/ValidationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RequiredAttribute>(errorMessage);
attribute.AllowEmptyStrings = allowEmptyStrings;
var attribute = AddErrorMessage(new RequiredAttribute
{
AllowEmptyStrings = allowEmptyStrings
}, errorMessage);
option.Validators.Add(new AttributeValidator(attribute));
return option;
}
Expand Down Expand Up @@ -57,8 +59,10 @@ public static CommandOption IsRequired(this CommandOption option, bool allowEmpt
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<RequiredAttribute>(errorMessage);
attribute.AllowEmptyStrings = allowEmptyStrings;
var attribute = AddErrorMessage(new RequiredAttribute
{
AllowEmptyStrings = allowEmptyStrings
}, errorMessage);
argument.Validators.Add(new AttributeValidator(attribute));
return argument;
}
Expand Down Expand Up @@ -247,7 +251,7 @@ public static IValidationBuilder Values(this IValidationBuilder builder, bool ig
/// <returns>The builder.</returns>
public static IValidationBuilder Values(this IValidationBuilder builder, StringComparison comparer, params string[] allowedValues)
{
return builder.Satisfies<AllowedValuesAttribute>(ctorArgs: new object[] { comparer, allowedValues });
return builder.Satisfies(new AllowedValuesAttribute(comparer, allowedValues));
}

/// <summary>
Expand All @@ -257,7 +261,7 @@ public static IValidationBuilder Values(this IValidationBuilder builder, StringC
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder EmailAddress(this IValidationBuilder builder, string? errorMessage = null)
=> builder.Satisfies<EmailAddressAttribute>(errorMessage);
=> builder.Satisfies(new EmailAddressAttribute(), errorMessage);

/// <summary>
/// Specifies that values must be a path to a file that already exists.
Expand All @@ -266,7 +270,7 @@ public static IValidationBuilder EmailAddress(this IValidationBuilder builder, s
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder ExistingFile(this IValidationBuilder builder, string? errorMessage = null)
=> builder.Satisfies<FileExistsAttribute>(errorMessage);
=> builder.Satisfies(new FileExistsAttribute(), errorMessage);

/// <summary>
/// Specifies that values must be a path to a file that does not already exist.
Expand All @@ -275,7 +279,7 @@ public static IValidationBuilder ExistingFile(this IValidationBuilder builder, s
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder NonExistingFile(this IValidationBuilder builder, string? errorMessage = null)
=> builder.Satisfies<FileNotExistsAttribute>(errorMessage);
=> builder.Satisfies(new FileNotExistsAttribute(), errorMessage);

/// <summary>
/// Specifies that values must be a path to a directory that already exists.
Expand All @@ -284,7 +288,7 @@ public static IValidationBuilder NonExistingFile(this IValidationBuilder builder
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder ExistingDirectory(this IValidationBuilder builder, string? errorMessage = null)
=> builder.Satisfies<DirectoryExistsAttribute>(errorMessage);
=> builder.Satisfies(new DirectoryExistsAttribute(), errorMessage);

/// <summary>
/// Specifies that values must be a path to a directory that does not already exist.
Expand All @@ -293,7 +297,7 @@ public static IValidationBuilder ExistingDirectory(this IValidationBuilder build
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder NonExistingDirectory(this IValidationBuilder builder, string? errorMessage = null)
=> builder.Satisfies<DirectoryNotExistsAttribute>(errorMessage);
=> builder.Satisfies(new DirectoryNotExistsAttribute(), errorMessage);

/// <summary>
/// Specifies that values must be a valid file path or directory, and the file path must already exist.
Expand All @@ -302,7 +306,7 @@ public static IValidationBuilder NonExistingDirectory(this IValidationBuilder bu
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder ExistingFileOrDirectory(this IValidationBuilder builder, string? errorMessage = null)
=> builder.Satisfies<FileOrDirectoryExistsAttribute>(errorMessage);
=> builder.Satisfies(new FileOrDirectoryExistsAttribute(), errorMessage);

/// <summary>
/// Specifies that values must be a valid file path or directory, and the file path must not already exist.
Expand All @@ -311,7 +315,7 @@ public static IValidationBuilder ExistingFileOrDirectory(this IValidationBuilder
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder NonExistingFileOrDirectory(this IValidationBuilder builder, string? errorMessage = null)
=> builder.Satisfies<FileOrDirectoryNotExistsAttribute>(errorMessage);
=> builder.Satisfies(new FileOrDirectoryNotExistsAttribute(), errorMessage);

/// <summary>
/// Specifies that values must be legal file paths.
Expand All @@ -320,7 +324,7 @@ public static IValidationBuilder NonExistingFileOrDirectory(this IValidationBuil
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder LegalFilePath(this IValidationBuilder builder, string? errorMessage = null)
=> builder.Satisfies<LegalFilePathAttribute>(errorMessage);
=> builder.Satisfies(new LegalFilePathAttribute(), errorMessage);

/// <summary>
/// Specifies that values must be a string at least <paramref name="length"/> characters long.
Expand All @@ -330,7 +334,7 @@ public static IValidationBuilder LegalFilePath(this IValidationBuilder builder,
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder MinLength(this IValidationBuilder builder, int length, string? errorMessage = null)
=> builder.Satisfies<MinLengthAttribute>(errorMessage, length);
=> builder.Satisfies(new MinLengthAttribute(length), errorMessage);

/// <summary>
/// Specifies that values must be a string no more than <paramref name="length"/> characters long.
Expand All @@ -340,7 +344,7 @@ public static IValidationBuilder MinLength(this IValidationBuilder builder, int
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder MaxLength(this IValidationBuilder builder, int length, string? errorMessage = null)
=> builder.Satisfies<MaxLengthAttribute>(errorMessage, length);
=> builder.Satisfies(new MaxLengthAttribute(length), errorMessage);

/// <summary>
/// Specifies that values must match a regular expression.
Expand All @@ -350,7 +354,7 @@ public static IValidationBuilder MaxLength(this IValidationBuilder builder, int
/// <param name="errorMessage">A custom error message to display.</param>
/// <returns>The builder.</returns>
public static IValidationBuilder RegularExpression(this IValidationBuilder builder, string pattern, string? errorMessage = null)
=> builder.Satisfies<RegularExpressionAttribute>(errorMessage, pattern);
=> builder.Satisfies(new RegularExpressionAttribute(pattern), errorMessage);

/// <summary>
/// Specifies that values must satisfy the requirements of the validation attribute of type <typeparamref name="TAttribute"/>.
Expand Down Expand Up @@ -380,7 +384,7 @@ public static IValidationBuilder Satisfies<TAttribute>(this IValidationBuilder b
public static IValidationBuilder<int> Range(this IValidationBuilder<int> builder, int minimum, int maximum, string? errorMessage = null)
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
{
var attribute = GetValidationAttr<RangeAttribute>(errorMessage, new object[] { minimum, maximum });
var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage);
builder.Use(new AttributeValidator(attribute));
return builder;
}
Expand All @@ -397,7 +401,7 @@ public static IValidationBuilder<int> Range(this IValidationBuilder<int> builder
public static IValidationBuilder<double> Range(this IValidationBuilder<double> builder, double minimum, double maximum, string? errorMessage = null)
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
{
var attribute = GetValidationAttr<RangeAttribute>(errorMessage, new object[] { minimum, maximum });
var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage);
builder.Use(new AttributeValidator(attribute));
return builder;
}
Expand Down Expand Up @@ -438,6 +442,16 @@ public static CommandOption OnValidate(this CommandOption option, Func<Validatio
return option;
}

private static IValidationBuilder Satisfies(this IValidationBuilder builder, ValidationAttribute attribute, string? errorMessage = null)
{
if (errorMessage is not null)
{
attribute.ErrorMessage = errorMessage;
}
builder.Use(new AttributeValidator(attribute));
return builder;
}

private static T GetValidationAttr<T>(string? errorMessage, object[]? ctorArgs = null)
where T : ValidationAttribute
{
Expand All @@ -448,5 +462,15 @@ private static T GetValidationAttr<T>(string? errorMessage, object[]? ctorArgs =
}
return attribute;
}

private static T AddErrorMessage<T>(T attribute, string? errorMessage)
where T : ValidationAttribute
{
if (errorMessage != null)
{
attribute.ErrorMessage = errorMessage;
}
return attribute;
}
}
}
19 changes: 18 additions & 1 deletion test/CommandLineUtils.Tests/AttributeValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<ModeValidationAttribute>("With an error message from model validation");
}
}

Expand All @@ -132,6 +135,9 @@ private class OptionApp
[Option, RegularExpression("^abc.*")]
public string? Regex { get; }

[Option, ModeValidation]
public string? Mode { get; }

private void OnExecute() { }
}

Expand All @@ -149,6 +155,8 @@ private class OptionApp
[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<OptionApp>(new TestConsole(_output), args));
Expand Down Expand Up @@ -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");
}
}
}
}

0 comments on commit 33b4ece

Please sign in to comment.