Skip to content

Commit 5b9a956

Browse files
committed
Merge branch 'grpc.statuspro' of https://github.com/tonydnewell/grpc-dotnet into tonydnewell-grpc.statuspro
2 parents 246c43d + 3b57cac commit 5b9a956

14 files changed

+1553
-0
lines changed

Grpc.DotNet.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.HealthCheck.Tests", "t
140140
EndProject
141141
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Reflection.Tests", "test\Grpc.Reflection.Tests\Grpc.Reflection.Tests.csproj", "{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC}"
142142
EndProject
143+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.StatusProto", "src\Grpc.StatusProto\Grpc.StatusProto.csproj", "{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}"
144+
EndProject
145+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.StatusProto.Tests", "test\Grpc.StatusProto.Tests\Grpc.StatusProto.Tests.csproj", "{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}"
146+
EndProject
143147
Global
144148
GlobalSection(SolutionConfigurationPlatforms) = preSolution
145149
Debug|Any CPU = Debug|Any CPU
@@ -302,6 +306,14 @@ Global
302306
{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
303307
{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
304308
{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC}.Release|Any CPU.Build.0 = Release|Any CPU
309+
{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
310+
{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
311+
{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
312+
{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}.Release|Any CPU.Build.0 = Release|Any CPU
313+
{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
314+
{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}.Debug|Any CPU.Build.0 = Debug|Any CPU
315+
{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}.Release|Any CPU.ActiveCfg = Release|Any CPU
316+
{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}.Release|Any CPU.Build.0 = Release|Any CPU
305317
EndGlobalSection
306318
GlobalSection(SolutionProperties) = preSolution
307319
HideSolutionNode = FALSE
@@ -355,6 +367,8 @@ Global
355367
{B4153E7F-5CF3-4DFB-A9D1-5E77A2FB2C48} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
356368
{25544326-C145-4D05-A4C3-AC7D59E17196} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
357369
{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
370+
{C01E4F44-9AB0-4478-A453-C88CCB49A4F1} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
371+
{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
358372
EndGlobalSection
359373
GlobalSection(ExtensibilityGlobals) = postSolution
360374
SolutionGuid = {CD5C2B19-49B4-480A-990C-36D98A719B07}

build/dependencies.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<Project>
22
<PropertyGroup>
33
<BenchmarkDotNetPackageVersion>0.13.1</BenchmarkDotNetPackageVersion>
4+
<GoogleApiCommonProtosPackageVersion>2.10.0</GoogleApiCommonProtosPackageVersion>
45
<GoogleApisAuthPackageVersion>1.46.0</GoogleApisAuthPackageVersion>
56
<GoogleProtobufPackageVersion>3.23.1</GoogleProtobufPackageVersion>
67
<GrpcDotNetPackageVersion>2.55.0</GrpcDotNetPackageVersion> <!-- Used by example projects -->
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2023 gRPC authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Google.Rpc;
16+
17+
namespace Grpc.StatusProto;
18+
19+
/// <summary>
20+
/// Extensions methods for <see cref="System.Exception"/>
21+
/// </summary>
22+
public static class ExceptionExtensions
23+
{
24+
/// <summary>
25+
/// Create a <see cref="Google.Rpc.DebugInfo"/> from an <see cref="System.Exception"/>,
26+
/// populating the Message and StackTrace from the exception.
27+
/// Note: experimental API that can change or be removed without any prior notice.
28+
/// </summary>
29+
/// <remarks>
30+
/// <example>
31+
/// For example:
32+
/// <code>
33+
/// try { /* ... */
34+
/// }
35+
/// catch (Exception e) {
36+
/// Google.Rpc.Status status = new() {
37+
/// Code = (int)StatusCode.Internal,
38+
/// Message = "Internal error",
39+
/// Details = {
40+
/// // populate debugInfo from the exception
41+
/// Any.Pack(e.ToRpcDebugInfo())
42+
/// }
43+
/// };
44+
/// // ...
45+
/// }
46+
/// </code>
47+
/// </example>
48+
/// </remarks>
49+
/// <param name="exception"></param>
50+
/// <param name="innerDepth">Maximum number of inner exceptions to include in the StackTrace. Defaults
51+
/// to not including any inner exceptions</param>
52+
/// <returns>
53+
/// A new <see cref="Google.Rpc.DebugInfo"/> populated from the exception.
54+
/// </returns>
55+
public static DebugInfo ToRpcDebugInfo(this Exception exception, int innerDepth = 0)
56+
{
57+
var debugInfo = new DebugInfo();
58+
59+
var message = exception.Message;
60+
var name = exception.GetType().FullName;
61+
62+
// Populate the Detail from the exception type and message
63+
debugInfo.Detail = message is null ? name : name + ": " + message;
64+
65+
// Populate the StackEntries from the exception StackTrace
66+
if (exception.StackTrace is not null)
67+
{
68+
var sr = new StringReader(exception.StackTrace);
69+
var entry = sr.ReadLine();
70+
while (entry is not null)
71+
{
72+
debugInfo.StackEntries.Add(entry);
73+
entry = sr.ReadLine();
74+
}
75+
}
76+
77+
// Add inner exceptions to the StackEntries
78+
var inner = exception.InnerException;
79+
while (innerDepth > 0 && inner is not null)
80+
{
81+
message = inner.Message;
82+
name = inner.GetType().FullName;
83+
debugInfo.StackEntries.Add("InnerException: " + (message is null ? name : name + ": " + message));
84+
85+
if (inner.StackTrace is not null)
86+
{
87+
var sr = new StringReader(inner.StackTrace);
88+
var entry = sr.ReadLine();
89+
while (entry is not null)
90+
{
91+
debugInfo.StackEntries.Add(entry);
92+
entry = sr.ReadLine();
93+
}
94+
}
95+
96+
inner = inner.InnerException;
97+
--innerDepth;
98+
}
99+
100+
return debugInfo;
101+
}
102+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Description>gRPC C# API for error handling using google/rpc/status.proto</Description>
5+
<PackageTags>gRPC RPC HTTP/2</PackageTags>
6+
7+
<IsGrpcPublishedPackage>true</IsGrpcPublishedPackage>
8+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
9+
<TargetFrameworks>net462;netstandard2.0;netstandard2.1</TargetFrameworks>
10+
<PackageReadmeFile>README.md</PackageReadmeFile>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Google.Api.CommonProtos" Version="$(GoogleApiCommonProtosPackageVersion)" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<None Include="README.md" Pack="true" PackagePath="\" />
19+
20+
<ProjectReference Include="..\Grpc.Core.Api\Grpc.Core.Api.csproj" />
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2023 gRPC authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Google.Protobuf;
16+
using Grpc.Core;
17+
18+
namespace Grpc.StatusProto;
19+
20+
/// <summary>
21+
/// Extension methods for the Grpc.Core.Metadata
22+
/// </summary>
23+
public static class MetadataExtensions
24+
{
25+
/// <summary>
26+
/// Name of key in the metadata for the binary encoding of
27+
/// <see cref="Google.Rpc.Status"/>
28+
/// </summary>
29+
public const string StatusDetailsTrailerName = "grpc-status-details-bin";
30+
31+
/// <summary>
32+
/// Get the <see cref="Google.Rpc.Status"/> from the metadata.
33+
/// If the metadata received contains duplicate status details then the last one found
34+
/// is the one that is returned.
35+
/// Note: experimental API that can change or be removed without any prior notice.
36+
/// </summary>
37+
/// <param name="metadata"></param>
38+
/// <param name="throwOnParseError">if true then <see cref="Google.Protobuf.InvalidProtocolBufferException"/>
39+
/// is thrown if the metadata cannot be parsed. Otherwise null is returned on a parsing error.</param>
40+
/// <returns>
41+
/// The found <see cref="Google.Rpc.Status"/> or null if it was
42+
/// not present or could the data could not be parsed.
43+
/// </returns>
44+
public static Google.Rpc.Status? GetRpcStatus(this Metadata metadata, bool throwOnParseError=false)
45+
{
46+
var entry = metadata.LastOrDefault(t => t.Key == StatusDetailsTrailerName);
47+
if (entry is null)
48+
{
49+
return null;
50+
}
51+
try
52+
{
53+
return Google.Rpc.Status.Parser.ParseFrom(entry.ValueBytes);
54+
}
55+
catch
56+
{
57+
if (throwOnParseError)
58+
{
59+
throw;
60+
}
61+
62+
// By default if the message is malformed, just report there's no information.
63+
return null;
64+
}
65+
}
66+
67+
/// <summary>
68+
/// Add <see cref="Google.Rpc.Status"/> to the metadata.
69+
/// Any existing status in the metadata will be overwritten.
70+
/// Note: experimental API that can change or be removed without any prior notice.
71+
/// </summary>
72+
/// <param name="metadata"></param>
73+
/// <param name="status">Status to add</param>
74+
public static void SetRpcStatus(this Metadata metadata, Google.Rpc.Status status)
75+
{
76+
var entry = metadata.Get(StatusDetailsTrailerName);
77+
while (entry is not null)
78+
{
79+
metadata.Remove(entry);
80+
entry = metadata.Get(StatusDetailsTrailerName);
81+
}
82+
metadata.Add(StatusDetailsTrailerName, status.ToByteArray());
83+
}
84+
}

0 commit comments

Comments
 (0)