diff --git a/src/GitHubActionsDemo.Api/Controllers/AuthorsController.cs b/src/GitHubActionsDemo.Api/Controllers/AuthorsController.cs index 2fcde26..91f639a 100644 --- a/src/GitHubActionsDemo.Api/Controllers/AuthorsController.cs +++ b/src/GitHubActionsDemo.Api/Controllers/AuthorsController.cs @@ -1,7 +1,9 @@ +using System.Runtime.Intrinsics.X86; using GitHubActionsDemo.Api.Models; using GitHubActionsDemo.Api.Mappers; using GitHubActionsDemo.Service; using Microsoft.AspNetCore.Mvc; +using FluentValidation; namespace GitHubActionsDemo.Api.Controllers; @@ -10,42 +12,59 @@ namespace GitHubActionsDemo.Api.Controllers; public class AuthorsController : BaseController { private readonly ILibraryService _libraryService; + private readonly IValidator _authorValidator; + private readonly IValidator _pageValidator; public AuthorsController( - ILibraryService libraryService + ILibraryService libraryService, + IValidator authorValidator, + IValidator pageValidator ) { _libraryService = libraryService ?? throw new ArgumentNullException(nameof(libraryService)); + _authorValidator = authorValidator ?? throw new ArgumentNullException(nameof(authorValidator)); + _pageValidator = pageValidator ?? throw new ArgumentNullException(nameof(pageValidator)); } [HttpPost] - public async Task AddAuthorAsync([FromBody] AuthorRequest authorRequest) + public async Task AddAuthorAsync([FromBody] AuthorRequest authorRequest) { + var validationResult = await _authorValidator.ValidateAsync(authorRequest); + + if (!validationResult.IsValid) + return Results.ValidationProblem(validationResult.ToDictionary()); + + var result = await _libraryService.AddAuthorAsync(authorRequest.Map()); return result.Match( - success => Ok(success.Value.Map()), + success => Results.Ok(success.Value.Map()), error => InternalError() ); } [HttpGet("{authorId}")] - public async Task GetAuthorAsync(int authorId) + public async Task GetAuthorAsync(int authorId) { var result = await _libraryService.GetAuthorAsync(authorId); return result.Match( - success => Ok(success.Value.Map()), - notfound => NotFound(), + success => Results.Ok(success.Value.Map()), + notfound => Results.NotFound(), error => InternalError() ); } [HttpGet] - public async Task GetAuthorsAsync([FromQuery(Name = "page")] int page = 1, [FromQuery(Name = "pageSize")] int pageSize = 10) + public async Task GetAuthorsAsync([FromQuery] PageParameters parameters) { - var result = await _libraryService.GetAuthorsAsync(page, pageSize); + var validationResult = await _pageValidator.ValidateAsync(parameters); + + if (!validationResult.IsValid) + return Results.ValidationProblem(validationResult.ToDictionary()); + + var result = await _libraryService.GetAuthorsAsync(parameters.Page, parameters.PageSize); return result.Match( - success => PagedResult(page, pageSize, success.Value.Select(x => x.Map()).ToList()), + success => PagedResult(parameters.Page, parameters.PageSize, success.Value.Select(x => x.Map()).ToList()), error => InternalError() ); } diff --git a/src/GitHubActionsDemo.Api/Controllers/BaseController.cs b/src/GitHubActionsDemo.Api/Controllers/BaseController.cs index 857a05e..7cb2fe8 100644 --- a/src/GitHubActionsDemo.Api/Controllers/BaseController.cs +++ b/src/GitHubActionsDemo.Api/Controllers/BaseController.cs @@ -7,13 +7,13 @@ namespace GitHubActionsDemo.Api.Controllers; public class BaseController : ControllerBase { - public IActionResult InternalError() + public IResult InternalError() { - return new StatusCodeResult((int)HttpStatusCode.InternalServerError); + return Results.StatusCode((int)HttpStatusCode.InternalServerError); } - public IActionResult PagedResult(int page, int pageSize, T result) where T : IList + public IResult PagedResult(int page, int pageSize, T result) where T : IList { - return Ok(new PagedResponse(page, pageSize, result)); + return Results.Ok(new PagedResponse(page, pageSize, result)); } } \ No newline at end of file diff --git a/src/GitHubActionsDemo.Api/Controllers/BooksController.cs b/src/GitHubActionsDemo.Api/Controllers/BooksController.cs index dea4b18..b7bb018 100644 --- a/src/GitHubActionsDemo.Api/Controllers/BooksController.cs +++ b/src/GitHubActionsDemo.Api/Controllers/BooksController.cs @@ -2,6 +2,7 @@ using GitHubActionsDemo.Api.Models; using GitHubActionsDemo.Api.Mappers; using GitHubActionsDemo.Service; using Microsoft.AspNetCore.Mvc; +using FluentValidation; namespace GitHubActionsDemo.Api.Controllers; @@ -10,42 +11,58 @@ namespace GitHubActionsDemo.Api.Controllers; public class BooksController : BaseController { private readonly ILibraryService _libraryService; + private readonly IValidator _bookValidator; + private readonly IValidator _pageValidator; public BooksController( - ILibraryService libraryService + ILibraryService libraryService, + IValidator bookValidator, + IValidator pageValidator ) { _libraryService = libraryService ?? throw new ArgumentNullException(nameof(libraryService)); + _bookValidator = bookValidator ?? throw new ArgumentNullException(nameof(bookValidator)); + _pageValidator = pageValidator ?? throw new ArgumentNullException(nameof(pageValidator)); } [HttpGet] - public async Task GetBooksAsync([FromQuery(Name = "page")] int page = 1, [FromQuery(Name = "pageSize")] int pageSize = 10) + public async Task GetBooksAsync([FromQuery] PageParameters parameters) { - var result = await _libraryService.GetBooksAsync(page, pageSize); + var validationResult = await _pageValidator.ValidateAsync(parameters); + + if (!validationResult.IsValid) + return Results.ValidationProblem(validationResult.ToDictionary()); + + var result = await _libraryService.GetBooksAsync(parameters.Page, parameters.PageSize); return result.Match( - success => PagedResult(page, pageSize, success.Value.Select(x => x.Map()).ToList()), + success => PagedResult(parameters.Page, parameters.PageSize, success.Value.Select(x => x.Map()).ToList()), error => InternalError() ); } [HttpGet("{bookId}")] - public async Task GetBookAsync(int bookId) + public async Task GetBookAsync(int bookId) { var result = await _libraryService.GetBookAsync(bookId); return result.Match( - success => Ok(success.Value.Map()), - notfound => NotFound(), + success => Results.Ok(success.Value.Map()), + notfound => Results.NotFound(), error => InternalError() ); } [HttpPost] - public async Task AddBookAsync([FromBody] BookRequest bookRequest) + public async Task AddBookAsync([FromBody] BookRequest bookRequest) { + var validationResult = await _bookValidator.ValidateAsync(bookRequest); + + if (!validationResult.IsValid) + return Results.ValidationProblem(validationResult.ToDictionary()); + var result = await _libraryService.AddBookAsync(bookRequest.Map()); return result.Match( - success => Ok(success.Value.Map()), + success => Results.Ok(success.Value.Map()), error => InternalError() ); } diff --git a/src/GitHubActionsDemo.Api/GitHubActionsDemo.Api.csproj b/src/GitHubActionsDemo.Api/GitHubActionsDemo.Api.csproj index 1cb6438..c48f0b4 100644 --- a/src/GitHubActionsDemo.Api/GitHubActionsDemo.Api.csproj +++ b/src/GitHubActionsDemo.Api/GitHubActionsDemo.Api.csproj @@ -8,6 +8,7 @@ + diff --git a/src/GitHubActionsDemo.Api/Models/PageParameters.cs b/src/GitHubActionsDemo.Api/Models/PageParameters.cs new file mode 100644 index 0000000..c48337b --- /dev/null +++ b/src/GitHubActionsDemo.Api/Models/PageParameters.cs @@ -0,0 +1,7 @@ +namespace GitHubActionsDemo.Api.Models; + +public class PageParameters +{ + public int Page { get; set; } = 1; + public int PageSize { get; set; } = 10; +} \ No newline at end of file diff --git a/src/GitHubActionsDemo.Api/Models/Validators/AuthorRequestValidator.cs b/src/GitHubActionsDemo.Api/Models/Validators/AuthorRequestValidator.cs new file mode 100644 index 0000000..8fa3824 --- /dev/null +++ b/src/GitHubActionsDemo.Api/Models/Validators/AuthorRequestValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace GitHubActionsDemo.Api.Models.Validators; + +public class AuthorRequestValidator : AbstractValidator +{ + public AuthorRequestValidator() + { + RuleFor(x => x.FirstName).NotEmpty(); + RuleFor(x => x.LastName).NotEmpty(); + } +} \ No newline at end of file diff --git a/src/GitHubActionsDemo.Api/Models/Validators/BookRequestValidator.cs b/src/GitHubActionsDemo.Api/Models/Validators/BookRequestValidator.cs new file mode 100644 index 0000000..84a6d31 --- /dev/null +++ b/src/GitHubActionsDemo.Api/Models/Validators/BookRequestValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; + +namespace GitHubActionsDemo.Api.Models.Validators; + +public class BookRequestValidator : AbstractValidator +{ + public BookRequestValidator() + { + RuleFor(x => x.Title).NotEmpty(); + RuleFor(x => x.AuthorId).NotEmpty(); + + RuleFor(x => x.Isbn) + .NotEmpty() + .MinimumLength(10) + .MaximumLength(17) + .Matches(@"^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0 - 9X]{ 13}$| 97[89][0 - 9]{ 10}$| (?= (?:[0 - 9] +[- ]){ 4})[- 0 - 9]{ 17}$)(?: 97[89][- ] ?)?[0 - 9]{ 1,5}[- ]?[0 - 9]+[- ]?[0 - 9] +[- ]?[0 - 9X]$"); + + RuleFor(x => x.DatePublished) + .NotEmpty(); + } +} \ No newline at end of file diff --git a/src/GitHubActionsDemo.Api/Models/Validators/PageParametersValidator.cs b/src/GitHubActionsDemo.Api/Models/Validators/PageParametersValidator.cs new file mode 100644 index 0000000..dac0bf7 --- /dev/null +++ b/src/GitHubActionsDemo.Api/Models/Validators/PageParametersValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace GitHubActionsDemo.Api.Models.Validators; + +public class PageParametersValidator : AbstractValidator +{ + public PageParametersValidator() + { + RuleFor(x => x.Page).GreaterThan(0); + RuleFor(x => x.PageSize).InclusiveBetween(1, 100); + } +} \ No newline at end of file diff --git a/src/GitHubActionsDemo.Api/Program.cs b/src/GitHubActionsDemo.Api/Program.cs index 6b9e4ac..090a4b7 100644 --- a/src/GitHubActionsDemo.Api/Program.cs +++ b/src/GitHubActionsDemo.Api/Program.cs @@ -1,6 +1,9 @@ using GitHubActionsDemo.Service.Infrastructure; using GitHubActionsDemo.Persistance.Infrastructure; using Serilog; +using FluentValidation; +using GitHubActionsDemo.Api.Models; +using GitHubActionsDemo.Api.Models.Validators; var builder = WebApplication.CreateBuilder(args); @@ -10,6 +13,10 @@ var logger = new LoggerConfiguration() .CreateLogger(); builder.Logging.AddSerilog(logger); +builder.Services.AddScoped, AuthorRequestValidator>(); +builder.Services.AddScoped, BookRequestValidator>(); +builder.Services.AddScoped, PageParametersValidator>(); + builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen();