From 5ac55a09141b1388172b91b9aac1c6a74fe3faa9 Mon Sep 17 00:00:00 2001 From: Jason Finch Date: Thu, 17 Aug 2023 11:20:41 +1000 Subject: [PATCH] feat(ProblemDetails): Allow filtering properties (Detail, ExceptionDetails) for response ProblemDetails response. Resolves #194 --- samples/ProblemDetails.Sample/Program.cs | 5 +++++ .../DeveloperProblemDetailsExtensions.cs | 15 ++++++++++++--- .../IncludeProblemDetailProps.cs | 19 +++++++++++++++++++ src/ProblemDetails/ProblemDetailsFactory.cs | 8 ++++++-- src/ProblemDetails/ProblemDetailsOptions.cs | 8 ++++++++ 5 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/ProblemDetails/IncludeProblemDetailProps.cs diff --git a/samples/ProblemDetails.Sample/Program.cs b/samples/ProblemDetails.Sample/Program.cs index 8b7df8c..0e8deba 100644 --- a/samples/ProblemDetails.Sample/Program.cs +++ b/samples/ProblemDetails.Sample/Program.cs @@ -66,6 +66,11 @@ private void ConfigureProblemDetails(ProblemDetailsOptions options) // Only include exception details in a development environment. There's really no need // to set this as it's the default behavior. It's just included here for completeness :) options.IncludeExceptionDetails = (ctx, ex) => Environment.IsDevelopment(); + options.IncludePropsFilter = (context, exception) => new IncludeProblemDetailProps() + { + Detail = true, + ExceptionDetails = true + }; // Custom mapping function for FluentValidation's ValidationException. options.MapFluentValidationException(); diff --git a/src/ProblemDetails/DeveloperProblemDetailsExtensions.cs b/src/ProblemDetails/DeveloperProblemDetailsExtensions.cs index a784c0a..5b450b6 100644 --- a/src/ProblemDetails/DeveloperProblemDetailsExtensions.cs +++ b/src/ProblemDetails/DeveloperProblemDetailsExtensions.cs @@ -11,13 +11,22 @@ namespace Hellang.Middleware.ProblemDetails { internal static class DeveloperProblemDetailsExtensions { - public static MvcProblemDetails WithExceptionDetails(this MvcProblemDetails problem, string propertyName, Exception error, IEnumerable details) + public static MvcProblemDetails WithExceptionDetails(this MvcProblemDetails problem, string propertyName, Exception error, IEnumerable details, IncludeProblemDetailProps includeProps) { problem.Title ??= TypeNameHelper.GetTypeDisplayName(error.GetType()); - problem.Extensions[propertyName] = GetErrors(details).ToList(); + + if (includeProps.ExceptionDetails) + { + problem.Extensions[propertyName] = GetErrors(details).ToList(); + } + problem.Status ??= StatusCodes.Status500InternalServerError; problem.Instance ??= GetHelpLink(error); - problem.Detail ??= error.Message; + if (includeProps.Detail) + { + problem.Detail ??= error.Message; + } + return problem; } diff --git a/src/ProblemDetails/IncludeProblemDetailProps.cs b/src/ProblemDetails/IncludeProblemDetailProps.cs new file mode 100644 index 0000000..da38adf --- /dev/null +++ b/src/ProblemDetails/IncludeProblemDetailProps.cs @@ -0,0 +1,19 @@ +namespace Hellang.Middleware.ProblemDetails +{ + /// + /// Properties to include in ProblemDetails. + /// By default, all properties are false and will not be included. + /// + public class IncludeProblemDetailProps + { + /// + /// Include the Exception Details Stack + /// + public bool ExceptionDetails { get; set; } + + /// + /// Include the Detail summary property + /// + public bool Detail { get; set; } + } +} diff --git a/src/ProblemDetails/ProblemDetailsFactory.cs b/src/ProblemDetails/ProblemDetailsFactory.cs index 71df0a0..8acb7bc 100644 --- a/src/ProblemDetails/ProblemDetailsFactory.cs +++ b/src/ProblemDetails/ProblemDetailsFactory.cs @@ -61,12 +61,16 @@ public ProblemDetailsFactory( } } - if (Options.IncludeExceptionDetails(context, error)) + var includePropsFilter = Options.IncludePropsFilter?.Invoke(context, error); + + if (Options.IncludeExceptionDetails(context, error) || includePropsFilter is not null) { try { + includePropsFilter ??= new IncludeProblemDetailProps{ExceptionDetails = true, Detail = true}; // Instead of returning a new object, we mutate the existing problem so users keep all details. - return result.WithExceptionDetails(Options.ExceptionDetailsPropertyName, error, DetailsProvider.GetDetails(error)); + return result.WithExceptionDetails(Options.ExceptionDetailsPropertyName, error, + DetailsProvider.GetDetails(error), includePropsFilter); } catch (Exception e) { diff --git a/src/ProblemDetails/ProblemDetailsOptions.cs b/src/ProblemDetails/ProblemDetailsOptions.cs index d7ef1e0..7d96929 100644 --- a/src/ProblemDetails/ProblemDetailsOptions.cs +++ b/src/ProblemDetails/ProblemDetailsOptions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.FileProviders; @@ -64,6 +65,13 @@ public ProblemDetailsOptions() /// public Func IncludeExceptionDetails { get; set; } = null!; + /// + /// Configure which properties to include in the problem details response. + /// By default all fields are excluded. + /// This can be used as an alternative to when specific fields are needed. + /// + public Func? IncludePropsFilter { get; set; } = null!; + /// /// The property name to use for traceId /// This defaults to (traceId).