├── .github └── workflows │ └── dart.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── lib ├── either.dart └── src │ ├── either.dart │ └── future_extension.dart ├── pubspec.yaml └── test ├── async_either_test.dart └── either_test.dart /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: unittests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | container: 15 | image: dart:latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install dependencies 20 | run: dart pub get 21 | - name: Tests 22 | run: dart test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | *coverage* 5 | .dart_tool/ 6 | .packages 7 | build/ 8 | # If you're building an application, you may want to check-in your pubspec.lock 9 | pubspec.lock 10 | .packages 11 | 12 | # Directory created by dartdoc 13 | # If you don't generate documentation locally you can remove this line. 14 | doc/api/ 15 | 16 | # Avoid committing generated Javascript files: 17 | *.dart.js 18 | *.info.json # Produced by the --dump-info flag. 19 | *.js # When generated by dart2js. Don't specify *.js if your 20 | # project includes source files written in JavaScript. 21 | *.js_ 22 | *.js.deps 23 | *.js.map 24 | 25 | # IntelliJ related 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | 31 | # Visual Studio Code related 32 | .vscode/ 33 | 34 | # Flutter/Dart/Pub related 35 | **/doc/api/ 36 | .dart_tool/ 37 | .flutter-plugins 38 | .packages 39 | .pub-cache/ 40 | .pub/ 41 | build/ 42 | 43 | # Android related 44 | **/android/**/gradle-wrapper.jar 45 | **/android/.gradle 46 | **/android/captures/ 47 | **/android/gradlew 48 | **/android/gradlew.bat 49 | **/android/local.properties 50 | **/android/**/GeneratedPluginRegistrant.java 51 | 52 | # iOS/XCode related 53 | **/ios/**/*.mode1v3 54 | **/ios/**/*.mode2v3 55 | **/ios/**/*.moved-aside 56 | **/ios/**/*.pbxuser 57 | **/ios/**/*.perspectivev3 58 | **/ios/**/*sync/ 59 | **/ios/**/.sconsign.dblite 60 | **/ios/**/.tags* 61 | **/ios/**/.vagrant/ 62 | **/ios/**/DerivedData/ 63 | **/ios/**/Icon? 64 | **/ios/**/Pods/ 65 | **/ios/**/.symlinks/ 66 | **/ios/**/profile 67 | **/ios/**/xcuserdata 68 | **/ios/.generated/ 69 | **/ios/Flutter/App.framework 70 | **/ios/Flutter/Flutter.framework 71 | **/ios/Flutter/Generated.xcconfig 72 | **/ios/Flutter/app.flx 73 | **/ios/Flutter/app.zip 74 | **/ios/Flutter/flutter_assets/ 75 | **/ios/ServiceDefinitions.json 76 | **/ios/Runner/GeneratedPluginRegistrant.* 77 | 78 | # Exceptions to above rules. 79 | !**/ios/**/default.mode1v3 80 | !**/ios/**/default.mode2v3 81 | !**/ios/**/default.pbxuser 82 | !**/ios/**/default.perspectivev3 83 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 84 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: bbfbf1770cca2da7c82e887e4e4af910034800b6 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.0] - Dart 3 support 2 | 3 | *remove deprecated methods from `Future[Either]`* 4 | 5 | `Either` is now [sealed](https://dart.dev/language/class-modifiers#sealed) - this gives you support [pattern matching](https://dart.dev/language/patterns). 6 | 7 | You now have an alternative for the fold and either 8 | 9 | before: 10 | ```dart 11 | 12 | final either = ...; 13 | 14 | final result = either.fold( 15 | (left) => do something with left 16 | (right) => do something with right 17 | ); 18 | 19 | ``` 20 | 21 | after: 22 | ```dart 23 | 24 | final either = ...; 25 | 26 | switch (either) { 27 | case Left(value: final left): 28 | do something with left 29 | case Right(value: final right): 30 | do something with right 31 | } 32 | 33 | ``` 34 | 35 | > Thanks to @dballance for creating the [issue](https://github.com/avdosev/either_dart/issues/7) 36 | 37 | Also, you can still use: \ 38 | `either.isLeft` - check either is left \ 39 | `either.isRight`- check either is right 40 | 41 | ## [0.4.0] - Update Future[Either].fold and Add Either.tryExcept 42 | 43 | Provide more async flexible type support for FutureEither's fold operation. 44 | > Thanks to @lukasbalaz for creating the [issue](https://github.com/avdosev/either_dart/issues/6) 45 | 46 | 47 | add: 48 | * tryExcept - 49 | A simple but powerful constructor for exception handling. Just specify the type of error you expect from the function and you will get `Either` 50 | 51 | 52 | 53 | ## [0.3.0] - Add equality and hash override 54 | 55 | Either now overrides equality and hash. 56 | > Thanks to @memishood for creating the [issue](https://github.com/avdosev/either_dart/issues/5) 57 | 58 | ## [0.2.0] - Update Future[Either] 59 | 60 | Updated to support `FutureOr` instead of `Future`: 61 | * Either: 62 | * thenAsync 63 | * thenLeftAsync 64 | * mapAsync 65 | * mapLeftAsync 66 | * Future[Either]: 67 | * thenRight 68 | * thenLeft 69 | 70 | Updated to support `FutureOr` instead of `T`: 71 | * Future[Either]: 72 | * mapRight 73 | * mapLeft 74 | 75 | 76 | Mark deprecated (not delete): 77 | * Future[Either].thenRightSync 78 | * Future[Either].thenLeftSync 79 | * Future[Either].mapRightAsync 80 | * Future[Either].mapLeftAsync 81 | 82 | The following methods were not needed after `FutureOr` support, so they are marked Deprecated. 83 | 84 | ## [0.1.4] - Update docs 85 | 86 | Update: 87 | * Readme 88 | 89 | (just this) 90 | 91 | ## [0.1.3] - Additional functionality 92 | 93 | Added: 94 | * thenLeft 95 | * thenLeftAsync 96 | * similiar methods for `FutureEither` extension 97 | 98 | ## [0.1.2] - Fixs 99 | 100 | Fixed: 101 | * remove the lint warning from Either.tryCatch 102 | 103 | 104 | ## [0.1.0-nullsafety.2] - Additional functionality and fixes 105 | 106 | Added: 107 | * mapLeftAsync 108 | * `FutureEither` extension 109 | 110 | Fixed: 111 | * change the naming of the async methods of `Either` class 112 | 113 | Changes `Either`: 114 | * asyncThen -> thenAsync 115 | * asyncMap -> mapAsync 116 | 117 | ## [0.1.0-nullsafety.1] - Additional functionality 118 | 119 | Added: 120 | * mapLeft 121 | 122 | Fixed: 123 | * documentation 124 | 125 | ## [0.1.0-nullsafety.0] - Additional functionality 126 | 127 | Added: 128 | * null-safety 129 | * swap method 130 | * const constructor for Left and Right 131 | 132 | Changed: 133 | * rename unite to fold 134 | 135 | ## [0.0.5] - Additional functionality 136 | 137 | Added: 138 | * asyncMap 139 | * asyncThen 140 | 141 | ## [0.0.4] - Bug fixs 142 | 143 | Fixed: 144 | * Not convenient use with autocomplete 145 | 146 | ## [0.0.3] - Additional functionality and testing 147 | 148 | Added: 149 | * Unit test 150 | * Lazy condition 151 | * Flutter example 152 | 153 | ## [0.0.2] - Additional functionality and testing 154 | 155 | Added: 156 | 157 | * Unit test 158 | * creators by: 159 | * condition 160 | * exception 161 | 162 | Fixed: 163 | 164 | * pubspec 165 | * Readme 166 | 167 | ## [0.0.1] - The base implementation either. 168 | Added: 169 | 170 | * Base implementation 171 | * Unit test 172 | * Example with flutter 173 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nikita Avdosev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # either dart · ![build status](https://github.com/avdosev/either_dart/workflows/unittests/badge.svg) 2 | 3 | 4 | The library for error handling and railway oriented programming. 5 | 6 | This library supports async "map" and async "then" hiding the boilerplate of working with asynchronous computations Future\[Either\]. 7 | 8 | ## Installation 9 | 10 | Add to pubspec.yml: 11 | 12 | ``` 13 | dependencies: 14 | either_dart: ... // latest package version 15 | ``` 16 | 17 | ## Documentation 18 | 19 | https://pub.dev/documentation/either_dart/latest/either/either-library.html 20 | 21 | ## Pub dev 22 | 23 | https://pub.dev/packages/either_dart 24 | 25 | ## How to use it? 26 | 27 | Sections: 28 | * [Basic usage](#basic-usage) 29 | * [Advanced usage](#advanced-usage) 30 | * [Case - Solution](#case---solution) 31 | 32 | ### Basic usage 33 | 34 | Create two entities for example, you can use your own abstractions for your project. 35 | 36 | ```dart 37 | enum AppError { 38 | NotFound, 39 | // some errors codes 40 | } 41 | 42 | class MyError { 43 | final AppError key; 44 | final String? message; 45 | 46 | const MyError({ 47 | required this.key, 48 | this.message, 49 | }); 50 | } 51 | ``` 52 | 53 | We can use Either as shown below: 54 | 55 | ```dart 56 | Either getCityNameByCode(int code) { 57 | const cities = { 58 | /// some cities 59 | }; 60 | 61 | if (cities.contains(code)) { 62 | return Right(cities[code]!); 63 | } else { 64 | return Left( 65 | key: AppError.NotFound, 66 | message: '[getCityNameByCode] can`t convert code:$code to city name', 67 | ); 68 | } 69 | } 70 | ``` 71 | 72 | Too, you can use `Either.cond` and `Either.condLazy` for simple cases: 73 | 74 | ```dart 75 | return Either.condLazy(cities.contains(code), 76 | () => cities[code]!, 77 | () => MyError( 78 | key: AppError.NotFound, 79 | message: '[getCityNameByCode] can`t convert code:$code to city name', 80 | ), 81 | ); 82 | ``` 83 | 84 | Either has the following methods: 85 | 86 | ***note:*** \ 87 | L - current `Left` type \ 88 | TL - new generic `Left` type \ 89 | R - current `Right` type \ 90 | TR - new generic `Right` type 91 | 92 | 93 | | name | result | description | 94 | | --- | --- | --- | 95 | | `isLeft` | `bool` | Represents the left side of Either class which by convention is a "Failure". | 96 | | `isRight` | `bool` | Represents the right side of Either class which by convention is a "Success" | 97 | | `left` | `L` | Get Left value, may throw an exception when the value is Right. **read-only** | 98 | | `right` | `R` | Get Right value, may throw an exception when the value is Left. **read-only** | 99 | | `either(TL fnL(L left), TR fnR(R right))` | `Either` | Transform values of Left and Right, equal of `bimap` in fp-libraries 100 | | `fold(T fnL(L left), T fnR(R right))` | `T` | Fold Left and Right into the value of one type 101 | | `map(TR fnR(R right))` | `Either` | Transform value of Right 102 | | `mapLeft(TL fnL(L left))` | `Either` | Transform value of Left 103 | | `mapAsync(FutureOr fnR(R right))` | `Future>` | Transform value of Right 104 | | `mapLeftAsync(FutureOr fnL(L left))` | `Future>` | Transform value of Left 105 | | `swap()` | `Either` | Swap Left and Right 106 | | `then(Either fnR(R right))` | `Either` | Transform value of Right when transformation may be finished with an error 107 | | `thenLeft(Either fnL(L left))` | `Either` | Transform value of Left when transformation may be finished with an Right 108 | | `thenAsync(FutureOr> fnR(R right))` | `Future>` | Transform value of Right when transformation may be finished with an error 109 | | `thenLeftAsync(FutureOr> fnL(L left))` | `Future>` | Transform value of Left when transformation may be finished with an Right 110 | 111 | ### Advanced usage 112 | 113 | This library provides an `FutureEither` extension which is designed to handle asynchronous computation with ease. 114 | 115 | You don't need to import or use new classes to use it - just use `Future>` 116 | 117 | | name | result | description | 118 | | --- | --- | --- | 119 | `either(TL fnL(L left), TR fnR(R right))` | `Future>` | Transform values of Left and Right 120 | `fold(T fnL(L left), T fnR(R right))` | `Future` | Fold Left and Right into the value of one type 121 | `mapRight(FutureOr fnR(R right))` | `Future>` | Transform value of Right 122 | `mapLeft(FutureOr fnL(L left))` | `Future>` | Transform value of Left 123 | `swap()` | `Future>` | Swap Left and Right 124 | `thenRight(FutureOr> fnR(R right))` | `Future>` | Async transform value of Right when transformation may be finished with an error 125 | `thenLeft(FutureOr> fnL(L left))` | `Future>` | Async transform value of Left when transformation may be finished with an Right 126 | 127 | Example: 128 | 129 | ```dart 130 | /// --- helpers --- 131 | 132 | import 'package:either_dart/either.dart'; 133 | import 'package:http/http.dart' as http; 134 | import 'package:flutter/foundation.dart'; 135 | import 'dart:convert'; 136 | 137 | Future> safe(Future request) async { 138 | try { 139 | return Right(await request); 140 | } catch (e) { 141 | return Left(MyError( 142 | key: AppError.BadRequest, 143 | message: "Request executing with errors:$e")); 144 | } 145 | } 146 | 147 | Either checkHttpStatus(http.Response response) { 148 | if (response.statusCode == 200) 149 | return Right(response); 150 | if (response.statusCode >= 500) 151 | return Left(MyError( 152 | key: AppError.ServerError, 153 | message: "Server error with http status ${response.statusCode}")); 154 | return Left(MyError( 155 | key: AppError.BadResponse, 156 | message: "Bad http status ${response.statusCode}")); 157 | } 158 | 159 | Future> parseJson(http.Response response) async { 160 | try { 161 | 162 | return Right(await compute((body) { 163 | final json = JsonCodec(); 164 | return json.decode(body)); 165 | }, response.body); 166 | } catch (e) { 167 | return Left(MyError( 168 | key: AppError.JsonParsing, 169 | message: 'failed on json parsing')); 170 | } 171 | } 172 | 173 | /// --- app code --- 174 | 175 | //// network 176 | Future> getDataFromServer() { 177 | return 178 | safe(http.get(Uri('some uri'))) 179 | .thenRight(checkHttpStatus) 180 | .thenRight(parseJson) 181 | .mapRight(Data.fromJson) 182 | } 183 | 184 | ``` 185 | 186 | ### Case - Solution 187 | 188 | * How I can use the value of `Either`? 189 | 190 | You can use right or left getters, but you should check what value is stored inside (`isLeft` or `isRight`) 191 | 192 | Also, my favorite methods `fold`, `either` 193 | 194 | * `fold` - used when you need to transform two rails to one type 195 | * `either` - used for two situations: 1. when you need transform left and right. 2. when you need to use stored value without next usage (see example). 196 | 197 | Example: 198 | ```dart 199 | /// either method 200 | showNotification(Either value) { 201 | return value.either( 202 | (left) => showWarning(left.message ?? left.key.toString()), 203 | (right) => showInfo(right.toString()), 204 | ); 205 | /// equal 206 | if (value.isLeft) { 207 | final left = value.left; 208 | showWarning(left.message ?? left.key.toString() 209 | } else { 210 | showInfo(value.right.toString()) 211 | } 212 | } 213 | ``` 214 | ```dart 215 | /// fold method 216 | class MyWidget { 217 | final Either> value; 218 | 219 | const MyWidget(this.value); 220 | 221 | Widget build(BuildContext context) { 222 | return Text( 223 | value.fold( 224 | (left) => left.message, 225 | (right) => right.join(', ')), 226 | ); 227 | /// or 228 | return value.fold( 229 | (left) => _buildSomeErrorWidget(context, left), 230 | (right) => _buildSomeRightWidget(context, right), 231 | ); 232 | } 233 | } 234 | ``` 235 | -------------------------------------------------------------------------------- /lib/either.dart: -------------------------------------------------------------------------------- 1 | library either; 2 | 3 | export './src/either.dart'; 4 | export './src/future_extension.dart'; 5 | -------------------------------------------------------------------------------- /lib/src/either.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | typedef Lazy = T Function(); 4 | 5 | /// Represents a value of one of two possible types. 6 | /// Instances of [Either] are either an instance of [Left] or [Right]. 7 | /// 8 | /// [Left] is used for "failure". 9 | /// [Right] is used for "success". 10 | sealed class Either { 11 | const Either(); 12 | 13 | /// Represents the left side of [Either] class which by convention is a "Failure". 14 | bool get isLeft => this is Left; 15 | 16 | /// Represents the right side of [Either] class which by convention is a "Success" 17 | bool get isRight => this is Right; 18 | 19 | /// Get [Left] value, may throw an exception when the value is [Right] 20 | L get left => this.fold( 21 | (value) => value, 22 | (right) => throw Exception( 23 | 'Illegal use. You should check isLeft before calling')); 24 | 25 | /// Get [Right] value, may throw an exception when the value is [Left] 26 | R get right => this.fold( 27 | (left) => throw Exception( 28 | 'Illegal use. You should check isRight before calling'), 29 | (value) => value); 30 | 31 | /// Transform values of [Left] and [Right] 32 | Either either( 33 | TL Function(L left) fnL, TR Function(R right) fnR); 34 | 35 | /// Transform value of [Right] when transformation may be finished with an error 36 | Either then(Either Function(R right) fnR); 37 | 38 | /// Transform value of [Right] when transformation may be finished with an error 39 | Future> thenAsync( 40 | FutureOr> Function(R right) fnR); 41 | 42 | /// Transform value of [Left] when transformation may be finished with an [Right] 43 | Either thenLeft(Either Function(L left) fnL); 44 | 45 | /// Transform value of [Left] when transformation may be finished with an [Right] 46 | Future> thenLeftAsync( 47 | FutureOr> Function(L left) fnL); 48 | 49 | /// Transform value of [Right] 50 | Either map(TR Function(R right) fnR); 51 | 52 | /// Transform value of [Left] 53 | Either mapLeft(TL Function(L left) fnL); 54 | 55 | /// Transform value of [Right] 56 | Future> mapAsync(FutureOr Function(R right) fnR); 57 | 58 | /// Transform value of [Left] 59 | Future> mapLeftAsync(FutureOr Function(L left) fnL); 60 | 61 | /// Fold [Left] and [Right] into the value of one type 62 | T fold(T Function(L left) fnL, T Function(R right) fnR); 63 | 64 | /// Swap [Left] and [Right] 65 | Either swap() => fold((left) => Right(left), (right) => Left(right)); 66 | 67 | /// Constructs a new [Either] from a function that might throw 68 | static Either tryCatch( 69 | L Function(Err err) onError, R Function() fnR) { 70 | try { 71 | return Right(fnR()); 72 | } on Err catch (e) { 73 | return Left(onError(e)); 74 | } 75 | } 76 | 77 | /// Constructs a new [Either] from a function that might throw 78 | /// 79 | /// simplified version of [Either.tryCatch] 80 | /// 81 | /// ```dart 82 | /// final fileOrError = Either.tryExcept(() => /* maybe throw */); 83 | /// ``` 84 | static Either tryExcept(R Function() fnR) { 85 | try { 86 | return Right(fnR()); 87 | } on Err catch (e) { 88 | return Left(e); 89 | } 90 | } 91 | 92 | /// If the condition is true then return [rightValue] in [Right] else [leftValue] in [Left] 93 | static Either cond(bool test, L leftValue, R rightValue) => 94 | test ? Right(rightValue) : Left(leftValue); 95 | 96 | /// If the condition is true then return [rightValue] in [Right] else [leftValue] in [Left] 97 | static Either condLazy( 98 | bool test, Lazy leftValue, Lazy rightValue) => 99 | test ? Right(rightValue()) : Left(leftValue()); 100 | 101 | @override 102 | bool operator ==(Object obj) { 103 | return this.fold( 104 | (left) => obj is Left && left == obj.value, 105 | (right) => obj is Right && right == obj.value, 106 | ); 107 | } 108 | 109 | @override 110 | int get hashCode => fold((left) => left.hashCode, (right) => right.hashCode); 111 | } 112 | 113 | /// Used for "failure" 114 | class Left extends Either { 115 | final L value; 116 | 117 | const Left(this.value); 118 | 119 | @override 120 | Either either( 121 | TL Function(L left) fnL, TR Function(R right) fnR) { 122 | return Left(fnL(value)); 123 | } 124 | 125 | @override 126 | Either then(Either Function(R right) fnR) { 127 | return Left(value); 128 | } 129 | 130 | @override 131 | Future> thenAsync( 132 | FutureOr> Function(R right) fnR) { 133 | return Future.value(Left(value)); 134 | } 135 | 136 | @override 137 | Either thenLeft(Either Function(L left) fnL) { 138 | return fnL(value); 139 | } 140 | 141 | @override 142 | Future> thenLeftAsync( 143 | FutureOr> Function(L left) fnL) { 144 | return Future.value(fnL(value)); 145 | } 146 | 147 | @override 148 | Either map(TR Function(R right) fnR) { 149 | return Left(value); 150 | } 151 | 152 | @override 153 | Either mapLeft(TL Function(L left) fnL) { 154 | return Left(fnL(value)); 155 | } 156 | 157 | @override 158 | Future> mapAsync(FutureOr Function(R right) fnR) { 159 | return Future.value(Left(value)); 160 | } 161 | 162 | @override 163 | Future> mapLeftAsync(FutureOr Function(L left) fnL) { 164 | return Future.value(fnL(value)).then((value) => Left(value)); 165 | } 166 | 167 | @override 168 | T fold(T Function(L left) fnL, T Function(R right) fnR) { 169 | return fnL(value); 170 | } 171 | } 172 | 173 | /// Used for "success" 174 | class Right extends Either { 175 | final R value; 176 | 177 | const Right(this.value); 178 | 179 | @override 180 | Either either( 181 | TL Function(L left) fnL, TR Function(R right) fnR) { 182 | return Right(fnR(value)); 183 | } 184 | 185 | @override 186 | Either then(Either Function(R right) fnR) { 187 | return fnR(value); 188 | } 189 | 190 | @override 191 | Future> thenAsync( 192 | FutureOr> Function(R right) fnR) { 193 | return Future.value(fnR(value)); 194 | } 195 | 196 | @override 197 | Either thenLeft(Either Function(L left) fnL) { 198 | return Right(value); 199 | } 200 | 201 | @override 202 | Future> thenLeftAsync( 203 | FutureOr> Function(L left) fnL) { 204 | return Future.value(Right(value)); 205 | } 206 | 207 | @override 208 | Either map(TR Function(R right) fnR) { 209 | return Right(fnR(value)); 210 | } 211 | 212 | @override 213 | Either mapLeft(TL Function(L left) fnL) { 214 | return Right(value); 215 | } 216 | 217 | @override 218 | Future> mapAsync(FutureOr Function(R right) fnR) { 219 | return Future.value(fnR(value)).then((value) => Right(value)); 220 | } 221 | 222 | @override 223 | Future> mapLeftAsync(FutureOr Function(L left) fnL) { 224 | return Future.value(Right(value)); 225 | } 226 | 227 | @override 228 | T fold(T Function(L left) fnL, T Function(R right) fnR) { 229 | return fnR(value); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /lib/src/future_extension.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import './either.dart'; 4 | 5 | extension FutureEither on Future> { 6 | /// Represents the left side of [Either] class which by convention is a "Failure". 7 | Future get isLeft => this.then((either) => either.isLeft); 8 | 9 | /// Represents the right side of [Either] class which by convention is a "Success" 10 | Future get isRight => this.then((either) => either.isRight); 11 | 12 | /// Transform values of [Left] and [Right] 13 | Future> either( 14 | TL Function(L left) fnL, TR Function(R right) fnR) => 15 | this.then((either) => either.either(fnL, fnR)); 16 | 17 | /// Transform value of [Right] 18 | Future> mapRight(FutureOr Function(R right) fnR) => 19 | this.then((either) => either.mapAsync(fnR)); 20 | 21 | /// Transform value of [Left] 22 | Future> mapLeft(FutureOr Function(L left) fnL) => 23 | this.then((either) => either.mapLeftAsync(fnL)); 24 | 25 | /// Async transform value of [Right] when transformation may be finished with an error 26 | Future> thenRight( 27 | FutureOr> Function(R right) fnR) => 28 | this.then((either) => either.thenAsync(fnR)); 29 | 30 | /// Async transform value of [Left] when transformation may be finished with an [Right] 31 | Future> thenLeft( 32 | FutureOr> Function(L left) fnL) => 33 | this.then((either) => either.thenLeftAsync(fnL)); 34 | 35 | /// Fold [Left] and [Right] into the value of one type 36 | Future fold( 37 | FutureOr Function(L left) fnL, 38 | FutureOr Function(R right) fnR, 39 | ) { 40 | return this.then((either) => either.fold(fnL, fnR)); 41 | } 42 | 43 | /// Swap [Left] and [Right] 44 | Future> swap() => 45 | this.fold>((left) => Right(left), (right) => Left(right)); 46 | } 47 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: either_dart 2 | description: Error handler library for type-safe and easy work with errors on Dart and Flutter. Either is an alternative to Nullable value and Exceptions. 3 | version: 1.0.0 4 | homepage: https://github.com/avdosev 5 | repository: https://github.com/avdosev/either_dart 6 | 7 | environment: 8 | sdk: ">=3.0.0 <4.0.0" 9 | 10 | dependencies: 11 | 12 | dev_dependencies: 13 | test: ^1.24.3 14 | -------------------------------------------------------------------------------- /test/async_either_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:test/test.dart'; 4 | 5 | import 'package:either_dart/either.dart'; 6 | 7 | extension XFuture on FutureOr { 8 | Future get future => Future.value(this); 9 | } 10 | 11 | extension XValue on T { 12 | Future get future => Future.value(this); 13 | } 14 | 15 | void main() { 16 | test('right generic is right', () async { 17 | final maybe = Right(true).future; 18 | expect(await maybe.isRight, true); 19 | expect(await maybe.isLeft, false); 20 | }); 21 | 22 | test('left generic is left', () async { 23 | final maybe = Left("true").future; 24 | expect(await maybe.isLeft, true); 25 | expect(await maybe.isRight, false); 26 | }); 27 | 28 | test('map right', () async { 29 | final maybe = Right(true).future; 30 | await maybe.either( 31 | (left) => expect(false, true), (right) => expect(right, true)); 32 | 33 | await maybe 34 | .mapRight((right) => false) 35 | .either((left) => expect(false, true), (right) => expect(right, false)); 36 | 37 | await maybe.either( 38 | (left) => expect(false, true), (right) => expect(right, true)); 39 | }); 40 | 41 | test('map left', () async { 42 | final maybe = Left(true).future; 43 | await maybe.either( 44 | (left) => expect(left, true), (right) => expect(false, true)); 45 | 46 | await maybe 47 | .mapRight((right) => false) 48 | .either((left) => expect(left, true), (right) => expect(true, false)); 49 | ; 50 | }); 51 | 52 | test('mapLeft right', () async { 53 | final maybe = Right(true).future; 54 | 55 | await maybe 56 | .mapLeft((left) => false) 57 | .either((left) => expect(false, true), (right) => expect(right, true)); 58 | }); 59 | 60 | test('mapLeft left', () async { 61 | final maybe = Left(true).future; 62 | 63 | await maybe 64 | .mapLeft((left) => false) 65 | .either((left) => expect(left, false), (right) => expect(true, false)); 66 | }); 67 | 68 | test('fold', () async { 69 | expect( 70 | await Left("").future.fold( 71 | (left) => true, 72 | (right) => false, 73 | ), 74 | true); 75 | expect( 76 | await Right("").future.fold( 77 | (left) => true, 78 | (right) => false, 79 | ), 80 | false); 81 | }); 82 | 83 | test('async fold', () async { 84 | expect( 85 | await Left("").future.fold( 86 | (left) async { 87 | await Future.delayed(const Duration(milliseconds: 500)); 88 | 89 | return true; 90 | }, 91 | (right) => false, 92 | ), 93 | true); 94 | expect( 95 | await Right("").future.fold( 96 | (left) => true, 97 | (right) async { 98 | await Future.delayed(const Duration(milliseconds: 500)); 99 | 100 | return false; 101 | }, 102 | ), 103 | false); 104 | }); 105 | 106 | test('swap', () async { 107 | final maybe = Right(true).future.swap(); 108 | expect(await maybe.isRight, false); 109 | expect(await maybe.isLeft, true); 110 | 111 | final maybe2 = Left(true).future.swap(); 112 | expect(await maybe2.isRight, true); 113 | expect(await maybe2.isLeft, false); 114 | 115 | final maybe3 = maybe.swap(); 116 | expect(await maybe3.isRight, true); 117 | expect(await maybe3.isLeft, false); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /test/either_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:either_dart/either.dart'; 4 | 5 | void main() { 6 | test('right generic is right', () { 7 | final maybe = Right(true); 8 | expect(maybe.isRight, true); 9 | expect(maybe.isLeft, false); 10 | expect(() => maybe.left, throwsA(isException)); 11 | expect(maybe.right, true); 12 | }); 13 | 14 | test('left generic is left', () { 15 | final maybe = Left("true"); 16 | expect(maybe.isLeft, true); 17 | expect(maybe.isRight, false); 18 | expect(() => maybe.right, throwsA(isException)); 19 | expect(maybe.left, "true"); 20 | }); 21 | 22 | test('map right', () { 23 | final maybe = Right(true); 24 | maybe.either((left) => expect(false, true), (right) => expect(right, true)); 25 | 26 | maybe 27 | .map((right) => false) 28 | .either((left) => expect(false, true), (right) => expect(right, false)); 29 | 30 | maybe.either((left) => expect(false, true), (right) => expect(right, true)); 31 | }); 32 | 33 | test('map left', () { 34 | final maybe = Left(true); 35 | maybe.either((left) => expect(left, true), (right) => expect(false, true)); 36 | 37 | maybe 38 | .map((right) => false) 39 | .either((left) => expect(left, true), (right) => expect(true, false)); 40 | ; 41 | }); 42 | 43 | test('mapLeft right', () { 44 | final maybe = Right(true); 45 | 46 | maybe 47 | .mapLeft((left) => false) 48 | .either((left) => expect(false, true), (right) => expect(right, true)); 49 | }); 50 | 51 | test('mapLeft left', () { 52 | final maybe = Left(true); 53 | 54 | maybe 55 | .mapLeft((left) => false) 56 | .either((left) => expect(left, false), (right) => expect(true, false)); 57 | }); 58 | 59 | test('fold', () { 60 | expect( 61 | Left("").fold((left) => true, (right) => false), 62 | true); 63 | expect( 64 | Right("").fold((left) => true, (right) => false), 65 | false); 66 | }); 67 | 68 | test('cond', () { 69 | expect(Either.cond(true, "left", "right").isRight, true); 70 | expect(Either.cond(false, "left", "right").isLeft, true); 71 | expect( 72 | Either.condLazy(true, () => throw Exception("not lazy"), () => "right") 73 | .isRight, 74 | true); 75 | expect( 76 | Either.condLazy(false, () => "left", () => throw Exception("not lazy")) 77 | .isLeft, 78 | true); 79 | }); 80 | 81 | test('swap', () { 82 | final maybe = Right(true).swap(); 83 | expect(maybe.isRight, false); 84 | expect(maybe.isLeft, true); 85 | 86 | final maybe2 = Left(true).swap(); 87 | expect(maybe2.isRight, true); 88 | expect(maybe2.isLeft, false); 89 | 90 | final maybe3 = maybe.swap(); 91 | expect(maybe3.isRight, true); 92 | expect(maybe3.isLeft, false); 93 | }); 94 | 95 | test('tryCatch', () { 96 | expect( 97 | Either.tryCatch( 98 | (err) => true, () => throw Exception("not right")).isLeft, 99 | true); 100 | expect( 101 | Either.tryCatch((err) => false, () => "right") 102 | .isRight, 103 | true); 104 | }); 105 | 106 | group('equal', () { 107 | test('Left and Left', () { 108 | final x = Left('test'); 109 | final z = Left('test'); 110 | expect(x, z); 111 | expect(x.hashCode, z.hashCode); 112 | expect(x == z, true); 113 | expect(x != z, false); 114 | expect(Left('1111'), isNot(equals(Left('2222')))); 115 | expect(Left('1111'), equals(Left('1111'))); 116 | expect(Left(null), Left(null)); 117 | expect(Left(null).hashCode, Left(null).hashCode); 118 | }); 119 | 120 | test('Right and Left', () { 121 | final x = Right('test'); 122 | final z = Left('test'); 123 | expect(x != z, true); 124 | expect(x == z, false); 125 | 126 | expect(Left('1111'), isNot(equals(Right('2222')))); 127 | expect(Left('1111'), isNot(equals(Right('1111')))); 128 | expect(Right('1111'), isNot(equals(Left('2222')))); 129 | expect(Right('1111'), isNot(equals(Left('1111')))); 130 | expect(Left(null), isNot(equals(Right(null)))); 131 | expect(Right(null), isNot(equals(Left(null)))); 132 | }); 133 | 134 | test('Right and Right', () { 135 | final x = Right('test'); 136 | final z = Right('test'); 137 | 138 | expect(x, z); 139 | expect(x.hashCode, z.hashCode); 140 | expect(x == z, true); 141 | expect(x != z, false); 142 | expect(Right('1111'), isNot(equals(Right('2222')))); 143 | expect(Right('1111'), equals(Right('1111'))); 144 | expect(Right(null), Right(null)); 145 | expect(Right(null).hashCode, Right(null).hashCode); 146 | }); 147 | }); 148 | group('pattern matching', () { 149 | test('base', () { 150 | final Either either = Left(""); 151 | 152 | late bool ok; 153 | switch (either) { 154 | case Left(): 155 | ok = true; 156 | case Right(): 157 | ok = false; 158 | } 159 | 160 | expect(ok, true); 161 | 162 | final Either either2 = Right(""); 163 | 164 | switch (either2) { 165 | case Left(): 166 | ok = false; 167 | case Right(): 168 | ok = true; 169 | } 170 | 171 | expect(ok, true); 172 | }); 173 | 174 | test('extract left', () { 175 | final either = Either.cond(false, 'left', 'right'); 176 | 177 | switch (either) { 178 | case Left(value: final value): 179 | expect(value, 'left'); 180 | case Right(value: final _): 181 | fail('newer been right'); 182 | } 183 | }); 184 | 185 | test('extract right', () { 186 | final either = Either.cond(true, 'left', 'right'); 187 | 188 | switch (either) { 189 | case Left(value: final _): 190 | fail('newer been left'); 191 | case Right(value: final value): 192 | expect(value, 'right'); 193 | } 194 | }); 195 | }); 196 | } 197 | --------------------------------------------------------------------------------