From 41382aad2f3760b3060f25745a2e6f5aaa576f8d Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Sun, 18 Feb 2024 22:48:20 +0100 Subject: [PATCH] refactor(wyatt_type_utils)!: upgrade Result to prepare migration to sealed classes --- .../lib/src/either/either_base.dart | 49 ++- .../lib/src/either/option.dart | 4 +- .../lib/src/either/result.dart | 325 ---------------- .../src/extensions/iterable_extension.dart | 3 - .../lib/src/extensions/string_extension.dart | 3 - .../wyatt_type_utils/lib/src/pair/pair.dart | 2 + .../{either => result}/future_or_result.dart | 14 +- .../src/{either => result}/future_result.dart | 12 +- .../lib/src/result/result.dart | 346 ++++++++++++++++++ packages/wyatt_type_utils/lib/src/src.dart | 3 +- packages/wyatt_type_utils/pubspec.yaml | 7 +- .../wyatt_type_utils/test/result_test.dart | 116 +++--- 12 files changed, 452 insertions(+), 432 deletions(-) delete mode 100644 packages/wyatt_type_utils/lib/src/either/result.dart rename packages/wyatt_type_utils/lib/src/{either => result}/future_or_result.dart (96%) rename packages/wyatt_type_utils/lib/src/{either => result}/future_result.dart (96%) create mode 100644 packages/wyatt_type_utils/lib/src/result/result.dart diff --git a/packages/wyatt_type_utils/lib/src/either/either_base.dart b/packages/wyatt_type_utils/lib/src/either/either_base.dart index 2f06414e..aef28c3a 100644 --- a/packages/wyatt_type_utils/lib/src/either/either_base.dart +++ b/packages/wyatt_type_utils/lib/src/either/either_base.dart @@ -1,26 +1,21 @@ -// // 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 +// Copyright (C) 2024 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 'dart:async'; -part 'future_result.dart'; -part 'future_or_result.dart'; -part 'result.dart'; part 'option.dart'; mixin _Left on _EitherBase {} @@ -35,7 +30,9 @@ class _EitherBaseException implements Exception { String toString() => '_EitherException: $message'; } +@Deprecated('Use Dart pattern matching instead') abstract class _EitherBase { + @Deprecated('Use Dart pattern matching instead') const _EitherBase(); bool get _isLeft => this is _Left; @@ -56,20 +53,20 @@ abstract class _EitherBase { if (U == LeftType) { return _fold( (value) => value, - (right) => throw const ResultException( + (right) => throw Exception( 'Illegal use. You should check left value before calling', ), ) as U; } if (U == RightType) { return _fold( - (left) => throw const ResultException( + (left) => throw Exception( 'Illegal use. You should check right value before calling', ), (value) => value, ) as U; } - throw ResultException( + throw Exception( 'Illegal use. You should use $LeftType or $RightType type', ); } @@ -78,20 +75,20 @@ abstract class _EitherBase { if (U == LeftType) { return _foldAsync( Future.value, - (right) => throw const ResultException( + (right) => throw Exception( 'Illegal use. You should check left value before calling', ), ) as Future; } if (U == RightType) { return _foldAsync( - (left) => throw const ResultException( + (left) => throw Exception( 'Illegal use. You should check right value before calling', ), Future.value, ) as Future; } - throw ResultException( + throw Exception( 'Illegal use. You should use $LeftType or $RightType type', ); } diff --git a/packages/wyatt_type_utils/lib/src/either/option.dart b/packages/wyatt_type_utils/lib/src/either/option.dart index 3502984f..f5bb8f89 100644 --- a/packages/wyatt_type_utils/lib/src/either/option.dart +++ b/packages/wyatt_type_utils/lib/src/either/option.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022 WYATT GROUP +// Copyright (C) 2024 WYATT GROUP // Please see the AUTHORS file for details. // // This program is free software: you can redistribute it and/or modify @@ -41,8 +41,10 @@ class OptionException extends _EitherBaseException { /// if value not present) and ifPresent() (execute a block of code if the /// value is present). /// {@endtemplate} +@Deprecated('Use Dart pattern matching instead') abstract class Option extends _EitherBase { /// {@macro option} + @Deprecated('Use Dart pattern matching instead') const Option._(); /// Represents the left side of [Option] class. diff --git a/packages/wyatt_type_utils/lib/src/either/result.dart b/packages/wyatt_type_utils/lib/src/either/result.dart deleted file mode 100644 index c4d03189..00000000 --- a/packages/wyatt_type_utils/lib/src/either/result.dart +++ /dev/null @@ -1,325 +0,0 @@ -// 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 { - /// {@macro ok} - const Ok(this.value) : super._(); - final T value; - - @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 { - /// {@macro err} - const Err(this.error) : super._(); - final E error; - - @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/extensions/iterable_extension.dart b/packages/wyatt_type_utils/lib/src/extensions/iterable_extension.dart index 0d04c775..a0849875 100644 --- a/packages/wyatt_type_utils/lib/src/extensions/iterable_extension.dart +++ b/packages/wyatt_type_utils/lib/src/extensions/iterable_extension.dart @@ -37,13 +37,10 @@ extension IterableIntExtension on Iterable? { switch (to) { case Encoding.utf8: str = utf8.decode(this?.toList() ?? []); - break; case Encoding.utf16: str = String.fromCharCodes(this ?? []); - break; case Encoding.base64: str = base64.encode(this?.toList() ?? []); - break; case Encoding.base16: str = List.generate( (this ?? []).length, diff --git a/packages/wyatt_type_utils/lib/src/extensions/string_extension.dart b/packages/wyatt_type_utils/lib/src/extensions/string_extension.dart index db75dd62..9510340c 100644 --- a/packages/wyatt_type_utils/lib/src/extensions/string_extension.dart +++ b/packages/wyatt_type_utils/lib/src/extensions/string_extension.dart @@ -33,13 +33,10 @@ extension StringExtension on String? { switch (from) { case Encoding.utf8: bytes = utf8.encode(this ?? '').toTypedList(); - break; case Encoding.utf16: bytes = (this ?? '').runes.toList().toTypedList(); - break; case Encoding.base64: bytes = base64.decode(this ?? ''); - break; case Encoding.base16: assert( (this ?? '').length.isEven, diff --git a/packages/wyatt_type_utils/lib/src/pair/pair.dart b/packages/wyatt_type_utils/lib/src/pair/pair.dart index 2a2104f9..20af8733 100644 --- a/packages/wyatt_type_utils/lib/src/pair/pair.dart +++ b/packages/wyatt_type_utils/lib/src/pair/pair.dart @@ -21,8 +21,10 @@ extension PairExtension on Pair { /// {@template pair} /// [Pair] is a simple object which contains pair of two values. /// {@endtemplate} +@Deprecated('Use Dart built-in record type instead') class Pair { /// {@macro pair} + @Deprecated('Use Dart built-in record type instead') const Pair(this.left, this.right); final L? left; final R? right; diff --git a/packages/wyatt_type_utils/lib/src/either/future_or_result.dart b/packages/wyatt_type_utils/lib/src/result/future_or_result.dart similarity index 96% rename from packages/wyatt_type_utils/lib/src/either/future_or_result.dart rename to packages/wyatt_type_utils/lib/src/result/future_or_result.dart index 8cf0e652..971a7f78 100644 --- a/packages/wyatt_type_utils/lib/src/either/future_or_result.dart +++ b/packages/wyatt_type_utils/lib/src/result/future_or_result.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022 WYATT GROUP +// Copyright (C) 2024 WYATT GROUP // Please see the AUTHORS file for details. // // This program is free software: you can redistribute it and/or modify @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -part of 'either_base.dart'; +import 'dart:async'; + +import 'package:wyatt_type_utils/src/result/result.dart'; extension FutureOrResultExtension on FutureOr> { /// Represents the left side of [Result] class. @@ -72,13 +74,13 @@ extension FutureOrResultExtension on FutureOr> { Future contains(U x) => Future.value(this).then((result) => result.contains(x)); - /// Returns the contained [Ok] value. Throw [ResultException] on [Err] with - /// its content. + /// Returns the contained [Ok] value. Throw [ResultException] on + /// [Err] with its content. Future expect(String msg) => Future.value(this).then((result) => result.expect(msg)); - /// Returns the contained [Err] value. Throw [ResultException] on [Ok] with - /// its content. + /// Returns the contained [Err] value. Throw [ResultException] on + /// [Ok] with its content. Future expectErr(String msg) => Future.value(this).then((result) => result.expectErr(msg)); diff --git a/packages/wyatt_type_utils/lib/src/either/future_result.dart b/packages/wyatt_type_utils/lib/src/result/future_result.dart similarity index 96% rename from packages/wyatt_type_utils/lib/src/either/future_result.dart rename to packages/wyatt_type_utils/lib/src/result/future_result.dart index 9460b2b4..6358e70a 100644 --- a/packages/wyatt_type_utils/lib/src/either/future_result.dart +++ b/packages/wyatt_type_utils/lib/src/result/future_result.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022 WYATT GROUP +// Copyright (C) 2024 WYATT GROUP // Please see the AUTHORS file for details. // // This program is free software: you can redistribute it and/or modify @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -part of 'either_base.dart'; +import 'package:wyatt_type_utils/src/result/result.dart'; extension FutureResultExtension on Future> { /// Represents the left side of [Result] class. @@ -62,12 +62,12 @@ extension FutureResultExtension on Future> { /// 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. + /// 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. + /// 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 diff --git a/packages/wyatt_type_utils/lib/src/result/result.dart b/packages/wyatt_type_utils/lib/src/result/result.dart new file mode 100644 index 00000000..918562ef --- /dev/null +++ b/packages/wyatt_type_utils/lib/src/result/result.dart @@ -0,0 +1,346 @@ +// Copyright (C) 2024 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_equals_and_hash_code_on_mutable_classes + +import 'package:sealed_result/sealed_result.dart' as sealed; + +export 'future_or_result.dart'; +export 'future_result.dart'; + +class ResultException extends sealed.ResultException { + const ResultException(String super.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 [Success] +/// - failure with [Failure] +/// +/// 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} +class Result { + /// {@macro result} + const Result._(this._result); + + factory Result.ok(Success value) => Ok(value); + factory Result.err(Failure value) => Err(value); + factory Result.error(Failure value) => Err(value); + factory Result.success(Success value) => Ok(value); + factory Result.failure(Failure value) => Err(value); + + final sealed.Result _result; + + /// Represents the left side of [Result] class. + bool get isOk => _result.isOk; + + /// Represents the right side of [Result] class. + bool get isErr => _result.isErr; + + /// Get nullable [Ok] value, and discarding the error, if any. + Success? get ok => _result.ok; + + /// Get nullable [Err] value, and discarding the success value, if any. + Failure? get err => _result.err; + + /// Get [U] value, may throw an exception. + U unwrap() { + if (U == Success) { + return fold( + (value) => value, + (right) => throw const ResultException( + 'Illegal use. You should check left value before calling', + ), + ) as U; + } + if (U == Failure) { + 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 $Success or $Failure type', + ); + } + + /// Get **async** [U] value, may throw an exception. + Future unwrapAsync() { + if (U == Success) { + return foldAsync( + Future.value, + (right) => throw const ResultException( + 'Illegal use. You should check left value before calling', + ), + ) as Future; + } + if (U == Failure) { + 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 $Success or $Failure type', + ); + } + + /// Fold [Ok] and [Err] into the value of one type + U fold( + U Function(Success value) valueTransformer, + U Function(Failure error) errorTransformer, + ) => + _result.fold( + (ok) => valueTransformer(ok), + (err) => errorTransformer(err), + ); + + /// Fold [Ok] and [Err] **asynchronously** into the value of one type + Future foldAsync( + Future Function(Success value) valueTransformer, + Future Function(Failure error) errorTransformer, + ) => + _result.foldAsync( + (ok) => valueTransformer(ok), + (err) => errorTransformer(err), + ); + + /// 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) => Result._( + _result.and(res._result), + ); + + /// Returns [res] if the [Result] is [Err], otherwise returns + /// the [Ok] value of this. + Result or(Result res) => Result._( + _result.or(res._result), + ); + + /// Returns true if the result is an [Ok] or [Err] value containing + /// the given value/error. + bool contains(U x) { + if (x is Success) { + return _result.contains(x); + } else if (x is Failure) { + return _result.containsErr(x); + } else { + throw ArgumentError('The value must be of type Success or Failure'); + } + } + + /// Returns the contained [Ok] value. Throw [ResultException] on [Err] + /// with its content. + Success expect(String msg) { + try { + return _result.expect(msg); + } on sealed.ResultException catch (e) { + throw ResultException(e.message.toString()); + } + } + + /// Returns the contained [Err] value. Throw [ResultException] on [Ok] + /// with its content. + Failure expectErr(String msg) { + try { + return _result.expectErr(msg); + } on sealed.ResultException catch (e) { + throw ResultException(e.message.toString()); + } + } + + /// Maps a [Result] to [Result] by applying a + /// function to a contained [Ok] value, leaving an [Err] value untouched. + Result map(U Function(Success value) mapper) => + 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(Success value) mapper, + ) async { + final result = await _result.mapAsync(mapper); + return Result._(result); + } + + /// Maps a [Result] to [Result] by applying a + /// function to a contained [Err] value, leaving an [Ok] value untouched. + Result mapErr(F Function(Failure error) mapper) => + 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(Failure error) mapper, + ) async { + final result = await _result.mapErrAsync(mapper); + return Result._(result); + } + + /// Transforms a [Result] to [Result] by applying + /// functions to contained [Ok] and [Err] values. + Result either( + U Function(Success value) valueTransformer, + F Function(Failure error) errorTransformer, + ) => + Result._( + _result + .map( + valueTransformer, + ) + .mapErr( + errorTransformer, + ), + ); + + /// Transforms a [Result] to [Result] by + /// applying **async** functions to contained [Ok] and [Err] values. + Future> eitherAsync( + Future Function(Success value) valueTransformer, + Future Function(Failure error) errorTransformer, + ) => + _result + .mapAsync( + valueTransformer, + ) + .mapErrAsync( + errorTransformer, + ) + .then(Result._); + + /// Constructs a new [Result] from a function that might throw + static Result + tryCatch( + Success Function() tryFn, + Failure 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, + Failure 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( + Success value, + Failure error, { + required bool test, + }) => + test ? Ok(value) : Err(error); + + /// If the condition is satify then return *command* [value] + /// in [Ok] else [error] in [Err] + static Result conditionalLazy( + Success Function() value, + Failure Function() error, + bool Function() predicate, + ) => + predicate.call() ? Ok(value()) : Err(error()); + + @override + int get hashCode => _result.hashCode; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + return other is Result && other._result == _result; + } +} + +final class Ok extends Result { + Ok(this.ok) : super._(sealed.Ok(ok)); + + @override + final Success ok; + + @override + bool get isErr => false; + + @override + bool get isOk => true; + + @override + Failure? get err => null; + + @override + String toString() => switch (ok) { + null => 'Ok(null)', + Future() => 'Ok(${ok.runtimeType})', + _ => 'Ok($ok)', + }; +} + +final class Err extends Result { + Err(this.err) : super._(sealed.Err(err)); + + @override + final Failure err; + + @override + bool get isErr => true; + + @override + bool get isOk => false; + + @override + Success? get ok => null; + + @override + String toString() => switch (err) { + _ => 'Err($err)', + }; +} diff --git a/packages/wyatt_type_utils/lib/src/src.dart b/packages/wyatt_type_utils/lib/src/src.dart index c1d65c3c..61d12cb8 100644 --- a/packages/wyatt_type_utils/lib/src/src.dart +++ b/packages/wyatt_type_utils/lib/src/src.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022 WYATT GROUP +// Copyright (C) 2024 WYATT GROUP // Please see the AUTHORS file for details. // // This program is free software: you can redistribute it and/or modify @@ -17,3 +17,4 @@ export 'either/either_base.dart'; export 'extensions/extensions.dart'; export 'pair/pair.dart'; +export 'result/result.dart'; diff --git a/packages/wyatt_type_utils/pubspec.yaml b/packages/wyatt_type_utils/pubspec.yaml index 1c818afb..57e126a1 100644 --- a/packages/wyatt_type_utils/pubspec.yaml +++ b/packages/wyatt_type_utils/pubspec.yaml @@ -6,11 +6,14 @@ version: 0.0.5 publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub environment: - sdk: ">=2.17.0 <3.0.0" + sdk: "^3.0.0" + +dependencies: + sealed_result: ^3.0.0 dev_dependencies: test: ^1.22.0 wyatt_analysis: hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub - version: ^2.5.0 + version: ^2.6.1 diff --git a/packages/wyatt_type_utils/test/result_test.dart b/packages/wyatt_type_utils/test/result_test.dart index 53706fd5..bf7be242 100644 --- a/packages/wyatt_type_utils/test/result_test.dart +++ b/packages/wyatt_type_utils/test/result_test.dart @@ -21,85 +21,83 @@ void main() { group('Result', () { test('`isOk` returns true on Ok value', () { expect( - const Ok(null).isOk, + Ok(null).isOk, true, ); expect( - const Err(null).isOk, + Err(null).isOk, false, ); }); test('`isErr` returns true on Err value', () { expect( - const Ok(null).isErr, + Ok(null).isErr, false, ); expect( - const Err(null).isErr, + Err(null).isErr, true, ); }); test('`ok` returns value on Ok value', () { - const Result x = Ok(2); + final Result x = Ok(2); expect(x.ok, 2); expect(x.err, null); }); test('`err` returns error on Err value', () { - const Result x = Err('error'); + final Result x = Err('error'); expect(x.ok, null); expect(x.err, 'error'); }); test('unwrap() returns value on Ok value', () { - const Result x = Ok(2); + final 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'); + final 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); + final 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'); + final 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), + Ok('').fold((left) => true, (right) => false), true, ); expect( - const Err('') - .fold((left) => true, (right) => false), + Err('').fold((left) => true, (right) => false), false, ); }); test('foldAsync() returns right value', () async { expect( - await const Ok('').foldAsync( + await Ok('').foldAsync( (left) => Future.value(true), (right) => Future.value(false), ), true, ); expect( - await const Err('').foldAsync( + await Err('').foldAsync( (left) => Future.value(true), (right) => Future.value(false), ), @@ -108,7 +106,7 @@ void main() { }); test('swap() swaps values', () { - const Result x = Ok(10); + final Result x = Ok(10); expect(x.isOk, true); expect(x.isErr, false); expect(x.ok, 10); @@ -122,24 +120,24 @@ void main() { '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'); + final Result x1 = Ok(2); + final Result y1 = Err('late error'); expect(x1.and(y1), y1); - const Result x2 = Err('early error'); - const Result y2 = Ok('foo'); + final Result x2 = Err('early error'); + final Result y2 = Ok('foo'); expect(x2.and(y2), x2); - const Result x3 = Err('not a 2'); - const Result y3 = Err('late error'); + final Result x3 = Err('not a 2'); + final Result y3 = Err('late error'); expect(x3.and(y3), x3); - const Result x4 = Ok(2); - const Result y4 = Ok('different result type'); + final Result x4 = Ok(2); + final Result y4 = Ok('different result type'); expect(x4.and(y4), y4); - const Result x5 = Ok(2); - const Result y5 = Ok(5); + final Result x5 = Ok(2); + final Result y5 = Ok(5); expect(x5.and(y5), y5); }, ); @@ -148,24 +146,24 @@ void main() { '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'); + final Result x1 = Ok(2); + final Result y1 = Err('late error'); expect(x1.or(y1), x1); - const Result x2 = Err('early error'); - const Result y2 = Ok('foo'); + final Result x2 = Err('early error'); + final Result y2 = Ok('foo'); expect(x2.or(y2), y2); - const Result x3 = Err('not a 2'); - const Result y3 = Err('late error'); + final Result x3 = Err('not a 2'); + final Result y3 = Err('late error'); expect(x3.or(y3), y3); - const Result x4 = Ok(2); - const Result y4 = Ok('different result type'); + final Result x4 = Ok(2); + final Result y4 = Ok('different result type'); expect(x4.or(y4), x4); - const Result x5 = Ok(2); - const Result y5 = Ok(5); + final Result x5 = Ok(2); + final Result y5 = Ok(5); expect(x5.or(y5), x5); }, ); @@ -174,10 +172,10 @@ void main() { 'contains() returns true if the result is an Ok value ' 'containing the given value.', () { - const Result x1 = Ok(2); + final Result x1 = Ok(2); expect(x1.contains(2), true); expect(x1.contains(3), false); - const Result x2 = Err('Some error message'); + final Result x2 = Err('Some error message'); expect(x2.contains(2), false); }, ); @@ -186,10 +184,10 @@ void main() { 'expect() return value if the result is an Ok value ' 'else throw Exception', () { - const Result x1 = Ok(2); + final Result x1 = Ok(2); expect(x1.expect('Testing expect'), 2); - const Result x2 = Err('emergency failure'); + final Result x2 = Err('emergency failure'); expect( () => x2.expect('Testing expect'), throwsA( @@ -205,10 +203,10 @@ void main() { 'expectErr() return value if the result is an Err value ' 'else throw Exception', () { - const Result x1 = Err(2); + final Result x1 = Err(2); expect(x1.expectErr('Testing expect err'), 2); - const Result x2 = Ok('success value'); + final Result x2 = Ok('success value'); expect( () => x2.expectErr('Testing expect err'), throwsA( @@ -223,12 +221,12 @@ void main() { test( 'map() transforms Ok value.', () { - const Result x1 = Ok(true); + final Result x1 = Ok(true); expect(x1.ok, true); expect(x1.map((value) => false).ok, false); - const Result x2 = Err('oops'); + final Result x2 = Err('oops'); expect(x2.map((value) => false).isErr, true); }, @@ -237,11 +235,11 @@ void main() { test( 'mapErr() transforms Err value.', () { - const Result x1 = Ok(true); + final Result x1 = Ok(true); expect(x1.mapErr((value) => false).isOk, true); - const Result x2 = Err('oops'); + final Result x2 = Err('oops'); expect(x2.err, 'oops'); expect(x2.mapErr((error) => 'failure').err, 'failure'); @@ -251,12 +249,12 @@ void main() { test( 'mapAsync() transforms Ok value.', () async { - const Result x1 = Ok(true); + final Result x1 = Ok(true); expect(x1.ok, true); expect((await x1.mapAsync((value) => Future.value(false))).ok, false); - const Result x2 = Err('oops'); + final Result x2 = Err('oops'); expect((await x2.mapAsync((value) => Future.value(false))).isErr, true); }, @@ -265,14 +263,14 @@ void main() { test( 'mapErrAsync() transforms Err value.', () async { - const Result x1 = Ok(true); + final Result x1 = Ok(true); expect( (await x1.mapErrAsync((value) => Future.value(false))).isOk, true, ); - const Result x2 = Err('oops'); + final Result x2 = Err('oops'); expect(x2.err, 'oops'); expect( @@ -285,14 +283,14 @@ void main() { test( 'either() transforms values.', () { - const Result x1 = Ok(true); + final 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 x2 = Err(true); final Result y2 = x2.either((value) => 1, (error) => 'error'); @@ -304,7 +302,7 @@ void main() { test( 'eitherAsync() transforms values.', () async { - const Result x1 = Ok(true); + final Result x1 = Ok(true); final Result y1 = await x1.eitherAsync( (value) => Future.value(1), (error) => Future.value('error'), @@ -313,7 +311,7 @@ void main() { expect(y1.isOk, true); expect(y1.ok, 1); - const Result x2 = Err(true); + final Result x2 = Err(true); final Result y2 = await x2.eitherAsync( (value) => Future.value(1), (error) => Future.value('error'), @@ -370,9 +368,9 @@ void main() { 'conditional() returns Result on true test', () { final Result x1 = Result.conditional( - false, 2, 'error', + test: false, ); expect( x1.isErr, @@ -382,9 +380,9 @@ void main() { expect(x1.err, 'error'); final Result x2 = Result.conditional( - true, 2, 'error', + test: true, ); expect( x2.isOk, @@ -399,9 +397,9 @@ void main() { 'conditionalLazy() returns Result on true test', () { final Result x1 = Result.conditionalLazy( - false, () => 2, () => 'error', + () => false, ); expect( x1.isErr, @@ -411,9 +409,9 @@ void main() { expect(x1.err, 'error'); final Result x2 = Result.conditionalLazy( - true, () => 2, () => 'error', + () => true, ); expect( x2.isOk,