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