14#include <fmt/format.h>
16#include <userver/compiler/demangle.hpp>
17#include <userver/utils/assert.hpp>
19USERVER_NAMESPACE_BEGIN
25constexpr bool HasUppercaseAscii(std::string_view value)
noexcept {
26 for (
auto c : value) {
27 if (
'A' <= c && c <=
'Z')
return true;
33constexpr bool ICaseEqualLowercase(std::string_view lowercase, std::string_view y)
noexcept {
34 const auto size = lowercase.size();
36 constexpr char kLowerToUpperMask =
static_cast<
char>(~
unsigned{32});
37 for (std::size_t i = 0; i < size; ++i) {
38 const auto lowercase_c = lowercase[i];
39 UASSERT(!(
'A' <= lowercase_c && lowercase_c <=
'Z'));
40 if (lowercase_c != y[i]) {
41 if (!(
'a' <= lowercase_c && lowercase_c <=
'z') || (lowercase_c & kLowerToUpperMask) != y[i]) {
51 constexpr explicit Found(std::size_t value)
noexcept {
UASSERT(value == 0); }
53 constexpr explicit operator std::size_t()
const noexcept {
return 0; }
56template <
typename Key,
typename Value,
typename Enabled =
void>
57class SearchState
final {
59 constexpr explicit SearchState(Key key)
noexcept : key_or_result_(std::in_place_index<0>, key) {}
61 constexpr bool IsFound()
const noexcept {
return key_or_result_.index() != 0; }
63 constexpr Key GetKey()
const noexcept {
65 return std::get<0>(key_or_result_);
68 constexpr void SetValue(Value value)
noexcept {
69 key_or_result_ = std::variant<Key, Value>(std::in_place_index<1>, value);
72 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
73 if (key_or_result_.index() == 1) {
74 return std::get<1>(key_or_result_);
81 std::variant<Key, Value> key_or_result_;
84inline constexpr std::size_t kInvalidSize = std::numeric_limits<std::size_t>::max();
86template <
typename Payload>
87inline constexpr bool kFitsInStringOrPayload =
88 sizeof(Payload) <=
sizeof(
const char*) &&
89 (std::is_integral_v<Payload> || std::is_enum_v<Payload> || std::is_same_v<Payload, Found>);
92template <
typename Payload>
93class StringOrPayload
final {
95 constexpr explicit StringOrPayload(std::string_view string)
noexcept
96 : data_or_payload_(string.data()), size_(string.size()) {
98 __builtin_assume(size_ != kInvalidSize);
99#elif defined(__GNUC__)
100 if (size_ == kInvalidSize) __builtin_unreachable();
104 constexpr explicit StringOrPayload(Payload payload)
noexcept : data_or_payload_(payload), size_(kInvalidSize) {}
106 constexpr bool HasPayload()
const noexcept {
return size_ == kInvalidSize; }
108 constexpr std::string_view GetString()
const noexcept {
110 return std::string_view{data_or_payload_.data, size_};
113 constexpr Payload GetPayload()
const noexcept {
115 return data_or_payload_.payload;
119 static_assert(kFitsInStringOrPayload<Payload>);
121 union DataOrPayload {
122 constexpr explicit DataOrPayload(
const char* data)
noexcept : data(data) {}
124 constexpr explicit DataOrPayload(Payload payload)
noexcept : payload(payload) {}
130 DataOrPayload data_or_payload_;
134template <
typename Value>
135class SearchState<std::string_view, Value, std::enable_if_t<kFitsInStringOrPayload<Value>>> final {
137 constexpr explicit SearchState(std::string_view key)
noexcept : state_(key) {}
139 constexpr bool IsFound()
const noexcept {
return state_.HasPayload(); }
141 constexpr std::string_view GetKey()
const noexcept {
return state_.GetString(); }
143 constexpr void SetValue(Value value)
noexcept { state_ = StringOrPayload<Value>{value}; }
145 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
146 return IsFound() ? std::optional{state_.GetPayload()} : std::nullopt;
150 StringOrPayload<Value> state_;
153template <
typename Key>
154class SearchState<Key, std::string_view, std::enable_if_t<kFitsInStringOrPayload<Key>>> final {
156 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
158 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
160 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
162 constexpr void SetValue(std::string_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
164 [[nodiscard]]
constexpr std::optional<std::string_view> Extract()
noexcept {
165 return IsFound() ? std::optional{state_.GetString()} : std::nullopt;
169 StringOrPayload<Key> state_;
172template <
typename First,
typename Second>
173class SwitchByFirst
final {
175 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
177 constexpr SwitchByFirst& Case(First first, Second second)
noexcept {
178 if (!state_.IsFound() && state_.GetKey() == first) {
179 state_.SetValue(second);
184 template <
typename T,
typename U =
void>
185 constexpr SwitchByFirst& Type() {
189 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
192 SearchState<First, Second> state_;
195template <
typename First>
196class SwitchByFirst<First,
void>
final {
198 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
200 constexpr SwitchByFirst& Case(First first)
noexcept {
201 if (!state_.IsFound() && state_.GetKey() == first) {
202 state_.SetValue(Found{0});
207 template <
typename T,
typename U =
void>
208 constexpr SwitchByFirst& Type() {
212 [[nodiscard]]
constexpr bool Extract()
noexcept {
return state_.IsFound(); }
215 SearchState<First, Found> state_;
218template <
typename Second>
219class SwitchByFirstICase
final {
221 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
223 constexpr SwitchByFirstICase& Case(std::string_view first, Second second)
noexcept {
225 !impl::HasUppercaseAscii(first),
227 "String literal '{}' in utils::Switch*::Case() "
228 "should be in lower case",
232 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
233 impl::ICaseEqualLowercase(first, state_.GetKey())) {
234 state_.SetValue(second);
239 template <
typename T,
typename U>
240 constexpr SwitchByFirstICase& Type() {
244 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
247 SearchState<std::string_view, Second> state_;
251class SwitchByFirstICase<
void>
final {
253 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
255 constexpr SwitchByFirstICase& Case(std::string_view first)
noexcept {
257 !impl::HasUppercaseAscii(first),
259 "String literal '{}' in utils::Switch*::Case() "
260 "should be in lower case",
264 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
265 impl::ICaseEqualLowercase(first, state_.GetKey())) {
266 state_.SetValue(Found{0});
271 template <
typename T,
typename U>
272 constexpr SwitchByFirstICase& Type() {
276 [[nodiscard]]
constexpr bool Extract()
const noexcept {
return state_.IsFound(); }
279 SearchState<std::string_view, Found> state_;
282template <
typename First>
283class SwitchBySecondICase
final {
285 constexpr explicit SwitchBySecondICase(std::string_view search)
noexcept : state_(search) {}
287 constexpr SwitchBySecondICase& Case(First first, std::string_view second)
noexcept {
289 !impl::HasUppercaseAscii(second),
291 "String literal '{}' in utils::Switch*::Case() "
292 "should be in lower case",
296 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
297 impl::ICaseEqualLowercase(second, state_.GetKey())) {
298 state_.SetValue(first);
303 template <
typename T,
typename U>
304 constexpr SwitchBySecondICase& Type() {
308 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
311 SearchState<std::string_view, First> state_;
314template <
typename First,
typename Second>
315class SwitchBySecond
final {
317 constexpr explicit SwitchBySecond(Second search)
noexcept : state_(search) {}
319 constexpr SwitchBySecond& Case(First first, Second second)
noexcept {
320 if (!state_.IsFound() && state_.GetKey() == second) {
321 state_.SetValue(first);
326 template <
typename T,
typename U>
327 constexpr SwitchBySecond& Type() {
331 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
334 SearchState<Second, First> state_;
337template <
typename First,
typename Second>
338class SwitchTypesDetected
final {
340 using first_type = First;
341 using second_type = Second;
343 constexpr SwitchTypesDetected& Case(First, Second)
noexcept {
return *
this; }
346template <
typename First>
347class SwitchTypesDetected<First,
void>
final {
349 using first_type = First;
350 using second_type =
void;
352 constexpr SwitchTypesDetected& Case(First)
noexcept {
return *
this; }
355class SwitchTypesDetector
final {
357 constexpr SwitchTypesDetector& operator()()
noexcept {
return *
this; }
359 template <
typename First,
typename Second>
360 constexpr auto Case(First, Second)
noexcept {
361 return Type<First, Second>();
364 template <
typename First>
365 constexpr auto Case(First)
noexcept {
366 return Type<First,
void>();
369 template <
typename First,
typename Second =
void>
370 constexpr auto Type()
noexcept {
371 using first_type = std::conditional_t<std::is_convertible_v<First, std::string_view>, std::string_view, First>;
373 std::conditional_t<std::is_convertible_v<Second, std::string_view>, std::string_view, Second>;
374 return SwitchTypesDetected<first_type, second_type>{};
378class CaseCounter
final {
380 template <
typename First,
typename Second>
381 constexpr CaseCounter& Case(First, Second)
noexcept {
386 template <
typename First>
387 constexpr CaseCounter& Case(First)
noexcept {
392 template <
typename T,
typename U>
393 constexpr CaseCounter& Type() {
397 [[nodiscard]]
constexpr std::size_t Extract()
const noexcept {
return count_; }
400 std::size_t count_{0};
403class CaseDescriber
final {
405 template <
typename First,
typename Second>
406 CaseDescriber& Case(First first, Second second)
noexcept {
407 if (!description_.empty()) {
408 description_ +=
", ";
411 description_ += fmt::format(
"('{}', '{}')", first, second);
416 template <
typename T,
typename U>
417 constexpr CaseDescriber& Type() {
421 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
424 std::string description_{};
427class CaseFirstDescriber
final {
429 template <
typename First>
430 CaseFirstDescriber& Case(First first)
noexcept {
431 if (!description_.empty()) {
432 description_ +=
", ";
435 description_ += fmt::format(
"'{}'", first);
440 template <
typename First,
typename Second>
441 CaseFirstDescriber& Case(First first, Second )
noexcept {
445 template <
typename T,
typename U>
446 constexpr CaseFirstDescriber& Type() {
450 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
453 std::string description_{};
456class CaseSecondDescriber
final {
458 template <
typename First,
typename Second>
459 CaseSecondDescriber& Case(First , Second second)
noexcept {
460 if (!description_.empty()) {
461 description_ +=
", ";
464 description_ += fmt::format(
"'{}'", second);
469 template <
typename T,
typename U>
470 constexpr CaseSecondDescriber& Type() {
474 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
477 std::string description_{};
480template <
typename First,
typename Second>
481class CaseGetValuesByIndex
final {
483 explicit constexpr CaseGetValuesByIndex(std::size_t search_index) : index_(search_index + 1) {}
485 constexpr CaseGetValuesByIndex& Case(First first, Second second)
noexcept {
498 template <
typename T,
typename U>
499 constexpr CaseGetValuesByIndex& Type() {
503 [[nodiscard]]
constexpr First GetFirst()
noexcept {
return std::move(first_); }
505 [[nodiscard]]
constexpr Second GetSecond()
noexcept {
return std::move(second_); }
513template <
typename First>
514class CaseFirstIndexer
final {
516 constexpr explicit CaseFirstIndexer(First search_value)
noexcept : state_(search_value) {}
518 constexpr CaseFirstIndexer& Case(First first)
noexcept {
519 if (!state_.IsFound() && state_.GetKey() == first) {
520 state_.SetValue(index_);
526 template <
typename T,
typename U =
void>
527 constexpr CaseFirstIndexer& Type() {
531 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
534 SearchState<First, std::size_t> state_;
535 std::size_t index_ = 0;
538template <
typename First>
539class CaseFirstIndexerICase
final {
541 constexpr explicit CaseFirstIndexerICase(First search_value)
noexcept : state_(search_value) {}
543 constexpr CaseFirstIndexerICase& Case(First first)
noexcept {
544 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
545 impl::ICaseEqualLowercase(first, state_.GetKey())) {
546 state_.SetValue(index_);
552 template <
typename T,
typename U =
void>
553 constexpr CaseFirstIndexerICase& Type() {
557 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
560 SearchState<First, std::size_t> state_;
561 std::size_t index_ = 0;
593template <
typename BuilderFunc>
594class TrivialBiMap
final {
595 using TypesPair = std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
598 using First =
typename TypesPair::first_type;
599 using Second =
typename TypesPair::second_type;
609 using MappedTypeFor = std::conditional_t<std::is_convertible_v<T, First>, Second, First>;
611 constexpr TrivialBiMap(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
612 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
613 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
615 !std::is_void_v<Second>,
616 "If second type in Case is missing, use "
617 "utils::TrivialSet instead of utils::TrivialBiMap"
619 static_assert(std::is_trivially_copyable_v<Second>,
"Second type in Case must be trivially copyable");
622 constexpr std::optional<Second> TryFindByFirst(First value)
const noexcept {
623 return func_([value]() {
return impl::SwitchByFirst<First, Second>{value}; }).Extract();
626 constexpr std::optional<First> TryFindBySecond(Second value)
const noexcept {
627 return func_([value]() {
return impl::SwitchBySecond<First, Second>{value}; }).Extract();
631 constexpr std::optional<MappedTypeFor<T>> TryFind(T value)
const noexcept {
633 !std::is_convertible_v<T, First> || !std::is_convertible_v<T, Second>,
634 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead"
637 if constexpr (std::is_convertible_v<T, First>) {
638 return TryFindByFirst(value);
640 return TryFindBySecond(value);
649 return func_([value]() {
return impl::SwitchByFirstICase<Second>{value}; }).Extract();
657 return func_([value]() {
return impl::SwitchBySecondICase<First>{value}; }).Extract();
664 !std::is_convertible_v<std::string_view, First> || !std::is_convertible_v<std::string_view, Second>,
665 "Ambiguous conversion, use "
666 "TryFindICaseByFirst/TryFindICaseBySecond"
669 if constexpr (std::is_convertible_v<std::string_view, First>) {
670 return TryFindICaseByFirst(value);
672 return TryFindICaseBySecond(value);
677 constexpr std::size_t
size()
const noexcept {
678 return func_([]() {
return impl::CaseCounter{}; }).Extract();
687 return func_([]() {
return impl::CaseDescriber{}; }).Extract();
697 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
707 return func_([]() {
return impl::CaseSecondDescriber{}; }).Extract();
716 template <
typename T>
718 if constexpr (std::is_convertible_v<T, First>) {
725 constexpr value_type GetValuesByIndex(std::size_t index)
const {
726 auto result = func_([index]() {
return impl::CaseGetValuesByIndex<First, Second>{index}; });
727 return value_type{result.GetFirst(), result.GetSecond()};
732 using iterator_category = std::input_iterator_tag;
733 using difference_type = std::ptrdiff_t;
735 explicit constexpr iterator(
const TrivialBiMap& map, std::size_t position) : map_{map}, position_{position} {}
737 constexpr bool operator==(
iterator other)
const {
return position_ == other.position_; }
739 constexpr bool operator!=(
iterator other)
const {
return position_ != other.position_; }
746 constexpr iterator operator++(
int) {
752 constexpr value_type operator*()
const {
return map_.GetValuesByIndex(position_); }
755 const TrivialBiMap& map_;
756 std::size_t position_;
761 constexpr iterator cbegin()
const {
return begin(); }
762 constexpr iterator cend()
const {
return end(); }
765 const BuilderFunc func_;
768template <
typename BuilderFunc>
769TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
777template <
typename BuilderFunc>
778class TrivialSet
final {
779 using TypesPair = std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
782 using First =
typename TypesPair::first_type;
783 using Second =
typename TypesPair::second_type;
785 constexpr TrivialSet(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
786 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
787 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
788 static_assert(std::is_void_v<Second>,
"Second type in Case should be skipped in utils::TrivialSet");
791 constexpr bool Contains(First value)
const noexcept {
792 return func_([value]() {
return impl::SwitchByFirst<First, Second>{value}; }).Extract();
795 constexpr bool ContainsICase(std::string_view value)
const noexcept {
796 static_assert(std::is_convertible_v<First, std::string_view>,
"ContainsICase works only with std::string_view");
798 return func_([value]() {
return impl::SwitchByFirstICase<
void>{value}; }).Extract();
801 constexpr std::size_t size()
const noexcept {
802 return func_([]() {
return impl::CaseCounter{}; }).Extract();
811 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
816 constexpr std::optional<std::size_t>
GetIndex(First value)
const {
817 return func_([value]() {
return impl::CaseFirstIndexer{value}; }).Extract();
823 return func_([value]() {
return impl::CaseFirstIndexerICase{value}; }).Extract();
827 const BuilderFunc func_;
830template <
typename BuilderFunc>
831TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
838template <
typename ExceptionType =
void,
typename Value,
typename BuilderFunc>
840 if constexpr (!std::is_void_v<ExceptionType>) {
841 if (!value.IsString()) {
842 throw ExceptionType(fmt::format(
"Invalid value at '{}': expected a string", value.GetPath()));
846 const auto string = value.
template As<std::string>();
847 const auto parsed = map.TryFind(string);
848 if (parsed)
return *parsed;
850 using Exception = std::conditional_t<std::is_void_v<ExceptionType>,
typename Value::Exception, ExceptionType>;
852 throw Exception(fmt::format(
853 "Invalid value of {} at '{}': '{}' is not one of {}",
854 compiler::GetTypeName<std::decay_t<
decltype(*parsed)>>(),
857 map.
template DescribeByType<std::string>()
866template <
typename Enum,
typename BuilderFunc>
867std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
868 static_assert(std::is_enum_v<Enum>);
869 if (
const auto string = map.TryFind(value))
return *string;
874 "Invalid value of enum {}: {}",
875 compiler::GetTypeName<Enum>(),
876 static_cast<std::underlying_type_t<Enum>>(value)
881template <
typename Selector,
class Keys,
typename Values, std::size_t... Indices>
883TrivialBiMapMultiCase(Selector selector,
const Keys& keys,
const Values& values, std::index_sequence<0, Indices...>) {
884 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
885 ((selector2 = selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])), ...);
889template <
const auto& Keys,
const auto& Values>
890struct TrivialBiMapMultiCaseDispatch {
891 template <
class Selector>
892 constexpr auto operator()(Selector selector)
const {
893 constexpr auto kKeysSize = std::size(Keys);
894 return impl::TrivialBiMapMultiCase(selector(), Keys, Values, std::make_index_sequence<kKeysSize>{});
898template <
typename Selector,
class Values, std::size_t... Indices>
899constexpr auto TrivialSetMultiCase(Selector selector,
const Values& values, std::index_sequence<0, Indices...>) {
900 auto selector2 = selector.Case(std::data(values)[0]);
901 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
905template <
const auto& Values>
906struct TrivialSetMultiCaseDispatch {
907 template <
class Selector>
908 constexpr auto operator()(Selector selector)
const {
909 constexpr auto kValuesSize = std::size(Values);
910 return impl::TrivialSetMultiCase(selector(), Values, std::make_index_sequence<kValuesSize>{});
917template <
const auto& Keys,
const auto& Values>
919 static_assert(std::size(Keys) == std::size(Values));
920 static_assert(std::size(Keys) >= 1);
921 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
924template <
const auto& Values>
925constexpr auto MakeTrivialSet() {
926 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});