diff --git a/packages/wyatt_type_utils/README.md b/packages/wyatt_type_utils/README.md index 19e55ae0..84de5044 100644 --- a/packages/wyatt_type_utils/README.md +++ b/packages/wyatt_type_utils/README.md @@ -29,14 +29,51 @@ Type Utils for Dart & Flutter. -## Features +## Option\ - +**Option** is a container object which may or may not contain a non-null value. If a **Value** is present, `isPresent()` will return true and `get()` will return the value. Additional methods that depend on the presence or absence of a contained value are provided, such as `orElse()` (return a default value if value not present) and `ifPresent()` (execute a block of code if the value is present). -## Getting started +```dart +final Option rand = Option.ofNullable( + Random().nextBool() ? null : 10, +); - +print( + "`rand` is${rand.isNull ? "" : " not"} null, " + "so it's value is ${rand.orElse(15)}", +); -## Usage +// Ok, `orElse` is equivalent to ?? but the Option is +// useful for match pattern and other utils accesses. +rand.match((value) { + print('Value is: $value'); + }, () { + print('Rand is null'); + }, +); - \ No newline at end of file +print('`rand` contains 10 ? => ${rand.contains(10)}'); +``` + +## Result\ + +**Result** type is coming from functional languages where exceptions are (rightfully) considered a side-effect, and therefore not appropriate to pass domain errors. Mind the difference between different kinds of errors: Some of them belong to domain, others don't. + +*E.g. null reference exception or index out of bounds are not related to domain - they rather indicate a defect.* + +Either is defined as a generic type with two branches +- success with **T** +- failure with **E** + +It can appear in two forms, where it contains an object of **Ok**, or where it contains an object of **Err**. It cannot appear in both states at once, or in none of them. Therefore, if one possesses an **Result** instance, it either contains a successfully produced result, or contains an error object. + +```dart +Future> requestTodo() async => Result.tryCatchAsync( + () => get('https://jsonplaceholder.typicode.com/todos/1'), + (error) => ServerError('Error while getting todo'), +); + +final Result todo1 = await requestTodo(); +// In Flutter: +todo1.either(buildWidgetWithTodo, buildSizedBox); +``` diff --git a/packages/wyatt_type_utils/analysis_options.yaml b/packages/wyatt_type_utils/analysis_options.yaml index aa9f26bc..2f4f39c0 100644 --- a/packages/wyatt_type_utils/analysis_options.yaml +++ b/packages/wyatt_type_utils/analysis_options.yaml @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - - include: package:wyatt_analysis/analysis_options.yaml + +analyzer: + exclude: "!example/**" diff --git a/packages/wyatt_type_utils/example/lib/error.dart b/packages/wyatt_type_utils/example/lib/error.dart new file mode 100644 index 00000000..67008d6d --- /dev/null +++ b/packages/wyatt_type_utils/example/lib/error.dart @@ -0,0 +1,33 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +class AppError { + final String message; + + const AppError(this.message); + + @override + // ignore: no_runtimetype_tostring + String toString() => '$runtimeType: $message'; +} + +class ServerError extends AppError { + ServerError(super.message); +} + +class ClientError extends AppError { + ClientError(super.message); +} diff --git a/packages/wyatt_type_utils/example/lib/main.dart b/packages/wyatt_type_utils/example/lib/main.dart index 5857e8c8..e905a556 100644 --- a/packages/wyatt_type_utils/example/lib/main.dart +++ b/packages/wyatt_type_utils/example/lib/main.dart @@ -14,8 +14,50 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +import 'dart:math'; + +import 'package:http/http.dart' as http; +import 'package:type_utils_example/error.dart'; +import 'package:type_utils_example/todo.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart'; -void main() { - print(wyatt()); +Future get(String url) async { + final res = await http.get(Uri.parse(url)); + final todo = TodoModel.fromJson(res.body); + return todo; +} + +Future> requestTodo() async => Result.tryCatchAsync( + () => get('https://jsonplaceholder.typicode.com/todos/1'), + (error) => ServerError('Error while getting todo'), + ); + +Future> requestBadTodo() async => + Result.tryCatchAsync( + () => get('https://jsonplaceholder.typicode.com/todos/123897'), + (error) => ServerError('Error while getting todo'), + ); + +Future main() async { + final Result todo1 = await requestTodo(); + todo1.either(print, print); + + final Result todo2 = await requestBadTodo(); + todo2.either(print, print); + + final Option rand = Option.ofNullable(Random().nextBool() ? null : 10); + + print( + "`rand` is${rand.isNull ? "" : " not"} null, " + "so it's value is ${rand.orElse(15)}", + ); + + // Ok, `orElse` is equivalent to ?? but the Option is useful for match pattern + rand.match((value) { + print('Value is: $value'); + }, () { + print('Rand is null'); + }); + + print('`rand` contains 10 ? => ${rand.contains(10)}'); } diff --git a/packages/wyatt_type_utils/example/lib/todo.dart b/packages/wyatt_type_utils/example/lib/todo.dart new file mode 100644 index 00000000..e5f21752 --- /dev/null +++ b/packages/wyatt_type_utils/example/lib/todo.dart @@ -0,0 +1,85 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// ignore_for_file: public_member_api_docs, sort_constructors_first, +// ignore_for_file: avoid_equals_and_hash_code_on_mutable_classes + +import 'dart:convert'; + +class TodoModel { + final int id; + final int userId; + final String title; + final bool completed; + TodoModel({ + required this.id, + required this.userId, + required this.title, + required this.completed, + }); + + TodoModel copyWith({ + int? id, + int? userId, + String? title, + bool? completed, + }) => + TodoModel( + id: id ?? this.id, + userId: userId ?? this.userId, + title: title ?? this.title, + completed: completed ?? this.completed, + ); + + Map toMap() => { + 'id': id, + 'userId': userId, + 'title': title, + 'completed': completed, + }; + + factory TodoModel.fromMap(Map map) => TodoModel( + id: map['id'] as int, + userId: map['userId'] as int, + title: map['title'] as String, + completed: map['completed'] as bool, + ); + + String toJson() => json.encode(toMap()); + + factory TodoModel.fromJson(String source) => + TodoModel.fromMap(json.decode(source) as Map); + + @override + String toString() => 'TodoModel(id: $id, userId: $userId, title: ' + '$title, completed: $completed)'; + + @override + bool operator ==(covariant TodoModel other) { + if (identical(this, other)) { + return true; + } + + return other.id == id && + other.userId == userId && + other.title == title && + other.completed == completed; + } + + @override + int get hashCode => + id.hashCode ^ userId.hashCode ^ title.hashCode ^ completed.hashCode; +} diff --git a/packages/wyatt_type_utils/example/pubspec.yaml b/packages/wyatt_type_utils/example/pubspec.yaml index 09085171..0562fe59 100644 --- a/packages/wyatt_type_utils/example/pubspec.yaml +++ b/packages/wyatt_type_utils/example/pubspec.yaml @@ -3,7 +3,7 @@ description: A new Flutter project. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -27,20 +27,17 @@ environment: # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: - - + http: ^0.13.4 + wyatt_type_utils: path: "../" dev_dependencies: - - wyatt_analysis: git: url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages ref: wyatt_analysis-v2.1.0 path: packages/wyatt_analysis - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/wyatt_type_utils/example/test/widget_test.dart b/packages/wyatt_type_utils/example/test/widget_test.dart index 52de7a27..1bb8b149 100644 --- a/packages/wyatt_type_utils/example/test/widget_test.dart +++ b/packages/wyatt_type_utils/example/test/widget_test.dart @@ -12,4 +12,4 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with this program. If not, see . \ No newline at end of file +// along with this program. If not, see . diff --git a/packages/wyatt_type_utils/lib/src/either_base.dart b/packages/wyatt_type_utils/lib/src/either_base.dart new file mode 100644 index 00000000..651132ef --- /dev/null +++ b/packages/wyatt_type_utils/lib/src/either_base.dart @@ -0,0 +1,145 @@ +// // Copyright (C) 2022 WYATT GROUP +// // Please see the AUTHORS file for details. +// // +// // This program is free software: you can redistribute it and/or modify +// // it under the terms of the GNU General Public License as published by +// // the Free Software Foundation, either version 3 of the License, or +// // any later version. +// // +// // This program is distributed in the hope that it will be useful, +// // but WITHOUT ANY WARRANTY; without even the implied warranty of +// // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// // GNU General Public License for more details. +// // +// // You should have received a copy of the GNU General Public License +// // along with this program. If not, see . + +// // ignore_for_file: avoid_positional_boolean_parameters + +part 'future_result.dart'; +part 'result.dart'; +part 'option.dart'; + +mixin _Left on _EitherBase {} + +mixin _Right on _EitherBase {} + +class _EitherBaseException implements Exception { + final String message; + const _EitherBaseException(this.message); + + @override + String toString() => '_EitherException: $message'; +} + +abstract class _EitherBase { + const _EitherBase(); + + bool get _isLeft => this is _Left; + + bool get _isRight => this is _Right; + + LeftType? get _left => _fold( + (value) => value, + (right) => null, + ); + + RightType? get _right => _fold( + (left) => null, + (value) => value, + ); + + U _unwrap() { + if (U == LeftType) { + return _fold( + (value) => value, + (right) => throw const ResultException( + 'Illegal use. You should check left value before calling', + ), + ) as U; + } + if (U == RightType) { + return _fold( + (left) => throw const ResultException( + 'Illegal use. You should check right value before calling', + ), + (value) => value, + ) as U; + } + throw ResultException( + 'Illegal use. You should use $LeftType or $RightType type', + ); + } + + Future _unwrapAsync() { + if (U == LeftType) { + return _foldAsync( + Future.value, + (right) => throw const ResultException( + 'Illegal use. You should check left value before calling', + ), + ) as Future; + } + if (U == RightType) { + return _foldAsync( + (left) => throw const ResultException( + 'Illegal use. You should check right value before calling', + ), + Future.value, + ) as Future; + } + throw ResultException( + 'Illegal use. You should use $LeftType or $RightType type', + ); + } + + U _fold( + U Function(LeftType left) fnL, + U Function(RightType right) fnR, + ); + + Future _foldAsync( + Future Function(LeftType left) fnL, + Future Function(RightType right) fnR, + ); + + _EitherBase _and( + _EitherBase res, + ); + + _EitherBase _or( + _EitherBase res, + ); + + bool _contains(U x); + + LeftType _expect(String msg); + + RightType _expectErr(String msg); + + _EitherBase _map( + U Function(LeftType left) mapper, + ); + + Future<_EitherBase> _mapAsync( + Future Function(LeftType left) mapper, + ); + + _EitherBase _mapErr( + F Function(RightType right) mapper, + ); + + Future<_EitherBase> _mapErrAsync( + Future Function(RightType right) mapper, + ); + + _EitherBase _either( + L Function(LeftType left) fnL, + R Function(RightType right) fnR, + ); + + Future<_EitherBase> _eitherAsync( + Future Function(LeftType left) fnL, + Future Function(RightType right) fnR, + ); +} diff --git a/packages/wyatt_type_utils/lib/src/future_result.dart b/packages/wyatt_type_utils/lib/src/future_result.dart new file mode 100644 index 00000000..95f8942f --- /dev/null +++ b/packages/wyatt_type_utils/lib/src/future_result.dart @@ -0,0 +1,108 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +part of 'either_base.dart'; + +extension FutureResult on Future> { + /// Represents the left side of [Result] class. + Future get isOk => then((result) => result.isOk); + + /// Represents the right side of [Result] class. + Future get isErr => then((result) => result.isErr); + + /// Get [U] value, may throw an exception. + Future unwrap() => then((result) => result.unwrap()); + + /// Get **async** [U] value, may throw an exception. + /// + /// With nullable, `Future(Future(U)) == Future(U)` + Future unwrapAsync() => then((result) => result.unwrapAsync()); + + /// Fold [Ok] and [Err] into the value of one type + Future fold( + U Function(T value) valueTransformer, + U Function(E error) errorTransformer, + ) => + then((result) => result.fold(valueTransformer, errorTransformer)); + + /// Fold [Ok] and [Err] **asynchronously** into the value of one type + Future foldAsync( + Future Function(T value) valueTransformer, + Future Function(E error) errorTransformer, + ) => + then((result) => result.foldAsync(valueTransformer, errorTransformer)); + + /// Swap [Ok] and [Err] + Future> swap() => then((result) => result.swap()); + + /// Returns [res] if the [Result] is [Ok], otherwise returns + /// the [Err] value of this. + Future> and(Result res) => + then((result) => result.and(res)); + + /// Returns [res] if the [Result] is [Err], otherwise returns + /// the [Ok] value of this. + Future> or(Result res) => + then((result) => result.or(res)); + + /// Returns true if the result is an [Ok] or [Err] value containing + /// the given value/error. + Future contains(U x) => then((result) => result.contains(x)); + + /// Returns the contained [Ok] value. Throw [ResultException] on [Err] with + /// its content. + Future expect(String msg) => then((result) => result.expect(msg)); + + /// Returns the contained [Err] value. Throw [ResultException] on [Ok] with + /// its content. + Future expectErr(String msg) => then((result) => result.expectErr(msg)); + + /// Maps a [Result] to [Result] by applying a function to a + /// contained [Ok] value, leaving an [Err] value untouched. + Future> map(U Function(T value) mapper) => + then((result) => result.map(mapper)); + + /// Maps a [Result] to [Result] by applying an **async** function + /// to a contained [Ok] value, leaving an [Err] value untouched. + Future> mapAsync(Future Function(T value) mapper) => + then((result) => result.mapAsync(mapper)); + + /// Maps a [Result] to [Result] by applying a function to a + /// contained [Err] value, leaving an [Ok] value untouched. + Future> mapErr(F Function(E error) mapper) => + then((result) => result.mapErr(mapper)); + + /// Maps a [Result] to [Result] by applying an **async** function + /// to a contained [Err] value, leaving an [Ok] value untouched. + Future> mapErrAsync(Future Function(E error) mapper) => + then((result) => result.mapErrAsync(mapper)); + + /// Transforms a [Result] to [Result] by applying functions to + /// contained [Ok] and [Err] values. + Future> either( + U Function(T value) valueTransformer, + F Function(E error) errorTransformer, + ) => + then((result) => result.either(valueTransformer, errorTransformer)); + + /// Transforms a [Result] to [Result] by applying **async** + /// functions to contained [Ok] and [Err] values. + Future> eitherAsync( + Future Function(T value) valueTransformer, + Future Function(E error) errorTransformer, + ) => + then((result) => result.eitherAsync(valueTransformer, errorTransformer)); +} diff --git a/packages/wyatt_type_utils/lib/src/option.dart b/packages/wyatt_type_utils/lib/src/option.dart new file mode 100644 index 00000000..b86a510b --- /dev/null +++ b/packages/wyatt_type_utils/lib/src/option.dart @@ -0,0 +1,301 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +part of 'either_base.dart'; + +/// {@template option_exception} +/// [OptionException] is sometimes threw by [Option] objects. +/// +/// ```dart +/// throw OptionException('Emergency failure!'); +/// ``` +/// {@endtemplate} +class OptionException extends _EitherBaseException { + /// {@macro result_exception} + const OptionException(super.message); + + @override + String toString() => 'OptionException: $message'; +} + +/// {@template option} +/// [Option] is a container object which may or may not contain a non-null +/// value. If a [Value] is present, isPresent() will return true and get() +/// will return the value. +/// +/// Additional methods that depend on the presence or absence of +/// a contained value are provided, such as orElse() (return a default value +/// if value not present) and ifPresent() (execute a block of code if the +/// value is present). +/// {@endtemplate} +abstract class Option extends _EitherBase { + /// {@macro option} + const Option._(); + + /// Represents the left side of [Option] class. + bool get isPresent => _isLeft; + + /// Represents the right side of [Option] class. + bool get isNull => _isRight; + + /// Get nullable [Value]. + T? get() => _left; + + /// Get [U] value, may throw an exception. + U unwrap() => _unwrap(); + + /// Get **async** [U] value, may throw an exception. + Future unwrapAsync() => _unwrapAsync(); + + /// Match [Value] and [None]. + void match( + void Function(T value) value, + void Function() none, + ) => + _fold( + (left) => value(left), + (right) => none(), + ); + + /// Match **async** [Value] and [None]. + Future matchAsync( + Future Function(T value) value, + Future Function() none, + ) => + _foldAsync( + (left) => value(left), + (right) => none(), + ); + + /// If [Value] is present, invoke the specified consumer + /// with the value, otherwise do nothing. + void ifPresent( + void Function(T value) consumer, + ) => + _fold( + (left) => consumer(left), + (right) => {}, + ); + + /// Return the [Value] if present, otherwise return [other]. + T orElse( + T other, + ) => + _fold( + (left) => left, + (right) => other, + ); + + /// Return the [Value] if present, otherwise call and return result + /// of [supplier]. + T orElseLazy( + T Function() supplier, + ) => + _fold( + (left) => left, + (right) => supplier(), + ); + + /// Returns [res] if the [Option] is [Value], otherwise returns + /// the [None] value of this. + Option and(Option res) => _and(res) as Option; + + /// Returns [res] if the [Option] is [None], otherwise returns + /// the [Value] of this. + Option or(Option res) => _or(res) as Option; + + /// Returns true if the result is a [Value]. + bool contains(U x) => _contains(x); + + /// Returns the contained [Value]. Throw [OptionException] on [None]. + T expect(String msg) => _expect(msg); + + /// Maps a [Option] to [Option] by applying a function to a + /// contained [Value]. + Option map(U Function(T value) mapper) => _map(mapper) as Option; + + /// Maps a [Option] to [Option] by applying an **async** function + /// to a contained [Value]. + Future> mapAsync(Future Function(T value) mapper) async => + await _mapAsync(mapper) as Option; + + /// Constructs an [Option] with the specified present non-null [Value]. + static Value of(T value) { + if (value == null) { + throw const OptionException("Value can' be null. Use `ofNullable()`"); + } + return ofNullable(value) as Value; + } + + /// Constructs an [Option] describing the specified [Value], + /// if non-null, otherwise returns a [None]. + static Option ofNullable(T? value) { + if (value != null) { + return Value(value); + } else { + return None(); + } + } + + /// Constructs an [Option] with the specified present non-null [Value]. + static Value ofLazy(T Function() value) { + final v = value(); + if (v == null) { + throw const OptionException("Value can' be null. Use `ofNullableLazy()`"); + } + return ofNullable(v) as Value; + } + + /// Constructs an [Option] describing the specified [Value], + /// if non-null, otherwise returns a [None]. + static Option ofNullableLazy(T? Function() value) { + final v = value(); + if (v != null) { + return Value(v); + } else { + return None(); + } + } +} + +class Value extends Option with _Left { + final T value; + + /// {@macro ok} + const Value(this.value) : super._(); + + @override + _EitherBase _and(_EitherBase res) => res as Option; + + @override + bool _contains(U x) => value == x; + + @override + _EitherBase _either( + L Function(T left) fnL, + R Function(void right) fnR, + ) => + throw UnimplementedError(); + + @override + Future<_EitherBase> _eitherAsync( + Future Function(T left) fnL, + Future Function(void right) fnR, + ) => + throw UnimplementedError(); + + @override + T _expect(String msg) => value; + + @override + void _expectErr(String msg) => throw UnimplementedError(); + + @override + U _fold(U Function(T left) fnL, U Function(void right) fnR) => fnL(value); + + @override + Future _foldAsync( + Future Function(T left) fnL, + Future Function(void right) fnR, + ) => + fnL(value); + + @override + _EitherBase _map(U Function(T left) mapper) => + Value(mapper(value)); + + @override + Future<_EitherBase> _mapAsync( + Future Function(T left) mapper, + ) => + mapper(value).then(Value.new); + + @override + _EitherBase _mapErr(F Function(void right) mapper) => + throw UnimplementedError(); + + @override + Future<_EitherBase> _mapErrAsync( + Future Function(void right) mapper, + ) => + throw UnimplementedError(); + + @override + _EitherBase _or(_EitherBase res) => this as _EitherBase; +} + +class None extends Option with _Right { + /// {@macro ok} + const None() : super._(); + + @override + _EitherBase _and(_EitherBase res) => this as Option; + + @override + bool _contains(U x) => false; + + @override + _EitherBase _either( + L Function(T left) fnL, + R Function(void right) fnR, + ) => + throw UnimplementedError(); + + @override + Future<_EitherBase> _eitherAsync( + Future Function(T left) fnL, + Future Function(void right) fnR, + ) => + throw UnimplementedError(); + + @override + T _expect(String msg) => throw OptionException(msg); + + @override + void _expectErr(String msg) => throw UnimplementedError(); + + @override + U _fold(U Function(T left) fnL, U Function(void right) fnR) => fnR(null); + + @override + Future _foldAsync( + Future Function(T left) fnL, + Future Function(void right) fnR, + ) => + fnR(null); + + @override + _EitherBase _map(U Function(T left) mapper) => None(); + + @override + Future<_EitherBase> _mapAsync( + Future Function(T left) mapper, + ) => + Future.value(None()); + + @override + _EitherBase _mapErr(F Function(void right) mapper) => + throw UnimplementedError(); + + @override + Future<_EitherBase> _mapErrAsync( + Future Function(void right) mapper, + ) => + throw UnimplementedError(); + + @override + _EitherBase _or(_EitherBase res) => res; +} diff --git a/packages/wyatt_type_utils/lib/src/result.dart b/packages/wyatt_type_utils/lib/src/result.dart new file mode 100644 index 00000000..4756f970 --- /dev/null +++ b/packages/wyatt_type_utils/lib/src/result.dart @@ -0,0 +1,327 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// ignore_for_file: avoid_positional_boolean_parameters + +part of 'either_base.dart'; + +/// {@template result_exception} +/// [ResultException] is sometimes threw by [Result] objects. +/// +/// ```dart +/// throw ResultException('Emergency failure!'); +/// ``` +/// {@endtemplate} +class ResultException extends _EitherBaseException { + /// {@macro result_exception} + const ResultException(super.message); + + @override + String toString() => 'ResultException: $message'; +} + +/// {@template result} +/// [Result] type is coming from functional languages where exceptions are +/// (rightfully) considered a side-effect, and therefore not appropriate to +/// pass domain errors. Mind the difference between different kinds of errors: +/// Some of them belong to domain, others don't. +/// +/// *E.g. null reference exception or index out of bounds +/// are not related to domain - they rather indicate a defect.* +/// +/// Either is defined as a generic type with two branches +/// - success with [T] +/// - failure with [E] +/// +/// It can appear in two forms, where +/// it contains an object of [Ok], or where it contains an object +/// of [Err]. It cannot appear in both states at once, or in none of them. +/// Therefore, if one possesses an [Result] instance, it either contains a +/// successfully produced result, or contains an error object. +/// {@endtemplate} +abstract class Result extends _EitherBase { + /// {@macro result} + const Result._(); + + /// Represents the left side of [Result] class. + bool get isOk => _isLeft; + + /// Represents the right side of [Result] class. + bool get isErr => _isRight; + + /// Get nullable [Ok] value, and discarding the error, if any. + T? get ok => _left; + + /// Get nullable [Err] value, and discarding the success value, if any. + E? get err => _right; + + /// Get [U] value, may throw an exception. + U unwrap() => _unwrap(); + + /// Get **async** [U] value, may throw an exception. + Future unwrapAsync() => _unwrapAsync(); + + /// Fold [Ok] and [Err] into the value of one type + U fold( + U Function(T value) valueTransformer, + U Function(E error) errorTransformer, + ) => + _fold( + (left) => valueTransformer(left), + (right) => errorTransformer(right), + ); + + /// Fold [Ok] and [Err] **asynchronously** into the value of one type + Future foldAsync( + Future Function(T value) valueTransformer, + Future Function(E error) errorTransformer, + ) => + _foldAsync( + (left) => valueTransformer(left), + (right) => errorTransformer(right), + ); + + /// Swap [Ok] and [Err] + Result swap() => fold(Err.new, Ok.new); + + /// Returns [res] if the [Result] is [Ok], otherwise returns + /// the [Err] value of this. + Result and(Result res) => _and(res) as Result; + + /// Returns [res] if the [Result] is [Err], otherwise returns + /// the [Ok] value of this. + Result or(Result res) => _or(res) as Result; + + /// Returns true if the result is an [Ok] or [Err] value containing + /// the given value/error. + bool contains(U x) => _contains(x); + + /// Returns the contained [Ok] value. Throw [ResultException] on [Err] with + /// its content. + T expect(String msg) => _expect(msg); + + /// Returns the contained [Err] value. Throw [ResultException] on [Ok] with + /// its content. + E expectErr(String msg) => _expectErr(msg); + + /// Maps a [Result] to [Result] by applying a function to a + /// contained [Ok] value, leaving an [Err] value untouched. + Result map(U Function(T value) mapper) => + _map(mapper) as Result; + + /// Maps a [Result] to [Result] by applying an **async** function + /// to a contained [Ok] value, leaving an [Err] value untouched. + Future> mapAsync(Future Function(T value) mapper) => + _mapAsync(mapper) as Future>; + + /// Maps a [Result] to [Result] by applying a function to a + /// contained [Err] value, leaving an [Ok] value untouched. + Result mapErr(F Function(E error) mapper) => + _mapErr(mapper) as Result; + + /// Maps a [Result] to [Result] by applying an **async** function + /// to a contained [Err] value, leaving an [Ok] value untouched. + Future> mapErrAsync(Future Function(E error) mapper) => + _mapErrAsync(mapper) as Future>; + + /// Transforms a [Result] to [Result] by applying functions to + /// contained [Ok] and [Err] values. + Result either( + U Function(T value) valueTransformer, + F Function(E error) errorTransformer, + ) => + _either(valueTransformer, errorTransformer) as Result; + + /// Transforms a [Result] to [Result] by applying **async** + /// functions to contained [Ok] and [Err] values. + Future> eitherAsync( + Future Function(T value) valueTransformer, + Future Function(E error) errorTransformer, + ) => + _eitherAsync(valueTransformer, errorTransformer) + as Future>; + + /// Constructs a new [Result] from a function that might throw + static Result tryCatch( + T Function() tryFn, + E Function(Error error) onError, + ) { + try { + return Ok(tryFn()); + } on Error catch (e) { + return Err(onError(e)); + } + } + + /// Constructs a new [Result] from an **async** function that might throw + static Future> tryCatchAsync( + Future Function() tryFn, + E Function(Error error) onError, + ) async { + try { + return Ok(await tryFn()); + } on Error catch (e) { + return Err(onError(e)); + } + } + + /// If the condition is satify then return [value] in + /// [Ok] else [error] in [Err] + static Result conditional( + bool test, + T value, + E error, + ) => + test ? Ok(value) : Err(error); + + /// If the condition is satify then return *command* [value] + /// in [Ok] else [error] in [Err] + static Result conditionalLazy( + bool test, + T Function() value, + E Function() error, + ) => + test ? Ok(value()) : Err(error()); +} + +/// {@template ok} +/// Contains the success value of a [Result] +/// +/// {@macro result} +/// {@endtemplate} +class Ok extends Result with _Left { + final T value; + + /// {@macro ok} + const Ok(this.value) : super._(); + + @override + U _fold(U Function(T left) fnL, U Function(E right) fnR) => fnL(value); + + @override + Future _foldAsync( + Future Function(T left) fnL, + Future Function(E right) fnR, + ) => + fnL(value); + + @override + Result _and(_EitherBase res) => res as Result; + + @override + Result _or(_EitherBase res) => this as Result; + + @override + bool _contains(U x) => value == x; + + @override + T _expect(String msg) => value; + + @override + E _expectErr(String msg) => throw ResultException('$msg: $value'); + + @override + Result _map(U Function(T value) mapper) => Ok(mapper(value)); + + @override + Future> _mapAsync(Future Function(T value) mapper) => + mapper(value).then(Ok.new); + + @override + Result _mapErr(F Function(E error) mapper) => Ok(value); + + @override + Future> _mapErrAsync(Future Function(E error) mapper) => + Future.value(Ok(value)); + + @override + Result _either( + U Function(T value) fnL, + F Function(E error) fnR, + ) => + Ok(fnL(value)); + + @override + Future> _eitherAsync( + Future Function(T value) fnL, + Future Function(E error) fnR, + ) => + fnL(value).then(Ok.new); +} + +/// {@template err} +/// Contains the error value of a [Result] +/// +/// {@macro result} +/// {@endtemplate} +class Err extends Result with _Right { + final E error; + + /// {@macro err} + const Err(this.error) : super._(); + + @override + U _fold(U Function(T left) fnL, U Function(E right) fnR) => fnR(error); + + @override + Future _foldAsync( + Future Function(T left) fnL, + Future Function(E right) fnR, + ) => + fnR(error); + + @override + Result _and(_EitherBase res) => this as Result; + @override + Result _or(_EitherBase res) => res as Result; + + @override + bool _contains(U x) => error == x; + + @override + T _expect(String msg) => throw ResultException('$msg: $error'); + + @override + E _expectErr(String msg) => error; + + @override + Result _map(U Function(T value) mapper) => Err(error); + + @override + Future> _mapAsync(Future Function(T value) mapper) => + Future.value(Err(error)); + + @override + Result _mapErr(F Function(E error) mapper) => Err(mapper(error)); + + @override + Future> _mapErrAsync(Future Function(E error) mapper) => + mapper(error).then(Err.new); + + @override + Result _either( + U Function(T value) fnL, + F Function(E error) fnR, + ) => + Err(fnR(error)); + + @override + Future> _eitherAsync( + Future Function(T value) fnL, + Future Function(E error) fnR, + ) => + fnR(error).then(Err.new); +} diff --git a/packages/wyatt_type_utils/lib/src/src.dart b/packages/wyatt_type_utils/lib/src/src.dart new file mode 100644 index 00000000..28295d72 --- /dev/null +++ b/packages/wyatt_type_utils/lib/src/src.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'either_base.dart'; diff --git a/packages/wyatt_type_utils/lib/wyatt_type_utils.dart b/packages/wyatt_type_utils/lib/wyatt_type_utils.dart new file mode 100644 index 00000000..fa4004b5 --- /dev/null +++ b/packages/wyatt_type_utils/lib/wyatt_type_utils.dart @@ -0,0 +1,20 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/// Type Utils +library wyatt_type_utils; + +export 'src/src.dart'; diff --git a/packages/wyatt_type_utils/pubspec.yaml b/packages/wyatt_type_utils/pubspec.yaml index 0fed0bb2..1fc28a4c 100644 --- a/packages/wyatt_type_utils/pubspec.yaml +++ b/packages/wyatt_type_utils/pubspec.yaml @@ -1,5 +1,5 @@ name: wyatt_type_utils -description: Either, Optional and other useful types. +description: Either, Option and other useful types. repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_type_utils version: 0.0.1 diff --git a/packages/wyatt_type_utils/test/option_test.dart b/packages/wyatt_type_utils/test/option_test.dart new file mode 100644 index 00000000..6e11031d --- /dev/null +++ b/packages/wyatt_type_utils/test/option_test.dart @@ -0,0 +1,280 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:test/test.dart'; +import 'package:wyatt_type_utils/src/either_base.dart'; + +void main() { + group('Option', () { + test('`isPresent` returns true on not null value', () { + expect( + const Value(10).isPresent, + true, + ); + expect( + const None().isPresent, + false, + ); + }); + + test('`isNull` returns true on null value', () { + expect( + const Value(10).isNull, + false, + ); + expect( + const None().isNull, + true, + ); + }); + + test('`get` returns value', () { + const Option x = Value(2); + expect(x.get(), 2); + + const Option y = None(); + expect(y.get(), null); + }); + + test('unwrap() returns value on Value', () { + const Option x = Value(2); + expect(x.unwrap(), 2); + expect(() => x.unwrap(), throwsA(isException)); + }); + + test('unwrap() throws on None', () { + const Option x = None(); + expect(() => x.unwrap(), throwsA(isException)); + }); + + test('unwrapAsync() returns value on Value', () async { + const Option x = Value(2); + expect(await x.unwrapAsync(), 2); + expect(() async => x.unwrapAsync(), throwsA(isException)); + }); + + test('unwrapAsync() throws on Nonce', () async { + const Option x = None(); + expect(() async => x.unwrapAsync(), throwsA(isException)); + }); + + test('ifPresent() invokes command on Value', () { + const Value(2).ifPresent( + print, + ); + }); + + test('orElse() returns value', () { + expect( + const Value(10).orElse(12), + 10, + ); + + expect( + const None().orElse(12), + 12, + ); + }); + + test('orElseLazy() returns value', () { + expect( + const Value(10).orElseLazy(() => 12), + 10, + ); + + expect( + const None().orElseLazy(() => 12), + 12, + ); + }); + + test( + 'and() returns res if the option is `Value`, otherwise ' + 'returns `None`.', + () { + const Option x1 = Value(2); + const Option y1 = None(); + expect(x1.and(y1), y1); + + const Option x2 = None(); + const Option y2 = Value('foo'); + expect(x2.and(y2), x2); + + const Option x3 = None(); + const Option y3 = None(); + expect(x3.and(y3), x3); + + const Option x4 = Value(2); + const Option y4 = Value('different Option type'); + expect(x4.and(y4), y4); + + const Option x5 = Value(2); + const Option y5 = Value(5); + expect(x5.and(y5), y5); + }, + ); + + test( + 'or() returns res if the option is `None`, otherwise ' + 'returns the `Value`.', + () { + const Option x1 = Value(2); + const Option y1 = None(); + expect(x1.or(y1), x1); + + const Option x2 = None(); + const Option y2 = Value('foo'); + expect(x2.or(y2), y2); + + const Option x3 = None(); + const Option y3 = None(); + expect(x3.or(y3), y3); + + const Option x4 = Value(2); + const Option y4 = Value('different result type'); + expect(x4.or(y4), x4); + + const Option x5 = Value(2); + const Option y5 = Value(5); + expect(x5.or(y5), x5); + }, + ); + + test( + 'contains() returns true if the option is ' + 'containing the given value.', + () { + const Option x1 = Value(2); + expect(x1.contains(2), true); + expect(x1.contains(3), false); + const Option x2 = None(); + expect(x2.contains(2), false); + }, + ); + + test( + 'expect() return value if the option is a Value ' + 'else throw Exception', + () { + const Option x1 = Value(2); + expect(x1.expect('Testing expect'), 2); + + const Option x2 = None(); + expect( + () => x2.expect('Testing expect'), + throwsA( + predicate( + (e) => e.message == ('Testing expect'), + ), + ), + ); + }, + ); + + test( + 'map() transforms Value.', + () { + const Option x1 = Value(true); + + expect(x1.get(), true); + expect(x1.map((value) => false).get(), false); + + const Option x2 = None(); + + expect(x2.map((value) => false).isNull, true); + }, + ); + + test( + 'mapAsync() transforms Value.', + () async { + const Option x1 = Value(true); + + expect(x1.get(), true); + expect( + (await x1.mapAsync((value) => Future.value(false))).get(), + false, + ); + + const Option x2 = None(); + + expect( + (await x2.mapAsync((value) => Future.value(false))).isNull, + true, + ); + }, + ); + + test( + 'of(T value) returns Option with Value', + () { + final Option x1 = Option.of(2); + expect(x1.contains(2), true); + expect(x1.contains(3), false); + expect( + () => Option.of(null), + throwsA( + predicate( + (e) => e.message == ("Value can' be null. Use `ofNullable()`"), + ), + ), + ); + }, + ); + + test( + "ofNullable(T value) returns Option with Value and don't throw on Null", + () { + final Option x1 = Option.ofNullable(2); + expect(x1.contains(2), true); + expect(x1.contains(3), false); + + final Option x2 = Option.ofNullable(null); + expect(x2.isNull, true); + }, + ); + + test( + 'ofLazy() returns Option with Value', + () { + final Option x1 = Option.ofLazy(() => 2); + expect(x1.contains(2), true); + expect(x1.contains(3), false); + expect( + () => Option.ofLazy(() => null), + throwsA( + predicate( + (e) => + e.message == ("Value can' be null. Use `ofNullableLazy()`"), + ), + ), + ); + }, + ); + + test( + "ofNullableLazy() returns Option with Value and don't throw on Null", + () { + final Option x1 = Option.ofNullableLazy(() => 2); + expect(x1.contains(2), true); + expect(x1.contains(3), false); + + final Option x2 = Option.ofNullableLazy(() => null); + expect(x2.isNull, true); + }, + ); + }); +} diff --git a/packages/wyatt_type_utils/test/result_test.dart b/packages/wyatt_type_utils/test/result_test.dart new file mode 100644 index 00000000..5bdc8b0f --- /dev/null +++ b/packages/wyatt_type_utils/test/result_test.dart @@ -0,0 +1,425 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:test/test.dart'; +import 'package:wyatt_type_utils/src/either_base.dart'; + +void main() { + group('Result', () { + test('`isOk` returns true on Ok value', () { + expect( + const Ok(null).isOk, + true, + ); + expect( + const Err(null).isOk, + false, + ); + }); + + test('`isErr` returns true on Err value', () { + expect( + const Ok(null).isErr, + false, + ); + expect( + const Err(null).isErr, + true, + ); + }); + + test('`ok` returns value on Ok value', () { + const Result x = Ok(2); + expect(x.ok, 2); + expect(x.err, null); + }); + + test('`err` returns error on Err value', () { + const Result x = Err('error'); + expect(x.ok, null); + expect(x.err, 'error'); + }); + + test('unwrap() returns value on Ok value', () { + const Result x = Ok(2); + expect(x.unwrap(), 2); + expect(() => x.unwrap(), throwsA(isException)); + }); + + test('unwrap() returns error on Err value', () { + const Result x = Err('error'); + expect(x.unwrap(), 'error'); + expect(() => x.unwrap(), throwsA(isException)); + }); + + test('unwrapAsync() returns value on Ok value', () async { + const Result x = Ok(2); + expect(await x.unwrapAsync(), 2); + expect(() async => x.unwrapAsync(), throwsA(isException)); + }); + + test('unwrapAsync() returns error on Err value', () async { + const Result x = Err('error'); + expect(await x.unwrapAsync(), 'error'); + expect(() async => x.unwrapAsync(), throwsA(isException)); + }); + + test('fold() returns right value', () { + expect( + const Ok('') + .fold((left) => true, (right) => false), + true, + ); + expect( + const Err('') + .fold((left) => true, (right) => false), + false, + ); + }); + + test('foldAsync() returns right value', () async { + expect( + await const Ok('').foldAsync( + (left) => Future.value(true), + (right) => Future.value(false), + ), + true, + ); + expect( + await const Err('').foldAsync( + (left) => Future.value(true), + (right) => Future.value(false), + ), + false, + ); + }); + + test('swap() swaps values', () { + const Result x = Ok(10); + expect(x.isOk, true); + expect(x.isErr, false); + expect(x.ok, 10); + final Result y = x.swap(); + expect(y.isOk, false); + expect(y.isErr, true); + expect(y.err, 10); + }); + + test( + 'and() returns res if the result is `Ok`, otherwise ' + 'returns the `Err` value of this.', + () { + const Result x1 = Ok(2); + const Result y1 = Err('late error'); + expect(x1.and(y1), y1); + + const Result x2 = Err('early error'); + const Result y2 = Ok('foo'); + expect(x2.and(y2), x2); + + const Result x3 = Err('not a 2'); + const Result y3 = Err('late error'); + expect(x3.and(y3), x3); + + const Result x4 = Ok(2); + const Result y4 = Ok('different result type'); + expect(x4.and(y4), y4); + + const Result x5 = Ok(2); + const Result y5 = Ok(5); + expect(x5.and(y5), y5); + }, + ); + + test( + 'or() returns res if the result is `Err`, otherwise ' + 'returns the `Ok` value of this.', + () { + const Result x1 = Ok(2); + const Result y1 = Err('late error'); + expect(x1.or(y1), x1); + + const Result x2 = Err('early error'); + const Result y2 = Ok('foo'); + expect(x2.or(y2), y2); + + const Result x3 = Err('not a 2'); + const Result y3 = Err('late error'); + expect(x3.or(y3), y3); + + const Result x4 = Ok(2); + const Result y4 = Ok('different result type'); + expect(x4.or(y4), x4); + + const Result x5 = Ok(2); + const Result y5 = Ok(5); + expect(x5.or(y5), x5); + }, + ); + + test( + 'contains() returns true if the result is an Ok value ' + 'containing the given value.', + () { + const Result x1 = Ok(2); + expect(x1.contains(2), true); + expect(x1.contains(3), false); + const Result x2 = Err('Some error message'); + expect(x2.contains(2), false); + }, + ); + + test( + 'expect() return value if the result is an Ok value ' + 'else throw Exception', + () { + const Result x1 = Ok(2); + expect(x1.expect('Testing expect'), 2); + + const Result x2 = Err('emergency failure'); + expect( + () => x2.expect('Testing expect'), + throwsA( + predicate( + (e) => e.message == ('Testing expect: emergency failure'), + ), + ), + ); + }, + ); + + test( + 'expectErr() return value if the result is an Err value ' + 'else throw Exception', + () { + const Result x1 = Err(2); + expect(x1.expectErr('Testing expect err'), 2); + + const Result x2 = Ok('success value'); + expect( + () => x2.expectErr('Testing expect err'), + throwsA( + predicate( + (e) => e.message == ('Testing expect err: success value'), + ), + ), + ); + }, + ); + + test( + 'map() transforms Ok value.', + () { + const Result x1 = Ok(true); + + expect(x1.ok, true); + expect(x1.map((value) => false).ok, false); + + const Result x2 = Err('oops'); + + expect(x2.map((value) => false).isErr, true); + }, + ); + + test( + 'mapErr() transforms Err value.', + () { + const Result x1 = Ok(true); + + expect(x1.mapErr((value) => false).isOk, true); + + const Result x2 = Err('oops'); + + expect(x2.err, 'oops'); + expect(x2.mapErr((error) => 'failure').err, 'failure'); + }, + ); + + test( + 'mapAsync() transforms Ok value.', + () async { + const Result x1 = Ok(true); + + expect(x1.ok, true); + expect((await x1.mapAsync((value) => Future.value(false))).ok, false); + + const Result x2 = Err('oops'); + + expect((await x2.mapAsync((value) => Future.value(false))).isErr, true); + }, + ); + + test( + 'mapErrAsync() transforms Err value.', + () async { + const Result x1 = Ok(true); + + expect( + (await x1.mapErrAsync((value) => Future.value(false))).isOk, + true, + ); + + const Result x2 = Err('oops'); + + expect(x2.err, 'oops'); + expect( + (await x2.mapErrAsync((error) => Future.value('failure'))).err, + 'failure', + ); + }, + ); + + test( + 'either() transforms values.', + () { + const Result x1 = Ok(true); + final Result y1 = + x1.either((value) => 1, (error) => 'error'); + + expect(y1.isOk, true); + expect(y1.ok, 1); + + const Result x2 = Err(true); + final Result y2 = + x2.either((value) => 1, (error) => 'error'); + + expect(y2.isErr, true); + expect(y2.err, 'error'); + }, + ); + + test( + 'eitherAsync() transforms values.', + () async { + const Result x1 = Ok(true); + final Result y1 = await x1.eitherAsync( + (value) => Future.value(1), + (error) => Future.value('error'), + ); + + expect(y1.isOk, true); + expect(y1.ok, 1); + + const Result x2 = Err(true); + final Result y2 = await x2.eitherAsync( + (value) => Future.value(1), + (error) => Future.value('error'), + ); + + expect(y2.isErr, true); + expect(y2.err, 'error'); + }, + ); + + test( + 'tryCatch() try and catch error as Err.', + () { + expect( + Result.tryCatch( + () => throw Exception('not success'), + (err) => 'error', + ).isErr, + true, + ); + expect( + Result.tryCatch( + () => 2, + (err) => 'error', + ).isOk, + true, + ); + }, + ); + + test( + 'tryCatchAsync() try and catch error as Err.', + () async { + expect( + (await Result.tryCatchAsync( + () => throw Exception('not success'), + (err) => 'error', + )).isErr, + true, + ); + expect( + (await Result.tryCatchAsync( + () => Future.value(2), + (err) => 'error', + )).isOk, + true, + ); + }, + ); + + test( + 'conditional() returns Result on true test', + () { + final Result x1 = Result.conditional( + false, + 2, + 'error', + ); + expect( + x1.isErr, + true, + ); + + expect(x1.err, 'error'); + + final Result x2 = Result.conditional( + true, + 2, + 'error', + ); + expect( + x2.isOk, + true, + ); + + expect(x2.ok, 2); + }, + ); + + test( + 'conditionalLazy() returns Result on true test', + () { + final Result x1 = Result.conditionalLazy( + false, + () => 2, + () => 'error', + ); + expect( + x1.isErr, + true, + ); + + expect(x1.err, 'error'); + + final Result x2 = Result.conditionalLazy( + true, + () => 2, + () => 'error', + ); + expect( + x2.isOk, + true, + ); + + expect(x2.ok, 2); + }, + ); + }); +}