Skip to content

Commit 04c35a7

Browse files
authored
[release/8.0] Fix casing of ProblemDetails for RFC compliance (#53792)
* Fix casing of ProblemDetails for RFC compliance * Update tests
1 parent c581bf4 commit 04c35a7

File tree

4 files changed

+71
-0
lines changed

4 files changed

+71
-0
lines changed

src/Http/Http.Abstractions/src/ProblemDetails/HttpValidationProblemDetails.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text.Json.Serialization;
45
using Microsoft.AspNetCore.Mvc;
56

67
namespace Microsoft.AspNetCore.Http;
@@ -36,5 +37,6 @@ private HttpValidationProblemDetails(Dictionary<string, string[]> errors)
3637
/// <summary>
3738
/// Gets the validation errors associated with this instance of <see cref="HttpValidationProblemDetails"/>.
3839
/// </summary>
40+
[JsonPropertyName("errors")]
3941
public IDictionary<string, string[]> Errors { get; set; } = new Dictionary<string, string[]>(StringComparer.Ordinal);
4042
}

src/Http/Http.Abstractions/src/ProblemDetails/ProblemDetails.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class ProblemDetails
1818
/// </summary>
1919
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2020
[JsonPropertyOrder(-5)]
21+
[JsonPropertyName("type")]
2122
public string? Type { get; set; }
2223

2324
/// <summary>
@@ -27,27 +28,31 @@ public class ProblemDetails
2728
/// </summary>
2829
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2930
[JsonPropertyOrder(-4)]
31+
[JsonPropertyName("title")]
3032
public string? Title { get; set; }
3133

3234
/// <summary>
3335
/// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.
3436
/// </summary>
3537
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
3638
[JsonPropertyOrder(-3)]
39+
[JsonPropertyName("status")]
3740
public int? Status { get; set; }
3841

3942
/// <summary>
4043
/// A human-readable explanation specific to this occurrence of the problem.
4144
/// </summary>
4245
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4346
[JsonPropertyOrder(-2)]
47+
[JsonPropertyName("detail")]
4448
public string? Detail { get; set; }
4549

4650
/// <summary>
4751
/// A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.
4852
/// </summary>
4953
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
5054
[JsonPropertyOrder(-1)]
55+
[JsonPropertyName("instance")]
5156
public string? Instance { get; set; }
5257

5358
/// <summary>

src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,68 @@ public async Task WriteAsync_Works()
5454
Assert.Equal(expectedProblem.Instance, problemDetails.Instance);
5555
}
5656

57+
[Fact]
58+
public async Task WriteAsync_Works_ProperCasing()
59+
{
60+
// Arrange
61+
var writer = GetWriter();
62+
var stream = new MemoryStream();
63+
var context = CreateContext(stream);
64+
var expectedProblem = new ProblemDetails()
65+
{
66+
Detail = "Custom Bad Request",
67+
Instance = "Custom Bad Request",
68+
Status = StatusCodes.Status400BadRequest,
69+
Type = "https://tools.ietf.org/html/rfc9110#section-15.5.1-custom",
70+
Title = "Custom Bad Request",
71+
Extensions = new Dictionary<string, object>() { { "extensionKey", 1 } }
72+
};
73+
var problemDetailsContext = new ProblemDetailsContext()
74+
{
75+
HttpContext = context,
76+
ProblemDetails = expectedProblem
77+
};
78+
79+
//Act
80+
await writer.WriteAsync(problemDetailsContext);
81+
82+
//Assert
83+
stream.Position = 0;
84+
var result = await JsonSerializer.DeserializeAsync<Dictionary<string, object>>(stream, JsonSerializerOptions.Default);
85+
Assert.Equal(result.Keys, new(new() { { "type", 0 }, { "title", 1 }, { "status", 2 }, { "detail", 3 }, { "instance", 4 }, { "extensionKey", 5 } }));
86+
}
87+
88+
[Fact]
89+
public async Task WriteAsync_Works_ProperCasing_ValidationProblemDetails()
90+
{
91+
// Arrange
92+
var writer = GetWriter();
93+
var stream = new MemoryStream();
94+
var context = CreateContext(stream);
95+
var expectedProblem = new ValidationProblemDetails()
96+
{
97+
Detail = "Custom Bad Request",
98+
Instance = "Custom Bad Request",
99+
Status = StatusCodes.Status400BadRequest,
100+
Type = "https://tools.ietf.org/html/rfc9110#section-15.5.1-custom",
101+
Title = "Custom Bad Request",
102+
Errors = new Dictionary<string, string[]>() { { "name", ["Name is invalid."] } }
103+
};
104+
var problemDetailsContext = new ProblemDetailsContext()
105+
{
106+
HttpContext = context,
107+
ProblemDetails = expectedProblem
108+
};
109+
110+
//Act
111+
await writer.WriteAsync(problemDetailsContext);
112+
113+
//Assert
114+
stream.Position = 0;
115+
var result = await JsonSerializer.DeserializeAsync<Dictionary<string, object>>(stream, JsonSerializerOptions.Default);
116+
Assert.Equal(result.Keys, new(new() { { "type", 0 }, { "title", 1 }, { "status", 2 }, { "detail", 3 }, { "instance", 4 }, { "errors", 5 } }));
117+
}
118+
57119
[Fact]
58120
public async Task WriteAsync_Works_WhenReplacingProblemDetailsUsingSetter()
59121
{

src/Mvc/Mvc.Core/src/ValidationProblemDetails.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text.Json.Serialization;
45
using Microsoft.AspNetCore.Http;
56
using Microsoft.AspNetCore.Mvc.Core;
67
using Microsoft.AspNetCore.Mvc.ModelBinding;
@@ -81,5 +82,6 @@ public ValidationProblemDetails(IDictionary<string, string[]> errors)
8182
/// <summary>
8283
/// Gets the validation errors associated with this instance of <see cref="HttpValidationProblemDetails"/>.
8384
/// </summary>
85+
[JsonPropertyName("errors")]
8486
public new IDictionary<string, string[]> Errors { get { return base.Errors; } set { base.Errors = value; } }
8587
}

0 commit comments

Comments
 (0)