Commits (2)
//------------------------------------------------------------------------------
// File: Formatting.hh
// Author: Georgios Bitzes - CERN
//------------------------------------------------------------------------------
/************************************************************************
* qclient - A simple redis C++ client with support for redirects *
* Copyright (C) 2019 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>.*
************************************************************************/
#ifndef QCLIENT_FORMATTING_HH
#define QCLIENT_FORMATTING_HH
#include <string>
#include <vector>
#include <map>
#include <sstream>
#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()
namespace qclient {
//------------------------------------------------------------------------------
// A class to help redis-serialize any given data structures
// NOTE: When in doubt, this class will serialize into string-type messages,
// not status-type messages. (status messages are not binary-safe)
//------------------------------------------------------------------------------
class Formatting {
public:
//----------------------------------------------------------------------------
// External API: Serialize onto a plain string.
//----------------------------------------------------------------------------
template<typename T>
static std::string serialize(T&& arg) {
std::ostringstream ss;
serializeInternal(ss, std::forward<T>(arg));
return ss.str();
}
//----------------------------------------------------------------------------
// Serialize a vector of arbitrary types
//----------------------------------------------------------------------------
template<typename... Args>
static std::string serializeVector(Args&&... args) {
const size_t n = sizeof...(args);
std::ostringstream ss;
ss << "*" << n << "\r\n";
serializeMulti(ss, std::forward<Args>(args)...);
return ss.str();
}
private:
//----------------------------------------------------------------------------
// Internal API: Serialize onto a given std::ostringstream.
// We need this to support efficient serialization of arrays.
//----------------------------------------------------------------------------
static void serializeInternal(std::ostringstream &ss, const std::string &str);
static void serializeInternal(std::ostringstream &ss, int64_t num);
//----------------------------------------------------------------------------
// Serialize any kind of vector
//----------------------------------------------------------------------------
template<typename T>
static void serializeInternal(std::ostringstream &ss, const std::vector<T> &vec) {
ss << "*" << vec.size() << "\r\n";
for(size_t i = 0; i < vec.size(); i++) {
serializeInternal(ss, vec[i]);
}
}
//----------------------------------------------------------------------------
// Serialize any kind of map
//----------------------------------------------------------------------------
template<typename K, typename V>
static void serializeInternal(std::ostringstream &ss, const std::map<K, V> &map) {
ss << "*" << 2*map.size() << "\r\n";
for(auto it = map.begin(); it != map.end(); it++) {
serializeInternal(ss, it->first);
serializeInternal(ss, it->second);
}
}
//----------------------------------------------------------------------------
// Recursively serialize each type in the vector
//----------------------------------------------------------------------------
template<class none = void>
static void serializeMulti(std::ostringstream &ss) {} // base case for recursion
template<typename Head, typename... Tail>
static void serializeMulti(std::ostringstream &ss, Head&& head, Tail&&... tail) {
serializeInternal(ss, std::forward<Head>(head));
serializeMulti<Tail...>(ss, std::forward<Tail>(tail)...);
}
};
}
#endif
......@@ -53,6 +53,10 @@ public:
static redisReplyPtr makeArr(const std::string &str1, const std::string &str2, int num);
static redisReplyPtr makeStatus(const std::string &msg);
// Convenience function to quickly parse a redis-encoded string into redisReplyPtr
static redisReplyPtr parseRedisEncodedString(const std::string &str);
private:
struct Deleter {
void operator()(redisReader *reader) { redisReaderFree(reader); }
......
......@@ -22,6 +22,7 @@
************************************************************************/
#include "qclient/Reply.hh"
#include "qclient/Formatting.hh"
#include <hiredis/hiredis.h>
#include <sstream>
#include <memory>
......@@ -112,4 +113,17 @@ std::string describeRedisReply(const redisReply *const redisReply, const std::st
std::string describeRedisReply(const redisReplyPtr &redisReply) {
return describeRedisReply(redisReply.get(), "");
}
//------------------------------------------------------------------------------
// Internal API: Serialize onto a given std::ostringstream.
// We need this to support efficient serialization of arrays.
//------------------------------------------------------------------------------
void Formatting::serializeInternal(std::ostringstream &ss, const std::string &str) {
ss << "$" << str.size() << "\r\n" << str << "\r\n";
}
void Formatting::serializeInternal(std::ostringstream &ss, int64_t num) {
ss << ":" << num << "\r\n";
}
}
......@@ -125,4 +125,14 @@ redisReplyPtr ResponseBuilder::makeStatus(const std::string &msg) {
return ans;
}
redisReplyPtr ResponseBuilder::parseRedisEncodedString(const std::string &str) {
ResponseBuilder builder;
builder.feed(str);
redisReplyPtr ans;
builder.pull(ans);
return ans;
}
}
......@@ -23,6 +23,7 @@
#include "gtest/gtest.h"
#include "qclient/QClient.hh"
#include "qclient/Formatting.hh"
using namespace qclient;
void setStr(redisReplyPtr reply, const std::string &str) {
......@@ -93,3 +94,54 @@ TEST(DescribeRedisReply, BasicSanity1) {
reply->type = 999;
ASSERT_EQ(describeRedisReply(reply), "!!! unknown reply type !!!");
}
TEST(Formatting, SerializeString) {
ASSERT_EQ(Formatting::serialize("asdf"), "$4\r\nasdf\r\n");
}
TEST(Formatting, SerializeVector) {
ASSERT_EQ(Formatting::serializeVector("asdf", "bbb", "aaaa"),
"*3\r\n"
"$4\r\nasdf\r\n"
"$3\r\nbbb\r\n"
"$4\r\naaaa\r\n"
);
ASSERT_EQ(Formatting::serializeVector("asdf", 1234),
"*2\r\n"
"$4\r\nasdf\r\n"
":1234\r\n"
);
}
TEST(Formatting, SerializeIntVector) {
std::vector<int64_t> vec = {4, 9, 8};
ASSERT_EQ(Formatting::serialize(vec),
"*3\r\n"
":4\r\n"
":9\r\n"
":8\r\n"
);
redisReplyPtr reply = ResponseBuilder::parseRedisEncodedString(Formatting::serialize(vec));
ASSERT_EQ(describeRedisReply(reply),
"1) (integer) 4\n"
"2) (integer) 9\n"
"3) (integer) 8\n"
);
}
TEST(Formatting, SerializeStringMap) {
std::map<std::string, std::string> map;
map["i like"] = "pickles";
map["asdf"] = "1234";
ASSERT_EQ(Formatting::serialize(map),
"*4\r\n"
"$4\r\nasdf\r\n"
"$4\r\n1234\r\n"
"$6\r\ni like\r\n"
"$7\r\npickles\r\n"
);
}