2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-19 04:42:09 +00:00

Adds protobuf example.

This commit is contained in:
Marcelo Zimbres
2023-03-10 14:49:16 +01:00
parent 52e62ba78c
commit 728b35cfe0
8 changed files with 186 additions and 88 deletions

View File

@@ -115,6 +115,8 @@ jobs:
uses: actions/checkout@v3
- name: Install CMake
run: sudo apt-get -y install cmake
- name: Install protobuf
run: sudo apt-get -y install protobuf-compiler
- name: Install compiler
run: sudo apt-get install -y ${{ matrix.install }}
- name: Install Redis

View File

@@ -42,6 +42,12 @@ write_basic_package_version_file(
)
find_package(Boost 1.80 REQUIRED)
# We test the protobuf example only on gcc.
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
find_package(Protobuf) # For the protobuf example.
endif()
include_directories(${Boost_INCLUDE_DIRS})
find_package(OpenSSL REQUIRED)
@@ -130,13 +136,26 @@ if (MSVC)
target_compile_definitions(cpp20_resolve_with_sentinel PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(cpp20_json_serialization examples/cpp20_json_serialization.cpp)
target_compile_features(cpp20_json_serialization PUBLIC cxx_std_20)
target_link_libraries(cpp20_json_serialization common)
add_test(cpp20_json_serialization cpp20_json_serialization)
add_executable(cpp20_json examples/cpp20_json.cpp)
target_compile_features(cpp20_json PUBLIC cxx_std_20)
target_link_libraries(cpp20_json common)
add_test(cpp20_json cpp20_json)
if (MSVC)
target_compile_options(cpp20_json_serialization PRIVATE /bigobj)
target_compile_definitions(cpp20_json_serialization PRIVATE _WIN32_WINNT=0x0601)
target_compile_options(cpp20_json PRIVATE /bigobj)
target_compile_definitions(cpp20_json PRIVATE _WIN32_WINNT=0x0601)
endif()
if (Protobuf_FOUND)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto)
add_executable(cpp20_protobuf examples/cpp20_protobuf.cpp ${PROTO_SRCS} ${PROTO_HDRS})
target_compile_features(cpp20_protobuf PUBLIC cxx_std_20)
target_link_libraries(cpp20_protobuf common ${Protobuf_LIBRARIES})
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
add_test(cpp20_protobuf cpp20_protobuf)
if (MSVC)
target_compile_options(cpp20_protobuf PRIVATE /bigobj)
target_compile_definitions(cpp20_protobuf PRIVATE _WIN32_WINNT=0x0601)
endif()
endif()
if (NOT MSVC)

View File

@@ -513,56 +513,24 @@ and other data structures in general.
<a name="serialization"></a>
## Serialization
Boost.Redis provides native support for serialization with Boost.Json.
To use it
* Include boost/redis/serialization.hpp
* Describe your class with Boost.Describe.
For example
```cpp
#include <boost/redis/json.hpp>
struct user {
std::string name;
std::string age;
std::string country;
};
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
```
After that you will be able to user your described `struct` both in
requests and responses, for example
```cpp
user foo{"Joao", "58", "Brazil"}
request req;
req.push("PING", foo);
response<user> resp;
co_await conn->async_exec(req, resp);
```
For other serialization formats it is necessary to define the
serialization functions `boost_redis_to_bulk` and `boost_redis_from_bulk` and
import them onto the global namespace so they become available over
ADL. They must have the following signature
Boost.Redis supports serialization of user defined types by means of
the following customization points
```cpp
// Serialize
// Serialize.
void boost_redis_to_bulk(std::string& to, mystruct const& obj);
// Deserialize
void boost_redis_from_bulk(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
```
Example cpp20_json_serialization.cpp shows how store json strings in Redis.
These functions are accessed over ADL and therefore they must be
imported in the global namespace by the user. In the
[Examples](#Examples) section the reader can find examples showing how
to serialize using json and [protobuf](https://protobuf.dev/).
<a name="examples"></a>
## Examples
The examples below show how to use the features discussed so far
@@ -571,7 +539,8 @@ The examples below show how to use the features discussed so far
* cpp20_intro.cpp: Does not use awaitable operators.
* cpp20_intro_tls.cpp: Communicates over TLS.
* cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions.
* cpp20_json_serialization.cpp: Shows how to serialize types using Boost.Json.
* cpp20_json.cpp: Shows how to serialize types using Boost.Json.
* cpp20_protobuf.cpp: Shows how to serialize types using protobuf.
* cpp20_resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels.
* cpp20_subscriber.cpp: Shows how to implement pubsub with reconnection re-subscription.
* cpp20_echo_server.cpp: A simple TCP echo server.

View File

@@ -10,11 +10,10 @@
#define BOOST_CONTAINER_NO_LIB
#include <boost/redis.hpp>
#include <boost/describe.hpp>
#include <boost/redis/json.hpp>
#include <set>
#include <string>
#include <iostream>
#include "common/common.hpp"
#include "json.hpp"
// Include this in no more than one .cpp file.
#include <boost/json/src.hpp>
@@ -27,18 +26,23 @@ using boost::redis::response;
using boost::redis::operation;
using boost::redis::ignore_t;
// Struct that will be stored in Redis using json serialization.
struct user {
std::string name;
std::string age;
std::string country;
friend
auto operator<(user const& a, user const& b)
{ return std::tie(a.name, a.age, a.country) < std::tie(b.name, b.age, b.country); }
};
// The type must be described for serialization to work.
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
// Boost.Redis customization points (examples/json.hpp)
void boost_redis_to_bulk(std::string& to, user const& u)
{ boost::redis::json::to_bulk(to, u); }
void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code& ec)
{ boost::redis::json::from_bulk(u, sv, ec); }
auto run(std::shared_ptr<connection> conn, std::string host, std::string port) -> net::awaitable<void>
{
co_await connect(conn, host, port);
@@ -51,44 +55,24 @@ net::awaitable<void> co_main(std::string host, std::string port)
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, host, port), net::detached);
// A set of users that will be automatically serialized to json.
std::set<user> users
{{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}};
// user object that will be stored in Redis in json format.
user const u{"Joao", "58", "Brazil"};
// To simplify we send the set and retrieve it in the same
// resquest.
// Stores and retrieves in the same request.
request req;
req.push("HELLO", 3);
req.push("SET", "json-key", u); // Stores in Redis.
req.push("GET", "json-key"); // Retrieves from Redis.
// Stores a std::set in a Redis set data structure.
req.push_range("SADD", "sadd-key", users);
response<ignore_t, ignore_t, user> resp;
// Sends a ping and retrieves it as a string to show what json
// serialization looks like.
req.push("PING", *users.begin());
// Sends another ping and retrieves it directly in a user type.
req.push("PING", *users.begin());
// Retrieves the set we have just stored.
req.push("SMEMBERS", "sadd-key");
response<ignore_t, ignore_t, std::string, user, std::set<user>> resp;
// Sends the request and receives the response.
co_await conn->async_exec(req, resp);
// Prints the first ping
auto const& pong1 = std::get<2>(resp).value();
std::cout << pong1 << "\n";
// Prints the second ping.
auto const& pong2 = std::get<3>(resp).value();
std::cout << pong2.name << " " << pong2.age << " " << pong2.country << "\n";
// Prints the set.
for (auto const& e: std::get<4>(resp).value())
std::cout << e.name << " " << e.age << " " << e.country << "\n";
std::cout
<< "Name: " << std::get<2>(resp).value().name << "\n"
<< "Age: " << std::get<2>(resp).value().age << "\n"
<< "Country: " << std::get<2>(resp).value().country << "\n";
conn->cancel(operation::run);
}

View File

@@ -0,0 +1,78 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/redis.hpp>
#include <iostream>
#include "common/common.hpp"
#include "protobuf.hpp"
// See the definition in person.proto. This header is automatically
// generated by CMakeLists.txt.
#include "person.pb.h"
namespace net = boost::asio;
namespace redis = boost::redis;
using boost::redis::request;
using boost::redis::response;
using boost::redis::operation;
using boost::redis::ignore_t;
// The protobuf type described in examples/person.proto
using tutorial::person;
// Boost.Redis customization points (examples/protobuf.hpp)
namespace tutorial
{
void boost_redis_to_bulk(std::string& to, person const& u)
{ boost::redis::protobuf::to_bulk(to, u); }
void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_code& ec)
{ boost::redis::protobuf::from_bulk(u, sv, ec); }
} // tutorial
using tutorial::boost_redis_to_bulk;
using tutorial::boost_redis_from_bulk;
auto run(std::shared_ptr<connection> conn, std::string host, std::string port) -> net::awaitable<void>
{
co_await connect(conn, host, port);
co_await conn->async_run();
}
net::awaitable<void> co_main(std::string host, std::string port)
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, host, port), net::detached);
person p;
p.set_name("Louis");
p.set_id(3);
p.set_email("No email yet.");
request req;
req.push("HELLO", 3);
req.push("SET", "protobuf-key", p);
req.push("GET", "protobuf-key");
response<ignore_t, ignore_t, person> resp;
// Sends the request and receives the response.
co_await conn->async_exec(req, resp);
std::cout
<< "Name: " << std::get<2>(resp).value().name() << "\n"
<< "Age: " << std::get<2>(resp).value().id() << "\n"
<< "Email: " << std::get<2>(resp).value().email() << "\n";
conn->cancel(operation::run);
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -14,13 +14,13 @@ namespace boost::redis::json
{
template <class T>
void boost_redis_to_bulk(std::string& to, T const& u)
void to_bulk(std::string& to, T const& u)
{
redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u)));
}
template <class T>
void boost_redis_from_bulk(T& u, std::string_view sv, system::error_code&)
void from_bulk(T& u, std::string_view sv, system::error_code&)
{
auto const jv = boost::json::parse(sv);
u = boost::json::value_to<T>(jv);
@@ -28,7 +28,4 @@ void boost_redis_from_bulk(T& u, std::string_view sv, system::error_code&)
} // boost::redis::json
using boost::redis::json::boost_redis_to_bulk;
using boost::redis::json::boost_redis_from_bulk;
#endif // BOOST_REDIS_JSON_HPP

9
examples/person.proto Normal file
View File

@@ -0,0 +1,9 @@
syntax = "proto2";
package tutorial;
message person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
}

40
examples/protobuf.hpp Normal file
View File

@@ -0,0 +1,40 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_PROTOBUF_HPP
#define BOOST_REDIS_PROTOBUF_HPP
#include <boost/redis/resp3/serialization.hpp>
#include <boost/system/errc.hpp>
namespace boost::redis::protobuf
{
// Below I am using a Boost.Redis to indicate a protobuf error, this
// is ok for an example, users however might want to define their own
// error codes.
template <class T>
void to_bulk(std::string& to, T const& u)
{
std::string tmp;
if (!u.SerializeToString(&tmp))
throw system::system_error(redis::error::invalid_data_type);
boost::redis::resp3::boost_redis_to_bulk(to, tmp);
}
template <class T>
void from_bulk(T& u, std::string_view sv, system::error_code& ec)
{
std::string const tmp {sv};
if (!u.ParseFromString(tmp))
ec = redis::error::invalid_data_type;
}
} // boost::redis::json
#endif // BOOST_REDIS_PROTOBUF_HPP