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