userver: userver/utils/default_dict.hpp Source File
Loading...
Searching...
No Matches
default_dict.hpp
1#pragma once
2
3/// @file userver/utils/default_dict.hpp
4/// @brief Dictionary with special value for missing keys
5
6#include <optional>
7#include <string>
8
9#include <userver/formats/json/value.hpp>
10#include <userver/formats/parse/common_containers.hpp>
11#include <userver/formats/serialize/common_containers.hpp>
12#include <userver/utils/impl/transparent_hash.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace utils {
17
18/// Name of the key with default value for utils::DefaultDict
19inline constexpr std::string_view kDefaultDictDefaultName = "__default__";
20
21namespace impl {
22
23[[noreturn]] void ThrowNoValueException(std::string_view dict_name, std::string_view key);
24
25} // namespace impl
26
27/// @ingroup userver_universal userver_containers
28///
29/// @brief Dictionary that for missing keys falls back to a default value
30/// stored by key utils::kDefaultDictDefaultName.
31///
32/// Usually used by dynamic configs for providing a fallback or default value
33/// along with the specific values. For example:
34/// @code{.json}
35/// {
36/// ".htm": "text/html",
37/// ".html": "text/html",
38/// "__default__": "text/plain"
39/// }
40/// @endcode
41template <typename ValueType>
42class DefaultDict final {
43public:
44 using DictType = utils::impl::TransparentMap<std::string, ValueType>;
45 using const_iterator = typename DictType::const_iterator;
46 using iterator = const_iterator;
47 using value_type = typename DictType::value_type;
48 using key_type = std::string;
49 using mapped_type = ValueType;
50 using init_list = std::initializer_list<std::pair<std::string_view, ValueType>>;
51
52 DefaultDict() = default;
53
54 DefaultDict(init_list contents) : dict_(contents.begin(), contents.end()) {}
55
56 DefaultDict(DictType dict) : dict_(std::move(dict)) {}
57
58 DefaultDict(std::string name, init_list contents)
59 : name_(std::move(name)), dict_(contents.begin(), contents.end()) {}
60
61 DefaultDict(std::string name, DictType dict) : name_(std::move(name)), dict_(std::move(dict)) {}
62
63 /// Returns true if *this has a utils::kDefaultDictDefaultName key,
64 /// otherwise returns false.
65 bool HasDefaultValue() const noexcept { return HasValue(kDefaultDictDefaultName); }
66
67 /// Returns true if *this has `key`, otherwise returns false.
68 bool HasValue(std::string_view key) const noexcept {
69 return utils::impl::FindTransparent(dict_, key) != dict_.end();
70 }
71
72 /// Returns value by utils::kDefaultDictDefaultName key,
73 /// throws a std::runtime_error exception.
74 const ValueType& GetDefaultValue() const {
75 const auto it = utils::impl::FindTransparent(dict_, kDefaultDictDefaultName);
76 if (it == dict_.end()) {
77 impl::ThrowNoValueException(name_, kDefaultDictDefaultName);
78 }
79 return it->second;
80 }
81
82 /// Returns value by `key` if it is in *this, otherwise returns value
83 /// by utils::kDefaultDictDefaultName if it is in *this,
84 /// otherwise throws a std::runtime_error exception.
85 const ValueType& operator[](std::string_view key) const {
86 auto it = utils::impl::FindTransparent(dict_, key);
87 if (it == dict_.end()) {
88 it = utils::impl::FindTransparent(dict_, kDefaultDictDefaultName);
89 if (it == dict_.end()) {
90 impl::ThrowNoValueException(name_, key);
91 }
92 }
93 return it->second;
94 }
95
96 /// Returns `key ? Get(*key) : GetDefaultValue()`
97 template <typename StringType>
98 const ValueType& operator[](const std::optional<StringType>& key) const {
99 return key ? Get(*key) : GetDefaultValue();
100 }
101
102 /// @overload operator[](std::string_view key)
103 const ValueType& Get(std::string_view key) const { return (*this)[key]; }
104
105 /// Returns `key ? Get(*key) : GetDefaultValue()`
106 template <typename StringType>
107 const ValueType& Get(const std::optional<StringType>& key) const {
108 return (*this)[key];
109 }
110
111 /// Returns value by `key` if it is in *this, otherwise returns value
112 /// by utils::kDefaultDictDefaultName if it is in *this,
113 /// otherwise returns an empty optional.
114 std::optional<ValueType> GetOptional(std::string_view key) const {
115 auto it = utils::impl::FindTransparent(dict_, key);
116 if (it == dict_.end()) {
117 it = utils::impl::FindTransparent(dict_, kDefaultDictDefaultName);
118 if (it == dict_.end()) return std::nullopt;
119 }
120
121 return it->second;
122 }
123
124 /// Sets the default value.
125 ///
126 /// The function is primarily there for testing purposes - DefaultDict is
127 /// normally obtained by parsing the config.
128 void SetDefault(ValueType value) { Set(kDefaultDictDefaultName, std::move(value)); }
129
130 /// Sets a mapping. key == utils::kDefaultDictDefaultName is allowed.
131 ///
132 /// The function is primarily there for testing purposes - DefaultDict is
133 /// normally obtained by parsing the config.
134 template <typename StringType>
135 void Set(StringType&& key, ValueType value) {
136 utils::impl::TransparentInsertOrAssign(dict_, std::forward<StringType>(key), std::move(value));
137 }
138
139 auto begin() const noexcept { return dict_.begin(); }
140
141 auto end() const noexcept { return dict_.end(); }
142
143 const std::string& GetName() const noexcept { return name_; }
144
145 bool operator==(const DefaultDict& r) const noexcept { return dict_ == r.dict_; }
146
147 bool operator!=(const DefaultDict& r) const noexcept { return !(*this == r); }
148
149private:
150 std::string name_;
151 DictType dict_;
152};
153
154template <typename Value, typename T>
155std::enable_if_t<formats::common::kIsFormatValue<Value>, DefaultDict<T>>
156Parse(const Value& value, formats::parse::To<DefaultDict<T>>) {
157 return DefaultDict<T>{value.GetPath(), value.template As<typename DefaultDict<T>::DictType>()};
158}
159
160} // namespace utils
161
162USERVER_NAMESPACE_END