blob: 30db26a31d65d86fbc10463fa2c6747e2370c5d7 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_ERROR_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_ERROR_H_
#include <lib/fitx/result.h>
#include <zircon/assert.h>
#include <type_traits>
#include <variant>
#include "src/connectivity/bluetooth/core/bt-host/common/host_error.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/lib/cpp-string/string_printf.h"
namespace bt {
// Type used to hold either a HostError or a ProtocolErrorCode, a protocol-defined code. This can
// not be constructed in such a way to represent a success or to contain the product of a successful
// operation, but to be used as the error type parameter of a generic result type like
// fitx::result<Error<…>> or fitx::result<Error<…>, V>.
//
// Errors can be directly constructed from HostErrors. ProtocolErrorCodes whose possible values all
// represent errors can also be used to construct Errors. Otherwise, ProtocolErrorCodes like that
// used by HCI must be converted using ToResult in order to capture success values.
template <typename ProtocolErrorCode>
class Error;
// Required trait for ProtocolErrorCode types.
template <typename ProtocolErrorCode>
struct ProtocolErrorTraits {
// Returns a string representation of the given ProtocolErrorCode value.
static std::string ToString(ProtocolErrorCode);
// Optional: returns true if the given ProtocolErrorCode value represents success. If no such
// value exists, do not declare this static function in the specialization.
// static constexpr bool is_success(ProtocolErrorCode);
};
// Marker used to indicate that an Error holds only HostError.
class NoProtocolError {
constexpr NoProtocolError() = delete;
};
template <>
struct ProtocolErrorTraits<NoProtocolError> {
// This won't be called but still needs to be stubbed out to link correctly.
static std::string ToString(NoProtocolError) {
ZX_ASSERT(false);
return std::string();
}
};
namespace detail {
// Detects whether the given expression implicitly converts to a bt::Error.
template <typename T, typename = void>
struct IsError : std::false_type {};
// This specialization is used when
// 1. T can be deduced as a template template (i.e. T<U>)
// 2. A function that takes Error<U> would accept a T<U>&& value as its parameter
template <template <typename> class T, typename U>
struct IsError<T<U>,
std::void_t<decltype(std::declval<void (&)(Error<U>)>()(std::declval<T<U>>()))>>
: std::true_type {};
template <typename T>
constexpr bool IsErrorV = IsError<T>::value;
// Detects whether ProtocolErrorTraits<ProtocolErrorCode>::is_success has been declared.
template <typename ProtocolErrorCode, typename = void>
struct CanRepresentSuccess : std::false_type {};
template <typename ProtocolErrorCode>
struct CanRepresentSuccess<ProtocolErrorCode,
std::void_t<decltype(ProtocolErrorTraits<ProtocolErrorCode>::is_success(
std::declval<ProtocolErrorCode>()))>> : std::true_type {};
template <typename ProtocolErrorCode>
constexpr bool CanRepresentSuccessV = CanRepresentSuccess<ProtocolErrorCode>::value;
} // namespace detail
// Create a fitx::result<Error<…>> from a HostError. The template parameter may be omitted to
// default to an fitx::result<Error<NoProtocolError>> in the case that it's not useful to specify
// the kind of protocol error that the result could hold instead.
template <typename ProtocolErrorCode = NoProtocolError>
[[nodiscard]] constexpr fitx::result<Error<ProtocolErrorCode>> ToResult(HostError host_error) {
return fitx::error(Error<ProtocolErrorCode>(host_error));
}
// Create a fitx::result<Error<…>> from a protocol error.
// This overload doesn't collide with the above when instantiated with <HostError>, because this
// would try to construct an invalid Error<HostError>.
template <typename ProtocolErrorCode>
[[nodiscard]] constexpr fitx::result<Error<ProtocolErrorCode>> ToResult(
ProtocolErrorCode proto_error) {
if constexpr (detail::CanRepresentSuccessV<ProtocolErrorCode>) {
if (ProtocolErrorTraits<ProtocolErrorCode>::is_success(proto_error)) {
return fitx::success();
}
}
return fitx::error(Error(std::move(proto_error)));
}
template <typename ProtocolErrorCode = NoProtocolError>
class [[nodiscard]] Error {
static_assert(!std::is_same_v<HostError, ProtocolErrorCode>,
"HostError can not be a protocol error");
static_assert(!detail::IsErrorV<ProtocolErrorCode>, "ProtocolErrorCode can not be a bt::Error");
public:
Error() = delete;
~Error() = default;
constexpr Error(const Error&) = default;
constexpr Error(Error&&) noexcept = default;
constexpr Error& operator=(const Error&) = default;
constexpr Error& operator=(Error&&) noexcept = default;
constexpr explicit Error(const HostError& host_error) : error_(host_error) {}
// This is disabled if ProtocolErrorCode may hold a value that means success, leaving only the
// private ctor. Instead use ToResult(ProtocolErrorCode), whose return value may hold success.
template <typename T = ProtocolErrorCode,
std::enable_if_t<!detail::CanRepresentSuccessV<T>, int> = 0>
constexpr explicit Error(const ProtocolErrorCode& proto_error) : error_(proto_error) {}
// Intentionally implicit conversion from Error<NoProtocolError> that holds only HostErrors.
// This allows any Error<…> to be compared to an Error<NoProtocolError>'s HostError payload. Also,
// functions that accept Error<…> will take Error<NoProtocolError> without an explicit conversion.
//
// Example:
// void Foo(Error<BarErrorCode>);
// Foo(ToResult(HostError::kTimedOut)); // Compiles without having to write BarErrorCode
//
// For safety, this implicit conversion does not "chain" to allow bare ProtocolErrorCodes or
// HostErrors to be converted into Error or fitx::result.
//
// The seemingly-extraneous template parameter serves to disable this overload when |*this| is an
// Error<NoProtocolError>
template <typename T = ProtocolErrorCode,
std::enable_if_t<Error<T>::may_hold_protocol_error(), int> = 0>
// NOLINTNEXTLINE(google-explicit-constructor)
constexpr Error(const Error<NoProtocolError>& other) : error_(other.host_error()) {}
// Evaluates to true if and only if both Errors hold the same class of error (host vs protocol)
// and their codes match in value. Errors with different ProtocolErrorCodes are comparable only if
// their ProtocolErrorCodes have a defined operator==, which is generally not the case if the
// codes are strongly-type "enum class" enumerations.
template <typename RErrorCode>
constexpr bool operator==(const Error<RErrorCode>& rhs) const {
auto proto_error_visitor = [&](ProtocolErrorCode held) {
if constexpr (may_hold_protocol_error() && Error<RErrorCode>::may_hold_protocol_error()) {
return held == rhs.protocol_error();
} else {
// This unreachable branch makes comparisons to Error<NoProtocolError> well-defined, so that
// the lambda compiles as long as the protocol error codes are comparable.
return false;
}
};
if (is_host_error() != rhs.is_host_error()) {
return false;
}
return Visit([&rhs](HostError held) { return held == rhs.host_error(); }, proto_error_visitor);
}
template <typename RErrorCode>
constexpr bool operator!=(const Error<RErrorCode>& rhs) const {
return !(*this == rhs);
}
[[nodiscard]] std::string ToString() const {
return Visit([](HostError held) { return HostErrorToString(held); },
[](ProtocolErrorCode held) {
return ProtocolErrorTraits<ProtocolErrorCode>::ToString(held);
});
}
[[nodiscard]] constexpr bool is_host_error() const {
return std::holds_alternative<HostError>(error_);
}
[[nodiscard]] constexpr bool is_protocol_error() const {
return std::holds_alternative<ProtocolErrorCode>(error_);
}
[[nodiscard]] constexpr HostError host_error() const {
ZX_ASSERT_MSG(is_host_error(), "Does not hold HostError");
return std::get<HostError>(error_);
}
[[nodiscard]] constexpr ProtocolErrorCode protocol_error() const {
ZX_ASSERT_MSG(is_protocol_error(), "Does not hold protocol error");
return std::get<ProtocolErrorCode>(error_);
}
[[nodiscard]] constexpr bool is(ProtocolErrorCode proto_error) const {
return Visit([](HostError) { return false; },
[proto_error](ProtocolErrorCode held) { return held == proto_error; });
}
[[nodiscard]] constexpr bool is(HostError host_error) const {
return Visit([host_error](HostError held) { return held == host_error; },
[](ProtocolErrorCode) { return false; });
}
template <typename... Ts>
[[nodiscard]] constexpr bool is_any_of(Ts... error_codes) const {
return (is(error_codes) || ...);
}
// Given two "visitors" (callable objects that accept HostError and ProtocolErrorCode), invoke the
// one that corresponds to the error held in storage, but not the other.
// This pattern allows the code within the visitors to statically presume the type of the error
// code that they work with. Example:
//
// int ConvertToInt(Error<FooError> error) {
// return Visit(
// [](HostError held) { return static_cast<int>(held); },
// [](ProtocolErrorCode held) { return static_cast<int>(held); });
// );
// }
//
// Unlike std::visit, the two visitors do not need to be differentiated from each other through
// overload resolution rules: the argument order to invoking Visit(…) is what determines which
// visitor gets called.
//
// Returns the return value of the visitor that was called (which may return void).
template <typename HostVisitor, typename ProtoVisitor>
[[nodiscard]] constexpr std::common_type_t<std::invoke_result_t<HostVisitor, HostError>,
std::invoke_result_t<ProtoVisitor, ProtocolErrorCode>>
Visit(HostVisitor host_error_visitor, ProtoVisitor proto_error_visitor) const {
if (is_host_error()) {
return host_error_visitor(host_error());
}
return proto_error_visitor(protocol_error());
}
static constexpr bool may_hold_protocol_error() {
return !std::is_same_v<ProtocolErrorCode, NoProtocolError>;
}
private:
// Factory functions
friend constexpr fitx::result<Error<ProtocolErrorCode>> ToResult<ProtocolErrorCode>(
ProtocolErrorCode);
template <typename T = ProtocolErrorCode,
std::enable_if_t<detail::CanRepresentSuccessV<T>, int> = 0>
constexpr explicit Error(const ProtocolErrorCode& proto_error) : error_(proto_error) {
ZX_ASSERT(!ProtocolErrorTraits<ProtocolErrorCode>::is_success(proto_error));
}
std::variant<HostError, ProtocolErrorCode> error_;
};
// Deduction guide to allow Errors to be constructed from a HostError without specifying what
// protocol error the Error can hold instead.
Error(HostError)->Error<NoProtocolError>;
// Comparison operators overloads useful for testing using {ASSERT,EXPECT}_{EQ,NE} GoogleTest
// macros. Each of these must explicitly define a operator!= as well as account for commutative
// calls, because C++ does not automatically generate these. Those variant overloads can not be
// generically defined because there's no way to test if those variants can actually be instantiated
// (using decltype etc), causing problems with e.g. fitx::result<E, T> == fitx::result<F, U>.
// Comparisons to fitx::result<Error<ProtocolErrorCode>>
template <typename LErrorCode, typename RErrorCode, typename... Ts>
constexpr bool operator==(const Error<LErrorCode>& lhs,
const fitx::result<Error<RErrorCode>, Ts...>& rhs) {
static_assert((!detail::IsErrorV<Ts> && ...),
"fitx::result should not contain Error as a success value");
return rhs.is_error() && (rhs.error_value() == lhs);
}
template <typename LErrorCode, typename RErrorCode, typename... Ts>
constexpr bool operator==(const fitx::result<Error<LErrorCode>, Ts...>& lhs,
const Error<RErrorCode>& rhs) {
return rhs == lhs;
}
template <typename LErrorCode, typename RErrorCode, typename... Ts>
constexpr bool operator!=(const Error<LErrorCode>& lhs,
const fitx::result<Error<RErrorCode>, Ts...>& rhs) {
return !(lhs == rhs);
}
template <typename LErrorCode, typename RErrorCode, typename... Ts>
constexpr bool operator!=(const fitx::result<Error<LErrorCode>, Ts...>& lhs,
const Error<RErrorCode>& rhs) {
return !(rhs == lhs);
}
// Comparisons between fitx::result<Error<…>> objects
// Note that this is not standard fitx::result relation behavior which normally compares all error
// results to be equal. These are preferred in overload resolution because they are more specialized
// templates than the ones provided by fitx. However, because they are more specialized, all of the
// combinations must be typed out separately to avoid ambiguous overload errors:
// 1. operands having zero or one success values
// 2. operation is == or !=
// The case of comparing a result with a success value to a result without is intentionally not
// defined because it's not obvious what behavior it should have when both results hold success.
template <typename LErrorCode, typename RErrorCode, typename T>
constexpr bool operator==(const fitx::result<Error<LErrorCode>, T>& lhs,
const fitx::result<Error<RErrorCode>, T>& rhs) {
static_assert(!detail::IsErrorV<T>, "fitx::result should not contain Error as a success value");
if (lhs.is_ok() != rhs.is_ok()) {
return false;
}
if (lhs.is_ok()) {
return lhs.value() == rhs.value();
}
return lhs.error_value() == rhs.error_value();
}
template <typename LErrorCode, typename RErrorCode, typename T>
constexpr bool operator!=(const fitx::result<Error<LErrorCode>, T>& lhs,
const fitx::result<Error<RErrorCode>, T>& rhs) {
return !(lhs == rhs);
}
template <typename LErrorCode, typename RErrorCode>
constexpr bool operator==(const fitx::result<Error<LErrorCode>>& lhs,
const fitx::result<Error<RErrorCode>>& rhs) {
if (lhs.is_ok() != rhs.is_ok()) {
return false;
}
if (lhs.is_ok()) {
return true;
}
return lhs.error_value() == rhs.error_value();
}
template <typename LErrorCode, typename RErrorCode>
constexpr bool operator!=(const fitx::result<Error<LErrorCode>>& lhs,
const fitx::result<Error<RErrorCode>>& rhs) {
return !(lhs == rhs);
}
namespace internal {
// Helper to build a string from result using generic concatenation calls.
template <typename Result, typename StringBuilder, typename ValueStringBuilder>
void BuildResultToString(const Result& result, StringBuilder builder,
ValueStringBuilder append_value) {
builder("[result: ");
if (result.is_ok()) {
builder("ok(");
append_value();
} else {
builder("error(");
builder(result.error_value().ToString());
}
builder(")]");
}
// Produces a human-readable representation of a fitx::result<Error<…>>
template <typename ProtocolErrorCode, typename... Ts>
std::string ToString(const fitx::result<Error<ProtocolErrorCode>, Ts...>& result) {
std::string out;
auto append_value_string = [&] {
if constexpr (sizeof...(Ts) > 0) {
if constexpr ((bt::internal::HasToStringV<Ts> && ...)) {
out += ToString(result.value());
} else {
// It's not possible to portably print e.g. the name of the value's type, so fall back to a
// placeholder. It may be useful to print the size and a hexdump, however.
out += "?";
}
}
};
bt::internal::BuildResultToString(
result, [&](auto s) { out += s; }, append_value_string);
return out;
}
// Overload for compatibility with the bt_is_error(status, …) macro where |status| is a
// fitx::result<Error<…>, …>
template <typename ProtocolErrorCode, typename... Ts>
[[gnu::format(printf, 6, 7)]] bool TestForErrorAndLog(
const fitx::result<Error<ProtocolErrorCode>, Ts...>& result, LogSeverity severity,
const char* tag, const char* file, int line, const char* fmt, ...) {
if (!(result.is_error() && IsLogLevelEnabled(severity))) {
return result.is_error();
}
va_list args;
va_start(args, fmt);
std::string msg = bt_lib_cpp_string::StringVPrintf(fmt, args);
LogMessage(file, line, severity, tag, "%s: %s", msg.c_str(), bt_str(result));
va_end(args);
return true;
}
} // namespace internal
namespace detail {
// Contains a |value| bool member that is true if overload operator<<(Lhs&, const Rhs&) exists
template <typename Lhs, typename Rhs, typename = void>
struct IsStreamable : std::false_type {};
template <typename Lhs, typename Rhs>
struct IsStreamable<Lhs, Rhs,
std::void_t<decltype(std::declval<Lhs&>() << std::declval<const Rhs&>())>>
: std::is_same<Lhs&, decltype(std::declval<Lhs&>() << std::declval<const Rhs&>())> {};
template <typename Lhs, typename Rhs>
constexpr bool IsStreamableV = IsStreamable<Lhs, Rhs>::value;
} // namespace detail
} // namespace bt
// Extends the GoogleTest value printer to print fitx::result<bt::Error<…>, …> types, including
// converting the contained value of "success" results to strings when possible.
//
// This must be defined in namespace fitx because GoogleTest uses argument-dependent lookup (ADL) to
// find this overload. |os|'s type is templated in order to avoid including <iostream>.
namespace fitx {
// Some GoogleTest internal objects (like testing::Message, the return type of ADD_FAILURE())
// declare broad operator<< overloads that conflict with this one. In those cases, it's likely
// easiest to wrap the result in bt_str(…).
template <typename OStream, typename ProtocolErrorCode, typename... Ts>
OStream& operator<<(OStream& os,
const fitx::result<::bt::Error<ProtocolErrorCode>, Ts...>& result) {
auto stream_value_string = [&] {
if constexpr (sizeof...(Ts) > 0) {
if constexpr ((::bt::internal::HasToStringV<Ts> && ...)) {
os << ::bt::internal::ToString(result.value());
} else if constexpr ((::bt::detail::IsStreamableV<OStream, Ts> && ...)) {
os << result.value();
} else {
// It may be prettier to default to ::testing::PrintToString here but that would require
// including <gtest/gtest.h> here, which is not ideal.
os << "?";
}
}
};
::bt::internal::BuildResultToString(
result, [&](auto s) { os << s; }, stream_value_string);
return os;
}
} // namespace fitx
// Macro to check and log any non-Success status of an event.
// Use these like:
// if (bt_is_error(status, WARN, "gap", "failed to set event mask")) {
// ...
// return;
// }
//
// It will log with the string prepended to the stringified status if status is
// a failure. Evaluates to true if the status indicates failure.
#define bt_is_error(status, flag, tag, fmt...) \
(::bt::internal::TestForErrorAndLog(status, bt::LogSeverity::flag, tag, \
bt::internal::BaseName(__FILE__), __LINE__, fmt))
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_ERROR_H_