chromium/chromecast/cast_core/grpc/grpc_unary_call.h

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMECAST_CAST_CORE_GRPC_GRPC_UNARY_CALL_H_
#define CHROMECAST_CAST_CORE_GRPC_GRPC_UNARY_CALL_H_

#include <grpcpp/grpcpp.h>
#include <grpcpp/support/client_callback.h>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "chromecast/cast_core/grpc/grpc_call.h"
#include "chromecast/cast_core/grpc/grpc_client_reactor.h"
#include "chromecast/cast_core/grpc/grpc_status_or.h"

namespace cast {
namespace utils {

// Typedef for the unary sync method generated by gRPC compiler.
template <typename TSyncInterface, typename TRequest, typename TResponse>
using SyncUnaryMethod = grpc::Status (TSyncInterface::*)(grpc::ClientContext*,
                                                         const TRequest&,
                                                         TResponse*);

// Typedef for the unary async method generated by gRPC compiler.
template <typename TAsyncInterface, typename TRequest, typename TResponse>
using AsyncUnaryMethod = void (TAsyncInterface::*)(grpc::ClientContext*,
                                                   const TRequest*,
                                                   TResponse*,
                                                   grpc::ClientUnaryReactor*);

// A GrpcCall implementation for unary gRPC calls specialized by the
// |AsyncMethodPtr| and SyncMethodPtr function pointers. The former must always
// be specified and is used for Async calls. The latter is used for Sync calls
// and could be omitted.
//  TGrpcStub - gRPC service stub type.
//  TRequest - gRPC request type for a method in the stub.
//  TResponse - gRPC response type for a method in the stub.
//  AsyncMethodPtr - pointer to an async method in the stub that handles a
//  streaming call.
//  SyncMethodPtr - pointer to a sync method in the stub that handles a
//  streaming call.
template <
    typename TGrpcStub,
    typename TRequest,
    typename TResponse,
    AsyncUnaryMethod<typename TGrpcStub::AsyncInterface, TRequest, TResponse>
        AsyncMethodPtr,
    SyncUnaryMethod<typename TGrpcStub::SyncInterface, TRequest, TResponse>
        SyncMethodPtr = nullptr>
class GrpcUnaryCall : public GrpcCall<TGrpcStub, TRequest> {
 public:
  static_assert(AsyncMethodPtr != nullptr, "AsyncMethodPtr must be specified");

  using Base = GrpcCall<TGrpcStub, TRequest>;
  using Base::async;
  using Base::GrpcCall;
  using Base::request;
  using Base::sync;
  using typename Base::AsyncInterface;
  using typename Base::Context;
  using typename Base::Request;

  using Response = TResponse;
  using ResponseCallback = base::OnceCallback<void(GrpcStatusOr<Response>)>;

  GrpcStatusOr<Response> Invoke() && {
    static_assert(SyncMethodPtr != nullptr,
                  "The sync interface is not defined for the stub. Please, add "
                  "&StubInterface::<method_name> to the GrpcUnaryCall "
                  "definition in the stub class.");
    Response response;
    grpc::ClientContext context;
    std::move(*this).options().ApplyOptionsToContext(&context);
    auto status = (std::move(*this).sync()->*SyncMethodPtr)(
        &context, std::move(*this).request(), &response);
    if (status.ok()) {
      return response;
    }
    return status;
  }

  // Invokes a unary gRPC call for a given tuple of AsyncInterface,
  // AsyncMethodPtr, Request and Response. The |service| must have a |method| of
  // a certain signature generated by the proto compiler.
  Context InvokeAsync(ResponseCallback response_callback) && {
    // Although |reactor| is a plain pointer, its ownership is transferred to
    // the gRPC stack in the |Start| call.
    auto reactor =
        new Reactor(std::move(*this).async(), std::move(*this).request(),
                    std::move(*this).options(), std::move(response_callback));
    reactor->Start();
    return Context(reactor->context());
  }

 private:
  using ReactorBase = GrpcClientReactor<Request, grpc::ClientUnaryReactor>;

  class Reactor final : public ReactorBase {
   public:
    using ReactorBase::context;
    using ReactorBase::request;

    Reactor(AsyncInterface* async_stub,
            Request request,
            GrpcCallOptions options,
            ResponseCallback response_callback)
        : ReactorBase(std::move(request), std::move(options)),
          async_interface_(async_stub),
          response_callback_(std::move(response_callback)) {}

    void Start() override {
      ReactorBase::Start();
      (async_interface_->*AsyncMethodPtr)(context(), request(), &response_,
                                          this);
      grpc::ClientUnaryReactor::StartCall();
    }

   private:
    // Implements grpc::ClientUnaryReactor APIs.
    // The method is always called on completion of all operations associated
    // with this call, and deletes itself on exit.
    void OnDone(const grpc::Status& status) override {
      if (status.ok()) {
        std::move(response_callback_).Run(std::move(response_));
      } else {
        std::move(response_callback_).Run(status);
      }
      ReactorBase::DeleteThis();
    }

    using AsyncStubCall = base::OnceCallback<void(grpc::ClientContext*,
                                                  const Request*,
                                                  Response*,
                                                  grpc::ClientUnaryReactor*)>;

    raw_ptr<AsyncInterface> async_interface_;
    ResponseCallback response_callback_;
    Response response_;
  };
};

}  // namespace utils
}  // namespace cast

#endif  // CHROMECAST_CAST_CORE_GRPC_GRPC_UNARY_CALL_H_