diff --git a/packages/fpdart/lib/src/effect.dart b/packages/fpdart/lib/src/effect.dart index b834a04..f9d6bee 100644 --- a/packages/fpdart/lib/src/effect.dart +++ b/packages/fpdart/lib/src/effect.dart @@ -241,7 +241,10 @@ final class Effect extends IEffect { static Effect> all( Iterable> iterable, ) => - Effect.forEach(iterable, (a, _) => a); + Effect.forEach( + iterable, + (effect, _) => effect, + ); /// {@category zipping} Effect zipWith( @@ -283,22 +286,33 @@ final class Effect extends IEffect { ), ); + Effect _provideEnvCloseScope(E env) => + env is ScopeMixin && !env.scopeClosable + ? Effect.from( + (context) => _unsafeRun(context).then( + (exit) => switch (exit) { + Left(value: final value) => Left(value), + Right(value: final value) => + env.closeScope()._unsafeRun(context).then( + (exit) => switch (exit) { + Left(value: final value) => Left(value), + Right() => Right(value), + }, + ), + }, + ), + ) + : this; + /// {@category context} - Effect provide(Context context) { - final env = context.env; - final effect = env is ScopeMixin && !env.scopeClosable - ? alwaysIgnore(env.closeScope()) - : this; - return Effect.from((_) => effect._unsafeRun(context)); - } + Effect provide(Context context) => Effect.from( + (_) => _provideEnvCloseScope(context.env)._unsafeRun(context), + ); /// {@category context} - Effect provideEnv(E env) { - final effect = env is ScopeMixin && !env.scopeClosable - ? alwaysIgnore(env.closeScope()) - : this; - return Effect.from((_) => effect._unsafeRun(Context.env(env))); - } + Effect provideEnv(E env) => Effect.from( + (_) => _provideEnvCloseScope(env)._unsafeRun(Context.env(env)), + ); /// {@category context} Effect provideEffect(Effect effect) => Effect.from( @@ -682,14 +696,11 @@ extension EffectWithScopeFinalizer } extension EffectNoScopeFinalizer on Effect { - /// {@category scoping} - Effect, L, R> get withScope => Effect, L, R>.from( - (ctx) => _unsafeRun(ctx.withEnv(ctx.env.env)), - ); - /// {@category scoping} Effect, L, R> addFinalizer(Effect release) => - withScope.tapEnv( + Effect, L, R>.from( + (context) => _unsafeRun(context.withEnv(context.env.env)), + ).tapEnv( (_, env) => env.addScopeFinalizer(release), ); @@ -697,18 +708,20 @@ extension EffectNoScopeFinalizer on Effect { Effect, L, R> acquireRelease( Effect Function(R r) release, ) => - withScope.tapEnv( - (r, _) => _.addScopeFinalizer(release(r)), + Effect, L, R>.from( + (context) => _unsafeRun(context.withEnv(context.env.env)), + ).tapEnv( + (r, env) => env.addScopeFinalizer( + release(r), + ), ); } extension EffectWithScope on Effect, L, R> { - /// {@category scoping} + /// {@category context} Effect get provideScope => Effect.from((context) { final scope = Scope.withEnv(context.env); - return alwaysIgnore(scope.closeScope())._unsafeRun( - context.withEnv(scope), - ); + return _provideEnvCloseScope(scope)._unsafeRun(context.withEnv(scope)); }); } diff --git a/packages/fpdart/test/src/effect/deferred_test.dart b/packages/fpdart/test/src/deferred_test.dart similarity index 100% rename from packages/fpdart/test/src/effect/deferred_test.dart rename to packages/fpdart/test/src/deferred_test.dart diff --git a/packages/fpdart/test/src/effect/effect_context_test.dart b/packages/fpdart/test/src/effect/effect_context_test.dart new file mode 100644 index 0000000..4d37516 --- /dev/null +++ b/packages/fpdart/test/src/effect/effect_context_test.dart @@ -0,0 +1,83 @@ +import "package:fpdart/fpdart.dart"; +import "package:test/test.dart"; + +import "../test_extension.dart"; + +class CustomError implements Exception { + const CustomError(); +} + +void main() { + group( + "Effect context", + () { + group('provideEnv', () { + test('handle throw in closing scope', () async { + final main = Effect.succeed(10) + .addFinalizer(Effect.succeedLazy( + () => throw const CustomError(), + )) + .provideEnv(Scope.withEnv(null)); + + final result = await main.runFutureExit(); + + result.expectLeft((value) { + expect(value, isA()); + if (value is Die) { + expect(value.error, isA()); + } + }); + }); + + test('handle failure in closing scope', () async { + final main = Effect.succeed(10) + .addFinalizer(Effect.die(const CustomError())) + .provideEnv(Scope.withEnv(null)); + + final result = await main.runFutureExit(); + + result.expectLeft((value) { + expect(value, isA()); + if (value is Die) { + expect(value.error, isA()); + } + }); + }); + }); + + group('provideScope', () { + test('handle throw in closing scope', () async { + final main = Effect.succeed(10) + .addFinalizer(Effect.succeedLazy( + () => throw const CustomError(), + )) + .provideScope; + + final result = await main.runFutureExit(); + + result.expectLeft((value) { + expect(value, isA()); + if (value is Die) { + expect(value.error, isA()); + } + }); + }); + + test('handle failure in closing scope', () async { + final main = Effect.succeed(10) + .addFinalizer(Effect.die(const CustomError())) + .provideScope; + + final result = await main.runFutureExit(); + + result.expectLeft((value) { + expect(value, isA()); + if (value is Die) { + expect(value.error, isA()); + } + }); + }); + }); + }, + ); +} diff --git a/packages/fpdart/test/src/effect/effect_interruption_test.dart b/packages/fpdart/test/src/effect/effect_interruption_test.dart index 7855a3f..820721d 100644 --- a/packages/fpdart/test/src/effect/effect_interruption_test.dart +++ b/packages/fpdart/test/src/effect/effect_interruption_test.dart @@ -1,6 +1,8 @@ import "package:fpdart/fpdart.dart"; import "package:test/test.dart"; +import "../test_extension.dart"; + void main() { group( "Effect interruption", @@ -12,12 +14,10 @@ void main() { ); final result = main.runSyncExit(); - switch (result) { - case Right(): - fail("Either expected to be Left: $result"); - case Left(value: final value): - expect(value, isA()); - } + + result.expectLeft((value) { + expect(value, isA()); + }); }); }); }, diff --git a/packages/fpdart/test/src/test_extension.dart b/packages/fpdart/test/src/test_extension.dart new file mode 100644 index 0000000..8f9b6f2 --- /dev/null +++ b/packages/fpdart/test/src/test_extension.dart @@ -0,0 +1,22 @@ +import 'package:fpdart/fpdart.dart'; +import 'package:test/test.dart'; + +extension EitherTest on Either { + void expectLeft(Function(L value) onLeft) { + switch (this) { + case Right(value: final value): + fail("Either expected to be Left, Right instead: $value"); + case Left(value: final value): + onLeft(value); + } + } + + void expectRight(Function(R value) onRight) { + switch (this) { + case Right(value: final value): + onRight(value); + case Left(value: final value): + fail("Either expected to be Right, Left instead: $value"); + } + } +}