1 // Copyright 2023 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 15 #pragma once 16 #include <cpp-string/string_printf.h> 17 #include <lib/fit/result.h> 18 19 #include <type_traits> 20 #include <variant> 21 22 #include "pw_bluetooth_sapphire/internal/host/common/assert.h" 23 #include "pw_bluetooth_sapphire/internal/host/common/host_error.h" 24 #include "pw_bluetooth_sapphire/internal/host/common/log.h" 25 #include "pw_bluetooth_sapphire/internal/host/common/macros.h" 26 27 namespace bt { 28 29 // Type used to hold either a HostError or a ProtocolErrorCode, a 30 // protocol-defined code. This can not be constructed in such a way to represent 31 // a success or to contain the product of a successful operation, but to be used 32 // as the error type parameter of a generic result type like 33 // fit::result<Error<…>> or fit::result<Error<…>, V>. 34 // 35 // Errors can be directly constructed from HostErrors. ProtocolErrorCodes whose 36 // possible values all represent errors can also be used to construct Errors. 37 // Otherwise, ProtocolErrorCodes like that used by HCI must be converted using 38 // ToResult in order to capture success values. 39 template <typename ProtocolErrorCode> 40 class Error; 41 42 // Required trait for ProtocolErrorCode types. 43 template <typename ProtocolErrorCode> 44 struct ProtocolErrorTraits { 45 // Returns a string representation of the given ProtocolErrorCode value. 46 static std::string ToString(ProtocolErrorCode); 47 48 // Optional: returns true if the given ProtocolErrorCode value represents 49 // success. If no such value exists, do not declare this static function in 50 // the specialization. static constexpr bool is_success(ProtocolErrorCode); 51 }; 52 53 // Marker used to indicate that an Error holds only HostError. 54 class NoProtocolError { 55 constexpr NoProtocolError() = delete; 56 }; 57 58 template <> 59 struct ProtocolErrorTraits<NoProtocolError> { 60 // This won't be called but still needs to be stubbed out to link correctly. 61 static std::string ToString(NoProtocolError) { 62 PW_CHECK(false); 63 return std::string(); 64 } 65 }; 66 67 namespace detail { 68 69 // Detects whether the given expression implicitly converts to a bt::Error. 70 template <typename T, typename = void> 71 struct IsError : std::false_type {}; 72 73 // This specialization is used when 74 // 1. T can be deduced as a template template (i.e. T<U>) 75 // 2. A function that takes Error<U> would accept a T<U>&& value as its 76 // parameter 77 template <template <typename> class T, typename U> 78 struct IsError<T<U>, 79 std::void_t<decltype(std::declval<void (&)(Error<U>)>()( 80 std::declval<T<U>>()))>> : std::true_type {}; 81 82 template <typename T> 83 constexpr bool IsErrorV = IsError<T>::value; 84 85 // Detects whether ProtocolErrorTraits<ProtocolErrorCode>::is_success has been 86 // declared. 87 template <typename ProtocolErrorCode, typename = void> 88 struct CanRepresentSuccess : std::false_type {}; 89 90 template <typename ProtocolErrorCode> 91 struct CanRepresentSuccess< 92 ProtocolErrorCode, 93 std::void_t<decltype(ProtocolErrorTraits<ProtocolErrorCode>::is_success( 94 std::declval<ProtocolErrorCode>()))>> : std::true_type {}; 95 96 template <typename ProtocolErrorCode> 97 constexpr bool CanRepresentSuccessV = 98 CanRepresentSuccess<ProtocolErrorCode>::value; 99 100 } // namespace detail 101 102 // Create a fit::result<Error<…>> from a HostError. The template parameter may 103 // be omitted to default to an fit::result<Error<NoProtocolError>> in the case 104 // that it's not useful to specify the kind of protocol error that the result 105 // could hold instead. 106 template <typename ProtocolErrorCode = NoProtocolError> 107 [[nodiscard]] constexpr fit::result<Error<ProtocolErrorCode>> ToResult( 108 HostError host_error) { 109 return fit::error(Error<ProtocolErrorCode>(host_error)); 110 } 111 112 // Create a fit::result<Error<…>> from a protocol error. 113 // This overload doesn't collide with the above when instantiated with 114 // <HostError>, because this would try to construct an invalid Error<HostError>. 115 template <typename ProtocolErrorCode> 116 [[nodiscard]] constexpr fit::result<Error<ProtocolErrorCode>> ToResult( 117 ProtocolErrorCode proto_error) { 118 if constexpr (detail::CanRepresentSuccessV<ProtocolErrorCode>) { 119 if (ProtocolErrorTraits<ProtocolErrorCode>::is_success(proto_error)) { 120 return fit::success(); 121 } 122 } 123 return fit::error(Error(std::move(proto_error))); 124 } 125 126 template <typename ProtocolErrorCode = NoProtocolError> 127 class [[nodiscard]] Error { 128 static_assert(!std::is_same_v<HostError, ProtocolErrorCode>, 129 "HostError can not be a protocol error"); 130 static_assert(!detail::IsErrorV<ProtocolErrorCode>, 131 "ProtocolErrorCode can not be a bt::Error"); 132 133 public: 134 Error() = delete; 135 ~Error() = default; 136 constexpr Error(const Error&) = default; 137 constexpr Error(Error&&) noexcept = default; 138 constexpr Error& operator=(const Error&) = default; 139 constexpr Error& operator=(Error&&) noexcept = default; 140 141 constexpr explicit Error(const HostError& host_error) : error_(host_error) {} 142 143 // This is disabled if ProtocolErrorCode may hold a value that means success, 144 // leaving only the private ctor. Instead use ToResult(ProtocolErrorCode), 145 // whose return value may hold success. 146 template <typename T = ProtocolErrorCode, 147 std::enable_if_t<!detail::CanRepresentSuccessV<T>, int> = 0> 148 constexpr explicit Error(const ProtocolErrorCode& proto_error) 149 : error_(proto_error) {} 150 151 // Intentionally implicit conversion from Error<NoProtocolError> that holds 152 // only HostErrors. This allows any Error<…> to be compared to an 153 // Error<NoProtocolError>'s HostError payload. Also, functions that accept 154 // Error<…> will take Error<NoProtocolError> without an explicit conversion. 155 // 156 // Example: 157 // void Foo(Error<BarErrorCode>); 158 // Foo(ToResult(HostError::kTimedOut)); // Compiles without having to write 159 // BarErrorCode 160 // 161 // For safety, this implicit conversion does not "chain" to allow bare 162 // ProtocolErrorCodes or HostErrors to be converted into Error or fit::result. 163 // 164 // The seemingly-extraneous template parameter serves to disable this overload 165 // when |*this| is an Error<NoProtocolError> 166 template <typename T = ProtocolErrorCode, 167 std::enable_if_t<Error<T>::may_hold_protocol_error(), int> = 0> 168 // NOLINTNEXTLINE(google-explicit-constructor) 169 constexpr Error(const Error<NoProtocolError>& other) 170 : error_(other.host_error()) {} 171 172 // Evaluates to true if and only if both Errors hold the same class of error 173 // (host vs protocol) and their codes match in value. Errors with different 174 // ProtocolErrorCodes are comparable only if their ProtocolErrorCodes have a 175 // defined operator==, which is generally not the case if the codes are 176 // strongly-type "enum class" enumerations. 177 template <typename RErrorCode> 178 constexpr bool operator==(const Error<RErrorCode>& rhs) const { 179 auto proto_error_visitor = [&](ProtocolErrorCode held) { 180 if constexpr (may_hold_protocol_error() && 181 Error<RErrorCode>::may_hold_protocol_error()) { 182 return held == rhs.protocol_error(); 183 } else { 184 // This unreachable branch makes comparisons to Error<NoProtocolError> 185 // well-defined, so that the lambda compiles as long as the protocol 186 // error codes are comparable. 187 return false; 188 } 189 }; 190 if (is_host_error() != rhs.is_host_error()) { 191 return false; 192 } 193 return Visit([&rhs](HostError held) { return held == rhs.host_error(); }, 194 proto_error_visitor); 195 } 196 197 template <typename RErrorCode> 198 constexpr bool operator!=(const Error<RErrorCode>& rhs) const { 199 return !(*this == rhs); 200 } 201 202 [[nodiscard]] std::string ToString() const { 203 return Visit( 204 [](HostError held) { return HostErrorToString(held); }, 205 [](ProtocolErrorCode held) { 206 return ProtocolErrorTraits<ProtocolErrorCode>::ToString(held); 207 }); 208 } 209 210 [[nodiscard]] constexpr bool is_host_error() const { 211 return std::holds_alternative<HostError>(error_); 212 } 213 214 [[nodiscard]] constexpr bool is_protocol_error() const { 215 return std::holds_alternative<ProtocolErrorCode>(error_); 216 } 217 218 [[nodiscard]] constexpr HostError host_error() const { 219 PW_ASSERT(is_host_error()); 220 return std::get<HostError>(error_); 221 } 222 223 [[nodiscard]] constexpr ProtocolErrorCode protocol_error() const { 224 PW_ASSERT(is_protocol_error()); 225 return std::get<ProtocolErrorCode>(error_); 226 } 227 228 [[nodiscard]] constexpr bool is(ProtocolErrorCode proto_error) const { 229 return Visit( 230 [](HostError) { return false; }, 231 [proto_error](ProtocolErrorCode held) { return held == proto_error; }); 232 } 233 234 [[nodiscard]] constexpr bool is(HostError host_error) const { 235 return Visit([host_error](HostError held) { return held == host_error; }, 236 [](ProtocolErrorCode) { return false; }); 237 } 238 239 template <typename... Ts> 240 [[nodiscard]] constexpr bool is_any_of(Ts... error_codes) const { 241 return (is(error_codes) || ...); 242 } 243 244 // Given two "visitors" (callable objects that accept HostError and 245 // ProtocolErrorCode), invoke the one that corresponds to the error held in 246 // storage, but not the other. This pattern allows the code within the 247 // visitors to statically presume the type of the error code that they work 248 // with. Example: 249 // 250 // int ConvertToInt(Error<FooError> error) { 251 // return Visit( 252 // [](HostError held) { return static_cast<int>(held); }, 253 // [](ProtocolErrorCode held) { return static_cast<int>(held); }); 254 // ); 255 // } 256 // 257 // Unlike std::visit, the two visitors do not need to be differentiated from 258 // each other through overload resolution rules: the argument order to 259 // invoking Visit(…) is what determines which visitor gets called. 260 // 261 // Returns the return value of the visitor that was called (which may return 262 // void). 263 template <typename HostVisitor, typename ProtoVisitor> 264 [[nodiscard]] constexpr std::common_type_t< 265 std::invoke_result_t<HostVisitor, HostError>, 266 std::invoke_result_t<ProtoVisitor, ProtocolErrorCode>> 267 Visit(HostVisitor host_error_visitor, 268 ProtoVisitor proto_error_visitor) const { 269 if (is_host_error()) { 270 return host_error_visitor(host_error()); 271 } 272 return proto_error_visitor(protocol_error()); 273 } 274 275 static constexpr bool may_hold_protocol_error() { 276 return !std::is_same_v<ProtocolErrorCode, NoProtocolError>; 277 } 278 279 private: 280 // Factory functions 281 friend constexpr fit::result<Error<ProtocolErrorCode>> 282 ToResult<ProtocolErrorCode>(ProtocolErrorCode); 283 284 template <typename T = ProtocolErrorCode, 285 std::enable_if_t<detail::CanRepresentSuccessV<T>, int> = 0> 286 constexpr explicit Error(const ProtocolErrorCode& proto_error) 287 : error_(proto_error) { 288 PW_ASSERT(!ProtocolErrorTraits<ProtocolErrorCode>::is_success(proto_error)); 289 } 290 291 std::variant<HostError, ProtocolErrorCode> error_; 292 }; 293 294 // Deduction guide to allow Errors to be constructed from a HostError without 295 // specifying what protocol error the Error can hold instead. 296 Error(HostError) -> Error<NoProtocolError>; 297 298 // Comparison operators overloads useful for testing using 299 // {ASSERT,EXPECT}_{EQ,NE} GoogleTest macros. Each of these must explicitly 300 // define a operator!= as well as account for commutative calls, because C++ 301 // does not automatically generate these. Those variant overloads can not be 302 // generically defined because there's no way to test if those variants can 303 // actually be instantiated (using decltype etc), causing problems with e.g. 304 // fit::result<E, T> == fit::result<F, U>. 305 306 // Comparisons to fit::result<Error<ProtocolErrorCode>> 307 template <typename LErrorCode, typename RErrorCode, typename... Ts> 308 constexpr bool operator==(const Error<LErrorCode>& lhs, 309 const fit::result<Error<RErrorCode>, Ts...>& rhs) { 310 static_assert((!detail::IsErrorV<Ts> && ...), 311 "fit::result should not contain Error as a success value"); 312 return rhs.is_error() && (rhs.error_value() == lhs); 313 } 314 315 template <typename LErrorCode, typename RErrorCode, typename... Ts> 316 constexpr bool operator==(const fit::result<Error<LErrorCode>, Ts...>& lhs, 317 const Error<RErrorCode>& rhs) { 318 return rhs == lhs; 319 } 320 321 template <typename LErrorCode, typename RErrorCode, typename... Ts> 322 constexpr bool operator!=(const Error<LErrorCode>& lhs, 323 const fit::result<Error<RErrorCode>, Ts...>& rhs) { 324 return !(lhs == rhs); 325 } 326 327 template <typename LErrorCode, typename RErrorCode, typename... Ts> 328 constexpr bool operator!=(const fit::result<Error<LErrorCode>, Ts...>& lhs, 329 const Error<RErrorCode>& rhs) { 330 return !(rhs == lhs); 331 } 332 333 // Comparisons between fit::result<Error<…>> objects 334 // Note that this is not standard fit::result relation behavior which normally 335 // compares all error results to be equal. These are preferred in overload 336 // resolution because they are more specialized templates than the ones provided 337 // by fit. However, because they are more specialized, all of the combinations 338 // must be typed out separately to avoid ambiguous overload errors: 339 // 1. operands having zero or one success values 340 // 2. operation is == or != 341 // The case of comparing a result with a success value to a result without is 342 // intentionally not defined because it's not obvious what behavior it should 343 // have when both results hold success. 344 template <typename LErrorCode, typename RErrorCode, typename T> 345 constexpr bool operator==(const fit::result<Error<LErrorCode>, T>& lhs, 346 const fit::result<Error<RErrorCode>, T>& rhs) { 347 static_assert(!detail::IsErrorV<T>, 348 "fit::result should not contain Error as a success value"); 349 if (lhs.is_ok() != rhs.is_ok()) { 350 return false; 351 } 352 if (lhs.is_ok()) { 353 return lhs.value() == rhs.value(); 354 } 355 return lhs.error_value() == rhs.error_value(); 356 } 357 358 template <typename LErrorCode, typename RErrorCode, typename T> 359 constexpr bool operator!=(const fit::result<Error<LErrorCode>, T>& lhs, 360 const fit::result<Error<RErrorCode>, T>& rhs) { 361 return !(lhs == rhs); 362 } 363 364 template <typename LErrorCode, typename RErrorCode> 365 constexpr bool operator==(const fit::result<Error<LErrorCode>>& lhs, 366 const fit::result<Error<RErrorCode>>& rhs) { 367 if (lhs.is_ok() != rhs.is_ok()) { 368 return false; 369 } 370 if (lhs.is_ok()) { 371 return true; 372 } 373 return lhs.error_value() == rhs.error_value(); 374 } 375 376 template <typename LErrorCode, typename RErrorCode> 377 constexpr bool operator!=(const fit::result<Error<LErrorCode>>& lhs, 378 const fit::result<Error<RErrorCode>>& rhs) { 379 return !(lhs == rhs); 380 } 381 382 namespace internal { 383 384 // Helper to build a string from result using generic concatenation calls. 385 template <typename Result, typename StringBuilder, typename ValueStringBuilder> 386 void BuildResultToString(const Result& result, 387 StringBuilder builder, 388 ValueStringBuilder append_value) { 389 builder("[result: "); 390 if (result.is_ok()) { 391 builder("ok("); 392 append_value(); 393 } else { 394 builder("error("); 395 builder(result.error_value().ToString()); 396 } 397 builder(")]"); 398 } 399 400 // Produces a human-readable representation of a fit::result<Error<…>> 401 template <typename ProtocolErrorCode, typename... Ts> 402 std::string ToString( 403 const fit::result<Error<ProtocolErrorCode>, Ts...>& result) { 404 std::string out; 405 auto append_value_string = [&] { 406 if constexpr (sizeof...(Ts) > 0) { 407 if constexpr ((bt::internal::HasToStringV<Ts> && ...)) { 408 out += ToString(result.value()); 409 } else { 410 // It's not possible to portably print e.g. the name of the value's 411 // type, so fall back to a placeholder. It may be useful to print the 412 // size and a hexdump, however. 413 out += "?"; 414 } 415 } 416 }; 417 bt::internal::BuildResultToString( 418 result, [&](auto s) { out += s; }, append_value_string); 419 return out; 420 } 421 422 } // namespace internal 423 424 namespace detail { 425 426 // Contains a |value| bool member that is true if overload operator<<(Lhs&, 427 // const Rhs&) exists 428 template <typename Lhs, typename Rhs, typename = void> 429 struct IsStreamable : std::false_type {}; 430 431 template <typename Lhs, typename Rhs> 432 struct IsStreamable< 433 Lhs, 434 Rhs, 435 std::void_t<decltype(std::declval<Lhs&>() << std::declval<const Rhs&>())>> 436 : std::is_same<Lhs&, 437 decltype(std::declval<Lhs&>() 438 << std::declval<const Rhs&>())> {}; 439 440 template <typename Lhs, typename Rhs> 441 constexpr bool IsStreamableV = IsStreamable<Lhs, Rhs>::value; 442 443 } // namespace detail 444 } // namespace bt 445 446 // Extends the GoogleTest value printer to print fit::result<bt::Error<…>, …> 447 // types, including converting the contained value of "success" results to 448 // strings when possible. 449 // 450 // This must be defined in namespace fit because GoogleTest uses 451 // argument-dependent lookup (ADL) to find this overload. |os|'s type is 452 // templated in order to avoid including <iostream>. 453 namespace fit { 454 455 // Some GoogleTest internal objects (like testing::Message, the return type of 456 // ADD_FAILURE()) declare broad operator<< overloads that conflict with this 457 // one. In those cases, it's likely easiest to wrap the result in bt_str(…). 458 template <typename OStream, typename ProtocolErrorCode, typename... Ts> 459 OStream& operator<<( 460 OStream& os, 461 const fit::result<::bt::Error<ProtocolErrorCode>, Ts...>& result) { 462 auto stream_value_string = [&] { 463 if constexpr (sizeof...(Ts) > 0) { 464 if constexpr ((::bt::internal::HasToStringV<Ts> && ...)) { 465 os << ::bt::internal::ToString(result.value()); 466 } else if constexpr ((::bt::detail::IsStreamableV<OStream, Ts> && ...)) { 467 os << result.value(); 468 } else { 469 // It may be prettier to default to ::testing::PrintToString here but 470 // that would require including <gtest/gtest.h> here, which is not 471 // ideal. 472 os << "?"; 473 } 474 } 475 }; 476 ::bt::internal::BuildResultToString( 477 result, [&](auto s) { os << s; }, stream_value_string); 478 return os; 479 } 480 481 } // namespace fit 482 483 // Macro to check and log any non-Success status of an event. 484 // Use these like: 485 // if (bt_is_error(result, WARN, "gap", "failed to set event mask")) { 486 // ... 487 // return; 488 // } 489 // 490 // It will log with the string prepended to the stringified result if result is 491 // a failure. Evaluates to true if the result indicates failure. 492 #define bt_is_error(result, level, tag, fmt, /*args*/...) \ 493 [&]() { \ 494 auto _result = result; \ 495 if (_result.is_error()) \ 496 bt_log(level, tag, "%s: " fmt, bt_str(_result), ##__VA_ARGS__); \ 497 return _result.is_error(); \ 498 }() 499