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