Error Handling
In AventusSharp, error handling is managed through custom error classes based on enums. There are two primary cases for handling errors:
- If you want to handle errors but the function does not return a value, use the
VoidWithError
class. - If the function returns a value along with error handling, use
ResultWithError<T>
.
These classes can be used directly, but they require error codes in an integer format, which can become cumbersome in larger projects. To improve usability and maintainability, it’s recommended to create custom error classes that extend VoidWithError
and ResultWithError
, utilizing a custom enum.
In the example below, we’ll define custom errors for a Demo
application. First, create a file called DemoError.cs
. At the top of this file, we define an enum that contains our list of potential errors, starting with one to prevent the application from starting.
Example: DemoError.cs
using System.Runtime.CompilerServices;using AventusSharp.Tools;
namespace Demo{ public enum DemoErrorCode { ManualFailed }}
Defining the Error Class
Next, we create a container class, DemoError
, to define specific errors. This class extends GenericError
, allowing us to manage errors in the DemoErrorCode
enum.
using System.Runtime.CompilerServices;using AventusSharp.Tools;
namespace Demo{ public enum DemoErrorCode { ManualFailed }
// Defines an error class to manage DemoErrorCode errors public class DemoError : GenericError<DemoErrorCode> { public DemoError(DemoErrorCode code, string message, [CallerFilePath] string callerPath = "", [CallerLineNumber] int callerNo = 0) : base(code, message, callerPath, callerNo) { }
public DemoError(DemoErrorCode code, Exception exception, [CallerFilePath] string callerPath = "", [CallerLineNumber] int callerNo = 0) : base(code, exception, callerPath, callerNo) { } }}
Defining ResultWithError
and VoidWithError
Finally, we define VoidWithDemoError
and ResultWithDemoError<T>
, which are custom containers to handle errors in our application.
using System.Runtime.CompilerServices;using AventusSharp.Tools;
namespace Demo{ public enum DemoErrorCode { ManualFailed }
public class DemoError : GenericError<DemoErrorCode> { public DemoError(DemoErrorCode code, string message, [CallerFilePath] string callerPath = "", [CallerLineNumber] int callerNo = 0) : base(code, message, callerPath, callerNo) { }
public DemoError(DemoErrorCode code, Exception exception, [CallerFilePath] string callerPath = "", [CallerLineNumber] int callerNo = 0) : base(code, exception, callerPath, callerNo) { } }
// Error container for DemoError without a return type public class VoidWithDemoError : VoidWithError<DemoError> { }
// Error container for DemoError with a return type T public class ResultWithDemoError<T> : ResultWithError<T, DemoError> { }}
Creating an Error to Prevent Startup
Now, let’s create an error in Aventus.cs
to prevent the program from starting.
using AventusSharp.Tools;
namespace Demo{ public static class Aventus { // First approach: Assigning a custom error to a generic void result public static VoidWithError Init(WebApplication app) { VoidWithError initResult = new(); initResult.Errors.Add(new DemoError(DemoErrorCode.ManualFailed, "The program won't start")); return initResult; }
// Second approach: Using a typed result and converting to generic at the end of the function with ToGeneric() public static VoidWithError Init2(WebApplication app) { VoidWithDemoError initResult = new(); initResult.Errors.Add(new DemoError(DemoErrorCode.ManualFailed, "The program won't start")); return initResult.ToGeneric(); } }}
You can use either approach depending on your specific requirements.
Using Chaining
When handling errors, it’s common to call multiple functions, each of which may return an error. VoidWithError
and ResultWithError
offer a chaining method (Run
) that simplifies error management and improves code readability.
For advanced users, these error classes use a monadic pattern, with Run
taking a function that returns errors and automatically assigning those errors. If you’re using ResultWithError
and the return type matches, the result value is automatically assigned.
Example: Example.cs
using AventusSharp.Tools;
namespace Demo{ public class Example { public ResultWithError<int> DoCalc() { ResultWithError<int> result = new ResultWithError<int>(); int a = 5; int b = 3;
result .Run(DoNothing) // Executes without error .Run(() => Sum(a, b)) // Injects custom parameters; result.Result is set if successful .Run(AddError) // Adds an error to the result .Run(ThrowError); // Not executed because an error was added earlier
return result; }
private VoidWithError DoNothing() { return new VoidWithError(); }
private ResultWithError<int> Sum(int a, int b) { ResultWithError<int> result = new ResultWithError<int>(); result.Result = a + b; return result; }
private VoidWithError AddError() { VoidWithError result = new VoidWithError(); result.Errors.Add(new GenericError(400, "Failed")); return result; }
private VoidWithError ThrowError() { throw new Exception("Never reached"); } }}
This demonstrates how you can effectively use chaining to handle multiple functions that may each produce errors. The chaining feature automatically propagates errors, halting subsequent calls when an error occurs.