Skip to content

Commit b8443b8

Browse files
authored
Merge pull request #68 from Research-Institute/feat/improved-errors
Feat/improved errors
2 parents e2c0cc8 + 7fd3e48 commit b8443b8

File tree

8 files changed

+176
-54
lines changed

8 files changed

+176
-54
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ JsonApiDotnetCore provides a framework for building [json:api](http://jsonapi.or
2727
- [Sorting](#sorting)
2828
- [Meta](#meta)
2929
- [Client Generated Ids](#client-generated-ids)
30+
- [Custom Errors](#custom-errors)
3031
- [Tests](#tests)
3132

3233
## Comprehensive Demo
@@ -364,6 +365,38 @@ services.AddJsonApi<AppDbContext>(opt =>
364365
});
365366
```
366367

368+
### Custom Errors
369+
370+
By default, errors will only contain the properties defined by the internal [Error](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Internal/Error.cs) class. However, you can create your own by inheriting from `Error` and either throwing it in a `JsonApiException` or returning the error from your controller.
371+
372+
```chsarp
373+
// custom error definition
374+
public class CustomError : Error {
375+
public CustomError(string status, string title, string detail, string myProp)
376+
: base(status, title, detail)
377+
{
378+
MyCustomProperty = myProp;
379+
}
380+
public string MyCustomProperty { get; set; }
381+
}
382+
383+
// throwing a custom error
384+
public void MyMethod() {
385+
var error = new CustomError("507", "title", "detail", "custom");
386+
throw new JsonApiException(error);
387+
}
388+
389+
// returning from controller
390+
[HttpPost]
391+
public override async Task<IActionResult> PostAsync([FromBody] MyEntity entity)
392+
{
393+
if(_db.IsFull)
394+
return new ObjectResult(new CustomError("507", "Database is full.", "Theres no more room.", "Sorry."));
395+
396+
// ...
397+
}
398+
```
399+
367400
## Tests
368401

369402
I am using DotNetCoreDocs to generate sample requests and documentation.

src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,20 @@
22
using System.Text;
33
using System.Threading.Tasks;
44
using JsonApiDotNetCore.Internal;
5-
using JsonApiDotNetCore.Models;
65
using JsonApiDotNetCore.Serialization;
7-
using JsonApiDotNetCore.Services;
86
using Microsoft.AspNetCore.Mvc.Formatters;
97
using Microsoft.Extensions.Logging;
10-
using Newtonsoft.Json;
118

129
namespace JsonApiDotNetCore.Formatters
1310
{
1411
public class JsonApiWriter : IJsonApiWriter
1512
{
1613
private readonly ILogger<JsonApiWriter> _logger;
17-
private readonly IJsonApiContext _jsonApiContext;
1814
private readonly IJsonApiSerializer _serializer;
1915

20-
public JsonApiWriter(IJsonApiContext jsonApiContext,
21-
IJsonApiSerializer serializer,
16+
public JsonApiWriter(IJsonApiSerializer serializer,
2217
ILoggerFactory loggerFactory)
2318
{
24-
_jsonApiContext = jsonApiContext;
2519
_serializer = serializer;
2620
_logger = loggerFactory.CreateLogger<JsonApiWriter>();
2721
}
@@ -58,36 +52,7 @@ public async Task WriteAsync(OutputFormatterWriteContext context)
5852

5953
private string GetResponseBody(object responseObject)
6054
{
61-
if (responseObject == null)
62-
return GetNullDataResponse();
63-
64-
if (responseObject.GetType() == typeof(Error) || _jsonApiContext.RequestEntity == null)
65-
return GetErrorJson(responseObject, _logger);
66-
6755
return _serializer.Serialize(responseObject);
68-
}
69-
70-
private string GetNullDataResponse()
71-
{
72-
return JsonConvert.SerializeObject(new Document
73-
{
74-
Data = null
75-
});
76-
}
77-
78-
private string GetErrorJson(object responseObject, ILogger logger)
79-
{
80-
if (responseObject.GetType() == typeof(Error))
81-
{
82-
var errors = new ErrorCollection();
83-
errors.Add((Error)responseObject);
84-
return errors.GetJson();
85-
}
86-
else
87-
{
88-
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
89-
return JsonConvert.SerializeObject(responseObject);
90-
}
91-
}
56+
}
9257
}
9358
}

src/JsonApiDotNetCore/Internal/Error.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,8 @@ public Error(string status, string title, string detail)
2828

2929
[JsonProperty("status")]
3030
public string Status { get; set; }
31+
32+
[JsonIgnore]
33+
public int StatusCode { get { return int.Parse(Status); } }
3134
}
3235
}

src/JsonApiDotNetCore/Internal/ErrorCollection.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using Newtonsoft.Json;
3+
using Newtonsoft.Json.Serialization;
34

45
namespace JsonApiDotNetCore.Internal
56
{
@@ -20,7 +21,8 @@ public void Add(Error error)
2021
public string GetJson()
2122
{
2223
return JsonConvert.SerializeObject(this, new JsonSerializerSettings {
23-
NullValueHandling = NullValueHandling.Ignore
24+
NullValueHandling = NullValueHandling.Ignore,
25+
ContractResolver = new CamelCasePropertyNamesContractResolver()
2426
});
2527
}
2628
}
Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,52 @@
11
using System;
2+
using System.Linq;
23

34
namespace JsonApiDotNetCore.Internal
45
{
56
public class JsonApiException : Exception
67
{
7-
private string _statusCode;
8-
private string _detail;
9-
private string _message;
8+
private ErrorCollection _errors = new ErrorCollection();
9+
10+
public JsonApiException(ErrorCollection errorCollection)
11+
{
12+
_errors = errorCollection;
13+
}
14+
15+
public JsonApiException(Error error)
16+
: base(error.Title)
17+
{
18+
_errors.Add(error);
19+
}
1020

1121
public JsonApiException(string statusCode, string message)
1222
: base(message)
1323
{
14-
_statusCode = statusCode;
15-
_message = message;
24+
_errors.Add(new Error(statusCode, message, null));
1625
}
1726

1827
public JsonApiException(string statusCode, string message, string detail)
1928
: base(message)
2029
{
21-
_statusCode = statusCode;
22-
_message = message;
23-
_detail = detail;
30+
_errors.Add(new Error(statusCode, message, detail));
2431
}
2532

26-
public Error GetError()
33+
public ErrorCollection GetError()
2734
{
28-
return new Error(_statusCode, _message, _detail);
35+
return _errors;
36+
}
37+
38+
public int GetStatusCode()
39+
{
40+
if(_errors.Errors.Count == 1)
41+
return _errors.Errors[0].StatusCode;
42+
43+
if(_errors.Errors.FirstOrDefault(e => e.StatusCode >= 500) != null)
44+
return 500;
45+
46+
if(_errors.Errors.FirstOrDefault(e => e.StatusCode >= 400) != null)
47+
return 400;
48+
49+
return 500;
2950
}
3051
}
3152
}

src/JsonApiDotNetCore/Middleware/JsonApiExceptionFilter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public void OnException(ExceptionContext context)
2323

2424
var error = jsonApiException.GetError();
2525
var result = new ObjectResult(error);
26-
result.StatusCode = Convert.ToInt16(error.Status);
26+
result.StatusCode = jsonApiException.GetStatusCode();
2727
context.Result = result;
2828
}
2929
}

src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,80 @@
11
using System.Collections.Generic;
22
using JsonApiDotNetCore.Builders;
3+
using JsonApiDotNetCore.Internal;
34
using JsonApiDotNetCore.Models;
5+
using JsonApiDotNetCore.Services;
6+
using Microsoft.Extensions.Logging;
47
using Newtonsoft.Json;
58

69
namespace JsonApiDotNetCore.Serialization
710
{
811
public class JsonApiSerializer : IJsonApiSerializer
912
{
1013
private readonly IDocumentBuilder _documentBuilder;
14+
private readonly ILogger<JsonApiSerializer> _logger;
15+
private readonly IJsonApiContext _jsonApiContext;
1116

12-
public JsonApiSerializer(IDocumentBuilder documentBuilder)
17+
public JsonApiSerializer(
18+
IJsonApiContext jsonApiContext,
19+
IDocumentBuilder documentBuilder)
1320
{
21+
_jsonApiContext = jsonApiContext;
22+
_documentBuilder = documentBuilder;
23+
}
24+
25+
public JsonApiSerializer(
26+
IJsonApiContext jsonApiContext,
27+
IDocumentBuilder documentBuilder,
28+
ILoggerFactory loggerFactory)
29+
{
30+
_jsonApiContext = jsonApiContext;
1431
_documentBuilder = documentBuilder;
32+
_logger = loggerFactory?.CreateLogger<JsonApiSerializer>();
1533
}
1634

1735
public string Serialize(object entity)
1836
{
37+
if (entity == null)
38+
return GetNullDataResponse();
39+
40+
if (entity.GetType() == typeof(ErrorCollection) || _jsonApiContext.RequestEntity == null)
41+
return GetErrorJson(entity, _logger);
42+
1943
if (entity is IEnumerable<IIdentifiable>)
20-
return _serializeDocuments(entity);
44+
return SerializeDocuments(entity);
45+
46+
return SerializeDocument(entity);
47+
}
48+
49+
private string GetNullDataResponse()
50+
{
51+
return JsonConvert.SerializeObject(new Document
52+
{
53+
Data = null
54+
});
55+
}
2156

22-
return _serializeDocument(entity);
57+
private string GetErrorJson(object responseObject, ILogger logger)
58+
{
59+
if (responseObject is ErrorCollection errorCollection)
60+
{
61+
return errorCollection.GetJson();
62+
}
63+
else
64+
{
65+
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
66+
return JsonConvert.SerializeObject(responseObject);
67+
}
2368
}
2469

25-
private string _serializeDocuments(object entity)
70+
private string SerializeDocuments(object entity)
2671
{
2772
var entities = entity as IEnumerable<IIdentifiable>;
2873
var documents = _documentBuilder.Build(entities);
2974
return _serialize(documents);
3075
}
3176

32-
private string _serializeDocument(object entity)
77+
private string SerializeDocument(object entity)
3378
{
3479
var identifiableEntity = entity as IIdentifiable;
3580
var document = _documentBuilder.Build(identifiableEntity);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using DotNetCoreDocs;
2+
using JsonApiDotNetCoreExample;
3+
using DotNetCoreDocs.Writers;
4+
using Newtonsoft.Json;
5+
using JsonApiDotNetCore.Internal;
6+
using JsonApiDotNetCore.Serialization;
7+
using Xunit;
8+
using System.Diagnostics;
9+
10+
namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility
11+
{
12+
public class CustomErrorTests
13+
{
14+
[Fact]
15+
public void Can_Return_Custom_Error_Types()
16+
{
17+
// while(!Debugger.IsAttached) { bool stop = false; }
18+
19+
// arrange
20+
var error = new CustomError("507", "title", "detail", "custom");
21+
var errorCollection = new ErrorCollection();
22+
errorCollection.Add(error);
23+
24+
var expectedJson = JsonConvert.SerializeObject(new {
25+
errors = new dynamic[] {
26+
new {
27+
myCustomProperty = "custom",
28+
title = "title",
29+
detail = "detail",
30+
status = "507"
31+
}
32+
}
33+
});
34+
35+
// act
36+
var result = new JsonApiSerializer(null, null, null)
37+
.Serialize(errorCollection);
38+
39+
// assert
40+
Assert.Equal(expectedJson, result);
41+
42+
}
43+
44+
class CustomError : Error {
45+
public CustomError(string status, string title, string detail, string myProp)
46+
: base(status, title, detail)
47+
{
48+
MyCustomProperty = myProp;
49+
}
50+
public string MyCustomProperty { get; set; }
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)