Whetstone.
Errors as values, the Fusion mechanicsFailIf accumulates; Bind short-circuits
Module 2 · Lesson 213 min

FailIf accumulates; Bind short-circuits

Now the idiom you’ll write most. To validate, you chain guards with FailIf, then terminate with OrSuccess. The crucial, easy-to-miss detail: FailIf accumulates. It does not stop at the first failure. If three rules are broken, the Result comes back with all three errors, so the user fixes everything at once instead of playing whack-a-mole.

The guard chain (domain/flight-notes/FlightNoteVo.cs)
public static Result<FlightNoteVo> Create(string? ejdpUniqueFlightKey, string? body)
{
    var trimmedBody = (body ?? string.Empty).Trim();

    return Result<FlightNoteVo>
        .FailIf(string.IsNullOrWhiteSpace(ejdpUniqueFlightKey), MissingFlightKey)
        .FailIf(trimmedBody.Length > MaxBodyLength,
            new EjError(nameof(Body), $"A note body must be at most {MaxBodyLength} characters."))
        .OrSuccess(() => new FlightNoteVo(ejdpUniqueFlightKey!.Trim(), trimmedBody));
}

OrSuccess takes either a value or a factory Func<T>. Use the factory form (() => new ...) when building the value would be wrong or would throw if the guards had failed, since the factory only runs on the success path. Notice ejdpUniqueFlightKey! inside it: the null-forgiving ! is safe precisely because the FailIf above already proved it’s non-null on this path.

Sometimes you need the opposite: step B depends on step A’s value, so if A fails there’s nothing to feed B. That’s Bind. It short-circuits: on failure it returns the failure untouched; on success it runs the next step with the value. Use FailIf to gather independent rules, Bind to sequence dependent steps.

Bind sequences dependent steps (FlightDisruptionAggregate.cs)
DisruptionDelayVo.Create(isCancelled, delayMinutes)
    .Bind(delay => Eu261Vo.Create(isExtraordinary, delay, pax, routeClaimRate, weighting));
The Result combinators (domain/Result.cs)
FailIfAccumulate an independent rule; chainable, does NOT short-circuit
OrSuccessTerminal: build the value if no errors accrued
BindSequence a dependent step; short-circuits on failure
MapTransform the success value; passes failure through
TapRun a side-effect on success, return the Result unchanged
TapAsyncAsync Tap: await a side-effect (e.g. save) on success
Practice

Try it yourself

Do

Write a Create with an accumulating chain

Build the muscle: turn two rules into an accumulating factory. Model it on FlightNoteVo.Create above.

  • Imagine a CrewCountVo wrapping an int that must be > 0 and <= 20
  • Write public static Result<CrewCountVo> Create(int value) using Result<CrewCountVo>.FailIf(value <= 0, err1).FailIf(value > 20, err2).OrSuccess(() => new CrewCountVo(value))
  • Pass value = -5: convince yourself the returned Result carries the > 0 error
  • Pass value = 25: it carries the <= 20 error. Neither call throws
Done whenYour Create returns a Result via a FailIf chain terminated by OrSuccess, accumulates the relevant error(s), and never throws for invalid input.
Quiz

FailIf or Bind

You validate two independent fields on a request, and you want the caller to see BOTH errors if both are wrong. Which do you reach for?

  1. AFailIf, because it accumulates all errors instead of stopping at the first
  2. BBind, because it chains each guard onto the previous one
  3. CA try/catch that collects exceptions
  4. DMap, because it transforms the value
Show answer

Correct answer: A — FailIf, because it accumulates all errors instead of stopping at the first

FailIf accumulates: chain both guards and the Result comes back with both errors. Bind short-circuits on the first failure, which is right for DEPENDENT steps but wrong when you want to report every independent problem at once.

Recall

The two chaining shapes

Lock the distinction in.

In one line each: when do you use FailIf and when do you use Bind?

Reveal answer

FailIf for independent validation rules you want to accumulate (report all failures at once). Bind for a dependent step whose input is the previous step's success value, so it short-circuits if the previous step failed.

Sign in to track your progress →