|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include "status_codes.h" |
| 4 | + |
| 5 | +#include <concepts> |
| 6 | +#include <string> |
| 7 | +#include <string_view> |
| 8 | +#include <type_traits> |
| 9 | + |
| 10 | +namespace cpp_core |
| 11 | +{ |
| 12 | + |
| 13 | +// Concepts |
| 14 | + |
| 15 | +// Matches any callable that can receive (int, const char*). |
| 16 | +// clang-format off |
| 17 | +template <typename F> |
| 18 | +concept ErrorCallback = std::is_null_pointer_v<std::remove_cvref_t<F>> |
| 19 | + || std::is_same_v<std::remove_cvref_t<F>, std::nullptr_t> |
| 20 | + || requires(F &&func, int code, const char *msg) { |
| 21 | + { func(code, msg) } -> std::same_as<void>; |
| 22 | + }; |
| 23 | +// clang-format on |
| 24 | + |
| 25 | +// Matches anything implicitly convertible to a C-style function pointer (ErrorCallbackT). |
| 26 | +template <typename F> |
| 27 | +concept LegacyErrorCallback = std::is_convertible_v<F, void (*)(int, const char *)>; |
| 28 | + |
| 29 | +// Matches any type whose value can be returned as a status/result code. |
| 30 | +template <typename T> |
| 31 | +concept StatusConvertible = std::is_arithmetic_v<T> && requires(StatusCodes code) { static_cast<T>(code); }; |
| 32 | + |
| 33 | +// Error invocation |
| 34 | + |
| 35 | +// Safely invoke an error callback, does nothing if callback is nullptr. |
| 36 | +template <ErrorCallback Callback> |
| 37 | +constexpr auto invokeError(Callback &&callback, StatusCodes code, std::string_view message) noexcept -> void |
| 38 | +{ |
| 39 | + if constexpr (std::is_null_pointer_v<std::remove_cvref_t<Callback>>) |
| 40 | + { |
| 41 | + (void)callback; |
| 42 | + (void)code; |
| 43 | + (void)message; |
| 44 | + return; |
| 45 | + } |
| 46 | + else |
| 47 | + { |
| 48 | + if (callback != nullptr) |
| 49 | + { |
| 50 | + const std::string term(message); |
| 51 | + callback(static_cast<int>(code), term.c_str()); |
| 52 | + } |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +// Fail helpers (concept-constrained) |
| 57 | + |
| 58 | +// Report failure through callback and return the status code cast to Ret. |
| 59 | +template <StatusConvertible Ret, ErrorCallback Callback> |
| 60 | +constexpr auto failMsg(Callback &&callback, StatusCodes code, std::string_view message) -> Ret |
| 61 | +{ |
| 62 | + invokeError(std::forward<Callback>(callback), code, message); |
| 63 | + return static_cast<Ret>(code); |
| 64 | +} |
| 65 | + |
| 66 | +// Overload that builds the message by concatenating a prefix and a detail string. |
| 67 | +template <StatusConvertible Ret, ErrorCallback Callback> |
| 68 | +auto failMsg(Callback &&callback, StatusCodes code, std::string_view prefix, std::string_view detail) -> Ret |
| 69 | +{ |
| 70 | + std::string full; |
| 71 | + full.reserve(prefix.size() + 2 + detail.size()); |
| 72 | + full.append(prefix); |
| 73 | + full.append(": "); |
| 74 | + full.append(detail); |
| 75 | + invokeError(std::forward<Callback>(callback), code, full); |
| 76 | + return static_cast<Ret>(code); |
| 77 | +} |
| 78 | + |
| 79 | +// Pipe-style error chaining |
| 80 | + |
| 81 | +/** |
| 82 | + * Chain operations that return a status-code integer. |
| 83 | + * Stops at the first non-success result and returns it. |
| 84 | + * auto result = chainStatus( |
| 85 | + * [&] { return configureBaudrate(h, 9600); }, |
| 86 | + * [&] { return configureParity(h, 0); }, |
| 87 | + * [&] { return configureStopBits(h, 1); } |
| 88 | + * ); |
| 89 | + */ |
| 90 | +template <std::invocable... Fns> |
| 91 | +requires(std::is_convertible_v<std::invoke_result_t<Fns>, int> && ...) |
| 92 | +constexpr auto chainStatus(Fns &&...fns) -> int |
| 93 | +{ |
| 94 | + int result = 0; |
| 95 | + ((static_cast<void>(result = static_cast<int>(std::forward<Fns>(fns)())), result < 0) || ...); |
| 96 | + return result; |
| 97 | +} |
| 98 | + |
| 99 | +} // namespace cpp_core |
0 commit comments