Skip to content

Feat/improved errors #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ JsonApiDotnetCore provides a framework for building [json:api](http://jsonapi.or
- [Sorting](#sorting)
- [Meta](#meta)
- [Client Generated Ids](#client-generated-ids)
- [Custom Errors](#custom-errors)
- [Tests](#tests)

## Comprehensive Demo
Expand Down Expand Up @@ -364,6 +365,38 @@ services.AddJsonApi<AppDbContext>(opt =>
});
```

### Custom Errors

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.

```chsarp
// custom error definition
public class CustomError : Error {
public CustomError(string status, string title, string detail, string myProp)
: base(status, title, detail)
{
MyCustomProperty = myProp;
}
public string MyCustomProperty { get; set; }
}

// throwing a custom error
public void MyMethod() {
var error = new CustomError("507", "title", "detail", "custom");
throw new JsonApiException(error);
}

// returning from controller
[HttpPost]
public override async Task<IActionResult> PostAsync([FromBody] MyEntity entity)
{
if(_db.IsFull)
return new ObjectResult(new CustomError("507", "Database is full.", "Theres no more room.", "Sorry."));

// ...
}
```

## Tests

I am using DotNetCoreDocs to generate sample requests and documentation.
Expand Down
39 changes: 2 additions & 37 deletions src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,20 @@
using System.Text;
using System.Threading.Tasks;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Serialization;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace JsonApiDotNetCore.Formatters
{
public class JsonApiWriter : IJsonApiWriter
{
private readonly ILogger<JsonApiWriter> _logger;
private readonly IJsonApiContext _jsonApiContext;
private readonly IJsonApiSerializer _serializer;

public JsonApiWriter(IJsonApiContext jsonApiContext,
IJsonApiSerializer serializer,
public JsonApiWriter(IJsonApiSerializer serializer,
ILoggerFactory loggerFactory)
{
_jsonApiContext = jsonApiContext;
_serializer = serializer;
_logger = loggerFactory.CreateLogger<JsonApiWriter>();
}
Expand Down Expand Up @@ -58,36 +52,7 @@ public async Task WriteAsync(OutputFormatterWriteContext context)

private string GetResponseBody(object responseObject)
{
if (responseObject == null)
return GetNullDataResponse();

if (responseObject.GetType() == typeof(Error) || _jsonApiContext.RequestEntity == null)
return GetErrorJson(responseObject, _logger);

return _serializer.Serialize(responseObject);
}

private string GetNullDataResponse()
{
return JsonConvert.SerializeObject(new Document
{
Data = null
});
}

private string GetErrorJson(object responseObject, ILogger logger)
{
if (responseObject.GetType() == typeof(Error))
{
var errors = new ErrorCollection();
errors.Add((Error)responseObject);
return errors.GetJson();
}
else
{
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
return JsonConvert.SerializeObject(responseObject);
}
}
}
}
}
3 changes: 3 additions & 0 deletions src/JsonApiDotNetCore/Internal/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ public Error(string status, string title, string detail)

[JsonProperty("status")]
public string Status { get; set; }

[JsonIgnore]
public int StatusCode { get { return int.Parse(Status); } }
}
}
4 changes: 3 additions & 1 deletion src/JsonApiDotNetCore/Internal/ErrorCollection.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace JsonApiDotNetCore.Internal
{
Expand All @@ -20,7 +21,8 @@ public void Add(Error error)
public string GetJson()
{
return JsonConvert.SerializeObject(this, new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Ignore
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
}
Expand Down
41 changes: 31 additions & 10 deletions src/JsonApiDotNetCore/Internal/JsonApiException.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,52 @@
using System;
using System.Linq;

namespace JsonApiDotNetCore.Internal
{
public class JsonApiException : Exception
{
private string _statusCode;
private string _detail;
private string _message;
private ErrorCollection _errors = new ErrorCollection();

public JsonApiException(ErrorCollection errorCollection)
{
_errors = errorCollection;
}

public JsonApiException(Error error)
: base(error.Title)
{
_errors.Add(error);
}

public JsonApiException(string statusCode, string message)
: base(message)
{
_statusCode = statusCode;
_message = message;
_errors.Add(new Error(statusCode, message, null));
}

public JsonApiException(string statusCode, string message, string detail)
: base(message)
{
_statusCode = statusCode;
_message = message;
_detail = detail;
_errors.Add(new Error(statusCode, message, detail));
}

public Error GetError()
public ErrorCollection GetError()
{
return new Error(_statusCode, _message, _detail);
return _errors;
}

public int GetStatusCode()
{
if(_errors.Errors.Count == 1)
return _errors.Errors[0].StatusCode;

if(_errors.Errors.FirstOrDefault(e => e.StatusCode >= 500) != null)
return 500;

if(_errors.Errors.FirstOrDefault(e => e.StatusCode >= 400) != null)
return 400;

return 500;
}
}
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Middleware/JsonApiExceptionFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void OnException(ExceptionContext context)

var error = jsonApiException.GetError();
var result = new ObjectResult(error);
result.StatusCode = Convert.ToInt16(error.Status);
result.StatusCode = jsonApiException.GetStatusCode();
context.Result = result;
}
}
Expand Down
55 changes: 50 additions & 5 deletions src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,80 @@
using System.Collections.Generic;
using JsonApiDotNetCore.Builders;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace JsonApiDotNetCore.Serialization
{
public class JsonApiSerializer : IJsonApiSerializer
{
private readonly IDocumentBuilder _documentBuilder;
private readonly ILogger<JsonApiSerializer> _logger;
private readonly IJsonApiContext _jsonApiContext;

public JsonApiSerializer(IDocumentBuilder documentBuilder)
public JsonApiSerializer(
IJsonApiContext jsonApiContext,
IDocumentBuilder documentBuilder)
{
_jsonApiContext = jsonApiContext;
_documentBuilder = documentBuilder;
}

public JsonApiSerializer(
IJsonApiContext jsonApiContext,
IDocumentBuilder documentBuilder,
ILoggerFactory loggerFactory)
{
_jsonApiContext = jsonApiContext;
_documentBuilder = documentBuilder;
_logger = loggerFactory?.CreateLogger<JsonApiSerializer>();
}

public string Serialize(object entity)
{
if (entity == null)
return GetNullDataResponse();

if (entity.GetType() == typeof(ErrorCollection) || _jsonApiContext.RequestEntity == null)
return GetErrorJson(entity, _logger);

if (entity is IEnumerable<IIdentifiable>)
return _serializeDocuments(entity);
return SerializeDocuments(entity);

return SerializeDocument(entity);
}

private string GetNullDataResponse()
{
return JsonConvert.SerializeObject(new Document
{
Data = null
});
}

return _serializeDocument(entity);
private string GetErrorJson(object responseObject, ILogger logger)
{
if (responseObject is ErrorCollection errorCollection)
{
return errorCollection.GetJson();
}
else
{
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
return JsonConvert.SerializeObject(responseObject);
}
}

private string _serializeDocuments(object entity)
private string SerializeDocuments(object entity)
{
var entities = entity as IEnumerable<IIdentifiable>;
var documents = _documentBuilder.Build(entities);
return _serialize(documents);
}

private string _serializeDocument(object entity)
private string SerializeDocument(object entity)
{
var identifiableEntity = entity as IIdentifiable;
var document = _documentBuilder.Build(identifiableEntity);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using DotNetCoreDocs;
using JsonApiDotNetCoreExample;
using DotNetCoreDocs.Writers;
using Newtonsoft.Json;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Serialization;
using Xunit;
using System.Diagnostics;

namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility
{
public class CustomErrorTests
{
[Fact]
public void Can_Return_Custom_Error_Types()
{
// while(!Debugger.IsAttached) { bool stop = false; }

// arrange
var error = new CustomError("507", "title", "detail", "custom");
var errorCollection = new ErrorCollection();
errorCollection.Add(error);

var expectedJson = JsonConvert.SerializeObject(new {
errors = new dynamic[] {
new {
myCustomProperty = "custom",
title = "title",
detail = "detail",
status = "507"
}
}
});

// act
var result = new JsonApiSerializer(null, null, null)
.Serialize(errorCollection);

// assert
Assert.Equal(expectedJson, result);

}

class CustomError : Error {
public CustomError(string status, string title, string detail, string myProp)
: base(status, title, detail)
{
MyCustomProperty = myProp;
}
public string MyCustomProperty { get; set; }
}
}
}