diff --git a/include/cpp_core/interface/serial_abort_read.h b/include/cpp_core/interface/serial_abort_read.h index 8db85bb..6ac8008 100644 --- a/include/cpp_core/interface/serial_abort_read.h +++ b/include/cpp_core/interface/serial_abort_read.h @@ -12,11 +12,11 @@ extern "C" * @brief Abort a blocking read operation running in a different thread. * * The target read function returns immediately with - * ::cpp_core::StatusCodes::kAbortReadError. + * ::cpp_core::StatusCode::Io::kAbortReadError. * * @param handle Port handle. * @param error_callback [optional] Callback to invoke on error. Defined in error_callback.h. Default is `nullptr`. - * @return 0 on success or a negative error code from ::cpp_core::StatusCodes on error. + * @return 0 on success or a negative error code from ::cpp_core::StatusCode on error. */ MODULE_API auto serialAbortRead(int64_t handle, ErrorCallbackT error_callback = nullptr) -> int; diff --git a/include/cpp_core/interface/serial_abort_write.h b/include/cpp_core/interface/serial_abort_write.h index 46fd50f..d579d94 100644 --- a/include/cpp_core/interface/serial_abort_write.h +++ b/include/cpp_core/interface/serial_abort_write.h @@ -12,11 +12,11 @@ extern "C" * @brief Abort a blocking write operation running in a different thread. * * The target write function returns immediately with - * ::cpp_core::StatusCodes::kAbortWriteError. + * ::cpp_core::StatusCode::Io::kAbortWriteError. * * @param handle Port handle. * @param error_callback [optional] Callback to invoke on error. Defined in error_callback.h. Default is `nullptr`. - * @return 0 on success or a negative error code from ::cpp_core::StatusCodes on error. + * @return 0 on success or a negative error code from ::cpp_core::StatusCode on error. */ MODULE_API auto serialAbortWrite(int64_t handle, ErrorCallbackT error_callback = nullptr) -> int; diff --git a/include/cpp_core/status_code.h b/include/cpp_core/status_code.h new file mode 100644 index 0000000..4f652a2 --- /dev/null +++ b/include/cpp_core/status_code.h @@ -0,0 +1,179 @@ +#pragma once + +#include +#include +#include + +namespace cpp_core::status_codes +{ + +namespace detail +{ + +using ValueType = std::int64_t; + +inline constexpr ValueType kCategoryMultiplier{100}; + +template struct Code +{ + static constexpr ValueType kValue = NumericValue; + std::string_view kName; // NOLINT(readability-identifier-naming) + + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + constexpr operator ValueType() const noexcept + { + return kValue; + } + [[nodiscard]] constexpr auto value() const noexcept -> ValueType + { + return kValue; + } + [[nodiscard]] constexpr auto name() const noexcept -> std::string_view + { + return kName; + } + [[nodiscard]] constexpr auto category() const noexcept -> std::string_view + { + return Category::kCategoryName; + } +}; + +template struct CategoryBase +{ + private: + constexpr CategoryBase() = default; + + protected: + template static consteval auto computeValue() -> ValueType + { + static_assert(Derived::kCategoryCode >= 0, "Category code must not be negative"); + static_assert(LocalCode >= 0, "Code index must not be negative"); + static_assert(LocalCode < kCategoryMultiplier, "Category overflow (max 99 codes)"); + static_assert(Derived::kCategoryCode <= + (std::numeric_limits::max() - kCategoryMultiplier + 1) / kCategoryMultiplier, + "Category code too large, multiplication would overflow"); + return -((Derived::kCategoryCode * kCategoryMultiplier) + LocalCode); + } + + template using Code = detail::Code()>; + + friend Derived; +}; + +} // namespace detail + +struct StatusCode +{ + using ValueType = detail::ValueType; + static constexpr ValueType kSuccess = 0; + + struct Configuration : detail::CategoryBase + { + static constexpr ValueType kCategoryCode = 1; + static constexpr std::string_view kCategoryName{"Configuration"}; + + static constexpr Code<0> kSetBaudrateError{"SetBaudrateError"}; + static constexpr Code<1> kSetDataBitsError{"SetDataBitsError"}; + static constexpr Code<2> kSetParityError{"SetParityError"}; + static constexpr Code<3> kSetStopBitsError{"SetStopBitsError"}; + static constexpr Code<4> kSetFlowControlError{"SetFlowControlError"}; + static constexpr Code<5> kSetTimeoutError{"SetTimeoutError"}; + }; + + struct Connection : detail::CategoryBase + { + static constexpr ValueType kCategoryCode = 2; + static constexpr std::string_view kCategoryName{"Connection"}; + + static constexpr Code<0> kNotFoundError{"NotFoundError"}; + static constexpr Code<1> kInvalidHandleError{"InvalidHandleError"}; + static constexpr Code<2> kCloseHandleError{"CloseHandleError"}; + }; + + struct Io : detail::CategoryBase + { + static constexpr ValueType kCategoryCode = 3; + static constexpr std::string_view kCategoryName{"Io"}; + + static constexpr Code<0> kReadError{"ReadError"}; + static constexpr Code<1> kWriteError{"WriteError"}; + static constexpr Code<2> kAbortReadError{"AbortReadError"}; + static constexpr Code<3> kAbortWriteError{"AbortWriteError"}; + static constexpr Code<4> kBufferError{"BufferError"}; + static constexpr Code<5> kClearBufferInError{"ClearBufferInError"}; + static constexpr Code<6> kClearBufferOutError{"ClearBufferOutError"}; + }; + + struct Control : detail::CategoryBase + { + static constexpr ValueType kCategoryCode = 4; + static constexpr std::string_view kCategoryName{"Control"}; + + static constexpr Code<0> kSetDtrError{"SetDtrError"}; + static constexpr Code<1> kSetRtsError{"SetRtsError"}; + static constexpr Code<2> kGetModemStatusError{"GetModemStatusError"}; + static constexpr Code<3> kSendBreakError{"SendBreakError"}; + static constexpr Code<4> kGetStateError{"GetStateError"}; + static constexpr Code<5> kSetStateError{"SetStateError"}; + }; + + [[nodiscard]] static constexpr auto isError(ValueType code) noexcept -> bool + { + return code < 0; + } + [[nodiscard]] static constexpr auto isSuccess(ValueType code) noexcept -> bool + { + return code >= 0; + } + template [[nodiscard]] static constexpr auto belongsTo(ValueType code) noexcept -> bool + { + return code < 0 && (-code) / detail::kCategoryMultiplier == Category::kCategoryCode; + } +}; + +} // namespace cpp_core::status_codes + +namespace cpp_core +{ +using ::cpp_core::status_codes::StatusCode; +} // namespace cpp_core + +namespace cpp_core::status_codes::detail::tests +{ + +template struct FakeCategory : CategoryBase> +{ + static constexpr ValueType kCategoryCode = CatCode; + + template static consteval auto call() -> ValueType + { + return CategoryBase::template computeValue(); + } +}; + +// Formula: result == -(kCategoryCode * 100 + LocalCode) +static_assert(FakeCategory<1>::call<0>() == -100); +static_assert(FakeCategory<1>::call<42>() == -142); +static_assert(FakeCategory<3>::call<7>() == -307); + +// Edge: kCategoryCode == 0 -> call<0>() produces 0 (not negative) +static_assert(FakeCategory<0>::call<0>() == 0); +static_assert(FakeCategory<0>::call<1>() == -1); + +// Edge: LocalCode == 99 (max before overflow guard) +static_assert(FakeCategory<2>::call<99>() == -299); + +// Consecutive codes differ by exactly -1 +static_assert(FakeCategory<1>::call<1>() - FakeCategory<1>::call<0>() == -1); + +// Adjacent category ranges don't overlap (last of cat N > first of cat N+1) +static_assert(FakeCategory<1>::call<99>() > FakeCategory<2>::call<0>()); + +// Overflow: largest safe category still produces correct results +inline constexpr ValueType kMaxSafeCat = + (std::numeric_limits::max() - kCategoryMultiplier + 1) / kCategoryMultiplier; +static_assert(kMaxSafeCat > 1'000'000); +static_assert(FakeCategory::call<0>() == -(kMaxSafeCat * kCategoryMultiplier)); +static_assert(FakeCategory::call<99>() > std::numeric_limits::min()); + +} // namespace cpp_core::status_codes::detail::tests diff --git a/include/cpp_core/status_codes.h b/include/cpp_core/status_codes.h deleted file mode 100644 index 4da48ce..0000000 --- a/include/cpp_core/status_codes.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -namespace cpp_core -{ -enum class StatusCodes -{ - kSuccess = 0, - kCloseHandleError = -1, - kInvalidHandleError = -2, - kReadError = -3, - kWriteError = -4, - kGetStateError = -5, - kSetStateError = -6, - kSetTimeoutError = -7, - kBufferError = -8, - kNotFoundError = -9, - kClearBufferInError = -10, - kClearBufferOutError = -11, - kAbortReadError = -12, - kAbortWriteError = -13, - kSetDtrError = -14, - kSetRtsError = -15, - kGetModemStatusError = -16, - kSendBreakError = -17, - kSetFlowControlError = -18, - kSetBaudrateError = -19, - kSetDataBitsError = -20, - kSetParityError = -22, - kSetStopBitsError = -23, -}; -} // namespace cpp_core diff --git a/include/cpp_core/status_codes.hpp b/include/cpp_core/status_codes.hpp deleted file mode 100644 index 3573de2..0000000 --- a/include/cpp_core/status_codes.hpp +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include "status_codes.h" - -#include -#include - -namespace cpp_core -{ - -// constexpr toString -[[nodiscard]] constexpr auto toString(StatusCodes code) noexcept -> std::string_view -{ - switch (code) - { - case StatusCodes::kSuccess: - return "Success"; - case StatusCodes::kCloseHandleError: - return "CloseHandleError"; - case StatusCodes::kInvalidHandleError: - return "InvalidHandleError"; - case StatusCodes::kReadError: - return "ReadError"; - case StatusCodes::kWriteError: - return "WriteError"; - case StatusCodes::kGetStateError: - return "GetStateError"; - case StatusCodes::kSetStateError: - return "SetStateError"; - case StatusCodes::kSetTimeoutError: - return "SetTimeoutError"; - case StatusCodes::kBufferError: - return "BufferError"; - case StatusCodes::kNotFoundError: - return "NotFoundError"; - case StatusCodes::kClearBufferInError: - return "ClearBufferInError"; - case StatusCodes::kClearBufferOutError: - return "ClearBufferOutError"; - case StatusCodes::kAbortReadError: - return "AbortReadError"; - case StatusCodes::kAbortWriteError: - return "AbortWriteError"; - case StatusCodes::kSetDtrError: - return "SetDtrError"; - case StatusCodes::kSetRtsError: - return "SetRtsError"; - case StatusCodes::kGetModemStatusError: - return "GetModemStatusError"; - case StatusCodes::kSendBreakError: - return "SendBreakError"; - case StatusCodes::kSetFlowControlError: - return "SetFlowControlError"; - case StatusCodes::kSetBaudrateError: - return "SetBaudrateError"; - case StatusCodes::kSetDataBitsError: - return "SetDataBitsError"; - case StatusCodes::kSetParityError: - return "SetParityError"; - case StatusCodes::kSetStopBitsError: - return "SetStopBitsError"; - } - return "Unknown"; -} - -[[nodiscard]] constexpr auto isError(StatusCodes code) noexcept -> bool -{ - return static_cast(code) < 0; -} - -[[nodiscard]] constexpr auto isSuccess(StatusCodes code) noexcept -> bool -{ - return code == StatusCodes::kSuccess; -} - -/** - * Convert a raw int (as returned by C API functions) back to a StatusCodes. - * Returns kSuccess for any non-negative value. - */ -[[nodiscard]] constexpr auto fromInt(int value) noexcept -> StatusCodes -{ - if (value >= 0) - { - return StatusCodes::kSuccess; - } - return static_cast(value); -} - -} // namespace cpp_core - -// std::format support -/** - * Enables: std::format("Port open failed: {}", StatusCodes::kNotFoundError) - * => "Port open failed: NotFoundError (-9)" - */ -template <> struct std::formatter : std::formatter -{ - auto format(cpp_core::StatusCodes code, auto &ctx) const - { - auto name = cpp_core::toString(code); - auto val = static_cast(code); - if (val == 0) - { - return std::formatter::format(name, ctx); - } - return std::format_to(ctx.out(), "{} ({})", name, val); - } -}; diff --git a/include/cpp_core/validation.hpp b/include/cpp_core/validation.hpp index f910d00..8abcdd1 100644 --- a/include/cpp_core/validation.hpp +++ b/include/cpp_core/validation.hpp @@ -19,9 +19,10 @@ constexpr auto validateHandle(int64_t handle, Callback &&error_callback) -> Ret { if (handle <= 0 || handle > std::numeric_limits::max()) { - return failMsg(std::forward(error_callback), StatusCodes::kInvalidHandleError, "Invalid handle"); + return failMsg(std::forward(error_callback), + static_cast(StatusCode::Connection::kInvalidHandleError), "Invalid handle"); } - return static_cast(StatusCodes::kSuccess); + return static_cast(StatusCode::kSuccess); } /** @@ -33,20 +34,23 @@ constexpr auto validateOpenParams(void *port, int baudrate, int data_bits, Callb { if (port == nullptr) { - return failMsg(std::forward(error_callback), StatusCodes::kNotFoundError, + return failMsg(std::forward(error_callback), + static_cast(StatusCode::Connection::kNotFoundError), "Port parameter is nullptr"); } if (baudrate < 300) { - return failMsg(std::forward(error_callback), StatusCodes::kSetStateError, + return failMsg(std::forward(error_callback), + static_cast(StatusCode::Control::kSetStateError), "Invalid baudrate: must be >= 300"); } if (data_bits < 5 || data_bits > 8) { - return failMsg(std::forward(error_callback), StatusCodes::kSetStateError, + return failMsg(std::forward(error_callback), + static_cast(StatusCode::Control::kSetStateError), "Invalid data bits: must be 5-8"); } - return static_cast(StatusCodes::kSuccess); + return static_cast(StatusCode::kSuccess); } // Validate buffer + size for read/write calls. @@ -55,10 +59,11 @@ constexpr auto validateBuffer(const void *buffer, int buffer_size, Callback &&er { if (buffer == nullptr || buffer_size <= 0) { - return failMsg(std::forward(error_callback), StatusCodes::kBufferError, + return failMsg(std::forward(error_callback), + static_cast(StatusCode::Io::kBufferError), "Invalid buffer or buffer_size"); } - return static_cast(StatusCodes::kSuccess); + return static_cast(StatusCode::kSuccess); } // Clamp timeout to non-negative.