Skip to content

REslava.ResultFlow β€” Library-agnostic Alternative

Feature Comparison

For projects not using REslava.Result, REslava.ResultFlow works with any fluent Result library (ErrorOr, FluentResults, LanguageExt, or your own):

dotnet add package REslava.ResultFlow

Add using REslava.ResultFlow; β€” the attribute is injected automatically by the generator.

Built-in convention dictionary β€” zero configuration for the most popular methods:

Library Recognized methods
REslava.Result Ensure, Bind, Map, Tap, TapOnFailure, TapBoth, Match, WithSuccess (+ Async)
ErrorOr Then, ThenAsync, Switch, SwitchAsync
LanguageExt Filter, Do, DoAsync, DoLeft, DoLeftAsync
FluentResults Bind, Map

Any unrecognized method renders as a generic operation node.

What REslava.ResultFlow does NOT have (compared to REslava.Result.Flow)

For projects using REslava.Result, the companion package REslava.Result.Flow adds two capabilities not possible in the library-agnostic package:

  • Typed error edges β€” scans each step's method body for new XxxError(...) constructions where XxxError implements IError; renders -->|DatabaseError| FAIL edges instead of generic fail arrows
  • IOperation chain walker β€” uses Roslyn's semantic model at the IInvocationOperation level, which makes the pipeline entry-point call (e.g. CreateUser()) visible as its own node
dotnet add package REslava.Result.Flow

Add using REslava.Result.Flow; β€” the attribute type is injected automatically by the generator, no separate assembly reference needed.

/*
```mermaid
flowchart LR
    N0_CreateUser["CreateUser<br/>User"]:::operation
    N0_CreateUser --> N1_EnsureAsync
    N1_EnsureAsync["EnsureAsync ⚑<br/>User"]:::gatekeeper
    N1_EnsureAsync -->|pass| N2_BindAsync
    N1_EnsureAsync -->|ValidationError| FAIL
    N2_BindAsync["BindAsync ⚑<br/>User"]:::transform
    N2_BindAsync -->|ok| N3_MapAsync
    N2_BindAsync -->|DatabaseError| FAIL
    N3_MapAsync["MapAsync ⚑<br/>User β†’ UserDto"]:::transform
    FAIL([fail]):::failure
    classDef gatekeeper fill:#e3e9fa,color:#3f5c9a
    classDef transform fill:#e3f0e8,color:#2f7a5c
    classDef failure fill:#f8e3e3,color:#b13e3e
    classDef operation fill:#e8f4f0,color:#1c7e6f
```*/
[ResultFlow]
public Task<Result<UserDto>> RegisterAsync(RegisterCommand cmd) =>
    CreateUser(cmd)
        .EnsureAsync(IsEmailValid, u => new ValidationError("email", "invalid"))
        .BindAsync(SaveUser)
        .MapAsync(ToUserDto);

Paste the comment into mermaid.live β€” CreateUser becomes the pipeline root, ValidationError and DatabaseError appear as typed failure edges on their respective steps. Here you can see the resulting diagram:

Pipeline with typed error edges

Error scanning is best-effort β€” only scans methods in the same compilation; errors created but not returned may appear as false positives; helper methods are not recursively followed.

REslava.ResultFlow REslava.Result.Flow
Works with any library βœ… ❌ REslava.Result only
⚑ async annotation βœ… βœ…
Success type travel βœ… (first generic arg) βœ… (via IResultBase)
Typed error edges ❌ βœ… (IError body scan)
Entry-point call as node ❌ βœ… (IOperation walk)