using Microsoft.Extensions.Logging; using Speckle.Connectors.DUI.Bindings; using Speckle.InterfaceGenerator; using Speckle.Sdk; using Speckle.Sdk.Models.Extensions; namespace Speckle.Connectors.DUI.Bridge; /// /// The functions provided by this class are designed to be used in all "top level" scenarios (e.g. Plugin, UI, and Event callbacks) /// To provide "last ditch effort" handling of unexpected exceptions that have not been handled. /// 1. Log events to the injected /// 2. Display a toast notification with exception details ///
///
/// /// exceptions cannot be recovered from. /// They will be rethrown to allow the host app to run its handlers
/// Depending on the host app, this may trigger windows event logging, and recovery snapshots before ultimately terminating the process
/// Attempting to swallow them may lead to data corruption, deadlocking, or things worse than a managed host app crash. ///
[GenerateAutoInterface] public sealed class TopLevelExceptionHandler : ITopLevelExceptionHandler { private readonly ILogger _logger; public IBridge Parent { get; } public string Name => nameof(TopLevelExceptionHandler); private const string UNHANDLED_LOGGER_TEMPLATE = "An unhandled Exception occured"; internal TopLevelExceptionHandler(ILogger logger, IBridge bridge) { _logger = logger; Parent = bridge; } /// /// Invokes the given within a / block, /// and provides exception handling for unexpected exceptions that have not been handled.
///
/// The function to invoke and provide error handling for /// will be rethrown, these should be allowed to bubble up to the host app /// public Result CatchUnhandled(Action function) { try { try { function.Invoke(); return new(); } catch (Exception ex) when (!ex.IsFatal()) { _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); SetGlobalNotification( ToastNotificationType.DANGER, "Unhandled Exception Occured", ex.ToFormattedString(), false ); return new(ex); } } catch (Exception ex) { _logger.LogCritical(ex, UNHANDLED_LOGGER_TEMPLATE); throw; } } /// /// return type /// A result pattern struct (where exceptions have been handled) public Result CatchUnhandled(Func function) => CatchUnhandled(() => Task.FromResult(function.Invoke())).Result; /// public async Task> CatchUnhandled(Func> function) { try { try { return new(await function.Invoke().ConfigureAwait(false)); } catch (Exception ex) when (!ex.IsFatal()) { _logger.LogError(ex, UNHANDLED_LOGGER_TEMPLATE); SetGlobalNotification( ToastNotificationType.DANGER, "Unhandled Exception Occured", ex.ToFormattedString(), false ); return new(ex); } } catch (Exception ex) { _logger.LogCritical(ex, UNHANDLED_LOGGER_TEMPLATE); throw; } } private void SetGlobalNotification(ToastNotificationType type, string title, string message, bool autoClose) => Parent.Send( BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, //TODO: We could move these constants into a DUI3 constants static class new { type, title, description = message, autoClose } ); }