Skip to content

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

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.

DemoError.cs
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.

DemoError.cs
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.

Aventus.cs
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

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.