diff --git a/Directory.Packages.props b/Directory.Packages.props
index 096f72701..e5914c97a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -56,6 +56,7 @@
     
     
     
+    
     
     
     
diff --git a/Grpc.DotNet.sln b/Grpc.DotNet.sln
index 24ff6e3dc..1e04a7f6a 100644
--- a/Grpc.DotNet.sln
+++ b/Grpc.DotNet.sln
@@ -140,6 +140,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.HealthCheck.Tests", "t
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Reflection.Tests", "test\Grpc.Reflection.Tests\Grpc.Reflection.Tests.csproj", "{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.StatusProto", "src\Grpc.StatusProto\Grpc.StatusProto.csproj", "{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.StatusProto.Tests", "test\Grpc.StatusProto.Tests\Grpc.StatusProto.Tests.csproj", "{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -302,6 +306,14 @@ Global
 		{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C01E4F44-9AB0-4478-A453-C88CCB49A4F1}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -355,6 +367,8 @@ Global
 		{B4153E7F-5CF3-4DFB-A9D1-5E77A2FB2C48} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
 		{25544326-C145-4D05-A4C3-AC7D59E17196} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
 		{857C5B4B-E2A8-4ACA-98FB-5E592E2224CC} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
+		{C01E4F44-9AB0-4478-A453-C88CCB49A4F1} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
+		{E49FA5BF-4D67-4C95-9543-8E9FCEAF3609} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {CD5C2B19-49B4-480A-990C-36D98A719B07}
diff --git a/src/Grpc.StatusProto/ExceptionExtensions.cs b/src/Grpc.StatusProto/ExceptionExtensions.cs
new file mode 100644
index 000000000..b22397eb4
--- /dev/null
+++ b/src/Grpc.StatusProto/ExceptionExtensions.cs
@@ -0,0 +1,105 @@
+// Copyright 2023 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.
+
+using Google.Rpc;
+using Grpc.Shared;
+
+namespace Grpc.Core;
+
+/// 
+/// Extensions methods for 
+/// 
+public static class ExceptionExtensions
+{
+    /// 
+    /// Create a  from an ,
+    /// populating the Message and StackTrace from the exception.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// 
+    /// 
+    /// 
+    /// For example:
+    /// 
+    /// try { /* ... */
+    /// }
+    /// catch (Exception e) {
+    ///    Google.Rpc.Status status = new() {
+    ///        Code = (int)StatusCode.Internal,
+    ///        Message = "Internal error",
+    ///        Details = {
+    ///            // populate debugInfo from the exception
+    ///            Any.Pack(e.ToRpcDebugInfo())
+    ///        }
+    ///    };
+    ///    // ...
+    /// }
+    /// 
+    /// 
+    /// 
+    /// 
+    /// Maximum number of inner exceptions to include in the StackTrace. Defaults
+    /// to not including any inner exceptions
+    /// 
+    /// A new  populated from the exception.
+    /// 
+    public static DebugInfo ToRpcDebugInfo(this Exception exception, int innerDepth = 0)
+    {
+        ArgumentNullThrowHelper.ThrowIfNull(exception);
+
+        var debugInfo = new DebugInfo();
+
+        var message = exception.Message;
+        var name = exception.GetType().FullName;
+
+        // Populate the Detail from the exception type and message
+        debugInfo.Detail = message is null ? name : name + ": " + message;
+
+        // Populate the StackEntries from the exception StackTrace
+        if (exception.StackTrace is not null)
+        {
+            var sr = new StringReader(exception.StackTrace);
+            var entry = sr.ReadLine();
+            while (entry is not null)
+            {
+                debugInfo.StackEntries.Add(entry);
+                entry = sr.ReadLine();
+            }
+        }
+
+        // Add inner exceptions to the StackEntries
+        var inner = exception.InnerException;
+        while (innerDepth > 0 && inner is not null)
+        {
+            message = inner.Message;
+            name = inner.GetType().FullName;
+            debugInfo.StackEntries.Add("InnerException: " + (message is null ? name : name + ": " + message));
+
+            if (inner.StackTrace is not null)
+            {
+                var sr = new StringReader(inner.StackTrace);
+                var entry = sr.ReadLine();
+                while (entry is not null)
+                {
+                    debugInfo.StackEntries.Add(entry);
+                    entry = sr.ReadLine();
+                }
+            }
+
+            inner = inner.InnerException;
+            --innerDepth;
+        }
+
+        return debugInfo;
+    }
+}
diff --git a/src/Grpc.StatusProto/Grpc.StatusProto.csproj b/src/Grpc.StatusProto/Grpc.StatusProto.csproj
new file mode 100644
index 000000000..da239737e
--- /dev/null
+++ b/src/Grpc.StatusProto/Grpc.StatusProto.csproj
@@ -0,0 +1,29 @@
+
+
+  
+    gRPC C# API for error handling using google/rpc/status.proto
+    gRPC RPC HTTP/2
+
+    true
+    true
+    net462;netstandard2.0;netstandard2.1
+    README.md
+  
+
+  
+    
+  
+  
+  
+    
+
+    
+    
+    
+  
+
+  
+    
+  
+
+
diff --git a/src/Grpc.StatusProto/MetadataExtensions.cs b/src/Grpc.StatusProto/MetadataExtensions.cs
new file mode 100644
index 000000000..4cbe2b6a1
--- /dev/null
+++ b/src/Grpc.StatusProto/MetadataExtensions.cs
@@ -0,0 +1,83 @@
+// Copyright 2023 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.
+
+using Google.Protobuf;
+using Grpc.Shared;
+
+namespace Grpc.Core;
+
+/// 
+/// Extension methods for the Grpc.Core.Metadata
+/// 
+public static class MetadataExtensions
+{
+    /// 
+    /// Name of key in the metadata for the binary encoding of
+    /// 
+    /// 
+    public const string StatusDetailsTrailerName = "grpc-status-details-bin";
+
+    /// 
+    /// Get the  from the metadata.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// 
+    /// 
+    /// if true then null is returned on a parsing error,
+    /// otherwise 
+    /// will be thrown if the metadata cannot be parsed.
+    /// 
+    /// The found  or null if it was
+    /// not present or could the data could not be parsed.
+    /// 
+    public static Google.Rpc.Status? GetRpcStatus(this Metadata metadata, bool ignoreParseError = false)
+    {
+        ArgumentNullThrowHelper.ThrowIfNull(metadata);
+
+        var entry = metadata.Get(StatusDetailsTrailerName);
+        if (entry is null)
+        {
+            return null;
+        }
+        try
+        {
+            return Google.Rpc.Status.Parser.ParseFrom(entry.ValueBytes);
+        }
+        catch when (ignoreParseError)
+        {
+            // If the message is malformed just report there's no information.
+            return null;
+        }
+    }
+
+    /// 
+    /// Add  to the metadata.
+    /// Any existing status in the metadata will be overwritten.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// 
+    /// 
+    /// Status to add
+    public static void SetRpcStatus(this Metadata metadata, Google.Rpc.Status status)
+    {
+        ArgumentNullThrowHelper.ThrowIfNull(metadata);
+        ArgumentNullThrowHelper.ThrowIfNull(status);
+
+        var entry = metadata.Get(StatusDetailsTrailerName);
+        while (entry is not null)
+        {
+            metadata.Remove(entry);
+            entry = metadata.Get(StatusDetailsTrailerName);
+        }
+        metadata.Add(StatusDetailsTrailerName, status.ToByteArray());
+    }
+}
diff --git a/src/Grpc.StatusProto/README.md b/src/Grpc.StatusProto/README.md
new file mode 100644
index 000000000..baef8e8f7
--- /dev/null
+++ b/src/Grpc.StatusProto/README.md
@@ -0,0 +1,301 @@
+# gRPC C# API for error handling with status.proto
+
+This is a protoype NuGet package providing C# and .NET client and server side support for the
+[gRPC richer error model](https://grpc.io/docs/guides/error/#richer-error-model).
+
+This feature is already available in many other implementations including C++,
+Go, Java and Python.
+
+This package has dependencies on these NuGet packages:
+* `Google.Api.CommonProtos` - to provide the proto implementations used by the richer error model
+* `Grpc.Core.Api` - for API classes such as `RpcException`
+
+## Error handling in gRPC
+
+The standard way for gRPC to report the success or failure of a gRPC call is for a
+status code to be returned. If a call completes successfully the server returns an `OK`
+status to the client, otherwise an error status code is returned with an optional string
+error message that provides further details about what happened. This is known as the
+_standard error model_ and is the official gRPC error model supported by all gRPC
+implementations.
+
+There is another error model known as the _richer error model_ that allows additional
+error details to be included by the server. These are expressed in protocol buffers
+messages, and a
+[set of standard error message types](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto)
+is defined to cover most needs. The protobuf binary encoding of this extra error
+information is provided as trailing metadata in the response.
+
+For more information on the richer error model see the
+[gRPC documentation on error handling](https://grpc.io/docs/guides/error/),
+and the [Google APIs overview of the error model](https://cloud.google.com/apis/design/errors#error_model).
+
+## .NET implementation of the richer error model
+
+The error model is define by the protocol buffers files [status.proto](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto)
+and [error_details.proto](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto),
+and the `Google.Api.CommonProtos` NuGet package that provides the generated .NET classes
+from these proto files.
+
+The error is encapsulated by an instance of `Google.Rpc.Status` and
+returned in the trailing response metadata with well-known key `grpc-status-details-bin`.
+Setting and reading this metadata is handled
+for you when using the methods provided in this package.
+
+## Server Side
+
+The server side uses C#'s Object and Collection initializer syntax.
+
+The server returns the additional error information by throwing an `RpcException` that is
+created from a `Google.Rpc.Status` which contains the details of the error.
+
+To add messages to the `Details` repeated field in `Google.Rpc.Status`, wrap each one in `Any.Pack()` - see example below.
+
+The `Google.Rpc.Status` extension method `ToRpcException` creates the appropriate `RpcException` from the status.
+
+__Example__ - creating and throwing a `RpcException`:
+```C#
+public override Task SayHello(HelloRequest request, ServerCallContext context)
+{
+    ArgumentNotNullOrEmpty(request.Name);
+
+    return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
+}
+
+private static void ArgumentNotNullOrEmpty(string value, [CallerArgumentExpression(nameof(value))] string? paramName = null)
+{
+    if (string.IsNullOrEmpty(value))
+    {
+        throw new Google.Rpc.Status
+        {
+            Code = (int)Code.InvalidArgument,
+            Message = "Bad request",
+            Details =
+            {
+                Any.Pack(new BadRequest
+                {
+                    FieldViolations =
+                    {
+                        new BadRequest.Types.FieldViolation
+                        {
+                            Field = paramName,
+                            Description = "Value is null or empty"
+                        }
+                    }
+                })
+            }
+        }.ToRpcException();
+    }
+}
+```
+
+### A note on error codes
+
+Both `Grpc.Core.StatusCode` and `Google.Rpc.Code` define enums for a common
+set of status codes such as `NotFound`, `PermissionDenied`, etc. They have the same values and are based on the codes defined
+in [grpc/status.h](https://github.com/grpc/grpc/blob/master/include/grpc/status.h).
+
+The recommendation is to use the values in `Google.Rpc.Code` as a convention.
+This is a must for Google APIs and strongly recommended for third party services.
+But users can use a different domain of values if they want and and as long as their
+services are mutually compatible, things will work fine.
+
+In the richer error model the `RpcException` will contain both a `Grpc.Core.Status` (for the
+standard error model) and a `Google.Rpc.Status` (for the richer error model), each with their
+own status code. While an application is free to set these to different values we recommend
+that they are set to the same value to avoid ambiguity.
+
+### Passing stack traces from the server to the client
+
+The richer error model defines a standard way of passing stack traces from the server to the
+client. The `DebugInfo` message can be populated with stack traces and then it can 
+be included in the `Details` of the `Google.Rpc.Status`.
+
+This package includes the extension method `ToRpcDebugInfo` for `System.Exception` to help
+create the `DebugInfo` message with the details from the exception.
+
+Example:
+
+```C#
+try
+{
+    // ...
+}
+catch (Exception e)
+{
+    throw new Google.Rpc.Status
+    {
+        Code = (int)Google.Rpc.Code.Internal,
+        Message = "Internal error",
+        Details =
+        {
+            // populate debugInfo from the exception
+            Any.Pack(e.ToRpcDebugInfo()),
+            // Add any other messages to the details ...
+        }
+    }.ToRpcException();
+}
+```
+
+## Client Side
+
+There is an extension method to retrieve a `Google.Rpc.Status` from the metadata in
+an `RpcException`.
+
+Once the `Google.Rpc.Status` has been retrieved the messages in the `Details`
+can be unpacked. There are two ways of doing this:
+
+- calling `GetDetail()` with one of the expected message types
+- iterating over all the messages in the `Details` using `UnpackDetailMessage()`
+
+__Example__ - calling `GetDetail()`:
+
+```C#
+void PrintError(RpcException ex)
+{
+    // Get the status from the RpcException
+    Google.Rpc.Status? rpcStatus = ex.GetRpcStatus(); // Extension method
+
+    if (rpcStatus != null)
+    {
+        Console.WriteLine($"Google.Rpc Status: Code: {rpcStatus.Code}, Message: {rpcStatus.Message}");
+
+        // Try and get the ErrorInfo from the details
+        ErrorInfo? errorInfo = rpcStatus.GetDetail();
+        if (errorInfo != null)
+        {
+            Console.WriteLine($"\tErrorInfo: Reason: {errorInfo.Reason}, Domain: {errorInfo.Domain}");
+            foreach (var md in errorInfo.Metadata)
+            {
+                Console.WriteLine($"\tKey: {md.Key}, Value: {md.Value}");
+            }
+        }
+        // etc, for any other messages expected in the Details ...
+    }
+}
+```
+
+__Example__ - iterating over all the messages in the `Details`:
+
+```C#
+void PrintStatusDetails(RpcException ex)
+{
+    // Get the status from the RpcException
+    Google.Rpc.Status? rpcStatus = ex.GetRpcStatus(); // Extension method
+
+    if (rpcStatus != null)
+    {
+        // Decode each message item in the details in turn
+        foreach (var msg in rpcStatus.UnpackDetailMessages())
+        {
+            switch (msg)
+            {
+                case ErrorInfo errorInfo:
+                    Console.WriteLine($"ErrorInfo: Reason: {errorInfo.Reason}, Domain: {errorInfo.Domain}");
+                    foreach (var md in errorInfo.Metadata)
+                    {
+                        Console.WriteLine($"\tKey: {md.Key}, Value: {md.Value}");
+                    }
+                    break;
+
+                case BadRequest badRequest:
+                    Console.WriteLine("BadRequest:");
+                    foreach (BadRequest.Types.FieldViolation fv in badRequest.FieldViolations)
+                    {
+                        Console.WriteLine($"\tField: {fv.Field}, Description: {fv.Description}");
+                    }
+                    break;
+
+                // Other cases handled here ...
+            }
+        }
+    }
+
+```
+
+## Returning errors within gRPC streams
+
+The model described above allows you to return an error status when the gRPC call finishes.
+
+As an extension to the richer error model you may want to allow servers to send back
+multiple statuses when streaming responses without terminating the call.
+
+One way of doing this is to include a `google.rpc.Status` message in the definition
+of the response messages returned by the server.  The client should also be aware
+that it may receive a status in the response.
+
+For example:
+
+
+```protobuf
+service WidgetLookupProvider {
+    rpc streamingLookup(stream WidgetReq) returns (stream WidgetRsp) {}
+}
+
+message WidgetReq {
+    string widget_name = 1;
+}
+
+message WidgetRsp {
+    oneof message{
+        // details when ok
+        string widget_details = 1;
+        // or error details
+        google.rpc.Status status = 2;
+   }   
+}
+```
+
+Note: the  [status.proto](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto)
+and [error_details.proto](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto)
+files are provided in the `Google.Api.CommonProtos` NuGet package.
+
+Example server code fragment:
+```C#
+await foreach (var request in requestStream.ReadAllAsync())
+{
+    var response = new WidgetRsp();
+
+    // ... process the request ...
+
+    // to return an error
+    if (error)
+    {
+        response.Status = new Google.Rpc.Status { /* ... */ };
+    }
+    else
+    {
+        response.WidgetDetails = "the details";
+    }
+}
+```
+
+Example client code fragment:
+```C#
+// reading the responses
+var responseReaderTask = Task.Run(async () =>
+{
+    await foreach (var rsp in call.ResponseStream.ReadAllAsync())
+    {
+        switch (rsp.MessageCase)
+        {
+            case WidgetRsp.MessageOneofCase.WidgetDetails:
+                // ... processes the details ...
+                break;
+            case WidgetRsp.MessageOneofCase.Status:
+                // ... handle the error ...
+                break;
+        }
+    }
+});
+
+// sending the requests
+foreach (var request in requests)
+{
+    await call.RequestStream.WriteAsync(request);
+}
+```
+
+## See also
+* [gRPC richer error model](https://grpc.io/docs/guides/error/#richer-error-model)
+* [Google.Api.CommonProtos](https://cloud.google.com/dotnet/docs/reference/Google.Api.CommonProtos/latest/Google.Api)
diff --git a/src/Grpc.StatusProto/RpcExceptionExtensions.cs b/src/Grpc.StatusProto/RpcExceptionExtensions.cs
new file mode 100644
index 000000000..5a7cfa3f1
--- /dev/null
+++ b/src/Grpc.StatusProto/RpcExceptionExtensions.cs
@@ -0,0 +1,37 @@
+// Copyright 2023 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.
+
+using Grpc.Shared;
+
+namespace Grpc.Core;
+
+/// 
+/// Extensions to  for handling rich error model.
+/// 
+public static class RpcExceptionExtensions
+{
+    /// 
+    /// Retrieves the  message containing extended error information
+    /// from the trailers in an , if present.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// 
+    /// The RPC exception to retrieve details from. Must not be null.
+    /// The  message specified in the exception, or null
+    /// if there is no such information.
+    public static Google.Rpc.Status? GetRpcStatus(this RpcException ex)
+    {
+        ArgumentNullThrowHelper.ThrowIfNull(ex);
+        return ex.Trailers.GetRpcStatus();
+    }
+}
diff --git a/src/Grpc.StatusProto/RpcStatusExtensions.cs b/src/Grpc.StatusProto/RpcStatusExtensions.cs
new file mode 100644
index 000000000..a3c8efd6a
--- /dev/null
+++ b/src/Grpc.StatusProto/RpcStatusExtensions.cs
@@ -0,0 +1,112 @@
+// Copyright 2023 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.
+
+using Grpc.Shared;
+
+namespace Grpc.Core;
+
+/// 
+/// Extensions for  to retrieve detailed error information.
+/// Based on ideas from:
+/// https://github.com/googleapis/gax-dotnet/blob/main/Google.Api.Gax.Grpc/RpcExceptionExtensions.cs
+/// 
+public static class RpcStatusExtensions
+{
+    /// 
+    /// Create a  from the 
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// 
+    /// 
+    /// 
+    /// The  and  in the
+    ///  within the exception are populated from the details in the
+    /// 
+    /// 
+    /// 
+    /// 
+    /// Example:
+    /// 
+    /// throw new Google.Rpc.Status {
+    ///   Code = (int) StatusCode.NotFound,
+    ///   Message = "Simple error message",
+    ///   Details = {
+    ///     Any.Pack(new ErrorInfo { Domain = "example", Reason = "some reason" })
+    ///   }
+    /// }.ToRpcException();
+    /// 
+    /// 
+    /// 
+    /// 
+    /// The RPC status. Must not be null
+    /// A  populated with the details from the status.
+    public static RpcException ToRpcException(this Google.Rpc.Status status)
+    {
+        ArgumentNullThrowHelper.ThrowIfNull(status);
+
+        // Both Grpc.Core.StatusCode and Google.Rpc.Code define enums for a common
+        // set of status codes such as "NotFound", "PermissionDenied", etc. They have the same
+        // values and are based on the codes defined "grpc/status.h"
+        //
+        // However applications can use a different domain of values if they want and and as
+        // long as their services are mutually compatible, things will work fine.
+        //
+        // If an application wants to explicitly set different status codes in Grpc.Core.Status
+        // and Google.Rpc.Status then use the ToRpcException below that takes additional parameters.
+        //
+        // Check here that we can convert Google.Rpc.Status.Code to Grpc.Core.StatusCode,
+        // and if not use StatusCode.Unknown.
+        var statusCode = System.Enum.IsDefined(typeof(StatusCode), status.Code) ? (StatusCode)status.Code : StatusCode.Unknown;
+        return status.ToRpcException(statusCode, status.Message);
+    }
+
+    /// 
+    /// Create a  from the 
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// 
+    /// 
+    /// 
+    /// The  and  in the
+    ///  within the exception are populated from the details in the
+    /// 
+    /// 
+    /// 
+    /// 
+    /// Example:
+    /// 
+    /// throw new Google.Rpc.Status {
+    ///   Code = (int) StatusCode.NotFound,
+    ///   Message = "Simple error message",
+    ///   Details = {
+    ///     Any.Pack(new ErrorInfo { Domain = "example", Reason = "some reason" })
+    ///   }
+    /// }.ToRpcException(StatusCode.NotFound, "status message");
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// The status to set in the contained 
+    /// The details to set in the contained 
+    /// 
+    public static RpcException ToRpcException(this Google.Rpc.Status status, StatusCode statusCode, string message)
+    {
+        ArgumentNullThrowHelper.ThrowIfNull(status);
+
+        var metadata = new Metadata();
+        metadata.SetRpcStatus(status);
+        return new RpcException(
+             new Grpc.Core.Status(statusCode, message),
+             metadata);
+    }
+}
diff --git a/test/Grpc.StatusProto.Tests/ExceptionExtensionsTest.cs b/test/Grpc.StatusProto.Tests/ExceptionExtensionsTest.cs
new file mode 100644
index 000000000..dc17d9a3d
--- /dev/null
+++ b/test/Grpc.StatusProto.Tests/ExceptionExtensionsTest.cs
@@ -0,0 +1,131 @@
+#region Copyright notice and license
+// Copyright 2023 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.Text;
+using Google.Rpc;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests;
+
+/// 
+/// Tests for ExceptionExtensions
+/// 
+[TestFixture]
+public class ExceptionExtensionsTest
+{
+    [Test]
+    public void ToRpcDebugInfoTest()
+    {
+        try
+        {
+            // Arrange and Act
+            ThrowException("extra details");
+        }
+        catch (Exception ex)
+        {
+            // Assert
+            var debugInfo = ex.ToRpcDebugInfo();
+            Assert.IsNotNull(debugInfo);
+            Assert.AreEqual("System.ArgumentException: extra details", debugInfo.Detail);
+
+            // Concatenate the returned stack traces into one string for checking
+            var stackTraces = ConcatStackTraces(debugInfo);
+            Console.WriteLine("Test stack trace data:");
+            Console.WriteLine(stackTraces);
+
+            // Test that some of the elements in the stack traces we expect are present.
+            // We are not doing a very strict comparision of the entire stack trace
+            // in case the format is slightly different in different environments.
+            Assert.IsTrue(stackTraces.Contains("ExceptionExtensionsTest.ThrowException"));
+            Assert.IsTrue(stackTraces.Contains("ExceptionExtensionsTest.ToRpcDebugInfoTest"));
+            Assert.IsFalse(stackTraces.Contains("InnerException:"));
+        }
+    }
+
+    [Test]
+    public void ToRpcDebugInfo_WithInnerExceptionTest()
+    {
+        try
+        {
+            // Arrange and Act
+            ThrowException("extra details");
+        }
+        catch (Exception ex)
+        {
+            // Assert
+            var debugInfo = ex.ToRpcDebugInfo(1);
+            Assert.IsNotNull(debugInfo);
+            Assert.AreEqual("System.ArgumentException: extra details", debugInfo.Detail);
+
+            // Concatenate the returned stack traces into one string for checking
+            var stackTraces = ConcatStackTraces(debugInfo);
+            Console.WriteLine("Test stack trace data:");
+            Console.WriteLine(stackTraces);
+
+            // Test that some of the elements in the stack traces we expect are present.
+            // We are not doing a very strict comparision of the entire stack trace
+            // in case the format is slightly different in different environments.
+            Assert.IsTrue(stackTraces.Contains("ExceptionExtensionsTest.ThrowException"));
+            Assert.IsTrue(stackTraces.Contains("ExceptionExtensionsTest.ToRpcDebugInfo_WithInnerExceptionTest"));
+            Assert.IsTrue(stackTraces.Contains("InnerException: System.ApplicationException: inner exception"));
+        }
+    }
+
+    /// 
+    /// Throw an exception that contains an inner exception so that we
+    /// produce a stack trace for the tests.
+    /// 
+    /// 
+    /// 
+    private void ThrowException(string message)
+    {
+        try
+        {
+            ThrowInnerException("inner exception");
+        }
+        catch (Exception ex)
+        {
+            throw new ArgumentException(message, ex);
+        }
+    }
+
+    /// 
+    /// Throw an exception that will be the inner exception in the tests
+    /// 
+    /// 
+    /// 
+    private void ThrowInnerException(string message)
+    {
+        throw new System.ApplicationException(message);
+    }
+
+    /// 
+    /// Join the stack entries into one string
+    /// 
+    /// 
+    /// 
+    private string ConcatStackTraces(DebugInfo debugInfo)
+    {
+        var sb = new StringBuilder();
+
+        foreach (var stackEntry in debugInfo.StackEntries)
+        {
+            sb.AppendLine(stackEntry);
+        }
+
+        return sb.ToString();
+    }
+}
diff --git a/test/Grpc.StatusProto.Tests/Grpc.StatusProto.Tests.csproj b/test/Grpc.StatusProto.Tests/Grpc.StatusProto.Tests.csproj
new file mode 100644
index 000000000..79555410c
--- /dev/null
+++ b/test/Grpc.StatusProto.Tests/Grpc.StatusProto.Tests.csproj
@@ -0,0 +1,13 @@
+
+  
+    net462;net6.0;net7.0;net8.0
+    true
+  
+
+  
+    
+    
+    
+  
+
+
diff --git a/test/Grpc.StatusProto.Tests/MetadataExtensionsTest.cs b/test/Grpc.StatusProto.Tests/MetadataExtensionsTest.cs
new file mode 100644
index 000000000..a21b882d6
--- /dev/null
+++ b/test/Grpc.StatusProto.Tests/MetadataExtensionsTest.cs
@@ -0,0 +1,162 @@
+#region Copyright notice and license
+// Copyright 2023 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 Google.Protobuf.WellKnownTypes;
+using Google.Rpc;
+using NUnit.Framework;
+using Google.Protobuf;
+
+namespace Grpc.Core.Tests;
+
+/// 
+/// Tests for MetadataExtensions
+/// 
+[TestFixture]
+public class MetadataExtensionsTest
+{
+    // creates a status to use in the tests
+    private readonly Google.Rpc.Status status = new()
+    {
+        Code = (int)StatusCode.NotFound,
+        Message = "Simple error message",
+        Details =
+            {
+                Any.Pack(new ErrorInfo
+                {
+                    Domain = "some domain",
+                    Reason = "a reason"
+                }),
+                Any.Pack(new RequestInfo
+                {
+                    RequestId = "request id",
+                    ServingData = "data"
+                }),
+            }
+    };
+
+    [Test]
+    public void SetRpcStatusTest()
+    {
+        // Arrange
+        var metadata = new Metadata();
+
+        // Act
+        metadata.SetRpcStatus(status);
+
+        // Assert
+        var entry = metadata.Get(MetadataExtensions.StatusDetailsTrailerName);
+        Assert.IsNotNull(entry);
+        var sts = Google.Rpc.Status.Parser.ParseFrom(entry!.ValueBytes);
+        Assert.AreEqual(status, sts);
+    }
+
+    [Test]
+    public void SetRpcStatus_MultipleTimes()
+    {
+        // Arrange
+        Google.Rpc.Status status1 = new()
+        {
+            Code = (int)StatusCode.NotFound,
+            Message = "first"
+        };
+
+        Google.Rpc.Status status2 = new()
+        {
+            Code = (int)StatusCode.NotFound,
+            Message = "second"
+        };
+
+        Google.Rpc.Status status3 = new()
+        {
+            Code = (int)StatusCode.NotFound,
+            Message = "third"
+        };
+        var metadata = new Metadata();
+
+        // Act - set the status three times
+        metadata.SetRpcStatus(status1);
+        metadata.SetRpcStatus(status2);
+        metadata.SetRpcStatus(status3);
+
+        // Assert - only the last one should be in the metadata
+        Assert.AreEqual(1, metadata.Count);
+
+        var entry = metadata.Get(MetadataExtensions.StatusDetailsTrailerName);
+        Assert.IsNotNull(entry);
+        var sts = Google.Rpc.Status.Parser.ParseFrom(entry!.ValueBytes);
+        Assert.AreEqual(status3, sts);
+    }
+
+    [Test]
+    public void GetRpcStatus_OK()
+    {
+        // Arrange
+        var metadata = new Metadata();
+        metadata.SetRpcStatus(status);
+
+        // Act - retrieve the status from the metadata
+        var sts = metadata.GetRpcStatus();
+
+        // Assert - status retrieved ok
+        Assert.IsNotNull(sts);
+        Assert.AreEqual(status, sts);
+    }
+
+    [Test]
+    public void GetRpcStatus_NotFound()
+    {
+        // Arrange
+        var metadata = new Metadata();
+
+        // Act - try and retrieve the non-existent status from the metadata
+        var sts = metadata.GetRpcStatus();
+
+        // Assert - not found
+        Assert.IsNull(sts);
+    }
+
+    [Test]
+    public void GetRpcStatus_BadEncoding()
+    {
+        // Arrange - create badly encoded status in the metadata
+        var metadata = new Metadata
+        {
+            { MetadataExtensions.StatusDetailsTrailerName, new byte[] { 1, 2, 3 } }
+        };
+
+        // Act - try and retrieve the badly formed status from the metadata
+        var sts = metadata.GetRpcStatus(ignoreParseError: true);
+
+        // Assert - not found as it could not be decoded
+        Assert.IsNull(sts);
+    }
+
+    [Test]
+    public void GetRpcStatus_BadEncodingWithException()
+    {
+        // Arrange - create badly encoded status in the metadata
+        var metadata = new Metadata
+        {
+            { MetadataExtensions.StatusDetailsTrailerName, new byte[] { 1, 2, 3 } }
+        };
+
+        // Act and Assert
+        // Try and retrieve the status from the metadata and expect an exception
+        // because it could not be decoded
+        _ = Assert.Throws(() => metadata.GetRpcStatus(ignoreParseError: false));
+    }
+
+}
diff --git a/test/Grpc.StatusProto.Tests/RpcExceptionExtensionsTest.cs b/test/Grpc.StatusProto.Tests/RpcExceptionExtensionsTest.cs
new file mode 100644
index 000000000..80fdfa5ef
--- /dev/null
+++ b/test/Grpc.StatusProto.Tests/RpcExceptionExtensionsTest.cs
@@ -0,0 +1,87 @@
+#region Copyright notice and license
+// Copyright 2023 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 Google.Protobuf.WellKnownTypes;
+using Google.Rpc;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests;
+
+/// 
+/// Tests for RpcExceptionExtensions
+/// 
+[TestFixture]
+public class RpcExceptionExtensionsTest
+{
+    // creates a status to use in the tests
+    private readonly Google.Rpc.Status status = new()
+    {
+        Code = (int)StatusCode.NotFound,
+        Message = "Simple error message",
+        Details =
+            {
+                Any.Pack(new ErrorInfo
+                {
+                    Domain = "some domain",
+                    Reason = "a reason"
+                }),
+                Any.Pack(new RequestInfo
+                {
+                    RequestId = "request id",
+                    ServingData = "data"
+                }),
+            }
+    };
+
+    [Test]
+    public void GetRpcStatus_OK()
+    {
+        // Act
+        var exception = status.ToRpcException();
+
+        // Assert - check the contents of the exception
+        Assert.AreEqual(status.Code, (int)exception.StatusCode);
+        Assert.AreEqual(status.Message, exception.Status.Detail);
+        var sts = exception.GetRpcStatus();
+        Assert.IsNotNull(sts);
+        Assert.AreEqual(status, sts);
+    }
+
+    [Test]
+    public void GetRpcStatus_NotFound()
+    {
+        // Act
+        var exception = new RpcException(new Core.Status());
+
+        // Assert - the exception does not contain a RpcStatus
+        var sts = exception.GetRpcStatus();
+        Assert.IsNull(sts);
+    }
+
+    [Test]
+    public void GetRpcStatus_SetCodeAndMessage()
+    {
+        // Arrange and Act - create the exception with status code and message
+        var exception = status.ToRpcException(StatusCode.Aborted, "Different message");
+
+        // Assert - check the details in the exception
+        Assert.AreEqual(StatusCode.Aborted, exception.StatusCode);
+        Assert.AreEqual("Different message", exception.Status.Detail);
+        var sts = exception.GetRpcStatus();
+        Assert.IsNotNull(sts);
+        Assert.AreEqual(status, sts);
+    }
+}
diff --git a/test/Grpc.StatusProto.Tests/RpcStatusExtensionsTest.cs b/test/Grpc.StatusProto.Tests/RpcStatusExtensionsTest.cs
new file mode 100644
index 000000000..1d03e58c7
--- /dev/null
+++ b/test/Grpc.StatusProto.Tests/RpcStatusExtensionsTest.cs
@@ -0,0 +1,423 @@
+#region Copyright notice and license
+// Copyright 2023 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 Google.Protobuf;
+using Google.Protobuf.WellKnownTypes;
+using Google.Rpc;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests;
+
+/// 
+/// Tests for RpcStatusExtensions
+/// 
+[TestFixture]
+public class RpcStatusExtensionsTest
+{
+    [Test]
+    public void ToRpcExceptionTest()
+    {
+        // Arrange - create a status
+        var status = CreateFullStatus();
+        // Act - get exception from the status
+        var ex = status.ToRpcException();
+
+        // Assert - check the details in the exception
+        Assert.IsNotNull(ex);
+
+        var grpcSts = ex.Status;
+        Assert.AreEqual(StatusCode.ResourceExhausted, grpcSts.StatusCode);
+        Assert.AreEqual("Test", grpcSts.Detail);
+
+        var sts = ex.GetRpcStatus();
+        Assert.IsNotNull(sts);
+        Assert.AreEqual(status, sts);
+    }
+
+    [Test]
+    public void ToRpcExceptionWithParamsTest()
+    {
+        // Arrange - create a status
+        var status = CreateFullStatus();
+
+        // Act - get exception from the status with specific parameters
+        var ex = status.ToRpcException(StatusCode.Cancelled, "status message");
+        Assert.IsNotNull(ex);
+
+        // Assert - check the details in the exception
+        var grpcSts = ex.Status;
+        Assert.AreEqual(StatusCode.Cancelled, grpcSts.StatusCode);
+        Assert.AreEqual("status message", grpcSts.Detail);
+
+        var sts = ex.GetRpcStatus();
+        Assert.IsNotNull(sts);
+        Assert.AreEqual(status, sts);
+    }
+
+    [Test]
+    public void GetStatusDetailTest()
+    {
+        // Arrange - create a status
+        // The detailsMap contains all the Messages added to the status so
+        // these can be used in the comparisions when then are retrieved later
+        var detailsMap = new Dictionary();
+        var status = CreateFullStatus(detailsMap);
+
+        // Act
+        var badRequest = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(badRequest);
+        var expected = detailsMap["badRequest"];
+        Assert.AreEqual(expected, badRequest);
+
+        // Act
+        var errorInfo = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(errorInfo);
+        expected = detailsMap["errorInfo"];
+        Assert.AreEqual(expected, errorInfo);
+
+        // Act
+        var retryInfo = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(retryInfo);
+        expected = detailsMap["retryInfo"];
+        Assert.AreEqual(expected, retryInfo);
+
+        // Act
+        var debugInfo = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(debugInfo);
+        expected = detailsMap["debugInfo"];
+        Assert.AreEqual(expected, debugInfo);
+
+        // Act
+        var quotaFailure = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(quotaFailure);
+        expected = detailsMap["quotaFailure"];
+        Assert.AreEqual(expected, quotaFailure);
+
+        // Act
+        var preconditionFailure = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(preconditionFailure);
+        expected = detailsMap["preconditionFailure"];
+        Assert.AreEqual(expected, preconditionFailure);
+
+        // Act
+        var requestInfo = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(requestInfo);
+        expected = detailsMap["requestInfo"];
+        Assert.AreEqual(expected, requestInfo);
+
+        // Act
+        var help = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(help);
+        expected = detailsMap["help"];
+        Assert.AreEqual(expected, help);
+
+        // Act
+        var localizedMessage = status.GetDetail();
+        // Assert
+        Assert.IsNotNull(localizedMessage);
+        expected = detailsMap["localizedMessage"];
+        Assert.AreEqual(expected, localizedMessage);
+    }
+
+    [Test]
+    public void GetStatusDetail_NotFound()
+    {
+        // Arrange - create a status with only a few details
+        // The detailsMap contains all the Messages added to the status so
+        // these can be used in the comparisions when then are retrieved later
+        var detailsMap = new Dictionary();
+        var status = CreatePartialStatus(detailsMap);
+
+        // Act - try and retieve non-existent BadRequest from the status
+        var badRequest = status.GetDetail();
+        // Assert
+        Assert.IsNull(badRequest);
+    }
+
+    [Test]
+    public void UnpackDetailMessageTest()
+    {
+        // Arrange - create a status
+        // The detailsMap contains all the Messages added to the status so
+        // these can be used in the comparisions when then are retrieved later
+        var detailsMap = new Dictionary();
+        var status = CreateFullStatus(detailsMap);
+
+        // foundSet will contain the messages found in the status so we can
+        // check all those expected were present
+        var foundSet = new HashSet();
+
+        // Act and Assert - iterate over all the messages in the status
+        // and check they contain what is expected
+        foreach (var msg in status.UnpackDetailMessages())
+        {
+            switch (msg)
+            {
+                case ErrorInfo errorInfo:
+                    {
+                        var expected = detailsMap["errorInfo"];
+                        Assert.AreEqual(expected, errorInfo);
+                        foundSet.Add("errorInfo");
+                        break;
+                    }
+                    
+                case BadRequest badRequest:
+                    {
+                        var expected = detailsMap["badRequest"];
+                        Assert.AreEqual(expected, badRequest);
+                        foundSet.Add("badRequest");
+                        break;
+                    }
+
+                case RetryInfo retryInfo:
+                    {
+                        var expected = detailsMap["retryInfo"];
+                        Assert.AreEqual(expected, retryInfo);
+                        foundSet.Add("retryInfo");
+                        break;
+                    }
+
+                case DebugInfo debugInfo:
+                    {
+                        var expected = detailsMap["debugInfo"];
+                        Assert.AreEqual(expected, debugInfo);
+                        foundSet.Add("debugInfo");
+                        break;
+                    }
+
+                case QuotaFailure quotaFailure:
+                    {
+                        var expected = detailsMap["quotaFailure"];
+                        Assert.AreEqual(expected, quotaFailure);
+                        foundSet.Add("quotaFailure");
+                        break;
+                    }
+
+                case PreconditionFailure preconditionFailure:
+                    {
+                        var expected = detailsMap["preconditionFailure"];
+                        Assert.AreEqual(expected, preconditionFailure);
+                        foundSet.Add("preconditionFailure");
+                        break;
+                    }
+
+                case RequestInfo requestInfo:
+                    {
+                        var expected = detailsMap["requestInfo"];
+                        Assert.AreEqual(expected, requestInfo);
+                        foundSet.Add("requestInfo");
+                        break;
+                    }
+
+                case ResourceInfo resourceInfo:
+                    {
+                        var expected = detailsMap["resourceInfo"];
+                        Assert.AreEqual(expected, resourceInfo);
+                        foundSet.Add("resourceInfo");
+                        break;
+                    }
+
+                case Help help:
+                    {
+                        var expected = detailsMap["help"];
+                        Assert.AreEqual(expected, help);
+                        foundSet.Add("help");
+                        break;
+                    }
+
+                case LocalizedMessage localizedMessage:
+                    {
+                        var expected = detailsMap["localizedMessage"];
+                        Assert.AreEqual(expected, localizedMessage);
+                        foundSet.Add("localizedMessage");
+                        break;
+                    }
+            }
+        }
+
+        // check everything was returned
+        Assert.AreEqual(detailsMap.Count, foundSet.Count);
+
+    }
+
+    private static Google.Rpc.Status CreatePartialStatus(Dictionary? detailsMap = null)
+    {
+        var retryInfo = new RetryInfo
+        {
+            RetryDelay = Duration.FromTimeSpan(new TimeSpan(0, 0, 5))
+        };
+
+        var debugInfo = new DebugInfo()
+        {
+            StackEntries = { "stack1", "stack2" },
+            Detail = "detail"
+        };
+
+        // add details to a map for later checking
+        if (detailsMap != null)
+        {
+            detailsMap.Clear();
+            detailsMap.Add("retryInfo", retryInfo);
+            detailsMap.Add("debugInfo", debugInfo);
+        }
+
+        var status = new Google.Rpc.Status()
+        {
+            Code = (int)StatusCode.Unavailable,
+            Message = "partial status",
+            Details =
+            {
+                Any.Pack(retryInfo),
+                Any.Pack(debugInfo),
+            }
+        };
+
+        return status;
+    }
+
+    static Google.Rpc.Status CreateFullStatus(Dictionary? detailsMap = null)
+    {
+        var errorInfo = new ErrorInfo()
+        {
+            Domain = "Rich Error Model Demo",
+            Reason = "Full error requested in the demo",
+            Metadata =
+                {
+                    { "key1", "value1" },
+                    { "key2", "value2" }
+                }
+        };
+
+        var badRequest = new BadRequest()
+        {
+            FieldViolations =
+            {
+                new BadRequest.Types.FieldViolation()
+                {
+                    Field = "field", Description = "description"
+                }
+            }
+        };
+
+        var retryInfo = new RetryInfo
+        {
+            RetryDelay = Duration.FromTimeSpan(new TimeSpan(0, 0, 5))
+        };
+
+        var debugInfo = new DebugInfo()
+        {
+            StackEntries = { "stack1", "stack2" },
+            Detail = "detail"
+        };
+
+        var quotaFailure = new QuotaFailure()
+        {
+            Violations =
+            {
+                new QuotaFailure.Types.Violation()
+                {
+                    Description =  "Too much disk space used",
+                    Subject = "Disk23"
+                }
+            }
+        };
+
+        var preconditionFailure = new PreconditionFailure()
+        {
+            Violations =
+            {
+                new PreconditionFailure.Types.Violation()
+                {
+                    Type = "type", Subject = "subject", Description = "description"
+                }
+            }
+        };
+
+        var requestInfo = new RequestInfo()
+        {
+            RequestId = "reqId",
+            ServingData = "data"
+        };
+
+        var resourceInfo = new ResourceInfo()
+        {
+            ResourceType = "type",
+            ResourceName = "name",
+            Owner = "owner",
+            Description = "description"
+        };
+
+        var help = new Help()
+        {
+            Links =
+            {
+                new Help.Types.Link() { Url="url1", Description="desc1" },
+                new Help.Types.Link() { Url="url2", Description="desc2" },
+            }
+        };
+
+        var localizedMessage = new LocalizedMessage()
+        {
+            Locale = "en-GB",
+            Message = "Example localised error message"
+        };
+
+        // add details to a map for later checking
+        if (detailsMap != null)
+        {
+            detailsMap.Clear();
+            detailsMap.Add("badRequest", badRequest);
+            detailsMap.Add("errorInfo", errorInfo);
+            detailsMap.Add("retryInfo", retryInfo);
+            detailsMap.Add("debugInfo", debugInfo);
+            detailsMap.Add("quotaFailure", quotaFailure);
+            detailsMap.Add("preconditionFailure", preconditionFailure);
+            detailsMap.Add("requestInfo", requestInfo);
+            detailsMap.Add("resourceInfo", resourceInfo);
+            detailsMap.Add("help", help);
+            detailsMap.Add("localizedMessage", localizedMessage);
+        }
+
+        var status = new Google.Rpc.Status()
+        {
+            Code = (int)StatusCode.ResourceExhausted,
+            Message = "Test",
+            Details =
+            {
+                Any.Pack(badRequest),
+                Any.Pack(errorInfo),
+                Any.Pack(retryInfo),
+                Any.Pack(debugInfo),
+                Any.Pack(quotaFailure),
+                Any.Pack(preconditionFailure),
+                Any.Pack(requestInfo),
+                Any.Pack(resourceInfo),
+                Any.Pack(help),
+                Any.Pack(localizedMessage)
+            }
+        };
+
+        return status;
+    }
+}