diff --git a/CliFx.Tests/ConversionSpecs.cs b/CliFx.Tests/ConversionSpecs.cs index b58c550..7e182bc 100644 --- a/CliFx.Tests/ConversionSpecs.cs +++ b/CliFx.Tests/ConversionSpecs.cs @@ -946,5 +946,48 @@ public class Command : ICommand exitCode.Should().NotBe(0); 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() + ); + + var stdErr = FakeConsole.ReadErrorString(); + + // Assert + exitCode.Should().NotBe(0); + stdErr.Should().Contain("Hello world"); + } } } \ No newline at end of file diff --git a/CliFx/CommandBinder.cs b/CliFx/CommandBinder.cs index e527868..e5868b6 100644 --- a/CliFx/CommandBinder.cs +++ b/CliFx/CommandBinder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using CliFx.Exceptions; using CliFx.Extensibility; using CliFx.Infrastructure; @@ -161,12 +162,18 @@ namespace CliFx } 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( $"{memberSchema.GetKind()} {memberSchema.GetFormattedIdentifier()} cannot be set from provided argument(s):" + Environment.NewLine + rawValues.Select(v => '<' + v + '>').JoinToString(" ") + Environment.NewLine + - $"Error: {ex.Message}", + $"Error: {errorMessage}", ex ); }