Skip to content

Commit

Permalink
Fix finally in empty chains of responsibility (#29)
Browse files Browse the repository at this point in the history
* replace IsDisposable with Dispose

* don't check the type of TFinally twice

* extract methods

* update variable names

* udpate PipelineNet.ServiceProvider csproj

* udpate readme

* extract locals

* fix bug in async middleware disposal

* fix test name

* execute finally for empty chains of responsibility

* extract default finally classes
  • Loading branch information
mariusz96 authored Nov 10, 2024
1 parent bfdf35c commit a43043d
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -271,5 +271,18 @@ public async Task Execute_ChainOfMiddlewareWithCancellableFinally_CancellableFin
// The 'FinallyThrowIfCancellationRequested' should throw 'OperationCanceledException'.
await Assert.ThrowsAsync<OperationCanceledException>(() => responsibilityChain.Execute(exception, cancellationToken));
}

[Fact]
public async Task Execute_EmptyChainOfMiddlewareWithFinally_FinallyIsExecuted()
{
var responsibilityChain = new AsyncResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Finally<FinallyThrow>();

// Creates an ArgumentNullException.
var exception = new ArgumentNullException();

// The 'FinallyThrow' should throw 'InvalidOperationException'.
await Assert.ThrowsAsync<InvalidOperationException>(() => responsibilityChain.Execute(exception));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,17 @@ public void Execute_ChainOfMiddlewareWithFinally_FinallyIsExecuted()
Assert.Throws<InvalidOperationException>(() => responsibilityChain.Execute(exception));
}

[Fact]
public void Execute_EmptyChainOfMiddlewareWithFinally_FinallyIsExecuted()
{
var responsibilityChain = new ResponsibilityChain<Exception, bool>(new ActivatorMiddlewareResolver())
.Finally<FinallyThrow>();

// Creates an ArgumentNullException.
var exception = new ArgumentNullException();

// The 'FinallyThrow' should throw 'InvalidOperationException'.
Assert.Throws<InvalidOperationException>(() => responsibilityChain.Execute(exception));
}
}
}
1 change: 0 additions & 1 deletion src/PipelineNet.Tests/Pipelines/AsyncPipelineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ public async Task Execute_RunSamePipelineTwice_SuccessfullyExecute()
// Check if the level of 'personModel' is 3, which is configured by 'PersonWithEmailName' middleware.
Assert.Equal(3, personModel.Level);


// Creates a new instance with a 'Gender' property. The 'PersonWithGenderProperty'
// middleware should be the last one to be executed.
personModel = new PersonModel
Expand Down
1 change: 0 additions & 1 deletion src/PipelineNet.Tests/Pipelines/PipelineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ public void Execute_RunSamePipelineTwice_SuccessfullyExecute()
// Check if the level of 'personModel' is 3, which is configured by 'PersonWithEmailName' middleware.
Assert.Equal(3, personModel.Level);


// Creates a new instance with a 'Gender' property. The 'PersonWithGenderProperty'
// middleware should be the last one to be executed.
personModel = new PersonModel
Expand Down
1 change: 0 additions & 1 deletion src/PipelineNet/AsyncBaseMiddlewareFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ internal AsyncBaseMiddlewareFlow(IMiddlewareResolver middlewareResolver)
/// </summary>
private static readonly TypeInfo CancellableMiddlewareTypeInfo = typeof(TCancellableMiddleware).GetTypeInfo();


/// <summary>
/// Adds a new middleware type to the internal list of types.
/// Middleware will be executed in the same order they are added.
Expand Down
37 changes: 35 additions & 2 deletions src/PipelineNet/ChainsOfResponsibility/AsyncResponsibilityChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public class AsyncResponsibilityChain<TParameter, TReturn> : AsyncBaseMiddleware
/// </summary>
private static readonly TypeInfo CancellableFinallyTypeInfo = typeof(ICancellableAsyncFinally<TParameter, TReturn>).GetTypeInfo();

/// <summary>
/// Stores the shared instance of <see cref="DefaultFinally"/>.
/// </summary>
private static readonly IAsyncFinally<TParameter, TReturn> DefaultFinallyInstance = new DefaultFinally();

private Type _finallyType;
private Func<TParameter, Task<TReturn>> _finallyFunc;
Expand Down Expand Up @@ -92,7 +96,30 @@ public async Task<TReturn> Execute(TParameter parameter) =>
public async Task<TReturn> Execute(TParameter parameter, CancellationToken cancellationToken)
{
if (MiddlewareTypes.Count == 0)
return default(TReturn);
{
MiddlewareResolverResult finallyResolverResult = null;
try
{
if (_finallyType != null)
{
finallyResolverResult = MiddlewareResolver.Resolve(_finallyType);
EnsureMiddlewareNotNull(finallyResolverResult, _finallyType);
return await RunFinallyAsync(finallyResolverResult, parameter, cancellationToken).ConfigureAwait(false);
}
else if (_finallyFunc != null)
{
return await _finallyFunc(parameter).ConfigureAwait(false);
}
else
{
return await DefaultFinallyInstance.Finally(parameter).ConfigureAwait(false);
}
}
finally
{
await DisposeMiddlewareAsync(finallyResolverResult).ConfigureAwait(false);
}
}

int index = 0;
Func<TParameter, Task<TReturn>> next = null;
Expand Down Expand Up @@ -123,7 +150,7 @@ public async Task<TReturn> Execute(TParameter parameter, CancellationToken cance
}
else
{
next = async (p) => await Task.FromResult(default(TReturn)).ConfigureAwait(false);
next = async (p) => await DefaultFinallyInstance.Finally(p).ConfigureAwait(false);
}
}
Expand Down Expand Up @@ -235,5 +262,11 @@ public IAsyncResponsibilityChain<TParameter, TReturn> Finally(Func<TParameter, T
this._finallyFunc = finallyFunc;
return this;
}

private class DefaultFinally : IAsyncFinally<TParameter, TReturn>
{
public async Task<TReturn> Finally(TParameter parameter) =>
await Task.FromResult(default(TReturn)).ConfigureAwait(false);
}
}
}
38 changes: 36 additions & 2 deletions src/PipelineNet/ChainsOfResponsibility/ResponsibilityChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public class ResponsibilityChain<TParameter, TReturn> : BaseMiddlewareFlow<IMidd
/// </summary>
private static readonly TypeInfo FinallyTypeInfo = typeof(IFinally<TParameter, TReturn>).GetTypeInfo();

/// <summary>
/// Stores the shared instance of <see cref="DefaultFinally"/>.
/// </summary>
private static readonly IFinally<TParameter, TReturn> DefaultFinallyInstance = new DefaultFinally();

private Type _finallyType;
private Func<TParameter, TReturn> _finallyFunc;

Expand Down Expand Up @@ -113,7 +118,30 @@ public IResponsibilityChain<TParameter, TReturn> Chain(Type middlewareType)
public TReturn Execute(TParameter parameter)
{
if (MiddlewareTypes.Count == 0)
return default(TReturn);
{
MiddlewareResolverResult finallyResolverResult = null;
try
{
if (_finallyType != null)
{
finallyResolverResult = MiddlewareResolver.Resolve(_finallyType);
EnsureMiddlewareNotNull(finallyResolverResult, _finallyType);
return RunFinally(finallyResolverResult, parameter);
}
else if (_finallyFunc != null)
{
return _finallyFunc(parameter);
}
else
{
return DefaultFinallyInstance.Finally(parameter);
}
}
finally
{
DisposeMiddleware(finallyResolverResult);
}
}

int index = 0;
Func<TParameter, TReturn> next = null;
Expand Down Expand Up @@ -144,7 +172,7 @@ public TReturn Execute(TParameter parameter)
}
else
{
next = (p) => default(TReturn);
next = (p) => DefaultFinallyInstance.Finally(p);
}
}
Expand Down Expand Up @@ -172,5 +200,11 @@ private static TReturn RunFinally(MiddlewareResolverResult finallyResolverResult
var @finally = (IFinally<TParameter, TReturn>)finallyResolverResult.Middleware;
return @finally.Finally(parameter);
}

private class DefaultFinally : IFinally<TParameter, TReturn>
{
public TReturn Finally(TParameter parameter) =>
default(TReturn);
}
}
}

0 comments on commit a43043d

Please sign in to comment.