Skip to content

Commit da8b21b

Browse files
authored
Support gRPC richer error model (#1436)
* First pass at supporting richer error model in Dapr .NET SDK Signed-off-by: jev-e <[email protected]> Signed-off-by: jev <[email protected]> * Add ExtendedErrorDetailFactory, move to seperate files / new folder, add test file. Signed-off-by: jev <[email protected]> * Flesh out + rename tests file, tidy more comments. Signed-off-by: jev <[email protected]> * Add metadata to ErrorInfo details, add tests for each details type, multiple details Signed-off-by: jev [email protected] Signed-off-by: jev <[email protected]> * Tidy up comments, add copyright to file. Signed-off-by: jev [email protected] Signed-off-by: jev <[email protected]> * add and use constants, more docs tidy up. Signed-off-by: jev [email protected] Signed-off-by: jev <[email protected]> * add initial docs pages for error handling in .net sdk. Signed-off-by: jev [email protected] Signed-off-by: jev <[email protected]> * write daprdocs detailing usage of extendedErrorInfo, rename vars signed-off-by: jev [email protected] Signed-off-by: jev <[email protected]> * Address PR comments Signed-off-by: jev <[email protected]> * pr comment; adjust weight Signed-off-by: jev <[email protected]> --------- Signed-off-by: jev-e <[email protected]> Signed-off-by: jev <[email protected]> Signed-off-by: jev [email protected]
1 parent a61db8b commit da8b21b

File tree

10 files changed

+1324
-0
lines changed

10 files changed

+1324
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
type: docs
3+
title: "Error Handling in the Dapr .NET SDK"
4+
linkTitle: "Error handling"
5+
weight: 90000
6+
description: Learn about error handling in the Dapr.NET SDK.
7+
---
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
type: docs
3+
title: "Richer Error Model in the Dapr .NET SDK"
4+
linkTitle: "Richer error model"
5+
weight: 59000
6+
description: Learn how to use the richer error model in the .NET SDK.
7+
---
8+
9+
The Dapr .NET SDK supports the richer error model, implemented by the Dapr runtime. This model provides a way for applications to enrich their errors with added context,
10+
allowing consumers of the application to better understand the issue and resolve faster. You can read more about the richer error model [here](https://google.aip.dev/193), and you
11+
can find the Dapr proto file implementing these errors [here](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto").
12+
13+
The Dapr .NET SDK implements all details supported by the Dapr runtime, implemented in the `Dapr.Common.Exceptions` namespace, and is accessible through
14+
the `DaprException` extension method `TryGetExtendedErrorInfo`. Currently this detail extraction is only supported for
15+
`RpcException`'s where the details are present.
16+
17+
```csharp
18+
// Example usage of ExtendedErrorInfo
19+
20+
try
21+
{
22+
// Perform some action with the Dapr client that throws a DaprException.
23+
}
24+
catch (DaprException daprEx)
25+
{
26+
if (daprEx.TryGetExtendedErrorInfo(out DaprExtendedErrorInfo errorInfo)
27+
{
28+
Console.WriteLine(errorInfo.Code);
29+
Console.WriteLine(errorInfo.Message);
30+
31+
foreach (DaprExtendedErrorDetail detail in errorInfo.Details)
32+
{
33+
Console.WriteLine(detail.ErrorType);
34+
switch (detail.ErrorType)
35+
case ExtendedErrorType.ErrorInfo:
36+
Console.WriteLine(detail.Reason);
37+
Console.WriteLine(detail.Domain);
38+
default:
39+
Console.WriteLine(detail.TypeUrl);
40+
}
41+
}
42+
}
43+
```
44+
45+
## DaprExtendedErrorInfo
46+
47+
Contains `Code` (the status code) and `Message` (the error message) associated with the error, parsed from an inner `RpcException`.
48+
Also contains a collection of `DaprExtendedErrorDetails` parsed from the details in the exception.
49+
50+
## DaprExtendedErrorDetail
51+
52+
All details implement the abstract `DaprExtendedErrorDetail` and have an associated `DaprExtendedErrorType`.
53+
54+
1. [RetryInfo](#retryinfo)
55+
56+
2. [DebugInfo](#debuginfo)
57+
58+
3. [QuotaFailure](#quotafailure)
59+
60+
4. [PreconditionFailure](#preconditionfailure)
61+
62+
5. [RequestInfo](#requestinfo)
63+
64+
6. [LocalizedMessage](#localizedmessage)
65+
66+
7. [BadRequest](#badrequest)
67+
68+
8. [ErrorInfo](#errorinfo)
69+
70+
9. [Help](#help)
71+
72+
10. [ResourceInfo](#resourceinfo)
73+
74+
11. [Unknown](#unknown)
75+
76+
## RetryInfo
77+
78+
Information telling the client how long to wait before they should retry. Provides a `DaprRetryDelay` with the properties
79+
`Second` (offset in seconds) and `Nano` (offset in nanoseconds).
80+
81+
## DebugInfo
82+
83+
Debugging information offered by the server. Contains `StackEntries` (a collection of strings containing the stack trace), and
84+
`Detail` (further debugging information).
85+
86+
## QuotaFailure
87+
88+
Information relating to some quota that may have been reached, such as a daily usage limit on an API. It has one property `Violations`,
89+
a collection of `DaprQuotaFailureViolation`, which each contain a `Subject` (the subject of the request) and `Description` (further information regarding the failure).
90+
91+
## PreconditionFailure
92+
93+
Information informing the client that some required precondition was not met. Has one property `Violations`, a collection of
94+
`DaprPreconditionFailureViolation`, which each has `Subject` (subject where the precondition failure occured e.g. "Azure"), `Type` (representation of the precondition type e.g. "TermsOfService"), and `Description` (further description e.g. "ToS must be accepted.").
95+
96+
## RequestInfo
97+
98+
Information returned by the server that can be used by the server to identify the clients request. Contains
99+
`RequestId` and `ServingData` properties, `RequestId` being some string (such as a UID) the server can interpret,
100+
and `ServingData` being some arbitrary data that made up part of the request.
101+
102+
## LocalizedMessage
103+
104+
Contains a localized message, along with the locale of the message. Contains `Locale` (the locale e.g. "en-US") and `Message` (the localized message).
105+
106+
## BadRequest
107+
108+
Describes a bad request field. Contains collection of `DaprBadRequestDetailFieldViolation`, which each has `Field` (the offending field in request e.g. 'first_name') and
109+
`Description` (further information detailing the reason e.g. "first_name cannot contain special characters").
110+
111+
## ErrorInfo
112+
113+
Details the cause of an error. Contains three properties, `Reason` (the reason for the error, which should take the form of UPPER_SNAKE_CASE e.g. DAPR_INVALID_KEY),
114+
`Domain` (domain the error belongs to e.g. 'dapr.io'), and `Metadata`, a key value based collection of futher information.
115+
116+
## Help
117+
118+
Provides resources for the client to perform further research into the issue. Contains a collection of `DaprHelpDetailLink`,
119+
which provides `Url` (a url to help or documentation), and `Description` (a description of what the link provides).
120+
121+
## ResourceInfo
122+
123+
Provides information relating to an accessed resource. Provides three properties `ResourceType` (type of the resource being access e.g. "Azure service bus"),
124+
`ResourceName` (The name of the resource e.g. "my-configured-service-bus"), `Owner` (the owner of the resource e.g. "[email protected]"),
125+
and `Description` (further information on the resource relating to the error e.g. "missing permissions to use this resource").
126+
127+
## Unknown
128+
129+
Returned when the detail type url cannot be mapped to the correct `DaprExtendedErrorDetail` implementation.
130+
Provides one property `TypeUrl` (the type url that could not be parsed e.g. "type.googleapis.com/Google.rpc.UnrecognizedType").
131+
132+
133+
134+
135+
136+
137+
138+
139+
140+

src/Dapr.Common/Dapr.Common.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="Google.Api.CommonProtos" />
1011
<PackageReference Include="Grpc.Net.Client" />
1112
<PackageReference Include="Microsoft.Extensions.Http" />
1213
<PackageReference Include="Microsoft.Extensions.Configuration" />
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// ------------------------------------------------------------------------
2+
// Copyright 2024 The Dapr Authors
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+
// http://www.apache.org/licenses/LICENSE-2.0
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
// See the License for the specific language governing permissions and
11+
// limitations under the License.
12+
// ------------------------------------------------------------------------
13+
14+
using System.Diagnostics.CodeAnalysis;
15+
using Grpc.Core;
16+
17+
namespace Dapr.Common.Exceptions
18+
{
19+
/// <summary>
20+
/// Provides extension methods for <see cref="DaprException"/>.
21+
/// </summary>
22+
public static class DaprExceptionExtensions
23+
{
24+
/// <summary>
25+
/// Attempt to retrieve <see cref="DaprExtendedErrorInfo"/> from <see cref="DaprException"/>.
26+
/// </summary>
27+
/// <param name="exception">A Dapr exception. <see cref="DaprException"/>.</param>
28+
/// <param name="daprExtendedErrorInfo"><see cref="DaprExtendedErrorInfo"/> out if parsable from inner exception, null otherwise.</param>
29+
/// <returns>True if extended info is available, false otherwise.</returns>
30+
public static bool TryGetExtendedErrorInfo(this DaprException exception, [NotNullWhen(true)] out DaprExtendedErrorInfo? daprExtendedErrorInfo)
31+
{
32+
daprExtendedErrorInfo = null;
33+
if (exception.InnerException is not RpcException rpcException)
34+
{
35+
return false;
36+
}
37+
38+
var metadata = rpcException.Trailers.Get(DaprExtendedErrorConstants.GrpcDetails);
39+
40+
if (metadata is null)
41+
{
42+
return false;
43+
}
44+
45+
var status = Google.Rpc.Status.Parser.ParseFrom(metadata.ValueBytes);
46+
47+
daprExtendedErrorInfo = new DaprExtendedErrorInfo(status.Code, status.Message)
48+
{
49+
Details = status.Details.Select(detail => ExtendedErrorDetailFactory.CreateErrorDetail(detail)).ToArray(),
50+
};
51+
52+
return true;
53+
}
54+
}
55+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// ------------------------------------------------------------------------
2+
// Copyright 2024 The Dapr Authors
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+
// http://www.apache.org/licenses/LICENSE-2.0
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
// See the License for the specific language governing permissions and
11+
// limitations under the License.
12+
// ------------------------------------------------------------------------
13+
14+
namespace Dapr.Common.Exceptions
15+
{
16+
/// <summary>
17+
/// Definitions of expected types to be returned from the Dapr runtime.
18+
/// </summary>
19+
internal static class DaprExtendedErrorConstants
20+
{
21+
public const string ErrorDetailTypeUrl = "type.googleapis.com/";
22+
public const string GrpcDetails = "grpc-status-details-bin";
23+
public const string ErrorInfo = $"{ErrorDetailTypeUrl}Google.rpc.ErrorInfo";
24+
public const string RetryInfo = $"{ErrorDetailTypeUrl}Google.rpc.RetryInfo";
25+
public const string DebugInfo = $"{ErrorDetailTypeUrl}Google.rpc.DebugInfo";
26+
public const string QuotaFailure = $"{ErrorDetailTypeUrl}Google.rpc.QuotaFailure";
27+
public const string PreconditionFailure = $"{ErrorDetailTypeUrl}Google.rpc.PreconditionFailure";
28+
public const string BadRequest = $"{ErrorDetailTypeUrl}Google.rpc.BadRequest";
29+
public const string RequestInfo = $"{ErrorDetailTypeUrl}Google.rpc.RequestInfo";
30+
public const string ResourceInfo = $"{ErrorDetailTypeUrl}Google.rpc.ResourceInfo";
31+
public const string Help = $"{ErrorDetailTypeUrl}Google.rpc.Help";
32+
public const string LocalizedMessage = $"{ErrorDetailTypeUrl}Google.rpc.LocalizedMessage";
33+
}
34+
}

0 commit comments

Comments
 (0)