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