Skip to content

Commit 33b4ece

Browse files
authored
refactor: use explicit constructors on validation attributes to workaround .NET 6 runtime trimming error (#491)
1 parent 3352423 commit 33b4ece

File tree

2 files changed

+60
-19
lines changed

2 files changed

+60
-19
lines changed

src/CommandLineUtils/Validation/ValidationExtensions.cs

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ public static class ValidationExtensions
2323
public static CommandOption IsRequired(this CommandOption option, bool allowEmptyStrings = false, string? errorMessage = null)
2424
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
2525
{
26-
var attribute = GetValidationAttr<RequiredAttribute>(errorMessage);
27-
attribute.AllowEmptyStrings = allowEmptyStrings;
26+
var attribute = AddErrorMessage(new RequiredAttribute
27+
{
28+
AllowEmptyStrings = allowEmptyStrings
29+
}, errorMessage);
2830
option.Validators.Add(new AttributeValidator(attribute));
2931
return option;
3032
}
@@ -57,8 +59,10 @@ public static CommandOption<T> IsRequired<T>(this CommandOption<T> option, bool
5759
public static CommandArgument IsRequired(this CommandArgument argument, bool allowEmptyStrings = false, string? errorMessage = null)
5860
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
5961
{
60-
var attribute = GetValidationAttr<RequiredAttribute>(errorMessage);
61-
attribute.AllowEmptyStrings = allowEmptyStrings;
62+
var attribute = AddErrorMessage(new RequiredAttribute
63+
{
64+
AllowEmptyStrings = allowEmptyStrings
65+
}, errorMessage);
6266
argument.Validators.Add(new AttributeValidator(attribute));
6367
return argument;
6468
}
@@ -247,7 +251,7 @@ public static IValidationBuilder Values(this IValidationBuilder builder, bool ig
247251
/// <returns>The builder.</returns>
248252
public static IValidationBuilder Values(this IValidationBuilder builder, StringComparison comparer, params string[] allowedValues)
249253
{
250-
return builder.Satisfies<AllowedValuesAttribute>(ctorArgs: new object[] { comparer, allowedValues });
254+
return builder.Satisfies(new AllowedValuesAttribute(comparer, allowedValues));
251255
}
252256

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

262266
/// <summary>
263267
/// 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
266270
/// <param name="errorMessage">A custom error message to display.</param>
267271
/// <returns>The builder.</returns>
268272
public static IValidationBuilder ExistingFile(this IValidationBuilder builder, string? errorMessage = null)
269-
=> builder.Satisfies<FileExistsAttribute>(errorMessage);
273+
=> builder.Satisfies(new FileExistsAttribute(), errorMessage);
270274

271275
/// <summary>
272276
/// 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
275279
/// <param name="errorMessage">A custom error message to display.</param>
276280
/// <returns>The builder.</returns>
277281
public static IValidationBuilder NonExistingFile(this IValidationBuilder builder, string? errorMessage = null)
278-
=> builder.Satisfies<FileNotExistsAttribute>(errorMessage);
282+
=> builder.Satisfies(new FileNotExistsAttribute(), errorMessage);
279283

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

289293
/// <summary>
290294
/// 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
293297
/// <param name="errorMessage">A custom error message to display.</param>
294298
/// <returns>The builder.</returns>
295299
public static IValidationBuilder NonExistingDirectory(this IValidationBuilder builder, string? errorMessage = null)
296-
=> builder.Satisfies<DirectoryNotExistsAttribute>(errorMessage);
300+
=> builder.Satisfies(new DirectoryNotExistsAttribute(), errorMessage);
297301

298302
/// <summary>
299303
/// 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
302306
/// <param name="errorMessage">A custom error message to display.</param>
303307
/// <returns>The builder.</returns>
304308
public static IValidationBuilder ExistingFileOrDirectory(this IValidationBuilder builder, string? errorMessage = null)
305-
=> builder.Satisfies<FileOrDirectoryExistsAttribute>(errorMessage);
309+
=> builder.Satisfies(new FileOrDirectoryExistsAttribute(), errorMessage);
306310

307311
/// <summary>
308312
/// 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
311315
/// <param name="errorMessage">A custom error message to display.</param>
312316
/// <returns>The builder.</returns>
313317
public static IValidationBuilder NonExistingFileOrDirectory(this IValidationBuilder builder, string? errorMessage = null)
314-
=> builder.Satisfies<FileOrDirectoryNotExistsAttribute>(errorMessage);
318+
=> builder.Satisfies(new FileOrDirectoryNotExistsAttribute(), errorMessage);
315319

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

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

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

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

355359
/// <summary>
356360
/// Specifies that values must satisfy the requirements of the validation attribute of type <typeparamref name="TAttribute"/>.
@@ -380,7 +384,7 @@ public static IValidationBuilder Satisfies<TAttribute>(this IValidationBuilder b
380384
public static IValidationBuilder<int> Range(this IValidationBuilder<int> builder, int minimum, int maximum, string? errorMessage = null)
381385
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
382386
{
383-
var attribute = GetValidationAttr<RangeAttribute>(errorMessage, new object[] { minimum, maximum });
387+
var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage);
384388
builder.Use(new AttributeValidator(attribute));
385389
return builder;
386390
}
@@ -397,7 +401,7 @@ public static IValidationBuilder<int> Range(this IValidationBuilder<int> builder
397401
public static IValidationBuilder<double> Range(this IValidationBuilder<double> builder, double minimum, double maximum, string? errorMessage = null)
398402
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
399403
{
400-
var attribute = GetValidationAttr<RangeAttribute>(errorMessage, new object[] { minimum, maximum });
404+
var attribute = AddErrorMessage(new RangeAttribute(minimum, maximum), errorMessage);
401405
builder.Use(new AttributeValidator(attribute));
402406
return builder;
403407
}
@@ -438,6 +442,16 @@ public static CommandOption OnValidate(this CommandOption option, Func<Validatio
438442
return option;
439443
}
440444

445+
private static IValidationBuilder Satisfies(this IValidationBuilder builder, ValidationAttribute attribute, string? errorMessage = null)
446+
{
447+
if (errorMessage is not null)
448+
{
449+
attribute.ErrorMessage = errorMessage;
450+
}
451+
builder.Use(new AttributeValidator(attribute));
452+
return builder;
453+
}
454+
441455
private static T GetValidationAttr<T>(string? errorMessage, object[]? ctorArgs = null)
442456
where T : ValidationAttribute
443457
{
@@ -448,5 +462,15 @@ private static T GetValidationAttr<T>(string? errorMessage, object[]? ctorArgs =
448462
}
449463
return attribute;
450464
}
465+
466+
private static T AddErrorMessage<T>(T attribute, string? errorMessage)
467+
where T : ValidationAttribute
468+
{
469+
if (errorMessage != null)
470+
{
471+
attribute.ErrorMessage = errorMessage;
472+
}
473+
return attribute;
474+
}
451475
}
452476
}

test/CommandLineUtils.Tests/AttributeValidatorTests.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public OptionBuilderApp(TestConsole testConsole)
105105
: base(testConsole)
106106
{
107107
Option("-e|--email", "Email", CommandOptionType.SingleValue)
108-
.Accepts().EmailAddress();
108+
.Accepts().EmailAddress("Invalid Email");
109109

110110
Option("-n|--name", "Name", CommandOptionType.SingleValue)
111111
.Accepts().MinLength(1);
@@ -115,6 +115,9 @@ public OptionBuilderApp(TestConsole testConsole)
115115

116116
Option("-r|--regex", "Regex", CommandOptionType.SingleValue)
117117
.Accepts().RegularExpression("^abc.*");
118+
119+
Option("-m|--mode", "Mode", CommandOptionType.SingleValue)
120+
.Accepts().Satisfies<ModeValidationAttribute>("With an error message from model validation");
118121
}
119122
}
120123

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

138+
[Option, ModeValidation]
139+
public string? Mode { get; }
140+
135141
private void OnExecute() { }
136142
}
137143

@@ -149,6 +155,8 @@ private void OnExecute() { }
149155
[InlineData(new[] { "-a", "abcdefghijk" }, 1)]
150156
[InlineData(new[] { "-r", "abcdefghijk" }, 0)]
151157
[InlineData(new[] { "-r", "xyz" }, 1)]
158+
[InlineData(new[] { "-m", "xyz" }, 1)]
159+
[InlineData(new[] { "-m", "mode" }, 0)]
152160
public void ValidatesAttributesOnOption(string[] args, int exitCode)
153161
{
154162
Assert.Equal(exitCode, CommandLineApplication.Execute<OptionApp>(new TestConsole(_output), args));
@@ -179,5 +187,14 @@ public override bool IsValid(object value)
179187
&& app.Arg1 != null && app.Arg1.Contains("good")
180188
&& app.Arg2 != null && app.Arg2.Contains("good");
181189
}
190+
191+
[AttributeUsage(AttributeTargets.Property)]
192+
private sealed class ModeValidationAttribute : ValidationAttribute
193+
{
194+
public override bool IsValid(object value)
195+
{
196+
return value is string text && text.Contains("mode");
197+
}
198+
}
182199
}
183200
}

0 commit comments

Comments
 (0)