userver: userver/utils/trivial_map.hpp Source File
Loading...
Searching...
No Matches
trivial_map.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/trivial_map.hpp
4/// @brief Bidirectional map|sets over string literals or other trivial types.
5
6#include <cstddef>
7#include <optional>
8#include <string>
9#include <string_view>
10#include <type_traits>
11#include <utility>
12#include <variant>
13
14#include <fmt/format.h>
15
16#include <userver/compiler/demangle.hpp>
17#include <userver/utils/assert.hpp>
18
19USERVER_NAMESPACE_BEGIN
20
21namespace utils {
22
23namespace impl {
24
25constexpr bool HasUppercaseAscii(std::string_view value) noexcept {
26 for (auto c : value) {
27 if ('A' <= c && c <= 'Z') return true;
28 }
29
30 return false;
31}
32
33constexpr bool ICaseEqualLowercase(std::string_view lowercase,
34 std::string_view y) noexcept {
35 const auto size = lowercase.size();
36 UASSERT(size == y.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]) {
44 return false;
45 }
46 }
47 }
48
49 return true;
50}
51
52struct Found final {
53 constexpr explicit Found(std::size_t value) noexcept { UASSERT(value == 0); }
54
55 constexpr explicit operator std::size_t() const noexcept { return 0; }
56};
57
58template <typename Key, typename Value, typename Enabled = void>
59class SearchState final {
60 public:
61 constexpr explicit SearchState(Key key) noexcept
62 : key_or_result_(std::in_place_index<0>, key) {}
63
64 constexpr bool IsFound() const noexcept {
65 return key_or_result_.index() != 0;
66 }
67
68 constexpr Key GetKey() const noexcept {
69 UASSERT(!IsFound());
70 return std::get<0>(key_or_result_);
71 }
72
73 constexpr void SetValue(Value value) noexcept {
74 key_or_result_ = std::variant<Key, Value>(std::in_place_index<1>, value);
75 }
76
77 [[nodiscard]] constexpr std::optional<Value> Extract() noexcept {
78 if (key_or_result_.index() == 1) {
79 return std::get<1>(key_or_result_);
80 } else {
81 return std::nullopt;
82 }
83 }
84
85 private:
86 std::variant<Key, Value> key_or_result_;
87};
88
89inline constexpr std::size_t kInvalidSize =
90 std::numeric_limits<std::size_t>::max();
91
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>);
97
98// A compacted std::variant<std::string_view, Payload>
99template <typename Payload>
100class StringOrPayload final {
101 public:
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();
108#endif
109 }
110
111 constexpr explicit StringOrPayload(Payload payload) noexcept
112 : data_or_payload_(payload), size_(kInvalidSize) {}
113
114 constexpr bool HasPayload() const noexcept { return size_ == kInvalidSize; }
115
116 constexpr std::string_view GetString() const noexcept {
117 UASSERT(!HasPayload());
118 return std::string_view{data_or_payload_.data, size_};
119 }
120
121 constexpr Payload GetPayload() const noexcept {
122 UASSERT(HasPayload());
123 return data_or_payload_.payload;
124 }
125
126 private:
127 static_assert(kFitsInStringOrPayload<Payload>);
128
129 union DataOrPayload {
130 constexpr explicit DataOrPayload(const char* data) noexcept : data(data) {}
131
132 constexpr explicit DataOrPayload(Payload payload) noexcept
133 : payload(payload) {}
134
135 const char* data{};
136 Payload payload;
137 };
138
139 DataOrPayload data_or_payload_;
140 std::size_t size_;
141};
142
143template <typename Value>
144class SearchState<std::string_view, Value,
145 std::enable_if_t<kFitsInStringOrPayload<Value>>>
146 final {
147 public:
148 constexpr explicit SearchState(std::string_view key) noexcept : state_(key) {}
149
150 constexpr bool IsFound() const noexcept { return state_.HasPayload(); }
151
152 constexpr std::string_view GetKey() const noexcept {
153 return state_.GetString();
154 }
155
156 constexpr void SetValue(Value value) noexcept {
157 state_ = StringOrPayload<Value>{value};
158 }
159
160 [[nodiscard]] constexpr std::optional<Value> Extract() noexcept {
161 return IsFound() ? std::optional{state_.GetPayload()} : std::nullopt;
162 }
163
164 private:
165 StringOrPayload<Value> state_;
166};
167
168template <typename Key>
169class SearchState<Key, std::string_view,
170 std::enable_if_t<kFitsInStringOrPayload<Key>>>
171 final {
172 public:
173 constexpr explicit SearchState(Key key) noexcept : state_(key) {}
174
175 constexpr bool IsFound() const noexcept { return !state_.HasPayload(); }
176
177 constexpr Key GetKey() const noexcept { return state_.GetPayload(); }
178
179 constexpr void SetValue(std::string_view value) noexcept {
180 state_ = StringOrPayload<Key>{value};
181 }
182
183 [[nodiscard]] constexpr std::optional<std::string_view> Extract() noexcept {
184 return IsFound() ? std::optional{state_.GetString()} : std::nullopt;
185 }
186
187 private:
188 StringOrPayload<Key> state_;
189};
190
191template <typename First, typename Second>
192class SwitchByFirst final {
193 public:
194 constexpr explicit SwitchByFirst(First search) noexcept : state_(search) {}
195
196 constexpr SwitchByFirst& Case(First first, Second second) noexcept {
197 if (!state_.IsFound() && state_.GetKey() == first) {
198 state_.SetValue(second);
199 }
200 return *this;
201 }
202
203 template <typename T, typename U = void>
204 constexpr SwitchByFirst& Type() {
205 return *this;
206 }
207
208 [[nodiscard]] constexpr std::optional<Second> Extract() noexcept {
209 return state_.Extract();
210 }
211
212 private:
213 SearchState<First, Second> state_;
214};
215
216template <typename First>
217class SwitchByFirst<First, void> final {
218 public:
219 constexpr explicit SwitchByFirst(First search) noexcept : state_(search) {}
220
221 constexpr SwitchByFirst& Case(First first) noexcept {
222 if (!state_.IsFound() && state_.GetKey() == first) {
223 state_.SetValue(Found{0});
224 }
225 return *this;
226 }
227
228 template <typename T, typename U = void>
229 constexpr SwitchByFirst& Type() {
230 return *this;
231 }
232
233 [[nodiscard]] constexpr bool Extract() noexcept { return state_.IsFound(); }
234
235 private:
236 SearchState<First, Found> state_;
237};
238
239template <typename Second>
240class SwitchByFirstICase final {
241 public:
242 constexpr explicit SwitchByFirstICase(std::string_view search) noexcept
243 : state_(search) {}
244
245 constexpr SwitchByFirstICase& Case(std::string_view first,
246 Second second) noexcept {
247 UASSERT_MSG(!impl::HasUppercaseAscii(first),
248 fmt::format("String literal '{}' in utils::Switch*::Case() "
249 "should be in lower case",
250 first));
251 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
252 impl::ICaseEqualLowercase(first, state_.GetKey())) {
253 state_.SetValue(second);
254 }
255 return *this;
256 }
257
258 template <typename T, typename U>
259 constexpr SwitchByFirstICase& Type() {
260 return *this;
261 }
262
263 [[nodiscard]] constexpr std::optional<Second> Extract() noexcept {
264 return state_.Extract();
265 }
266
267 private:
268 SearchState<std::string_view, Second> state_;
269};
270
271template <>
272class SwitchByFirstICase<void> final {
273 public:
274 constexpr explicit SwitchByFirstICase(std::string_view search) noexcept
275 : state_(search) {}
276
277 constexpr SwitchByFirstICase& Case(std::string_view first) noexcept {
278 UASSERT_MSG(!impl::HasUppercaseAscii(first),
279 fmt::format("String literal '{}' in utils::Switch*::Case() "
280 "should be in lower case",
281 first));
282 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
283 impl::ICaseEqualLowercase(first, state_.GetKey())) {
284 state_.SetValue(Found{0});
285 }
286 return *this;
287 }
288
289 template <typename T, typename U>
290 constexpr SwitchByFirstICase& Type() {
291 return *this;
292 }
293
294 [[nodiscard]] constexpr bool Extract() const noexcept {
295 return state_.IsFound();
296 }
297
298 private:
299 SearchState<std::string_view, Found> state_;
300};
301
302template <typename First>
303class SwitchBySecondICase final {
304 public:
305 constexpr explicit SwitchBySecondICase(std::string_view search) noexcept
306 : state_(search) {}
307
308 constexpr SwitchBySecondICase& Case(First first,
309 std::string_view second) noexcept {
310 UASSERT_MSG(!impl::HasUppercaseAscii(second),
311 fmt::format("String literal '{}' in utils::Switch*::Case() "
312 "should be in lower case",
313 second));
314 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
315 impl::ICaseEqualLowercase(second, state_.GetKey())) {
316 state_.SetValue(first);
317 }
318 return *this;
319 }
320
321 template <typename T, typename U>
322 constexpr SwitchBySecondICase& Type() {
323 return *this;
324 }
325
326 [[nodiscard]] constexpr std::optional<First> Extract() noexcept {
327 return state_.Extract();
328 }
329
330 private:
331 SearchState<std::string_view, First> state_;
332};
333
334template <typename First, typename Second>
335class SwitchBySecond final {
336 public:
337 constexpr explicit SwitchBySecond(Second search) noexcept : state_(search) {}
338
339 constexpr SwitchBySecond& Case(First first, Second second) noexcept {
340 if (!state_.IsFound() && state_.GetKey() == second) {
341 state_.SetValue(first);
342 }
343 return *this;
344 }
345
346 template <typename T, typename U>
347 constexpr SwitchBySecond& Type() {
348 return *this;
349 }
350
351 [[nodiscard]] constexpr std::optional<First> Extract() noexcept {
352 return state_.Extract();
353 }
354
355 private:
356 SearchState<Second, First> state_;
357};
358
359template <typename First, typename Second>
360class SwitchTypesDetected final {
361 public:
362 using first_type = First;
363 using second_type = Second;
364
365 constexpr SwitchTypesDetected& Case(First, Second) noexcept { return *this; }
366};
367
368template <typename First>
369class SwitchTypesDetected<First, void> final {
370 public:
371 using first_type = First;
372 using second_type = void;
373
374 constexpr SwitchTypesDetected& Case(First) noexcept { return *this; }
375};
376
377class SwitchTypesDetector final {
378 public:
379 constexpr SwitchTypesDetector& operator()() noexcept { return *this; }
380
381 template <typename First, typename Second>
382 constexpr auto Case(First, Second) noexcept {
383 return Type<First, Second>();
384 }
385
386 template <typename First>
387 constexpr auto Case(First) noexcept {
388 return Type<First, void>();
389 }
390
391 template <typename First, typename Second = void>
392 constexpr auto Type() noexcept {
393 using first_type =
394 std::conditional_t<std::is_convertible_v<First, std::string_view>,
395 std::string_view, First>;
396 using second_type =
397 std::conditional_t<std::is_convertible_v<Second, std::string_view>,
398 std::string_view, Second>;
399 return SwitchTypesDetected<first_type, second_type>{};
400 }
401};
402
403class CaseCounter final {
404 public:
405 template <typename First, typename Second>
406 constexpr CaseCounter& Case(First, Second) noexcept {
407 ++count_;
408 return *this;
409 }
410
411 template <typename First>
412 constexpr CaseCounter& Case(First) noexcept {
413 ++count_;
414 return *this;
415 }
416
417 template <typename T, typename U>
418 constexpr CaseCounter& Type() {
419 return *this;
420 }
421
422 [[nodiscard]] constexpr std::size_t Extract() const noexcept {
423 return count_;
424 }
425
426 private:
427 std::size_t count_{0};
428};
429
430class CaseDescriber final {
431 public:
432 template <typename First, typename Second>
433 CaseDescriber& Case(First first, Second second) noexcept {
434 if (!description_.empty()) {
435 description_ += ", ";
436 }
437
438 description_ += fmt::format("('{}', '{}')", first, second);
439
440 return *this;
441 }
442
443 template <typename T, typename U>
444 constexpr CaseDescriber& Type() {
445 return *this;
446 }
447
448 [[nodiscard]] std::string Extract() && noexcept {
449 return std::move(description_);
450 }
451
452 private:
453 std::string description_{};
454};
455
456class CaseFirstDescriber final {
457 public:
458 template <typename First>
459 CaseFirstDescriber& Case(First first) noexcept {
460 if (!description_.empty()) {
461 description_ += ", ";
462 }
463
464 description_ += fmt::format("'{}'", first);
465
466 return *this;
467 }
468
469 template <typename First, typename Second>
470 CaseFirstDescriber& Case(First first, Second /*second*/) noexcept {
471 return Case(first);
472 }
473
474 template <typename T, typename U>
475 constexpr CaseFirstDescriber& Type() {
476 return *this;
477 }
478
479 [[nodiscard]] std::string Extract() && noexcept {
480 return std::move(description_);
481 }
482
483 private:
484 std::string description_{};
485};
486
487class CaseSecondDescriber final {
488 public:
489 template <typename First, typename Second>
490 CaseSecondDescriber& Case(First /*first*/, Second second) noexcept {
491 if (!description_.empty()) {
492 description_ += ", ";
493 }
494
495 description_ += fmt::format("'{}'", second);
496
497 return *this;
498 }
499
500 template <typename T, typename U>
501 constexpr CaseSecondDescriber& Type() {
502 return *this;
503 }
504
505 [[nodiscard]] std::string Extract() && noexcept {
506 return std::move(description_);
507 }
508
509 private:
510 std::string description_{};
511};
512
513template <typename First, typename Second>
514class CaseGetValuesByIndex final {
515 public:
516 explicit constexpr CaseGetValuesByIndex(std::size_t search_index)
517 : index_(search_index + 1) {}
518
519 constexpr CaseGetValuesByIndex& Case(First first, Second second) noexcept {
520 if (index_ == 0) {
521 return *this;
522 }
523 if (index_ == 1) {
524 first_ = first;
525 second_ = second;
526 }
527 --index_;
528
529 return *this;
530 }
531
532 template <typename T, typename U>
533 constexpr CaseGetValuesByIndex& Type() {
534 return *this;
535 }
536
537 [[nodiscard]] constexpr First GetFirst() noexcept {
538 return std::move(first_);
539 }
540
541 [[nodiscard]] constexpr Second GetSecond() noexcept {
542 return std::move(second_);
543 }
544
545 private:
546 std::size_t index_;
547 First first_{};
548 Second second_{};
549};
550
551template <typename First>
552class CaseFirstIndexer final {
553 public:
554 constexpr explicit CaseFirstIndexer(First search_value) noexcept
555 : state_(search_value) {}
556
557 constexpr CaseFirstIndexer& Case(First first) noexcept {
558 if (!state_.IsFound() && state_.GetKey() == first) {
559 state_.SetValue(index_);
560 }
561 ++index_;
562 return *this;
563 }
564
565 template <typename T, typename U>
566 constexpr CaseFirstIndexer& Type() {
567 return *this;
568 }
569
570 [[nodiscard]] constexpr std::optional<std::size_t> Extract() && noexcept {
571 return state_.Extract();
572 }
573
574 private:
575 SearchState<First, std::size_t> state_;
576 std::size_t index_ = 0;
577};
578
579} // namespace impl
580
581/// @ingroup userver_universal userver_containers
582///
583/// @brief Bidirectional unordered map for trivial types, including string
584/// literals; could be efficiently used as a unordered non-bidirectional map.
585///
586/// @snippet universal/src/utils/trivial_map_test.cpp sample string bimap
587///
588/// utils::TrivialBiMap and utils::TrivialSet are known to outperform
589/// std::unordered_map if:
590/// * there's 32 or less elements in map/set
591/// * or keys are string literals and all of them differ in length.
592///
593/// Implementation of string search is \b very efficient due to
594/// modern compilers optimize it to a switch by input string
595/// length and an integral comparison (rather than a std::memcmp call). In other
596/// words, it usually takes O(1) to find the match in the map.
597///
598/// The same story with integral or enum mappings - compiler optimizes them
599/// into a switch and it usually takes O(1) to find the match.
600///
601/// @snippet universal/src/utils/trivial_map_test.cpp sample bidir bimap
602///
603/// Empty map:
604///
605/// @snippet universal/src/utils/trivial_map_test.cpp sample empty bimap
606///
607/// For a single value Case statements see @ref utils::TrivialSet.
608template <typename BuilderFunc>
609class TrivialBiMap final {
610 using TypesPair =
611 std::invoke_result_t<const BuilderFunc&, impl::SwitchTypesDetector>;
612
613 public:
614 using First = typename TypesPair::first_type;
615 using Second = typename TypesPair::second_type;
616
617 struct value_type {
618 First first;
619 Second second;
620 };
621
622 /// Returns Second if T is convertible to First, otherwise returns Second
623 /// type.
624 template <class T>
625 using MappedTypeFor =
626 std::conditional_t<std::is_convertible_v<T, First>, Second, First>;
627
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");
638 }
639
640 constexpr std::optional<Second> TryFindByFirst(First value) const noexcept {
641 return func_(
642 [value]() { return impl::SwitchByFirst<First, Second>{value}; })
643 .Extract();
644 }
645
646 constexpr std::optional<First> TryFindBySecond(Second value) const noexcept {
647 return func_(
648 [value]() { return impl::SwitchBySecond<First, Second>{value}; })
649 .Extract();
650 }
651
652 template <class T>
653 constexpr std::optional<MappedTypeFor<T>> TryFind(T value) const noexcept {
654 static_assert(
655 !std::is_convertible_v<T, First> || !std::is_convertible_v<T, Second>,
656 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead");
657
658 if constexpr (std::is_convertible_v<T, First>) {
659 return TryFindByFirst(value);
660 } else {
661 return TryFindBySecond(value);
662 }
663 }
664
665 /// @brief Case insensitive search for value.
666 ///
667 /// For efficiency reasons, first parameter in Case() should be lower case
668 /// string literal.
670 std::string_view value) const noexcept {
671 return func_([value]() { return impl::SwitchByFirstICase<Second>{value}; })
672 .Extract();
673 }
674
675 /// @brief Case insensitive search for value.
676 ///
677 /// For efficiency reasons, second parameter in Case() should be lower case
678 /// string literal.
680 std::string_view value) const noexcept {
681 return func_([value]() { return impl::SwitchBySecondICase<First>{value}; })
682 .Extract();
683 }
684
685 /// @brief Case insensitive search for value that calls either
686 /// TryFindICaseBySecond or TryFindICaseByFirst.
688 std::string_view value) const noexcept {
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");
693
694 if constexpr (std::is_convertible_v<std::string_view, First>) {
695 return TryFindICaseByFirst(value);
696 } else {
697 return TryFindICaseBySecond(value);
698 }
699 }
700
701 /// Returns count of Case's in mapping
702 constexpr std::size_t size() const noexcept {
703 return func_([]() { return impl::CaseCounter{}; }).Extract();
704 }
705
706 /// Returns a string of comma separated quoted values of Case parameters.
707 ///
708 /// \b Example: "('a', '1'), ('b', '2'), ('c', '3')"
709 ///
710 /// Parameters of Case should be formattable.
711 std::string Describe() const {
712 return func_([]() { return impl::CaseDescriber{}; }).Extract();
713 }
714
715 /// Returns a string of comma separated quoted values of first Case
716 /// parameters.
717 ///
718 /// \b Example: "'a', 'b', 'c'"
719 ///
720 /// First parameters of Case should be formattable.
721 std::string DescribeFirst() const {
722 return func_([]() { return impl::CaseFirstDescriber{}; }).Extract();
723 }
724
725 /// Returns a string of comma separated quoted values of second Case
726 /// parameters.
727 ///
728 /// \b Example: "'1', '2', '3'"
729 ///
730 /// Second parameters of Case should be formattable.
731 std::string DescribeSecond() const {
732 return func_([]() { return impl::CaseSecondDescriber{}; }).Extract();
733 }
734
735 /// Returns a string of comma separated quoted values of Case
736 /// parameters that matches by type.
737 ///
738 /// \b Example: "'1', '2', '3'"
739 ///
740 /// Corresponding Case must be formattable
741 template <typename T>
742 std::string DescribeByType() const {
743 if constexpr (std::is_convertible_v<T, First>) {
744 return DescribeFirst();
745 } else {
746 return DescribeSecond();
747 }
748 }
749
750 constexpr value_type GetValuesByIndex(std::size_t index) const {
751 auto result = func_(
752 [index]() { return impl::CaseGetValuesByIndex<First, Second>{index}; });
753 return value_type{result.GetFirst(), result.GetSecond()};
754 }
755
756 class iterator {
757 public:
758 using iterator_category = std::input_iterator_tag;
759 using difference_type = std::ptrdiff_t;
760
761 explicit constexpr iterator(const TrivialBiMap& map, std::size_t position)
762 : map_{map}, position_{position} {}
763
764 constexpr bool operator==(iterator other) const {
765 return position_ == other.position_;
766 }
767
768 constexpr bool operator!=(iterator other) const {
769 return position_ != other.position_;
770 }
771
772 constexpr iterator operator++() {
773 ++position_;
774 return *this;
775 }
776
777 constexpr iterator operator++(int) {
778 iterator copy{*this};
779 ++position_;
780 return copy;
781 }
782
783 constexpr value_type operator*() const {
784 return map_.GetValuesByIndex(position_);
785 }
786
787 private:
788 const TrivialBiMap& map_;
789 std::size_t position_;
790 };
791
792 constexpr iterator begin() const { return iterator(*this, 0); }
793 constexpr iterator end() const { return iterator(*this, size()); }
794 constexpr iterator cbegin() const { return begin(); }
795 constexpr iterator cend() const { return end(); }
796
797 private:
798 const BuilderFunc func_;
799};
800
801template <typename BuilderFunc>
802TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
803
804/// @ingroup userver_universal userver_containers
805///
806/// @brief Unordered set for trivial types, including string literals.
807///
808/// For a two-value Case statements or efficiency notes
809/// see @ref utils::TrivialBimap.
810template <typename BuilderFunc>
811class TrivialSet final {
812 using TypesPair =
813 std::invoke_result_t<const BuilderFunc&, impl::SwitchTypesDetector>;
814
815 public:
816 using First = typename TypesPair::first_type;
817 using Second = typename TypesPair::second_type;
818
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");
826 }
827
828 constexpr bool Contains(First value) const noexcept {
829 return func_(
830 [value]() { return impl::SwitchByFirst<First, Second>{value}; })
831 .Extract();
832 }
833
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");
837
838 return func_([value]() { return impl::SwitchByFirstICase<void>{value}; })
839 .Extract();
840 }
841
842 constexpr std::size_t size() const noexcept {
843 return func_([]() { return impl::CaseCounter{}; }).Extract();
844 }
845
846 /// Returns a string of comma separated quoted values of Case parameters.
847 ///
848 /// \b Example: "'a', 'b', 'c'"
849 ///
850 /// Parameters of Case should be formattable.
851 std::string Describe() const {
852 return func_([]() { return impl::CaseFirstDescriber{}; }).Extract();
853 }
854
855 /// Returns index of the value in Case parameters or std::nullopt if no such
856 /// value.
857 constexpr std::optional<std::size_t> GetIndex(First value) const {
858 return func_([value]() { return impl::CaseFirstIndexer{value}; }).Extract();
859 }
860
861 private:
862 const BuilderFunc func_;
863};
864
865template <typename BuilderFunc>
866TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
867
868/// @brief Parses and returns whatever is specified by `map` from a
869/// `formats::*::Value`.
870/// @throws ExceptionType or `Value::Exception` by default, if `value` is not a
871/// string, or if `value` is not contained in `map`.
872/// @see @ref scripts/docs/en/userver/formats.md
873template <typename ExceptionType = void, typename Value, typename BuilderFunc>
874auto ParseFromValueString(const Value& value, TrivialBiMap<BuilderFunc> map) {
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()));
879 }
880 }
881
882 const auto string = value.template As<std::string>();
883 const auto parsed = map.TryFind(string);
884 if (parsed) return *parsed;
885
886 using Exception =
887 std::conditional_t<std::is_void_v<ExceptionType>,
888 typename Value::Exception, ExceptionType>;
889
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>()));
894}
895
896namespace impl {
897
898// @brief Converts `value` to `std::string_view` using `map`. If `value` is not
899// contained in `map`, then crashes the service in Debug builds, or throws
900// utils::InvariantError in Release builds.
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;
905
907 false,
908 fmt::format("Invalid value of enum {}: {}", compiler::GetTypeName<Enum>(),
909 static_cast<std::underlying_type_t<Enum>>(value)));
910}
911
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]);
918 ((selector2 =
919 selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])),
920 ...);
921 return selector2;
922}
923
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>{});
931 }
932};
933
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])), ...);
939 return selector2;
940}
941
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>{});
949 }
950};
951
952} // namespace impl
953
954/// @brief Zips two global `constexpr` arrays into an utils::TrivialBiMap.
955template <const auto& Keys, const auto& Values>
956constexpr auto MakeTrivialBiMap() {
957 static_assert(std::size(Keys) == std::size(Values));
958 static_assert(std::size(Keys) >= 1);
959 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
960}
961
962template <const auto& Values>
963constexpr auto MakeTrivialSet() {
964 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});
965}
966
967} // namespace utils
968
969USERVER_NAMESPACE_END