alternative API: expose debug error in form of an exception

pull/22891/head
Jan Tattermusch 5 years ago
parent 860d6e5e26
commit 72c92813a8
  1. 16
      src/csharp/Grpc.Core.Api/Status.cs
  2. 6
      src/csharp/Grpc.Core.Tests/ClientServerTest.cs
  3. 2
      src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
  4. 35
      src/csharp/Grpc.Core/Internal/DebugErrorException.cs

@ -14,6 +14,8 @@
// limitations under the License. // limitations under the License.
#endregion #endregion
using System;
namespace Grpc.Core namespace Grpc.Core
{ {
/// <summary> /// <summary>
@ -47,12 +49,12 @@ namespace Grpc.Core
/// </summary> /// </summary>
/// <param name="statusCode">Status code.</param> /// <param name="statusCode">Status code.</param>
/// <param name="detail">Detail.</param> /// <param name="detail">Detail.</param>
/// <param name="debugErrorString">Optional internal error string.</param> /// <param name="debugErrorException">Optional internal error details.</param>
public Status(StatusCode statusCode, string detail, string debugErrorString) public Status(StatusCode statusCode, string detail, Exception debugErrorException)
{ {
StatusCode = statusCode; StatusCode = statusCode;
Detail = detail; Detail = detail;
DebugErrorString = debugErrorString; DebugErrorException = debugErrorException;
} }
/// <summary> /// <summary>
@ -70,20 +72,20 @@ namespace Grpc.Core
/// This field will be only populated on a client and its value is generated locally, /// This field will be only populated on a client and its value is generated locally,
/// based on the internal state of the gRPC client stack (i.e. the value is never sent over the wire). /// based on the internal state of the gRPC client stack (i.e. the value is never sent over the wire).
/// Note that this field is available only for debugging purposes, the application logic should /// Note that this field is available only for debugging purposes, the application logic should
/// never rely on values of this field (it should should <c>StatusCode</c> and <c>Detail</c> instead). /// never rely on values of this field (it should use <c>StatusCode</c> and <c>Detail</c> instead).
/// Example: when a client fails to connect to a server, this field may provide additional details /// Example: when a client fails to connect to a server, this field may provide additional details
/// why the connection to the server has failed. /// why the connection to the server has failed.
/// </summary> /// </summary>
public string DebugErrorString { get; } public Exception DebugErrorException { get; }
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Status"/>. /// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Status"/>.
/// </summary> /// </summary>
public override string ToString() public override string ToString()
{ {
if (DebugErrorString != null) if (DebugErrorException != null)
{ {
return $"Status(StatusCode=\"{StatusCode}\", Detail=\"{Detail}\", DebugErrorString=\"{DebugErrorString}\")"; return $"Status(StatusCode=\"{StatusCode}\", Detail=\"{Detail}\", DebugErrorException=\"{DebugErrorException}\")";
} }
return $"Status(StatusCode=\"{StatusCode}\", Detail=\"{Detail}\")"; return $"Status(StatusCode=\"{StatusCode}\", Detail=\"{Detail}\")";
} }

@ -144,18 +144,18 @@ namespace Grpc.Core.Tests
{ {
helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
{ {
context.Status = new Status(StatusCode.Unauthenticated, "", "this DebugErrorString value should not be transmitted to the client"); context.Status = new Status(StatusCode.Unauthenticated, "", new DebugErrorException("this DebugErrorString value should not be transmitted to the client"));
return Task.FromResult(""); return Task.FromResult("");
}); });
var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc")); var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode); Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
StringAssert.Contains("Error received from peer", ex.Status.DebugErrorString, "Is \"Error received from peer\" still a valid substring to search for in the client-generated error message from C-core?"); StringAssert.Contains("Error received from peer", ex.Status.DebugErrorException.Message, "Is \"Error received from peer\" still a valid substring to search for in the client-generated error message from C-core?");
Assert.AreEqual(0, ex.Trailers.Count); Assert.AreEqual(0, ex.Trailers.Count);
var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc")); var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode); Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
StringAssert.Contains("Error received from peer", ex2.Status.DebugErrorString, "Is \"Error received from peer\" still a valid substring to search for in the client-generated error message from C-core?"); StringAssert.Contains("Error received from peer", ex2.Status.DebugErrorException.Message, "Is \"Error received from peer\" still a valid substring to search for in the client-generated error message from C-core?");
Assert.AreEqual(0, ex2.Trailers.Count); Assert.AreEqual(0, ex2.Trailers.Count);
} }

@ -93,7 +93,7 @@ namespace Grpc.Core.Internal
IntPtr detailsPtr = Native.grpcsharp_batch_context_recv_status_on_client_details(this, out detailsLength); IntPtr detailsPtr = Native.grpcsharp_batch_context_recv_status_on_client_details(this, out detailsLength);
string details = MarshalUtils.PtrToStringUTF8(detailsPtr, (int)detailsLength.ToUInt32()); string details = MarshalUtils.PtrToStringUTF8(detailsPtr, (int)detailsLength.ToUInt32());
string debugErrorString = Marshal.PtrToStringAnsi(Native.grpcsharp_batch_context_recv_status_on_client_error_string(this)); string debugErrorString = Marshal.PtrToStringAnsi(Native.grpcsharp_batch_context_recv_status_on_client_error_string(this));
var status = new Status(Native.grpcsharp_batch_context_recv_status_on_client_status(this), details, debugErrorString); var status = new Status(Native.grpcsharp_batch_context_recv_status_on_client_status(this), details, debugErrorString != null ? new DebugErrorException(debugErrorString) : null);
IntPtr metadataArrayPtr = Native.grpcsharp_batch_context_recv_status_on_client_trailing_metadata(this); IntPtr metadataArrayPtr = Native.grpcsharp_batch_context_recv_status_on_client_trailing_metadata(this);
var metadata = MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr); var metadata = MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr);

@ -0,0 +1,35 @@
#region Copyright notice and license
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Grpc.Core.Utils;
namespace Grpc.Core.Internal
{
/// <summary>
/// Represents error details provides by C-core's debug_error_string
/// </summary>
internal class DebugErrorException : Exception
{
public DebugErrorException(string message) : base(message)
{
}
}
}
Loading…
Cancel
Save