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