Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 192 additions & 90 deletions include/scale/detail/enum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,124 +5,224 @@
*/

/**
* @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>
consteval 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);
static_assert(prefix_pos != std::string_view::npos);
constexpr std::size_t start = prefix_pos + enum_prefix.size();
if (func[start] == '(')
return {}; // Invalid: __PRETTY_FUNCTION__ prints invalid value of enum
// as number C-style-casted into enum type, i.e. `(Enum)-1`.
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: __PRETTY_FUNCTION__ always prints valid value
// with type, i.e. `Enum::value`
return full.substr(colons + 2);
}

/**
* @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 } -> std::ranges::range;
};

template <typename E>
concept HasMinMax = requires {
{
enum_traits<E>::min_value
} -> std::convertible_to<std::underlying_type_t<E>>;
{
enum_traits<E>::max_value
} -> std::convertible_to<std::underlying_type_t<E>>;
};

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(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may cause confusion with C++26 reflection.

Copy link
Member Author

Choose a reason for hiding this comment

The 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")]]
template <Enumeration 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)>
Expand All @@ -133,9 +233,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)>
Expand All @@ -153,12 +254,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 <> \
Expand All @@ -169,10 +270,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 <> \
Expand Down
Loading