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>
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>
90concept FitsInStringOrPayload =
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(FitsInStringOrPayload<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 <FitsInStringOrPayload Value>
147class SearchState<std::string_view, 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 <FitsInStringOrPayload Value>
168class SearchState<
zstring_view, Value>
final :
public SearchState<std::string_view, Value> {};
170template <FitsInStringOrPayload Value>
171class SearchState<
StringLiteral, Value>
final :
public SearchState<std::string_view, Value> {};
173template <FitsInStringOrPayload Key>
174class SearchState<Key, std::string_view>
final {
176 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
178 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
180 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
182 constexpr void SetValue(std::string_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
184 [[nodiscard]]
constexpr std::optional<std::string_view> Extract()
noexcept {
185 return IsFound() ? std::optional{std::string_view{state_.GetStringPointer(), state_.GetStringSize()}}
190 StringOrPayload<Key> state_;
193template <FitsInStringOrPayload Key>
196 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
198 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
200 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
202 constexpr void SetValue(
zstring_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
204 [[nodiscard]]
constexpr std::optional<
zstring_view> Extract()
noexcept {
210 StringOrPayload<Key> state_;
213template <FitsInStringOrPayload Key>
216 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
218 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
220 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
222 constexpr void SetValue(
StringLiteral value)
noexcept { state_ = StringOrPayload<Key>{value}; }
224 [[nodiscard]]
constexpr std::optional<
StringLiteral> Extract()
noexcept {
230 StringOrPayload<Key> state_;
233template <
typename First,
typename Second>
234class SwitchByFirst
final {
236 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
238 constexpr SwitchByFirst& Case(First first, Second second)
noexcept {
239 if (!state_.IsFound() && state_.GetKey() == first) {
240 state_.SetValue(second);
245 template <
typename T,
typename U =
void>
246 constexpr SwitchByFirst& Type() {
250 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
253 SearchState<First, Second> state_;
256template <
typename First>
257class SwitchByFirst<First,
void>
final {
259 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
261 constexpr SwitchByFirst& Case(First first)
noexcept {
262 if (!state_.IsFound() && state_.GetKey() == first) {
263 state_.SetValue(Found{0});
268 template <
typename T,
typename U =
void>
269 constexpr SwitchByFirst& Type() {
273 [[nodiscard]]
constexpr bool Extract()
noexcept {
return state_.IsFound(); }
276 SearchState<First, Found> state_;
279template <
typename Second>
280class SwitchByFirstICase
final {
282 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
284 constexpr SwitchByFirstICase& Case(std::string_view first, Second second)
noexcept {
286 !impl::HasUppercaseAscii(first),
287 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", first)
289 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
290 impl::ICaseEqualLowercase(first, state_.GetKey()))
292 state_.SetValue(second);
297 template <
typename T,
typename U>
298 constexpr SwitchByFirstICase& Type() {
302 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
305 SearchState<std::string_view, Second> state_;
309class SwitchByFirstICase<
void>
final {
311 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
313 constexpr SwitchByFirstICase& Case(std::string_view first)
noexcept {
315 !impl::HasUppercaseAscii(first),
316 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", first)
318 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
319 impl::ICaseEqualLowercase(first, state_.GetKey()))
321 state_.SetValue(Found{0});
326 template <
typename T,
typename U>
327 constexpr SwitchByFirstICase& Type() {
331 [[nodiscard]]
constexpr bool Extract()
const noexcept {
return state_.IsFound(); }
334 SearchState<std::string_view, Found> state_;
337template <
typename First>
338class SwitchBySecondICase
final {
340 constexpr explicit SwitchBySecondICase(std::string_view search)
noexcept : state_(search) {}
342 constexpr SwitchBySecondICase& Case(First first, std::string_view second)
noexcept {
344 !impl::HasUppercaseAscii(second),
345 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", second)
347 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
348 impl::ICaseEqualLowercase(second, state_.GetKey()))
350 state_.SetValue(first);
355 template <
typename T,
typename U>
356 constexpr SwitchBySecondICase& Type() {
360 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
363 SearchState<std::string_view, First> state_;
366template <
typename First,
typename Second>
367class SwitchBySecond
final {
369 constexpr explicit SwitchBySecond(Second search)
noexcept : state_(search) {}
371 constexpr SwitchBySecond& Case(First first, Second second)
noexcept {
372 if (!state_.IsFound() && state_.GetKey() == second) {
373 state_.SetValue(first);
378 template <
typename T,
typename U>
379 constexpr SwitchBySecond& Type() {
383 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
386 SearchState<Second, First> state_;
389template <
typename First,
typename Second>
390class SwitchTypesDetected
final {
392 using first_type = First;
393 using second_type = Second;
395 constexpr SwitchTypesDetected& Case(First, Second)
noexcept {
return *
this; }
398template <
typename First>
399class SwitchTypesDetected<First,
void>
final {
401 using first_type = First;
402 using second_type =
void;
404 constexpr SwitchTypesDetected& Case(First)
noexcept {
return *
this; }
407class SwitchTypesDetector
final {
410 using DetectType = std::conditional_t<
416 std::conditional_t<std::is_convertible_v<T, std::string_view>, std::string_view, T>>>;
418 constexpr SwitchTypesDetector& operator()()
noexcept {
return *
this; }
420 template <
typename First,
typename Second>
421 constexpr auto Case(First, Second)
noexcept {
422 return Type<First, Second>();
425 template <
typename First>
426 constexpr auto Case(First)
noexcept {
427 return Type<First,
void>();
430 template <
typename First,
typename Second =
void>
431 constexpr auto Type()
noexcept {
432 return SwitchTypesDetected<DetectType<First>, DetectType<Second>>{};
436class CaseCounter
final {
438 template <
typename First,
typename Second>
439 constexpr CaseCounter& Case(First, Second)
noexcept {
444 template <
typename First>
445 constexpr CaseCounter& Case(First)
noexcept {
450 template <
typename T,
typename U>
451 constexpr CaseCounter& Type() {
455 [[nodiscard]]
constexpr std::size_t Extract()
const noexcept {
return count_; }
458 std::size_t count_{0};
461class CaseDescriber
final {
463 template <
typename First,
typename Second>
464 CaseDescriber& Case(First first, Second second)
noexcept {
465 if (!description_.empty()) {
466 description_ +=
", ";
469 description_ += fmt::format(
"('{}', '{}')", first, second);
474 template <
typename T,
typename U>
475 constexpr CaseDescriber& Type() {
479 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
482 std::string description_{};
485class CaseFirstDescriber
final {
487 template <
typename First>
488 CaseFirstDescriber& Case(First first)
noexcept {
489 if (!description_.empty()) {
490 description_ +=
", ";
493 description_ += fmt::format(
"'{}'", first);
498 template <
typename First,
typename Second>
499 CaseFirstDescriber& Case(First first, Second )
noexcept {
503 template <
typename T,
typename U =
void>
504 constexpr CaseFirstDescriber& Type() {
508 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
511 std::string description_{};
514class CaseSecondDescriber
final {
516 template <
typename First,
typename Second>
517 CaseSecondDescriber& Case(First , Second second)
noexcept {
518 if (!description_.empty()) {
519 description_ +=
", ";
522 description_ += fmt::format(
"'{}'", second);
527 template <
typename T,
typename U>
528 constexpr CaseSecondDescriber& Type() {
532 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
535 std::string description_{};
538template <
typename First,
typename Second>
539class CaseGetValuesByIndex
final {
541 explicit constexpr CaseGetValuesByIndex(std::size_t search_index)
542 : index_(search_index + 1)
545 constexpr CaseGetValuesByIndex& Case(First first, Second second)
noexcept {
550 lazy_ = Lazy{Storage{first, second}};
557 template <
typename T,
typename U>
558 constexpr CaseGetValuesByIndex& Type() {
562 [[nodiscard]]
constexpr First GetFirst()
noexcept {
return std::move(lazy_.storage.first); }
564 [[nodiscard]]
constexpr Second GetSecond()
noexcept {
return std::move(lazy_.storage.second); }
576 constexpr Lazy()
noexcept : empty{} {}
577 constexpr Lazy(Storage s)
noexcept : storage{s} {}
585template <
typename First>
586class CaseFirstIndexer
final {
588 constexpr explicit CaseFirstIndexer(First search_value)
noexcept : state_(search_value) {}
590 constexpr CaseFirstIndexer& Case(First first)
noexcept {
591 if (!state_.IsFound() && state_.GetKey() == first) {
592 state_.SetValue(index_);
598 template <
typename T,
typename U =
void>
599 constexpr CaseFirstIndexer& Type() {
603 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
606 SearchState<First, std::size_t> state_;
607 std::size_t index_ = 0;
610template <
typename First>
611class CaseFirstIndexerICase
final {
613 constexpr explicit CaseFirstIndexerICase(First search_value)
noexcept : state_(search_value) {}
615 constexpr CaseFirstIndexerICase& Case(First first)
noexcept {
616 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
617 impl::ICaseEqualLowercase(first, state_.GetKey()))
619 state_.SetValue(index_);
625 template <
typename T,
typename U =
void>
626 constexpr CaseFirstIndexerICase& Type() {
630 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
633 SearchState<First, std::size_t> state_;
634 std::size_t index_ = 0;
641using DecayToStringView =
671template <
typename BuilderFunc>
672class TrivialBiMap
final {
673 using TypesPair = std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
676 using First =
typename TypesPair::first_type;
677 using Second =
typename TypesPair::second_type;
687 using MappedTypeFor = std::conditional_t<std::is_convertible_v<T, DecayToStringView<First>>, Second, First>;
689 constexpr TrivialBiMap(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
690 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
691 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
693 !std::is_void_v<Second>,
694 "If second type in Case is missing, use utils::TrivialSet instead of utils::TrivialBiMap"
696 static_assert(std::is_trivially_copyable_v<Second>,
"Second type in Case must be trivially copyable");
699 constexpr std::optional<Second> TryFindByFirst(DecayToStringView<First> value)
const noexcept {
700 return func_([value]() {
return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
703 constexpr std::optional<First> TryFindBySecond(DecayToStringView<Second> value)
const noexcept {
704 return func_([value]() {
return impl::SwitchBySecond<First, DecayToStringView<Second>>{value}; }).Extract();
708 constexpr std::optional<MappedTypeFor<T>> TryFind(T value)
const noexcept {
709 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
711 !std::is_convertible_v<T, DecayToStringView<Second>>,
712 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead"
714 return TryFindByFirst(value);
716 return TryFindBySecond(value);
725 return func_([value]() {
return impl::SwitchByFirstICase<Second>{value}; }).Extract();
733 return func_([value]() {
return impl::SwitchBySecondICase<First>{value}; }).Extract();
738 constexpr std::optional<MappedTypeFor<std::string_view>>
TryFindICase(std::string_view value)
const noexcept {
739 if constexpr (std::is_convertible_v<std::string_view, DecayToStringView<First>>) {
741 !std::is_convertible_v<std::string_view, DecayToStringView<Second>>,
742 "Ambiguous conversion, use TryFindICaseByFirst/TryFindICaseBySecond"
751 constexpr std::size_t
size()
const noexcept {
752 return func_([]() {
return impl::CaseCounter{}; }).Extract();
761 return func_([]() {
return impl::CaseDescriber{}; }).Extract();
771 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
781 return func_([]() {
return impl::CaseSecondDescriber{}; }).Extract();
790 template <
typename T>
792 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
799 constexpr ValueType GetValuesByIndex(std::size_t index)
const {
801 auto result = func_([index]() {
return impl::CaseGetValuesByIndex<First, Second>{index}; });
802 return ValueType{result.GetFirst(), result.GetSecond()};
807 using iterator_category = std::input_iterator_tag;
808 using difference_type = std::ptrdiff_t;
810 explicit constexpr Iterator(
const TrivialBiMap& map, std::size_t position)
815 constexpr bool operator==(
Iterator other)
const {
return position_ == other.position_; }
817 constexpr bool operator!=(
Iterator other)
const {
return position_ != other.position_; }
824 constexpr Iterator operator++(
int) {
830 constexpr ValueType operator*()
const {
return map_.GetValuesByIndex(position_); }
833 const TrivialBiMap& map_;
834 std::size_t position_;
839 constexpr Iterator cbegin()
const {
return begin(); }
840 constexpr Iterator cend()
const {
return end(); }
843 const BuilderFunc func_;
846template <
typename BuilderFunc>
847TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
855template <
typename BuilderFunc>
856class TrivialSet
final {
857 using TypesPair = std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
860 using First =
typename TypesPair::first_type;
861 using Second =
typename TypesPair::second_type;
863 constexpr TrivialSet(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
864 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
865 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
866 static_assert(std::is_void_v<Second>,
"Second type in Case should be skipped in utils::TrivialSet");
869 constexpr bool Contains(DecayToStringView<First> value)
const noexcept {
870 return func_([value]() {
return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
873 constexpr bool ContainsICase(std::string_view value)
const noexcept {
875 std::is_convertible_v<DecayToStringView<First>, std::string_view>,
876 "ContainsICase works only with std::string_view"
879 return func_([value]() {
return impl::SwitchByFirstICase<
void>{value}; }).Extract();
882 constexpr std::size_t size()
const noexcept {
883 return func_([]() {
return impl::CaseCounter{}; }).Extract();
892 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
897 constexpr std::optional<std::size_t>
GetIndex(DecayToStringView<First> value)
const {
898 return func_([value]() {
return impl::CaseFirstIndexer{value}; }).Extract();
903 constexpr std::optional<std::size_t>
GetIndexICase(std::string_view value)
const {
904 return func_([value]() {
return impl::CaseFirstIndexerICase{value}; }).Extract();
908 const BuilderFunc func_;
911template <
typename BuilderFunc>
912TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
919template <
typename ExceptionType =
void,
typename Value,
typename BuilderFunc>
921 if constexpr (!std::is_void_v<ExceptionType>) {
922 if (!value.IsString()) {
923 throw ExceptionType(fmt::format(
"Invalid value at '{}': expected a string", value.GetPath()));
927 const auto string = value.
template As<std::string>();
928 const auto parsed = map.TryFind(string);
933 using Exception = std::conditional_t<std::is_void_v<ExceptionType>,
typename Value::Exception, ExceptionType>;
935 throw Exception(fmt::format(
936 "Invalid value of {} at '{}': '{}' is not one of {}",
937 compiler::GetTypeName<std::decay_t<
decltype(*parsed)>>(),
940 map.
template DescribeByType<std::string_view>()
949template <
typename Enum,
typename BuilderFunc>
950std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
951 static_assert(std::is_enum_v<Enum>);
952 if (
const auto string = map.TryFind(value)) {
959 "Invalid value of enum {}: {}",
961 static_cast<std::underlying_type_t<Enum>>(value)
966template <
typename Selector,
class Keys,
typename Values, std::size_t... Indices>
968TrivialBiMapMultiCase(Selector selector,
const Keys& keys,
const Values& values, std::index_sequence<0, Indices...>) {
969 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
970 ((selector2 = selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])), ...);
974template <
const auto& Keys,
const auto& Values>
975struct TrivialBiMapMultiCaseDispatch {
976 template <
class Selector>
977 constexpr auto operator()(Selector selector)
const {
978 constexpr auto kKeysSize = std::size(Keys);
979 return impl::TrivialBiMapMultiCase(selector(), Keys, Values, std::make_index_sequence<kKeysSize>{});
983template <
typename Selector,
class Values, std::size_t... Indices>
984constexpr auto TrivialSetMultiCase(Selector selector,
const Values& values, std::index_sequence<0, Indices...>) {
985 auto selector2 = selector.Case(std::data(values)[0]);
986 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
990template <
const auto& Values>
991struct TrivialSetMultiCaseDispatch {
992 template <
class Selector>
993 constexpr auto operator()(Selector selector)
const {
994 constexpr auto kValuesSize = std::size(Values);
995 return impl::TrivialSetMultiCase(selector(), Values, std::make_index_sequence<kValuesSize>{});
1002template <
const auto& Keys,
const auto& Values>
1004 static_assert(std::size(Keys) == std::size(Values));
1005 static_assert(std::size(Keys) >= 1);
1006 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
1009template <
const auto& Values>
1010constexpr auto MakeTrivialSet() {
1011 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});
1016USERVER_NAMESPACE_END