forked from Harrm/scale-codec
-
Notifications
You must be signed in to change notification settings - Fork 7
Feature: validate one-byte enum value by reflection #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,124 +5,216 @@ | |
| */ | ||
|
|
||
| /** | ||
| * @brief Implements encoding and validation of enumeration values using SCALE | ||
| * encoding. | ||
| * @brief Implements encoding and validation of enumeration values using SCALE. | ||
| * | ||
| * This file provides utilities for handling enumerations in SCALE encoding. | ||
| * It allows defining constraints on enum values via `enum_traits` | ||
| * specializations, ensuring that only valid values are encoded or decoded. | ||
| * There are two ways to specialize an enumeration type: | ||
| * | ||
| * 1. **Defining a range of valid values** using `min_value` and `max_value`. | ||
| * 2. **Providing an explicit list of valid values** using `valid_values`. | ||
| * There are two ways to specialize an enumeration type: | ||
| * - Define a range using `min_value` and `max_value`. | ||
| * - Provide an explicit list using `valid_values`. | ||
| * | ||
| * The validation ensures that any decoded value belongs to the expected set, | ||
| * reducing the risk of unexpected errors when processing SCALE-encoded data. | ||
| * Validation guarantees decoded values are within expected bounds, | ||
| * reducing risk when handling SCALE-encoded data. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <algorithm> | ||
| #include <limits> | ||
| #include <string_view> | ||
| #include <type_traits> | ||
|
|
||
| #include <scale/detail/tagged.hpp> | ||
| #include <scale/outcome/outcome_throw.hpp> | ||
| #include <scale/scale_error.hpp> | ||
| #include <scale/types.hpp> | ||
|
|
||
| namespace scale { | ||
|
|
||
| namespace detail::enumerations { | ||
|
|
||
| template <typename T> | ||
| concept Enumeration = std::is_enum_v<std::remove_cvref_t<T>>; | ||
| #define ENUM_NAME_PRETTY_FUNCTION __PRETTY_FUNCTION__ | ||
| #if defined(__clang__) | ||
| // clang-format off | ||
| // Invalid: | ||
| // "std::string_view scale::detail::enumerations::enum_name_impl() [V = (Baz)-128]" | ||
| // Valid: | ||
| // "std::string_view scale::detail::enumerations::enum_name_impl() [V = Enum_ValidatingByReflection_I8_Test::TestBody()::Baz::A]" | ||
| // clang-format on | ||
| constexpr std::string_view enum_prefix = "EnumValue = "; | ||
| constexpr std::string_view enum_suffix = "]"; | ||
| #elif defined(__GNUC__) | ||
| // clang-format off | ||
| // Invalid: | ||
| // "std::string_view scale::detail::enumerations::enum_name_impl() [with auto V = (Enum_ValidatingByReflection_I8_Test::TestBody::Baz)-128; std::string_view = std::basic_string_view<char>]" | ||
| // Valid: | ||
| // "std::string_view scale::detail::enumerations::enum_name_impl() [with auto V = Enum_ValidatingByReflection_I8_Test::TestBody::Baz::A; std::string_view = std::basic_string_view<char>]" | ||
| // clang-format on | ||
| constexpr std::string_view enum_prefix = "EnumValue = "; | ||
| constexpr std::string_view enum_suffix = "; "; | ||
| #else | ||
| #error Unsupported compiler | ||
| #endif | ||
|
|
||
| /** | ||
| * @brief Traits for enum validation. | ||
| * | ||
| * Provides two specialization choices: | ||
| * - `min_value` and `max_value` convertible to `std::underlying_type_t<E>`. | ||
| * - A container of `std::underlying_type_t<E>` named `valid_values`, | ||
| * listing valid values. | ||
| * | ||
| * @note Check the macros below for specialization convenience. | ||
| * @tparam E The enum type. | ||
| * @brief Extracts enumeration name from the compiler-specific string. | ||
| * @tparam V The enum value. | ||
| */ | ||
| template <typename E> | ||
| requires std::is_enum_v<E> | ||
| struct [[deprecated( | ||
| "Check the doc comment to see the specialization options")]] // | ||
| enum_traits final { | ||
| /// Used to detect an unspecialized enum_traits | ||
| static constexpr bool is_default = true; | ||
| }; | ||
| template <auto EnumValue> | ||
| std::string_view enum_name_impl() { | ||
| constexpr std::string_view func = ENUM_NAME_PRETTY_FUNCTION; | ||
| constexpr std::size_t prefix_pos = func.find(enum_prefix); | ||
| assert(prefix_pos != std::string_view::npos); | ||
| constexpr std::size_t start = prefix_pos + enum_prefix.size(); | ||
| if (func[start] == '(') return {}; // Invalid, because wrapped by parenthesis) | ||
| constexpr std::size_t end = func.find(enum_suffix, start); | ||
| constexpr std::string_view full = func.substr(start, end - start); | ||
| constexpr std::size_t colons = full.rfind("::"); | ||
| if (colons == std::string_view::npos) return {}; // Invalid, no namespace | ||
| constexpr std::string_view result = full.substr(colons + 2); | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * @brief Checks if a given value is within a defined range of valid enum | ||
| * values. | ||
| * @tparam T The input type (expected to be an enum or convertible | ||
| * underlying type). | ||
| * @param value The value to check. | ||
| * @return True if the value is within the range, false otherwise. | ||
| * @brief Concept that checks if a type is an enumeration. | ||
| */ | ||
| template <typename T, | ||
| typename E = std::decay_t<T>, | ||
| typename E_traits = enum_traits<E>, | ||
| std::underlying_type_t<E> Min = E_traits::min_value, | ||
| std::underlying_type_t<E> Max = E_traits::max_value> | ||
| constexpr bool is_valid_enum_value( | ||
| std::underlying_type_t<E> value) noexcept { | ||
| return value >= Min && value <= Max; | ||
| } | ||
| template <typename T> | ||
| concept Enumeration = std::is_enum_v<std::remove_cvref_t<T>>; | ||
|
|
||
| /** | ||
| * @brief Checks if a given value is within an explicitly defined set of | ||
| * valid enum values. | ||
| * @tparam T The input type (expected to be an enum or convertible | ||
| * underlying type). | ||
| * @param value The value to check. | ||
| * @return True if the value is listed in `valid_values`, false otherwise. | ||
| * @brief Traits struct to be specialized for enumeration validation. | ||
| */ | ||
| template <typename T, | ||
| typename E = std::decay_t<T>, | ||
| typename E_traits = enum_traits<E>, | ||
| typename = decltype(E_traits::valid_values)> | ||
| constexpr bool is_valid_enum_value( | ||
| std::underlying_type_t<E> value) noexcept { | ||
| const auto &valid_values = E_traits::valid_values; | ||
| return std::find(std::begin(valid_values), | ||
| std::end(valid_values), | ||
| static_cast<E>(value)) | ||
| != std::end(valid_values); | ||
| } | ||
| template <typename E> | ||
| struct enum_traits; // default not specialized | ||
|
|
||
| namespace detail_impl { | ||
|
|
||
| template <typename E> | ||
| concept HasValidValues = requires { enum_traits<E>::valid_values; }; | ||
xDimon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| template <typename E> | ||
| concept HasMinMax = requires { | ||
| enum_traits<E>::min_value; | ||
| enum_traits<E>::max_value; | ||
xDimon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| template <typename E, typename U = std::underlying_type_t<E>, U... Vs> | ||
| constexpr bool is_valid_enum_value_by_reflection_impl( | ||
| U value, std::integer_sequence<U, Vs...>) { | ||
| return ((enum_name_impl<static_cast<E>(Vs)>().size() > 0 | ||
| && static_cast<U>(static_cast<E>(Vs)) == value) | ||
| || ...); | ||
| } | ||
|
|
||
| template <typename E, int... Is> | ||
| constexpr bool call_with_casted_signed_range( | ||
| std::underlying_type_t<E> value, std::integer_sequence<int, Is...>) { | ||
| using U = std::underlying_type_t<E>; | ||
| constexpr int min = -128; | ||
| return is_valid_enum_value_by_reflection_impl<E>( | ||
| value, std::integer_sequence<U, static_cast<U>(min + Is)...>{}); | ||
| } | ||
|
|
||
| template <typename E, int... Is> | ||
| constexpr bool call_with_casted_unsigned_range( | ||
| std::underlying_type_t<E> value, std::integer_sequence<int, Is...>) { | ||
| using U = std::underlying_type_t<E>; | ||
| return is_valid_enum_value_by_reflection_impl<E>( | ||
| value, std::integer_sequence<U, static_cast<U>(Is)...>{}); | ||
| } | ||
|
|
||
| /** | ||
| * @brief Fallback validation using reflection for enums of size 1 byte. | ||
| */ | ||
| template <typename E> | ||
| requires(sizeof(std::underlying_type_t<E>) == 1) | ||
| constexpr bool is_valid_enum_value_by_reflection( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this may cause confusion with C++26 reflection.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll worry about that after switch to C++26 |
||
| std::underlying_type_t<E> value) { | ||
| using U = std::underlying_type_t<E>; | ||
|
|
||
| if constexpr (std::is_signed_v<U>) { | ||
| constexpr int min = -128; | ||
| constexpr int max = 127; | ||
| return call_with_casted_signed_range<E>( | ||
| value, std::make_integer_sequence<int, max - min + 1>{}); | ||
| } else { | ||
| constexpr int max = 255; | ||
| return call_with_casted_unsigned_range<E>( | ||
| value, std::make_integer_sequence<int, max + 1>{}); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @brief Validates enum value using min/max range. | ||
| */ | ||
| template <typename T> | ||
| requires HasMinMax<std::decay_t<T>> | ||
| constexpr bool is_valid_enum_value_range( | ||
| std::underlying_type_t<std::decay_t<T>> value) noexcept { | ||
| using E = std::decay_t<T>; | ||
| constexpr auto Min = enum_traits<E>::min_value; | ||
| constexpr auto Max = enum_traits<E>::max_value; | ||
| return value >= Min && value <= Max; | ||
| } | ||
|
|
||
| /** | ||
| * @brief Validates enum value against list of valid values. | ||
| */ | ||
| template <typename T> | ||
| requires HasValidValues<std::decay_t<T>> | ||
| constexpr bool is_valid_enum_value_list( | ||
| std::underlying_type_t<std::decay_t<T>> value) noexcept { | ||
| using E = std::decay_t<T>; | ||
| const auto &valid_values = enum_traits<E>::valid_values; | ||
| return std::find(valid_values.begin(), | ||
| valid_values.end(), | ||
| static_cast<E>(value)) | ||
| != valid_values.end(); | ||
| } | ||
|
|
||
| } // namespace detail_impl | ||
|
|
||
| template <typename T> | ||
| constexpr bool CannotValidateEnum = false; | ||
|
|
||
| /** | ||
| * @brief Default case for unspecialized enum types. | ||
| * | ||
| * This function always returns `true`, but a `static_assert` ensures that | ||
| * an explicit specialization of `enum_traits` is required. | ||
| * | ||
| * @tparam T The input type (expected to be an enum). | ||
| * @return Always true, but triggers a compilation error if used. | ||
| * @brief Central enum validation entry point. | ||
| * @tparam T Enum type. | ||
| * @param value The underlying integer value. | ||
| * @return true if value is valid. | ||
| */ | ||
| template <typename T> | ||
| requires enum_traits<std::decay_t<T>>::is_default | ||
| [[deprecated( | ||
| "Please specialize scale::enum_traits for your enum so it can be " | ||
| "validated during decoding")]] | ||
| requires std::is_enum_v<std::decay_t<T>> | ||
| constexpr bool is_valid_enum_value( | ||
| std::underlying_type_t<std::decay_t<T>>) noexcept { | ||
| return true; | ||
| std::underlying_type_t<std::decay_t<T>> value) noexcept { | ||
| using E = std::decay_t<T>; | ||
|
|
||
| if constexpr (detail_impl::HasValidValues<E>) { | ||
| return detail_impl::is_valid_enum_value_list<T>(value); | ||
| } else if constexpr (detail_impl::HasMinMax<E>) { | ||
| return detail_impl::is_valid_enum_value_range<T>(value); | ||
| } else if constexpr (sizeof(std::underlying_type_t<E>) == 1) { | ||
| return detail_impl::is_valid_enum_value_by_reflection<E>(value); | ||
| } else { | ||
| static_assert(CannotValidateEnum<T>, | ||
| "Cannot validate enum: " | ||
| "define enum_traits<> with min/max or valid_values."); | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| } // namespace detail::enumerations | ||
|
|
||
| using detail::enumerations::enum_traits; | ||
| using detail::enumerations::Enumeration; | ||
| using detail::enumerations::is_valid_enum_value; | ||
|
|
||
| /** | ||
| * @brief Encodes an enumeration into its underlying type. | ||
| * @param enumeration The enumeration value to encode. | ||
| * @param encoder SCALE encoder. | ||
| * @brief Encodes an enum value using SCALE encoding. | ||
| * @tparam T Enum type. | ||
| * @param enumeration Enum value to encode. | ||
| * @param encoder Encoder instance. | ||
| */ | ||
| void encode(const Enumeration auto &enumeration, Encoder &encoder) | ||
| requires NoTagged<decltype(enumeration)> | ||
|
|
@@ -133,9 +225,10 @@ namespace scale { | |
| } | ||
|
|
||
| /** | ||
| * @brief Decodes an enumeration from its underlying type. | ||
| * @param v The enumeration value to decode into. | ||
| * @param decoder SCALE decoder. | ||
| * @brief Decodes an enum value using SCALE decoding. | ||
| * @tparam T Enum type. | ||
| * @param v Enum variable to store the decoded value. | ||
| * @param decoder Decoder instance. | ||
| */ | ||
| void decode(Enumeration auto &v, Decoder &decoder) | ||
| requires NoTagged<decltype(v)> | ||
|
|
@@ -153,12 +246,12 @@ namespace scale { | |
| } // namespace scale | ||
|
|
||
| /** | ||
| * @brief Defines a valid range for an enumeration. | ||
| * @note You should use this macro only in the global namespace | ||
| * @param enum_namespace The namespace of the enum. | ||
| * @param enum_name The enum type. | ||
| * @param min The minimum valid value. | ||
| * @param max The maximum valid value. | ||
| * @def SCALE_DEFINE_ENUM_VALUE_RANGE | ||
| * @brief Defines a valid value range for an enum. | ||
| * @param enum_namespace Namespace where enum is defined. | ||
| * @param enum_name Enum type name. | ||
| * @param min Minimum valid value. | ||
| * @param max Maximum valid value. | ||
| */ | ||
| #define SCALE_DEFINE_ENUM_VALUE_RANGE(enum_namespace, enum_name, min, max) \ | ||
| template <> \ | ||
|
|
@@ -169,10 +262,11 @@ namespace scale { | |
| }; | ||
|
|
||
| /** | ||
| * @brief Defines a valid list of values for an enumeration. | ||
| * @param enum_namespace The namespace of the enum. | ||
| * @param enum_name The enum type. | ||
| * @param ... The valid values. | ||
| * @def SCALE_DEFINE_ENUM_VALUE_LIST | ||
| * @brief Defines an explicit list of valid enum values. | ||
| * @param enum_namespace Namespace where enum is defined. | ||
| * @param enum_name Enum type name. | ||
| * @param ... List of valid values. | ||
| */ | ||
| #define SCALE_DEFINE_ENUM_VALUE_LIST(enum_namespace, enum_name, ...) \ | ||
| template <> \ | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.