diff --git a/examples/poke_api/analysis_options.yaml b/examples/poke_api/analysis_options.yaml new file mode 100644 index 0000000..d875ed0 --- /dev/null +++ b/examples/poke_api/analysis_options.yaml @@ -0,0 +1,11 @@ +include: package:lints/recommended.yaml + +linter: + rules: + annotate_overrides: true + +analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true diff --git a/examples/poke_api/lib/main.dart b/examples/poke_api/lib/main.dart index 546b055..43657e9 100644 --- a/examples/poke_api/lib/main.dart +++ b/examples/poke_api/lib/main.dart @@ -1,47 +1,59 @@ import 'dart:convert'; import 'package:fpdart/fpdart.dart'; +import 'package:http/http.dart' as http; import 'package:poke_api/constants.dart'; import 'package:poke_api/pokemon.dart'; import 'package:poke_api/pokemon_error.dart'; abstract interface class HttpClient { - String get(Uri uri); + Effect get(Uri uri); } -Effect<(HttpClient, JsonCodec), PokemonError, Pokemon> program( +class Http implements HttpClient { + @override + Effect get(Uri uri) => Effect.gen( + ($) async { + final response = await $.async(Effect.tryCatch( + execute: () => http.get(uri), + onError: (error, stackTrace) => const GetPokemonRequestError(), + )); + + return response.body; + }, + ); +} + +typedef Env = (HttpClient, JsonCodec); + +Effect program( String pokemonId, ) => Effect.gen(($) async { final (client, json) = $.sync(Effect.env()); - final id = $.sync( - Either.fromNullable( - int.tryParse(pokemonId), - PokemonIdNotInt.new, - ), - ); + final id = $.sync(Either.fromNullable( + int.tryParse(pokemonId), + PokemonIdNotInt.new, + ).provide()); if (id < Constants.minimumPokemonId && id > Constants.maximumPokemonId) { return $.sync(Effect.fail(const InvalidPokemonIdRange())); } final uri = Uri.parse(Constants.requestAPIUrl(id)); - final body = await $.async(Effect.tryCatch( - execute: () => client.get(uri), - onError: (_, __) => const GetPokemonRequestError(), - )); + final body = await $.async(client.get(uri).provideVoid()); final bodyJson = $.sync(Either.tryCatch( execute: () => json.decode(body), onError: (_, __) => const PokemonJsonDecodeError(), - )); + ).provide()); final bodyJsonMap = $.sync>( - Either.safeCastStrict( + Either.safeCastStrict, dynamic>( bodyJson, (value) => const PokemonJsonInvalidMap(), - ), + ).provide(), ); return $.sync(Effect.tryCatch( @@ -49,3 +61,14 @@ Effect<(HttpClient, JsonCodec), PokemonError, Pokemon> program( onError: (_, __) => const PokemonInvalidJsonModel(), )); }); + +void main() async { + await program("721") + .map((pokemon) => print(pokemon)) + .catchError( + (error) => Effect.function( + () => print("No pokemon: $error"), + ), + ) + .runFuture((Http(), JsonCodec())); +} diff --git a/examples/poke_api/lib/pokemon.dart b/examples/poke_api/lib/pokemon.dart index 34409b1..6008654 100644 --- a/examples/poke_api/lib/pokemon.dart +++ b/examples/poke_api/lib/pokemon.dart @@ -17,4 +17,9 @@ class Pokemon { height: json['height'] as int, weight: json['weight'] as int, ); + + @override + String toString() { + return "Pokemon(id:$id, name:$name, height:$height, weight:$weight)"; + } } diff --git a/examples/poke_api/pubspec.yaml b/examples/poke_api/pubspec.yaml index 423285d..1988048 100644 --- a/examples/poke_api/pubspec.yaml +++ b/examples/poke_api/pubspec.yaml @@ -10,6 +10,7 @@ environment: sdk: ">=3.3.0 <4.0.0" dependencies: + http: ^1.2.1 fpdart: path: ../../packages/fpdart diff --git a/packages/fpdart/lib/src/effect.dart b/packages/fpdart/lib/src/effect.dart index 221c589..9e274fd 100644 --- a/packages/fpdart/lib/src/effect.dart +++ b/packages/fpdart/lib/src/effect.dart @@ -425,3 +425,8 @@ extension ProvideNever on Effect { /// {@category execution} Future> runFutureExitNoEnv() async => _unsafeRun(null); } + +extension ProvideVoid on Effect { + /// {@category execution} + Effect provideVoid() => provide((env) {}); +} diff --git a/packages/fpdart/lib/src/either.dart b/packages/fpdart/lib/src/either.dart index 5841e66..d3b1892 100644 --- a/packages/fpdart/lib/src/either.dart +++ b/packages/fpdart/lib/src/either.dart @@ -1,6 +1,6 @@ part of "effect.dart"; -sealed class Either extends IEffect { +sealed class Either extends IEffect { const Either(); /// If calling `predicate` with `r` returns `true`, then return `Right(r)`. diff --git a/packages/fpdart/lib/src/extension/future_or_extension.dart b/packages/fpdart/lib/src/extension/future_or_extension.dart index 578a99b..eade6c6 100644 --- a/packages/fpdart/lib/src/extension/future_or_extension.dart +++ b/packages/fpdart/lib/src/extension/future_or_extension.dart @@ -1,11 +1,8 @@ import 'dart:async'; extension FutureOrThenExtension on FutureOr { - FutureOr then(FutureOr Function(A a) f) { - if (this is Future) { - return (this as Future).then(f); - } - - return f(this as A); - } + FutureOr then(FutureOr Function(A a) f) => switch (this) { + final Future self => self.then(f), + final A self => f(self), + }; }