light_result 0.1.0 copy "light_result: ^0.1.0" to clipboard
light_result: ^0.1.0 copied to clipboard

A lightweight, zero-dependency functional error handling package for Dart 3. Built with sealed classes and pattern matching.

light_result #

A lightweight, zero-dependency* functional error handling package for Dart 3.

Built entirely on sealed classes and pattern matching — the compiler forces you to handle every case. No category theory. No boilerplate. Just safe, composable error handling.

*Core library has zero dependencies. The optional testing.dart utilities depend on matcher.

Why light_result? #

Feature light_result fpdart dartz
Dart 3 sealed classes
Zero dependencies
Exhaustive pattern matching
Lightweight API
Full documentation
Async chaining extensions
Guard/Combine utilities Partial
Test matchers included

Installation #

dependencies:
  light_result: ^0.1.0

Quick Start #

import 'package:light_result/light_result.dart';

// Define a function that can fail
Result<String, int> divide(int a, int b) {
  if (b == 0) return Left('Division by zero');
  return Right(a ~/ b);
}

// Exhaustive pattern matching — compiler ensures both cases are handled
final result = divide(10, 2);
final message = switch (result) {
  Left(value: final error) => 'Error: $error',
  Right(value: final data) => 'Result: $data',
};

Core Types #

Result<L, R> #

Represents either a failure (Left<L>) or success (Right<R>).

// Creating results
final success = Right<String, int>(42);
final failure = Left<String, int>('Something went wrong');

// Factory constructors
final fromNull = Result<String, int>.fromNullable(json['age'], () => 'missing');
final guarded = Result.guard(() => int.parse(input), (e, s) => 'Parse error: $e');

Option #

Represents an optional value — eliminates null ambiguity.

final some = Some(42);
final none = None<int>();
final fromNullable = Option.fromNullable(possiblyNull);

final value = switch (some) {
  Some(value: final v) => 'Got: $v',
  None() => 'Empty',
};

Functional Chaining #

Result<String, int> parse(String s) =>
    Result.guard(() => int.parse(s), (e, _) => 'Invalid number');

Result<String, int> validate(int n) =>
    n > 0 ? Right(n) : Left('Must be positive');

// Chain operations — short-circuits on first Left
final result = parse('42')
    .flatMap(validate)
    .map((n) => n * 2)
    .tap((n) => print('Result: $n'));

// Extract value safely
final value = result.getOrElse((_) => 0);

Async Chaining (TaskResult) #

No more await chains! Compose async operations fluently:

Future<Result<AppError, User>> fetchUser(int id) async { ... }
Future<Result<AppError, Profile>> fetchProfile(User user) async { ... }

// Fluent async chain
final profile = await fetchUser(1)
    .thenFlatMap((user) => fetchProfile(user))
    .thenMap((profile) => profile.avatar)
    .thenTapLeft((err) => logger.error(err));

Guard & Combine #

// Safely wrap throwing code
final result = Result.guard(
  () => jsonDecode(input),
  (error, stack) => ParseFailure(error.toString()),
);

// Async guard
final data = await Result.guardAsync(
  () => httpClient.get('/api/users'),
  (error, stack) => NetworkFailure(error.toString()),
);

// Combine multiple results
final combined = Result.combine([
  validateName(name),
  validateEmail(email),
  validateAge(age),
]); // Right([name, email, age]) or first Left

// Parallel async
final users = await Result.waitAll([
  fetchUser(1),
  fetchUser(2),
  fetchUser(3),
]);

Typed Failure Hierarchies #

sealed class AppFailure extends Failure {
  const AppFailure(super.message, {super.stackTrace});
}

final class NetworkFailure extends AppFailure {
  final int? statusCode;
  const NetworkFailure(super.message, {this.statusCode});
}

final class ValidationFailure extends AppFailure {
  final String field;
  const ValidationFailure(super.message, {required this.field});
}

// Exhaustive error handling with typed failures
final result = await fetchUser(1);
final widget = switch (result) {
  Left(value: NetworkFailure(:final statusCode)) => ErrorWidget('Network: $statusCode'),
  Left(value: ValidationFailure(:final field)) => ErrorWidget('Invalid: $field'),
  Right(value: final user) => UserWidget(user),
};

Type Aliases #

// Shorthand types for common patterns
typedef ResultOf<T> = Result<Exception, T>;
typedef StringResult<T> = Result<String, T>;
typedef AppResult<T> = Result<Failure, T>;
typedef AsyncResult<L, R> = Future<Result<L, R>>;
typedef AsyncResultOf<T> = Future<Result<Exception, T>>;

Test Matchers #

import 'package:light_result/testing.dart';

test('returns user on success', () async {
  final result = await repository.fetchUser(1);
  
  expect(result, isRight);
  expect(result, isRightWith(expectedUser));
});

test('returns failure on error', () async {
  final result = await repository.fetchUser(-1);
  
  expect(result, isLeft);
  expect(result, isLeftWith(NetworkFailure('Not found')));
});

test('option matchers', () {
  expect(Option.fromNullable(42), isSome);
  expect(Option.fromNullable(42), isSomeWith(42));
  expect(Option.fromNullable(null), isNone);
});

Migration from fpdart/dartz #

fpdart/dartz light_result
Either<L, R> Result<L, R>
Either.right(value) Right(value)
Either.left(error) Left(error)
Either.tryCatch(...) Result.guard(...)
Option.of(value) Some(value)
Option.none() None()
TaskEither Future<Result<L, R>> + extensions
.match(onLeft, onRight) .fold(onLeft, onRight) or switch

License #

MIT

1
likes
0
points
247
downloads

Publisher

unverified uploader

Weekly Downloads

A lightweight, zero-dependency functional error handling package for Dart 3. Built with sealed classes and pattern matching.

Repository (GitHub)
View/report issues

Topics

#functional-programming #error-handling #result-type #either #monad

License

unknown (license)

Dependencies

matcher

More

Packages that depend on light_result