avoid_throw_in_fp_callback
This rule flags a throw inside an fpdart Do body or inside a callback passed to one of fpdart’s chaining methods (map, flatMap, chainEither, mapLeft, andThen, alt, orElse).
Why use this rule
Section titled “Why use this rule”fpdart’s whole premise is that failure travels in the value — the Left of an Either, the None of an Option. That is what lets a caller handle every failure in one fold, and what makes the compiler enforce it.
A throw inside map or a Do body leaves that channel. It escapes the pipeline as an ordinary exception, so a caller that carefully folds every failure still crashes. Worse, the signature keeps promising otherwise: TaskEither<Failure, T> says the only failures are Failures.
This is one of four Do pitfalls that fpdart documents in its own do_constructor_pitfalls example.
See also: fpdart: Do notation, fpdart: Either
The signature promises the only failure is a String, but the throw leaves
the error channel entirely — a caller that folds every Left still crashes:
TaskEither<String, int> parseAge(TaskEither<String, String> raw) => raw.map((value) { final age = int.tryParse(value); if (age == null) { throw FormatException('not a number: $value'); // escapes the pipeline } return age; });Put the failure in the value, where the signature already says it is:
TaskEither<String, int> parseAge(TaskEither<String, String> raw) => raw.flatMap( (value) => switch (int.tryParse(value)) { final age? => TaskEither.of(age), _ => TaskEither.left('not a number: $value'), }, );When the throwing code is someone else’s, catch it at the boundary and map it in once:
TaskEither<String, String> load(Future<String> Function() request) => TaskEither.tryCatch(request, (error, stackTrace) => '$error');Known limitations
Section titled “Known limitations”Throwing UnimplementedError, UnsupportedError, StateError or AssertionError is allowed by default. Those mark a branch that should never run — a programmer error, not a domain outcome — so routing them through the error channel would hand callers a Left they can neither handle nor meaningfully report. Set ignore_unimplemented: false to report them too.
Only fpdart’s own combinators are checked. A throw inside Iterable.map is ordinary Dart and is never reported.
Options
Section titled “Options”many_lints: rules: avoid_throw_in_fp_callback: ignore_unimplemented: false additional_methods: - traverserules: avoid_throw_in_fp_callback: ignore_unimplemented: false additional_methods: - traverse| Option | Type | Default | Description |
|---|---|---|---|
ignore_unimplemented |
bool | true |
Allow thrown UnimplementedError, UnsupportedError, StateError and AssertionError, which mark unreachable branches |
methods |
list of strings | the seven chaining methods | Replace the set of callback-taking methods that are checked |
additional_methods |
list of strings | [] |
Extend the set, for projects that wrap fpdart’s combinators in their own |
Configuration
Section titled “Configuration”This rule is in the core preset, so it is on with preset: core,
preset: recommended or preset: opinionated.
To turn it off:
rules: avoid_throw_in_fp_callback: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_chain_first_swallowing_failure— chainFirst turns a failing effect into success.avoid_get_or_else_swallowing_failure— getOrElse is handed the failure; ignoring it should be a visible decision.prefer_and_then— andThen says that the previous value is not used.prefer_chain_either— chainEither lifts a synchronous Either step for you.