Skip to content

Commit

Permalink
Improve error response from invalid ModelState when [ApiController] i…
Browse files Browse the repository at this point in the history
…s used
  • Loading branch information
bkoelman committed Jan 20, 2023
1 parent 55d7cb0 commit 79cfb3d
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 3 deletions.
22 changes: 19 additions & 3 deletions src/JsonApiDotNetCore/Errors/UnsuccessfulActionResultException.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Net;
using JetBrains.Annotations;
using JsonApiDotNetCore.Serialization.Objects;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace JsonApiDotNetCore.Errors;
Expand All @@ -20,20 +21,35 @@ public UnsuccessfulActionResultException(HttpStatusCode status)
}

public UnsuccessfulActionResultException(ProblemDetails problemDetails)
: base(ToError(problemDetails))
: base(ToErrorObjects(problemDetails))
{
}

private static ErrorObject ToError(ProblemDetails problemDetails)
private static IEnumerable<ErrorObject> ToErrorObjects(ProblemDetails problemDetails)
{
ArgumentGuard.NotNull(problemDetails);

HttpStatusCode status = problemDetails.Status != null ? (HttpStatusCode)problemDetails.Status.Value : HttpStatusCode.InternalServerError;

if (problemDetails is HttpValidationProblemDetails validationProblemDetails && validationProblemDetails.Errors.Any())
{
foreach (string errorMessage in validationProblemDetails.Errors.SelectMany(pair => pair.Value))
{
yield return ToErrorObject(status, validationProblemDetails, errorMessage);
}
}
else
{
yield return ToErrorObject(status, problemDetails, problemDetails.Detail);
}
}

private static ErrorObject ToErrorObject(HttpStatusCode status, ProblemDetails problemDetails, string? detail)
{
var error = new ErrorObject(status)
{
Title = problemDetails.Title,
Detail = problemDetails.Detail
Detail = detail
};

if (!string.IsNullOrWhiteSpace(problemDetails.Instance))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,48 @@ public async Task ApiController_attribute_transforms_NotFound_action_result_with
error.Links.ShouldNotBeNull();
error.Links.About.Should().Be("https://tools.ietf.org/html/rfc7231#section-6.5.4");
}

[Fact]
public async Task ProblemDetails_from_invalid_ModelState_is_translated_into_error_response()
{
// Arrange
var requestBody = new
{
data = new
{
type = "civilians",
attributes = new
{
name = (string?)null,
yearOfBirth = 1850
}
}
};

const string route = "/world-civilians";

// Act
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync<Document>(route, requestBody);

// Assert
httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest);

responseDocument.Errors.ShouldHaveCount(2);

ErrorObject error1 = responseDocument.Errors[0];
error1.StatusCode.Should().Be(HttpStatusCode.BadRequest);
error1.Links.ShouldNotBeNull();
error1.Links.About.Should().Be("https://tools.ietf.org/html/rfc7231#section-6.5.1");
error1.Title.Should().Be("One or more validation errors occurred.");
error1.Detail.Should().Be("The Name field is required.");
error1.Source.Should().BeNull();

ErrorObject error2 = responseDocument.Errors[1];
error2.StatusCode.Should().Be(HttpStatusCode.BadRequest);
error2.Links.ShouldNotBeNull();
error2.Links.About.Should().Be("https://tools.ietf.org/html/rfc7231#section-6.5.1");
error2.Title.Should().Be("One or more validation errors occurred.");
error2.Detail.Should().Be("The field YearOfBirth must be between 1900 and 2050.");
error2.Source.Should().BeNull();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
Expand All @@ -10,4 +11,8 @@ public sealed class Civilian : Identifiable<int>
{
[Attr]
public string Name { get; set; } = null!;

[Attr]
[Range(1900, 2050)]
public int YearOfBirth { get; set; }
}

0 comments on commit 79cfb3d

Please sign in to comment.