Skip to content

Commit 63cfe1d

Browse files
committed
Bind to the target type to find OnValidationError
1 parent ea2f40c commit 63cfe1d

File tree

5 files changed

+59
-11
lines changed

5 files changed

+59
-11
lines changed

src/CommandLineUtils/CommandLineApplication.Execute.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,16 @@ public static async Task<int> ExecuteAsync<TApp>(IConsole console, params string
123123

124124
private static int HandleValidationError<TApp>(IConsole console, BindContext bindResult)
125125
{
126-
var method = typeof(TApp).GetTypeInfo().GetMethod("OnValidationError", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
126+
const BindingFlags MethodFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
127+
128+
var method = bindResult.Target
129+
.GetType()
130+
.GetTypeInfo()
131+
.GetMethod("OnValidationError", MethodFlags);
132+
127133
if (method == null)
128134
{
129-
return bindResult.App.DefaultValidationErrorHandler(bindResult.ValidationResult);
135+
return bindResult.RootApp.DefaultValidationErrorHandler(bindResult.ValidationResult);
130136
}
131137

132138
var arguments = ReflectionHelper.BindParameters(method, console, bindResult);
@@ -147,20 +153,19 @@ private static int HandleValidationError<TApp>(IConsole console, BindContext bin
147153
}
148154

149155
var applicationBuilder = new ReflectionAppBuilder<TApp>();
150-
var bindResult = applicationBuilder.Bind(console, args).GetBottomContext();
151-
return bindResult;
156+
return applicationBuilder.Bind(console, args).GetBottomContext();
152157
}
153158

154159
private static bool IsShowingInfo(BindContext bindResult)
155160
{
156-
if (bindResult.App.IsShowingInformation)
161+
if (bindResult.RootApp.IsShowingInformation)
157162
{
158-
if (bindResult.App.OptionHelp?.HasValue() == true && bindResult.App.StopParsingAfterHelpOption)
163+
if (bindResult.RootApp.OptionHelp?.HasValue() == true && bindResult.RootApp.StopParsingAfterHelpOption)
159164
{
160165
return true;
161166
}
162167

163-
if (bindResult.App.OptionVersion?.HasValue() == true && bindResult.App.StopParsingAfterVersionOption)
168+
if (bindResult.RootApp.OptionVersion?.HasValue() == true && bindResult.RootApp.StopParsingAfterVersionOption)
164169
{
165170
return true;
166171
}

src/CommandLineUtils/Internal/BindContext.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,36 @@ namespace McMaster.Extensions.CommandLineUtils
77
{
88
internal class BindContext
99
{
10-
public CommandLineApplication App { get; set; }
10+
/// <summary>
11+
/// The top-level application generated by the app builder
12+
/// </summary>
13+
public CommandLineApplication RootApp { get; set; }
14+
15+
/// <summary>
16+
/// The command selected based on input args.
17+
/// </summary>
18+
public CommandLineApplication SelectedCommand { get; set; }
19+
20+
/// <summary>
21+
/// An instance of the object that matches <see cref="SelectedCommand" />.
22+
/// </summary>
1123
public object Target { get; set; }
24+
25+
/// <summary>
26+
/// The binding context for the object for the next subcommand, if SelectedCommand != App.
27+
/// </summary>
28+
/// <returns></returns>
1229
public BindContext Child { get; set; }
30+
31+
/// <summary>
32+
/// Validation result, if any.
33+
/// </summary>
1334
public ValidationResult ValidationResult { get; set; }
1435

36+
/// <summary>
37+
/// Get the binding context that matches <see cref="Target" />.
38+
/// </summary>
39+
/// <returns>The context.</returns>
1540
public BindContext GetBottomContext()
1641
{
1742
var retVal = this;

src/CommandLineUtils/Internal/ReflectionAppBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ private int OnExecute()
381381

382382
var ctx = new BindContext
383383
{
384-
App = App,
384+
RootApp = App,
385385
Target = new TTarget(),
386386
};
387387

src/CommandLineUtils/Internal/ReflectionHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static object[] BindParameters(MethodInfo method, IConsole console, BindC
4343

4444
if (typeof(CommandLineApplication).GetTypeInfo().IsAssignableFrom(methodParam.ParameterType))
4545
{
46-
arguments[i] = bindResult.App;
46+
arguments[i] = bindResult.RootApp;
4747
}
4848
else if (typeof(IConsole).GetTypeInfo().IsAssignableFrom(methodParam.ParameterType))
4949
{

test/CommandLineUtils.Tests/ValidationTests.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void ValidationHandlerInvoked()
3232
public void ValidationHandlerOfSubcommandIsInvoked()
3333
{
3434
var app = new CommandLineApplication();
35-
var sub = app.Command("sub", c => {});
35+
var sub = app.Command("sub", c => { });
3636
var called = false;
3737
sub.OnValidationError(_ => called = true);
3838
sub.Argument("t", "test").IsRequired();
@@ -182,6 +182,7 @@ public void ValidatesEmailOption(string[] args, int exitCode)
182182
Assert.Equal(exitCode, CommandLineApplication.Execute<EmailOptionApp>(args));
183183
}
184184

185+
[Subcommand("sub", typeof(ValidationErrorSubcommand))]
185186
private class ValidationErrorApp
186187
{
187188
[Required, Option]
@@ -192,12 +193,29 @@ private int OnValidationError()
192193
}
193194
}
194195

196+
private class ValidationErrorSubcommand
197+
{
198+
[Argument(0), Required]
199+
public string[] Args { get; }
200+
201+
private int OnValidationError()
202+
{
203+
return 49;
204+
}
205+
}
206+
195207
[Fact]
196208
public void OnValidationErrorIsInvokedOnError()
197209
{
198210
Assert.Equal(7, CommandLineApplication.Execute<ValidationErrorApp>(new TestConsole(_output)));
199211
}
200212

213+
[Fact]
214+
public void OnValidationErrorIsInvokedOnSubcommandError()
215+
{
216+
Assert.Equal(49, CommandLineApplication.Execute<ValidationErrorApp>(new TestConsole(_output), "sub"));
217+
}
218+
201219
private class ThrowOnExecuteApp
202220
{
203221
[Option, Required]

0 commit comments

Comments
 (0)