public async void Should_Rename_TraceId_To_CorrelationId()
        {
            // given
            const string traceId = "123";
            var          problem = new MvcProblemDetails
            {
                Extensions =
                {
                    { "traceId", traceId }
                }
            };

            New.ProblemDetails.BadRequest.CopyStandardFieldsTo(problem);

            var response = await New.HttpResponseMessage.Of(problem);

            // when
            Func <Task> act = async() => await response.EnsureNotProblemDetailAsync();

            // then
            var expected = new MvcProblemDetails
            {
                Extensions =
                {
                    { "correlationId", traceId }
                }
            };

            problem.CopyStandardFieldsTo(expected);

            (await act.Should().ThrowAsync <ProblemDetailsException>())
            .Which.Details.Should().BeEquivalentTo(expected);
        }
Example #2
0
 /// <summary>
 ///     Copy the fields defined in the RFC-7807 spec from <paramref name="source" /> to <paramref name="target" />
 /// </summary>
 public static void CopyStandardFieldsTo(this MvcProblemDetails source, MvcProblemDetails target)
 {
     target.Type     = source.Type;
     target.Title    = source.Title;
     target.Instance = source.Instance;
     target.Status   = source.Status;
     target.Detail   = source.Detail;
 }
        public static MvcProblemDetails Create(int statusCode)
        {
            var details = new MvcProblemDetails();

            SetDetails(details, statusCode);

            return(details);
        }
        internal void CallBeforeWriteHook(HttpContext context, MvcProblemDetails details)
        {
            AddTraceId(context, details);
            OnBeforeWriteDetails?.Invoke(context, details);
#if NETCOREAPP3_1
            // Workaround for https://github.com/dotnet/aspnetcore/pull/17565.
            context.Response.StatusCode = details.Status ?? context.Response.StatusCode;
#endif
        }
 public static MvcProblemDetails WithExceptionDetails(this MvcProblemDetails problem, Exception error, IEnumerable <ExceptionDetails> details)
 {
     problem.Title ??= TypeNameHelper.GetTypeDisplayName(error.GetType());
     problem.Status ??= StatusCodes.Status500InternalServerError;
     problem.Extensions["errors"] = GetErrors(details).ToList();
     problem.Instance ??= GetHelpLink(error);
     problem.Detail ??= error.Message;
     return(problem);
 }
Example #6
0
        private static object DoRemoveExtensionValue(MvcProblemDetails problem, string key)
        {
            if (!problem.Extensions.ContainsKey(key))
            {
                return(null);
            }

            var value = problem.Extensions[key];

            problem.Extensions.Remove(key);
            return(value);
        }
Example #7
0
        internal bool TryMapProblemDetails(Exception exception, out MvcProblemDetails problem)
        {
            foreach (var mapper in Mappers)
            {
                if (mapper.TryMap(exception, out problem))
                {
                    return(true);
                }
            }

            problem = default;
            return(false);
        }
Example #8
0
        public void ShouldHandleEmptyProblemDetails()
        {
            var details = new Microsoft.AspNetCore.Mvc.ProblemDetails();

            var serialized   = MessagePackSerializer.Serialize(details, _options);
            var deserialized = MessagePackSerializer.Deserialize <Microsoft.AspNetCore.Mvc.ProblemDetails>(serialized, _options);

            Assert.Null(deserialized.Detail);
            Assert.Null(deserialized.Instance);
            Assert.Null(deserialized.Status);
            Assert.Null(deserialized.Title);
            Assert.Null(deserialized.Type);
            Assert.Empty(deserialized.Extensions);
        }
        private void AddTraceId(HttpContext context, MvcProblemDetails details)
        {
            var key = TraceIdPropertyName;

            if (details.Extensions.ContainsKey(key))
            {
                return;
            }

            var traceId = GetTraceId.Invoke(context);

            if (!string.IsNullOrEmpty(traceId))
            {
                details.Extensions[key] = traceId;
            }
        }
Example #10
0
        public async Task Explicit_Client_Exception_Is_Not_Logged_As_Unhandled_Error()
        {
            var details = new MvcProblemDetails
            {
                Title  = "Too Many Requests",
                Status = StatusCodes.Status429TooManyRequests,
            };

            var ex = new ProblemDetailsException(details);

            using var client = CreateClient(handler: ResponseThrows(ex));

            var response = await client.GetAsync(string.Empty);

            Assert.Equal((HttpStatusCode)StatusCodes.Status429TooManyRequests, response.StatusCode);
            AssertUnhandledExceptionNotLogged(Logger);
        }
        private static void FlattenNestedExtensions(MvcProblemDetails problem)
        {
            // a *sub-class* of a ProblemDetails or ValidationProblemDetails aren't associated
            // with special-case converters and so end up causing the Extensions property to be
            // serialized "as is" and ending up being added as an nested entry in
            // the Extensions property itself!
            // this is a workaround to flatten the extensions values into the Extensions property
            // for details of issue see: https://github.com/khellang/Middleware/issues/74
            if (!(problem.RemoveExtensionValue("extensions") is JObject unhandledExtensionValue))
            {
                return;
            }

            foreach (var(key, value) in unhandledExtensionValue)
            {
                problem.Extensions[key] = value.ToObject(typeof(object));
            }
        }
Example #12
0
            public bool TryMap(Exception exception, out MvcProblemDetails problem)
            {
                if (CanMap(exception.GetType()))
                {
                    try
                    {
                        problem = Mapping(exception);
                        return(true);
                    }
                    catch
                    {
                        problem = default;
                        return(false);
                    }
                }

                problem = default;
                return(false);
            }
Example #13
0
        public async Task ProblemDetailsException_IsHandled()
        {
            var expected = HttpStatusCode.TooManyRequests;

            var details = new MvcProblemDetails
            {
                Title  = "Too Many Requests",
                Status = (int)expected,
            };

            var ex = new ProblemDetailsException(details);

            using var client = CreateClient(handler: ResponseThrows(ex));

            var response = await client.GetAsync("/");

            Assert.Equal(expected, response.StatusCode);
            await AssertIsProblemDetailsResponse(response);
        }
        internal bool TryMapProblemDetails(Exception exception, out MvcProblemDetails problem)
        {
            var type = exception.GetType();

            if (Mappings.TryGetValue(type, out var mapping))
            {
                try
                {
                    problem = mapping(exception);
                    return(true);
                }
                catch
                {
                    problem = default;
                    return(false);
                }
            }

            problem = default;
            return(false);
        }
        public async Task ProblemDetailsException_IsHandled()
        {
            var problemStatus = HttpStatusCode.TooManyRequests;

            var details = new MvcProblemDetails
            {
                Title  = "Too Many Requests",
                Status = (int)problemStatus,
            };

            var ex = new ProblemDetailsException(details);

            using (var server = CreateServer(handler: ResponseThrows(ex)))
                using (var client = server.CreateClient())
                {
                    var response = await client.GetAsync("/");

                    Assert.Equal(problemStatus, response.StatusCode);
                    await AssertIsProblemDetailsResponse(response);
                }
        }
        public async Task ProblemDetailsException_IsHandled()
        {
            const int expected = StatusCodes.Status429TooManyRequests;

            var details = new MvcProblemDetails
            {
                Title  = ReasonPhrases.GetReasonPhrase(expected),
                Type   = $"https://httpstatuses.com/{expected}",
                Status = expected,
            };

            var ex = new ProblemDetailsException(details);

            using var client = CreateClient(handler: ResponseThrows(ex));

            var response = await client.GetAsync("/");

            Assert.Equal(expected, (int)response.StatusCode);

            await AssertIsProblemDetailsResponse(response, expectExceptionDetails : false);
        }
        public async void CorrelationId_Should_Use_Configured_Casing()
        {
            // IMPORTANT: This is causing flaky tests as we're modifying static fields and yet xunit will run
            // tests is parallel and so the change to this static field will apply to those tests that are running
            // in parallel with this one!
            // todo: resolve test flake by making this test run NOT in parallel with other tests

            var originalSettings = JsonProblemDetailsConverter.SerializerOptions;

            try
            {
                // given
                JsonProblemDetailsConverter.SerializerOptions = new JsonSerializerOptions
                {
                    PropertyNamingPolicy = null // Pascal casing
                };
                const string traceId = "123";
                var          problem = new MvcProblemDetails
                {
                    Extensions =
                    {
                        { "traceId", traceId }
                    }
                };
                New.ProblemDetails.BadRequest.CopyStandardFieldsTo(problem);

                var response = await New.HttpResponseMessage.Of(problem);

                // when
                Func <Task> act = async() => await response.EnsureNotProblemDetailAsync();

                // then
                (await act.Should().ThrowAsync <ProblemDetailsException>())
                .Which.Details.Extensions.Should().ContainKey("CorrelationId");
            }
            finally
            {
                JsonProblemDetailsConverter.SerializerOptions = originalSettings;
            }
        }
Example #18
0
        public void ShouldHandleProblemDetails()
        {
            var details = new Microsoft.AspNetCore.Mvc.ProblemDetails
            {
                Type   = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
                Title  = "Internal Server Error",
                Status = 500,
                Detail = "Failed to serialize CSharpFunctionalExtensions.Result`2[[System.Decimal, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Microsoft.AspNetCore.Mvc.ProblemDetails, Microsoft.AspNetCore.Mvc.Core, Version=3.1.4.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]] value."
            };

            details.Extensions.Add("spanId", "0000000000000000");
            details.Extensions.Add("requestId", "0HLVTHCNOGD28");

            var serialized   = MessagePackSerializer.Serialize(details, _options);
            var deserialized = MessagePackSerializer.Deserialize <Microsoft.AspNetCore.Mvc.ProblemDetails>(serialized, _options);

            Assert.Equal(details.Detail, deserialized.Detail);
            Assert.Null(deserialized.Instance);
            Assert.Equal(details.Status, deserialized.Status);
            Assert.Equal(details.Title, deserialized.Title);
            Assert.Equal(details.Type, deserialized.Type);
            Assert.Equal(details.Extensions, deserialized.Extensions);
        }
Example #19
0
 /// <summary>
 ///     Attempts to remove the entry in <see cref="MvcProblemDetails.Extensions" /> matching the
 ///     <paramref name="key" />, returning the value of the entry removed, if any.
 /// </summary>
 public static object RemoveExtensionValue(this MvcProblemDetails problem, string key)
 {
     return(DoRemoveExtensionValue(problem, key) ?? DoRemoveExtensionValue(problem, StringUtils.PascalCase(key)));
 }
Example #20
0
 public ProblemDetailsException(MvcProblemDetails details)
     : this(details, null)
 {
     Details = details;
 }
Example #21
0
 public ProblemDetailsException(Microsoft.AspNetCore.Mvc.ProblemDetails details)
 {
     Details = details;
 }
 public ProblemDetailsWrapper(Microsoft.AspNetCore.Mvc.ProblemDetails problemDetails)
 {
 }
Example #23
0
 public ProblemDetailsException(MvcProblemDetails details, Exception?innerException)
     : base($"{details.Type} : {details.Title}", innerException)
 {
     Details = details;
 }
 public ProblemDetailsException(MvcProblemDetails details)
     : base($"{details.Type} : {details.Title}")
 {
     Details = details;
 }
Example #25
0
 public ProblemDetailsException(Microsoft.AspNetCore.Mvc.ProblemDetails details)
     : base($"{details.Type} : {details.Title}")
 {
     Details = details;
 }
 private static void SetDetails(MvcProblemDetails details, int statusCode)
 {
     details.Status = statusCode;
     details.Type   = GetDefaultType(statusCode);
     details.Title  = ReasonPhrases.GetReasonPhrase(statusCode);
 }
 private static void SetDetails(MvcProblemDetails details, int statusCode)
 {
     details.Status = statusCode;
     details.Type   = $"https://httpstatuses.com/{statusCode}";
     details.Title  = ReasonPhrases.GetReasonPhrase(statusCode);
 }