55 */
66
77/* *
8- * @brief Implements encoding and validation of enumeration values using SCALE
9- * encoding.
8+ * @brief Implements encoding and validation of enumeration values using SCALE.
109 *
1110 * This file provides utilities for handling enumerations in SCALE encoding.
1211 * It allows defining constraints on enum values via `enum_traits`
1312 * specializations, ensuring that only valid values are encoded or decoded.
14- * There are two ways to specialize an enumeration type:
1513 *
16- * 1. **Defining a range of valid values** using `min_value` and `max_value`.
17- * 2. **Providing an explicit list of valid values** using `valid_values`.
14+ * There are two ways to specialize an enumeration type:
15+ * - Define a range using `min_value` and `max_value`.
16+ * - Provide an explicit list using `valid_values`.
1817 *
19- * The validation ensures that any decoded value belongs to the expected set ,
20- * reducing the risk of unexpected errors when processing SCALE-encoded data.
18+ * Validation guarantees decoded values are within expected bounds ,
19+ * reducing risk when handling SCALE-encoded data.
2120 */
2221
2322#pragma once
2423
24+ #include < algorithm>
25+ #include < limits>
26+ #include < string_view>
2527#include < type_traits>
2628
2729#include < scale/detail/tagged.hpp>
2830#include < scale/outcome/outcome_throw.hpp>
2931#include < scale/scale_error.hpp>
30- #include < scale/types.hpp>
3132
3233namespace scale {
3334
3435 namespace detail ::enumerations {
3536
36- template <typename T>
37- concept Enumeration = std::is_enum_v<std::remove_cvref_t <T>>;
37+ #define ENUM_NAME_PRETTY_FUNCTION __PRETTY_FUNCTION__
38+ #if defined(__clang__)
39+ // clang-format off
40+ // Invalid:
41+ // "std::string_view scale::detail::enumerations::enum_name_impl() [V = (Baz)-128]"
42+ // Valid:
43+ // "std::string_view scale::detail::enumerations::enum_name_impl() [V = Enum_ValidatingByReflection_I8_Test::TestBody()::Baz::A]"
44+ // clang-format on
45+ constexpr std::string_view enum_prefix = " EnumValue = " ;
46+ constexpr std::string_view enum_suffix = " ]" ;
47+ #elif defined(__GNUC__)
48+ // clang-format off
49+ // Invalid:
50+ // "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>]"
51+ // Valid:
52+ // "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>]"
53+ // clang-format on
54+ constexpr std::string_view enum_prefix = " EnumValue = " ;
55+ constexpr std::string_view enum_suffix = " ; " ;
56+ #else
57+ #error Unsupported compiler
58+ #endif
3859
3960 /* *
40- * @brief Traits for enum validation.
41- *
42- * Provides two specialization choices:
43- * - `min_value` and `max_value` convertible to `std::underlying_type_t<E>`.
44- * - A container of `std::underlying_type_t<E>` named `valid_values`,
45- * listing valid values.
46- *
47- * @note Check the macros below for specialization convenience.
48- * @tparam E The enum type.
61+ * @brief Extracts enumeration name from the compiler-specific string.
62+ * @tparam V The enum value.
4963 */
50- template <typename E>
51- requires std::is_enum_v<E>
52- struct [[deprecated(
53- " Check the doc comment to see the specialization options" )]] //
54- enum_traits final {
55- // / Used to detect an unspecialized enum_traits
56- static constexpr bool is_default = true ;
57- };
64+ template <auto EnumValue>
65+ consteval std::string_view enum_name_impl () {
66+ constexpr std::string_view func = ENUM_NAME_PRETTY_FUNCTION;
67+ constexpr std::size_t prefix_pos = func.find (enum_prefix);
68+ static_assert (prefix_pos != std::string_view::npos);
69+ constexpr std::size_t start = prefix_pos + enum_prefix.size ();
70+ if (func[start] == ' (' )
71+ return {}; // Invalid: __PRETTY_FUNCTION__ prints invalid value of enum
72+ // as number C-style-casted into enum type, i.e. `(Enum)-1`.
73+ constexpr std::size_t end = func.find (enum_suffix, start);
74+ constexpr std::string_view full = func.substr (start, end - start);
75+ constexpr std::size_t colons = full.rfind (" ::" );
76+ if (colons == std::string_view::npos)
77+ return {}; // Invalid: __PRETTY_FUNCTION__ always prints valid value
78+ // with type, i.e. `Enum::value`
79+ return full.substr (colons + 2 );
80+ }
5881
5982 /* *
60- * @brief Checks if a given value is within a defined range of valid enum
61- * values.
62- * @tparam T The input type (expected to be an enum or convertible
63- * underlying type).
64- * @param value The value to check.
65- * @return True if the value is within the range, false otherwise.
83+ * @brief Concept that checks if a type is an enumeration.
6684 */
67- template <typename T,
68- typename E = std::decay_t <T>,
69- typename E_traits = enum_traits<E>,
70- std::underlying_type_t <E> Min = E_traits::min_value,
71- std::underlying_type_t <E> Max = E_traits::max_value>
72- constexpr bool is_valid_enum_value (
73- std::underlying_type_t <E> value) noexcept {
74- return value >= Min && value <= Max;
75- }
85+ template <typename T>
86+ concept Enumeration = std::is_enum_v<std::remove_cvref_t <T>>;
7687
7788 /* *
78- * @brief Checks if a given value is within an explicitly defined set of
79- * valid enum values.
80- * @tparam T The input type (expected to be an enum or convertible
81- * underlying type).
82- * @param value The value to check.
83- * @return True if the value is listed in `valid_values`, false otherwise.
89+ * @brief Traits struct to be specialized for enumeration validation.
8490 */
85- template <typename T,
86- typename E = std::decay_t <T>,
87- typename E_traits = enum_traits<E>,
88- typename = decltype (E_traits::valid_values)>
89- constexpr bool is_valid_enum_value (
90- std::underlying_type_t <E> value) noexcept {
91- const auto &valid_values = E_traits::valid_values;
92- return std::find (std::begin (valid_values),
93- std::end (valid_values),
94- static_cast <E>(value))
95- != std::end (valid_values);
96- }
91+ template <typename E>
92+ struct enum_traits ; // default not specialized
93+
94+ namespace detail_impl {
95+
96+ template <typename E>
97+ concept HasValidValues = requires {
98+ { enum_traits<E>::valid_values } -> std::ranges::range;
99+ };
100+
101+ template <typename E>
102+ concept HasMinMax = requires {
103+ {
104+ enum_traits<E>::min_value
105+ } -> std::convertible_to<std::underlying_type_t <E>>;
106+ {
107+ enum_traits<E>::max_value
108+ } -> std::convertible_to<std::underlying_type_t <E>>;
109+ };
110+
111+ template <typename E, typename U = std::underlying_type_t <E>, U... Vs>
112+ constexpr bool is_valid_enum_value_by_reflection_impl (
113+ U value, std::integer_sequence<U, Vs...>) {
114+ return ((enum_name_impl<static_cast <E>(Vs)>().size () > 0
115+ && static_cast <U>(static_cast <E>(Vs)) == value)
116+ || ...);
117+ }
118+
119+ template <typename E, int ... Is>
120+ constexpr bool call_with_casted_signed_range (
121+ std::underlying_type_t <E> value, std::integer_sequence<int , Is...>) {
122+ using U = std::underlying_type_t <E>;
123+ constexpr int min = -128 ;
124+ return is_valid_enum_value_by_reflection_impl<E>(
125+ value, std::integer_sequence<U, static_cast <U>(min + Is)...>{});
126+ }
127+
128+ template <typename E, int ... Is>
129+ constexpr bool call_with_casted_unsigned_range (
130+ std::underlying_type_t <E> value, std::integer_sequence<int , Is...>) {
131+ using U = std::underlying_type_t <E>;
132+ return is_valid_enum_value_by_reflection_impl<E>(
133+ value, std::integer_sequence<U, static_cast <U>(Is)...>{});
134+ }
135+
136+ /* *
137+ * @brief Fallback validation using reflection for enums of size 1 byte.
138+ */
139+ template <typename E>
140+ requires (sizeof (std::underlying_type_t <E>) == 1 )
141+ constexpr bool is_valid_enum_value_by_reflection (
142+ std::underlying_type_t <E> value) {
143+ using U = std::underlying_type_t <E>;
144+
145+ if constexpr (std::is_signed_v<U>) {
146+ constexpr int min = -128 ;
147+ constexpr int max = 127 ;
148+ return call_with_casted_signed_range<E>(
149+ value, std::make_integer_sequence<int , max - min + 1 >{});
150+ } else {
151+ constexpr int max = 255 ;
152+ return call_with_casted_unsigned_range<E>(
153+ value, std::make_integer_sequence<int , max + 1 >{});
154+ }
155+ }
156+
157+ /* *
158+ * @brief Validates enum value using min/max range.
159+ */
160+ template <typename T>
161+ requires HasMinMax<std::decay_t <T>>
162+ constexpr bool is_valid_enum_value_range (
163+ std::underlying_type_t <std::decay_t <T>> value) noexcept {
164+ using E = std::decay_t <T>;
165+ constexpr auto Min = enum_traits<E>::min_value;
166+ constexpr auto Max = enum_traits<E>::max_value;
167+ return value >= Min && value <= Max;
168+ }
169+
170+ /* *
171+ * @brief Validates enum value against list of valid values.
172+ */
173+ template <typename T>
174+ requires HasValidValues<std::decay_t <T>>
175+ constexpr bool is_valid_enum_value_list (
176+ std::underlying_type_t <std::decay_t <T>> value) noexcept {
177+ using E = std::decay_t <T>;
178+ const auto &valid_values = enum_traits<E>::valid_values;
179+ return std::find (valid_values.begin (),
180+ valid_values.end (),
181+ static_cast <E>(value))
182+ != valid_values.end ();
183+ }
184+
185+ } // namespace detail_impl
186+
187+ template <typename T>
188+ constexpr bool CannotValidateEnum = false ;
97189
98190 /* *
99- * @brief Default case for unspecialized enum types.
100- *
101- * This function always returns `true`, but a `static_assert` ensures that
102- * an explicit specialization of `enum_traits` is required.
103- *
104- * @tparam T The input type (expected to be an enum).
105- * @return Always true, but triggers a compilation error if used.
191+ * @brief Central enum validation entry point.
192+ * @tparam T Enum type.
193+ * @param value The underlying integer value.
194+ * @return true if value is valid.
106195 */
107- template <typename T>
108- requires enum_traits<std::decay_t <T>>::is_default
109- [[deprecated(
110- " Please specialize scale::enum_traits for your enum so it can be "
111- " validated during decoding" )]]
196+ template <Enumeration T>
112197 constexpr bool is_valid_enum_value (
113- std::underlying_type_t <std::decay_t <T>>) noexcept {
114- return true ;
198+ std::underlying_type_t <std::decay_t <T>> value) noexcept {
199+ using E = std::decay_t <T>;
200+
201+ if constexpr (detail_impl::HasValidValues<E>) {
202+ return detail_impl::is_valid_enum_value_list<T>(value);
203+ } else if constexpr (detail_impl::HasMinMax<E>) {
204+ return detail_impl::is_valid_enum_value_range<T>(value);
205+ } else if constexpr (sizeof (std::underlying_type_t <E>) == 1 ) {
206+ return detail_impl::is_valid_enum_value_by_reflection<E>(value);
207+ } else {
208+ static_assert (CannotValidateEnum<T>,
209+ " Cannot validate enum: "
210+ " define enum_traits<> with min/max or valid_values." );
211+ return true ;
212+ }
115213 }
214+
116215 } // namespace detail::enumerations
117216
118217 using detail::enumerations::enum_traits;
119218 using detail::enumerations::Enumeration;
120219 using detail::enumerations::is_valid_enum_value;
121220
122221 /* *
123- * @brief Encodes an enumeration into its underlying type.
124- * @param enumeration The enumeration value to encode.
125- * @param encoder SCALE encoder.
222+ * @brief Encodes an enum value using SCALE encoding.
223+ * @tparam T Enum type.
224+ * @param enumeration Enum value to encode.
225+ * @param encoder Encoder instance.
126226 */
127227 void encode (const Enumeration auto &enumeration, Encoder &encoder)
128228 requires NoTagged<decltype(enumeration)>
@@ -133,9 +233,10 @@ namespace scale {
133233 }
134234
135235 /* *
136- * @brief Decodes an enumeration from its underlying type.
137- * @param v The enumeration value to decode into.
138- * @param decoder SCALE decoder.
236+ * @brief Decodes an enum value using SCALE decoding.
237+ * @tparam T Enum type.
238+ * @param v Enum variable to store the decoded value.
239+ * @param decoder Decoder instance.
139240 */
140241 void decode (Enumeration auto &v, Decoder &decoder)
141242 requires NoTagged<decltype(v)>
@@ -153,12 +254,12 @@ namespace scale {
153254} // namespace scale
154255
155256/* *
156- * @brief Defines a valid range for an enumeration.
157- * @note You should use this macro only in the global namespace
158- * @param enum_namespace The namespace of the enum .
159- * @param enum_name The enum type.
160- * @param min The minimum valid value.
161- * @param max The maximum valid value.
257+ * @def SCALE_DEFINE_ENUM_VALUE_RANGE
258+ * @brief Defines a valid value range for an enum.
259+ * @param enum_namespace Namespace where enum is defined .
260+ * @param enum_name Enum type name .
261+ * @param min Minimum valid value.
262+ * @param max Maximum valid value.
162263 */
163264#define SCALE_DEFINE_ENUM_VALUE_RANGE (enum_namespace, enum_name, min, max ) \
164265 template <> \
@@ -169,10 +270,11 @@ namespace scale {
169270 };
170271
171272/* *
172- * @brief Defines a valid list of values for an enumeration.
173- * @param enum_namespace The namespace of the enum.
174- * @param enum_name The enum type.
175- * @param ... The valid values.
273+ * @def SCALE_DEFINE_ENUM_VALUE_LIST
274+ * @brief Defines an explicit list of valid enum values.
275+ * @param enum_namespace Namespace where enum is defined.
276+ * @param enum_name Enum type name.
277+ * @param ... List of valid values.
176278 */
177279#define SCALE_DEFINE_ENUM_VALUE_LIST (enum_namespace, enum_name, ...) \
178280 template <> \
0 commit comments