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 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
204 return state_.Extract();
208 SearchState<First, Second> state_;
211template <
typename First>
212class SwitchByFirst<First,
void>
final {
214 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
216 constexpr SwitchByFirst& Case(First first)
noexcept {
217 if (!state_.IsFound() && state_.GetKey() == first) {
218 state_.SetValue(Found{0});
223 [[nodiscard]]
constexpr bool Extract()
noexcept {
return state_.IsFound(); }
226 SearchState<First, Found> state_;
229template <
typename Second>
230class SwitchByFirstICase
final {
232 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept
235 constexpr SwitchByFirstICase& Case(std::string_view first,
236 Second second)
noexcept {
238 fmt::format(
"String literal '{}' in utils::Switch*::Case() "
239 "should be in lower case",
241 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
242 impl::ICaseEqualLowercase(first, state_.GetKey())) {
243 state_.SetValue(second);
248 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
249 return state_.Extract();
253 SearchState<std::string_view, Second> state_;
257class SwitchByFirstICase<
void>
final {
259 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept
262 constexpr SwitchByFirstICase& Case(std::string_view first)
noexcept {
264 fmt::format(
"String literal '{}' in utils::Switch*::Case() "
265 "should be in lower case",
267 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
268 impl::ICaseEqualLowercase(first, state_.GetKey())) {
269 state_.SetValue(Found{0});
274 [[nodiscard]]
constexpr bool Extract()
const noexcept {
275 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
288 constexpr SwitchBySecondICase& Case(First first,
289 std::string_view second)
noexcept {
291 fmt::format(
"String literal '{}' in utils::Switch*::Case() "
292 "should be in lower case",
294 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
295 impl::ICaseEqualLowercase(second, state_.GetKey())) {
296 state_.SetValue(first);
301 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
302 return state_.Extract();
306 SearchState<std::string_view, First> state_;
309template <
typename First,
typename Second>
310class SwitchBySecond
final {
312 constexpr explicit SwitchBySecond(Second search)
noexcept : state_(search) {}
314 constexpr SwitchBySecond& Case(First first, Second second)
noexcept {
315 if (!state_.IsFound() && state_.GetKey() == second) {
316 state_.SetValue(first);
321 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
322 return state_.Extract();
326 SearchState<Second, First> state_;
329template <
typename First,
typename Second>
330class SwitchTypesDetected
final {
332 using first_type = First;
333 using second_type = Second;
335 constexpr SwitchTypesDetected& Case(First, Second)
noexcept {
return *
this; }
338template <
typename First>
339class SwitchTypesDetected<First,
void>
final {
341 using first_type = First;
342 using second_type =
void;
344 constexpr SwitchTypesDetected& Case(First)
noexcept {
return *
this; }
347class SwitchTypesDetector
final {
349 constexpr SwitchTypesDetector& operator()()
noexcept {
return *
this; }
351 template <
typename First,
typename Second>
352 constexpr auto Case(First, Second)
noexcept {
354 std::conditional_t<std::is_convertible_v<First, std::string_view>,
355 std::string_view, First>;
357 std::conditional_t<std::is_convertible_v<Second, std::string_view>,
358 std::string_view, Second>;
359 return SwitchTypesDetected<first_type, second_type>{};
362 template <
typename First>
363 constexpr auto Case(First)
noexcept {
365 std::conditional_t<std::is_convertible_v<First, std::string_view>,
366 std::string_view, First>;
367 return SwitchTypesDetected<first_type,
void>{};
371class CaseCounter
final {
373 template <
typename First,
typename Second>
374 constexpr CaseCounter& Case(First, Second)
noexcept {
379 template <
typename First>
380 constexpr CaseCounter& Case(First)
noexcept {
385 [[nodiscard]]
constexpr std::size_t Extract()
const noexcept {
390 std::size_t count_{0};
393class CaseDescriber
final {
395 template <
typename First,
typename Second>
396 CaseDescriber& Case(First first, Second second)
noexcept {
397 if (!description_.empty()) {
398 description_ +=
", ";
401 description_ += fmt::format(
"('{}', '{}')", first, second);
406 [[nodiscard]] std::string Extract() &&
noexcept {
407 return std::move(description_);
411 std::string description_{};
414class CaseFirstDescriber
final {
416 template <
typename First>
417 CaseFirstDescriber& Case(First first)
noexcept {
418 if (!description_.empty()) {
419 description_ +=
", ";
422 description_ += fmt::format(
"'{}'", first);
427 template <
typename First,
typename Second>
428 CaseFirstDescriber& Case(First first, Second )
noexcept {
432 [[nodiscard]] std::string Extract() &&
noexcept {
433 return std::move(description_);
437 std::string description_{};
440class CaseSecondDescriber
final {
442 template <
typename First,
typename Second>
443 CaseSecondDescriber& Case(First , Second second)
noexcept {
444 if (!description_.empty()) {
445 description_ +=
", ";
448 description_ += fmt::format(
"'{}'", second);
453 [[nodiscard]] std::string Extract() &&
noexcept {
454 return std::move(description_);
458 std::string description_{};
486template <
typename BuilderFunc>
487class TrivialBiMap
final {
489 std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
492 using First =
typename TypesPair::first_type;
493 using Second =
typename TypesPair::second_type;
498 using MappedTypeFor =
499 std::conditional_t<std::is_convertible_v<T, First>, Second, First>;
501 constexpr TrivialBiMap(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
502 static_assert(std::is_empty_v<BuilderFunc>,
503 "Mapping function should not capture variables");
504 static_assert(std::is_trivially_copyable_v<First>,
505 "First type in Case must be trivially copyable");
506 static_assert(!std::is_void_v<Second>,
507 "If second type in Case is missing, use "
508 "utils::TrivialSet instead of utils::TrivialBiMap");
509 static_assert(std::is_trivially_copyable_v<Second>,
510 "Second type in Case must be trivially copyable");
513 constexpr std::optional<Second> TryFindByFirst(First value)
const noexcept {
515 [value]() {
return impl::SwitchByFirst<First, Second>{value}; })
519 constexpr std::optional<First> TryFindBySecond(Second value)
const noexcept {
521 [value]() {
return impl::SwitchBySecond<First, Second>{value}; })
526 constexpr std::optional<MappedTypeFor<T>> TryFind(T value)
const noexcept {
528 !std::is_convertible_v<T, First> || !std::is_convertible_v<T, Second>,
529 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead");
531 if constexpr (std::is_convertible_v<T, First>) {
532 return TryFindByFirst(value);
534 return TryFindBySecond(value);
544 return func_([value]() {
return impl::SwitchByFirstICase<Second>{value}; })
554 return func_([value]() {
return impl::SwitchBySecondICase<First>{value}; })
562 static_assert(!std::is_convertible_v<std::string_view, First> ||
563 !std::is_convertible_v<std::string_view, Second>,
564 "Ambiguous conversion, use "
565 "TryFindICaseByFirst/TryFindICaseBySecond");
567 if constexpr (std::is_convertible_v<std::string_view, First>) {
568 return TryFindICaseByFirst(value);
570 return TryFindICaseBySecond(value);
575 constexpr std::size_t
size()
const noexcept {
576 return func_([]() {
return impl::CaseCounter{}; }).Extract();
585 return func_([]() {
return impl::CaseDescriber{}; }).Extract();
595 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
605 return func_([]() {
return impl::CaseSecondDescriber{}; }).Extract();
614 template <
typename T>
616 if constexpr (std::is_convertible_v<T, First>) {
624 const BuilderFunc func_;
627template <
typename BuilderFunc>
628TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
636template <
typename BuilderFunc>
637class TrivialSet
final {
639 std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
642 using First =
typename TypesPair::first_type;
643 using Second =
typename TypesPair::second_type;
645 constexpr TrivialSet(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
646 static_assert(std::is_empty_v<BuilderFunc>,
647 "Mapping function should not capture variables");
648 static_assert(std::is_trivially_copyable_v<First>,
649 "First type in Case must be trivially copyable");
650 static_assert(std::is_void_v<Second>,
651 "Second type in Case should be skipped in utils::TrivialSet");
654 constexpr bool Contains(First value)
const noexcept {
656 [value]() {
return impl::SwitchByFirst<First, Second>{value}; })
660 constexpr bool ContainsICase(std::string_view value)
const noexcept {
661 static_assert(std::is_convertible_v<First, std::string_view>,
662 "ContainsICase works only with std::string_view");
664 return func_([value]() {
return impl::SwitchByFirstICase<
void>{value}; })
668 constexpr std::size_t size()
const noexcept {
669 return func_([]() {
return impl::CaseCounter{}; }).Extract();
678 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
682 const BuilderFunc func_;
685template <
typename BuilderFunc>
686TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
693template <
typename ExceptionType =
void,
typename Value,
typename BuilderFunc>
695 if constexpr (!std::is_void_v<ExceptionType>) {
696 if (!value.IsString()) {
697 throw ExceptionType(fmt::format(
698 "Invalid value at '{}': expected a string", value.GetPath()));
702 const auto string = value.
template As<std::string>();
703 const auto parsed = map.TryFind(string);
704 if (parsed)
return *parsed;
707 std::conditional_t<std::is_void_v<ExceptionType>,
708 typename Value::Exception, ExceptionType>;
710 throw Exception(fmt::format(
711 "Invalid value of {} at '{}': '{}' is not one of {}",
712 compiler::GetTypeName<std::decay_t<
decltype(*parsed)>>(), value.GetPath(),
713 string, map.
template DescribeByType<std::string>()));
721template <
typename Enum,
typename BuilderFunc>
722std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
723 static_assert(std::is_enum_v<Enum>);
724 if (
const auto string = map.TryFind(value))
return *string;
728 fmt::format(
"Invalid value of enum {}: {}", compiler::GetTypeName<Enum>(),
729 static_cast<std::underlying_type_t<Enum>>(value)));