From cd4f7c8e573f4f28d34d21711cba9d7a9e6a101b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:01:55 -0700 Subject: [PATCH] [FEATURE]: Enhance documentation (#29) * Documentation updates * Bump test versions --------- Co-authored-by: Brenton Farmer --- docs/child-pipeline.md | 32 +++ docs/childPipeline.md | 7 - docs/{execution.md => command-pattern.md} | 63 +----- ...cyInjection.md => dependency-injection.md} | 0 docs/docs.projitems | 7 +- docs/index.md | 179 +++++----------- docs/middleware.md | 49 ++++- docs/syntax.md | 148 ++++++++++++++ src/Hyperbee.Pipeline/README.md | 191 +++++------------- .../Hyperbee.Pipeline.Auth.Tests.csproj | 4 +- .../Hyperbee.Pipeline.Tests.csproj | 4 +- .../Hyperbee.Pipeline.Caching.Tests.csproj | 4 +- .../Hyperbee.Pipeline.Benchmark.csproj | 2 +- 13 files changed, 340 insertions(+), 350 deletions(-) create mode 100644 docs/child-pipeline.md delete mode 100644 docs/childPipeline.md rename docs/{execution.md => command-pattern.md} (64%) rename docs/{dependencyInjection.md => dependency-injection.md} (100%) create mode 100644 docs/syntax.md diff --git a/docs/child-pipeline.md b/docs/child-pipeline.md new file mode 100644 index 0000000..6ba2928 --- /dev/null +++ b/docs/child-pipeline.md @@ -0,0 +1,32 @@ +--- +layout: default +title: Child Pipelines +nav_order: 5 +--- + +# Child Piplelines + +The `PipelineFactory` library allows you to use pipelines together. Since pipelines are just functions, they can be used +as input to other pipelines. This allows you to create complex data processing flows by reusing and chaining together +multiple pipelines. + +Here's an example of how to use pipelines together: + + +```csharp +var pipeline2 = PipelineFactory + .Start() + .Pipe( ( ctx, arg ) => $"{arg} again!" ) + .Build(); + +var pipeline1 = PipelineFactory + .Start() + .Pipe( ( ctx, arg ) => $"hello {arg}" ) + .PipeAsync( pipeline2 ) + .Build(); + +var result = await pipeline1( new PipelineContext(), "you" ); + +Assert.AreEqual( "hello you again!", result ); +``` + diff --git a/docs/childPipeline.md b/docs/childPipeline.md deleted file mode 100644 index 59d8f0e..0000000 --- a/docs/childPipeline.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: default -title: Child Pipelines -nav_order: 3 ---- - -# Child Piplelines diff --git a/docs/execution.md b/docs/command-pattern.md similarity index 64% rename from docs/execution.md rename to docs/command-pattern.md index c4b0841..9bc4d96 100644 --- a/docs/execution.md +++ b/docs/command-pattern.md @@ -1,61 +1,10 @@ --- layout: default -title: Execution -nav_order: 2 +title: Commands +nav_order: 6 --- -# Execution - -| Method | Description -| ---------- | ----------- -| Call | Execute a `void` step that does not transform the pipeline output. -| CallAsync | Asynchronously execute a `void` step that does not transform the pipeline output. -| Pipe | Execute a step that transforms the pipeline output. -| PipeAsync | Asynchronously execute a step that transforms the pipeline output. - -## Flow Control - -| Method | Description -| ---------- | ----------- -| Cancel | Cancels the pipeline after the current step. -| CancelWith | Cancels the pipeline with a value, after the current step. -| Pipe | Pipes a child pipeline with optional middlewares. -| PipeIf | Conditionally pipes a child pipeline with optional middlewares. -| Call | Calls a child pipeline with optional middlewares. -| CallIf | Conditionally calls a child pipeline with optional middlewares. -| ForEach | Enumerates a collection pipeline input. -| Reduce | Transforms an enumerable pipeline input. - -## Parallel Flow - -| Method | Description -| ---------- | ----------- -| WaitAll | Waits for concurrent pipelines to complete. - -## Middleware - -| Method | Description -| ---------- | ----------- -| Hook | Applies middleware to each step in the pipeline. -| Wrap | Wraps the middleware around the preceeding steps. - -## Building and Executing Pipelines - -Pipelines are built using `PipelineFactory`. Once built, a pipeline is just an async function that takes a `PipelineContext` and -an optional input value as parameters, and returns a result. - -```csharp - var command = PipelineFactory - .Start() - .Pipe( ( ctx, arg ) => $"hello {arg}" ) - .Build(); - - var result = await command( new PipelineContext(), "pipeline" ); - - Assert.AreEqual( "hello pipeline", result ); -``` - -## Command Pattern +# Command Pattern `ICommand*` interfaces and `Command*` base classes provide a lightweight pattern for constructing injectable commands built around pipelines and middleware. @@ -66,7 +15,7 @@ an optional input value as parameters, and returns a result. | ICommandFunction<TOutput> | CommandFunction<TOutput> | A command that takes no input and returns an output | ICommandProcedure<TInput> | CommandProcedure<TInput> | A command that takes an input and returns void -#### Example 1 +## Example 1 Example of a command that takes an input and produces an output. ```csharp @@ -103,7 +52,7 @@ Example of a command that takes an input and produces an output. } ``` -#### Example 2 +## Example 2 Example of a command that takes no input and produces an output. ```csharp @@ -140,7 +89,7 @@ Example of a command that takes no input and produces an output. } ``` -#### Example 3 +## Example 3 Example of a command that takes an input and produces no output. ```csharp diff --git a/docs/dependencyInjection.md b/docs/dependency-injection.md similarity index 100% rename from docs/dependencyInjection.md rename to docs/dependency-injection.md diff --git a/docs/docs.projitems b/docs/docs.projitems index ffcfd11..37bd226 100644 --- a/docs/docs.projitems +++ b/docs/docs.projitems @@ -9,9 +9,10 @@ docs - - - + + + + diff --git a/docs/index.md b/docs/index.md index 7ceffe4..8a380dd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ title: Hyperbee Pipeline nav_order: 1 --- -# Hyperbee.Pipeline +# Hyperbee Pipeline `Hyperbee.Pipeline` allows you to construct asynchronous fluent pipelines in .NET. A pipeline, in this context, refers to a sequence of data processing elements arranged in series, where the output of one element serves as the input for the subsequent @@ -25,63 +25,42 @@ Some key features are: * Early returns and cancellation * Child pipelines +## Why Use Pipelines -```csharp -// Takes a string and returns a number -var question = PipelineFactory - .Start() - .PipeIf((ctx, arg) => arg == "Adams", builder => builder - .Pipe((ctx, arg) => 42) - .Cancel() - ) - .Pipe((ctx, arg) => 0) - .Build(); +Pipelines provide a structured approach to managing complex processes, promoting [SOLID](https://en.wikipedia.org/wiki/SOLID) +principles, including Inversion of Control (IoC) and Separation of Concerns (SoC). They enable composability, making it easier +to build, test, and maintain your code. By extending the benefits of middleware and request-response pipelines throughout your +application, you achieve greater modularity, scalability, and flexibility. This is especially critical in domains such as +healthcare, compliance auditing, identity and roles, and high-security environments where clear boundaries and responsibilities +are essential. Hyperbee.Pipeline ensures that the advantages of pipelines and middleware are not abandoned at the controller +implementation, addressing a common gap in many frameworks. By using a functional approach, Hyperbee.Pipeline ensures that your +pipelines are not only robust and maintainable but also highly adaptable to changing requirements. -var answer1 = await question(new PipelineContext(), "Adams"); -Assert.AreEqual(42, answer1); -var answer2 = await question(new PipelineContext(), "Smith"); -Assert.AreEqual(0, answer2); -``` +## Getting Started -## Hook +To get started with Hyperbee.Pipeline, refer to the documentation for detailed instructions and examples. -The `Hook` and `HookAsync` methods allow you to add a hook that is called for every statement in the pipeline. This hook takes the current context, the current argument, and a delegate to the next part of the pipeline. It can manipulate the argument before and after calling the next part of the pipeline. +Install via NuGet: -Here's an example of how to use `HookAsync`: - -```csharp -var command = PipelineFactory - .Start() - .HookAsync( async ( ctx, arg, next ) => await next( ctx, arg + "{" ) + "}" ) - .Pipe( ( ctx, arg ) => arg + "1" ) - .Pipe( ( ctx, arg ) => arg + "2" ) - .Build(); - -var result = await command( new PipelineContext() ); - -Assert.AreEqual( "{1}{2}", result ); +```bash +dotnet add package Hyperbee.Pipeline ``` -## Wrap +## Building and Executing Pipelines -The `Wrap` and `WrapAsync` method allows you to wrap a part of the pipeline. This is useful when you want to apply a transformation to only a part of the pipeline. - -Here’s an example of how to use `WrapAsync`: +Pipelines are built using `PipelineFactory`. Once built, a pipeline is just an async function that takes a `PipelineContext` and +an optional input value as parameters, and returns a result. ```csharp -var command = PipelineFactory - .Start() - .Pipe( ( ctx, arg ) => arg + "1" ) - .Pipe( ( ctx, arg ) => arg + "2" ) - .WrapAsync( async ( ctx, arg, next ) => await next( ctx, arg + "{" ) + "}" ) - .Pipe( ( ctx, arg ) => arg + "3" ) - .Build(); + var command = PipelineFactory + .Start() + .Pipe( ( ctx, arg ) => $"hello {arg}" ) + .Build(); -var result = await command( new PipelineContext() ); - -Assert.AreEqual( "{12}3", result ); + var result = await command( new PipelineContext(), "pipeline" ); + Assert.AreEqual( "hello pipeline", result ); ``` ## Dependency Injection @@ -110,110 +89,42 @@ services.AddPipeline( (factoryServices, rootProvider) => } ); ``` -## Advanced Features - -The `PipelineFactory` library provides a variety of helper methods that allow you to customize the behavior of your pipelines. These methods provide powerful functionality for manipulating data as it passes through the pipeline. - -### Reduce - -The `Reduce` and `ReduceAsync` methods allow you to reduce a sequence of elements to a single value. You can specify a reducer function that defines how the elements should be combined, and a builder function that creates the pipeline for processing the elements. - -### WaitAll - -The `WaitAll` method allows you to wait for all pipelines to complete before continuing. You can specify a set of builders that create the pipelines to wait for, a reducer function that combines the results of the pipelines. - -```csharp -var count = 0; - -var command = PipelineFactory - .Start() - .WaitAll( builders => builders.Create( - builder => builder.Pipe( ( ctx, arg ) => Interlocked.Increment( ref count ) ), - builder => builder.Pipe( ( ctx, arg ) => Interlocked.Increment( ref count ) ) - ), - reducer: ( ctx, arg, results ) => { return arg + results.Sum( x => (int) x.Result ); } - ) - .Build(); - -var result = await command( new PipelineContext() ); - -Assert.AreEqual( 2, count ); -Assert.AreEqual( 3, result ); -``` - -### PipeIf - -The `PipeIf` method allows you to conditionally add a step to the pipeline. You can specify a condition function that determines whether the step should be added, a builder function that creates the step, and an optional flag indicating whether middleware should be inherited. +## Pipeline of Pipelines -### ForEach and ForEachAsync +The `PipelineFactory` library allows you to use pipelines together. Since pipelines are just functions, they can be used +as input to other pipelines. This allows you to create complex data processing flows by reusing and chaining together +multiple pipelines. -The `ForEach` and `ForEachAsync` methods allow you to apply a pipeline to each element in a sequence. You can specify a builder function that creates the pipeline for processing the elements. +Here's an example of how to use pipelines together: ```csharp -var count = 0; - -var command = PipelineFactory +var pipeline2 = PipelineFactory .Start() - .Pipe( ( ctx, arg ) => arg.Split( ' ' ) ) - .ForEach( builder => builder - .Pipe( ( ctx, arg ) => count += 10 ) - ) - .Pipe( ( ctx, arg ) => count += 5 ) + .Pipe( ( ctx, arg ) => $"{arg} again!" ) .Build(); -await command( new PipelineContext(), "e f" ); - -Assert.AreEqual( count, 25 ); -``` - -### Call and CallAsync - -The `Call` and `CallAsync` methods allow you to add a procedure to the pipeline. You can think of these as `Action` and `Pipe` like `Func`. - -In this example notice that `arg + 9` is not returned from the use of `Call` - -```csharp -var callResult = string.Empty; - -var command = PipelineFactory +var pipeline1 = PipelineFactory .Start() - .Pipe( ( ctx, arg ) => arg + "1" ) - .Pipe( ( ctx, arg ) => arg + "2" ) - .Call( builder => builder - .Call( ( ctx, arg ) => callResult = arg + "3" ) - .Pipe( ( ctx, arg ) => arg + "9" ) - ) - .Pipe( ( ctx, arg ) => arg + "4" ) + .Pipe( ( ctx, arg ) => $"hello {arg}" ) + .PipeAsync( pipeline2 ) .Build(); -var result = await command( new PipelineContext() ); +var result = await pipeline1( new PipelineContext(), "you" ); -Assert.AreEqual( "124", result ); -Assert.AreEqual( "123", callResult ); +Assert.AreEqual( "hello you again!", result ); ``` -### Chaining Child Pipelines +## Conditional Flow and Advanced Features -The `PipelineFactory` library allows you to chain pipelines together. Since pipelines are just functions, they can be used as input to other pipelines. This allows you to create complex data processing flows by reusing and chaining together multiple pipelines. +The `PipelineFactory` library provides a variety of builders that allow you to customize the behavior of your pipelines. +These methods provide powerful functionality for manipulating data as it passes through the pipeline. -Here's an example of how to chain pipelines together: - -```csharp -var command2 = PipelineFactory - .Start() - .Pipe( ( ctx, arg ) => $"{arg} again!" ) - .Build(); - -var command1 = PipelineFactory - .Start() - .Pipe( ( ctx, arg ) => $"hello {arg}" ) - .PipeAsync( command2 ) - .Build(); - -var result = await command1( new PipelineContext(), "pipeline" ); - -Assert.AreEqual( "hello pipeline again!", result ); -``` +- Functions +- Procedures +- Conditional Flow +- Iterators +- Reduce +- Parallel execution ## Credits diff --git a/docs/middleware.md b/docs/middleware.md index 3b716f2..5ae0773 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -7,9 +7,34 @@ nav_order: 4 # Middleware Pipelines support custom middleware. Custom middleware can be created by implementing an extension method that uses a `Hook` or `Wrap` builder. -## Hooks +## Middleware Syntax -`Hooks` are middleware that surround individual pipeline actions. +| Method | Description +| ---------- | ----------- +| Hook | Applies middleware to each step in the pipeline. +| Wrap | Wraps the middleware around the preceeding steps. + + +## Hook + +`Hooks` are middleware that surround individual pipeline actions. The `Hook` and `HookAsync` methods allow you to add a hook that is called +for every statement in the pipeline. This hook takes the current context, the current argument, and a delegate to the next part of the +pipeline. It can manipulate the argument before and after calling the next part of the pipeline. + +Here's an example of how to use `HookAsync` with an inline lambda: + +```csharp +var command = PipelineFactory + .Start() + .HookAsync( async ( ctx, arg, next ) => await next( ctx, arg + "[" ) + "]" ) + .Pipe( ( ctx, arg ) => arg + "1" ) + .Pipe( ( ctx, arg ) => arg + "2" ) + .Build(); + +var result = await command( new PipelineContext() ); + +Assert.AreEqual( "[1][2]", result ); +``` ### Example Example of a `hook` middleware that surrounds each step. Hooks must be constrained to only be available at the start @@ -58,7 +83,25 @@ The `WithLogging` hooked into the beginning and end of each pipeline step with t ## Wraps -`Wraps` are middleware that surround a group of pipeline actions. +`Wraps` are middleware that surround a group of pipeline actions. The `Wrap` and `WrapAsync` method allows you to wrap a part of the +pipeline. This is useful when you want to apply a transformation to only a part of the pipeline. + +Here’s an example of how to use `WrapAsync`: + +```csharp +var command = PipelineFactory + .Start() + .Pipe( ( ctx, arg ) => arg + "1" ) + .Pipe( ( ctx, arg ) => arg + "2" ) + .WrapAsync( async ( ctx, arg, next ) => await next( ctx, arg + "{" ) + "}" ) + .Pipe( ( ctx, arg ) => arg + "3" ) + .Build(); + +var result = await command( new PipelineContext() ); + +Assert.AreEqual( "{12}3", result ); + +``` ### Example Example of a `wrap` middleware that surrounds a block of steps. Create `Wrap` middleware by extending `IPipelineBuilder`. diff --git a/docs/syntax.md b/docs/syntax.md new file mode 100644 index 0000000..a449bd7 --- /dev/null +++ b/docs/syntax.md @@ -0,0 +1,148 @@ +--- +layout: default +title: Syntax +nav_order: 2 +--- + +# Pipeline Syntax + +## Statements + +| Method | Description +| ---------- | ----------- +| Call | Execute a `void` statement that does not transform the pipeline output. +| CallAsync | Asynchronously execute a `void` statement that does not transform the pipeline output. +| Pipe | Execute a statement that transforms the pipeline output. +| PipeAsync | Asynchronously execute a statement that transforms the pipeline output. + +## Flow Control + +| Method | Description +| ---------- | ----------- +| Pipe | Pipes a child pipeline with optional middlewares. +| PipeIf | Conditionally pipes a child pipeline with optional middlewares. +| Call | Calls a child pipeline with optional middlewares. +| CallIf | Conditionally calls a child pipeline with optional middlewares. +| ForEach | Enumerates a collection pipeline input. +| Reduce | Transforms an enumerable pipeline input. +| WaitAll | Waits for concurrent pipelines to complete. + +## Cancellation + +| Method | Description +| ---------- | ----------- +| Cancel | Cancels the pipeline after the current step. +| CancelWith | Cancels the pipeline with a value, after the current step. + +## Reference + +### Statements + +- `Call` - Execute a statement that does not transform the pipeline output. +- `CallAsync` - Asynchronously execute a statement that does not transform the pipeline output. +- `Pipe` - Execute a statement that transforms the pipeline output. +- `PipeAsync` - Asynchronously execute a statement that transforms the pipeline output. + +In this example notice that `arg + 9` is not returned from the use of `Call`. + +```csharp +var callResult = string.Empty; + +var command = PipelineFactory + .Start() + .Pipe( ( ctx, arg ) => arg + "1" ) + .Pipe( ( ctx, arg ) => arg + "2" ) + .Call( builder => builder + .Call( ( ctx, arg ) => callResult = arg + "3" ) + .Pipe( ( ctx, arg ) => arg + "9" ) + ) + .Pipe( ( ctx, arg ) => arg + "4" ) + .Build(); + +var result = await command( new PipelineContext() ); + +Assert.AreEqual( "124", result ); +Assert.AreEqual( "123", callResult ); +``` + +### If Conditions + +`PipeIf` and `PipeIfAsync` allow you to conditionally add a step to the pipeline. You can specify a condition function that determines whether +the step should be added, a builder function that creates the step, and an optional flag indicating whether middleware should be inherited.### CallIf + +`CallIf` and `CallIfAsync` allow you to conditionally call a child pipeline with optional middlewares. You can specify a condition function that determines whether +the child pipeline should be called, a builder function that creates the child pipeline, and an optional flag indicating whether middleware should be inherited.### ForEach + +```csharp +// Takes a string and returns a number +var question = PipelineFactory + .Start() + .PipeIf((ctx, arg) => arg == "Adams", builder => builder + .Pipe((ctx, arg) => 42) + .Cancel() + ) + .Pipe((ctx, arg) => 0) + .Build(); + +var answer1 = await question(new PipelineContext(), "Adams"); +Assert.AreEqual(42, answer1); + +var answer2 = await question(new PipelineContext(), "Smith"); +Assert.AreEqual(0, answer2); +``` + +### ForEach + +`ForEach` and `ForEachAsync` allow you to enumerate a collection pipeline input and apply a pipeline to each element. + +```csharp +var count = 0; + +var command = PipelineFactory + .Start() + .Pipe( ( ctx, arg ) => arg.Split( ' ' ) ) + .ForEach( builder => builder + .Pipe( ( ctx, arg ) => count += 10 ) + ) + .Pipe( ( ctx, arg ) => count += 5 ) + .Build(); + +await command( new PipelineContext(), "e f" ); + +Assert.AreEqual( count, 25 ); +``` + +### Reduce + +`Reduce` and `ReduceAync` allow you to transform an enumerable pipeline input to a single value. You can specify a reducer function +that defines how the elements should be combined, and a builder function that creates the pipeline for processing the elements.### Cancel + +### WaitAll + +`WaitAll` allows you to wait for concurrent pipelines to complete before continuing. You can specify a set of builders that create +the pipelines to wait for, and a reducer function that combines the results of the pipelines. + +```csharp +var count = 0; + +var command = PipelineFactory + .Start() + .WaitAll( builders => builders.Create( + builder => builder.Pipe( ( ctx, arg ) => Interlocked.Increment( ref count ) ), + builder => builder.Pipe( ( ctx, arg ) => Interlocked.Increment( ref count ) ) + ), + reducer: ( ctx, arg, results ) => { return arg + results.Sum( x => (int) x.Result ); } + ) + .Build(); + +var result = await command( new PipelineContext() ); + +Assert.AreEqual( 2, count ); +Assert.AreEqual( 3, result ); +``` + +### Cancellation + +`Cancel` method allows you to cancel the pipeline after the current step. + +`CancelWith` method allows you to cancel the pipeline with a value after the current step.## Parallel Flow diff --git a/src/Hyperbee.Pipeline/README.md b/src/Hyperbee.Pipeline/README.md index 5e397bd..9f4c09b 100644 --- a/src/Hyperbee.Pipeline/README.md +++ b/src/Hyperbee.Pipeline/README.md @@ -1,4 +1,4 @@ -# Hyperbee.Pipeline +# Hyperbee Pipeline `Hyperbee.Pipeline` allows you to construct asynchronous fluent pipelines in .NET. A pipeline, in this context, refers to a sequence of data processing elements arranged in series, where the output of one element serves as the input for the subsequent @@ -19,63 +19,43 @@ Some key features are: * Early returns and cancellation * Child pipelines -* -```csharp -// Takes a string and returns a number -var question = PipelineFactory - .Start() - .PipeIf((ctx, arg) => arg == "Adams", builder => builder - .Pipe((ctx, arg) => 42) - .Cancel() - ) - .Pipe((ctx, arg) => 0) - .Build(); - -var answer1 = await question(new PipelineContext(), "Adams"); -Assert.AreEqual(42, answer1); - -var answer2 = await question(new PipelineContext(), "Smith"); -Assert.AreEqual(0, answer2); -``` +## Why Use Pipelines -## Hook +Pipelines provide a structured approach to managing complex processes, promoting [SOLID](https://en.wikipedia.org/wiki/SOLID) +principles, including Inversion of Control (IoC) and Separation of Concerns (SoC). They enable composability, making it easier +to build, test, and maintain your code. By extending the benefits of middleware and request-response pipelines throughout your +application, you achieve greater modularity, scalability, and flexibility. This is especially critical in domains such as +healthcare, compliance auditing, identity and roles, and high-security environments where clear boundaries and responsibilities +are essential. Hyperbee.Pipeline ensures that the advantages of pipelines and middleware are not abandoned at the controller +implementation, addressing a common gap in many frameworks. By using a functional approach, Hyperbee.Pipeline ensures that your +pipelines are not only robust and maintainable but also highly adaptable to changing requirements. -The `Hook` and `HookAsync` methods allow you to add a hook that is called for every statement in the pipeline. This hook takes the current context, the current argument, and a delegate to the next part of the pipeline. It can manipulate the argument before and after calling the next part of the pipeline. -Here's an example of how to use `HookAsync`: +## Getting Started -```csharp -var command = PipelineFactory - .Start() - .HookAsync( async ( ctx, arg, next ) => await next( ctx, arg + "{" ) + "}" ) - .Pipe( ( ctx, arg ) => arg + "1" ) - .Pipe( ( ctx, arg ) => arg + "2" ) - .Build(); +To get started with Hyperbee.Json, refer to the [documentation](https://stillpoint-software.github.io/hyperbee.pipeline) for +detailed instructions and examples. -var result = await command( new PipelineContext() ); +Install via NuGet: -Assert.AreEqual( "{1}{2}", result ); +```bash +dotnet add package Hyperbee.Pipeline ``` -## Wrap +## Building and Executing Pipelines -The `Wrap` and `WrapAsync` method allows you to wrap a part of the pipeline. This is useful when you want to apply a transformation to only a part of the pipeline. - -Here’s an example of how to use `WrapAsync`: +Pipelines are built using `PipelineFactory`. Once built, a pipeline is just an async function that takes a `PipelineContext` and +an optional input value as parameters, and returns a result. ```csharp -var command = PipelineFactory - .Start() - .Pipe( ( ctx, arg ) => arg + "1" ) - .Pipe( ( ctx, arg ) => arg + "2" ) - .WrapAsync( async ( ctx, arg, next ) => await next( ctx, arg + "{" ) + "}" ) - .Pipe( ( ctx, arg ) => arg + "3" ) - .Build(); + var command = PipelineFactory + .Start() + .Pipe( ( ctx, arg ) => $"hello {arg}" ) + .Build(); -var result = await command( new PipelineContext() ); - -Assert.AreEqual( "{12}3", result ); + var result = await command( new PipelineContext(), "pipeline" ); + Assert.AreEqual( "hello pipeline", result ); ``` ## Dependency Injection @@ -104,117 +84,50 @@ services.AddPipeline( (factoryServices, rootProvider) => } ); ``` -## Advanced Features - -The `PipelineFactory` library provides a variety of helper methods that allow you to customize the behavior of your pipelines. These methods provide powerful functionality for manipulating data as it passes through the pipeline. - -### Reduce - -The `Reduce` and `ReduceAsync` methods allow you to reduce a sequence of elements to a single value. You can specify a reducer function that defines how the elements should be combined, and a builder function that creates the pipeline for processing the elements. - -### WaitAll - -The `WaitAll` method allows you to wait for all pipelines to complete before continuing. You can specify a set of builders that create the pipelines to wait for, a reducer function that combines the results of the pipelines. - -```csharp -var count = 0; - -var command = PipelineFactory - .Start() - .WaitAll( builders => builders.Create( - builder => builder.Pipe( ( ctx, arg ) => Interlocked.Increment( ref count ) ), - builder => builder.Pipe( ( ctx, arg ) => Interlocked.Increment( ref count ) ) - ), - reducer: ( ctx, arg, results ) => { return arg + results.Sum( x => (int) x.Result ); } - ) - .Build(); - -var result = await command( new PipelineContext() ); - -Assert.AreEqual( 2, count ); -Assert.AreEqual( 3, result ); -``` - -### PipeIf - -The `PipeIf` method allows you to conditionally add a step to the pipeline. You can specify a condition function that determines whether the step should be added, a builder function that creates the step, and an optional flag indicating whether middleware should be inherited. +## Pipeline of Pipelines -### ForEach and ForEachAsync +The `PipelineFactory` library allows you to use pipelines together. Since pipelines are just functions, they can be used +as input to other pipelines. This allows you to create complex data processing flows by reusing and chaining together +multiple pipelines. -The `ForEach` and `ForEachAsync` methods allow you to apply a pipeline to each element in a sequence. You can specify a builder function that creates the pipeline for processing the elements. +Here's an example of how to use pipelines together: ```csharp -var count = 0; - -var command = PipelineFactory +var pipeline2 = PipelineFactory .Start() - .Pipe( ( ctx, arg ) => arg.Split( ' ' ) ) - .ForEach( builder => builder - .Pipe( ( ctx, arg ) => count += 10 ) - ) - .Pipe( ( ctx, arg ) => count += 5 ) + .Pipe( ( ctx, arg ) => $"{arg} again!" ) .Build(); -await command( new PipelineContext(), "e f" ); - -Assert.AreEqual( count, 25 ); -``` - -### Call and CallAsync - -The `Call` and `CallAsync` methods allow you to add a procedure to the pipeline. You can think of these as `Action` and `Pipe` like `Func`. - -In this example notice that `arg + 9` is not returned from the use of `Call` - -```csharp -var callResult = string.Empty; - -var command = PipelineFactory +var pipeline1 = PipelineFactory .Start() - .Pipe( ( ctx, arg ) => arg + "1" ) - .Pipe( ( ctx, arg ) => arg + "2" ) - .Call( builder => builder - .Call( ( ctx, arg ) => callResult = arg + "3" ) - .Pipe( ( ctx, arg ) => arg + "9" ) - ) - .Pipe( ( ctx, arg ) => arg + "4" ) + .Pipe( ( ctx, arg ) => $"hello {arg}" ) + .PipeAsync( pipeline2 ) .Build(); -var result = await command( new PipelineContext() ); +var result = await pipeline1( new PipelineContext(), "you" ); -Assert.AreEqual( "124", result ); -Assert.AreEqual( "123", callResult ); +Assert.AreEqual( "hello you again!", result ); ``` -### Chaining Child Pipelines +## Conditional Flow and Advanced Features -The `PipelineFactory` library allows you to chain pipelines together. Since pipelines are just functions, they can be used as input to other pipelines. This allows you to create complex data processing flows by reusing and chaining together multiple pipelines. +The `PipelineFactory` library provides a variety of builders that allow you to customize the behavior of your pipelines. +These methods provide powerful functionality for manipulating data as it passes through the pipeline. -Here's an example of how to chain pipelines together: +- Functions +- Procedures +- Conditional Flow +- Iterators +- Reduce +- Parallel execution -```csharp -var command2 = PipelineFactory - .Start() - .Pipe( ( ctx, arg ) => $"{arg} again!" ) - .Build(); +## Credits -var command1 = PipelineFactory - .Start() - .Pipe( ( ctx, arg ) => $"hello {arg}" ) - .PipeAsync( command2 ) - .Build(); +Hyperbee.Pipeline is built upon the great work of several open-source projects. Special thanks to: -var result = await command1( new PipelineContext(), "pipeline" ); - -Assert.AreEqual( "hello pipeline again!", result ); -``` +- [Just The Docs](https://github.com/just-the-docs/just-the-docs) for the documentation theme. -## Additional Documentation -Classes for building composable async pipelines supporting: +## Contributing - * [Middleware](https://github.com/Stillpoint-Software/Hyperbee.Pipeline/blob/main/docs/middleware.md) - * [Conditional flow](https://github.com/Stillpoint-Software/Hyperbee.Pipeline/blob/main/docs/execution.md) - * [Dependency Injection](https://github.com/Stillpoint-Software/Hyperbee.Pipeline/blob/main/docs/dependencyInjection.md) - * Value projections - * Early returns - * Child pipelines \ No newline at end of file +We welcome contributions! Please see our [Contributing Guide](https://github.com/Stillpoint-Software/.github/blob/main/.github/CONTRIBUTING.md) +for more details. diff --git a/test/Hyperbee.Pipeline.Auth.Tests/Hyperbee.Pipeline.Auth.Tests.csproj b/test/Hyperbee.Pipeline.Auth.Tests/Hyperbee.Pipeline.Auth.Tests.csproj index 75faf26..8283bd4 100644 --- a/test/Hyperbee.Pipeline.Auth.Tests/Hyperbee.Pipeline.Auth.Tests.csproj +++ b/test/Hyperbee.Pipeline.Auth.Tests/Hyperbee.Pipeline.Auth.Tests.csproj @@ -15,8 +15,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/test/Hyperbee.Pipeline.Tests/Hyperbee.Pipeline.Tests.csproj b/test/Hyperbee.Pipeline.Tests/Hyperbee.Pipeline.Tests.csproj index 81759aa..e5c9897 100644 --- a/test/Hyperbee.Pipeline.Tests/Hyperbee.Pipeline.Tests.csproj +++ b/test/Hyperbee.Pipeline.Tests/Hyperbee.Pipeline.Tests.csproj @@ -4,9 +4,9 @@ false - + - + diff --git a/test/Hyperbee.PipelineCaching.Tests/Hyperbee.Pipeline.Caching.Tests.csproj b/test/Hyperbee.PipelineCaching.Tests/Hyperbee.Pipeline.Caching.Tests.csproj index f42e14f..dbdb0eb 100644 --- a/test/Hyperbee.PipelineCaching.Tests/Hyperbee.Pipeline.Caching.Tests.csproj +++ b/test/Hyperbee.PipelineCaching.Tests/Hyperbee.Pipeline.Caching.Tests.csproj @@ -15,9 +15,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/test/Hyperbee.Pipleline.Benchmark/Hyperbee.Pipeline.Benchmark.csproj b/test/Hyperbee.Pipleline.Benchmark/Hyperbee.Pipeline.Benchmark.csproj index 6aa32b7..e5b2efc 100644 --- a/test/Hyperbee.Pipleline.Benchmark/Hyperbee.Pipeline.Benchmark.csproj +++ b/test/Hyperbee.Pipleline.Benchmark/Hyperbee.Pipeline.Benchmark.csproj @@ -8,7 +8,7 @@ - +