Skip to content

Instantly share code, notes, and snippets.

@wardsa013
Created January 14, 2017 23:58
Show Gist options
  • Save wardsa013/cea062f8a65e01018e2becaa91b7a801 to your computer and use it in GitHub Desktop.
Save wardsa013/cea062f8a65e01018e2becaa91b7a801 to your computer and use it in GitHub Desktop.
Error Support for Hypermedia.JsonApi.WebApi
public static class ErrorKeys
{
public const string Status = "Status";
public const string Title = "Title";
public const string Detail = "Detail";
}
// A couple helpful extension methods to create an HttpResponse
public static HttpResponseMessage CreateJsonApiErrorResponse(this HttpRequestMessage request, HttpError error)
{
HttpStatusCode code = HttpStatusCode.InternalServerError;
TryParseStatusCode(error[ErrorKeys.Status].ToString(), out code);
return request.CreateErrorResponse(code, error);
}
public static HttpResponseMessage CreateJsonApiErrorResponse(this HttpRequestMessage request, Exception ex)
{
var error = ex.ToHttpError();
return request.CreateJsonApiErrorResponse(error);
}
// Parser for HttpStatusCode from a string status code
public static bool TryParseStatusCode(string statusCode, out HttpStatusCode result)
{
result = HttpStatusCode.InternalServerError;
int parsedStatusCode = -1;
if (!int.TryParse(statusCode, out parsedStatusCode)) return false;
result = (HttpStatusCode)parsedStatusCode;
return true;
}
// An example helper to generate properly formatted HttpErrors
public static HttpError ToHttpError(this Exception exception)
{
var result = new HttpError(exception, true);
if (typeof(AuthorizationException).IsAssignableFrom(exception.GetType()))
{
result[ErrorKeys.Status] = "401";
result[ErrorKeys.Title] = "Authentication Failed";
result[ErrorKeys.Detail] = exception.Message;
}
else if (typeof(Services.TimeoutException).IsAssignableFrom(exception.GetType()))
{
result[ErrorKeys.Status] = "401";
result[ErrorKeys.Title] = "Session Timedout";
result[ErrorKeys.Detail] = "Session timedout please login.";
}
else
{
result[ErrorKeys.Status] = "500";
result[ErrorKeys.Title] = nameof(exception);
result[ErrorKeys.Detail] = exception.Message;
}
return result;
}
public class JsonApiMediaTypeFormatterWithErrorSupport : JsonApiMediaTypeFormatter
{
// No access to JsonApiMediaTypeFormatter.fieldNamingStrategy
// so created it here
private readonly IFieldNamingStrategy fieldNamingStrategy;
public JsonApiMediaTypeFormatterWithErrorSupport(IContractResolver contractResolver)
: this(contractResolver, new DasherizedFieldNamingStrategy()) { }
public JsonApiMediaTypeFormatterWithErrorSupport(IContractResolver contractResolver, IFieldNamingStrategy fieldNamingStrategy)
: base(contractResolver)
{
this.fieldNamingStrategy = fieldNamingStrategy;
}
protected override JsonValue SerializeValue(Type type, object value)
{
var errorSerializer = new JsonApiSerializerWithErrorSupport(fieldNamingStrategy);
if (typeof(HttpError).IsAssignableFrom(type))
{
return errorSerializer.SerializeError((HttpError)value);
}
return base.SerializeValue(type, value);
}
}
public class JsonApiSerializerWithErrorSupport
{
private readonly IFieldNamingStrategy fieldNamingStrategy;
public JsonApiSerializerWithErrorSupport(IFieldNamingStrategy fieldNamingStrategy)
{
this.fieldNamingStrategy = fieldNamingStrategy;
}
public JsonValue SerializeError(HttpError value)
{
if(value == null)
{
throw new ArgumentNullException(nameof(value));
}
var serializer = new Serializer(fieldNamingStrategy);
var members = new List<JsonMember>
{
new JsonMember("errors", new JsonArray(serializer.Serialize(new [] { value }).ToList()))
};
return new JsonObject(members);
}
class Serializer
{
private readonly IJsonSerializer jsonSerializer;
private HashSet<JsonObject> visited = new HashSet<JsonObject>(JsonApiEntityKeyEqualityComparer.Instance);
internal Serializer(IFieldNamingStrategy fieldNamingStrategy)
{
jsonSerializer = new JsonSerializer(new JsonConverterFactory(), fieldNamingStrategy);
}
internal IEnumerable<JsonObject> Serialize(IEnumerable<HttpError> errors)
{
foreach(var error in errors)
{
var jsonObject = Serialize(error);
yield return jsonObject;
}
}
internal JsonObject Serialize(HttpError error)
{
var members = new List<JsonMember>();
members.Add(new JsonMember("status", SerializeValue(error[ErrorKeys.Status])));
members.Add(new JsonMember("title", SerializeValue(error[ErrorKeys.Title])));
members.Add(new JsonMember("detail", SerializeValue(error[ErrorKeys.Detail])));
return new JsonObject(members);
}
internal JsonValue SerializeValue(object value)
{
return jsonSerializer.SerializeValue(value);
}
bool HasVisited(JsonObject jsonObject)
{
return visited.Contains(jsonObject);
}
}
/// <summary>
/// Direct copy of Hypermedia.JsonApi.JsonApiEntityKeyEqualityComparer because it is internal.
/// </summary>
class JsonApiEntityKeyEqualityComparer : IEqualityComparer<JsonObject>
{
internal static readonly IEqualityComparer<JsonObject> Instance = new JsonApiEntityKeyEqualityComparer();
/// <summary>
/// Constructor.
/// </summary>
JsonApiEntityKeyEqualityComparer() { }
/// <summary>
/// Determines whether the specified objects are equal.
/// </summary>
/// <param name="x">The first object of type <paramref name="T"/> to compare.</param>
/// <param name="y">The second object of type <paramref name="T"/> to compare.</param>
/// <returns>true if the specified objects are equal; otherwise, false.</returns>
public bool Equals(JsonObject x, JsonObject y)
{
return String.Equals(CreateKey(x), CreateKey(y));
}
/// <summary>
/// Returns a hash code for the specified object.
/// </summary>
/// <param name="obj">The <see cref="T:System.Object"/> for which a hash code is to be returned.</param>
/// <exception cref="T:System.ArgumentNullException">The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.</exception>
/// <returns>A hash code for the specified object.</returns>
public int GetHashCode(JsonObject obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
return CreateKey(obj).GetHashCode();
}
/// <summary>
/// Creates a string key representation that is used for comparissons.
/// </summary>
/// <param name="jsonObject">The JSON object to create the key for.</param>
/// <returns>The string key that represents the given JSON object.</returns>
static string CreateKey(JsonObject jsonObject)
{
if (jsonObject["id"] != null)
{
return $"{jsonObject["type"].Stringify()}:{jsonObject["id"].Stringify()}".ToLower();
}
return jsonObject["type"].Stringify();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment