Skip to content

Commit

Permalink
feat: Add a GetAllStatusDetails extension method for RpcException
Browse files Browse the repository at this point in the history
This is jsut a convenience method over calling GetRpcStatus and then
calling UnpackDetailMessages, but avoids users needing to take into
account that GetRpcStatus can return null.
  • Loading branch information
jskeet committed Feb 6, 2025
1 parent 35dfc93 commit 46758ed
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 4 deletions.
15 changes: 12 additions & 3 deletions Google.Api.Gax.Grpc.Tests/RpcExceptionExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Google.Protobuf.WellKnownTypes;
using Google.Protobuf;
using static Google.Rpc.Help.Types;
using System.Linq;

namespace Google.Api.Gax.Grpc.Tests
{
Expand All @@ -32,31 +33,35 @@ public void NullArgument_AllMethodsThrow()
Assert.Throws<ArgumentNullException>(() => ex.GetHelp());
Assert.Throws<ArgumentNullException>(() => ex.GetLocalizedMessage());
Assert.Throws<ArgumentNullException>(() => ex.GetStatusDetail<BadRequest>());
Assert.Throws<ArgumentNullException>(() => ex.GetAllStatusDetails());
}

[Fact]
public void NoTrailers_AllMethodsReturnNull()
public void NoTrailers_AllMethodsReturnNullOrEmpty()
{
RpcException ex = new RpcException(s_status);
AssertAllMethodsReturnNull(ex);
Assert.Empty(ex.GetAllStatusDetails());
}

[Fact]
public void IrrelevantTrailer_AllMethodsReturnNull()
public void IrrelevantTrailer_AllMethodsReturnNullOrEmpty()
{
var metadata = new Metadata();
metadata.Add("key", "value");
RpcException ex = new RpcException(s_status, metadata);
AssertAllMethodsReturnNull(ex);
Assert.Empty(ex.GetAllStatusDetails());
}

[Fact]
public void InvalidProtobufStatusTrailer_AllMethodsReturnNull()
public void InvalidProtobufStatusTrailer_AllMethodsReturnNullOrEmpty()
{
var metadata = new Metadata();
metadata.Add(RpcExceptionExtensions.StatusDetailsTrailerName, new byte[] { 1, 2, 3, 4 });
RpcException ex = new RpcException(s_status, metadata);
AssertAllMethodsReturnNull(ex);
Assert.Empty(ex.GetAllStatusDetails());
}

[Fact]
Expand All @@ -78,6 +83,7 @@ public void ValidTrailer_GetRpcStatus()
Assert.Null(ex.GetHelp());
Assert.Null(ex.GetLocalizedMessage());
Assert.Null(ex.GetStatusDetail<DebugInfo>());
Assert.Empty(ex.GetAllStatusDetails());
}

[Fact]
Expand Down Expand Up @@ -128,6 +134,7 @@ public void ValidTrailer_ArbitraryMessages()
Assert.Equal(badRequest, ex.GetStatusDetail<BadRequest>());
Assert.Equal(help, ex.GetStatusDetail<Help>());
Assert.Equal(localizedMessage, ex.GetStatusDetail<LocalizedMessage>());
Assert.Equal(ex.GetAllStatusDetails(), new IMessage[] { debugInfo, requestInfo, badRequest, help, localizedMessage });
}

[Fact]
Expand All @@ -152,6 +159,7 @@ public void GetErrorInfo_Present()

RpcException ex = new RpcException(s_status, metadata);
Assert.Equal(errorInfo, ex.GetErrorInfo());
Assert.Equal(ex.GetAllStatusDetails(), new IMessage[] { errorInfo });
}

[Fact]
Expand All @@ -168,6 +176,7 @@ public void GetStatusDetail_BadlyPackedMessage()

Assert.Equal(status, ex.GetRpcStatus());
Assert.Null(ex.GetStatusDetail<DebugInfo>());
Assert.Throws<InvalidProtocolBufferException>(() => ex.GetAllStatusDetails().Count());
}

private void AssertAllMethodsReturnNull(RpcException ex)
Expand Down
16 changes: 15 additions & 1 deletion Google.Api.Gax.Grpc/RpcExceptionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Google.Protobuf;
using Google.Rpc;
using Grpc.Core;
using System.Collections.Generic;
using System.Linq;
using Status = Google.Rpc.Status;

Expand Down Expand Up @@ -73,7 +74,7 @@ public static Rpc.Status GetRpcStatus(this RpcException ex) =>
/// </summary>
/// <typeparam name="T">The message type to decode from within the error details.</typeparam>
/// <param name="ex">The RPC exception to retrieve details from. Must not be null.</param>
/// <returns></returns>
/// <returns>The requested status detail, or null if the exception does not contain one.</returns>
public static T GetStatusDetail<T>(this RpcException ex) where T : class, IMessage<T>, new()
{
var status = GetRpcStatus(ex);
Expand All @@ -88,6 +89,19 @@ public static Rpc.Status GetRpcStatus(this RpcException ex) =>
}
}

/// <summary>
/// Returns all standard status details associated with an RPC exception.
/// </summary>
/// <remarks>
/// Status details are assumed to be in the <see cref="StandardErrorTypeRegistry">standard error type registry</see>.
/// If any detail messages are invalid (known, but containing invalid data) then iterating over the returned collection
/// will throw <see cref="InvalidProtocolBufferException"/> when the iterator reaches the invalid message.
/// </remarks>
/// <param name="ex">The RPC exception to retrieve details from. Must not be null.</param>
/// <returns>All standard status details within the exception.</returns>
public static IEnumerable<IMessage> GetAllStatusDetails(this RpcException ex) =>
GetRpcStatus(ex)?.UnpackDetailMessages() ?? Enumerable.Empty<IMessage>();

private static Metadata.Entry GetTrailer(RpcException ex, string key)
{
GaxPreconditions.CheckNotNull(ex, nameof(ex));
Expand Down

0 comments on commit 46758ed

Please sign in to comment.