feat: hide pipelines internals from stack trace #3
5 changed files with 87 additions and 4 deletions
|
|
@ -32,6 +32,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||
|
||||
### Changed
|
||||
|
||||
- **request.dispatcher:** Hide pipeline internals in stack frames
|
||||
|
||||
### Removed
|
||||
|
||||
[1.0.0]: https://code.geekeey.de/geekeey/request/releases/tag/1.0.0
|
||||
|
|
|
|||
73
src/request.dispatcher.tests/StackTraceTests.cs
Normal file
73
src/request.dispatcher.tests/StackTraceTests.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Geekeey.Request.Dispatcher.Tests;
|
||||
|
||||
internal sealed class StackTraceTests
|
||||
{
|
||||
[Test]
|
||||
public async Task I_can_not_see_pipeline_internals_are_hidden_from_stack_trace_scalar()
|
||||
{
|
||||
// Arrange
|
||||
var sc = new ServiceCollection();
|
||||
sc.AddSingleton<ScalarTestTracker>();
|
||||
sc.AddRequestDispatcher(builder => builder
|
||||
.Add(typeof(FailingScalarHandler))
|
||||
.Add(typeof(ScalarOpenBehavior<,>)));
|
||||
|
||||
var provider = sc.BuildServiceProvider();
|
||||
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
|
||||
var request = new FailingScalarRequest();
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
dispatcher.DispatchAsync(request));
|
||||
|
||||
// Assert
|
||||
await Assert.That(exception).IsNotNull();
|
||||
|
||||
var stackTrace = await Assert.That(exception.StackTrace).IsNotNull();
|
||||
|
||||
await Assert.That(stackTrace).Contains(nameof(FailingScalarHandler));
|
||||
await Assert.That(stackTrace).Contains(nameof(ScalarOpenBehavior<,>));
|
||||
|
||||
// 3. Verify that the internal lambda from ScalarRequestInvoker.Chain is HIDDEN.
|
||||
// In C#, these lambdas usually appear as "ScalarRequestInvoker`2.<>c__DisplayClass..." or similar.
|
||||
// Since we added [StackTraceHidden], this frame should be omitted.
|
||||
await Assert.That(stackTrace).DoesNotContain("ScalarRequestInvoker+<>");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task I_can_not_see_pipeline_internals_are_hidden_from_stack_trace_stream()
|
||||
{
|
||||
// Arrange
|
||||
var sc = new ServiceCollection();
|
||||
sc.AddSingleton<StreamTestTracker>();
|
||||
sc.AddRequestDispatcher(builder => builder
|
||||
.Add(typeof(FailingStreamHandler))
|
||||
.Add(typeof(StreamOpenBehavior<,>)));
|
||||
|
||||
var provider = sc.BuildServiceProvider();
|
||||
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
|
||||
var request = new FailingStreamRequest();
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
dispatcher.DispatchAsync(request).ToListAsync().AsTask());
|
||||
|
||||
// Assert
|
||||
await Assert.That(exception).IsNotNull();
|
||||
|
||||
var stackTrace = await Assert.That(exception.StackTrace).IsNotNull();
|
||||
|
||||
await Assert.That(stackTrace).Contains(nameof(FailingStreamHandler));
|
||||
await Assert.That(stackTrace).Contains(nameof(StreamOpenBehavior<,>));
|
||||
|
||||
// 3. Verify that the internal lambda from ScalarRequestInvoker.Chain is HIDDEN.
|
||||
// In C#, these lambdas usually appear as "ScalarRequestInvoker`2.<>c__DisplayClass..." or similar.
|
||||
// Since we added [StackTraceHidden], this frame should be omitted.
|
||||
await Assert.That(stackTrace).DoesNotContain("StreamRequestInvoker+<>");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Geekeey.Request.Dispatcher.Tests;
|
||||
|
||||
public class StreamOpenBehavior<TRequest, TResponse>(StreamTestTracker tracker) : IStreamRequestBehavior<TRequest, TResponse>
|
||||
where TRequest : IStreamRequest<TResponse>
|
||||
{
|
||||
public IAsyncEnumerable<TResponse> HandleAsync(TRequest request, StreamHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
|
||||
public async IAsyncEnumerable<TResponse> HandleAsync(TRequest request, StreamHandlerDelegate<TResponse> next, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
tracker.Executed = true;
|
||||
return next(request, cancellationToken);
|
||||
await foreach (var response in next(request, cancellationToken))
|
||||
{
|
||||
yield return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
|
@ -36,7 +38,7 @@ internal sealed class ScalarRequestInvoker<TRequest, TResponse> : ScalarRequestI
|
|||
|
||||
static ScalarHandlerDelegate<TResponse> Chain(ScalarHandlerDelegate<TResponse> next, IScalarRequestBehavior<TRequest, TResponse> filter)
|
||||
{
|
||||
return (req, ct) => filter.HandleAsync((TRequest)req, next, ct);
|
||||
return [StackTraceHidden] (req, ct) => filter.HandleAsync((TRequest)req, next, ct);
|
||||
}
|
||||
|
||||
Task<TResponse> Head(IScalarRequest<TResponse> r, CancellationToken ct)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -41,7 +42,7 @@ internal sealed class StreamRequestInvoker<TRequest, TResponse> : StreamRequestI
|
|||
|
||||
static StreamHandlerDelegate<TResponse> Chain(StreamHandlerDelegate<TResponse> next, IStreamRequestBehavior<TRequest, TResponse> filter)
|
||||
{
|
||||
return (req, ct) => filter.HandleAsync((TRequest)req, next, ct);
|
||||
return [StackTraceHidden] (req, ct) => filter.HandleAsync((TRequest)req, next, ct);
|
||||
}
|
||||
|
||||
IAsyncEnumerable<TResponse> Head(IStreamRequest<TResponse> r, CancellationToken ct)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue