Comments (2)
Here's an example for how the error information can be extracted, today, with Dapr v1.13:
using Dapr;
using Dapr.Client;
using Grpc.Core;
var client = new DaprClientBuilder()
.UseHttpEndpoint(/* ... */)
.Build();
try
{
/* Perform operation with DaprClient... */
}
catch (DaprException ex) when (ex.InnerException is RpcException rex)
{
var details = rex.Trailers.FirstOrDefault(m => m.Key == "grpc-status-details-bin");
if (details is not null)
{
Google.Rpc.Status status = Google.Rpc.Status.Parser.ParseFrom(details.ValueBytes);
Console.WriteLine(status);
Console.WriteLine($"Status:");
Console.WriteLine($"Code: {status.Code}");
Console.WriteLine($"Message: {status.Message}");
foreach (var detail in status.Details)
{
switch (detail.TypeUrl)
{
case "type.googleapis.com/google.rpc.BadRequest":
var badRequest = Google.Rpc.BadRequest.Parser.ParseFrom(detail.Value);
Console.WriteLine("Bad Request:");
foreach (var fieldViolation in badRequest.FieldViolations)
{
Console.WriteLine($"Field: {fieldViolation.Field}");
Console.WriteLine($"Description: {fieldViolation.Description}");
}
break;
case "type.googleapis.com/google.rpc.ErrorInfo":
var errorInfo = Google.Rpc.ErrorInfo.Parser.ParseFrom(detail.Value);
Console.WriteLine("Error Info:");
Console.WriteLine($"Reason: {errorInfo.Reason}");
Console.WriteLine($"Domain: {errorInfo.Domain}");
break;
case "type.googleapis.com/google.rpc.Help":
var help = Google.Rpc.Help.Parser.ParseFrom(detail.Value);
Console.WriteLine("Help:");
Console.WriteLine($"Links:");
foreach (var link in help.Links)
{
Console.WriteLine($" Description: {link.Description}");
Console.WriteLine($" Url: {link.Url}");
}
break;
case "type.googleapis.com/google.rpc.ResourceInfo":
var resourceInfo = Google.Rpc.ResourceInfo.Parser.ParseFrom(detail.Value);
Console.WriteLine("Resource Info:");
Console.WriteLine($"Resource Type: {resourceInfo.ResourceType}");
Console.WriteLine($"Resource Name: {resourceInfo.ResourceName}");
Console.WriteLine($"Owner: {resourceInfo.Owner}");
Console.WriteLine($"Description: {resourceInfo.Description}");
break;
}
}
}
else
{
Console.WriteLine($"RPC exception: {rex}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex}");
}
from dotnet-sdk.
Here's a proposed API for how extended error info should be obtained by users:
- The starting point would be a method
TryGetExtendedErrorInfo()
exposed directly fromDaprException
or as an extension method - If such info is available on the underlying RPC exception, the method performs the extraction and returns a
DaprExtendedErrorInfo
object which represents a collection of error "details" - Each Dapr-supported details type (i.e. bad request, error info, help, and resource info) is represented as its own type (derived from a base detail type)
- These details types are independent but parallel their respective gRPC types
Question:
Why not just directly return the gRPC types?
Answer:
While the Dapr .NET SDK uses gRPC for communication with the sidecar, such use has generally been considered an implementation detail. There are some exceptions, for example, in the case where applications are calling other applications via gRPC but through the sidecar, but still underlying gRPC types are not exposed from the Dapr API. Exposing Dapr-specific types therefore seems appropriate and also has a benefit in that the complexities of the gRPC type system are not exposed to users.
using System.Diagnostics.CodeAnalysis;
using Grpc.Core;
namespace Dapr.Errors
{
public enum DaprExtendedErrorDetailType
{
BadRequest,
ErrorInfo,
Help,
ResourceInfo
}
public abstract record DaprExtendedErrorDetail(DaprExtendedErrorDetailType Type);
public sealed record DaprBadRequestDetailFieldViolation(string Field, string Description);
public sealed record DaprBadRequestDetail() : DaprExtendedErrorDetail(DaprExtendedErrorDetailType.BadRequest)
{
public DaprBadRequestDetailFieldViolation[] FieldViolations { get; init; } = Array.Empty<DaprBadRequestDetailFieldViolation>();
}
public sealed record DaprErrorInfoDetail(string Reason, string Domain) : DaprExtendedErrorDetail(DaprExtendedErrorDetailType.ErrorInfo);
public sealed record DaprHelpDetailLink(string Url, string Description);
public sealed record DaprHelpDetail() : DaprExtendedErrorDetail(DaprExtendedErrorDetailType.Help)
{
public DaprHelpDetailLink[] Links { get; init; } = Array.Empty<DaprHelpDetailLink>();
}
public sealed record DaprResourceInfoDetail(string ResourceType, string ResourceName, string Owner, string Description) : DaprExtendedErrorDetail(DaprExtendedErrorDetailType.ResourceInfo);
public sealed record DaprExtendedErrorInfo(int Code, string Message)
{
public DaprExtendedErrorDetail[] Details { get; init; } = Array.Empty<DaprExtendedErrorDetail>();
}
public static class DaprExceptionExtensions
{
public static bool TryGetExtendedErrorInfo(this DaprException ex, [NotNullWhen(true)] out DaprExtendedErrorInfo? errorInfo)
{
errorInfo = null;
if (ex.InnerException is RpcException rex)
{
var details = rex.Trailers.FirstOrDefault(m => m.Key == "grpc-status-details-bin");
if (details is not null)
{
Google.Rpc.Status status = Google.Rpc.Status.Parser.ParseFrom(details.ValueBytes);
errorInfo = new DaprExtendedErrorInfo(status.Code, status.Message)
{
Details =
status
.Details
.Select(
detail => detail.TypeUrl switch
{
"type.googleapis.com/google.rpc.BadRequest" => ToDaprBadRequestDetail(Google.Rpc.BadRequest.Parser.ParseFrom(detail.Value)),
"type.googleapis.com/google.rpc.ErrorInfo" => ToDaprErrorInfoDetail(Google.Rpc.ErrorInfo.Parser.ParseFrom(detail.Value)),
"type.googleapis.com/google.rpc.Help" => ToDaprHelpDetail(Google.Rpc.Help.Parser.ParseFrom(detail.Value)),
"type.googleapis.com/google.rpc.ResourceInfo" => ToDaprResourceInfoDetail(Google.Rpc.ResourceInfo.Parser.ParseFrom(detail.Value)),
_ => (DaprExtendedErrorDetail?)null
})
.WhereNotNull()
.ToArray()
};
return true;
}
}
return false;
}
private static DaprBadRequestDetail ToDaprBadRequestDetail(Google.Rpc.BadRequest badRequest) => new DaprBadRequestDetail() { FieldViolations = badRequest.FieldViolations.Select(fieldViolation => new DaprBadRequestDetailFieldViolation(fieldViolation.Field, fieldViolation.Description)).ToArray() };
private static DaprErrorInfoDetail ToDaprErrorInfoDetail(Google.Rpc.ErrorInfo errorInfo) => new DaprErrorInfoDetail(errorInfo.Reason, errorInfo.Domain);
private static DaprHelpDetail ToDaprHelpDetail(Google.Rpc.Help help) => new DaprHelpDetail() { Links = help.Links.Select(link => new DaprHelpDetailLink(link.Url, link.Description)).ToArray() };
private static DaprResourceInfoDetail ToDaprResourceInfoDetail(Google.Rpc.ResourceInfo resourceInfo) => new DaprResourceInfoDetail(resourceInfo.ResourceType, resourceInfo.ResourceName, resourceInfo.Owner, resourceInfo.Description);
}
public static class IEnumerableExtensions
{
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) where T : class => source.Where(item => item is not null)!;
}
}
Use of extended error info would then look like:
try
{
/* Perform operation with DaprClient... */
}
catch (DaprException ex)
{
if (ex.TryGetExtendedErrorInfo(out var details))
{
Console.WriteLine($"Status:");
Console.WriteLine($"Code: {details.Code}");
Console.WriteLine($"Message: {details.Message}");
foreach (var detail in details.Details)
{
switch (detail)
{
case DaprBadRequestDetail badRequest:
Console.WriteLine("Bad Request:");
foreach (var fieldViolation in badRequest.FieldViolations)
{
Console.WriteLine($"Field: {fieldViolation.Field}");
Console.WriteLine($"Description: {fieldViolation.Description}");
}
break;
case DaprErrorInfoDetail errorInfo:
Console.WriteLine("Error Info:");
Console.WriteLine($"Reason: {errorInfo.Reason}");
Console.WriteLine($"Domain: {errorInfo.Domain}");
break;
case DaprHelpDetail help:
Console.WriteLine("Help:");
Console.WriteLine($"Links:");
foreach (var link in help.Links)
{
Console.WriteLine($" Description: {link.Description}");
Console.WriteLine($" Url: {link.Url}");
}
break;
case DaprResourceInfoDetail resourceInfo:
Console.WriteLine("Resource Info:");
Console.WriteLine($"Resource Type: {resourceInfo.ResourceType}");
Console.WriteLine($"Resource Name: {resourceInfo.ResourceName}");
Console.WriteLine($"Owner: {resourceInfo.Owner}");
Console.WriteLine($"Description: {resourceInfo.Description}");
break;
}
}
}
else
{
Console.WriteLine($"Exception: {ex}");
}
}
Open questions:
-
There may still be design/work needed to expose the extended error info in those APIs using HTTP to call the sidecar, where the sidecar returns such info
-
While the extended error info mechansim allows any number of details to be returned, in practice, the Dapr runtime only returns, at most, a single detail of each type. Given that, would it make sense to explicitly expose properties on the root object for each known type? Doing so could
public sealed record DaprExtendedErrorInfo(int Code, string Message) { public DaprExtendedErrorDetail[] Details { get; init; } = Array.Empty<DaprExtendedErrorDetail>(); public DaprBadRequestDetail? BadRequest => Details.OfType<DaprBadRequestDetail>().FirstOrDefault(); public DaprErrorInfoDetail? ErrorInfo => Details.OfType<DaprErrorInfoDetail>().FirstOrDefault(); public DaprHelpDetail? Help => Details.OfType<DaprHelpDetail>().FirstOrDefault(); public DaprResourceInfoDetail? ResourceInfo => Details.OfType<DaprResourceInfoDetail>().FirstOrDefault(); }
from dotnet-sdk.
Related Issues (20)
- How to use ExecuteStateTransactionAsync() HOT 3
- It is not possible to read headers sent from a RabbitMQ message HOT 3
- Unable to use EventHub or Service Bus Dapr Component HOT 1
- Update the holopin.yml to use the sdk badge HOT 2
- Unable to stream using HTTP service invocation HOT 1
- Add option to handle 'dead letters' by sending them to a dead letter queue rather than a dead leter topic HOT 2
- intermittent exceptions from WorkflowLoggingService
- AddDaprSecretStore() fails on Kubernetes HOT 1
- Regarding custom middleware to setup in the pipeline and how to create
- Regarding custom middleware to setup in the pipeline and how to create
- PublishEventAsync Blocking When Delivering Locally? HOT 7
- Cryptography Support Enhancements
- SaveBulkStateAsync does not respect DAPR_API_TOKEN
- Standardize README with shield badges HOT 8
- Dead letter subscription not working with pub sub having consumer ID specified in metadata
- Allow access to injected services during DI registration of DaprClient HOT 1
- Publish message to Kafka topic (KRaft) with Dapr C#
- Best way to create a Dapr HttpClient with custom delegating handler? HOT 1
- [Workflow] GRPC connection to workflow runtime doesn't self-heal when app restarts
- Same application doesn't allow to process CloudEvent and raw messages
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from dotnet-sdk.