Skip to content

Commit

Permalink
Improve exception handling (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalSenn authored Jul 28, 2023
1 parent 5172364 commit 1482d03
Show file tree
Hide file tree
Showing 30 changed files with 247 additions and 193 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public static void Exception(
{
Template = message,
Arguments = arguments,
Verbosity = Verbosity.Quiet,
Verbosity = Verbosity.Detailed,
Style = Styles.Error,
Glyph = Glyph.Cross,
Exception = exception
Expand Down
14 changes: 6 additions & 8 deletions src/Confix.Tool/src/Confix.Tool/ConfigurationFile/JsonFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@

namespace Confix.Tool.Middlewares;

public sealed record JsonFile
(
FileInfo File,
JsonNode Content
)
public sealed record JsonFile(FileInfo File, JsonNode Content)
{
public static async ValueTask<JsonFile> FromFile(FileInfo file, CancellationToken cancellationToken)
public static async ValueTask<JsonFile> FromFile(
FileInfo file,
CancellationToken cancellationToken)
{
var content = await file.ReadAllText(cancellationToken);
var json = JsonNode.Parse(content);

if (json is null)
{
throw new ExitException($"File {file.FullName} has invalid content.");
throw ThrowHelper.CouldNotParseJsonFile(file);
}

return new(file, json);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public async Task InvokeAsync(IMiddlewareContext context, MiddlewareDelegate nex
context.SetStatus("Persisting configuration changes");

var configurationFeature = context.Features.Get<ConfigurationFileFeature>();
var encryptionConfigured = context.Features.TryGet<EncryptionFeature>(out var encryptionFeature);
var encryptionConfigured =
context.Features.TryGet<EncryptionFeature>(out var encryptionFeature);
bool encryptFile = context.Parameter.Get(EncryptionOption.Instance);

if (encryptFile && !encryptionConfigured)
Expand All @@ -36,11 +37,13 @@ public async Task InvokeAsync(IMiddlewareContext context, MiddlewareDelegate nex
if (encryptFile)
{
context.SetStatus("Encrypting configuration file");

await using MemoryStream memoryStream = new();
await file.Content.SerializeToStreamAsync(memoryStream, context.CancellationToken);
var encrypted = await encryptionFeature.EncryptionProvider.EncryptAsync(
memoryStream.ToArray(),
context.CancellationToken);

var encrypted = await encryptionFeature!.EncryptionProvider
.EncryptAsync(memoryStream.ToArray(), context.CancellationToken);

await stream.WriteAsync(encrypted, context.CancellationToken);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ public record MagicPathContext(
DirectoryInfo HomeDirectory,
DirectoryInfo? SolutionDirectory,
DirectoryInfo? ProjectDirectory,
DirectoryInfo FileDirectory
);
DirectoryInfo FileDirectory);

/// <summary>
/// Rewrites the magic paths in the configuration files.
Expand All @@ -22,16 +21,19 @@ protected override JsonNode Rewrite(JsonValue value, MagicPathContext context)
switch (value.GetSchemaValueType())
{
case SchemaValueType.String when MagicPath.From(value) is { } magicPath:
string replacedValue = magicPath.Replace(context);
App.Log.ReplacedMagicPath(magicPath, replacedValue);
return JsonValue.Create(replacedValue);
var replacedValue = magicPath.Replace(context);

App.Log.ReplacedMagicPath((string?) value, replacedValue);

return JsonValue.Create(replacedValue)!;

default:
return base.Rewrite(value!, context);
}
}
}

file enum MagicPathType
file enum Scope
{
Home,
Solution,
Expand All @@ -51,83 +53,84 @@ private static class Prefix
public const string FileWindows = ".\\";
}

private readonly MagicPathType _type;
private readonly Scope _type;
private readonly string _path;
private readonly string _original;

public MagicPath(MagicPathType type, string path, string original)
public MagicPath(Scope type, string path, string original)
{
_type = type;
_path = path;
_original = original;
}

public static MagicPath? From(JsonValue value)
public static MagicPath? From(JsonValue jsonValue)
{
if ((string?)value is { } stringValue)
if ((string?) jsonValue is not { } value)
{
return stringValue switch
{
string s when s.StartsWith(Prefix.Home)
=> new MagicPath(
MagicPathType.Home,
s.Remove(0, Prefix.Home.Length),
stringValue),
string s when s.StartsWith(Prefix.Tilde)
=> new MagicPath(
MagicPathType.Home,
s.Remove(0, Prefix.Tilde.Length),
stringValue),
string s when s.StartsWith(Prefix.Solution)
=> new MagicPath(
MagicPathType.Solution,
s.Remove(0, Prefix.Solution.Length),
stringValue),
string s when s.StartsWith(Prefix.Project)
=> new MagicPath(
MagicPathType.Project,
s.Remove(0, Prefix.Project.Length),
stringValue),
string s when s.StartsWith(Prefix.FileLinux)
=> new MagicPath(
MagicPathType.File,
s.Remove(0, Prefix.FileLinux.Length),
stringValue),
string s when s.StartsWith(Prefix.FileWindows)
=> new MagicPath(
MagicPathType.File,
s.Remove(0, Prefix.FileWindows.Length),
stringValue),
_ => null
};
return null;
}
return null;

return value switch
{
not null when value.StartsWith(Prefix.Home)
=> new MagicPath(Scope.Home, value.RemovePrefix(Prefix.Home), value),

not null when value.StartsWith(Prefix.Tilde)
=> new MagicPath(Scope.Home, value.RemovePrefix(Prefix.Tilde), value),

not null when value.StartsWith(Prefix.Solution)
=> new MagicPath(Scope.Solution, value.RemovePrefix(Prefix.Solution), value),

not null when value.StartsWith(Prefix.Project)
=> new MagicPath(Scope.Project, value.RemovePrefix(Prefix.Project), value),

not null when value.StartsWith(Prefix.FileLinux)
=> new MagicPath(Scope.File, value.RemovePrefix(Prefix.FileLinux), value),

not null when value.StartsWith(Prefix.FileWindows)
=> new MagicPath(Scope.File, value.RemovePrefix(Prefix.FileWindows), value),
_ => null
};
}

public string Replace(MagicPathContext context)
{
switch (_type)
{
case MagicPathType.Home:
case Scope.Home:
return Path.Combine(context.HomeDirectory.FullName, _path);
case MagicPathType.Solution when context.SolutionDirectory is not null:

case Scope.Solution when context.SolutionDirectory is not null:
return Path.Combine(context.SolutionDirectory.FullName, _path);
case MagicPathType.Project when context.ProjectDirectory is not null:

case Scope.Project when context.ProjectDirectory is not null:
return Path.Combine(context.ProjectDirectory.FullName, _path);
case MagicPathType.File:

case Scope.File:
return Path.Combine(context.FileDirectory.FullName, _path);

default:
App.Log.NotSupportedInContext(_type);
return _original;
}
}
}

file static class Extensions
{
public static string RemovePrefix(this string value, string prefix)
=> value.Remove(0, prefix.Length);
}

file static class LogExtensions
{
public static void NotSupportedInContext(this IConsoleLogger log, MagicPathType type)
=> log.Debug($"Magic path type {0} is not supported in this context.", type.ToString());
public static void NotSupportedInContext(this IConsoleLogger log, Scope type)
=> log.Debug("Magic path type {0} is not supported in this context.", type.ToString());

public static void ReplacedMagicPath(this IConsoleLogger log, MagicPath magicPath, string replacedValue)
=> log.Debug($"Replaced magic path {0} with {1}.", magicPath, replacedValue);
public static void ReplacedMagicPath(
this IConsoleLogger log,
string originalValue,
string replacedValue)
=> log.Debug("Replaced magic path {0} with {1}.", originalValue, replacedValue);
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,7 @@ private static async Task InvokeAsync(IMiddlewareContext context)
var environments = configFeature.Project?.Environments;
if (environments?.FirstOrDefault(x => x.Name == toEnvironment) is null)
{
throw new ExitException($"Environment '{toEnvironment}' does not exists.")
{
Help = $"Use [blue]confix environment set {toEnvironment}[/] to change it."
};
throw ThrowHelper.EnvironmentDoesNotExist(toEnvironment);
}

return variableFeature.CreateResolver(toEnvironment);
Expand All @@ -81,18 +78,12 @@ private static VariablePath Parse(
{
if (!VariablePath.TryParse(argumentValue, out var parsed))
{
throw new ExitException($"Invalid variable name: {argumentValue}")
{
Help = "Variable name must be like: [blue]$provider:some.path[/]"
};
throw ThrowHelper.InvalidVariableName(argumentValue);
}

if (!providers.Contains(parsed.Value.ProviderName))
{
throw new ExitException($"Invalid provider name: {parsed.Value.ProviderName}")
{
Help = $"Available providers: {string.Join(", ", providers)}"
};
throw ThrowHelper.InvalidProviderName(providers, parsed.Value.ProviderName);
}

return parsed.Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private static async Task InvokeAsync(IMiddlewareContext context)
}
else
{
context.Logger.InvalidVariableName(variableName);
throw ThrowHelper.InvalidVariableName(variableName);
}
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/Confix.Tool/src/Confix.Tool/ThrowHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Confix.Tool;

public static class ThrowHelper
{
public static Exception InvalidVariableName(string name)
=> new ExitException($"Invalid variable name: {name}")
{
Help = "Variable name must be like: [blue]$provider:some.path[/]"
};

public static Exception InvalidProviderName(
IEnumerable<string> providers,
string name)
=> throw new ExitException($"Invalid provider name: {name}")
{
Help = $"Available providers: {string.Join(", ", providers)}"
};

public static Exception EnvironmentDoesNotExist(string name)
=> new ExitException($"Environment '{name}' does not exists.")
{
Help = $"Use [blue]confix environment set {name}[/] to change it."
};

public static Exception VariableNotFound(string name) =>
throw new ExitException($"Variable not found: {name}")
{
Help = "Run [blue]confix variable list[/] to see all available variables."
};

public static Exception CouldNotParseJsonFile(FileInfo file)
=> throw new ExitException($"File {file.FullName} has invalid content.");
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ private static async Task ExceptionMiddleware(
{
await next(context);
}
catch (AggregateException exception) when (exception.InnerExceptions.Any(e => e is ExitException or ValidationException))
catch (AggregateException exception) when (
exception.InnerExceptions.Any(e => e is ExitException or ValidationException))
{
foreach (var innerException in exception.InnerExceptions)
{
Expand All @@ -36,8 +37,6 @@ private static async Task ExceptionMiddleware(
context.HandleException(exception);
}
}


}

file static class LogExtensions
Expand All @@ -52,21 +51,25 @@ public static void HandleException(this InvocationContext context, Exception exc
case OperationCanceledException or TaskCanceledException:
// ignored on purpose
return;

case ExitException exitException:
logger.ExitException(exitException);
console.PrintHelp(exitException);
break;

case ValidationException validationException:
logger.ValidationException(validationException);
console.PrintValidationError(validationException);
break;

default:
logger.UnhandledException(exception);
throw exception;
break;
}

context.ExitCode = ExitCodes.Error;
}

public static void ExitException(this IConsoleLogger logger, ExitException exception)
{
logger.Error("Confix failed.");
Expand All @@ -86,24 +89,30 @@ public static void PrintHelp(this IAnsiConsole console, ExitException exception)
}
}

public static void ValidationException(this IConsoleLogger logger, ValidationException exception)
public static void ValidationException(
this IConsoleLogger logger,
ValidationException exception)
{
logger.Error("Confix failed due to faulty configuration.");
logger.TraceException(exception);
}

public static void PrintValidationError(this IAnsiConsole console, ValidationException exception)
public static void PrintValidationError(
this IAnsiConsole console,
ValidationException exception)
{
var validationErrorTree = new Tree($"[red]{exception.Message}[/]");
foreach (var error in exception.Errors)
{
validationErrorTree.AddNode($"[red]{error}[/]");
}

console.Write(validationErrorTree);
}

public static void UnhandledException(this IConsoleLogger logger, Exception exception)
{
logger.Exception("Confix terminated unexpectedly.", exception);
logger.Error("Confix terminated unexpectedly. \n {0}", exception.Message);
logger.Exception("Exception: ", exception);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static string RelativeTo(this FileInfo fileInfo, DirectoryInfo directoryI
}

public static string ToLink(this FileSystemInfo fileInfo)
=> $"'{fileInfo.Name.EscapeMarkup().ToLink(fileInfo)}'";
=> $"'{fileInfo.FullName.EscapeMarkup().ToLink(fileInfo)}'";

public static string ToLink(this string str, FileSystemInfo fileInfo)
=> $"[link='{Uri.EscapeDataString(fileInfo.FullName)}']{str}[/]";
Expand Down
Loading

0 comments on commit 1482d03

Please sign in to comment.