Skip to content

Commit 728b35c

Browse files
committed
Adds protobuf example.
1 parent 52e62ba commit 728b35c

File tree

8 files changed

+186
-88
lines changed

8 files changed

+186
-88
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ jobs:
115115
uses: actions/checkout@v3
116116
- name: Install CMake
117117
run: sudo apt-get -y install cmake
118+
- name: Install protobuf
119+
run: sudo apt-get -y install protobuf-compiler
118120
- name: Install compiler
119121
run: sudo apt-get install -y ${{ matrix.install }}
120122
- name: Install Redis

CMakeLists.txt

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ write_basic_package_version_file(
4242
)
4343

4444
find_package(Boost 1.80 REQUIRED)
45+
46+
# We test the protobuf example only on gcc.
47+
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
48+
find_package(Protobuf) # For the protobuf example.
49+
endif()
50+
4551
include_directories(${Boost_INCLUDE_DIRS})
4652

4753
find_package(OpenSSL REQUIRED)
@@ -130,13 +136,26 @@ if (MSVC)
130136
target_compile_definitions(cpp20_resolve_with_sentinel PRIVATE _WIN32_WINNT=0x0601)
131137
endif()
132138

133-
add_executable(cpp20_json_serialization examples/cpp20_json_serialization.cpp)
134-
target_compile_features(cpp20_json_serialization PUBLIC cxx_std_20)
135-
target_link_libraries(cpp20_json_serialization common)
136-
add_test(cpp20_json_serialization cpp20_json_serialization)
139+
add_executable(cpp20_json examples/cpp20_json.cpp)
140+
target_compile_features(cpp20_json PUBLIC cxx_std_20)
141+
target_link_libraries(cpp20_json common)
142+
add_test(cpp20_json cpp20_json)
137143
if (MSVC)
138-
target_compile_options(cpp20_json_serialization PRIVATE /bigobj)
139-
target_compile_definitions(cpp20_json_serialization PRIVATE _WIN32_WINNT=0x0601)
144+
target_compile_options(cpp20_json PRIVATE /bigobj)
145+
target_compile_definitions(cpp20_json PRIVATE _WIN32_WINNT=0x0601)
146+
endif()
147+
148+
if (Protobuf_FOUND)
149+
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto)
150+
add_executable(cpp20_protobuf examples/cpp20_protobuf.cpp ${PROTO_SRCS} ${PROTO_HDRS})
151+
target_compile_features(cpp20_protobuf PUBLIC cxx_std_20)
152+
target_link_libraries(cpp20_protobuf common ${Protobuf_LIBRARIES})
153+
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
154+
add_test(cpp20_protobuf cpp20_protobuf)
155+
if (MSVC)
156+
target_compile_options(cpp20_protobuf PRIVATE /bigobj)
157+
target_compile_definitions(cpp20_protobuf PRIVATE _WIN32_WINNT=0x0601)
158+
endif()
140159
endif()
141160

142161
if (NOT MSVC)

README.md

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -513,56 +513,24 @@ and other data structures in general.
513513
<a name="serialization"></a>
514514
## Serialization
515515
516-
Boost.Redis provides native support for serialization with Boost.Json.
517-
To use it
518-
519-
* Include boost/redis/serialization.hpp
520-
* Describe your class with Boost.Describe.
521-
522-
For example
523-
524-
```cpp
525-
#include <boost/redis/json.hpp>
526-
527-
struct user {
528-
std::string name;
529-
std::string age;
530-
std::string country;
531-
};
532-
533-
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
534-
```
535-
536-
After that you will be able to user your described `struct` both in
537-
requests and responses, for example
538-
539-
```cpp
540-
user foo{"Joao", "58", "Brazil"}
541-
542-
request req;
543-
req.push("PING", foo);
544-
545-
response<user> resp;
546-
547-
co_await conn->async_exec(req, resp);
548-
```
549-
550-
For other serialization formats it is necessary to define the
551-
serialization functions `boost_redis_to_bulk` and `boost_redis_from_bulk` and
552-
import them onto the global namespace so they become available over
553-
ADL. They must have the following signature
516+
Boost.Redis supports serialization of user defined types by means of
517+
the following customization points
554518
555519
```cpp
556520
557-
// Serialize
521+
// Serialize.
558522
void boost_redis_to_bulk(std::string& to, mystruct const& obj);
559523
560524
// Deserialize
561525
void boost_redis_from_bulk(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
562526
```
563527

564-
Example cpp20_json_serialization.cpp shows how store json strings in Redis.
528+
These functions are accessed over ADL and therefore they must be
529+
imported in the global namespace by the user. In the
530+
[Examples](#Examples) section the reader can find examples showing how
531+
to serialize using json and [protobuf](https://protobuf.dev/).
565532

533+
<a name="examples"></a>
566534
## Examples
567535

568536
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
571539
* cpp20_intro.cpp: Does not use awaitable operators.
572540
* cpp20_intro_tls.cpp: Communicates over TLS.
573541
* cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions.
574-
* cpp20_json_serialization.cpp: Shows how to serialize types using Boost.Json.
542+
* cpp20_json.cpp: Shows how to serialize types using Boost.Json.
543+
* cpp20_protobuf.cpp: Shows how to serialize types using protobuf.
575544
* cpp20_resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels.
576545
* cpp20_subscriber.cpp: Shows how to implement pubsub with reconnection re-subscription.
577546
* cpp20_echo_server.cpp: A simple TCP echo server.

examples/cpp20_json_serialization.cpp renamed to examples/cpp20_json.cpp

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
#define BOOST_CONTAINER_NO_LIB
1111
#include <boost/redis.hpp>
1212
#include <boost/describe.hpp>
13-
#include <boost/redis/json.hpp>
14-
#include <set>
1513
#include <string>
1614
#include <iostream>
1715
#include "common/common.hpp"
16+
#include "json.hpp"
1817

1918
// Include this in no more than one .cpp file.
2019
#include <boost/json/src.hpp>
@@ -27,18 +26,23 @@ using boost::redis::response;
2726
using boost::redis::operation;
2827
using boost::redis::ignore_t;
2928

29+
// Struct that will be stored in Redis using json serialization.
3030
struct user {
3131
std::string name;
3232
std::string age;
3333
std::string country;
34-
35-
friend
36-
auto operator<(user const& a, user const& b)
37-
{ return std::tie(a.name, a.age, a.country) < std::tie(b.name, b.age, b.country); }
3834
};
3935

36+
// The type must be described for serialization to work.
4037
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
4138

39+
// Boost.Redis customization points (examples/json.hpp)
40+
void boost_redis_to_bulk(std::string& to, user const& u)
41+
{ boost::redis::json::to_bulk(to, u); }
42+
43+
void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code& ec)
44+
{ boost::redis::json::from_bulk(u, sv, ec); }
45+
4246
auto run(std::shared_ptr<connection> conn, std::string host, std::string port) -> net::awaitable<void>
4347
{
4448
co_await connect(conn, host, port);
@@ -51,44 +55,24 @@ net::awaitable<void> co_main(std::string host, std::string port)
5155
auto conn = std::make_shared<connection>(ex);
5256
net::co_spawn(ex, run(conn, host, port), net::detached);
5357

54-
// A set of users that will be automatically serialized to json.
55-
std::set<user> users
56-
{{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}};
58+
// user object that will be stored in Redis in json format.
59+
user const u{"Joao", "58", "Brazil"};
5760

58-
// To simplify we send the set and retrieve it in the same
59-
// resquest.
61+
// Stores and retrieves in the same request.
6062
request req;
6163
req.push("HELLO", 3);
64+
req.push("SET", "json-key", u); // Stores in Redis.
65+
req.push("GET", "json-key"); // Retrieves from Redis.
6266

63-
// Stores a std::set in a Redis set data structure.
64-
req.push_range("SADD", "sadd-key", users);
65-
66-
// Sends a ping and retrieves it as a string to show what json
67-
// serialization looks like.
68-
req.push("PING", *users.begin());
69-
70-
// Sends another ping and retrieves it directly in a user type.
71-
req.push("PING", *users.begin());
67+
response<ignore_t, ignore_t, user> resp;
7268

73-
// Retrieves the set we have just stored.
74-
req.push("SMEMBERS", "sadd-key");
75-
76-
response<ignore_t, ignore_t, std::string, user, std::set<user>> resp;
77-
78-
// Sends the request and receives the response.
7969
co_await conn->async_exec(req, resp);
8070

8171
// Prints the first ping
82-
auto const& pong1 = std::get<2>(resp).value();
83-
std::cout << pong1 << "\n";
84-
85-
// Prints the second ping.
86-
auto const& pong2 = std::get<3>(resp).value();
87-
std::cout << pong2.name << " " << pong2.age << " " << pong2.country << "\n";
88-
89-
// Prints the set.
90-
for (auto const& e: std::get<4>(resp).value())
91-
std::cout << e.name << " " << e.age << " " << e.country << "\n";
72+
std::cout
73+
<< "Name: " << std::get<2>(resp).value().name << "\n"
74+
<< "Age: " << std::get<2>(resp).value().age << "\n"
75+
<< "Country: " << std::get<2>(resp).value().country << "\n";
9276

9377
conn->cancel(operation::run);
9478
}

examples/cpp20_protobuf.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva ([email protected])
2+
*
3+
* Distributed under the Boost Software License, Version 1.0. (See
4+
* accompanying file LICENSE.txt)
5+
*/
6+
7+
#include <boost/asio.hpp>
8+
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
9+
#include <boost/redis.hpp>
10+
#include <iostream>
11+
#include "common/common.hpp"
12+
#include "protobuf.hpp"
13+
14+
// See the definition in person.proto. This header is automatically
15+
// generated by CMakeLists.txt.
16+
#include "person.pb.h"
17+
18+
namespace net = boost::asio;
19+
namespace redis = boost::redis;
20+
using boost::redis::request;
21+
using boost::redis::response;
22+
using boost::redis::operation;
23+
using boost::redis::ignore_t;
24+
25+
// The protobuf type described in examples/person.proto
26+
using tutorial::person;
27+
28+
// Boost.Redis customization points (examples/protobuf.hpp)
29+
namespace tutorial
30+
{
31+
32+
void boost_redis_to_bulk(std::string& to, person const& u)
33+
{ boost::redis::protobuf::to_bulk(to, u); }
34+
35+
void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_code& ec)
36+
{ boost::redis::protobuf::from_bulk(u, sv, ec); }
37+
38+
} // tutorial
39+
40+
using tutorial::boost_redis_to_bulk;
41+
using tutorial::boost_redis_from_bulk;
42+
43+
auto run(std::shared_ptr<connection> conn, std::string host, std::string port) -> net::awaitable<void>
44+
{
45+
co_await connect(conn, host, port);
46+
co_await conn->async_run();
47+
}
48+
49+
net::awaitable<void> co_main(std::string host, std::string port)
50+
{
51+
auto ex = co_await net::this_coro::executor;
52+
auto conn = std::make_shared<connection>(ex);
53+
net::co_spawn(ex, run(conn, host, port), net::detached);
54+
55+
person p;
56+
p.set_name("Louis");
57+
p.set_id(3);
58+
p.set_email("No email yet.");
59+
60+
request req;
61+
req.push("HELLO", 3);
62+
req.push("SET", "protobuf-key", p);
63+
req.push("GET", "protobuf-key");
64+
65+
response<ignore_t, ignore_t, person> resp;
66+
67+
// Sends the request and receives the response.
68+
co_await conn->async_exec(req, resp);
69+
70+
std::cout
71+
<< "Name: " << std::get<2>(resp).value().name() << "\n"
72+
<< "Age: " << std::get<2>(resp).value().id() << "\n"
73+
<< "Email: " << std::get<2>(resp).value().email() << "\n";
74+
75+
conn->cancel(operation::run);
76+
}
77+
78+
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

include/boost/redis/json.hpp renamed to examples/json.hpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,18 @@ namespace boost::redis::json
1414
{
1515

1616
template <class T>
17-
void boost_redis_to_bulk(std::string& to, T const& u)
17+
void to_bulk(std::string& to, T const& u)
1818
{
1919
redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u)));
2020
}
2121

2222
template <class T>
23-
void boost_redis_from_bulk(T& u, std::string_view sv, system::error_code&)
23+
void from_bulk(T& u, std::string_view sv, system::error_code&)
2424
{
2525
auto const jv = boost::json::parse(sv);
2626
u = boost::json::value_to<T>(jv);
2727
}
2828

2929
} // boost::redis::json
3030

31-
using boost::redis::json::boost_redis_to_bulk;
32-
using boost::redis::json::boost_redis_from_bulk;
33-
3431
#endif // BOOST_REDIS_JSON_HPP

examples/person.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
syntax = "proto2";
2+
3+
package tutorial;
4+
5+
message person {
6+
optional string name = 1;
7+
optional int32 id = 2;
8+
optional string email = 3;
9+
}

examples/protobuf.hpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva ([email protected])
2+
*
3+
* Distributed under the Boost Software License, Version 1.0. (See
4+
* accompanying file LICENSE.txt)
5+
*/
6+
7+
#ifndef BOOST_REDIS_PROTOBUF_HPP
8+
#define BOOST_REDIS_PROTOBUF_HPP
9+
10+
#include <boost/redis/resp3/serialization.hpp>
11+
#include <boost/system/errc.hpp>
12+
13+
namespace boost::redis::protobuf
14+
{
15+
16+
// Below I am using a Boost.Redis to indicate a protobuf error, this
17+
// is ok for an example, users however might want to define their own
18+
// error codes.
19+
20+
template <class T>
21+
void to_bulk(std::string& to, T const& u)
22+
{
23+
std::string tmp;
24+
if (!u.SerializeToString(&tmp))
25+
throw system::system_error(redis::error::invalid_data_type);
26+
27+
boost::redis::resp3::boost_redis_to_bulk(to, tmp);
28+
}
29+
30+
template <class T>
31+
void from_bulk(T& u, std::string_view sv, system::error_code& ec)
32+
{
33+
std::string const tmp {sv};
34+
if (!u.ParseFromString(tmp))
35+
ec = redis::error::invalid_data_type;
36+
}
37+
38+
} // boost::redis::json
39+
40+
#endif // BOOST_REDIS_PROTOBUF_HPP

0 commit comments

Comments
 (0)