Skip to content

Commit

Permalink
Added the ITypeResolver to the ExceptionHandler (#1411)
Browse files Browse the repository at this point in the history
  • Loading branch information
nils-a authored Jan 6, 2024
1 parent a94bc15 commit 544e6a9
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 40 deletions.
14 changes: 9 additions & 5 deletions docs/input/cli/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ Using the `SetExceptionHandler()` during configuration it is possible to handle
This method comes in two flavours: One that uses the default exitCode (or `return` value) of `-1` and one
where the exitCode needs to be supplied.

### Using `SetExceptionHandler(Func<Exception, int> handler)`
The `ITypeResolver?` parameter will be null, when the exception occurs while no `ITypeResolver` is available.
(Basically the `ITypeResolver` will be set, when the exception occurs during a command execution, but not
during the parsing phase and construction of the command.)

### Using `SetExceptionHandler(Func<Exception, ITypeResolver?, int> handler)`

Using this method exceptions can be handled in a custom way. The return value of the handler is used as
the exitCode for the application.
Expand All @@ -71,7 +75,7 @@ namespace MyApp

app.Configure(config =>
{
config.SetExceptionHandler(ex =>
config.SetExceptionHandler((ex, resolver) =>
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
return -99;
Expand All @@ -84,9 +88,9 @@ namespace MyApp
}
```

### Using `SetExceptionHandler(Action<Exception> handler)`
### Using `SetExceptionHandler(Action<Exception, ITypeResolver?> handler)`

Using this method exceptions can be handled in a custom way, much the same as with the `SetExceptionHandler(Func<Exception, int> handler)`.
Using this method exceptions can be handled in a custom way, much the same as with the `SetExceptionHandler(Func<Exception, ITypeResolver?, int> handler)`.
Using the `Action` as the handler however, it is not possible (or required) to supply a return value.
The exitCode for the application will be `-1`.

Expand All @@ -103,7 +107,7 @@ namespace MyApp

app.Configure(config =>
{
config.SetExceptionHandler(ex =>
config.SetExceptionHandler((ex, resolver) =>
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
});
Expand Down
2 changes: 1 addition & 1 deletion src/Spectre.Console.Cli/CommandApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public async Task<int> RunAsync(IEnumerable<string> args)

if (_configurator.Settings.ExceptionHandler != null)
{
return _configurator.Settings.ExceptionHandler(ex);
return _configurator.Settings.ExceptionHandler(ex, null);
}

// Render the exception.
Expand Down
8 changes: 4 additions & 4 deletions src/Spectre.Console.Cli/ConfiguratorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,11 @@ public static ICommandConfigurator AddAsyncDelegate<TSettings>(
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Action<Exception> exceptionHandler)
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Action<Exception, ITypeResolver?> exceptionHandler)
{
return configurator.SetExceptionHandler(ex =>
return configurator.SetExceptionHandler((ex, resolver) =>
{
exceptionHandler(ex);
exceptionHandler(ex, resolver);
return -1;
});
}
Expand All @@ -382,7 +382,7 @@ public static IConfigurator SetExceptionHandler(this IConfigurator configurator,
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Func<Exception, int>? exceptionHandler)
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Func<Exception, ITypeResolver?, int>? exceptionHandler)
{
if (configurator == null)
{
Expand Down
4 changes: 3 additions & 1 deletion src/Spectre.Console.Cli/ICommandAppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public interface ICommandAppSettings
/// <summary>
/// Gets or sets a handler for Exceptions.
/// <para>This handler will not be called, if <see cref="PropagateExceptions"/> is set to <c>true</c>.</para>
/// The <see cref="ITypeResolver"/> argument will only be not-null, when the exception occurs during execution of
/// a command. I.e. only when the resolver is available.
/// </summary>
public Func<Exception, int>? ExceptionHandler { get; set; }
public Func<Exception, ITypeResolver?, int>? ExceptionHandler { get; set; }
}
59 changes: 33 additions & 26 deletions src/Spectre.Console.Cli/Internal/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,37 +128,44 @@ private static async Task<int> Execute(
ITypeResolver resolver,
IConfiguration configuration)
{
// Bind the command tree against the settings.
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
var interceptors =
((IEnumerable<ICommandInterceptor>?)resolver.Resolve(typeof(IEnumerable<ICommandInterceptor>))
?? Array.Empty<ICommandInterceptor>()).ToList();
#pragma warning disable CS0618 // Type or member is obsolete
if (configuration.Settings.Interceptor != null)
try
{
interceptors.Add(configuration.Settings.Interceptor);
}
// Bind the command tree against the settings.
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
var interceptors =
((IEnumerable<ICommandInterceptor>?)resolver.Resolve(typeof(IEnumerable<ICommandInterceptor>))
?? Array.Empty<ICommandInterceptor>()).ToList();
#pragma warning disable CS0618 // Type or member is obsolete
if (configuration.Settings.Interceptor != null)
{
interceptors.Add(configuration.Settings.Interceptor);
}
#pragma warning restore CS0618 // Type or member is obsolete
foreach (var interceptor in interceptors)
{
interceptor.Intercept(context, settings);
}
foreach (var interceptor in interceptors)
{
interceptor.Intercept(context, settings);
}

// Create and validate the command.
var command = leaf.CreateCommand(resolver);
var validationResult = command.Validate(context, settings);
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}
// Create and validate the command.
var command = leaf.CreateCommand(resolver);
var validationResult = command.Validate(context, settings);
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}

// Execute the command.
var result = await command.Execute(context, settings);
foreach (var interceptor in interceptors)
// Execute the command.
var result = await command.Execute(context, settings);
foreach (var interceptor in interceptors)
{
interceptor.InterceptResult(context, settings, ref result);
}

return result;
}
catch (Exception ex) when (configuration.Settings is { ExceptionHandler: not null, PropagateExceptions: false })
{
interceptor.InterceptResult(context, settings, ref result);
return configuration.Settings.ExceptionHandler(ex, resolver);
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings
public ParsingMode ParsingMode =>
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;

public Func<Exception, int>? ExceptionHandler { get; set; }
public Func<Exception, ITypeResolver?, int>? ExceptionHandler { get; set; }

public CommandAppSettings(ITypeRegistrar registrar)
{
Expand Down
48 changes: 46 additions & 2 deletions test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void Should_Handle_Exceptions_If_ExceptionHandler_Is_Set_Using_Action()
app.Configure(config =>
{
config.AddCommand<ThrowingCommand>("throw");
config.SetExceptionHandler(_ =>
config.SetExceptionHandler((_, _) =>
{
exceptionHandled = true;
});
Expand All @@ -74,7 +74,7 @@ public void Should_Handle_Exceptions_If_ExceptionHandler_Is_Set_Using_Function()
app.Configure(config =>
{
config.AddCommand<ThrowingCommand>("throw");
config.SetExceptionHandler(_ =>
config.SetExceptionHandler((_, _) =>
{
exceptionHandled = true;
return -99;
Expand All @@ -88,5 +88,49 @@ public void Should_Handle_Exceptions_If_ExceptionHandler_Is_Set_Using_Function()
result.ExitCode.ShouldBe(-99);
exceptionHandled.ShouldBeTrue();
}

[Fact]
public void Should_Have_Resolver_Set_When_Exception_Occurs_In_Command_Execution()
{
// Given
ITypeResolver resolver = null;
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddCommand<ThrowingCommand>("throw");
config.SetExceptionHandler((_, r) =>
{
resolver = r;
});
});

// When
app.Run(new[] { "throw" });

// Then
resolver.ShouldNotBeNull();
}

[Fact]
public void Should_Not_Have_Resolver_Set_When_Exception_Occurs_Before_Command_Execution()
{
// Given
ITypeResolver resolver = null;
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddCommand<DogCommand>("Лайка");
config.SetExceptionHandler((_, r) =>
{
resolver = r;
});
});

// When
app.Run(new[] { "throw", "on", "arg", "parsing" });

// Then
resolver.ShouldBeNull();
}
}
}

0 comments on commit 544e6a9

Please sign in to comment.