Unwrap TargetInvocationException to provide more user-friendly errors when binding fails

This commit is contained in:
Tyrrrz
2021-07-17 21:32:15 +03:00
parent b1d01898b6
commit 84672c92f6
2 changed files with 51 additions and 1 deletions

View File

@@ -946,5 +946,48 @@ public class Command : ICommand
exitCode.Should().NotBe(0); exitCode.Should().NotBe(0);
stdErr.Should().Contain("Hello world"); stdErr.Should().Contain("Hello world");
} }
[Fact]
public async Task Parameter_or_option_value_conversion_fails_if_the_static_parse_method_throws()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
public class CustomType
{
public string Value { get; }
private CustomType(string value) => Value = value;
public static CustomType Parse(string value) => throw new Exception(""Hello world"");
}
[Command]
public class Command : ICommand
{
[CommandOption('f')]
public CustomType Foo { get; set; }
public ValueTask ExecuteAsync(IConsole console) => default;
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"-f", "bar"},
new Dictionary<string, string>()
);
var stdErr = FakeConsole.ReadErrorString();
// Assert
exitCode.Should().NotBe(0);
stdErr.Should().Contain("Hello world");
}
} }
} }

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection;
using CliFx.Exceptions; using CliFx.Exceptions;
using CliFx.Extensibility; using CliFx.Extensibility;
using CliFx.Infrastructure; using CliFx.Infrastructure;
@@ -161,12 +162,18 @@ namespace CliFx
} }
catch (Exception ex) when (ex is not CliFxException) // don't wrap CliFxException catch (Exception ex) when (ex is not CliFxException) // don't wrap CliFxException
{ {
// We use reflection-based invocation which can throw TargetInvocationException.
// Unwrap these exceptions to provide a more user-friendly error message.
var errorMessage = ex is TargetInvocationException invokeEx
? invokeEx.InnerException?.Message ?? invokeEx.Message
: ex.Message;
throw CliFxException.UserError( throw CliFxException.UserError(
$"{memberSchema.GetKind()} {memberSchema.GetFormattedIdentifier()} cannot be set from provided argument(s):" + $"{memberSchema.GetKind()} {memberSchema.GetFormattedIdentifier()} cannot be set from provided argument(s):" +
Environment.NewLine + Environment.NewLine +
rawValues.Select(v => '<' + v + '>').JoinToString(" ") + rawValues.Select(v => '<' + v + '>').JoinToString(" ") +
Environment.NewLine + Environment.NewLine +
$"Error: {ex.Message}", $"Error: {errorMessage}",
ex ex
); );
} }