14#include <fmt/format.h>
16#include <userver/compiler/demangle.hpp>
17#include <userver/utils/assert.hpp>
18#include <userver/utils/string_literal.hpp>
20USERVER_NAMESPACE_BEGIN
26constexpr bool HasUppercaseAscii(std::string_view value)
noexcept {
27 for (
auto c : value) {
28 if (
'A' <= c && c <=
'Z') {
36constexpr bool ICaseEqualLowercase(std::string_view lowercase, std::string_view y)
noexcept {
37 const auto size = lowercase.size();
39 constexpr char kLowerToUpperMask =
static_cast<
char>(~
unsigned{32});
40 for (std::size_t i = 0; i < size; ++i) {
41 const auto lowercase_c = lowercase[i];
42 UASSERT(!(
'A' <= lowercase_c && lowercase_c <=
'Z'));
43 if (lowercase_c != y[i]) {
44 if (!(
'a' <= lowercase_c && lowercase_c <=
'z') || (lowercase_c & kLowerToUpperMask) != y[i]) {
54 constexpr explicit Found(std::size_t value)
noexcept {
UASSERT(value == 0); }
56 constexpr explicit operator std::size_t()
const noexcept {
return 0; }
59template <
typename Key,
typename Value,
typename Enabled =
void>
60class SearchState
final {
62 constexpr explicit SearchState(Key key)
noexcept : key_or_result_(std::in_place_index<0>, key) {}
64 constexpr bool IsFound()
const noexcept {
return key_or_result_.index() != 0; }
66 constexpr Key GetKey()
const noexcept {
68 return std::get<0>(key_or_result_);
71 constexpr void SetValue(Value value)
noexcept {
72 key_or_result_ = std::variant<Key, Value>(std::in_place_index<1>, value);
75 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
76 if (key_or_result_.index() == 1) {
77 return std::get<1>(key_or_result_);
84 std::variant<Key, Value> key_or_result_;
87inline constexpr std::size_t kInvalidSize = std::numeric_limits<std::size_t>::max();
89template <
typename Payload>
90inline constexpr bool kFitsInStringOrPayload =
91 sizeof(Payload) <=
sizeof(
const char*) &&
92 (std::is_integral_v<Payload> || std::is_enum_v<Payload> || std::is_same_v<Payload, Found>);
95template <
typename Payload>
96class StringOrPayload
final {
98 constexpr explicit StringOrPayload(std::string_view string)
noexcept
99 : data_or_payload_(string.data()), size_(string.size()) {
100#if defined(__clang__
)
101 __builtin_assume(size_ != kInvalidSize);
102#elif defined(__GNUC__)
103 if (size_ == kInvalidSize) {
104 __builtin_unreachable();
109 constexpr explicit StringOrPayload(Payload payload)
noexcept : data_or_payload_(payload), size_(kInvalidSize) {}
111 constexpr bool HasPayload()
const noexcept {
return size_ == kInvalidSize; }
113 constexpr const char* GetStringPointer()
const noexcept {
115 UASSERT(data_or_payload_.data);
116 return data_or_payload_.data;
119 constexpr std::size_t GetStringSize()
const noexcept {
121 UASSERT(data_or_payload_.data);
125 constexpr Payload GetPayload()
const noexcept {
127 return data_or_payload_.payload;
131 static_assert(kFitsInStringOrPayload<Payload>);
133 union DataOrPayload {
134 constexpr explicit DataOrPayload(
const char* data)
noexcept : data(data) {}
136 constexpr explicit DataOrPayload(Payload payload)
noexcept : payload(payload) {}
142 DataOrPayload data_or_payload_;
146template <
typename Value>
147class SearchState<std::string_view, Value, std::enable_if_t<kFitsInStringOrPayload<Value>>> {
149 constexpr explicit SearchState(std::string_view key)
noexcept : state_(key) {}
151 constexpr bool IsFound()
const noexcept {
return state_.HasPayload(); }
153 constexpr std::string_view GetKey()
const noexcept {
154 return std::string_view{state_.GetStringPointer(), state_.GetStringSize()};
157 constexpr void SetValue(Value value)
noexcept { state_ = StringOrPayload<Value>{value}; }
159 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
160 return IsFound() ? std::optional{state_.GetPayload()} : std::nullopt;
164 StringOrPayload<Value> state_;
167template <
typename Value>
168class SearchState<
zstring_view, Value, std::enable_if_t<kFitsInStringOrPayload<Value>>>
final
169 :
public SearchState<std::string_view, Value> {};
171template <
typename Value>
172class SearchState<
StringLiteral, Value, std::enable_if_t<kFitsInStringOrPayload<Value>>>
final
173 :
public SearchState<std::string_view, Value> {};
175template <
typename Key>
176class SearchState<Key, std::string_view, std::enable_if_t<kFitsInStringOrPayload<Key>>>
final {
178 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
180 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
182 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
184 constexpr void SetValue(std::string_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
186 [[nodiscard]]
constexpr std::optional<std::string_view> Extract()
noexcept {
187 return IsFound() ? std::optional{std::string_view{state_.GetStringPointer(), state_.GetStringSize()}}
192 StringOrPayload<Key> state_;
195template <
typename Key>
196class SearchState<Key,
zstring_view, std::enable_if_t<kFitsInStringOrPayload<Key>>>
final {
198 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
200 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
202 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
204 constexpr void SetValue(
zstring_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
206 [[nodiscard]]
constexpr std::optional<
zstring_view> Extract()
noexcept {
212 StringOrPayload<Key> state_;
215template <
typename Key>
216class SearchState<Key,
StringLiteral, std::enable_if_t<kFitsInStringOrPayload<Key>>>
final {
218 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
220 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
222 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
224 constexpr void SetValue(
StringLiteral value)
noexcept { state_ = StringOrPayload<Key>{value}; }
226 [[nodiscard]]
constexpr std::optional<
StringLiteral> Extract()
noexcept {
232 StringOrPayload<Key> state_;
235template <
typename First,
typename Second>
236class SwitchByFirst
final {
238 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
240 constexpr SwitchByFirst& Case(First first, Second second)
noexcept {
241 if (!state_.IsFound() && state_.GetKey() == first) {
242 state_.SetValue(second);
247 template <
typename T,
typename U =
void>
248 constexpr SwitchByFirst& Type() {
252 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
255 SearchState<First, Second> state_;
258template <
typename First>
259class SwitchByFirst<First,
void>
final {
261 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
263 constexpr SwitchByFirst& Case(First first)
noexcept {
264 if (!state_.IsFound() && state_.GetKey() == first) {
265 state_.SetValue(Found{0});
270 template <
typename T,
typename U =
void>
271 constexpr SwitchByFirst& Type() {
275 [[nodiscard]]
constexpr bool Extract()
noexcept {
return state_.IsFound(); }
278 SearchState<First, Found> state_;
281template <
typename Second>
282class SwitchByFirstICase
final {
284 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
286 constexpr SwitchByFirstICase& Case(std::string_view first, Second second)
noexcept {
288 !
impl::HasUppercaseAscii(first),
289 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", first)
291 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
292 impl::ICaseEqualLowercase(first, state_.GetKey()))
294 state_.SetValue(second);
299 template <
typename T,
typename U>
300 constexpr SwitchByFirstICase& Type() {
304 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
307 SearchState<std::string_view, Second> state_;
311class SwitchByFirstICase<
void>
final {
313 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
315 constexpr SwitchByFirstICase& Case(std::string_view first)
noexcept {
317 !
impl::HasUppercaseAscii(first),
318 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", first)
320 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
321 impl::ICaseEqualLowercase(first, state_.GetKey()))
323 state_.SetValue(Found{0});
328 template <
typename T,
typename U>
329 constexpr SwitchByFirstICase& Type() {
333 [[nodiscard]]
constexpr bool Extract()
const noexcept {
return state_.IsFound(); }
336 SearchState<std::string_view, Found> state_;
339template <
typename First>
340class SwitchBySecondICase
final {
342 constexpr explicit SwitchBySecondICase(std::string_view search)
noexcept : state_(search) {}
344 constexpr SwitchBySecondICase& Case(First first, std::string_view second)
noexcept {
346 !
impl::HasUppercaseAscii(second),
347 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", second)
349 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
350 impl::ICaseEqualLowercase(second, state_.GetKey()))
352 state_.SetValue(first);
357 template <
typename T,
typename U>
358 constexpr SwitchBySecondICase& Type() {
362 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
365 SearchState<std::string_view, First> state_;
368template <
typename First,
typename Second>
369class SwitchBySecond
final {
371 constexpr explicit SwitchBySecond(Second search)
noexcept : state_(search) {}
373 constexpr SwitchBySecond& Case(First first, Second second)
noexcept {
374 if (!state_.IsFound() && state_.GetKey() == second) {
375 state_.SetValue(first);
380 template <
typename T,
typename U>
381 constexpr SwitchBySecond& Type() {
385 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
388 SearchState<Second, First> state_;
391template <
typename First,
typename Second>
392class SwitchTypesDetected
final {
394 using first_type = First;
395 using second_type = Second;
397 constexpr SwitchTypesDetected& Case(First, Second)
noexcept {
return *
this; }
400template <
typename First>
401class SwitchTypesDetected<First,
void>
final {
403 using first_type = First;
404 using second_type =
void;
406 constexpr SwitchTypesDetected& Case(First)
noexcept {
return *
this; }
409class SwitchTypesDetector
final {
412 using DetectType = std::conditional_t<
418 std::conditional_t<std::is_convertible_v<T, std::string_view>, std::string_view, T>>>;
420 constexpr SwitchTypesDetector& operator()()
noexcept {
return *
this; }
422 template <
typename First,
typename Second>
423 constexpr auto Case(First, Second)
noexcept {
424 return Type<First, Second>();
427 template <
typename First>
428 constexpr auto Case(First)
noexcept {
429 return Type<First,
void>();
432 template <
typename First,
typename Second =
void>
433 constexpr auto Type()
noexcept {
434 return SwitchTypesDetected<DetectType<First>, DetectType<Second>>{};
438class CaseCounter
final {
440 template <
typename First,
typename Second>
441 constexpr CaseCounter& Case(First, Second)
noexcept {
446 template <
typename First>
447 constexpr CaseCounter& Case(First)
noexcept {
452 template <
typename T,
typename U>
453 constexpr CaseCounter& Type() {
457 [[nodiscard]]
constexpr std::size_t Extract()
const noexcept {
return count_; }
460 std::size_t count_{0};
463class CaseDescriber
final {
465 template <
typename First,
typename Second>
466 CaseDescriber& Case(First first, Second second)
noexcept {
467 if (!description_.empty()) {
468 description_ +=
", ";
471 description_ += fmt::format(
"('{}', '{}')", first, second);
476 template <
typename T,
typename U>
477 constexpr CaseDescriber& Type() {
481 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
484 std::string description_{};
487class CaseFirstDescriber
final {
489 template <
typename First>
490 CaseFirstDescriber& Case(First first)
noexcept {
491 if (!description_.empty()) {
492 description_ +=
", ";
495 description_ += fmt::format(
"'{}'", first);
500 template <
typename First,
typename Second>
501 CaseFirstDescriber& Case(First first, Second )
noexcept {
505 template <
typename T,
typename U =
void>
506 constexpr CaseFirstDescriber& Type() {
510 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
513 std::string description_{};
516class CaseSecondDescriber
final {
518 template <
typename First,
typename Second>
519 CaseSecondDescriber& Case(First , Second second)
noexcept {
520 if (!description_.empty()) {
521 description_ +=
", ";
524 description_ += fmt::format(
"'{}'", second);
529 template <
typename T,
typename U>
530 constexpr CaseSecondDescriber& Type() {
534 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
537 std::string description_{};
540template <
typename First,
typename Second>
541class CaseGetValuesByIndex
final {
543 explicit constexpr CaseGetValuesByIndex(std::size_t search_index)
544 : index_(search_index + 1)
547 constexpr CaseGetValuesByIndex& Case(First first, Second second)
noexcept {
552 lazy_ = Lazy{Storage{first, second}};
559 template <
typename T,
typename U>
560 constexpr CaseGetValuesByIndex& Type() {
564 [[nodiscard]]
constexpr First GetFirst()
noexcept {
return std::move(lazy_.storage.first); }
566 [[nodiscard]]
constexpr Second GetSecond()
noexcept {
return std::move(lazy_.storage.second); }
578 constexpr Lazy()
noexcept : empty{} {}
579 constexpr Lazy(Storage s)
noexcept : storage{s} {}
587template <
typename First>
588class CaseFirstIndexer
final {
590 constexpr explicit CaseFirstIndexer(First search_value)
noexcept : state_(search_value) {}
592 constexpr CaseFirstIndexer& Case(First first)
noexcept {
593 if (!state_.IsFound() && state_.GetKey() == first) {
594 state_.SetValue(index_);
600 template <
typename T,
typename U =
void>
601 constexpr CaseFirstIndexer& Type() {
605 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
608 SearchState<First, std::size_t> state_;
609 std::size_t index_ = 0;
612template <
typename First>
613class CaseFirstIndexerICase
final {
615 constexpr explicit CaseFirstIndexerICase(First search_value)
noexcept : state_(search_value) {}
617 constexpr CaseFirstIndexerICase& Case(First first)
noexcept {
618 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
619 impl::ICaseEqualLowercase(first, state_.GetKey()))
621 state_.SetValue(index_);
627 template <
typename T,
typename U =
void>
628 constexpr CaseFirstIndexerICase& Type() {
632 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
635 SearchState<First, std::size_t> state_;
636 std::size_t index_ = 0;
643using DecayToStringView =
673template <
typename BuilderFunc>
674class TrivialBiMap
final {
675 using TypesPair = std::invoke_result_t<
const BuilderFunc&,
impl::SwitchTypesDetector>;
678 using First =
typename TypesPair::first_type;
679 using Second =
typename TypesPair::second_type;
689 using MappedTypeFor = std::conditional_t<std::is_convertible_v<T, DecayToStringView<First>>, Second, First>;
691 constexpr TrivialBiMap(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
692 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
693 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
695 !std::is_void_v<Second>,
696 "If second type in Case is missing, use utils::TrivialSet instead of utils::TrivialBiMap"
698 static_assert(std::is_trivially_copyable_v<Second>,
"Second type in Case must be trivially copyable");
701 constexpr std::optional<Second> TryFindByFirst(DecayToStringView<First> value)
const noexcept {
702 return func_([value]() {
return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
705 constexpr std::optional<First> TryFindBySecond(DecayToStringView<Second> value)
const noexcept {
706 return func_([value]() {
return impl::SwitchBySecond<First, DecayToStringView<Second>>{value}; }).Extract();
710 constexpr std::optional<MappedTypeFor<T>> TryFind(T value)
const noexcept {
711 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
713 !std::is_convertible_v<T, DecayToStringView<Second>>,
714 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead"
716 return TryFindByFirst(value);
718 return TryFindBySecond(value);
727 return func_([value]() {
return impl::SwitchByFirstICase<Second>{value}; }).Extract();
735 return func_([value]() {
return impl::SwitchBySecondICase<First>{value}; }).Extract();
740 constexpr std::optional<MappedTypeFor<std::string_view>>
TryFindICase(std::string_view value)
const noexcept {
741 if constexpr (std::is_convertible_v<std::string_view, DecayToStringView<First>>) {
743 !std::is_convertible_v<std::string_view, DecayToStringView<Second>>,
744 "Ambiguous conversion, use TryFindICaseByFirst/TryFindICaseBySecond"
753 constexpr std::size_t
size()
const noexcept {
754 return func_([]() {
return impl::CaseCounter{}; }).Extract();
763 return func_([]() {
return impl::CaseDescriber{}; }).Extract();
773 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
783 return func_([]() {
return impl::CaseSecondDescriber{}; }).Extract();
792 template <
typename T>
794 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
801 constexpr ValueType GetValuesByIndex(std::size_t index)
const {
803 auto result = func_([index]() {
return impl::CaseGetValuesByIndex<First, Second>{index}; });
804 return ValueType{result.GetFirst(), result.GetSecond()};
809 using iterator_category = std::input_iterator_tag;
810 using difference_type = std::ptrdiff_t;
812 explicit constexpr Iterator(
const TrivialBiMap& map, std::size_t position)
817 constexpr bool operator==(
Iterator other)
const {
return position_ == other.position_; }
819 constexpr bool operator!=(
Iterator other)
const {
return position_ != other.position_; }
826 constexpr Iterator operator++(
int) {
832 constexpr ValueType operator*()
const {
return map_.GetValuesByIndex(position_); }
835 const TrivialBiMap& map_;
836 std::size_t position_;
841 constexpr Iterator cbegin()
const {
return begin(); }
842 constexpr Iterator cend()
const {
return end(); }
845 const BuilderFunc func_;
848template <
typename BuilderFunc>
849TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
857template <
typename BuilderFunc>
858class TrivialSet
final {
859 using TypesPair = std::invoke_result_t<
const BuilderFunc&,
impl::SwitchTypesDetector>;
862 using First =
typename TypesPair::first_type;
863 using Second =
typename TypesPair::second_type;
865 constexpr TrivialSet(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
866 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
867 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
868 static_assert(std::is_void_v<Second>,
"Second type in Case should be skipped in utils::TrivialSet");
871 constexpr bool Contains(DecayToStringView<First> value)
const noexcept {
872 return func_([value]() {
return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
875 constexpr bool ContainsICase(std::string_view value)
const noexcept {
877 std::is_convertible_v<DecayToStringView<First>, std::string_view>,
878 "ContainsICase works only with std::string_view"
881 return func_([value]() {
return impl::SwitchByFirstICase<
void>{value}; }).Extract();
884 constexpr std::size_t size()
const noexcept {
885 return func_([]() {
return impl::CaseCounter{}; }).Extract();
894 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
899 constexpr std::optional<std::size_t>
GetIndex(DecayToStringView<First> value)
const {
900 return func_([value]() {
return impl::CaseFirstIndexer{value}; }).Extract();
905 constexpr std::optional<std::size_t>
GetIndexICase(std::string_view value)
const {
906 return func_([value]() {
return impl::CaseFirstIndexerICase{value}; }).Extract();
910 const BuilderFunc func_;
913template <
typename BuilderFunc>
914TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
921template <
typename ExceptionType =
void,
typename Value,
typename BuilderFunc>
923 if constexpr (!std::is_void_v<ExceptionType>) {
924 if (!value.IsString()) {
925 throw ExceptionType(fmt::format(
"Invalid value at '{}': expected a string", value.GetPath()));
929 const auto string = value.
template As<std::string>();
930 const auto parsed = map.TryFind(string);
935 using Exception = std::conditional_t<std::is_void_v<ExceptionType>,
typename Value::Exception, ExceptionType>;
937 throw Exception(fmt::format(
938 "Invalid value of {} at '{}': '{}' is not one of {}",
939 compiler::GetTypeName<std::decay_t<
decltype(*parsed)>>(),
942 map.
template DescribeByType<std::string_view>()
951template <
typename Enum,
typename BuilderFunc>
952std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
953 static_assert(std::is_enum_v<Enum>);
954 if (
const auto string = map.TryFind(value)) {
961 "Invalid value of enum {}: {}",
963 static_cast<std::underlying_type_t<Enum>>(value)
968template <
typename Selector,
class Keys,
typename Values, std::size_t... Indices>
970TrivialBiMapMultiCase(Selector selector,
const Keys& keys,
const Values& values, std::index_sequence<0, Indices...>) {
971 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
972 ((selector2 = selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])), ...);
976template <
const auto& Keys,
const auto& Values>
977struct TrivialBiMapMultiCaseDispatch {
978 template <
class Selector>
979 constexpr auto operator()(Selector selector)
const {
980 constexpr auto kKeysSize = std::size(Keys);
981 return impl::TrivialBiMapMultiCase(selector(), Keys, Values, std::make_index_sequence<kKeysSize>{});
985template <
typename Selector,
class Values, std::size_t... Indices>
986constexpr auto TrivialSetMultiCase(Selector selector,
const Values& values, std::index_sequence<0, Indices...>) {
987 auto selector2 = selector.Case(std::data(values)[0]);
988 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
992template <
const auto& Values>
993struct TrivialSetMultiCaseDispatch {
994 template <
class Selector>
995 constexpr auto operator()(Selector selector)
const {
996 constexpr auto kValuesSize = std::size(Values);
997 return impl::TrivialSetMultiCase(selector(), Values, std::make_index_sequence<kValuesSize>{});
1004template <
const auto& Keys,
const auto& Values>
1006 static_assert(std::size(Keys) == std::size(Values));
1007 static_assert(std::size(Keys) >= 1);
1008 return TrivialBiMap(
impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
1011template <
const auto& Values>
1012constexpr auto MakeTrivialSet() {
1013 return TrivialSet(
impl::TrivialSetMultiCaseDispatch<Values>{});
1018USERVER_NAMESPACE_END