using HyperBooru.ApiModels; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; namespace HyperBooru; // Middleware class to intercept API controller exceptions and // return said exceptions to API clients as serialized JSON objects public sealed class ExceptionMiddleware { private RequestDelegate next; public ExceptionMiddleware(RequestDelegate next) => this.next = next; public async Task Invoke(HttpContext context) { try { await next(context); } catch(HBException e) { context.Response.ContentType = "application/json"; context.Response.StatusCode = e.GetType().GetCustomAttribute()?.StatusCode ?? StatusCodes.Status500InternalServerError; context.Response.Clear(); await context.Response.WriteAsJsonAsync(e); } catch(Exception) { context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json"; context.Response.Clear(); await context.Response.WriteAsJsonAsync(new ServerException()); } } } // This class is needed as the JSON serializer often fails to serialize // members of the native 'Exception' class public sealed class ExceptionJsonResolver : DefaultJsonTypeInfoResolver { public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) { var info = base.GetTypeInfo(type, options); if(!typeof(Exception).IsAssignableFrom(type)) return info; string[] excludedProps = [ "data", "hResult", "helpLink", "innerException", "source", "stackTrace", "targetSite" ]; foreach(var p in info.Properties.Where(p => excludedProps.Contains(p.Name))) p.ShouldSerialize = (_, _) => false; return info; } }