safe_result
A Dart package providing a Result type for handling success and error cases in a type-safe way, with support for custom error types.
Features
- Type-safe error handling with custom error types
- Pattern matching support
- Functional programming utilities
- Async operation support
- Null safety
- Generic error types for better error handling
Getting started
Add the package to your pubspec.yaml:
dependencies:
safe_result: ^2.0.0
Usage
import 'package:safe_result/safe_result.dart';
// Define custom error types
sealed class AppError {
final String message;
const AppError(this.message);
@override
String toString() => message;
}
class ValidationError extends AppError {
const ValidationError(super.message);
}
class NetworkError extends AppError {
final int statusCode;
const NetworkError(super.message, this.statusCode);
@override
String toString() => '$message (Status: $statusCode)';
}
// Creating Results with specific error types
final success = Result<int, ValidationError>.ok(42);
final failure = Result<int, ValidationError>.error(
ValidationError('Value must be positive')
);
// Pattern matching
switch (success) {
case Ok(value: final v):
print('Value: $v');
case Error(error: final e):
print('Error: $e');
}
// Using map for transformation
final doubled = success.map((value) => value * 2);
// Using fold for transformation with error handling
final message = success.fold(
onOk: (value) => 'Success: $value',
onError: (error) => 'Error: ${error.message}',
);
// Using when for pattern matching with specific return types
final state = success.when(
ok: (value) => UserState(
isLoading: false,
value: value,
error: null,
),
error: (error) => UserState(
isLoading: false,
value: null,
error: error,
),
);
// Safe error access with type checking
try {
final error = failure.error; // Safe: failure is Error
print('Error occurred: $error');
} on TypeError {
print('Cannot access error on Ok result');
}
// Handling nullable values with specific error types
final Result<String?, ValidationError> nullableResult = Result.ok(null);
final Result<String, ValidationError> nonNullResult = nullableResult
.requireNotNull(ValidationError('Value cannot be null'));
// Async operations with NetworkError
Future<Result<User, NetworkError>> getUser() async {
try {
final user = await api.fetchUser();
return Result.ok(user);
} catch (e) {
return Result.error(NetworkError('Failed to fetch user', 404));
}
}
// Chaining async operations with consistent error types
final userSettings = await getUser()
.andThen((user) => fetchUserSettings(user.id))
.mapAsync((settings) async {
await cache.save(settings);
return settings;
});
// Validation example
Result<User, ValidationError> validateUser(String name, int age, String email) {
return switch ((name.isEmpty, age < 0, !email.contains('@'))) {
(true, _, _) => Result.error(ValidationError('Name cannot be empty')),
(_, true, _) => Result.error(ValidationError('Age cannot be negative')),
(_, _, true) => Result.error(ValidationError('Invalid email format')),
_ => Result.ok(User(name, age, email))
};
}
Error Handling Best Practices
-
Define Domain-Specific Error Types
sealed class ApiError extends AppError { ... } sealed class DomainError extends AppError { ... } sealed class ValidationError extends AppError { ... } -
Use Pattern Matching with Error Types
switch (result) { case Error(error: NetworkError e) when e.statusCode == 404: print('Resource not found: ${e.message}'); case Error(error: ValidationError e): print('Validation failed: ${e.message}'); case Ok(value: final v): print('Success: $v'); } -
Chain Operations with Consistent Error Types
Result<T, E> validateAndTransform<T, E extends AppError>(T input) { return validate(input) .map(transform) .fold( onOk: (value) => Result.ok(value), onError: (error) => Result.error(error), ); }
Additional information
For more examples and detailed documentation, visit the GitHub repository.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Libraries
- safe_result
- A Result type for handling success and error cases in a type-safe way.