userver: userver/storages/redis/reply.hpp Source File
Loading...
Searching...
No Matches
reply.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/redis/reply.hpp
4/// @brief Redis reply payload (ReplyData) and per-command reply wrapper (Reply)
5
6#include <iterator>
7#include <string>
8#include <variant>
9#include <vector>
10
11#include <userver/logging/log_extra.hpp>
12#include <userver/utils/assert.hpp>
13
14#include <userver/storages/redis/base.hpp>
15#include <userver/storages/redis/impl/deadline_propagation_meta.hpp>
16#include <userver/storages/redis/reply_fwd.hpp>
17#include <userver/storages/redis/reply_status.hpp>
18
19struct redisReply;
20
21USERVER_NAMESPACE_BEGIN
22
23namespace storages::redis {
24
25/// @brief Typed view of a hiredis `redisReply` value
26class ReplyData final {
27public:
28 using Array = std::vector<ReplyData>;
29
30 enum class Type {
31 kNoReply,
32 kString,
33 kArray,
34 kInteger,
35 kNil,
36 kStatus,
37 kError,
38 };
39
40 class MovableKeyValues final {
41 public:
42 class View final {
43 public:
44 View(ReplyData& key_data, ReplyData& value_data) noexcept : key_data_(key_data), value_data_(value_data) {}
45
46 std::string& Key() noexcept { return key_data_.GetString(); }
47 std::string& Value() noexcept { return value_data_.GetString(); }
48
49 private:
50 ReplyData& key_data_;
51 ReplyData& value_data_;
52 };
53
54 class Iterator final {
55 public:
56 using iterator_category = std::input_iterator_tag;
57 using difference_type = std::ptrdiff_t;
58 using value_type = View;
59 using reference = View;
60 using pointer = void;
61
62 constexpr Iterator(Array& array, std::size_t index) noexcept : array_(&array), index_(index) {}
63
64 Iterator& operator++() noexcept {
65 ++index_;
66 return *this;
67 }
68
69 Iterator operator++(int) noexcept {
70 Iterator copy{*this};
71 ++*this;
72 return copy;
73 }
74
75 bool operator==(const Iterator& r) const noexcept { return index_ == r.index_; }
76 bool operator!=(const Iterator& r) const noexcept { return index_ != r.index_; }
77
78 View operator*() const noexcept { return {(*array_)[index_ * 2], (*array_)[index_ * 2 + 1]}; }
79
80 private:
81 Array* array_;
82 std::size_t index_;
83 };
84
85 explicit MovableKeyValues(Array& array) noexcept : array_(array) {}
86
87 Iterator begin() const noexcept { return {array_, 0}; }
88 Iterator end() const noexcept { return {array_, size()}; }
89
90 std::size_t size() const noexcept { return array_.size() / 2; }
91
92 private:
93 Array& array_;
94 };
95
96 MovableKeyValues GetMovableKeyValues();
97
98 ReplyData(const redisReply* reply);
99 ReplyData(Array&& array);
100 ReplyData(std::string s);
101 ReplyData(int value);
102 static ReplyData CreateError(std::string&& error_msg);
103 static ReplyData CreateStatus(std::string&& status_msg);
104 static ReplyData CreateNil();
105
106 explicit operator bool() const noexcept { return GetType() != Type::kNoReply; }
107
108 Type GetType() const noexcept {
109 UASSERT(!data_.valueless_by_exception());
110 return Type(data_.index());
111 }
112 std::string GetTypeString() const;
113
114 inline bool IsString() const noexcept { return GetType() == Type::kString; }
115 inline bool IsArray() const noexcept { return GetType() == Type::kArray; }
116 inline bool IsInt() const noexcept { return GetType() == Type::kInteger; }
117 inline bool IsNil() const noexcept { return GetType() == Type::kNil; }
118 inline bool IsStatus() const noexcept { return GetType() == Type::kStatus; }
119 inline bool IsError() const noexcept { return GetType() == Type::kError; }
120 bool IsUnusableInstanceError() const;
121 bool IsReadonlyError() const;
122 bool IsUnknownCommandError() const;
123
124 bool IsErrorMoved() const { return IsError() && !GetError().compare(0, 6, "MOVED "); }
125
126 bool IsErrorAsk() const { return IsError() && !GetError().compare(0, 4, "ASK "); }
127
128 bool IsErrorClusterdown() const { return IsError() && !GetError().compare(0, 12, "CLUSTERDOWN "); }
129
130 const std::string& GetString() const {
131 UASSERT(IsString());
132 return std::get_if<String>(&data_)->GetUnderlying();
133 }
134
135 std::string& GetString() {
136 UASSERT(IsString());
137 return std::get_if<String>(&data_)->GetUnderlying();
138 }
139
140 const Array& GetArray() const {
141 UASSERT(IsArray());
142 return *std::get_if<Array>(&data_);
143 }
144
145 Array& GetArray() {
146 UASSERT(IsArray());
147 return *std::get_if<Array>(&data_);
148 }
149
150 std::int64_t GetInt() const {
151 UASSERT(IsInt());
152 return *std::get_if<Integer>(&data_);
153 }
154
155 const std::string& GetStatus() const {
156 UASSERT(IsStatus());
157 return std::get_if<Status>(&data_)->GetUnderlying();
158 }
159
160 std::string& GetStatus() {
161 UASSERT(IsStatus());
162 return std::get_if<Status>(&data_)->GetUnderlying();
163 }
164
165 const std::string& GetError() const {
166 UASSERT(IsError());
167 return std::get_if<Error>(&data_)->GetUnderlying();
168 }
169
170 std::string& GetError() {
171 UASSERT(IsError());
172 return std::get_if<Error>(&data_)->GetUnderlying();
173 }
174
175 std::size_t GetSize() const noexcept;
176
177 std::string ToDebugString() const;
178 static std::string TypeToString(Type type);
179
180 void ExpectType(ReplyData::Type type, const std::string& request_description = {}) const;
181
182 void ExpectString(const std::string& request_description = {}) const;
183 void ExpectArray(const std::string& request_description = {}) const;
184 void ExpectInt(const std::string& request_description = {}) const;
185 void ExpectNil(const std::string& request_description = {}) const;
186 void ExpectStatus(const std::string& request_description = {}) const;
187 void ExpectStatusEqualTo(const std::string& expected_status_str, const std::string& request_description = {}) const;
188 void ExpectError(const std::string& request_description = {}) const;
189
190private:
191 ReplyData() = default;
192
193 [[noreturn]] void ThrowUnexpectedReplyType(ReplyData::Type expected, const std::string& request_description) const;
194
195 struct NoReply {};
196 using String = utils::StrongTypedef<class StringTag, std::string>;
197 using Integer = std::int64_t;
198 struct Nil {};
199 using Status = utils::StrongTypedef<class StatusTag, std::string>;
200 using Error = utils::StrongTypedef<class ErrorTag, std::string>;
201
202 // Matches `enum class Type`
203 std::variant<NoReply, String, Array, Integer, Nil, Status, Error> data_{};
204};
205
206/// @brief Redis command reply with metadata (server, status, timing)
207class Reply final {
208public:
209 Reply(std::string command, ReplyData&& reply_data, ReplyStatus reply_status = ReplyStatus::kOk);
210
211 std::string server;
212 ServerId server_id;
213 const std::string cmd;
214 ReplyData data;
215 const ReplyStatus status;
216 double time = 0.0;
217 logging::LogExtra log_extra;
218 /// @cond For internal use only
219 DeadlinePropagationMeta deadline_propagation_meta;
220 /// @endcond
221
222 operator bool() const { return IsOk(); }
223
224 std::string_view GetStatusString() const {
225 return (!IsOk() && data.IsError() ? data.GetError() : std::string_view{});
226 }
227
228 bool IsOk() const;
229 bool IsLoggableError() const;
230 bool IsUnusableInstanceError() const;
231 bool IsReadonlyError() const;
232 bool IsUnknownCommandError() const;
233 const logging::LogExtra& GetLogExtra() const;
234 void FillSpanTags(tracing::Span& span) const;
235
236 void ExpectIsOk(const std::string& request_description = {}) const;
237
238 const std::string& GetRequestDescription(const std::string& request_description) const;
239};
240
241} // namespace storages::redis
242
243USERVER_NAMESPACE_END