Errors as values, the Fusion mechanicsResult and EjError
No narration yet
Module 2 · Lesson 112 min

Result and EjError

Here is the single move that most separates Fusion code from slop. Domain validation never throws. It returns a Result<T>: either a success carrying a value, or a failure carrying a list of errors. The caller inspects the Result and decides what to do. No try/catch for expected, everyday validation failures.

An error is a small record. EjError has a Field (which part failed) and a Message (why). There are typed subtypes so a caller can branch on the KIND of failure, not on the message text.

From domain/Result.cs
public record EjError(string Field, string Message);

public sealed record EntityNotFound(string Field, string Message) : EjError(Field, Message);

public sealed record EjTimeout(string Field, string Message) : EjError(Field, Message);

Result<T> itself gives you IsSuccess / IsFailure, the Errors list, and a Value. Reading Value on a failed result throws, on purpose: it's a programming error to look at a value that isn't there, so it fails loudly rather than handing you a default. You construct results with the static Success and Failure factories.

The surface you use (domain/Result.cs)
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;

public T Value => IsSuccess
    ? _value!
    : throw new InvalidOperationException("Cannot access Value on a failed Result. Check IsSuccess first.");

public static Result<T> Success(T value) => new(value);
public static Result<T> Failure(params EjError[] errors) => /* ... */;
Practice

Try it yourself

Do

Read a Result correctly

Wire your fingers to check before you access. Sketch this (in a scratch file or on paper) against the real API above.

Tick every step to confirm you did it.

Recall

Type over message

This is a named anti-pattern; have it cold.

An endpoint needs to return 404 for a missing flight and 504 for a timeout. What does it branch on, and what must it never branch on?