pure_result 0.1.3
pure_result: ^0.1.3 copied to clipboard
A sealed Result<T, E> package for Dart and Flutter to model success and failure explicitly in app, package, and async domain error handling.
pure_result #
A sealed Result<T, E> type for Dart and Flutter.
pure_result helps you represent success/failure as values instead of throwing everywhere.
✨ Highlights #
- ✅ Dart 3
sealed classAPI. - ✅ Strongly typed success and error channels (
T/E). - ✅ Functional composition:
map,flatMap,mapError,flatMapError,recover. - ✅ Named-parameter matching:
whenfor readable branching. - ✅ Exception capture helpers:
tryRunSync,tryRun. - ✅ Async chaining on
Future<Result<...>>viaAsyncResultOps.
📦 Installation #
dependencies:
pure_result: ^0.1.3
Then run:
dart pub get
📥 Imports #
Core API:
import 'package:pure_result/pure_result.dart';
Optional async extension API:
import 'package:pure_result/async_result.dart';
🚀 Quick Start #
import 'package:pure_result/pure_result.dart';
Result<int, String> parsePort(String raw) {
final value = int.tryParse(raw);
if (value == null) {
return const Result.failure('Invalid port');
}
return Result.success(value);
}
void main() {
final result = parsePort('8080').map((port) => port + 1);
final text = result.fold(
(value) => 'OK: $value',
(error) => 'ERR: $error',
);
print(text); // OK: 8081
}
🧱 Core Model #
Create success/failure values:
const ok = Result<int, String>.success(42);
const err = Result<int, String>.failure('boom');
Read state:
print(ok.isSuccess); // true
print(ok.isFailure); // false
print(ok.valueOrNull); // 42
print(ok.errorOrNull); // null
print(err.isSuccess); // false
print(err.valueOrNull); // null
print(err.errorOrNull); // boom
Pattern-match with switch:
String describe(Result<int, String> r) {
return switch (r) {
Success(value: final v) => 'value=$v',
Failure(error: final e) => 'error=$e',
};
}
🛠 Value-Side APIs #
fold #
final label = Result<int, String>.success(7).fold(
(v) => 'value:$v',
(e) => 'error:$e',
);
// value:7
when #
Named-parameter variant of fold for improved readability:
final label = Result<int, String>.success(7).when(
success: (v) => 'value:$v',
failure: (e) => 'error:$e',
);
// value:7
getOrElse #
final value = Result<int, String>.failure('bad').getOrElse((_) => 0);
// 0
getOrThrow #
final value = Result<int, Exception>.success(10).getOrThrow();
// 10
// Throws stored error when failure
// Result<int, Exception>.failure(Exception('x')).getOrThrow();
🔁 Transform APIs #
map / flatMap #
Result<int, String> readCount() => const Result.success(2);
Result<String, String> toText(int n) => Result.success('count=$n');
final mapped = readCount().map((n) => n + 1);
// Success(3)
final chained = readCount().flatMap(toText);
// Success(count=2)
mapError / flatMapError #
const failed = Result<int, String>.failure('not_found');
// mapError: transform the error type
final mappedError = failed.mapError((msg) => msg.length);
// Failure(9)
// flatMapError: attempt recovery or map to a different error
final retried = failed.flatMapError((msg) {
if (msg == 'not_found') return const Result.success(-1); // fallback
return Result<int, int>.failure(msg.length);
});
// Success(-1)
recover #
const failed = Result<int, String>.failure('timeout');
final recovered = failed.recover((_) => 30);
// Success(30)
🧯 Exception Capture APIs #
pure_result can convert thrown errors into typed failure values.
tryRunSync #
final ok = tryRunSync(() => 100 ~/ 4);
// Success(25)
final failed = tryRunSync(() => 100 ~/ 0);
// Failure(CaughtError(...))
tryRun #
final result = await tryRun(() async {
await Future<void>.delayed(const Duration(milliseconds: 10));
return 'done';
});
// Success(done)
CaughtError stores both error and stackTrace:
final r = tryRunSync(() => throw StateError('explode'));
if (r.isFailure) {
final ce = r.errorOrNull!;
print(ce.error); // StateError: Bad state: explode
print(ce.stackTrace);
}
Composing tryRunSync with existing APIs #
You can combine tryRunSync / tryRun with fold, map, or flatMap
to handle exceptions in a pipeline without losing type safety:
// Map a success value with a function that might throw:
final result = tryRunSync(() => int.parse(rawInput))
.map((n) => n * 2);
// Success(result) or Failure(CaughtError(...))
// Chain multiple fallible steps:
final chained = tryRunSync(() => step1())
.flatMap((v) => tryRunSync(() => step2(v)));
// Each step is independently captured
🌊 Async Result Chaining (AsyncResultOps) #
Import optional async extension:
import 'package:pure_result/async_result.dart';
Then chain directly on Future<Result<T, E>>:
Future<Result<int, String>> fetchPort() async {
return const Result.success(8080);
}
final result = await fetchPort()
.map((port) => port + 1)
.flatMap((port) => Result<String, String>.success('port=$port'))
.mapError((e) => 'ERR:$e')
.flatMapError((e) async => const Result.success('port=fallback'))
.recover((_) => 'port=80');
print(result); // Success(port=8081)
📚 API Surface #
From package:pure_result/pure_result.dart:
Result.success/Result.failureisSuccess/isFailurevalueOrNull/errorOrNullfold/when/getOrElse/getOrThrowmap/flatMap/mapError/flatMapError/recovertryRunSync/tryRunCaughtError
From package:pure_result/async_result.dart:
AsyncResultOps.mapAsyncResultOps.flatMapAsyncResultOps.mapErrorAsyncResultOps.flatMapErrorAsyncResultOps.recover
📊 Test & Coverage #
The badges at the top are powered by:
- GitHub Actions workflow:
.github/workflows/ci.yml - Codecov report from
coverage/lcov.info
Run locally:
dart test -r expanded
dart test --coverage=coverage
dart pub global activate coverage
dart pub global run coverage:format_coverage \
--packages=.dart_tool/package_config.json \
--report-on=lib \
--in=coverage \
--out=coverage/lcov.info \
--lcov
📄 License #
MIT License.