// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only

/*!
    \example clientguide
    \title Qt GRPC Client Guide
    \ingroup qtgrpc-examples
    \examplecategory {Networking}
    \meta tag {network,protobuf,grpc,serialization,overview}
    \brief The Qt GRPC client guide.

    \section1 Service Methods

    In \gRPC, service methods can be defined in a protobuf schema to specify the
    communication between clients and servers. The protobuf compiler,
    \c{protoc}, can then generate the required server and client interfaces
    based on these definitions. \gRPC supports four types of service methods:

    \list
        \li \l{Unary Calls} — The client sends a single request and receives a
            single response.
            \badcode
                rpc UnaryCall (Request) returns (Response);
            \endcode
            The corresponding client handler is QGrpcCallReply.
        \li \l{Server Streaming} — The client sends a single request and
            receives multiple responses.
            \badcode
                rpc ServerStreaming (Request) returns (stream Response);
            \endcode
            The corresponding client handler is QGrpcServerStream.
        \li \l{Client Streaming} — The client sends multiple requests and
            receives a single response.
            \badcode
                rpc ClientStreaming (stream Request) returns (Response);
            \endcode
            The corresponding client handler is QGrpcClientStream.
        \li \l{Bidirectional Streaming} — The client and server exchange
            multiple messages.
            \badcode
                rpc BidirectionalStreaming (stream Request) returns (stream Response);
            \endcode
            The corresponding client handler is QGrpcBidiStream.
    \endlist

    \gRPC communication always starts with the client, which initiates the
    \l{https://en.wikipedia.org/wiki/Remote_procedure_call}{remote procedure
    call} (RPC) by sending the first message to the server. The server then
    concludes any type of communication by returning a \l{QtGrpc::}
    {StatusCode}.

    All client RPC handlers are derived from the QGrpcOperation class, which
    provides shared functionality. Due to the asynchronous nature of RPCs, they
    are naturally managed through Qt's \l{Signals & Slots} mechanism.

    A key signal common to all RPC handlers is \l{QGrpcOperation::} {finished},
    which indicates the completion of an RPC. The handler emits this signal
    exactly once during its lifetime. This signal delivers the corresponding
    QGrpcStatus, providing additional information about the success or failure
    of the RPC.

    There are also operation-specific functionalities, such as \l{QGrpcServerStream::}
    {messageReceived} for incoming messages, \l{QGrpcClientStream::}
    {writeMessage} for sending messages to the server, and
    \l{QGrpcBidiStream::} {writesDone} for closing client-side communication.
    The table below outlines the supported functionality of the RPC client
    handlers:

    \table 85 %
    \header
        \li Functionality
        \li QGrpcCallReply
        \li QGrpcServerStream
        \li QGrpcClientStream
        \li QGrpcBidiStream
    \row
        \li \l {QGrpcOperation::}{finished}
        \li ✓ (\l{QGrpcOperation::}{read} final response)
        \li ✓
        \li ✓ (\l{QGrpcOperation::}{read} final response)
        \li ✓
    \row
        \li \l {QGrpcServerStream::}{messageReceived}
        \li ✗
        \li ✓
        \li ✗
        \li ✓
    \row
        \li \l {QGrpcClientStream::}{writeMessage}
        \li ✗
        \li ✗
        \li ✓
        \li ✓
    \row
        \li \l {QGrpcBidiStream::}{writesDone}
        \li ✗
        \li ✗
        \li ✓
        \li ✓
    \endtable

    \section1 Getting Started

    To use the Qt GRPC C++ API, start by using an already available protobuf
    schema or define your own. We will use the \c {clientguide.proto} file as
    an example:

    \snippet clientguide/proto/clientguide.proto 0

    To use this \e {.proto} file for our Qt GRPC client in C++, we must run
    the \c protoc compiler with the Qt generator plugins on it. Fortunately, Qt
    provides the \l{qt_add_grpc} and \l{qt_add_protobuf} CMake functions to
    streamline this process.

    \snippet clientguide/client/CMakeLists.txt 0

    This results in two header files being generated in the current build
    directory:
    \list
        \li \e {clientguide.qpb.h}: Generated by \l{The qtprotobufgen
            Tool}{qtprotobufgen}. Declares the \c Request and \c Response
            protobuf messages from the schema.
        \li \e {clientguide_client.grpc.qpb.h}: Generated by \l{The qtgrpcgen
            Tool}{qtgrpcgen}. Declares the client interface for calling the
            methods of a \gRPC server implementing the \c ClientGuideService
            from the schema.
    \endlist

    The following client interface is generated:

    \code
    namespace client::guide {
    namespace ClientGuideService {

    class Client : public QGrpcClientBase
    {
        ...
        std::unique_ptr<QGrpcCallReply> UnaryCall(const client::guide::Request &arg);
        std::unique_ptr<QGrpcServerStream> ServerStreaming(const client::guide::Request &arg);
        std::unique_ptr<QGrpcClientStream> ClientStreaming(const client::guide::Request &arg);
        std::unique_ptr<QGrpcBidiStream> BidirectionalStreaming(const client::guide::Request &arg);
        ...
    };

    } // namespace ClientGuideService
    } // namespace client::guide
    \endcode

    \include qtgrpc-shared.qdocinc rpc-lifetime-note

    \section2 Server Setup

    The server implementation for the \c ClientGuideService follows a
    straightforward approach. It validates the request message's \c time field,
    returning the \c INVALID_ARGUMENT status code if the time is in the future:

    \snippet clientguide/server/main.cpp time

    Additionally, the server sets the current time in every response message:

    \snippet clientguide/server/main.cpp response

    For valid \c time requests, the service methods behave as follows:

    \list
        \li \c{UnaryCall}: Responds with the \c num field from the request.
        \li \c{ServerStreaming}: Sends \c num responses matching the request
            message.
        \li \c{ClientStreaming}: Counts the number of request messages and sets
            this count as \c num.
        \li \c{BidirectionalStreaming}: Immediately responds with the \c num
            field from each incoming request message.
    \endlist

    \section2 Client Setup

    We begin by including the generated header files:

    \snippet clientguide/client/main.cpp gen-includes

    For this example, we create the \c ClientGuide class to manage all
    communication, making it easier to follow. We begin by setting up the
    backbone of all \gRPC communication: a channel.

    \snippet clientguide/client/main.cpp basic-0a
    \snippet clientguide/client/main.cpp basic-0b

    The Qt GRPC library offers QGrpcHttp2Channel, which you can
    \l{QGrpcClientBase::attachChannel} {attach} to the generated
    client interface:

    \snippet clientguide/client/main.cpp basic-1

    With this setup, the client will communicate over HTTP/2 using TCP as the
    transport protocol. The communication will be unencrypted (i.e. without
    SSL/TLS setup).

    \section3 Creating a request message

    Here's a simple wrapper to create request messages:

    \snippet clientguide/client/main.cpp basic-2

    This function takes an integer and an optional boolean. By default its
    messages use the current time, so the \l{Server Setup}{server logic} should
    accept them. When called with \c fail set to \c true, however, it produces
    messages that the server shall reject.

    \section2 Single Shot RPCs

    There are different paradigms for working with RPC client handlers.
    Specifically, you can choose a class-based design where the RPC handler is
    a member of the enclosing class, or you can manage the lifetime of the RPC
    handler through the \l {QGrpcOperation::} {finished} signal.

    There are two important things to remember when applying the single-shot
    paradigm. The code below demonstrates how it would work for unary calls,
    but it's the same for any other RPC type.

    \code
        std::unique_ptr<QGrpcCallReply> reply = m_client.UnaryCall(requestMessage);
        const auto *replyPtr = reply.get(); // 1
        QObject::connect(
            replyPtr, &QGrpcCallReply::finished, replyPtr,
            [reply = std::move(reply)](const QGrpcStatus &status) {
                ...
            },
            Qt::SingleShotConnection        // 2
        );
    \endcode

    \list
        \li \b{1}: Since we manage the lifetime of the unique RPC object within
            the lambda, moving it into the lambda's capture would invalidate \c
            {get()} and other member functions. Therefore, we must copy the
            pointers address before moving it.
        \li \b{2}: The \l{QGrpcOperation::}{finished} signal is emitted only
            once, making this a true single-shot connection. It is \b{important}
            to mark this connection as \l{Qt::}{SingleShotConnection}! If not,
            the capture of \c reply will not be destroyed, leading to a \b
            {hidden memory leak} that is hard to discover.
    \endlist

    The \l{Qt::}{SingleShotConnection} argument in the \c{connect} call ensures
    that the slot functor (the lambda) is destroyed after being emitted,
    freeing the resources associated with the slot, including its captures.

    \section1 Remote Procedure Calls

    \section2 Unary Calls

    Unary calls require only the \l {QGrpcOperation::} {finished} signal to be
    handled. When this signal is emitted, we can check the \l {QGrpcStatus}
    {status} of the RPC to determine if it was successful. If it was, we can \l
    {QGrpcOperation::} {read} the single and final response from the server.

    In this example, we use the single-shot paradigm. Ensure you carefully read
    the \l {Single Shot RPCs} section.

    \snippet clientguide/client/main.cpp unary-0

    The function starts the RPC by invoking the \c UnaryCall member function
    of the generated client interface \c m_client. The lifetime is solely
    managed by the \l{QGrpcCallReply::} {finished} signal.

    \details {Running the code}
        In \c main, we simply invoke this function three times, letting the
        second invocation fail:

        \snippet clientguide/client/main.cpp unary-1

        A possible output of running this could look like the following:

        \badcode
            Welcome to the clientguide!
            Starting the server process ...
                Server listening on: localhost:50056
                Server (UnaryCall): Request( time: 1733498584776, num: 1 )
                Server (UnaryCall): Request( time: 9223372036854775807, num: 2 )
                Server (UnaryCall): Request( time: 1733498584776, num: 3 )
            Client (UnaryCall) finished, received: Response( time:  1733498584778257 , num:  1  )
            Client (UnaryCall) failed: QGrpcStatus( code: QtGrpc::StatusCode::InvalidArgument, message: "Request time is in the future!" )
            Client (UnaryCall) finished, received: Response( time:  1733498584778409 , num:  3  )
        \endcode

        We see the server receiving the three messages, with the second
        containing a large value for its time. On the client side, the first
        and last calls returned an \l {QtGrpc::StatusCode::} {Ok} status code,
        but the second message failed with the \l {QtGrpc::StatusCode::}
        {InvalidArgument} status code due to the message time being in the future.
    \enddetails

    \section2 Server Streaming

    In a server stream, the client sends an initial request, and the server
    responds with one or more messages. In addition to the \l{QGrpcOperation::}
    {finished} signal you also have to handle the \l{QGrpcServerStream::}
    {messageReceived} signal.

    In this example, we use the single-shot paradigm to manage the streaming RPC
    lifecycle. Ensure you carefully read the \l {Single Shot RPCs} section.

    As with any RPC, we connect to the \l{QGrpcOperation::} {finished} signal
    first:

    \snippet clientguide/client/main.cpp sstream-0

    To handle the server messages, we connect to the \l{QGrpcServerStream::}
    {messageReceived} signal and \l{QGrpcOperation::} {read} the response when
    the signal is emitted.

    \snippet clientguide/client/main.cpp sstream-1

    \details {Running the code}
        The server logic streams back the amount received in the initial
        request to the client. We create such a request and invoke the
        function.

        \snippet clientguide/client/main.cpp sstream-2

        A possible output of running the server streaming could look like this:

        \badcode
            Welcome to the clientguide!
            Starting the server process ...
                Server listening on: localhost:50056
                Server (ServerStreaming): Request( time: 1733504435800, num: 3 )
            Client (ServerStream) received: Response( time:  1733504435801724 , num:  0  )
            Client (ServerStream) received: Response( time:  1733504435801871 , num:  1  )
            Client (ServerStream) received: Response( time:  1733504435801913 , num:  2  )
            Client (ServerStreaming) finished
        \endcode

        Once the server starts, it receives a request with a \e num value of 3
        and responds with three \c Response messages before completing the
        communication.
    \enddetails

    \section2 Client Streaming

    In a client stream, the client sends one or more requests, and the server
    responds with a single final response. The \l {QGrpcOperation::} {finished}
    signal must be handled, and messages can be sent using the
    \l{QGrpcClientStream::} {writeMessage} function. The
    \l{QGrpcClientStream::} {writesDone} function can then be used to indicate
    that the client has finished writing and that no more messages will be
    sent.

    We use a class-based approach to interact with the streaming RPC,
    incorporating the handler as a member of the class. As with any RPC, we
    connect to the \l {QGrpcOperation::} {finished} signal:

    \snippet clientguide/client/main.cpp cstream-0

    The function starts the client stream with an initial message. Then it
    continues to write two additional messages before signaling the end of
    communication by calling \l{QGrpcClientStream::}{writesDone}. If the
    streaming RPC succeeds, we \l {QGrpcOperation::} {read} the final response
    from the server and \c reset the RPC object. If the RPC fails, we retry
    by invoking the same function, which overwrites the \c m_clientStream
    member and reconnects the \l {QGrpcOperation::} {finished} signal. We
    cannot simply reassign the \c m_clientStream member within the lambda, as
    this would lose the necessary connection.

    \details {Running the code}
        In \c main, we invoke the \c clientStreaming function with a failing
        message, triggering an RPC failure and executing the retry logic.

        \snippet clientguide/client/main.cpp cstream-1

        A possible output of running the client streaming could look like this:

        \badcode
            Welcome to the clientguide!
            Starting the server process ...
                Server listening on: localhost:50056
                Server (ClientStreaming): Request( time: 9223372036854775807, num: 0 )
            Client (ClientStreaming) failed: QGrpcStatus( code: QtGrpc::StatusCode::InvalidArgument, message: "Request time is in the future!" )
            Restarting the client stream
                Server (ClientStreaming): Request( time: 1733912946696, num: 0 )
                Server (ClientStreaming): Request( time: 1733912946697, num: 1 )
                Server (ClientStreaming): Request( time: 1733912946697, num: 2 )
            Client (ClientStreaming) finished, received: Response( time:  1733912946696922 , num:  3  )
        \endcode

        The server receives an initial message that causes the RPC to fail,
        triggering the retry logic. The retry starts the RPC with a valid
        message, after which three messages are sent to the server before
        completing gracefully.
    \enddetails

    \section2 Bidirectional Streaming

    Bidirectional streaming offers the most flexibility, allowing both the
    client and server to send and receive messages simultaneously. It requires
    the \l {QGrpcOperation::} {finished} and \l {QGrpcBidiStream::}
    {messageReceived} signal to be handled and provides the write functionality
    through \l {QGrpcBidiStream::} {writeMessage}.

    We use a class-based approach with member function \e slot connections to
    demonstrate the functionality, incorporating the handler as a member of the
    class. Additionally, we utilize the pointer-based \l {QGrpcOperation::}
    {read} function. The two members used are:

    \snippet clientguide/client/main.cpp bstream-0

    We create a function to start the bidirectional streaming from an initial
    message and connect the slot functions to the respective \l
    {QGrpcOperation::} {finished} and \l {QGrpcBidiStream::} {messageReceived}
    signals.

    \snippet clientguide/client/main.cpp bstream-1

    The slot functionality is straightforward. The \l {QGrpcOperation::}
    {finished} slot simply prints and resets the RPC object:

    \snippet clientguide/client/main.cpp bstream-2

    The \l {QGrpcBidiStream::} {messageReceived} slot \l {QGrpcOperation::}
    {read}s into the \c m_bidiResponse member, continuing to write messages
    until the received response number hits zero. At that point, we half-close
    the client-side communication using \l {QGrpcBidiStream::} {writesDone}.

    \snippet clientguide/client/main.cpp bstream-3

    \details {Running the code}
        The server logic simply returns a message as soon as it reads
        something, creating a response with the number from the request. In \c
        main, we create such a request, which ultimately serves as a counter.

        \snippet clientguide/client/main.cpp bstream-4

        A possible output of running the bidirectional streaming could look
        like this:

        \badcode
            Welcome to the clientguide!
            Starting the server process ...
                Server listening on: localhost:50056
                Server (BidirectionalStreaming): Request( time: 1733503832107, num: 3 )
            Client (BidirectionalStreaming) received: Response( time:  1733503832108708 , num:  3  )
                Server (BidirectionalStreaming): Request( time: 1733503832109, num: 2 )
            Client (BidirectionalStreaming) received: Response( time:  1733503832109024 , num:  2  )
                Server (BidirectionalStreaming): Request( time: 1733503832109, num: 1 )
            Client (BidirectionalStreaming) received: Response( time:  1733503832109305 , num:  1  )
                Server (BidirectionalStreaming): Request( time: 1733503832109, num: 0 )
            Client (BidirectionalStreaming) received: Response( time:  1733503832109529 , num:  0  )
            Client (BidirectionalStreaming) finished
        \endcode
    \enddetails

    \section1 ClientGuide Interceptors

    \include qtgrpc-shared.qdocinc interceptors_overview_ref

    The client guide example demonstrates three interceptors combined in a
    single chain:

    \list
    \li A \e{metrics} interceptor that records RPC statistics and injects a trace id.
    \li An \e{authentication} interceptor that attaches a bearer token.
    \li A \e{sanitizing} interceptor that validates and normalizes messages.
    \endlist

    Together they demonstrate how interceptors can observe, modify, and
    validate RPC traffic at different stages of the call lifecycle.

    \section2 Metrics Interceptor

    The metrics interceptor is placed first in the chain so it can observe
    the entire RPC lifecycle. It records basic statistics such as received
    payload sizes, RPC durations, and success or failure counts for each RPC
    method.

    \snippet clientguide/client/interceptors.h metrics-0

    \details {Metrics Interceptor Implementation}
        When an RPC starts, the interceptor records the current timestamp and
        stores it using the unique \l{QGrpcOperationContext::}{operationId} as
        a key. A trace identifier is also added to the outgoing metadata so the
        call can be correlated across services.

        \snippet clientguide/client/interceptors.cpp metrics-1

        While the RPC is active, incoming messages contribute to the collected
        metrics by accumulating the number of received payload bytes.

        When the RPC finishes, the interceptor computes the total duration and
        updates aggregated statistics per RPC descriptor.

        For demonstration purposes, the collected metrics are printed when the
        interceptor instance is destroyed.

        \snippet clientguide/client/interceptors.cpp metrics-2
    \enddetails

    \section2 Authentication Interceptor

    The authentication interceptor attaches a bearer token to outgoing RPC
    calls. If the application already provides an \c authorization header, the
    interceptor leaves it unchanged.

    If the server reports an authentication failure, the interceptor refreshes
    the token so that subsequent RPC calls use updated credentials.

    \snippet clientguide/client/interceptors.h auth-0

    \details {Auth Interceptor Implementation}
        The \c TokenProvider encapsulates token management. In this example, it
        generates a simplified token string for demonstration purposes only. It
        does not represent a real authentication token. In real applications,
        tokens would typically be obtained and refreshed through an external
        identity system responsible for issuing and managing authentication
        tokens.

        \snippet clientguide/client/interceptors.cpp auth-1

        On the server side, incoming calls validate the \c authorization
        metadata. If validation fails, the server returns an \c Unauthenticated
        status and provides additional information in trailing metadata.

        \snippet clientguide/server/main.cpp auth

        When an RPC starts, the interceptor checks whether the call already
        contains an \c authorization header. If not, it attaches a bearer token
        to the outgoing metadata.

        Trailing metadata is inspected for authentication hints returned by the
        server. If present, the hint is stored so it can be reported if the
        call ultimately fails with an authentication error.

        When the RPC finishes with an \c Unauthenticated status, the
        interceptor refreshes the token. In this example, the refresh simply
        regenerates a token with a new current time. In real-world scenarios,
        this would involve requesting a new token from an external identity
        system.

        \snippet clientguide/client/interceptors.cpp auth-2
    \enddetails

    \section2 Sanitizing Interceptor

    The sanitizing interceptor validates and normalizes request and response
    data. It demonstrates how interceptors can enforce basic client-side
    constraints before messages are sent and after responses are received.

    In this example the interceptor clamps request values to acceptable ranges,
    normalizes timestamps, and rejects calls whose metadata exceeds a
    predefined size limit.

    \snippet clientguide/client/interceptors.h sanitizing-0

    \details {Sanitizing Interceptor Implementation}
      Helper functions are used to calculate the total metadata size and to
      apply basic normalization rules to request and response messages.

      \snippet clientguide/client/interceptors.cpp sanitizing-1

      When an RPC starts, the interceptor first verifies that the outgoing
      metadata does not exceed the allowed size. If the limit is exceeded, the
      call is aborted by returning \l{QGrpcStartInterceptor::}{Continuation::Drop}

      If the request message matches the expected type, its fields are
      sanitized before the RPC is sent.

      For streaming calls, outgoing messages are also sanitized in the \c
      onWriteMessage hook.

      Finally, incoming responses are intercepted. The serialized payload is
      deserialized, normalized, and serialized again before it is delivered to
      the application.

      \snippet clientguide/client/interceptors.cpp sanitizing-2
    \enddetails

    \section2 Interceptor Usage

    Construct the interceptor chain in a helper function and return it to the
    caller.

    \snippet clientguide/client/interceptors.h interceptorchain-0

    Before attaching the chain to the channel, verify that it was populated
    successfully (for example by checking \c isEmpty()) and apply an
    appropriate error-handling strategy if creation failed.

    \snippet clientguide/client/main.cpp interceptorchain-1

    For unary calls, the example schedules a sequence of requests with a short
    delay between them. This makes it easier to observe how the interceptors
    affect successive calls: trace metadata is added, authentication failures
    refresh the token for later requests, and invalid metadata can cause a call
    to be dropped before it is sent.

    \snippet clientguide/client/main.cpp interceptors-0a
    \snippet clientguide/client/main.cpp interceptors-0b

    The example also performs a bidirectional streaming RPC. The
    \c{bidirectionalStreaming()} implementation would normally exchange \c{666}
    messages, but the sanitizing interceptor clamps the request values so that
    the stream terminates after five exchanges.

    \snippet clientguide/client/main.cpp interceptors-1

    \details {Running the code}

    Run \c clientguide_client with the \c{-I} option to enable interceptor
    support. A possible output is shown below:

    \badcode
        Welcome to the clientguide!
        Running with Interceptor support
        Starting the server process ...
            Server listening on: localhost:50056
            Server (UnaryCall): Request( time: 1774360832277, num: 1 ), Metadata: { x-trace-id: {360bb041-9ab5-4940-95c1-0d4c89491570} }
        Client (UnaryCall) finished, received: Response( time: 1774360832278, num: 1 )
            Server (UnaryCall): Request( time: 1774360832427, num: 2 ), Metadata: { x-trace-id: {01817a8f-9f0e-404e-a21c-767f81e74a2d} }
        Client (UnaryCall) finished, received: Response( time: 1774360832429, num: 2 )
            Server (UnaryCall): Request( time: 1774360832577, num: 3 ), Metadata: { x-trace-id: {78cb336f-0396-4bbc-b1ee-59914651feef} }
        Client (UnaryCall) finished, received: Response( time: 1774360832578, num: 3 )
        [AuthInterceptor] Refreshing token for next call
        Client (UnaryCall) failed: QGrpcStatus( code: QtGrpc::StatusCode::Unauthenticated, message: "Token expired with age: 603 ms, Hint: Bearer realm=\"clientguide\", error=\"invalid_token\"" )
            Server (UnaryCall): Request( time: 1774360832876, num: 5 ), Metadata: { x-trace-id: {63a40327-2f46-43c4-9e2c-44b67f297bd2} }
        Client (UnaryCall) finished, received: Response( time: 1774360832877, num: 5 )
        [Sanitizing] Aborting call with metadata size of 80129 bytes
        Client (UnaryCall) failed: QGrpcStatus( code: QtGrpc::StatusCode::Aborted, message: "Interceptors dropped the call" )
            Server (BidirectionalStreaming) accepted call, Metadata: { x-trace-id: {fc945bdb-5c8a-4c65-8bfb-8655800d5774} }
            Server (BidirectionalStreaming): Request( time: 1774360833177, num: 5 )
        Client (BidirectionalStreaming) received: Response( time: 1774360833178, num: 5 )
            Server (BidirectionalStreaming): Request( time: 1774360833178, num: 4 )
        Client (BidirectionalStreaming) received: Response( time: 1774360833179, num: 4 )
            Server (BidirectionalStreaming): Request( time: 1774360833179, num: 3 )
        Client (BidirectionalStreaming) received: Response( time: 1774360833179, num: 3 )
            Server (BidirectionalStreaming): Request( time: 1774360833179, num: 2 )
        Client (BidirectionalStreaming) received: Response( time: 1774360833180, num: 2 )
            Server (BidirectionalStreaming): Request( time: 1774360833180, num: 1 )
        Client (BidirectionalStreaming) received: Response( time: 1774360833180, num: 1 )
            Server (BidirectionalStreaming): Request( time: 1774360833180, num: 0 )
        Client (BidirectionalStreaming) received: Response( time: 1774360833180, num: 0 )
        Client (BidirectionalStreaming) finished
        All operations completed! Automatically shutting down...
        Metrics for: QtGrpc::RpcDescriptor( service: "client.guide.ClientGuideService", method: "BidirectionalStreaming", type: QtGrpc::RpcType::BidiStreaming )
        Bytes received    : 52
        Failed RPCs       : 0
        Completed RPCs    : 1
        Completed Durations (ms): QList(3.97233)
        Metrics for: QtGrpc::RpcDescriptor( service: "client.guide.ClientGuideService", method: "UnaryCall", type: QtGrpc::RpcType::UnaryCall )
        Bytes received    : 36
        Failed RPCs       : 2
        Completed RPCs    : 4
        Completed Durations (ms): QList(1.937, 1.90971, 1.624, 1.50429)
    \endcode

    The output shows the interceptor chain in action. Outgoing unary calls
    reach the server with trace metadata added by the metrics interceptor. When
    the server reports an authentication failure, the authentication
    interceptor refreshes the token for subsequent calls and augments the error
    message with the server-provided hint. A later call is rejected locally by
    the sanitizing interceptor because its metadata exceeds the configured
    limit. The bidirectional streaming call shows that interceptors also apply
    to streaming RPCs: the sanitizing interceptor clamps the request value, so
    the stream terminates after five message exchanges instead of continuing
    from \c 666. Finally, when the client shuts down, the metrics interceptor
    prints the collected statistics for each RPC method.

    \enddetails
*/
