Browse Source

Add detailed error example and error article (#2145)

Motivation:

We should document how service authors and clients should 
deal with errors and what options are available to them.

Modifications:

- Add an article explaining what the error mechanisms available
- Add an example

Result:

Better docs

---------

Co-authored-by: Gus Cairo <me@gustavocairo.com>
George Barnett 1 year ago
parent
commit
e431ed6ab8

+ 8 - 0
Examples/error-details/.gitignore

@@ -0,0 +1,8 @@
+.DS_Store
+/.build
+/Packages
+xcuserdata/
+DerivedData/
+.swiftpm/configuration/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc

+ 37 - 0
Examples/error-details/Package.swift

@@ -0,0 +1,37 @@
+// swift-tools-version:6.0
+/*
+ * Copyright 2024, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+
+import PackageDescription
+
+let package = Package(
+  name: "error-details",
+  platforms: [.macOS(.v15)],
+  dependencies: [
+    .package(url: "https://github.com/grpc/grpc-swift.git", branch: "main"),
+    .package(url: "https://github.com/grpc/grpc-swift-protobuf.git", branch: "main"),
+  ],
+  targets: [
+    .executableTarget(
+      name: "error-details",
+      dependencies: [
+        .product(name: "GRPCCore", package: "grpc-swift"),
+        .product(name: "GRPCInProcessTransport", package: "grpc-swift"),
+        .product(name: "GRPCProtobuf", package: "grpc-swift-protobuf"),
+      ]
+    )
+  ]
+)

+ 26 - 0
Examples/error-details/README.md

@@ -0,0 +1,26 @@
+# Detailed Error
+
+This example demonstrates how to create and unpack detailed errors.
+
+## Overview
+
+A command line tool that demonstrates how a detailed error can be thrown by a
+service and unpacked and inspected by a client. The detailed error model is
+described in more detailed in the [gRPC Error
+Guide](https://grpc.io/docs/guides/error/) and is made available via the
+[grpc-swift-protobuf](https://github.com/grpc-swift-protobuf) package.
+
+## Usage
+
+Build and run the example using the CLI:
+
+```console
+$ swift run
+Error code: resourceExhausted
+Error message: The greeter has temporarily run out of greetings.
+Error details:
+- Localized message (en-GB): Out of enthusiasm. The greeter is having a cup of tea, try again after that.
+- Localized message (en-US): Out of enthusiasm. The greeter is taking a coffee break, try again later.
+- Help links:
+   - https://en.wikipedia.org/wiki/Caffeine (A Wikipedia page about caffeine including its properties and effects.)
+```

+ 85 - 0
Examples/error-details/Sources/DetailedErrorExample.swift

@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024, gRPC Authors All rights reserved.
+ *
+ * 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.
+ */
+
+import GRPCCore
+import GRPCInProcessTransport
+import GRPCProtobuf
+
+@main
+struct DetailedErrorExample {
+  static func main() async throws {
+    let inProcess = InProcessTransport()
+    try await withGRPCServer(transport: inProcess.server, services: [Greeter()]) { server in
+      try await withGRPCClient(transport: inProcess.client) { client in
+        try await Self.doRPC(Helloworld_Greeter.Client(wrapping: client))
+      }
+    }
+  }
+
+  static func doRPC(_ greeter: Helloworld_Greeter.Client) async throws {
+    do {
+      let reply = try await greeter.sayHello(.with { $0.name = "(ignored)" })
+      print("Unexpected reply: \(reply.message)")
+    } catch let error as RPCError {
+      // Unpack the detailed from the standard 'RPCError'.
+      guard let status = try error.unpackGoogleRPCStatus() else { return }
+      print("Error code: \(status.code)")
+      print("Error message: \(status.message)")
+      print("Error details:")
+      for detail in status.details {
+        if let localizedMessage = detail.localizedMessage {
+          print("- Localized message (\(localizedMessage.locale)): \(localizedMessage.message)")
+        } else if let help = detail.help {
+          print("- Help links:")
+          for link in help.links {
+            print("   - \(link.url) (\(link.linkDescription))")
+          }
+        }
+      }
+    }
+  }
+}
+
+struct Greeter: Helloworld_Greeter.SimpleServiceProtocol {
+  func sayHello(
+    request: Helloworld_HelloRequest,
+    context: ServerContext
+  ) async throws -> Helloworld_HelloReply {
+    // Always throw a detailed error.
+    throw GoogleRPCStatus(
+      code: .resourceExhausted,
+      message: "The greeter has temporarily run out of greetings.",
+      details: [
+        .localizedMessage(
+          locale: "en-GB",
+          message: "Out of enthusiasm. The greeter is having a cup of tea, try again after that."
+        ),
+        .localizedMessage(
+          locale: "en-US",
+          message: "Out of enthusiasm. The greeter is taking a coffee break, try again later."
+        ),
+        .help(
+          links: [
+            ErrorDetails.Help.Link(
+              url: "https://en.wikipedia.org/wiki/Caffeine",
+              description: "A Wikipedia page about caffeine including its properties and effects."
+            )
+          ]
+        ),
+      ]
+    )
+  }
+}

+ 362 - 0
Examples/error-details/Sources/Generated/helloworld.grpc.swift

@@ -0,0 +1,362 @@
+/// Copyright 2015 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.
+
+// DO NOT EDIT.
+// swift-format-ignore-file
+//
+// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
+// Source: helloworld.proto
+//
+// For information on using the generated types, please see the documentation:
+//   https://github.com/grpc/grpc-swift
+
+import GRPCCore
+import GRPCProtobuf
+
+// MARK: - helloworld.Greeter
+
+/// Namespace containing generated types for the "helloworld.Greeter" service.
+internal enum Helloworld_Greeter {
+    /// Service descriptor for the "helloworld.Greeter" service.
+    internal static let descriptor = GRPCCore.ServiceDescriptor(fullyQualifiedService: "helloworld.Greeter")
+    /// Namespace for method metadata.
+    internal enum Method {
+        /// Namespace for "SayHello" metadata.
+        internal enum SayHello {
+            /// Request type for "SayHello".
+            internal typealias Input = Helloworld_HelloRequest
+            /// Response type for "SayHello".
+            internal typealias Output = Helloworld_HelloReply
+            /// Descriptor for "SayHello".
+            internal static let descriptor = GRPCCore.MethodDescriptor(
+                service: GRPCCore.ServiceDescriptor(fullyQualifiedService: "helloworld.Greeter"),
+                method: "SayHello"
+            )
+        }
+        /// Descriptors for all methods in the "helloworld.Greeter" service.
+        internal static let descriptors: [GRPCCore.MethodDescriptor] = [
+            SayHello.descriptor
+        ]
+    }
+}
+
+extension GRPCCore.ServiceDescriptor {
+    /// Service descriptor for the "helloworld.Greeter" service.
+    internal static let helloworld_Greeter = GRPCCore.ServiceDescriptor(fullyQualifiedService: "helloworld.Greeter")
+}
+
+// MARK: helloworld.Greeter (server)
+
+extension Helloworld_Greeter {
+    /// Streaming variant of the service protocol for the "helloworld.Greeter" service.
+    ///
+    /// This protocol is the lowest-level of the service protocols generated for this service
+    /// giving you the most flexibility over the implementation of your service. This comes at
+    /// the cost of more verbose and less strict APIs. Each RPC requires you to implement it in
+    /// terms of a request stream and response stream. Where only a single request or response
+    /// message is expected, you are responsible for enforcing this invariant is maintained.
+    ///
+    /// Where possible, prefer using the stricter, less-verbose ``ServiceProtocol``
+    /// or ``SimpleServiceProtocol`` instead.
+    ///
+    /// > Source IDL Documentation:
+    /// >
+    /// > The greeting service definition.
+    internal protocol StreamingServiceProtocol: GRPCCore.RegistrableRPCService {
+        /// Handle the "SayHello" method.
+        ///
+        /// > Source IDL Documentation:
+        /// >
+        /// > Sends a greeting
+        ///
+        /// - Parameters:
+        ///   - request: A streaming request of `Helloworld_HelloRequest` messages.
+        ///   - context: Context providing information about the RPC.
+        /// - Throws: Any error which occurred during the processing of the request. Thrown errors
+        ///     of type `RPCError` are mapped to appropriate statuses. All other errors are converted
+        ///     to an internal error.
+        /// - Returns: A streaming response of `Helloworld_HelloReply` messages.
+        func sayHello(
+            request: GRPCCore.StreamingServerRequest<Helloworld_HelloRequest>,
+            context: GRPCCore.ServerContext
+        ) async throws -> GRPCCore.StreamingServerResponse<Helloworld_HelloReply>
+    }
+
+    /// Service protocol for the "helloworld.Greeter" service.
+    ///
+    /// This protocol is higher level than ``StreamingServiceProtocol`` but lower level than
+    /// the ``SimpleServiceProtocol``, it provides access to request and response metadata and
+    /// trailing response metadata. If you don't need these then consider using
+    /// the ``SimpleServiceProtocol``. If you need fine grained control over your RPCs then
+    /// use ``StreamingServiceProtocol``.
+    ///
+    /// > Source IDL Documentation:
+    /// >
+    /// > The greeting service definition.
+    internal protocol ServiceProtocol: Helloworld_Greeter.StreamingServiceProtocol {
+        /// Handle the "SayHello" method.
+        ///
+        /// > Source IDL Documentation:
+        /// >
+        /// > Sends a greeting
+        ///
+        /// - Parameters:
+        ///   - request: A request containing a single `Helloworld_HelloRequest` message.
+        ///   - context: Context providing information about the RPC.
+        /// - Throws: Any error which occurred during the processing of the request. Thrown errors
+        ///     of type `RPCError` are mapped to appropriate statuses. All other errors are converted
+        ///     to an internal error.
+        /// - Returns: A response containing a single `Helloworld_HelloReply` message.
+        func sayHello(
+            request: GRPCCore.ServerRequest<Helloworld_HelloRequest>,
+            context: GRPCCore.ServerContext
+        ) async throws -> GRPCCore.ServerResponse<Helloworld_HelloReply>
+    }
+
+    /// Simple service protocol for the "helloworld.Greeter" service.
+    ///
+    /// This is the highest level protocol for the service. The API is the easiest to use but
+    /// doesn't provide access to request or response metadata. If you need access to these
+    /// then use ``ServiceProtocol`` instead.
+    ///
+    /// > Source IDL Documentation:
+    /// >
+    /// > The greeting service definition.
+    internal protocol SimpleServiceProtocol: Helloworld_Greeter.ServiceProtocol {
+        /// Handle the "SayHello" method.
+        ///
+        /// > Source IDL Documentation:
+        /// >
+        /// > Sends a greeting
+        ///
+        /// - Parameters:
+        ///   - request: A `Helloworld_HelloRequest` message.
+        ///   - context: Context providing information about the RPC.
+        /// - Throws: Any error which occurred during the processing of the request. Thrown errors
+        ///     of type `RPCError` are mapped to appropriate statuses. All other errors are converted
+        ///     to an internal error.
+        /// - Returns: A `Helloworld_HelloReply` to respond with.
+        func sayHello(
+            request: Helloworld_HelloRequest,
+            context: GRPCCore.ServerContext
+        ) async throws -> Helloworld_HelloReply
+    }
+}
+
+// Default implementation of 'registerMethods(with:)'.
+extension Helloworld_Greeter.StreamingServiceProtocol {
+    internal func registerMethods(with router: inout GRPCCore.RPCRouter) {
+        router.registerHandler(
+            forMethod: Helloworld_Greeter.Method.SayHello.descriptor,
+            deserializer: GRPCProtobuf.ProtobufDeserializer<Helloworld_HelloRequest>(),
+            serializer: GRPCProtobuf.ProtobufSerializer<Helloworld_HelloReply>(),
+            handler: { request, context in
+                try await self.sayHello(
+                    request: request,
+                    context: context
+                )
+            }
+        )
+    }
+}
+
+// Default implementation of streaming methods from 'StreamingServiceProtocol'.
+extension Helloworld_Greeter.ServiceProtocol {
+    internal func sayHello(
+        request: GRPCCore.StreamingServerRequest<Helloworld_HelloRequest>,
+        context: GRPCCore.ServerContext
+    ) async throws -> GRPCCore.StreamingServerResponse<Helloworld_HelloReply> {
+        let response = try await self.sayHello(
+            request: GRPCCore.ServerRequest(stream: request),
+            context: context
+        )
+        return GRPCCore.StreamingServerResponse(single: response)
+    }
+}
+
+// Default implementation of methods from 'ServiceProtocol'.
+extension Helloworld_Greeter.SimpleServiceProtocol {
+    internal func sayHello(
+        request: GRPCCore.ServerRequest<Helloworld_HelloRequest>,
+        context: GRPCCore.ServerContext
+    ) async throws -> GRPCCore.ServerResponse<Helloworld_HelloReply> {
+        return GRPCCore.ServerResponse<Helloworld_HelloReply>(
+            message: try await self.sayHello(
+                request: request.message,
+                context: context
+            ),
+            metadata: [:]
+        )
+    }
+}
+
+// MARK: helloworld.Greeter (client)
+
+extension Helloworld_Greeter {
+    /// Generated client protocol for the "helloworld.Greeter" service.
+    ///
+    /// You don't need to implement this protocol directly, use the generated
+    /// implementation, ``Client``.
+    ///
+    /// > Source IDL Documentation:
+    /// >
+    /// > The greeting service definition.
+    internal protocol ClientProtocol: Sendable {
+        /// Call the "SayHello" method.
+        ///
+        /// > Source IDL Documentation:
+        /// >
+        /// > Sends a greeting
+        ///
+        /// - Parameters:
+        ///   - request: A request containing a single `Helloworld_HelloRequest` message.
+        ///   - serializer: A serializer for `Helloworld_HelloRequest` messages.
+        ///   - deserializer: A deserializer for `Helloworld_HelloReply` messages.
+        ///   - options: Options to apply to this RPC.
+        ///   - handleResponse: A closure which handles the response, the result of which is
+        ///       returned to the caller. Returning from the closure will cancel the RPC if it
+        ///       hasn't already finished.
+        /// - Returns: The result of `handleResponse`.
+        func sayHello<Result>(
+            request: GRPCCore.ClientRequest<Helloworld_HelloRequest>,
+            serializer: some GRPCCore.MessageSerializer<Helloworld_HelloRequest>,
+            deserializer: some GRPCCore.MessageDeserializer<Helloworld_HelloReply>,
+            options: GRPCCore.CallOptions,
+            onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Helloworld_HelloReply>) async throws -> Result
+        ) async throws -> Result where Result: Sendable
+    }
+
+    /// Generated client for the "helloworld.Greeter" service.
+    ///
+    /// The ``Client`` provides an implementation of ``ClientProtocol`` which wraps
+    /// a `GRPCCore.GRPCCClient`. The underlying `GRPCClient` provides the long-lived
+    /// means of communication with the remote peer.
+    ///
+    /// > Source IDL Documentation:
+    /// >
+    /// > The greeting service definition.
+    internal struct Client: ClientProtocol {
+        private let client: GRPCCore.GRPCClient
+
+        /// Creates a new client wrapping the provided `GRPCCore.GRPCClient`.
+        ///
+        /// - Parameters:
+        ///   - client: A `GRPCCore.GRPCClient` providing a communication channel to the service.
+        internal init(wrapping client: GRPCCore.GRPCClient) {
+            self.client = client
+        }
+
+        /// Call the "SayHello" method.
+        ///
+        /// > Source IDL Documentation:
+        /// >
+        /// > Sends a greeting
+        ///
+        /// - Parameters:
+        ///   - request: A request containing a single `Helloworld_HelloRequest` message.
+        ///   - serializer: A serializer for `Helloworld_HelloRequest` messages.
+        ///   - deserializer: A deserializer for `Helloworld_HelloReply` messages.
+        ///   - options: Options to apply to this RPC.
+        ///   - handleResponse: A closure which handles the response, the result of which is
+        ///       returned to the caller. Returning from the closure will cancel the RPC if it
+        ///       hasn't already finished.
+        /// - Returns: The result of `handleResponse`.
+        internal func sayHello<Result>(
+            request: GRPCCore.ClientRequest<Helloworld_HelloRequest>,
+            serializer: some GRPCCore.MessageSerializer<Helloworld_HelloRequest>,
+            deserializer: some GRPCCore.MessageDeserializer<Helloworld_HelloReply>,
+            options: GRPCCore.CallOptions = .defaults,
+            onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Helloworld_HelloReply>) async throws -> Result = { response in
+                try response.message
+            }
+        ) async throws -> Result where Result: Sendable {
+            try await self.client.unary(
+                request: request,
+                descriptor: Helloworld_Greeter.Method.SayHello.descriptor,
+                serializer: serializer,
+                deserializer: deserializer,
+                options: options,
+                onResponse: handleResponse
+            )
+        }
+    }
+}
+
+// Helpers providing default arguments to 'ClientProtocol' methods.
+extension Helloworld_Greeter.ClientProtocol {
+    /// Call the "SayHello" method.
+    ///
+    /// > Source IDL Documentation:
+    /// >
+    /// > Sends a greeting
+    ///
+    /// - Parameters:
+    ///   - request: A request containing a single `Helloworld_HelloRequest` message.
+    ///   - options: Options to apply to this RPC.
+    ///   - handleResponse: A closure which handles the response, the result of which is
+    ///       returned to the caller. Returning from the closure will cancel the RPC if it
+    ///       hasn't already finished.
+    /// - Returns: The result of `handleResponse`.
+    internal func sayHello<Result>(
+        request: GRPCCore.ClientRequest<Helloworld_HelloRequest>,
+        options: GRPCCore.CallOptions = .defaults,
+        onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Helloworld_HelloReply>) async throws -> Result = { response in
+            try response.message
+        }
+    ) async throws -> Result where Result: Sendable {
+        try await self.sayHello(
+            request: request,
+            serializer: GRPCProtobuf.ProtobufSerializer<Helloworld_HelloRequest>(),
+            deserializer: GRPCProtobuf.ProtobufDeserializer<Helloworld_HelloReply>(),
+            options: options,
+            onResponse: handleResponse
+        )
+    }
+}
+
+// Helpers providing sugared APIs for 'ClientProtocol' methods.
+extension Helloworld_Greeter.ClientProtocol {
+    /// Call the "SayHello" method.
+    ///
+    /// > Source IDL Documentation:
+    /// >
+    /// > Sends a greeting
+    ///
+    /// - Parameters:
+    ///   - message: request message to send.
+    ///   - metadata: Additional metadata to send, defaults to empty.
+    ///   - options: Options to apply to this RPC, defaults to `.defaults`.
+    ///   - handleResponse: A closure which handles the response, the result of which is
+    ///       returned to the caller. Returning from the closure will cancel the RPC if it
+    ///       hasn't already finished.
+    /// - Returns: The result of `handleResponse`.
+    internal func sayHello<Result>(
+        _ message: Helloworld_HelloRequest,
+        metadata: GRPCCore.Metadata = [:],
+        options: GRPCCore.CallOptions = .defaults,
+        onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse<Helloworld_HelloReply>) async throws -> Result = { response in
+            try response.message
+        }
+    ) async throws -> Result where Result: Sendable {
+        let request = GRPCCore.ClientRequest<Helloworld_HelloRequest>(
+            message: message,
+            metadata: metadata
+        )
+        return try await self.sayHello(
+            request: request,
+            options: options,
+            onResponse: handleResponse
+        )
+    }
+}

+ 129 - 0
Examples/error-details/Sources/Generated/helloworld.pb.swift

@@ -0,0 +1,129 @@
+// DO NOT EDIT.
+// swift-format-ignore-file
+// swiftlint:disable all
+//
+// Generated by the Swift generator plugin for the protocol buffer compiler.
+// Source: helloworld.proto
+//
+// For information on using the generated types, please see the documentation:
+//   https://github.com/apple/swift-protobuf/
+
+/// Copyright 2015 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.
+
+import SwiftProtobuf
+
+// If the compiler emits an error on this type, it is because this file
+// was generated by a version of the `protoc` Swift plug-in that is
+// incompatible with the version of SwiftProtobuf to which you are linking.
+// Please ensure that you are building against the same version of the API
+// that was used to generate this file.
+fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
+  struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
+  typealias Version = _2
+}
+
+/// The request message containing the user's name.
+struct Helloworld_HelloRequest: Sendable {
+  // SwiftProtobuf.Message conformance is added in an extension below. See the
+  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
+  // methods supported on all messages.
+
+  var name: String = String()
+
+  var unknownFields = SwiftProtobuf.UnknownStorage()
+
+  init() {}
+}
+
+/// The response message containing the greetings
+struct Helloworld_HelloReply: Sendable {
+  // SwiftProtobuf.Message conformance is added in an extension below. See the
+  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
+  // methods supported on all messages.
+
+  var message: String = String()
+
+  var unknownFields = SwiftProtobuf.UnknownStorage()
+
+  init() {}
+}
+
+// MARK: - Code below here is support for the SwiftProtobuf runtime.
+
+fileprivate let _protobuf_package = "helloworld"
+
+extension Helloworld_HelloRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
+  static let protoMessageName: String = _protobuf_package + ".HelloRequest"
+  static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
+    1: .same(proto: "name"),
+  ]
+
+  mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
+    while let fieldNumber = try decoder.nextFieldNumber() {
+      // The use of inline closures is to circumvent an issue where the compiler
+      // allocates stack space for every case branch when no optimizations are
+      // enabled. https://github.com/apple/swift-protobuf/issues/1034
+      switch fieldNumber {
+      case 1: try { try decoder.decodeSingularStringField(value: &self.name) }()
+      default: break
+      }
+    }
+  }
+
+  func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
+    if !self.name.isEmpty {
+      try visitor.visitSingularStringField(value: self.name, fieldNumber: 1)
+    }
+    try unknownFields.traverse(visitor: &visitor)
+  }
+
+  static func ==(lhs: Helloworld_HelloRequest, rhs: Helloworld_HelloRequest) -> Bool {
+    if lhs.name != rhs.name {return false}
+    if lhs.unknownFields != rhs.unknownFields {return false}
+    return true
+  }
+}
+
+extension Helloworld_HelloReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
+  static let protoMessageName: String = _protobuf_package + ".HelloReply"
+  static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
+    1: .same(proto: "message"),
+  ]
+
+  mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
+    while let fieldNumber = try decoder.nextFieldNumber() {
+      // The use of inline closures is to circumvent an issue where the compiler
+      // allocates stack space for every case branch when no optimizations are
+      // enabled. https://github.com/apple/swift-protobuf/issues/1034
+      switch fieldNumber {
+      case 1: try { try decoder.decodeSingularStringField(value: &self.message) }()
+      default: break
+      }
+    }
+  }
+
+  func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
+    if !self.message.isEmpty {
+      try visitor.visitSingularStringField(value: self.message, fieldNumber: 1)
+    }
+    try unknownFields.traverse(visitor: &visitor)
+  }
+
+  static func ==(lhs: Helloworld_HelloReply, rhs: Helloworld_HelloReply) -> Bool {
+    if lhs.message != rhs.message {return false}
+    if lhs.unknownFields != rhs.unknownFields {return false}
+    return true
+  }
+}

+ 83 - 0
Sources/GRPCCore/Documentation.docc/Articles/Errors.md

@@ -0,0 +1,83 @@
+# Errors
+
+Learn about the different error mechanisms in gRPC and how to use them.
+
+## Overview
+
+gRPC has a well defined error model for RPCs and a common extension to provide
+richer errors when using Protocol Buffers. This article explains both mechanisms
+and offers advice on using and handling RPC errors for service authors and
+clients.
+
+### Error models
+
+gRPC has two widely used error models:
+
+1. A 'standard' error model supported by all client/server gRPC libraries.
+2. A 'rich' error model providing more detailed error information via serialized
+   Protocol Buffers messages.
+
+#### Standard error model
+
+In gRPC the outcome of every RPC is represented by a status made up of a code
+and a message. The status is propagated from the server to the client in the
+metadata as the final part of an RPC indicating the outcome of the RPC.
+
+You can find more information about the error codes in ``RPCError/Code`` and in
+the status codes guide on the
+[gRPC website](https://grpc.io/docs/guides/status-codes/).
+
+This mechanism is part of the gRPC protocol and is supported by all client/server
+gRPC libraries regardless of the data format (e.g. Protocol Buffers) being used
+for messages.
+
+#### Rich error model
+
+The standard error model is quite limited and doesn't include the ability to
+communicate details about the error. If you're using the Protocol Buffers data
+format for messages then you may wish to use the "rich" error model.
+
+The model was developed and used by Google and is described in more detail
+in the [gRPC error guide](https://grpc.io/docs/guides/error/) and
+[Google AIP-193](https://google.aip.dev/193).
+
+While not officially part of gRPC it's a widely used convention with support in
+various client/server gRPC libraries, including gRPC Swift.
+
+It specifies a standard set of error message types covering the most common
+situations. The error details are encoded as protobuf messages in the trailing
+metadata of an RPC. Clients are able to deserialize and access the details as
+type-safe structured messages should they need to.
+
+### User guide
+
+Learn how to use both models in gRPC Swift.
+
+#### Service authors
+
+Errors thrown from an RPC handler are caught by the gRPC runtime and turned into
+a status. You have a two options to ensure that an appropriate status is sent to
+the client if your RPC handler throws an error:
+
+1. Throw an ``RPCError`` which explicitly sets the desired status code and
+   message.
+2. Throw an error conforming to ``RPCErrorConvertible`` which the gRPC runtime
+   will use to create an ``RPCError``.
+
+Any errors thrown which don't fall into these categories will result in a status
+code of `unknown` being sent to the client.
+
+Generally speaking expected failure scenarios should be considered as part of
+the API contract and each RPC should be documented accordingly.
+
+#### Clients
+
+Clients should catch ``RPCError`` if they are interested in the failures from an
+RPC. This is a manifestation of the error sent by the server but in some cases
+it may be synthesized locally.
+
+For clients using the rich error model, the ``RPCError`` can be caught and a
+detailed error can be extracted from it using `unpackGoogleRPCStatus()`.
+
+See [`error-details`](https://github.com/grpc/grpc-swift/tree/main/Examples/error-details) for
+an example.

+ 1 - 0
Sources/GRPCCore/Documentation.docc/Documentation.md

@@ -52,6 +52,7 @@ as tutorials.
 ### Essentials
 
 - <doc:Generating-stubs>
+- <doc:Errors>
 
 ### Project Information
 

+ 9 - 0
dev/protos/generate.sh

@@ -97,6 +97,14 @@ function generate_routeguide_example {
   generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal"
 }
 
+function generate_error_details_example {
+  local proto="$here/upstream/grpc/examples/helloworld.proto"
+  local output="$root/Examples/error-details/Sources/Generated"
+
+  generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal"
+  generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal"
+}
+
 #- TESTS ----------------------------------------------------------------------
 
 function generate_service_config_for_tests {
@@ -119,6 +127,7 @@ function generate_service_config_for_tests {
 generate_echo_example
 generate_helloworld_example
 generate_routeguide_example
+generate_error_details_example
 
 # Tests
 generate_service_config_for_tests