From bb89448efb0d7565f17eeee1d7a544acdec05f69 Mon Sep 17 00:00:00 2001 From: Tom van Dinther <39470469+tvandinther@users.noreply.github.com> Date: Thu, 17 Mar 2022 11:26:23 +1300 Subject: [PATCH] 0.1.4 (#3) * Fix typo * Add error message concatenation and formatting to facilitate best practice error chaining. --- Fallible.Tests/ErrorTests.cs | 37 +++++++++++++++++++++++++++++++ Fallible/Error.cs | 18 +++++++++++++++- Fallible/Fallible.csproj | 2 +- README.md | 42 +++++++++++++++++++++++++++++++++++- 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/Fallible.Tests/ErrorTests.cs b/Fallible.Tests/ErrorTests.cs index 2243484..7c733aa 100644 --- a/Fallible.Tests/ErrorTests.cs +++ b/Fallible.Tests/ErrorTests.cs @@ -141,4 +141,41 @@ public void ToString_ContainsStackTrace() } #endregion + + #region Message Tests + + [Fact] + public void Format_CorrectlyFormatsMessage() + { + const string expected = "test :test: test"; + var error = new Error("test"); + + error.Format("test :{0}: test", error.Message); + + Assert.Equal(expected, error.Message); + } + + [Fact] + public void AdditionOperator_CorrectlyPrependsString_WhenStringOnLHS() + { + const string expected = "test: appended"; + var error = new Error("test"); + + error += ": appended"; + + Assert.Equal(expected, error.Message); + } + + [Fact] + public void AdditionOperator_CorrectlyAppendsString_WhenStringOnRHS() + { + const string expected = "prepended: test"; + var error = new Error("test"); + + error = "prepended: " + error; + + Assert.Equal(expected, error.Message); + } + + #endregion } \ No newline at end of file diff --git a/Fallible/Error.cs b/Fallible/Error.cs index d194e73..4a16fa6 100644 --- a/Fallible/Error.cs +++ b/Fallible/Error.cs @@ -5,7 +5,7 @@ namespace Fallible; public class Error : IEquatable { - public readonly string Message; + public string Message { get; private set; } public readonly string StackTrace; private readonly string _callingFilePath; private readonly string _callingMemberName; @@ -22,7 +22,23 @@ public Error(string message, [CallerFilePath] string callingFilePath = "", } public static implicit operator bool(Error? error) => error is not default(Error); + public static Error operator +(string message, Error error) + { + error.Message = string.Concat(message, error.Message); + return error; + } + public static Error operator +(Error error, string message) + { + error.Message = string.Concat(error.Message, message); + return error; + } + + public void Format(string format, params object[] args) + { + Message = string.Format(format, args); + } + public bool Equals(Error? other) { if (ReferenceEquals(null, other)) return false; diff --git a/Fallible/Fallible.csproj b/Fallible/Fallible.csproj index 25d50b9..5d62831 100644 --- a/Fallible/Fallible.csproj +++ b/Fallible/Fallible.csproj @@ -5,7 +5,7 @@ enable enable true - 0.1.3 + 0.1.4 Fallible Tom van Dinther An idiomatic way to explicitly define, propagate and handle error states in C#. This library is inspired by Go's errors. diff --git a/README.md b/README.md index d9abdca..adf4cb5 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ public Fallible GetValue(int arg) #### Returning `void` -Fallible includes a `void` type that can be used to return *void* from a method. It does not have an accessible constructor and can only be created by using the `Fallible.Return` property. +Fallible includes a `Void` type that can be used to return *void* from a method. It does not have an accessible constructor and can only be created by using the `Fallible.Return` property. ```c# public Fallible DoSomething() @@ -133,6 +133,46 @@ For example, `DateTime.Parse` can throw two exceptions: `FormatException` and `A var (result, error) = Fallible.Try(() => DateTime.Parse("1/1/2019")); ``` +### Chaining error messages + +When dealing with an `Error` object, often you may want to pass the error up the call stack. As it is passed up the call stack, the level of abstraction is increased which can give increasing context to the error message. To facilitate this best practice, the `Error` object allows string concatenation with itself. + +```c# +Fallible GetUserFromDB(UserId id) +{ + if (!databaseIsConnected) return new Error("Database is not connected"); + ... +} + +Fallible FindUserById(UserId id) +{ + var (user, error) = GetUserFromDB(id); + if (error) return "Could not find user: " + error; + + return user; +} + +var (user, error) = FindUserById(id); +if (error) +{ + Console.WriteLine(error); // "Could not find user: Database is not connected" +} +``` + +Messages can also be appended by putting the string on the right hand side of the `+` operator. + +```c# +return error + ": Could not find user"; +``` + +#### Error message formatting + +To ensure that error messages are not accidentally overwritten, directly setting the `Error.Message` property is not possible. If appending or prepending from the error message is not suitable, you can use the `Error.Format` method to format the message more comprehensively. This method functions exactly like the `string.Format` method and uses that in its implementation. + +```c# +return error.Format("Could not find user: {0}: Aborting...", error.Message); +``` + ## Final Notes If you are using this library in your project, it does not mean that you can not use exceptions. Exceptions are still an effective way of quickly returning up the call stack when the application is in a serious erroneous state. This usage would be similar to `panic()` in Go. I hope you enjoy using this library and find it an enjoyable addition to the C# coding experience.