Skip to content

Commit

Permalink
fix: Return 410 Gone when updating deleted dialog (#464)
Browse files Browse the repository at this point in the history
  • Loading branch information
oskogstad authored Feb 21, 2024
1 parent 5e55c34 commit 2498b0a
Show file tree
Hide file tree
Showing 9 changed files with 34 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using FluentValidation.Results;

namespace Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;

public record BadRequest(List<string> Reasons)
{
private const string BadRequestMessage = "BadRequest";

public BadRequest(params string[] reasons) : this(reasons.ToList()) { }

public List<ValidationFailure> ToValidationResults() =>
Reasons.Select(x => new ValidationFailure(BadRequestMessage, x)).ToList();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public sealed class UpdateDialogCommand : IRequest<UpdateDialogResult>
}

[GenerateOneOf]
public partial class UpdateDialogResult : OneOfBase<Success, EntityNotFound, ValidationError, DomainError, ConcurrencyError>;
public partial class UpdateDialogResult : OneOfBase<Success, EntityNotFound, BadRequest, ValidationError, DomainError, ConcurrencyError>;

internal sealed class UpdateDialogCommandHandler : IRequestHandler<UpdateDialogCommand, UpdateDialogResult>
{
Expand Down Expand Up @@ -67,6 +67,7 @@ public async Task<UpdateDialogResult> Handle(UpdateDialogCommand request, Cancel
.ThenInclude(x => x.Title!.Localizations)
.Include(x => x.ApiActions)
.ThenInclude(x => x.Endpoints)
.IgnoreQueryFilters()
.Where(x => resourceIds.Contains(x.ServiceResource))
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);

Expand All @@ -75,6 +76,13 @@ public async Task<UpdateDialogResult> Handle(UpdateDialogCommand request, Cancel
return new EntityNotFound<DialogEntity>(request.Id);
}

if (dialog.Deleted)
{
// TODO: When restoration is implemented, add a hint to the error message.
// https://github.com/digdir/dialogporten/pull/406
return new BadRequest($"Entity '{nameof(DialogEntity)}' with key '{request.Id}' is removed, and cannot be updated.");
}

// Update primitive properties
_mapper.Map(request.Dto, dialog);
ValidateTimeFields(dialog);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ public static Task BadRequestAsync(this IEndpoint ep, ValidationError failure, C
=> ep.BadRequestAsync(failure.Errors, cancellationToken);
public static Task BadRequestAsync(this IEndpoint ep, IEnumerable<ValidationFailure> failures, CancellationToken cancellationToken = default)
=> ep.HttpContext.Response.SendErrorsAsync(failures.ToList() ?? [], StatusCodes.Status400BadRequest, cancellation: cancellationToken);
public static Task BadRequestAsync(this IEndpoint ep, BadRequest badRequest, CancellationToken cancellationToken = default)
=> ep.HttpContext.Response.SendErrorsAsync(
badRequest.ToValidationResults(),
cancellation: cancellationToken);

public static Task PreconditionFailed(this IEndpoint ep, CancellationToken cancellationToken = default)
=> ep.HttpContext.Response.SendErrorsAsync([], StatusCodes.Status412PreconditionFailed, cancellation: cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public override async Task HandleAsync(CreateDialogActivityRequest req, Cancella
await result.Match(
success => SendCreatedAtAsync<GetDialogActivityEndpoint>(new GetDialogActivityQuery { DialogId = dialog.Id, ActivityId = req.Id.Value }, req.Id, cancellation: ct),
notFound => this.NotFoundAsync(notFound, ct),
badRequest => this.BadRequestAsync(badRequest, ct),
validationError => this.BadRequestAsync(validationError, ct),
domainError => this.UnprocessableEntityAsync(domainError, ct),
concurrencyError => this.PreconditionFailed(cancellationToken: ct));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public override async Task HandleAsync(CreateDialogElementRequest req, Cancellat
await result.Match(
success => SendCreatedAtAsync<GetDialogElementEndpoint>(new GetDialogElementQuery { DialogId = dialog.Id, ElementId = req.Id.Value }, req.Id, cancellation: ct),
notFound => this.NotFoundAsync(notFound, ct),
badRequest => this.BadRequestAsync(badRequest, ct),
validationError => this.BadRequestAsync(validationError, ct),
domainError => this.UnprocessableEntityAsync(domainError, ct),
concurrencyError => this.PreconditionFailed(cancellationToken: ct));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public override async Task HandleAsync(DeleteDialogElementRequest req, Cancellat
await result.Match(
success => SendNoContentAsync(ct),
notFound => this.NotFoundAsync(notFound, ct),
badRequest => this.BadRequestAsync(badRequest, ct),
validationError => this.BadRequestAsync(validationError, ct),
domainError => this.UnprocessableEntityAsync(domainError, ct),
concurrencyError => this.PreconditionFailed(cancellationToken: ct));
Expand Down Expand Up @@ -102,4 +103,3 @@ Deletes a given dialog element (hard delete). For more information see the docum
Responses[StatusCodes.Status412PreconditionFailed] = Constants.SwaggerSummary.RevisionMismatch;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ await this.NotFoundAsync(
await result.Match(
success => SendNoContentAsync(ct),
notFound => this.NotFoundAsync(notFound, ct),
badRequest => this.BadRequestAsync(badRequest, ct),
validationError => this.BadRequestAsync(validationError, ct),
domainError => this.UnprocessableEntityAsync(domainError, ct),
concurrencyError => this.PreconditionFailed(cancellationToken: ct));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public async Task<IActionResult> Patch(
var result = await _sender.Send(command, ct);
return result.Match(
success => (IActionResult)NoContent(),
entityNotFound => NotFound(HttpContext.ResponseBuilder(StatusCodes.Status404NotFound, entityNotFound.ToValidationResults())),
notFound => NotFound(HttpContext.ResponseBuilder(StatusCodes.Status404NotFound, notFound.ToValidationResults())),
badRequest => BadRequest(HttpContext.ResponseBuilder(StatusCodes.Status400BadRequest, badRequest.ToValidationResults())),
validationFailed => BadRequest(HttpContext.ResponseBuilder(StatusCodes.Status400BadRequest, validationFailed.Errors.ToList())),
domainError => UnprocessableEntity(HttpContext.ResponseBuilder(StatusCodes.Status422UnprocessableEntity, domainError.ToValidationResults())),
concurrencyError => new ObjectResult(HttpContext.ResponseBuilder(StatusCodes.Status412PreconditionFailed)) { StatusCode = StatusCodes.Status412PreconditionFailed }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public override async Task HandleAsync(UpdateDialogRequest req, CancellationToke
var updateDialogResult = await _sender.Send(command, ct);
await updateDialogResult.Match(
success => SendNoContentAsync(ct),
entityNotFound => this.NotFoundAsync(entityNotFound, ct),
notFound => this.NotFoundAsync(notFound, ct),
badRequest => this.BadRequestAsync(badRequest, ct),
validationFailed => this.BadRequestAsync(validationFailed, ct),
domainError => this.UnprocessableEntityAsync(domainError, ct),
concurrencyError => this.PreconditionFailed(ct));
Expand Down

0 comments on commit 2498b0a

Please sign in to comment.