Skip to content

Commit 58e4b35

Browse files
authored
Feature: validate one-byte enum value by reflection (#36)
* feature: validate_onebyte_enum_over_refletion Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> * fix: for g++-14 Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> * clean: unused code Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> * fix: constexpr Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> * hotfix Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> * fix: review issues Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]> --------- Signed-off-by: Dmitriy Khaustov aka xDimon <[email protected]>
1 parent ff1a6e3 commit 58e4b35

File tree

2 files changed

+269
-94
lines changed

2 files changed

+269
-94
lines changed

include/scale/detail/enum.hpp

Lines changed: 192 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5,124 +5,224 @@
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

3233
namespace 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

Comments
 (0)