Skip to content

Commit ce921c1

Browse files
rlvandaveernatemcmaster
authored andcommitted
Fix #211 - add support for class level validation attributes (#212)
1 parent ce715e8 commit ce921c1

File tree

6 files changed

+95
-4
lines changed

6 files changed

+95
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
## [Unreleased]
44

55
Enhancements:
6+
* Fix [#211] by [@rlvandaveer] - honor attributes on classes which implement ValidationAttribute
67

78
Bugs fixed:
89
* Fix [#207](https://github.com/natemcmaster/CommandLineUtils/issues/207) by [@jcaillon]: Option for the case sensitivity of command names
910

11+
[#211]: https://github.com/natemcmaster/CommandLineUtils/issues/211
12+
1013
## [v2.3.1]
1114

1215
Bugs fixed:
@@ -315,6 +318,7 @@ Other:
315318
[@liamdawson]: https://github.com/liamdawson
316319
[@lvermeulen]: https://github.com/lvermeulen
317320
[@MadbHatter]: https://github.com/MadbHatter
321+
[@rlvandaveer]: https://github.com/rlvandaveer
318322
[@rmcc13]: https://github.com/rmcc13
319323
[@SeanFeldman]: https://github.com/SeanFeldman
320324
[@sebastienros]: https://github.com/sebastienros

docs/samples/validation/attributes/Program.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
using System;
55
using System.ComponentModel.DataAnnotations;
6+
using System.Linq;
67
using McMaster.Extensions.CommandLineUtils;
78

9+
[MaxSizeOptionRequiresAttachmentValidation()]
810
class AttributeProgram
911
{
1012
public static int Main(string[] args) => CommandLineApplication.Execute<AttributeProgram>(args);
@@ -49,6 +51,8 @@ private void OnExecute()
4951
{
5052
Console.WriteLine("Max size = " + MaxSize.Value);
5153
}
54+
55+
Console.ReadKey();
5256
}
5357
}
5458

@@ -69,3 +73,19 @@ protected override ValidationResult IsValid(object value, ValidationContext cont
6973
return ValidationResult.Success;
7074
}
7175
}
76+
77+
[AttributeUsage(AttributeTargets.Class)]
78+
public class MaxSizeOptionRequiresAttachmentValidationAttribute : ValidationAttribute
79+
{
80+
protected override ValidationResult IsValid(object value, ValidationContext context)
81+
{
82+
if (value is AttributeProgram obj)
83+
{
84+
if (obj.MaxSize.HasValue && (obj.Attachments == null || obj.Attachments?.Length == 0))
85+
{
86+
return new ValidationResult("--max-size cannot be used unless --attachments is also specified");
87+
}
88+
}
89+
return ValidationResult.Success;
90+
}
91+
}

src/CommandLineUtils/Conventions/CommandAttributeConvention.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
// Copyright (c) Nate McMaster.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.ComponentModel.DataAnnotations;
46
using System.Reflection;
57
using McMaster.Extensions.CommandLineUtils.Abstractions;
8+
using McMaster.Extensions.CommandLineUtils.Validation;
69

710
namespace McMaster.Extensions.CommandLineUtils.Conventions
811
{
912
/// <summary>
10-
/// Adds settings from <see cref="CommandAttribute"/> set
11-
/// on the model type for <see cref="CommandLineApplication{TModel}"/>.
13+
/// Adds settings from <see cref="CommandAttribute" /> and <see cref="ValidationAttribute"/> set on the model type for <see cref="CommandLineApplication{TModel}" />.
1214
/// </summary>
15+
/// <seealso cref="McMaster.Extensions.CommandLineUtils.Conventions.IConvention" />
1316
public class CommandAttributeConvention : IConvention
1417
{
18+
/// <summary>
19+
/// Apply the convention.
20+
/// </summary>
21+
/// <param name="context">The context in which the convention is applied.</param>
1522
/// <inheritdoc />
1623
public virtual void Apply(ConventionContext context)
1724
{
@@ -30,6 +37,11 @@ public virtual void Apply(ConventionContext context)
3037
Apply(new ConventionContext(subcommand, subcommandAccessor.GetModelType()));
3138
}
3239
}
40+
41+
foreach (var attr in context.ModelType.GetTypeInfo().GetCustomAttributes<ValidationAttribute>())
42+
{
43+
context.Application.Validators.Add(new AttributeValidator(attr));
44+
}
3345
}
3446
}
3547
}

src/CommandLineUtils/Validation/AttributeValidator.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
using System;
55
using System.Collections.Generic;
66
using System.ComponentModel.DataAnnotations;
7+
using McMaster.Extensions.CommandLineUtils.Abstractions;
78

89
namespace McMaster.Extensions.CommandLineUtils.Validation
910
{
1011
/// <summary>
11-
/// A validator that uses a <see cref="ValidationAttribute"/> to validate a command line option or argument.
12+
/// A validator that uses a <see cref="ValidationAttribute"/> to validate a command, command line option, or argument.
1213
/// </summary>
13-
public class AttributeValidator : IValidator
14+
public class AttributeValidator : IValidator, ICommandValidator
1415
{
1516
private readonly ValidationAttribute _attribute;
1617

@@ -74,5 +75,16 @@ private ValidationResult GetValidationResult(List<string> values, ValidationCont
7475

7576
return ValidationResult.Success;
7677
}
78+
79+
/// <summary>Checks whether the command is valid using any associated validation attributes.</summary>
80+
/// <param name="command">The command line application to validate</param>
81+
/// <param name="context">The context under which validation should be performed</param>
82+
public ValidationResult GetValidationResult(CommandLineApplication command, ValidationContext context)
83+
{
84+
var model = (command as IModelAccessor)?.GetModel();
85+
return model != null
86+
? _attribute.GetValidationResult(model, context)
87+
: _attribute.GetValidationResult(command, context);
88+
}
7789
}
7890
}

src/CommandLineUtils/releasenotes.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<Project>
22
<PropertyGroup>
33
<PackageReleaseNotes Condition="'$(VersionPrefix)' == '2.3.2'">
4+
Enhancement:
5+
* @rlvandaveer: honor attributes on classes which implement ValidationAttribute
6+
47
Bugs fixed:
58
* @jcaillon: Option for the case sensitivity of command names.
69
</PackageReleaseNotes>

test/CommandLineUtils.Tests/AttributeValidatorTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ public void ItExecutesValidationAttribute(Type attributeType, string validValue,
5757
Assert.NotEmpty(result.ErrorMessage);
5858
}
5959

60+
[Theory]
61+
[InlineData(typeof(ClassLevelValidationAttribute), "good", "also good", "bad", "also bad")]
62+
public void ItExecutesClassLevelValidationAttribute(Type attributeType, string validProp1Value, string validProp2Value, string invalidProp1Value, string invalidProp2Value)
63+
{
64+
var attr = (ValidationAttribute)Activator.CreateInstance(attributeType);
65+
var app = new CommandLineApplication<ClassLevelValidationApp>();
66+
var validator = new AttributeValidator(attr);
67+
var factory = new CommandLineValidationContextFactory(app);
68+
var context = factory.Create(app);
69+
70+
app.Model.Arg1 = validProp1Value;
71+
app.Model.Arg2 = validProp2Value;
72+
73+
Assert.Equal(ValidationResult.Success, validator.GetValidationResult(app, context));
74+
75+
app.Model.Arg1 = invalidProp1Value;
76+
app.Model.Arg2 = invalidProp2Value;
77+
78+
var result = validator.GetValidationResult(app, context);
79+
Assert.NotNull(result);
80+
Assert.NotEmpty(result.ErrorMessage);
81+
}
82+
6083
private class EmailArgumentApp
6184
{
6285
[Argument(0), EmailAddress]
@@ -139,5 +162,22 @@ public override bool IsValid(object value)
139162
throw new InvalidOperationException();
140163
}
141164
}
165+
166+
private sealed class ClassLevelValidationApp
167+
{
168+
[Option]
169+
public string Arg1 { get; set; }
170+
[Option]
171+
public string Arg2 { get; set; }
172+
}
173+
174+
[AttributeUsage(AttributeTargets.Class)]
175+
private sealed class ClassLevelValidationAttribute : ValidationAttribute
176+
{
177+
public override bool IsValid(object value)
178+
=> value is ClassLevelValidationApp app
179+
&& app.Arg1.Contains("good")
180+
&& app.Arg2.Contains("good");
181+
}
142182
}
143183
}

0 commit comments

Comments
 (0)