diff --git a/src/SwiftLink.Application/Behaviors/ValidationBehavior.cs b/src/SwiftLink.Application/Behaviors/ValidationBehavior.cs index f22c606..655c170 100644 --- a/src/SwiftLink.Application/Behaviors/ValidationBehavior.cs +++ b/src/SwiftLink.Application/Behaviors/ValidationBehavior.cs @@ -1,47 +1,35 @@ -using Azure; -using FluentValidation; +using FluentValidation; using MediatR; -using System.Net; +using SwiftLink.Application.Common; +using SwiftLink.Application.Common.Exceptions; namespace SwiftLink.Application.Behaviors; -/// -/// Pipie line for Validation. -/// -/// -/// -public class ValidationBehavior(IEnumerable> validators) - : IPipelineBehavior where TRequest : notnull +public sealed class ValidationBehavior(IEnumerable> validators) + : IPipelineBehavior { private readonly IEnumerable> _validators = validators; - - public async Task Handle(TRequest request, RequestHandlerDelegate next, - CancellationToken ct = default) + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken) { - if (!_validators.Any()) - return await next(); - - var validationContext = new ValidationContext(request); - - var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(validationContext, ct))); + var context = new ValidationContext(request); - var errorList = validationResults.Where(r => r.Errors.Count != 0) - .SelectMany(x => x.Errors) - .ToList(); + var validationFailures = await Task.WhenAll(_validators.Select(validator => validator.ValidateAsync(context))); - if (errorList.Count > 0) - { - var message = string.Join(" | ", errorList.Select(e => e.ErrorMessage)); + var errors = validationFailures + .Where(validationResult => !validationResult.IsValid) + .SelectMany(validationResult => validationResult.Errors) + .Select(validationFailure => new ValidationError( + validationFailure.PropertyName, + validationFailure.ErrorMessage)) + .ToList(); - if (Enum.TryParse(errorList.First().ErrorCode, out HttpStatusCode statusCode)) - return GenerateResponse(message, statusCode); + if (errors.Count != 0) + throw new BusinessValidationException(errors); - return GenerateResponse(message); - } - - return await next(); + var response = await next(); + return response; } - - private static TResult GenerateResponse(string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest) - => (TResult)Activator.CreateInstance(typeof(Result), false, message, statusCode); -} +} \ No newline at end of file diff --git a/src/SwiftLink.Application/Common/Exceptions/BusinessValidationException.cs b/src/SwiftLink.Application/Common/Exceptions/BusinessValidationException.cs new file mode 100644 index 0000000..c9c5cba --- /dev/null +++ b/src/SwiftLink.Application/Common/Exceptions/BusinessValidationException.cs @@ -0,0 +1,5 @@ +namespace SwiftLink.Application.Common.Exceptions; +public class BusinessValidationException(List errors) : Exception +{ + public readonly List Errors = errors; +} diff --git a/src/SwiftLink.Application/Common/ValidationError.cs b/src/SwiftLink.Application/Common/ValidationError.cs new file mode 100644 index 0000000..196600a --- /dev/null +++ b/src/SwiftLink.Application/Common/ValidationError.cs @@ -0,0 +1,2 @@ +namespace SwiftLink.Application.Common; +public record ValidationError(string PropertyName, string ErrorMessage); diff --git a/src/SwiftLink.Application/UseCases/Links/Commands/GenerateShortCode/GenerateShortCodeCommandHandler.cs b/src/SwiftLink.Application/UseCases/Links/Commands/GenerateShortCode/GenerateShortCodeCommandHandler.cs index fab0401..951d179 100644 --- a/src/SwiftLink.Application/UseCases/Links/Commands/GenerateShortCode/GenerateShortCodeCommandHandler.cs +++ b/src/SwiftLink.Application/UseCases/Links/Commands/GenerateShortCode/GenerateShortCodeCommandHandler.cs @@ -19,6 +19,7 @@ public class GenerateShortCodeCommandHandler(IApplicationDbContext dbContext, public async Task> Handle(GenerateShortCodeCommand request, CancellationToken cancellationToken = default) { + var subscriber = _dbContext.Set().Find(request.Token); var link = new Link { OriginalUrl = request.Url, diff --git a/src/SwiftLink.Application/UseCases/Links/Commands/GenerateShortCode/GenerateShortCodeValidator.cs b/src/SwiftLink.Application/UseCases/Links/Commands/GenerateShortCode/GenerateShortCodeValidator.cs index 1211134..571ce09 100644 --- a/src/SwiftLink.Application/UseCases/Links/Commands/GenerateShortCode/GenerateShortCodeValidator.cs +++ b/src/SwiftLink.Application/UseCases/Links/Commands/GenerateShortCode/GenerateShortCodeValidator.cs @@ -10,9 +10,9 @@ public GenerateShortCodeValidator() .NotNull().WithMessage(Constants.Link.UrlMustBeSent) .Must(BeAValidUrl).WithMessage(Constants.Link.InvalidUrlFormat); - RuleFor(x => x.Token) - .NotNull().WithMessage(Constants.Link.UrlMustBeSent) - .Must(BeAValidUrl).WithMessage(Constants.Link.InvalidUrlFormat); + //RuleFor(x => x.Token) + // .NotNull().WithMessage(Constants.Link.UrlMustBeSent) + // .Must(BeAValidUrl).WithMessage(Constants.Link.InvalidUrlFormat); } private bool BeAValidUrl(string url) diff --git a/src/SwiftLink.Presentation/Middleware/BusinessValidationExceptionHandlingMiddleware.cs b/src/SwiftLink.Presentation/Middleware/BusinessValidationExceptionHandlingMiddleware.cs new file mode 100644 index 0000000..dafa01a --- /dev/null +++ b/src/SwiftLink.Presentation/Middleware/BusinessValidationExceptionHandlingMiddleware.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using SwiftLink.Application.Common.Exceptions; +using System; + +namespace SwiftLink.Presentation.Middleware; + +public sealed class BusinessValidationExceptionHandlingMiddleware(RequestDelegate next) +{ + private readonly RequestDelegate _next = next; + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (BusinessValidationException exception) + { + var problemDetails = new ProblemDetails + { + Status = StatusCodes.Status400BadRequest, + Type = "ValidationFailure", + Title = "Validation error", + Detail = "One or more validation errors has occurred" + }; + + if (exception.Errors is not null) + problemDetails.Extensions["errors"] = exception.Errors; + + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsJsonAsync(problemDetails); + } + } +} \ No newline at end of file diff --git a/src/SwiftLink.Presentation/Program.cs b/src/SwiftLink.Presentation/Program.cs index ba50d11..bf8bfd2 100644 --- a/src/SwiftLink.Presentation/Program.cs +++ b/src/SwiftLink.Presentation/Program.cs @@ -6,6 +6,7 @@ using SwiftLink.Infrastructure; using SwiftLink.Infrastructure.Persistence.Context; using SwiftLink.Presentation; +using SwiftLink.Presentation.Middleware; using SwiftLink.Shared; var builder = WebApplication.CreateBuilder(args); @@ -41,6 +42,7 @@ var app = builder.Build(); { + app.UseMiddleware(); app.UseExceptionHandler(error => { error.Run(async context =>