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,
34 std::string_view y)
noexcept {
35 const auto size = lowercase.size();
37 constexpr char kLowerToUpperMask =
static_cast<
char>(~
unsigned{32});
38 for (std::size_t i = 0; i < size; ++i) {
39 const auto lowercase_c = lowercase[i];
40 UASSERT(!(
'A' <= lowercase_c && lowercase_c <=
'Z'));
41 if (lowercase_c != y[i]) {
42 if (!(
'a' <= lowercase_c && lowercase_c <=
'z') ||
43 (lowercase_c & kLowerToUpperMask) != y[i]) {
53 constexpr explicit Found(std::size_t value)
noexcept {
UASSERT(value == 0); }
55 constexpr explicit operator std::size_t()
const noexcept {
return 0; }
58template <
typename Key,
typename Value,
typename Enabled =
void>
59class SearchState
final {
61 constexpr explicit SearchState(Key key)
noexcept
62 : key_or_result_(std::in_place_index<0>, key) {}
64 constexpr bool IsFound()
const noexcept {
65 return key_or_result_.index() != 0;
68 constexpr Key GetKey()
const noexcept {
70 return std::get<0>(key_or_result_);
73 constexpr void SetValue(Value value)
noexcept {
74 key_or_result_ = std::variant<Key, Value>(std::in_place_index<1>, value);
77 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
78 if (key_or_result_.index() == 1) {
79 return std::get<1>(key_or_result_);
86 std::variant<Key, Value> key_or_result_;
89inline constexpr std::size_t kInvalidSize =
90 std::numeric_limits<std::size_t>::max();
92template <
typename Payload>
93inline constexpr bool kFitsInStringOrPayload =
94 sizeof(Payload) <=
sizeof(
const char*) &&
95 (std::is_integral_v<Payload> || std::is_enum_v<Payload> ||
96 std::is_same_v<Payload, Found>);
99template <
typename Payload>
100class StringOrPayload
final {
102 constexpr explicit StringOrPayload(std::string_view string)
noexcept
103 : data_or_payload_(string.data()), size_(string.size()) {
104#if defined(__clang__
)
105 __builtin_assume(size_ != kInvalidSize);
106#elif defined(__GNUC__)
107 if (size_ == kInvalidSize) __builtin_unreachable();
111 constexpr explicit StringOrPayload(Payload payload)
noexcept
112 : data_or_payload_(payload), size_(kInvalidSize) {}
114 constexpr bool HasPayload()
const noexcept {
return size_ == kInvalidSize; }
116 constexpr std::string_view GetString()
const noexcept {
118 return std::string_view{data_or_payload_.data, size_};
121 constexpr Payload GetPayload()
const noexcept {
123 return data_or_payload_.payload;
127 static_assert(kFitsInStringOrPayload<Payload>);
129 union DataOrPayload {
130 constexpr explicit DataOrPayload(
const char* data)
noexcept : data(data) {}
132 constexpr explicit DataOrPayload(Payload payload)
noexcept
133 : payload(payload) {}
139 DataOrPayload data_or_payload_;
143template <
typename Value>
144class SearchState<std::string_view, Value,
145 std::enable_if_t<kFitsInStringOrPayload<Value>>>
148 constexpr explicit SearchState(std::string_view key)
noexcept : state_(key) {}
150 constexpr bool IsFound()
const noexcept {
return state_.HasPayload(); }
152 constexpr std::string_view GetKey()
const noexcept {
153 return state_.GetString();
156 constexpr void SetValue(Value value)
noexcept {
157 state_ = StringOrPayload<Value>{value};
160 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
161 return IsFound() ? std::optional{state_.GetPayload()} : std::nullopt;
165 StringOrPayload<Value> state_;
168template <
typename Key>
169class SearchState<Key, std::string_view,
170 std::enable_if_t<kFitsInStringOrPayload<Key>>>
173 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
175 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
177 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
179 constexpr void SetValue(std::string_view value)
noexcept {
180 state_ = StringOrPayload<Key>{value};
183 [[nodiscard]]
constexpr std::optional<std::string_view> Extract()
noexcept {
184 return IsFound() ? std::optional{state_.GetString()} : std::nullopt;
188 StringOrPayload<Key> state_;
191template <
typename First,
typename Second>
192class SwitchByFirst
final {
194 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
196 constexpr SwitchByFirst& Case(First first, Second second)
noexcept {
197 if (!state_.IsFound() && state_.GetKey() == first) {
198 state_.SetValue(second);
203 template <
typename T,
typename U =
void>
204 constexpr SwitchByFirst& Type() {
208 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
209 return state_.Extract();
213 SearchState<First, Second> state_;
216template <
typename First>
217class SwitchByFirst<First,
void>
final {
219 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
221 constexpr SwitchByFirst& Case(First first)
noexcept {
222 if (!state_.IsFound() && state_.GetKey() == first) {
223 state_.SetValue(Found{0});
228 template <
typename T,
typename U =
void>
229 constexpr SwitchByFirst& Type() {
233 [[nodiscard]]
constexpr bool Extract()
noexcept {
return state_.IsFound(); }
236 SearchState<First, Found> state_;
239template <
typename Second>
240class SwitchByFirstICase
final {
242 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept
245 constexpr SwitchByFirstICase& Case(std::string_view first,
246 Second second)
noexcept {
248 fmt::format(
"String literal '{}' in utils::Switch*::Case() "
249 "should be in lower case",
251 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
252 impl::ICaseEqualLowercase(first, state_.GetKey())) {
253 state_.SetValue(second);
258 template <
typename T,
typename U>
259 constexpr SwitchByFirstICase& Type() {
263 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
264 return state_.Extract();
268 SearchState<std::string_view, Second> state_;
272class SwitchByFirstICase<
void>
final {
274 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept
277 constexpr SwitchByFirstICase& Case(std::string_view first)
noexcept {
279 fmt::format(
"String literal '{}' in utils::Switch*::Case() "
280 "should be in lower case",
282 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
283 impl::ICaseEqualLowercase(first, state_.GetKey())) {
284 state_.SetValue(Found{0});
289 template <
typename T,
typename U>
290 constexpr SwitchByFirstICase& Type() {
294 [[nodiscard]]
constexpr bool Extract()
const noexcept {
295 return state_.IsFound();
299 SearchState<std::string_view, Found> state_;
302template <
typename First>
303class SwitchBySecondICase
final {
305 constexpr explicit SwitchBySecondICase(std::string_view search)
noexcept
308 constexpr SwitchBySecondICase& Case(First first,
309 std::string_view second)
noexcept {
311 fmt::format(
"String literal '{}' in utils::Switch*::Case() "
312 "should be in lower case",
314 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
315 impl::ICaseEqualLowercase(second, state_.GetKey())) {
316 state_.SetValue(first);
321 template <
typename T,
typename U>
322 constexpr SwitchBySecondICase& Type() {
326 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
327 return state_.Extract();
331 SearchState<std::string_view, First> state_;
334template <
typename First,
typename Second>
335class SwitchBySecond
final {
337 constexpr explicit SwitchBySecond(Second search)
noexcept : state_(search) {}
339 constexpr SwitchBySecond& Case(First first, Second second)
noexcept {
340 if (!state_.IsFound() && state_.GetKey() == second) {
341 state_.SetValue(first);
346 template <
typename T,
typename U>
347 constexpr SwitchBySecond& Type() {
351 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
352 return state_.Extract();
356 SearchState<Second, First> state_;
359template <
typename First,
typename Second>
360class SwitchTypesDetected
final {
362 using first_type = First;
363 using second_type = Second;
365 constexpr SwitchTypesDetected& Case(First, Second)
noexcept {
return *
this; }
368template <
typename First>
369class SwitchTypesDetected<First,
void>
final {
371 using first_type = First;
372 using second_type =
void;
374 constexpr SwitchTypesDetected& Case(First)
noexcept {
return *
this; }
377class SwitchTypesDetector
final {
379 constexpr SwitchTypesDetector& operator()()
noexcept {
return *
this; }
381 template <
typename First,
typename Second>
382 constexpr auto Case(First, Second)
noexcept {
383 return Type<First, Second>();
386 template <
typename First>
387 constexpr auto Case(First)
noexcept {
388 return Type<First,
void>();
391 template <
typename First,
typename Second =
void>
392 constexpr auto Type()
noexcept {
394 std::conditional_t<std::is_convertible_v<First, std::string_view>,
395 std::string_view, First>;
397 std::conditional_t<std::is_convertible_v<Second, std::string_view>,
398 std::string_view, Second>;
399 return SwitchTypesDetected<first_type, second_type>{};
403class CaseCounter
final {
405 template <
typename First,
typename Second>
406 constexpr CaseCounter& Case(First, Second)
noexcept {
411 template <
typename First>
412 constexpr CaseCounter& Case(First)
noexcept {
417 template <
typename T,
typename U>
418 constexpr CaseCounter& Type() {
422 [[nodiscard]]
constexpr std::size_t Extract()
const noexcept {
427 std::size_t count_{0};
430class CaseDescriber
final {
432 template <
typename First,
typename Second>
433 CaseDescriber& Case(First first, Second second)
noexcept {
434 if (!description_.empty()) {
435 description_ +=
", ";
438 description_ += fmt::format(
"('{}', '{}')", first, second);
443 template <
typename T,
typename U>
444 constexpr CaseDescriber& Type() {
448 [[nodiscard]] std::string Extract() &&
noexcept {
449 return std::move(description_);
453 std::string description_{};
456class CaseFirstDescriber
final {
458 template <
typename First>
459 CaseFirstDescriber& Case(First first)
noexcept {
460 if (!description_.empty()) {
461 description_ +=
", ";
464 description_ += fmt::format(
"'{}'", first);
469 template <
typename First,
typename Second>
470 CaseFirstDescriber& Case(First first, Second )
noexcept {
474 template <
typename T,
typename U>
475 constexpr CaseFirstDescriber& Type() {
479 [[nodiscard]] std::string Extract() &&
noexcept {
480 return std::move(description_);
484 std::string description_{};
487class CaseSecondDescriber
final {
489 template <
typename First,
typename Second>
490 CaseSecondDescriber& Case(First , Second second)
noexcept {
491 if (!description_.empty()) {
492 description_ +=
", ";
495 description_ += fmt::format(
"'{}'", second);
500 template <
typename T,
typename U>
501 constexpr CaseSecondDescriber& Type() {
505 [[nodiscard]] std::string Extract() &&
noexcept {
506 return std::move(description_);
510 std::string description_{};
513template <
typename First,
typename Second>
514class CaseGetValuesByIndex
final {
516 explicit constexpr CaseGetValuesByIndex(std::size_t search_index)
517 : index_(search_index + 1) {}
519 constexpr CaseGetValuesByIndex& Case(First first, Second second)
noexcept {
532 template <
typename T,
typename U>
533 constexpr CaseGetValuesByIndex& Type() {
537 [[nodiscard]]
constexpr First GetFirst()
noexcept {
538 return std::move(first_);
541 [[nodiscard]]
constexpr Second GetSecond()
noexcept {
542 return std::move(second_);
551template <
typename First>
552class CaseFirstIndexer
final {
554 constexpr explicit CaseFirstIndexer(First search_value)
noexcept
555 : state_(search_value) {}
557 constexpr CaseFirstIndexer& Case(First first)
noexcept {
558 if (!state_.IsFound() && state_.GetKey() == first) {
559 state_.SetValue(index_);
565 template <
typename T,
typename U =
void>
566 constexpr CaseFirstIndexer& Type() {
570 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
571 return state_.Extract();
575 SearchState<First, std::size_t> state_;
576 std::size_t index_ = 0;
608template <
typename BuilderFunc>
609class TrivialBiMap
final {
611 std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
614 using First =
typename TypesPair::first_type;
615 using Second =
typename TypesPair::second_type;
625 using MappedTypeFor =
626 std::conditional_t<std::is_convertible_v<T, First>, Second, First>;
628 constexpr TrivialBiMap(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
629 static_assert(std::is_empty_v<BuilderFunc>,
630 "Mapping function should not capture variables");
631 static_assert(std::is_trivially_copyable_v<First>,
632 "First type in Case must be trivially copyable");
633 static_assert(!std::is_void_v<Second>,
634 "If second type in Case is missing, use "
635 "utils::TrivialSet instead of utils::TrivialBiMap");
636 static_assert(std::is_trivially_copyable_v<Second>,
637 "Second type in Case must be trivially copyable");
640 constexpr std::optional<Second> TryFindByFirst(First value)
const noexcept {
642 [value]() {
return impl::SwitchByFirst<First, Second>{value}; })
646 constexpr std::optional<First> TryFindBySecond(Second value)
const noexcept {
648 [value]() {
return impl::SwitchBySecond<First, Second>{value}; })
653 constexpr std::optional<MappedTypeFor<T>> TryFind(T value)
const noexcept {
655 !std::is_convertible_v<T, First> || !std::is_convertible_v<T, Second>,
656 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead");
658 if constexpr (std::is_convertible_v<T, First>) {
659 return TryFindByFirst(value);
661 return TryFindBySecond(value);
671 return func_([value]() {
return impl::SwitchByFirstICase<Second>{value}; })
681 return func_([value]() {
return impl::SwitchBySecondICase<First>{value}; })
689 static_assert(!std::is_convertible_v<std::string_view, First> ||
690 !std::is_convertible_v<std::string_view, Second>,
691 "Ambiguous conversion, use "
692 "TryFindICaseByFirst/TryFindICaseBySecond");
694 if constexpr (std::is_convertible_v<std::string_view, First>) {
695 return TryFindICaseByFirst(value);
697 return TryFindICaseBySecond(value);
702 constexpr std::size_t
size()
const noexcept {
703 return func_([]() {
return impl::CaseCounter{}; }).Extract();
712 return func_([]() {
return impl::CaseDescriber{}; }).Extract();
722 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
732 return func_([]() {
return impl::CaseSecondDescriber{}; }).Extract();
741 template <
typename T>
743 if constexpr (std::is_convertible_v<T, First>) {
750 constexpr value_type GetValuesByIndex(std::size_t index)
const {
752 [index]() {
return impl::CaseGetValuesByIndex<First, Second>{index}; });
753 return value_type{result.GetFirst(), result.GetSecond()};
758 using iterator_category = std::input_iterator_tag;
759 using difference_type = std::ptrdiff_t;
761 explicit constexpr iterator(
const TrivialBiMap& map, std::size_t position)
762 : map_{map}, position_{position} {}
764 constexpr bool operator==(
iterator other)
const {
765 return position_ == other.position_;
768 constexpr bool operator!=(
iterator other)
const {
769 return position_ != other.position_;
777 constexpr iterator operator++(
int) {
784 return map_.GetValuesByIndex(position_);
788 const TrivialBiMap& map_;
789 std::size_t position_;
794 constexpr iterator cbegin()
const {
return begin(); }
795 constexpr iterator cend()
const {
return end(); }
798 const BuilderFunc func_;
801template <
typename BuilderFunc>
802TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
810template <
typename BuilderFunc>
811class TrivialSet
final {
813 std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
816 using First =
typename TypesPair::first_type;
817 using Second =
typename TypesPair::second_type;
819 constexpr TrivialSet(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
820 static_assert(std::is_empty_v<BuilderFunc>,
821 "Mapping function should not capture variables");
822 static_assert(std::is_trivially_copyable_v<First>,
823 "First type in Case must be trivially copyable");
824 static_assert(std::is_void_v<Second>,
825 "Second type in Case should be skipped in utils::TrivialSet");
828 constexpr bool Contains(First value)
const noexcept {
830 [value]() {
return impl::SwitchByFirst<First, Second>{value}; })
834 constexpr bool ContainsICase(std::string_view value)
const noexcept {
835 static_assert(std::is_convertible_v<First, std::string_view>,
836 "ContainsICase works only with std::string_view");
838 return func_([value]() {
return impl::SwitchByFirstICase<
void>{value}; })
842 constexpr std::size_t size()
const noexcept {
843 return func_([]() {
return impl::CaseCounter{}; }).Extract();
852 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
857 constexpr std::optional<std::size_t>
GetIndex(First value)
const {
858 return func_([value]() {
return impl::CaseFirstIndexer{value}; }).Extract();
862 const BuilderFunc func_;
865template <
typename BuilderFunc>
866TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
873template <
typename ExceptionType =
void,
typename Value,
typename BuilderFunc>
875 if constexpr (!std::is_void_v<ExceptionType>) {
876 if (!value.IsString()) {
877 throw ExceptionType(fmt::format(
878 "Invalid value at '{}': expected a string", value.GetPath()));
882 const auto string = value.
template As<std::string>();
883 const auto parsed = map.TryFind(string);
884 if (parsed)
return *parsed;
887 std::conditional_t<std::is_void_v<ExceptionType>,
888 typename Value::Exception, ExceptionType>;
890 throw Exception(fmt::format(
891 "Invalid value of {} at '{}': '{}' is not one of {}",
892 compiler::GetTypeName<std::decay_t<
decltype(*parsed)>>(), value.GetPath(),
893 string, map.
template DescribeByType<std::string>()));
901template <
typename Enum,
typename BuilderFunc>
902std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
903 static_assert(std::is_enum_v<Enum>);
904 if (
const auto string = map.TryFind(value))
return *string;
908 fmt::format(
"Invalid value of enum {}: {}", compiler::GetTypeName<Enum>(),
909 static_cast<std::underlying_type_t<Enum>>(value)));
912template <
typename Selector,
class Keys,
typename Values,
913 std::size_t... Indices>
914constexpr auto TrivialBiMapMultiCase(Selector selector,
const Keys& keys,
915 const Values& values,
916 std::index_sequence<0, Indices...>) {
917 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
919 selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])),
924template <
const auto& Keys,
const auto& Values>
925struct TrivialBiMapMultiCaseDispatch {
926 template <
class Selector>
927 constexpr auto operator()(Selector selector)
const {
928 constexpr auto kKeysSize = std::size(Keys);
929 return impl::TrivialBiMapMultiCase(selector(), Keys, Values,
930 std::make_index_sequence<kKeysSize>{});
934template <
typename Selector,
class Values, std::size_t... Indices>
935constexpr auto TrivialSetMultiCase(Selector selector,
const Values& values,
936 std::index_sequence<0, Indices...>) {
937 auto selector2 = selector.Case(std::data(values)[0]);
938 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
942template <
const auto& Values>
943struct TrivialSetMultiCaseDispatch {
944 template <
class Selector>
945 constexpr auto operator()(Selector selector)
const {
946 constexpr auto kValuesSize = std::size(Values);
947 return impl::TrivialSetMultiCase(selector(), Values,
948 std::make_index_sequence<kValuesSize>{});
955template <
const auto& Keys,
const auto& Values>
957 static_assert(std::size(Keys) == std::size(Values));
958 static_assert(std::size(Keys) >= 1);
959 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
962template <
const auto& Values>
963constexpr auto MakeTrivialSet() {
964 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});