Skip to content

Instantly share code, notes, and snippets.

@CassiusPacheco
Last active August 16, 2024 07:29
Show Gist options
  • Select an option

  • Save CassiusPacheco/409e66e220ce563440df00385f39ac98 to your computer and use it in GitHub Desktop.

Select an option

Save CassiusPacheco/409e66e220ce563440df00385f39ac98 to your computer and use it in GitHub Desktop.
Dart's DataResult<S> inspired by my Swift enum implementation and Avdosev's Dart Either implementation
// The code below was inspired by my swift implementation https://gist.github.com/CassiusPacheco/4378d30d69316e4a6ba28a0c3af72628
// and Avdosev's Dart Either https://github.com/avdosev/either_dart/blob/master/lib/src/either.dart
import 'package:equatable/equatable.dart';
abstract class Failure extends Equatable implements Exception {
@override
String toString() => '$runtimeType Exception';
@override
List<Object> get props => [];
}
// General failures
class GenericFailure extends Failure {}
class APIFailure extends Failure {}
/// This abstraction contains either a success data of generic type `S` or a
/// failure error of type `Failure` as its result.
///
/// `data` property must only be retrieved when `DataResult` was constructed by
/// using `DataResult.success(value)`. It can be validated by calling
/// `isSuccess` first. Alternatively, `dataOrElse` can be used instead since it
/// ensures a valid value is returned in case the result is a failure.
///
/// `error` must only be retrieved when `DataResult` was constructed by using
/// `DataResult.failure(error)`. It can be validated by calling `isFailure`
/// first.
abstract class DataResult<S> extends Equatable {
static DataResult<S> failure<S>(Failure failure) => _FailureResult(failure);
static DataResult<S> success<S>(S data) => _SuccessResult(data);
/// Get [error] value, may throw an exception when the value is [data]
Failure get error => fold<Failure>(
(error) => error,
(data) => throw Exception(
'No error found. Check `isFailure()` before calling `error`'));
/// Get [data] value, may throw an exception when the value is [error]
S get data => fold<S>(
(error) => throw Exception(
'No data found. Check `isSuccess()` before calling `data`'),
(data) => data);
/// Returns `true` if the object is of the `SuccessResult` type, which means
/// `data` will return a valid result.
bool get isSuccess => this is _SuccessResult<S>;
/// Returns `true` if the object is of the `FailureResult` type, which means
/// `error` will return a valid result.
bool get isFailure => this is _FailureResult<S>;
/// Returns `data` if `isSuccess()` returns `true`, otherwise it returns
/// `other`.
S dataOrElse(S other) => isSuccess ? data : other;
/// Sugar syntax that calls `dataOrElse` under the hood. Returns left value if
/// `isSuccess()` returns `true`, otherwise it returns the right value.
S operator |(S other) => dataOrElse(other);
/// Transforms values of [error] and [data] in new a `DataResult` type. Only
/// the matching function to the object type will be executed. For example,
/// for a `SuccessResult` object only the [fnData] function will be executed.
DataResult<T> either<T>(
Failure Function(Failure error) fnFailure, T Function(S data) fnData);
/// Transforms value of [data] allowing a new `DataResult` to be returned.
/// A `SuccessResult` might return a `FailureResult` and vice versa.
DataResult<T> then<T>(DataResult<T> Function(S data) fnData);
/// Transforms value of [data] always keeping the original identity of the
/// `DataResult`, meaning that a `SuccessResult` returns a `SuccessResult` and
/// a `FailureResult` always returns a `FailureResult`.
DataResult<T> map<T>(T Function(S data) fnData);
/// Folds [error] and [data] into the value of one type. Only the matching
/// function to the object type will be executed. For example, for a
/// `SuccessResult` object only the [fnData] function will be executed.
T fold<T>(T Function(Failure error) fnFailure, T Function(S data) fnData);
@override
List<Object> get props => [if (isSuccess) data else error];
}
/// Success implementation of `DataResult`. It contains `data`. It's abstracted
/// away by `DataResult`. It shouldn't be used directly in the app.
class _SuccessResult<S> extends DataResult<S> {
final S _value;
_SuccessResult(this._value);
@override
_SuccessResult<T> either<T>(
Failure Function(Failure error) fnFailure, T Function(S data) fnData) {
return _SuccessResult<T>(fnData(_value));
}
@override
DataResult<T> then<T>(DataResult<T> Function(S data) fnData) {
return fnData(_value);
}
@override
_SuccessResult<T> map<T>(T Function(S data) fnData) {
return _SuccessResult<T>(fnData(_value));
}
@override
T fold<T>(T Function(Failure error) fnFailure, T Function(S data) fnData) {
return fnData(_value);
}
}
/// Failure implementation of `DataResult`. It contains `error`. It's
/// abstracted away by `DataResult`. It shouldn't be used directly in the app.
class _FailureResult<S> extends DataResult<S> {
final Failure _value;
_FailureResult(this._value);
@override
_FailureResult<T> either<T>(
Failure Function(Failure error) fnFailure, T Function(S data) fnData) {
return _FailureResult<T>(fnFailure(_value));
}
@override
_FailureResult<T> map<T>(T Function(S data) fnData) {
return _FailureResult<T>(_value);
}
@override
_FailureResult<T> then<T>(DataResult<T> Function(S data) fnData) {
return _FailureResult<T>(_value);
}
@override
T fold<T>(T Function(Failure error) fnFailure, T Function(S data) fnData) {
return fnFailure(_value);
}
}
@fredgrott
Copy link
Copy Markdown

To jazz it up add this

`extension TaskX<T extends Either<Object, U>, U> on Task {
Task<Either<Failure, U>> mapLeftToFailure() {
// ignore: unnecessary_this
return this.map(
(either) => either.leftMap((obj) {
try {
log(obj.toString());

      return obj as Failure;
    } catch (e) {

      log(obj.toString());

      throw obj;
    }
  }),
);

}
}
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment