diff --git a/src/Flo/Builder.cs b/src/Flo/Builder.cs index e3257ab..6131a6f 100644 --- a/src/Flo/Builder.cs +++ b/src/Flo/Builder.cs @@ -59,6 +59,29 @@ public TBuilder When( }); } + public TBuilder When( + Func> predicate, + Func>, Func>, Task> handler, + Action configurePipeline) + { + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + if (handler == null) throw new ArgumentNullException(nameof(handler)); + if (configurePipeline == null) throw new ArgumentNullException(nameof(configurePipeline)); + + return Add(async (input, next) => + { + var doInvoke = await predicate.Invoke(input); + if (doInvoke) + { + var builder = CreateBuilder(); + configurePipeline(builder); + return await handler.Invoke(input, builder.Build(), next); + } + + return await next.Invoke(input); + }); + } + protected abstract TBuilder CreateBuilder(); public TBuilder Final(Func> handler) diff --git a/src/Flo/OutputPipelineBuilder.cs b/src/Flo/OutputPipelineBuilder.cs index 8ca0fc6..5a84c34 100644 --- a/src/Flo/OutputPipelineBuilder.cs +++ b/src/Flo/OutputPipelineBuilder.cs @@ -31,6 +31,24 @@ public OutputPipelineBuilder When( configurePipeline); } + public OutputPipelineBuilder When( + Func> predicate, + Action> configurePipeline, + Func continueIf = null) + { + return When(predicate, async (input, innerPipeline, next) => + { + var result = await innerPipeline.Invoke(input); + + // Continue on to the parent pipeline if the continueIf output predicate matches + if ((continueIf ?? DefaultContinuation).Invoke(result)) + return await next.Invoke(input); + + return result; + }, + configurePipeline); + } + protected override OutputPipelineBuilder CreateBuilder() => new OutputPipelineBuilder(ServiceProvider); } } \ No newline at end of file diff --git a/src/Flo/PipelineBuilder.cs b/src/Flo/PipelineBuilder.cs index 938b2eb..8ab3fc2 100644 --- a/src/Flo/PipelineBuilder.cs +++ b/src/Flo/PipelineBuilder.cs @@ -24,6 +24,18 @@ public PipelineBuilder When( configurePipeline); } + public PipelineBuilder When( + Func> predicate, + Action> configurePipeline) + { + return When(predicate, async (input, innerPipeline, next) => + { + input = await innerPipeline.Invoke(input); + return await next.Invoke(input); + }, + configurePipeline); + } + protected override PipelineBuilder CreateBuilder() => new PipelineBuilder(ServiceProvider); } } \ No newline at end of file diff --git a/test/Flo.Tests/OutputPipelineBuilderWhenTests.cs b/test/Flo.Tests/OutputPipelineBuilderWhenTests.cs index a9f0f66..0ea7776 100644 --- a/test/Flo.Tests/OutputPipelineBuilderWhenTests.cs +++ b/test/Flo.Tests/OutputPipelineBuilderWhenTests.cs @@ -22,6 +22,21 @@ async Task it_ignores_the_handler_if_the_predicate_returns_false() result.ShouldBe(0); } + async Task it_ignores_the_handler_if_the_async_predicate_returns_false() + { + var pipeline = Pipeline.Build(cfg => + cfg.When(input => Task.FromResult(input == "hello world"), + builder => builder.Add((input, next) => + { + return Task.FromResult(input.Length); + }) + ) + ); + + var result = await pipeline.Invoke("hello"); + result.ShouldBe(0); + } + async Task it_executes_the_handler_if_the_predicate_returns_true() { var pipeline = Pipeline.Build(cfg => @@ -36,7 +51,22 @@ async Task it_executes_the_handler_if_the_predicate_returns_true() var result = await pipeline.Invoke("hello world"); result.ShouldBe(11); } - + + async Task it_executes_the_handler_if_the_async_predicate_returns_true() + { + var pipeline = Pipeline.Build(cfg => + cfg.When(input => Task.FromResult(input == "hello world"), + builder => builder.Add((input, next) => + { + return Task.FromResult(input.Length); + }) + ) + ); + + var result = await pipeline.Invoke("hello world"); + result.ShouldBe(11); + } + async Task it_continues_to_parent_pipeline_if_child_pipeline_returns_default_value() { var pipeline = Pipeline.Build(cfg => @@ -56,6 +86,25 @@ async Task it_continues_to_parent_pipeline_if_child_pipeline_returns_default_val result.ShouldContainKey("Test"); } + async Task it_continues_to_parent_pipeline_if_child_pipeline_returns_default_value_with_async_predicate() + { + var pipeline = Pipeline.Build(cfg => + cfg.When(input => Task.FromResult(true), + builder => builder.Add((ctx, next) => + { + return Task.FromResult(default(TestContext)); + }) + ) + .Final(ctx => { + ctx.Add("Test", "TestValue"); + return Task.FromResult(ctx); + }) + ); + + var result = await pipeline.Invoke(new TestContext()); + result.ShouldContainKey("Test"); + } + async Task it_does_not_continue_to_parent_pipeline_if_child_pipeline_returns_value() { var pipeline = Pipeline.Build(cfg => @@ -74,5 +123,24 @@ async Task it_does_not_continue_to_parent_pipeline_if_child_pipeline_returns_val var result = await pipeline.Invoke(new TestContext()); result.ShouldNotContainKey("Test"); } + + async Task it_does_not_continue_to_parent_pipeline_if_child_pipeline_returns_value_with_async_predicate() + { + var pipeline = Pipeline.Build(cfg => + cfg.When(input => Task.FromResult(true), + builder => builder.Add((ctx, next) => + { + return Task.FromResult(ctx); + }) + ) + .Final(ctx => { + ctx.Add("Test", "TestValue"); + return Task.FromResult(ctx); + }) + ); + + var result = await pipeline.Invoke(new TestContext()); + result.ShouldNotContainKey("Test"); + } } } \ No newline at end of file diff --git a/test/Flo.Tests/PipelineBuilderWhenTests.cs b/test/Flo.Tests/PipelineBuilderWhenTests.cs index 98825c0..71e641a 100644 --- a/test/Flo.Tests/PipelineBuilderWhenTests.cs +++ b/test/Flo.Tests/PipelineBuilderWhenTests.cs @@ -1,105 +1,199 @@ - using System.Collections.Generic; - using System.Threading.Tasks; - using NSpec; - using Shouldly; +using NSpec; +using Shouldly; +using System.Threading.Tasks; - namespace Flo.Tests +namespace Flo.Tests +{ + public class describe_PipelineBuilder_When : nspec { - public class describe_PipelineBuilder_When : nspec + async Task it_ignores_the_handler_if_the_predicate_returns_false() { - async Task it_ignores_the_handler_if_the_predicate_returns_false() - { - var pipeline = Pipeline.Build(cfg => - cfg.Add((ctx, next) => + var pipeline = Pipeline.Build(cfg => + cfg.Add((ctx, next) => + { + ctx.Add("Item1", "Item1Value"); + return next.Invoke(ctx); + }) + .When(ctx => ctx.ContainsKey("Item2"), + builder => builder.Add((ctx, next) => { - ctx.Add("Item1", "Item1Value"); + ctx.Add("Item3", "Item3Value"); return next.Invoke(ctx); }) - .When(ctx => ctx.ContainsKey("Item2"), - builder => builder.Add((ctx, next) => - { - ctx.Add("Item3", "Item3Value"); - return next.Invoke(ctx); - }) - ) - .Final(ctx => + ) + .Final(ctx => + { + ctx.Add("Item4", "Item4Value"); + return Task.FromResult(ctx); + }) + ); + + var context = new TestContext(); + await pipeline.Invoke(context); + + context.Count.ShouldBe(2); + context.ShouldNotContainKey("Item3"); + } + + async Task it_ignores_the_handler_if_the_async_predicate_returns_false() + { + var pipeline = Pipeline.Build(cfg => + cfg.Add((ctx, next) => + { + ctx.Add("Item1", "Item1Value"); + return next.Invoke(ctx); + }) + .When(ctx => Task.FromResult(ctx.ContainsKey("Item2")), + builder => builder.Add((ctx, next) => { - ctx.Add("Item4", "Item4Value"); - return Task.FromResult(ctx); + ctx.Add("Item3", "Item3Value"); + return next.Invoke(ctx); }) - ); + ) + .Final(ctx => + { + ctx.Add("Item4", "Item4Value"); + return Task.FromResult(ctx); + }) + ); - var context = new TestContext(); - await pipeline.Invoke(context); + var context = new TestContext(); + await pipeline.Invoke(context); - context.Count.ShouldBe(2); - context.ShouldNotContainKey("Item3"); - } + context.Count.ShouldBe(2); + context.ShouldNotContainKey("Item3"); + } - async Task it_executes_the_handler_if_the_predicate_returns_true() - { - var pipeline = Pipeline.Build(cfg => - cfg.Add((ctx, next) => + async Task it_executes_the_handler_if_the_predicate_returns_true() + { + var pipeline = Pipeline.Build(cfg => + cfg.Add((ctx, next) => + { + ctx.Add("Item1", "Item1Value"); + return next.Invoke(ctx); + }) + .Add((ctx, next) => + { + ctx.Add("Item2", "Item2Value"); + return next.Invoke(ctx); + }) + .When(ctx => ctx.ContainsKey("Item2"), + builder => builder.Final(ctx => { - ctx.Add("Item1", "Item1Value"); - return next.Invoke(ctx); + ctx.Add("Item3", "Item3Value"); + return Task.FromResult(ctx); }) - .Add((ctx, next) => + ) + ); + + var context = new TestContext(); + await pipeline.Invoke(context); + + context.Count.ShouldBe(3); + context.ShouldContainKey("Item3"); + } + + async Task it_executes_the_handler_if_the_async_predicate_returns_true() + { + var pipeline = Pipeline.Build(cfg => + cfg.Add((ctx, next) => + { + ctx.Add("Item1", "Item1Value"); + return next.Invoke(ctx); + }) + .Add((ctx, next) => + { + ctx.Add("Item2", "Item2Value"); + return next.Invoke(ctx); + }) + .When(ctx => Task.FromResult(ctx.ContainsKey("Item2")), + builder => builder.Final(ctx => { - ctx.Add("Item2", "Item2Value"); - return next.Invoke(ctx); + ctx.Add("Item3", "Item3Value"); + return Task.FromResult(ctx); }) - .When(ctx => ctx.ContainsKey("Item2"), - builder => builder.Final(ctx => + ) + ); + + var context = new TestContext(); + await pipeline.Invoke(context); + + context.Count.ShouldBe(3); + context.ShouldContainKey("Item3"); + } + + async Task it_continues_to_parent_pipeline_after_child_pipeline_has_completed() + { + var pipeline = Pipeline.Build(cfg => + cfg.Add((ctx, next) => + { + ctx.Add("Item1", "Item1Value"); + return next.Invoke(ctx); + }) + .When(ctx => ctx.Count == 1, + builder => builder + .Add((ctx, next) => + { + ctx.Add("Item2", "Item2Value"); + return next.Invoke(ctx); + }) + .Add((ctx, next) => { ctx.Add("Item3", "Item3Value"); return Task.FromResult(ctx); }) - ) - ); + ) + .Final(ctx => + { + ctx.Add("Item4", "Item4Value"); + return Task.FromResult(ctx); + }) + ); - var context = new TestContext(); - await pipeline.Invoke(context); + var context = new TestContext(); + await pipeline.Invoke(context); - context.Count.ShouldBe(3); - context.ShouldContainKey("Item3"); - } + context.ShouldContainKey("Item1"); + context.ShouldContainKey("Item2"); + context.ShouldContainKey("Item3"); + context.ShouldContainKey("Item4"); + } - async Task it_continues_to_parent_pipeline_after_child_pipeline_has_completed() - { - var pipeline = Pipeline.Build(cfg => - cfg.Add((ctx, next) => - { - ctx.Add("Item1", "Item1Value"); - return next.Invoke(ctx); - }) - .When(ctx => ctx.Count == 1, - builder => builder - .Add((ctx, next) => - { - ctx.Add("Item2", "Item2Value"); - return next.Invoke(ctx); - }) - .Add((ctx, next) => - { - ctx.Add("Item3", "Item3Value"); - return Task.FromResult(ctx); - }) - ) - .Final(ctx => - { - ctx.Add("Item4", "Item4Value"); - return Task.FromResult(ctx); - }) - ); + async Task it_continues_to_parent_pipeline_after_child_pipeline_has_completed_with_async_predicate() + { + var pipeline = Pipeline.Build(cfg => + cfg.Add((ctx, next) => + { + ctx.Add("Item1", "Item1Value"); + return next.Invoke(ctx); + }) + .When(ctx => Task.FromResult(ctx.Count == 1), + builder => builder + .Add((ctx, next) => + { + ctx.Add("Item2", "Item2Value"); + return next.Invoke(ctx); + }) + .Add((ctx, next) => + { + ctx.Add("Item3", "Item3Value"); + return Task.FromResult(ctx); + }) + ) + .Final(ctx => + { + ctx.Add("Item4", "Item4Value"); + return Task.FromResult(ctx); + }) + ); - var context = new TestContext(); - await pipeline.Invoke(context); + var context = new TestContext(); + await pipeline.Invoke(context); - context.ShouldContainKey("Item1"); - context.ShouldContainKey("Item2"); - context.ShouldContainKey("Item3"); - context.ShouldContainKey("Item4"); - } + context.ShouldContainKey("Item1"); + context.ShouldContainKey("Item2"); + context.ShouldContainKey("Item3"); + context.ShouldContainKey("Item4"); } - } \ No newline at end of file + } +} \ No newline at end of file