chromium/chromecast/cast_core/grpc/grpc_status_or.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_STATUS_OR_H_
#define CHROMECAST_CAST_CORE_GRPC_GRPC_STATUS_OR_H_

#include <grpcpp/support/status.h>

#include <optional>
#include <string>
#include <type_traits>

#include "base/check.h"

namespace cast {
namespace utils {

// Converts grpc::Status to a string message.
std::string GrpcStatusToString(const grpc::Status& status);

// Holds a value of type T with grpc::Status::OK or an error grpc::Status.
template <typename T,
          typename std::enable_if<!std::is_reference<T>::value, T*>::type =
              nullptr>
class GrpcStatusOr {
 public:
  GrpcStatusOr() : status_(grpc::StatusCode::UNKNOWN, "") {}

  // Constructs GrpcStatusOr from an error |status_code|.
  GrpcStatusOr(grpc::StatusCode status_code)  // NOLINT
      : GrpcStatusOr(status_code, "") {}

  // Constructs GrpcStatusOr from an error |status_code| and |error_message|.
  GrpcStatusOr(grpc::StatusCode status_code, const std::string& error_message)
      : status_(status_code, error_message) {
    DCHECK(!status_.ok());
  }

  // Constructs GrpcStatusOr from an error |status|.
  GrpcStatusOr(grpc::Status status)  // NOLINT
      : status_(std::move(status)) {
    DCHECK(!status_.ok());
  }

  // Constructs GrpcStatusOr from the |data|. Status code is set to OK.
  GrpcStatusOr(const T& data)  // NOLINT
      : status_(grpc::Status::OK), data_(data) {}

  // Constructs GrpcStatusOr from the |data|. Status code is set to OK.
  GrpcStatusOr(T&& data)  // NOLINT
      : status_(grpc::Status::OK), data_(std::move(data)) {}

  // Constructs GrpcStatusOr from the |data| of type U that is possible to
  // convert to type |T|. For example, std::unique_ptr<MockClass> converted to
  // std::unique_ptr<Class>. Status code is set to OK.
  template <typename U,
            typename std::enable_if<std::is_constructible<T, U>::value,
                                    U>::type* = nullptr>
  GrpcStatusOr(U&& data)  // NOLINT
      : status_(grpc::Status::OK), data_(std::forward<U>(data)) {}

  GrpcStatusOr(GrpcStatusOr&&) = default;
  GrpcStatusOr& operator=(GrpcStatusOr&&) = default;
  GrpcStatusOr(const GrpcStatusOr&) = default;
  GrpcStatusOr& operator=(const GrpcStatusOr&) = default;
  ~GrpcStatusOr() = default;

  // Returns if status is OK.
  bool ok() const { return status_.ok(); }

  // Returns const reference to gRPC status.
  const grpc::Status& status() const& { return status_; }

  // Returns r-value to gRPC status.
  grpc::Status&& status() && { return std::move(status_); }

  // Returns the pointer to the stored data. Checks if status is not OK.
  T* operator->() {
    DCHECK(status_.ok());
    return &*data_;
  }

  // Returns the const pointer to the stored data. Checks if status is not OK.
  const T* operator->() const {
    DCHECK(status_.ok());
    return &*data_;
  }

  // Returns the const reference to the stored data. Checks if status is not OK.
  const T& operator*() const& {
    DCHECK(status_.ok());
    return *data_;
  }

  // Returns the lvalue-ref to the underlying value. Checks if status is not OK.
  const T& value() const& {
    DCHECK(status_.ok());
    return *data_;
  }

  // Returns the rvalue-ref to the underlying value. To trigger this accessor,
  // the status object needs to be moved first: std::move(status_).value().
  // Checks if status is not OK.
  T&& value() && {
    DCHECK(status_.ok());
    status_ = grpc::Status(grpc::StatusCode::UNKNOWN, "");
    return std::move(data_).value();
  }

  // Sets the data.
  void emplace(T&& data) {
    status_ = grpc::Status::OK;
    data_.emplace(std::move(data));
  }

  std::string ToString() const { return GrpcStatusToString(status()); }

 private:
  grpc::Status status_;
  std::optional<T> data_;
};

}  // namespace utils
}  // namespace cast

#endif  // CHROMECAST_CAST_CORE_GRPC_GRPC_STATUS_OR_H_