Skip to content

Commit

Permalink
Decode known status code for local execution
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubfijalkowski committed Feb 7, 2024
1 parent 59da577 commit 124155f
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ Action<ICQRSApplicationBuilder> configure
)
: base(serviceProvider, objectSource, configure) { }

public async Task<CommandResult> RunAsync<T>(
public Task<CommandResult> RunAsync<T>(
T command,
ClaimsPrincipal user,
CancellationToken cancellationToken = default
)
where T : ICommand => (CommandResult)(await RunInternalAsync(command, user, null, cancellationToken))!;
where T : ICommand => RunInternalAsync<CommandResult>(command, user, null, cancellationToken);

public async Task<CommandResult> RunAsync<T>(
public Task<CommandResult> RunAsync<T>(
T command,
ClaimsPrincipal user,
IHeaderDictionary headers,
CancellationToken cancellationToken = default
)
where T : ICommand => (CommandResult)(await RunInternalAsync(command, user, headers, cancellationToken))!;
where T : ICommand => RunInternalAsync<CommandResult>(command, user, headers, cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Security.Claims;
using LeanCode.Contracts;
using LeanCode.CQRS.AspNetCore.Middleware;
using LeanCode.CQRS.AspNetCore.Registration;
using LeanCode.CQRS.Execution;
using LeanCode.OpenTelemetry;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;

namespace LeanCode.CQRS.AspNetCore.Local;
Expand All @@ -32,7 +32,7 @@ Action<ICQRSApplicationBuilder> configure
pipeline = app.Build();
}

protected async Task<object?> RunInternalAsync(
protected async Task<TResult> RunInternalAsync<TResult>(
object obj,
ClaimsPrincipal user,
IHeaderDictionary? headers,
Expand Down Expand Up @@ -61,6 +61,17 @@ CancellationToken cancellationToken

localContext.CallAborted.ThrowIfCancellationRequested();

return localContext.GetCQRSRequestPayload().Result!.Value.Payload;
return Decode<TResult>(obj, localContext.GetCQRSRequestPayload().Result!.Value);
}

private static T Decode<T>(object payload, ExecutionResult result)
{
return result.StatusCode switch
{
StatusCodes.Status200OK or StatusCodes.Status422UnprocessableEntity => (T)result.Payload!,
StatusCodes.Status401Unauthorized => throw new UnauthenticatedCQRSRequestException(payload.GetType()),
StatusCodes.Status403Forbidden => throw new UnauthorizedCQRSRequestException(payload.GetType()),
var e => throw new UnknownStatusCodeException(e, payload.GetType()),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ Action<ICQRSApplicationBuilder> configure
)
: base(serviceProvider, objectSource, configure) { }

Check warning on line 15 in src/CQRS/LeanCode.CQRS.AspNetCore/Local/MiddlewareBasedLocalOperationExecutor.cs

View check run for this annotation

Codecov / codecov/patch

src/CQRS/LeanCode.CQRS.AspNetCore/Local/MiddlewareBasedLocalOperationExecutor.cs#L15

Added line #L15 was not covered by tests

public async Task<TResult> ExecuteAsync<TResult>(
public Task<TResult> ExecuteAsync<TResult>(
IOperation<TResult> query,
ClaimsPrincipal user,
CancellationToken cancellationToken = default
) => (TResult)(await RunInternalAsync(query, user, null, cancellationToken))!;
) => RunInternalAsync<TResult>(query, user, null, cancellationToken);

Check warning on line 21 in src/CQRS/LeanCode.CQRS.AspNetCore/Local/MiddlewareBasedLocalOperationExecutor.cs

View check run for this annotation

Codecov / codecov/patch

src/CQRS/LeanCode.CQRS.AspNetCore/Local/MiddlewareBasedLocalOperationExecutor.cs#L21

Added line #L21 was not covered by tests

public async Task<TResult> ExecuteAsync<TResult>(
public Task<TResult> ExecuteAsync<TResult>(
IOperation<TResult> query,
ClaimsPrincipal user,
IHeaderDictionary headers,
CancellationToken cancellationToken = default
) => (TResult)(await RunInternalAsync(query, user, null, cancellationToken))!;
) => RunInternalAsync<TResult>(query, user, null, cancellationToken);

Check warning on line 28 in src/CQRS/LeanCode.CQRS.AspNetCore/Local/MiddlewareBasedLocalOperationExecutor.cs

View check run for this annotation

Codecov / codecov/patch

src/CQRS/LeanCode.CQRS.AspNetCore/Local/MiddlewareBasedLocalOperationExecutor.cs#L28

Added line #L28 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ Action<ICQRSApplicationBuilder> configure
)
: base(serviceProvider, objectSource, configure) { }

public async Task<TResult> GetAsync<TResult>(
public Task<TResult> GetAsync<TResult>(
IQuery<TResult> query,
ClaimsPrincipal user,
CancellationToken cancellationToken = default
) => (TResult)(await RunInternalAsync(query, user, null, cancellationToken))!;
) => RunInternalAsync<TResult>(query, user, null, cancellationToken);

public async Task<TResult> GetAsync<TResult>(
public Task<TResult> GetAsync<TResult>(
IQuery<TResult> query,
ClaimsPrincipal user,
IHeaderDictionary headers,
CancellationToken cancellationToken = default
) => (TResult)(await RunInternalAsync(query, user, headers, cancellationToken))!;
) => RunInternalAsync<TResult>(query, user, headers, cancellationToken);

Check warning on line 28 in src/CQRS/LeanCode.CQRS.AspNetCore/Local/MiddlewareBasedLocalQueryExecutor.cs

View check run for this annotation

Codecov / codecov/patch

src/CQRS/LeanCode.CQRS.AspNetCore/Local/MiddlewareBasedLocalQueryExecutor.cs#L28

Added line #L28 was not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace LeanCode.CQRS.AspNetCore.Local;

public class UnauthenticatedCQRSRequestException : Exception
{
public Type ObjectType { get; }

public UnauthenticatedCQRSRequestException(Type objectType)
: base($"The request {objectType.FullName} was not authenticated.")
{
ObjectType = objectType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace LeanCode.CQRS.AspNetCore.Local;

public class UnauthorizedCQRSRequestException : Exception
{
public Type ObjectType { get; }

public UnauthorizedCQRSRequestException(Type objectType)
: base($"The request {objectType.FullName} was not authorized.")
{
ObjectType = objectType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace LeanCode.CQRS.AspNetCore.Local;

public class UnknownStatusCodeException : Exception
{
public int StatusCode { get; }
public Type ObjectType { get; }

public UnknownStatusCodeException(int statusCode, Type objectType)
: base($"Unknown status code {statusCode} for request {objectType.FullName}.")
{
StatusCode = statusCode;
ObjectType = objectType;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Globalization;
using System.Security.Claims;
using FluentAssertions;
using LeanCode.Components;
Expand Down Expand Up @@ -98,6 +99,34 @@ public async Task Object_metadata_is_set()

await executor.RunAsync(command, new ClaimsPrincipal());
}

[Fact]
public async Task Decodes_401_status_code()
{
var headers = new HeaderDictionary { [LocalHandlerMiddleware.StatusHeader] = "401", };

var act = () => executor.RunAsync(new LocalCommand(), new ClaimsPrincipal(), headers);
await act.Should().ThrowAsync<UnauthenticatedCQRSRequestException>();
}

[Fact]
public async Task Decodes_403_status_code()
{
var headers = new HeaderDictionary { [LocalHandlerMiddleware.StatusHeader] = "403", };

var act = () => executor.RunAsync(new LocalCommand(), new ClaimsPrincipal(), headers);
await act.Should().ThrowAsync<UnauthorizedCQRSRequestException>();
}

[Fact]
public async Task Decodes_499_status_code_as_unknown()
{
var headers = new HeaderDictionary { [LocalHandlerMiddleware.StatusHeader] = "499", };

var act = () => executor.RunAsync(new LocalCommand(), new ClaimsPrincipal(), headers);
var exc = await act.Should().ThrowAsync<UnknownStatusCodeException>();
exc.Which.StatusCode.Should().Be(499);
}
}

public class LocalDataStorage
Expand Down Expand Up @@ -146,9 +175,20 @@ public Task ExecuteAsync(HttpContext context, LocalCommand command)

public class LocalHandlerMiddleware : IMiddleware
{
public const string StatusHeader = "X-Status";

public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
context.RequestServices.GetRequiredService<LocalDataStorage>().Middlewares.Add(this);
return next(context);
if (context.Request.Headers.TryGetValue(StatusHeader, out var value))
{
var code = int.Parse(value!, CultureInfo.InvariantCulture);
context.GetCQRSRequestPayload().SetResult(ExecutionResult.Empty(code));
return Task.CompletedTask;
}
else
{
context.RequestServices.GetRequiredService<LocalDataStorage>().Middlewares.Add(this);
return next(context);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ public async Task Security_middleware_works()
{
var executor = BuildWith(c => c.Secure());

var result = await executor.RunAsync(new SecuredCommand(), new());
result.WasSuccessful.Should().BeFalse();
var act = () => executor.RunAsync(new SecuredCommand(), new());
await act.Should().ThrowAsync<UnauthenticatedCQRSRequestException>();
}

public static ILocalCommandExecutor BuildWith(Action<ICQRSApplicationBuilder> configure)
Expand Down

0 comments on commit 124155f

Please sign in to comment.