Skip to content

Commit 2322ac8

Browse files
author
Bart Koelman
committed
Unified remaining error messages
1 parent fe10147 commit 2322ac8

File tree

9 files changed

+49
-73
lines changed

9 files changed

+49
-73
lines changed

src/JsonApiDotNetCore/Errors/CannotClearRequiredRelationshipException.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public CannotClearRequiredRelationshipException(string relationshipName, string
1414
: base(new ErrorObject(HttpStatusCode.BadRequest)
1515
{
1616
Title = "Failed to clear a required relationship.",
17-
Detail = $"The relationship '{relationshipName}' of resource type '{resourceType}' " +
17+
Detail = $"The relationship '{relationshipName}' on resource type '{resourceType}' " +
1818
$"with ID '{resourceId}' cannot be cleared because it is a required relationship."
1919
})
2020
{

src/JsonApiDotNetCore/Serialization/DeserializationException.cs

-23
This file was deleted.

src/JsonApiDotNetCore/Serialization/JsonApiReader.cs

+14-16
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,7 @@ private object GetModel(string requestBody)
6464
using IDisposable _ = CodeTimingSessionManager.Current.Measure("Read request body");
6565

6666
Document document = DeserializeDocument(requestBody, _options.SerializerReadOptions);
67-
68-
try
69-
{
70-
return _documentAdapter.Convert(document);
71-
}
72-
catch (ModelConversionException exception)
73-
{
74-
throw new InvalidRequestBodyException(requestBody, exception.GenericMessage, exception.SpecificMessage, exception.SourcePointer,
75-
exception.StatusCode, exception);
76-
}
77-
catch (DeserializationException exception)
78-
{
79-
throw new InvalidRequestBodyException(requestBody, exception.GenericMessage, exception.SpecificMessage, exception.SourcePointer);
80-
}
67+
return ConvertDocumentToModel(document, requestBody);
8168
}
8269

8370
[AssertionMethod]
@@ -94,8 +81,6 @@ private static void AssertHasRequestBody(string requestBody)
9481

9582
private Document DeserializeDocument(string requestBody, JsonSerializerOptions serializerOptions)
9683
{
97-
ArgumentGuard.NotNull(requestBody, nameof(requestBody));
98-
9984
try
10085
{
10186
using IDisposable _ =
@@ -111,5 +96,18 @@ private Document DeserializeDocument(string requestBody, JsonSerializerOptions s
11196
throw new InvalidRequestBodyException(requestBody, null, exception.Message, null, null, exception);
11297
}
11398
}
99+
100+
private object ConvertDocumentToModel(Document document, string requestBody)
101+
{
102+
try
103+
{
104+
return _documentAdapter.Convert(document);
105+
}
106+
catch (ModelConversionException exception)
107+
{
108+
throw new InvalidRequestBodyException(requestBody, exception.GenericMessage, exception.SpecificMessage, exception.SourcePointer,
109+
exception.StatusCode, exception);
110+
}
111+
}
114112
}
115113
}

src/JsonApiDotNetCore/Serialization/RequestAdapters/ResourceObjectAdapter.cs

+13-12
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ private void ConvertAttribute(IIdentifiable resource, string attributeName, obje
6666

6767
AssertIsKnownAttribute(attr, attributeName, resourceContext, state);
6868
AssertNoInvalidAttribute(attributeValue, state);
69-
AssertNoBlockedCreate(attr, state);
70-
AssertNoBlockedChange(attr, state);
71-
AssertNotReadOnly(attr, state);
69+
AssertNoBlockedCreate(attr, resourceContext, state);
70+
AssertNoBlockedChange(attr, resourceContext, state);
71+
AssertNotReadOnly(attr, resourceContext, state);
7272

7373
attr!.SetValue(resource, attributeValue);
7474
state.WritableTargetedFields.Attributes.Add(attr);
@@ -90,7 +90,7 @@ private static void AssertNoInvalidAttribute(object attributeValue, RequestAdapt
9090
{
9191
if (info == JsonInvalidAttributeInfo.Id)
9292
{
93-
throw new DeserializationException(state.Position, null, "Resource ID is read-only.");
93+
throw new ModelConversionException(state.Position, "Resource ID is read-only.", null);
9494
}
9595

9696
string typeName = info.AttributeType.GetFriendlyTypeName();
@@ -100,29 +100,30 @@ private static void AssertNoInvalidAttribute(object attributeValue, RequestAdapt
100100
}
101101
}
102102

103-
private static void AssertNoBlockedCreate(AttrAttribute attr, RequestAdapterState state)
103+
private static void AssertNoBlockedCreate(AttrAttribute attr, ResourceContext resourceContext, RequestAdapterState state)
104104
{
105105
if (state.Request.WriteOperation == WriteOperationKind.CreateResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowCreate))
106106
{
107-
throw new DeserializationException(state.Position, "Setting the initial value of the requested attribute is not allowed.",
108-
$"Setting the initial value of '{attr.PublicName}' is not allowed.");
107+
throw new ModelConversionException(state.Position, "Attribute value cannot be assigned when creating resource.",
108+
$"The attribute '{attr.PublicName}' on resource type '{resourceContext.PublicName}' cannot be assigned to.");
109109
}
110110
}
111111

112-
private static void AssertNoBlockedChange(AttrAttribute attr, RequestAdapterState state)
112+
private static void AssertNoBlockedChange(AttrAttribute attr, ResourceContext resourceContext, RequestAdapterState state)
113113
{
114114
if (state.Request.WriteOperation == WriteOperationKind.UpdateResource && !attr.Capabilities.HasFlag(AttrCapabilities.AllowChange))
115115
{
116-
throw new DeserializationException(state.Position, "Changing the value of the requested attribute is not allowed.",
117-
$"Changing the value of '{attr.PublicName}' is not allowed.");
116+
throw new ModelConversionException(state.Position, "Attribute value cannot be assigned when updating resource.",
117+
$"The attribute '{attr.PublicName}' on resource type '{resourceContext.PublicName}' cannot be assigned to.");
118118
}
119119
}
120120

121-
private static void AssertNotReadOnly(AttrAttribute attr, RequestAdapterState state)
121+
private static void AssertNotReadOnly(AttrAttribute attr, ResourceContext resourceContext, RequestAdapterState state)
122122
{
123123
if (attr.Property.SetMethod == null)
124124
{
125-
throw new DeserializationException(state.Position, "Attribute is read-only.", $"Attribute '{attr.PublicName}' is read-only.");
125+
throw new ModelConversionException(state.Position, "Attribute is read-only.",
126+
$"Attribute '{attr.PublicName}' on resource type '{resourceContext.PublicName}' is read-only.");
126127
}
127128
}
128129

test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -754,8 +754,8 @@ public async Task Cannot_create_resource_attribute_with_blocked_capability()
754754

755755
ErrorObject error = responseDocument.Errors[0];
756756
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
757-
error.Title.Should().Be("Failed to deserialize request body: Setting the initial value of the requested attribute is not allowed.");
758-
error.Detail.Should().Be("Setting the initial value of 'createdAt' is not allowed.");
757+
error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when creating resource.");
758+
error.Detail.Should().Be("The attribute 'createdAt' on resource type 'lyrics' cannot be assigned to.");
759759
error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/createdAt");
760760
}
761761

@@ -798,7 +798,7 @@ public async Task Cannot_create_resource_with_readonly_attribute()
798798
ErrorObject error = responseDocument.Errors[0];
799799
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
800800
error.Title.Should().Be("Failed to deserialize request body: Attribute is read-only.");
801-
error.Detail.Should().Be("Attribute 'isArchived' is read-only.");
801+
error.Detail.Should().Be("Attribute 'isArchived' on resource type 'playlists' is read-only.");
802802
error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/isArchived");
803803
}
804804

test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1507,8 +1507,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
15071507

15081508
ErrorObject error = responseDocument.Errors[0];
15091509
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
1510-
error.Title.Should().Be("Failed to deserialize request body: Changing the value of the requested attribute is not allowed.");
1511-
error.Detail.Should().Be("Changing the value of 'createdAt' is not allowed.");
1510+
error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when updating resource.");
1511+
error.Detail.Should().Be("The attribute 'createdAt' on resource type 'lyrics' cannot be assigned to.");
15121512
error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/createdAt");
15131513
}
15141514

@@ -1557,7 +1557,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
15571557
ErrorObject error = responseDocument.Errors[0];
15581558
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
15591559
error.Title.Should().Be("Failed to deserialize request body: Attribute is read-only.");
1560-
error.Detail.Should().Be("Attribute 'isArchived' is read-only.");
1560+
error.Detail.Should().Be("Attribute 'isArchived' on resource type 'playlists' is read-only.");
15611561
error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/isArchived");
15621562
}
15631563

@@ -1605,8 +1605,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
16051605

16061606
ErrorObject error = responseDocument.Errors[0];
16071607
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
1608-
error.Title.Should().Be("Failed to deserialize request body.");
1609-
error.Detail.Should().Be("Resource ID is read-only.");
1608+
error.Title.Should().Be("Failed to deserialize request body: Resource ID is read-only.");
1609+
error.Detail.Should().BeNull();
16101610
error.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/id");
16111611
}
16121612

test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -730,8 +730,8 @@ public async Task Cannot_create_resource_attribute_with_blocked_capability()
730730

731731
ErrorObject error = responseDocument.Errors[0];
732732
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
733-
error.Title.Should().Be("Failed to deserialize request body: Setting the initial value of the requested attribute is not allowed.");
734-
error.Detail.Should().Be("Setting the initial value of 'isImportant' is not allowed.");
733+
error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when creating resource.");
734+
error.Detail.Should().Be("The attribute 'isImportant' on resource type 'workItems' cannot be assigned to.");
735735
error.Source.Pointer.Should().Be("/data/attributes/isImportant");
736736

737737
responseDocument.Meta["requestBody"].ToString().Should().NotBeNullOrEmpty();
@@ -766,7 +766,7 @@ public async Task Cannot_create_resource_with_readonly_attribute()
766766
ErrorObject error = responseDocument.Errors[0];
767767
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
768768
error.Title.Should().Be("Failed to deserialize request body: Attribute is read-only.");
769-
error.Detail.Should().Be("Attribute 'isDeprecated' is read-only.");
769+
error.Detail.Should().Be("Attribute 'isDeprecated' on resource type 'workItemGroups' is read-only.");
770770
error.Source.Pointer.Should().Be("/data/attributes/isDeprecated");
771771

772772
responseDocument.Meta["requestBody"].ToString().Should().NotBeNullOrEmpty();

test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1076,8 +1076,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
10761076

10771077
ErrorObject error = responseDocument.Errors[0];
10781078
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
1079-
error.Title.Should().Be("Failed to deserialize request body: Changing the value of the requested attribute is not allowed.");
1080-
error.Detail.Should().Be("Changing the value of 'isImportant' is not allowed.");
1079+
error.Title.Should().Be("Failed to deserialize request body: Attribute value cannot be assigned when updating resource.");
1080+
error.Detail.Should().Be("The attribute 'isImportant' on resource type 'workItems' cannot be assigned to.");
10811081
error.Source.Pointer.Should().Be("/data/attributes/isImportant");
10821082

10831083
responseDocument.Meta["requestBody"].ToString().Should().NotBeNullOrEmpty();
@@ -1121,7 +1121,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
11211121
ErrorObject error = responseDocument.Errors[0];
11221122
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
11231123
error.Title.Should().Be("Failed to deserialize request body: Attribute is read-only.");
1124-
error.Detail.Should().Be("Attribute 'isDeprecated' is read-only.");
1124+
error.Detail.Should().Be("Attribute 'isDeprecated' on resource type 'workItemGroups' is read-only.");
11251125
error.Source.Pointer.Should().Be("/data/attributes/isDeprecated");
11261126

11271127
responseDocument.Meta["requestBody"].ToString().Should().NotBeNullOrEmpty();
@@ -1197,8 +1197,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
11971197

11981198
ErrorObject error = responseDocument.Errors[0];
11991199
error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
1200-
error.Title.Should().Be("Failed to deserialize request body.");
1201-
error.Detail.Should().Be("Resource ID is read-only.");
1200+
error.Title.Should().Be("Failed to deserialize request body: Resource ID is read-only.");
1201+
error.Detail.Should().BeNull();
12021202
error.Source.Pointer.Should().Be("/data/attributes/id");
12031203

12041204
responseDocument.Meta["requestBody"].ToString().Should().NotBeNullOrEmpty();

test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorTests.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
212212
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
213213
error.Title.Should().Be("Failed to clear a required relationship.");
214214

215-
error.Detail.Should().Be($"The relationship 'customer' of resource type 'orders' with ID '{existingOrder.StringId}' " +
215+
error.Detail.Should().Be($"The relationship 'customer' on resource type 'orders' with ID '{existingOrder.StringId}' " +
216216
"cannot be cleared because it is a required relationship.");
217217
}
218218

@@ -249,7 +249,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
249249
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
250250
error.Title.Should().Be("Failed to clear a required relationship.");
251251

252-
error.Detail.Should().Be($"The relationship 'customer' of resource type 'orders' with ID '{existingOrder.StringId}' " +
252+
error.Detail.Should().Be($"The relationship 'customer' on resource type 'orders' with ID '{existingOrder.StringId}' " +
253253
"cannot be cleared because it is a required relationship.");
254254
}
255255

@@ -297,7 +297,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
297297
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
298298
error.Title.Should().Be("Failed to clear a required relationship.");
299299

300-
error.Detail.Should().Be($"The relationship 'orders' of resource type 'customers' with ID '{existingOrder.StringId}' " +
300+
error.Detail.Should().Be($"The relationship 'orders' on resource type 'customers' with ID '{existingOrder.StringId}' " +
301301
"cannot be cleared because it is a required relationship.");
302302
}
303303

@@ -334,7 +334,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
334334
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
335335
error.Title.Should().Be("Failed to clear a required relationship.");
336336

337-
error.Detail.Should().Be($"The relationship 'orders' of resource type 'customers' with ID '{existingOrder.StringId}' " +
337+
error.Detail.Should().Be($"The relationship 'orders' on resource type 'customers' with ID '{existingOrder.StringId}' " +
338338
"cannot be cleared because it is a required relationship.");
339339
}
340340

@@ -378,7 +378,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
378378
error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
379379
error.Title.Should().Be("Failed to clear a required relationship.");
380380

381-
error.Detail.Should().Be($"The relationship 'orders' of resource type 'customers' with ID '{existingOrder.StringId}' " +
381+
error.Detail.Should().Be($"The relationship 'orders' on resource type 'customers' with ID '{existingOrder.StringId}' " +
382382
"cannot be cleared because it is a required relationship.");
383383
}
384384

0 commit comments

Comments
 (0)