Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Creating Domain Error Types

How a domain error should be implemented is not a trivial question. The implementation of IError has nothing to do with it, since domain errors are defined to be handled in the code itself without type erasure. Examples of errors implementations are presented here, but the "how" answer cannot be given.

C# Examples

// EnumWithContextIoErrorExample

public enum IoErrorKind
{
    FileNotFound,
    PermissionDenied,
}

public class IoError(IoErrorKind kind, string path) : IError
{
    public IoErrorKind Kind { get; } = kind;
    public string Path { get; } = path;

    public string Message
    {
        get
        {
            return Kind switch
            {
                IoErrorKind.FileNotFound => $"File '{Path}' not found",
                IoErrorKind.PermissionDenied => $"File '{Path}' can't be opened",
                _ => throw new UnreachableException()
            };
        }
    }

    public IError? InnerError => null;
}
// EnumOnlyIoErrorExample

public enum IoErrorKind
{
    FileNotFound,
    PermissionDenied,
}

public class IoError(IoErrorKind kind) : IError
{
    public IoErrorKind Kind { get; } = kind;

    public string Message
    {
        get
        {
            return Kind switch
            {
                IoErrorKind.FileNotFound => $"File not found",
                IoErrorKind.PermissionDenied => $"File can't be opened",
                _ => throw new UnreachableException()
            };
        }
    }

    public IError? InnerError => null;
}
// InheritanceIoErrorExample

public abstract class IoError : IError
{
    public abstract string Message { get; }
    public abstract IError? InnerError { get; }
}

public class FileNotFoundIoError() : IoError
{
    public override string Message => "File not found";
    public override IError? InnerError => null;
}

public class PermissionDeniedIoError() : IoError
{
    public override string Message => "Permission denied";
    public override IError? InnerError => null;
}

F# Examples

// EnumWithContextIoErrorExample =

[<RequireQualifiedAccess>]
type IoErrorKind = FileNotFound | PermissionDenied

type IoError =
    { Kind: IoErrorKind; Path: string }
    interface IError with
        member this.Message =
            match this.Kind with
            | IoErrorKind.FileNotFound -> $"File '{this.Path}' not found"
            | IoErrorKind.PermissionDenied -> $"File '{this.Path}' can't be opened"
        member this.InnerError = null
// EnumOnlyIoErrorExample =

[<RequireQualifiedAccess>]
type IoError =
    | FileNotFound
    | PermissionDenied
    interface IError with
        member this.Message =
            match this with
            | IoError.FileNotFound -> "File not found"
            | IoError.PermissionDenied -> "File can't be opened"
        member this.InnerError = null
// HierarchicalExample =

type SimpleError =
    | SimpleCase
    interface IError with
        member this.Message =
            match this with
            | SimpleCase -> "Some simple error case"
        member this.InnerError =
            null

type ComplexError =
    | Source of SimpleError
    | SomeError
    interface IError with
        member this.Message =
            match this with
            | Source _ -> "Error caused by simple error source"
            | SomeError -> "Some complex error case"
        member this.InnerError =
            match this with
            | Source simpleError -> simpleError
            | SomeError -> null

I recommend the following thoughts from the Rust ecosystem if the language barrier is not an issue for you. Error design in Rust