Skip to content

Commit 499e87b

Browse files
committed
add problem details
1 parent f2c14d7 commit 499e87b

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

AzureFunctions.Worker.Extensions.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31521.260
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http.Authentication.JwtBearer", "src\Http.Authentication.JwtBearer\Http.Authentication.JwtBearer.csproj", "{96EED901-92B2-4460-9D9D-FF981D5DFA66}"
77
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http.ProblemDetails", "src\Http.ProblemDetails\Http.ProblemDetails.csproj", "{4A8E996C-9FC8-42F6-941B-0B967600D1D7}"
9+
EndProject
810
Global
911
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1012
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
1517
{96EED901-92B2-4460-9D9D-FF981D5DFA66}.Debug|Any CPU.Build.0 = Debug|Any CPU
1618
{96EED901-92B2-4460-9D9D-FF981D5DFA66}.Release|Any CPU.ActiveCfg = Release|Any CPU
1719
{96EED901-92B2-4460-9D9D-FF981D5DFA66}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{4A8E996C-9FC8-42F6-941B-0B967600D1D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{4A8E996C-9FC8-42F6-941B-0B967600D1D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{4A8E996C-9FC8-42F6-941B-0B967600D1D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{4A8E996C-9FC8-42F6-941B-0B967600D1D7}.Release|Any CPU.Build.0 = Release|Any CPU
1824
EndGlobalSection
1925
GlobalSection(SolutionProperties) = preSolution
2026
HideSolutionNode = FALSE
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<AssemblyName>Microsoft.Azure.Functions.Worker.Extensions.Http.ProblemDetails</AssemblyName>
6+
<RootNamespace>Microsoft.Azure.Functions.Worker.Http</RootNamespace>
7+
<PackageId>Community.Azure.Functions.Worker.Extensions.Http.ProblemDetails</PackageId>
8+
<Nullable>enable</Nullable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
13+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Core" Version="1.4.0" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace Microsoft.Azure.Functions.Worker.Http;
7+
8+
/// <summary>
9+
/// A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807.
10+
/// </summary>
11+
[JsonConverter(typeof(ProblemDetailsJsonConverter))]
12+
public class ProblemDetails
13+
{
14+
/// <summary>
15+
/// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when
16+
/// dereferenced, it provide human-readable documentation for the problem type
17+
/// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be
18+
/// "about:blank".
19+
/// </summary>
20+
[JsonPropertyName("type")]
21+
public string? Type { get; set; }
22+
23+
/// <summary>
24+
/// A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence
25+
/// of the problem, except for purposes of localization(e.g., using proactive content negotiation;
26+
/// see[RFC7231], Section 3.4).
27+
/// </summary>
28+
[JsonPropertyName("title")]
29+
public string? Title { get; set; }
30+
31+
/// <summary>
32+
/// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.
33+
/// </summary>
34+
[JsonPropertyName("status")]
35+
public int? Status { get; set; }
36+
37+
/// <summary>
38+
/// A human-readable explanation specific to this occurrence of the problem.
39+
/// </summary>
40+
[JsonPropertyName("detail")]
41+
public string? Detail { get; set; }
42+
43+
/// <summary>
44+
/// A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.
45+
/// </summary>
46+
[JsonPropertyName("instance")]
47+
public string? Instance { get; set; }
48+
49+
/// <summary>
50+
/// Gets the <see cref="IDictionary{TKey, TValue}"/> for extension members.
51+
/// <para>
52+
/// Problem type definitions MAY extend the problem details object with additional members. Extension members appear in the same namespace as
53+
/// other members of a problem type.
54+
/// </para>
55+
/// </summary>
56+
/// <remarks>
57+
/// The round-tripping behavior for <see cref="Extensions"/> is determined by the implementation of the Input \ Output formatters.
58+
/// In particular, complex types or collection types may not round-trip to the original type when using the built-in JSON or XML formatters.
59+
/// </remarks>
60+
[JsonExtensionData]
61+
public IDictionary<string, object?> Extensions { get; } = new Dictionary<string, object?>(StringComparer.Ordinal);
62+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
8+
namespace Microsoft.Azure.Functions.Worker.Http;
9+
10+
internal sealed class ProblemDetailsJsonConverter : JsonConverter<ProblemDetails>
11+
{
12+
private static readonly JsonEncodedText Type = JsonEncodedText.Encode("type");
13+
private static readonly JsonEncodedText Title = JsonEncodedText.Encode("title");
14+
private static readonly JsonEncodedText Status = JsonEncodedText.Encode("status");
15+
private static readonly JsonEncodedText Detail = JsonEncodedText.Encode("detail");
16+
private static readonly JsonEncodedText Instance = JsonEncodedText.Encode("instance");
17+
18+
public override ProblemDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
19+
{
20+
var problemDetails = new ProblemDetails();
21+
22+
if (reader.TokenType != JsonTokenType.StartObject)
23+
{
24+
throw new JsonException("Unexcepted end when reading JSON.");
25+
}
26+
27+
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
28+
{
29+
ReadValue(ref reader, problemDetails, options);
30+
}
31+
32+
if (reader.TokenType != JsonTokenType.EndObject)
33+
{
34+
throw new JsonException("Unexcepted end when reading JSON.");
35+
}
36+
37+
return problemDetails;
38+
}
39+
40+
public override void Write(Utf8JsonWriter writer, ProblemDetails value, JsonSerializerOptions options)
41+
{
42+
writer.WriteStartObject();
43+
WriteProblemDetails(writer, value, options);
44+
writer.WriteEndObject();
45+
}
46+
47+
internal static void ReadValue(ref Utf8JsonReader reader, ProblemDetails value, JsonSerializerOptions options)
48+
{
49+
if (TryReadStringProperty(ref reader, Type, out var propertyValue))
50+
{
51+
value.Type = propertyValue;
52+
}
53+
else if (TryReadStringProperty(ref reader, Title, out propertyValue))
54+
{
55+
value.Title = propertyValue;
56+
}
57+
else if (TryReadStringProperty(ref reader, Detail, out propertyValue))
58+
{
59+
value.Detail = propertyValue;
60+
}
61+
else if (TryReadStringProperty(ref reader, Instance, out propertyValue))
62+
{
63+
value.Instance = propertyValue;
64+
}
65+
else if (reader.ValueTextEquals(Status.EncodedUtf8Bytes))
66+
{
67+
reader.Read();
68+
if (reader.TokenType == JsonTokenType.Null)
69+
{
70+
// Nothing to do here.
71+
}
72+
else
73+
{
74+
value.Status = reader.GetInt32();
75+
}
76+
}
77+
else
78+
{
79+
var key = reader.GetString()!;
80+
reader.Read();
81+
value.Extensions[key] = JsonSerializer.Deserialize(ref reader, typeof(object), options);
82+
}
83+
}
84+
85+
internal static bool TryReadStringProperty(ref Utf8JsonReader reader, JsonEncodedText propertyName, [NotNullWhen(true)] out string? value)
86+
{
87+
if (!reader.ValueTextEquals(propertyName.EncodedUtf8Bytes))
88+
{
89+
value = default;
90+
return false;
91+
}
92+
93+
reader.Read();
94+
value = reader.GetString()!;
95+
return true;
96+
}
97+
98+
internal static void WriteProblemDetails(Utf8JsonWriter writer, ProblemDetails value, JsonSerializerOptions options)
99+
{
100+
if (value.Type != null)
101+
{
102+
writer.WriteString(Type, value.Type);
103+
}
104+
105+
if (value.Title != null)
106+
{
107+
writer.WriteString(Title, value.Title);
108+
}
109+
110+
if (value.Status != null)
111+
{
112+
writer.WriteNumber(Status, value.Status.Value);
113+
}
114+
115+
if (value.Detail != null)
116+
{
117+
writer.WriteString(Detail, value.Detail);
118+
}
119+
120+
if (value.Instance != null)
121+
{
122+
writer.WriteString(Instance, value.Instance);
123+
}
124+
125+
foreach (var kvp in value.Extensions)
126+
{
127+
writer.WritePropertyName(kvp.Key);
128+
JsonSerializer.Serialize(writer, kvp.Value, kvp.Value?.GetType() ?? typeof(object), options);
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)