feat(application): enforce validation and API error mapping rules
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
using DigitalData.TaskFlow.Domain.Enums;
|
||||
|
||||
namespace DigitalData.TaskFlow.Application.Common.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 403 API exception.
|
||||
/// </summary>
|
||||
public sealed class ForbiddenApiException : ApiException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ForbiddenApiException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">Response message.</param>
|
||||
public ForbiddenApiException(string message)
|
||||
: base(403, MetaCode.NoAccess, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using DigitalData.TaskFlow.Domain.Enums;
|
||||
|
||||
namespace DigitalData.TaskFlow.Application.Common.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 400 API exception.
|
||||
/// </summary>
|
||||
public sealed class InvalidRequestApiException : ApiException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InvalidRequestApiException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">Response message.</param>
|
||||
public InvalidRequestApiException(string message)
|
||||
: base(400, MetaCode.InvalidRequest, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,6 @@ public interface IDocumentRepository : IRepository
|
||||
/// </summary>
|
||||
/// <param name="id">Document identifier.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Binary document payload.</returns>
|
||||
Task<DocumentBinaryResult> GetAsync(string id, CancellationToken cancellationToken);
|
||||
/// <returns>Binary document payload when found; otherwise <see langword="null"/>.</returns>
|
||||
Task<DocumentBinaryResult?> GetAsync(string id, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using DigitalData.TaskFlow.Application.Common.Factories;
|
||||
using DigitalData.TaskFlow.Application.Common.Exceptions;
|
||||
using DigitalData.TaskFlow.Application.Common.Interfaces;
|
||||
using DigitalData.TaskFlow.Domain.Common;
|
||||
using DigitalData.TaskFlow.Domain.Documents;
|
||||
@@ -45,6 +46,11 @@ public sealed class GetDocumentQueryHandler(IDocumentRepository documentReposito
|
||||
{
|
||||
var document = await documentRepository.GetAsync(request.Id, cancellationToken);
|
||||
|
||||
if (document is null)
|
||||
{
|
||||
throw new NotFoundApiException("Dokument nicht gefunden");
|
||||
}
|
||||
|
||||
return ApiEnvelopeFactory.Create(document);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ public sealed record CloseWorkflowCommand(
|
||||
public sealed class CloseWorkflowCommandValidator : AbstractValidator<CloseWorkflowCommand>
|
||||
{
|
||||
private static readonly string[] AllowedSources = ["Ferdinand", "Contracts"];
|
||||
private const int ApproveMethodId = 2;
|
||||
private const int RejectMethodId = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes validation rules for <see cref="CloseWorkflowCommand"/>.
|
||||
@@ -53,6 +55,20 @@ public sealed class CloseWorkflowCommandValidator : AbstractValidator<CloseWorkf
|
||||
.NotEmpty()
|
||||
.Must(source => AllowedSources.Contains(source))
|
||||
.WithMessage("Source must be one of the supported source systems.");
|
||||
|
||||
RuleFor(x => x.ClosingMethodId)
|
||||
.Must(methodId => methodId is ApproveMethodId or RejectMethodId)
|
||||
.WithMessage("Unbekannte closing_method_id");
|
||||
|
||||
RuleFor(x => x.Comment)
|
||||
.NotEmpty()
|
||||
.When(x => x.ClosingMethodId == RejectMethodId)
|
||||
.WithMessage("Kommentar ist fur diese closing_method Pflicht");
|
||||
|
||||
RuleFor(x => x.RejectionReasonId)
|
||||
.NotNull()
|
||||
.When(x => x.ClosingMethodId == RejectMethodId)
|
||||
.WithMessage("rejection_reason_id ist fur diese closing_method Pflicht");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +95,16 @@ public sealed class CloseWorkflowCommandHandler(IWorkflowRepository workflowRepo
|
||||
decision,
|
||||
cancellationToken);
|
||||
|
||||
if (result.Outcome == CloseWorkflowOutcome.NoAccess)
|
||||
{
|
||||
throw new ForbiddenApiException("Kein Zugriff");
|
||||
}
|
||||
|
||||
if (result.Outcome == CloseWorkflowOutcome.NotImplemented)
|
||||
{
|
||||
throw new InvalidOperationException("Closing integration is not finalized yet.");
|
||||
}
|
||||
|
||||
if (result.ProcessId <= 0)
|
||||
{
|
||||
throw new NotFoundApiException("Vorgang nicht gefunden");
|
||||
|
||||
@@ -45,6 +45,11 @@ public sealed class GetWorkflowsQueryHandler(IWorkflowRepository workflowReposit
|
||||
{
|
||||
var data = await workflowRepository.GetAsync(request.WisagId, cancellationToken);
|
||||
|
||||
if (data.Sources.Count == 0)
|
||||
{
|
||||
return ApiEnvelopeFactory.Create(data, MetaCode.NoAccess);
|
||||
}
|
||||
|
||||
var hasListingData = data.Sources
|
||||
.SelectMany(source => source switch
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user