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