Skip to content

Commit

Permalink
Added RolandK.AvaloniaExtensions.ExceptionHandling
Browse files Browse the repository at this point in the history
  • Loading branch information
RolandKoenig committed Mar 24, 2024
1 parent 423ee42 commit e316999
Show file tree
Hide file tree
Showing 20 changed files with 672 additions and 12 deletions.
6 changes: 6 additions & 0 deletions RolandK.AvaloniaExtensions.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RolandK.AvaloniaExtensions.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RolandK.AvaloniaExtensions.DependencyInjection.Tests", "src\RolandK.AvaloniaExtensions.DependencyInjection.Tests\RolandK.AvaloniaExtensions.DependencyInjection.Tests.csproj", "{03D69FBA-0153-4CF3-AC81-07E8CC0BB526}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RolandK.AvaloniaExtensions.ExceptionHandling", "src\RolandK.AvaloniaExtensions.ExceptionHandling\RolandK.AvaloniaExtensions.ExceptionHandling.csproj", "{FC6CF99C-571A-454F-B385-52F0DEC21BFA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -39,6 +41,10 @@ Global
{03D69FBA-0153-4CF3-AC81-07E8CC0BB526}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03D69FBA-0153-4CF3-AC81-07E8CC0BB526}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03D69FBA-0153-4CF3-AC81-07E8CC0BB526}.Release|Any CPU.Build.0 = Release|Any CPU
{FC6CF99C-571A-454F-B385-52F0DEC21BFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC6CF99C-571A-454F-B385-52F0DEC21BFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC6CF99C-571A-454F-B385-52F0DEC21BFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC6CF99C-571A-454F-B385-52F0DEC21BFA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<!-- Version -->
<PropertyGroup>
<VersionPrefix>2.0.4</VersionPrefix>
<VersionPrefix>2.1.0</VersionPrefix>
</PropertyGroup>

<!-- Nuget -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="Avalonia.Headless" Version="11.0.6" />
<PackageReference Include="Avalonia.Headless" Version="11.0.10" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.6" />
<PackageReference Include="Avalonia" Version="11.0.10" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;

public class AggregateExceptionAnalyzer : IExceptionAnalyzer
{
/// <inheritdoc />
public IEnumerable<ExceptionProperty>? ReadExceptionInfo(Exception ex)
{
return null;
}

/// <inheritdoc />
public IEnumerable<Exception>? GetInnerExceptions(Exception ex)
{
if (ex is AggregateException aggEx)
{
foreach (var actInnerException in aggEx.InnerExceptions)
{
yield return actInnerException;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;

namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;

public class ArgumentExceptionAnalyzer : IExceptionAnalyzer
{
/// <inheritdoc />
public IEnumerable<ExceptionProperty>? ReadExceptionInfo(Exception ex)
{
if (ex is not ArgumentException argumentException) { yield break; }

yield return new ExceptionProperty("ParamName", argumentException.ParamName ?? string.Empty);
}

/// <inheritdoc />
public IEnumerable<Exception>? GetInnerExceptions(Exception ex)
{
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;

public class DefaultExceptionAnalyzer : IExceptionAnalyzer
{
/// <inheritdoc />
public IEnumerable<ExceptionProperty>? ReadExceptionInfo(Exception ex)
{
yield return new ExceptionProperty("Type", ex.GetType().FullName ?? string.Empty);
yield return new ExceptionProperty("Message", ex.Message);
yield return new ExceptionProperty("HResult", ex.HResult.ToString());
yield return new ExceptionProperty("HelpLink", ex.HelpLink ?? string.Empty);
yield return new ExceptionProperty("Source", ex.Source ?? string.Empty);

if (ex.TargetSite != null)
{
var sourceMethod = ex.TargetSite;
yield return new ExceptionProperty("SourceMethod.Name", sourceMethod.Name);
yield return new ExceptionProperty("SourceMethod.IsStatic", sourceMethod.IsStatic.ToString());
yield return new ExceptionProperty(
"SourceMethod.Type",
sourceMethod.DeclaringType?.FullName ?? string.Empty);
}

yield return new ExceptionProperty("StackTrace", ex.StackTrace ?? string.Empty);
}

/// <inheritdoc />
public IEnumerable<Exception>? GetInnerExceptions(Exception ex)
{
if(ex.InnerException != null)
{
yield return ex.InnerException;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;

public class SystemIOExceptionAnalyzer : IExceptionAnalyzer
{
/// <inheritdoc />
public IEnumerable<ExceptionProperty>? ReadExceptionInfo(Exception ex)
{
switch (ex)
{
case FileLoadException fileLoadEx:
yield return new ExceptionProperty("FileName", fileLoadEx.FileName ?? string.Empty);
yield return new ExceptionProperty("FusionLog", fileLoadEx.FusionLog ?? string.Empty);
break;

case FileNotFoundException fileNotFoundEx:
yield return new ExceptionProperty("FileName", fileNotFoundEx.FileName ?? string.Empty);
yield return new ExceptionProperty("FusionLog", fileNotFoundEx.FusionLog ?? string.Empty);
break;

case DirectoryNotFoundException:
break;
}
}

/// <inheritdoc />
public IEnumerable<Exception>? GetInnerExceptions(Exception ex)
{
return null;
}
}
112 changes: 112 additions & 0 deletions src/RolandK.AvaloniaExtensions.ExceptionHandling/Data/ExceptionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RolandK.AvaloniaExtensions.ExceptionHandling.Data.Analyzers;

namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data;

public class ExceptionInfo
{
/// <summary>
/// Gets a collection containing all child nodes.
/// </summary>
public List<ExceptionInfoNode> ChildNodes { get; set; } = new();

/// <summary>
/// Gets or sets the main message.
/// </summary>
public string MainMessage
{
get;
set;
} = string.Empty;

public string Description
{
get;
set;
} = string.Empty;

public ExceptionInfo()
{

}

/// <summary>
/// Initializes a new instance of the <see cref="ExceptionInfo"/> class.
/// </summary>
public ExceptionInfo(Exception ex, IEnumerable<IExceptionAnalyzer>? exceptionAnalyzers = null)
{
exceptionAnalyzers ??= CreateDefaultAnalyzers();

this.MainMessage = "Unexpected Error";
this.Description = ex.Message;

// Analyze the given exception
ExceptionInfoNode newNode = new(ex);
this.ChildNodes.Add(newNode);

AnalyzeException(ex, newNode, exceptionAnalyzers);
}

public static IEnumerable<IExceptionAnalyzer> CreateDefaultAnalyzers()
{
yield return new DefaultExceptionAnalyzer();
yield return new SystemIOExceptionAnalyzer();
yield return new AggregateExceptionAnalyzer();
yield return new ArgumentExceptionAnalyzer();
}

/// <summary>
/// Analyzes the given exception.
/// </summary>
/// <param name="ex">The exception to be analyzed.</param>
/// <param name="targetNode">The target node where to put all data to.</param>
/// <param name="exceptionAnalyzers">All loaded analyzer objects.</param>
private static void AnalyzeException(Exception ex, ExceptionInfoNode targetNode, IEnumerable<IExceptionAnalyzer> exceptionAnalyzers)
{
// Query over all exception data
var analyzedInnerExceptions = new HashSet<Exception>(2);
foreach(IExceptionAnalyzer actAnalyzer in exceptionAnalyzers)
{
// Read all properties of the current exception
var exceptionInfos = actAnalyzer.ReadExceptionInfo(ex);
if (exceptionInfos != null)
{
foreach (ExceptionProperty actProperty in exceptionInfos)
{
if (string.IsNullOrEmpty(actProperty.Name)) { continue; }

ExceptionInfoNode propertyNode = new(actProperty);

targetNode.ChildNodes ??= new List<ExceptionInfoNode>();
targetNode.ChildNodes.Add(propertyNode);
}
}

// Read all inner exception information
var innerExceptions = actAnalyzer.GetInnerExceptions(ex);
if (innerExceptions == null) { continue; }

foreach (Exception actInnerException in innerExceptions)
{
if(analyzedInnerExceptions.Contains(actInnerException)){ continue; }
analyzedInnerExceptions.Add(actInnerException);

ExceptionInfoNode actInfoNode = new(actInnerException);
AnalyzeException(actInnerException, actInfoNode, exceptionAnalyzers);

targetNode.ChildNodes ??= new List<ExceptionInfoNode>();
targetNode.ChildNodes.Add(actInfoNode);
}
}

// Sort all generated nodes
if (targetNode.ChildNodes?.Count > 0)
{
targetNode.ChildNodes.Sort();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Reflection;
using System.Collections.Generic;

namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data;

public class ExceptionInfoNode : IComparable<ExceptionInfoNode>
{
/// <summary>
/// Gets a collection containing all child nodes.
/// </summary>
public List<ExceptionInfoNode>? ChildNodes { get; set; }

public bool IsExceptionNode { get; set; }

public string PropertyName { get; set; } = string.Empty;

public string PropertyValue { get; set; } = string.Empty;

public ExceptionInfoNode()
{

}

/// <summary>
/// Initializes a new instance of the <see cref="ExceptionInfoNode"/> class.
/// </summary>
public ExceptionInfoNode(Exception ex)
{
this.IsExceptionNode = true;
this.PropertyName = ex.GetType().GetTypeInfo().Name;
this.PropertyValue = ex.Message;
}

public ExceptionInfoNode(ExceptionProperty property)
{
this.PropertyName = property.Name;
this.PropertyValue = property.Value;
}

public int CompareTo(ExceptionInfoNode? other)
{
if (other == null) { return -1; }
if(this.IsExceptionNode != other.IsExceptionNode)
{
if (this.IsExceptionNode) { return 1; }
else { return -1; }
}

return 0;
}

public override string ToString()
{
return $"{this.PropertyName}: {this.PropertyValue}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data;

public class ExceptionProperty(string name, string value)
{
public string Name { get; } = name;
public string Value { get; } = value;

public override string ToString()
{
return $"{this.Name}: {this.Value}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RolandK.AvaloniaExtensions.ExceptionHandling.Data;

/// <summary>
/// This interface is used by the error-reporting framework.
/// It queries for all information provided by an exception which will be presented to
/// the user / developer.
/// </summary>
public interface IExceptionAnalyzer
{
/// <summary>
/// Reads all exception information from the given exception object.
/// </summary>
/// <param name="ex">The exception to be analyzed.</param>
IEnumerable<ExceptionProperty>? ReadExceptionInfo(Exception ex);

/// <summary>
/// Gets all inner exceptions provided by the given exception object.
/// </summary>
/// <param name="ex">The exception to be analyzed.</param>
IEnumerable<Exception>? GetInnerExceptions(Exception ex);
}
Loading

0 comments on commit e316999

Please sign in to comment.