| // 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_ |