The F# + C# library for working with dynamic Error types.
Ferrum allows you to create errors based on the message, add context to errors, aggregate errors, and more. In many scenarios, using error-values can be more practical than using exceptions or errors as a simple string, which have awkward composition methods.
Open the Ferrum book to learn how to use Ferrum.
using Ferrum;
using Ferrum.Errors;
using Ferrum.ExceptionInterop;
using Ferrum.Formatting;
public static class ReadmeExample
{
public static IError? ReadFile(string path, out string content)
{
try
{
var text = File.ReadAllText(path);
content = text;
return null;
}
catch (Exception e)
{
content = string.Empty;
return e.ToError();
}
}
public static IError? CreateHello(out string hello)
{
var readError = ReadFile("name.txt", out var name);
if (readError is not null)
{
hello = string.Empty;
return new ContextError("Hello not created", readError);
}
hello = $"Hello, {name}!";
return null;
}
public static void ItIsMain(string[] args)
{
var helloStringError = CreateHello(out var helloString);
if (helloStringError is not null)
{
// Print:
// I can't say hello: Hello not created: Could not find file '.../name.txt'.
Console.WriteLine($"I can't say hello: {helloStringError.FormatS()}");
}
else
{
// Print:
// I say: Hello, <name>!
Console.WriteLine($"I say: {helloString}");
}
}
}
TODO
open System.IO
open Ferrum
open Ferrum.FSharp
module ReadmeExample =
let readFile (path: string) : Result<string, IError> =
try
Ok (File.ReadAllText(path))
with ex ->
Error (Error.ofException ex)
let createHello () : Result<string, IError> =
readFile "name.txt"
|> Result.context "Hello not created"
|> Result.map (fun name -> $"Hello, {name}!")
let itIsMain (args: string array) : int =
match createHello () with
| Error error ->
// Print:
// I can't say hello: Hello not created: Could not find file '.../name.txt'.
printfn $"I can't say hello: {Error.formatS error}"
| Ok helloString ->
// Print:
// I say: Hello, <name>!
printfn $"I say: {helloString}"
0
The concept of a dynamic error is useful for non-domain code that doesn't care about the details of errors.
However, interfaces that add some structure allow for unified post-processing that doesn't rely on domain details.
Ferrum's interfaces are oriented toward (but not limited to) a chained error hierarchy, which is most useful for adding high-level context on top of low-level context.
In terms of basic properties, Ferrum.IError
and System.Exception
are equivalent.
Message/Message, InnerError/InnerException, StackTrace/StackTrace.
The difference is in the "emphasis".
Errors are intended to be primarily transferable and composable values.
These same properties are useful for TError in Result<TValue, TError> types.
It all comes down to stylistic preferences.
Ferrum has no goals to cover the usability of error handling beyond the universal error type. dotNext, FsToolkit.ErrorHandling, and other .NET libraries do a great job with this.
In version 0.4, Ferrum is pretty experimental. Therefore, some components may be unstable. If we ignore the namespace and division into packages, then I would give the following stability rating:
The most stable components are IError
, MessageError
, ContextError
, IErrorFormatter
and related components.
Fairly stable components are IAggregateError
, AggregateError
,
ITracedError
, TracedMessageError
, TracedContextError
, TracedAggregateError
,
MessageErrorFormatter
, SummaryErrorFormatter
, DetailedErrorFormatter
, DiagnosticErrorFormatter
,
ExceptionErrors, ErrorExceptions, AnyError.OfValue
and related components.
The remaining components are the least stable.
Open an issue to discuss design elements, implementations, or use cases.
Starting with v0.4, the core components were rewritten in C# so that non-F# .NET libraries could use Ferrum without unnecessary dependencies. The main Ferrum package became a C# package. To use F# integrations, use the Ferrum.FSharp package.