forked from Kistler-Group/sdbus-cpp
feat: introduce support for struct-as-dict serialization (#459)
This extends the functionality of SDBUSCPP_REGISTER_STRUCT macro. It now provides functionality for serializing a user-defined struct as an a{sv} dictionary, and for deserializing an a{sv} dictionary into a user-defined struct. The former one is achieved by decorating the struct with sdbus::as_dictionary(struct), the latter one is an automatic behavior -- when sdbus-c++ is asked to deserialize into a struct but the data in the message is of type a{sv}, then the dict-to-struct deserialization is performed automatically. There are some aspects of behavior in the serialization/deserialization functionality that can be customized by the client. Newly introduced SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION and SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION macros serve the purpose.
This commit is contained in:
committed by
GitHub
parent
8a117f8b42
commit
02ca7212d1
@ -20,11 +20,12 @@ Using sdbus-c++ library
|
||||
15. [Using D-Bus properties](#using-d-bus-properties)
|
||||
16. [Standard D-Bus interfaces](#standard-d-bus-interfaces)
|
||||
17. [Representing D-Bus Types in sdbus-c++](#representing-d-bus-types-in-sdbus-c)
|
||||
18. [Support for match rules](#support-for-match-rules)
|
||||
19. [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections)
|
||||
20. [Using sdbus-c++ in external event loops](#using-sdbus-c-in-external-event-loops)
|
||||
21. [Migrating to sdbus-c++ v2](#migrating-to-sdbus-c-v2)
|
||||
22. [Conclusion](#conclusion)
|
||||
18. [Adding user-defined types to the sdbus-c++ type system](#adding-user-defined-types-to-the-sdbus-c-type-system)
|
||||
19. [Support for match rules](#support-for-match-rules)
|
||||
20. [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections)
|
||||
21. [Using sdbus-c++ in external event loops](#using-sdbus-c-in-external-event-loops)
|
||||
22. [Migrating to sdbus-c++ v2](#migrating-to-sdbus-c-v2)
|
||||
23. [Conclusion](#conclusion)
|
||||
|
||||
Introduction
|
||||
------------
|
||||
@ -1577,7 +1578,7 @@ To see how C++ types are mapped to D-Bus types (including container types) in sd
|
||||
|
||||
For more information on basic D-Bus types, D-Bus container types, and D-Bus type system in general, make sure to consult the [D-Bus specification](https://dbus.freedesktop.org/doc/dbus-specification.html#type-system).
|
||||
|
||||
### Extending sdbus-c++ type system
|
||||
## Adding user-defined types to the sdbus-c++ type system
|
||||
|
||||
The above mapping between D-Bus and C++ types is what sdbus-c++ provides by default. However, the mapping can be extended. We can implement additional mapping between a D-Bus type and our custom type, i.e. teach sdbus-c++ to recognize and accept our own C++ types.
|
||||
|
||||
@ -1645,9 +1646,9 @@ Then we can simply use `std::list`s, serialize/deserialize them in a D-Bus messa
|
||||
|
||||
Similarly, say we have our own `lockfree_map` which we would like to use natively with sdbus-c++ as a C++ type for D-Bus dictionary -- we can copy or build on top of `std::map` specializations.
|
||||
|
||||
#### Using user-defined structs instead of `sdbus::Struct`
|
||||
### Teaching sdbus-c++ about user-defined structs
|
||||
|
||||
Many times, we have our own structs defined in our business logic code, and it would be very convenient to pass these structs directly to or from the sdbus-c++ IPC API where a D-Bus struct is expected, without having to translate them to or from `sdbus::Struct`.
|
||||
There is `SDBUSCPP_REGISTER_STRUCT` macro that we can use to teach sdbus-c++ about our structs and unlock some struct-related convenience functionality.
|
||||
|
||||
Say we have our custom type `my::Struct`:
|
||||
|
||||
@ -1662,7 +1663,7 @@ namespace my {
|
||||
} // namespace my
|
||||
```
|
||||
|
||||
We can teach sdbus-c++ about our struct type very easily with `SDBUSCPP_REGISTER_STRUCT` macro:
|
||||
This is how we introduce the struct to sdbus-c++:
|
||||
|
||||
```c++
|
||||
SDBUSCPP_REGISTER_STRUCT(my::Struct, i, s, l);
|
||||
@ -1670,37 +1671,90 @@ SDBUSCPP_REGISTER_STRUCT(my::Struct, i, s, l);
|
||||
|
||||
The macro must be placed in the global namespace. The first argument is the struct type name and the remaining arguments are names of struct members. Of course, struct members must be of types supported by sdbus-c++ (or of user-defined types that sdbus-c++ was taught to recognize). This also means that members can be other structs -- provided that sdbus-c++ was taught about them with `SDBUSCPP_REGISTER_STRUCT` prior to this one.
|
||||
|
||||
The macro effectively generates the `sdbus::Message` serialization and deserialization operators and the type traits (the `sdbus::signature_of` specialization) for `my::Struct`.
|
||||
`SDBUSCPP_REGISTER_STRUCT` enables us:
|
||||
|
||||
> **_Note_:** The macro supports **max 16 struct members**. If you need more, feel free to open an issue, or read the next paragraph and write the teaching boilerplate code yourself.
|
||||
* to use user-defined structs in place of (more generic, less expressive) `sdbus::Struct`s
|
||||
* to serialize a user-defined struct as a dictionary of strings to variants (`a{sv}` dictionary)
|
||||
* to deserialize the `a{sv}` dictionary into a user-defined struct.
|
||||
|
||||
Alternatively, we can to provide the message serialization/deserialization functions and the type traits manually. We can build on top of `sdbus::Struct`, so we don't have to copy and write a lot of boilerplate. Serialization/deserialization functions can be placed in the same namespace as our custom type, and will be found thanks to the ADR lookup. The `signature_of` specialization must always be in either `sdbus` namespace or in a global namespace:
|
||||
This is described in detail in the following sections.
|
||||
|
||||
> **_Note_:** The macro supports **max 16 struct members**. If you need more, feel free to open an issue, or implement the teaching code yourself :o)
|
||||
|
||||
> **_Another note_:** You may have noticed one of `my::Struct` members is `std::list`. Thanks to the custom support for `std::list` implemented higher above, it's now automatically accepted by sdbus-c++ as a D-Bus array representation.
|
||||
|
||||
### Using user-defined structs in place of `sdbus::Struct`
|
||||
|
||||
Many times, we have our own structs defined in our business logic code, and it would be very convenient to pass these structs directly to or from the sdbus-c++ IPC API where a D-Bus struct is expected, without having to translate them to or from `sdbus::Struct`.
|
||||
|
||||
For example, a D-Bus method `foo` that takes an argument of signature `(isad)` can simply be called with `my::Struct` instance instead of `sdbus::Struct<int, std::string, std::vector<dobule>>` instance:
|
||||
|
||||
```c++
|
||||
namespace my {
|
||||
sdbus::Message& operator<<(sdbus::Message& msg, const Struct& items)
|
||||
{
|
||||
// Re-use sdbus::Struct functionality for simplicity -- view of my::Struct through sdbus::Struct with reference types
|
||||
return msg << sdbus::Struct{std::forward_as_tuple(items.i, items.s, items.l)};
|
||||
}
|
||||
|
||||
sdbus::Message& operator>>(sdbus::Message& msg, Struct& items)
|
||||
{
|
||||
// Re-use sdbus::Struct functionality for simplicity -- view of my::Struct through sdbus::Struct with reference types
|
||||
sdbus::Struct s{std::forward_as_tuple(items.i, items.s, items.l)};
|
||||
return msg >> s;
|
||||
}
|
||||
} // namespace my
|
||||
|
||||
template <>
|
||||
struct sdbus::signature_of<my::Struct>
|
||||
: sdbus::signature_of<sdbus::Struct<int, std::string, std::list<double>>>
|
||||
{};
|
||||
my::Struct s{77, "hello"s, {3.14, 285.9}};
|
||||
proxy->callMethod("foo").onInterface(INTERFACE_NAME).withArguments(s);
|
||||
```
|
||||
|
||||
> **_Note_:** One of `my::Struct` members is `std::list`. Thanks to the above custom support for `std::list`, it's now automatically accepted by sdbus-c++ as a D-Bus array representation.
|
||||
For this purpose, the macro simply generates the `sdbus::Message` serialization and deserialization operators and the type traits (the `sdbus::signature_of` specialization) for `my::Struct`.
|
||||
|
||||
Live examples of extending sdbus-c++ types can be found in [Message unit tests](/tests/unittests/Message_test.cpp).
|
||||
Nesting structs is supported by default.
|
||||
|
||||
### Serializing a user-defined struct as the a{sv} dictionary
|
||||
|
||||
`SDBUSCPP_REGISTER_STRUCT` macro additionally teaches sdbus-c++ to serialize our structs as `a{sv}` dictionaries. This can be quite a handy feature.
|
||||
|
||||
For example, a D-Bus method `foo` that takes an argument of signature `a{sv}` can be passed `my::Struct` instance:
|
||||
|
||||
```c++
|
||||
my::Struct s{77, "hello"s, {3.14, 285.9}};
|
||||
proxy->callMethod("foo").onInterface(INTERFACE_NAME).withArguments(sdbus::as_dictionary(s));
|
||||
```
|
||||
|
||||
Decorating the struct instance with `sdbus::as_dictionary()` instructs sdbus-c++ to serialize the struct as an `a{sv}` dictionary, with struct field name being the key and struct field value being the value. Here is a C++ representation of the resulting dictionary:
|
||||
|
||||
```c++
|
||||
std::map<std::string, sdbus::Variant> dict{{"i"s, sdbus::Variant{77}}, {"s"s, sdbus::Variant{"hello"s}}, {"l"s, sdbus::Variant{std::list<double>{3.14, 285.9}}}};
|
||||
```
|
||||
|
||||
The default struct-as-dict serialization strategy is single-level (as opposed to nested). Single-level means that struct members that are structs themselves are serialized as D-Bus structs (the variant in the dict entry contains a struct value). Nested means that also struct members that are structs are all serialized as an `a{sv}` dictionary (the variant in the dict entry contains `a{sv}` dictionary). We can turn on nested serialization with the `SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION` macro:
|
||||
|
||||
```c++
|
||||
SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION(my::Struct);
|
||||
```
|
||||
|
||||
If nested strategy is also enabled for the nested struct type, then the same behavior applies for that struct, recursively. (It goes without saying that member struct type needs to be registered through `SDBUSCPP_REGISTER_STRUCT` macro, too.)
|
||||
|
||||
The macro must be placed before the `SDBUSCPP_REGISTER_STRUCT(my::Struct);` macro.
|
||||
|
||||
### Deserializing the a{sv} dictionary into a user-defined struct
|
||||
|
||||
Another handy feature enabled by the `SDBUSCPP_REGISTER_STRUCT` macro is an automatic deserialization of `a{sv}` dictionaries to user-defined structs.
|
||||
|
||||
For example, a D-Bus signal `bar` that carries data of signature `a{sv}` can be deserialized not only into a C++ dictionary type, but also directly into a user-defined struct, leading to shorter and more natural code:
|
||||
|
||||
```c++
|
||||
proxy->uponSignal("bar").onInterface(INTERFACE_NAME).call([](const my::Struct& s){ std::cout << "Got signal with s.i == " << s.i << "\n"; });
|
||||
```
|
||||
|
||||
How easy and convenient, right?
|
||||
|
||||
The requirements:
|
||||
|
||||
* All keys in the dictionary must exactly match the names of fields in the struct. Ordering of struct fields vs. items in the dictionary is irrelevant; the field in the struct is found by its name given by the dict key. If the corresponding struct field is not found, `sdbus::Error` exception is thrown.
|
||||
* The type of value in the dictionary item and the corresponding struct field must also exactly match. Otherwise, `sdbus::Error` exception is thrown.
|
||||
|
||||
The first bullet point is a so-called strict dict-to-struct deserialization strategy. There is also a relaxed one -- meaning that a dict entry key that does not have a matching struct member counterpart is not an error and is silently skipped. We can turn on relaxed deserialization with the `SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION` macro:
|
||||
|
||||
```c++
|
||||
SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION(my::Struct);
|
||||
```
|
||||
|
||||
The macro must be placed before the `SDBUSCPP_REGISTER_STRUCT(my::Struct);` macro.
|
||||
|
||||
Real examples of extending sdbus-c++ types, including the use of all above-mentioned struct-related macros, can be found in [Message unit tests](/tests/unittests/Message_test.cpp) and also in test case [SdbusTestObject.CanSendAndReceiveDictionariesAsCustomStructsImplicitly](/tests/integrationtests/DBusMethodsTests.cpp#L266) in integration tests.
|
||||
|
||||
Happy `struct`ing!
|
||||
|
||||
> **_Wait!_:** You might say. What about XML IDL and generated C++ bindings? Well, there is no user-defined struct support in there. Yet. An extended XML syntax would be required. But we may implement something like that in the future (and you can help us).
|
||||
|
||||
Support for match rules
|
||||
-----------------------
|
||||
|
@ -114,6 +114,8 @@ namespace sdbus {
|
||||
#endif
|
||||
template <typename _Enum, typename = std::enable_if_t<std::is_enum_v<_Enum>>>
|
||||
Message& operator<<(const _Enum& item);
|
||||
template <typename _Key, typename _Value>
|
||||
Message& operator<<(const DictEntry<_Key, _Value>& value);
|
||||
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
|
||||
Message& operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items);
|
||||
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
|
||||
@ -150,6 +152,8 @@ namespace sdbus {
|
||||
#endif
|
||||
template <typename _Enum, typename = std::enable_if_t<std::is_enum_v<_Enum>>>
|
||||
Message& operator>>(_Enum& item);
|
||||
template <typename _Key, typename _Value>
|
||||
Message& operator>>(DictEntry<_Key, _Value>& value);
|
||||
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
|
||||
Message& operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items);
|
||||
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
|
||||
@ -196,6 +200,13 @@ namespace sdbus {
|
||||
Message& appendArray(char type, const void *ptr, size_t size);
|
||||
Message& readArray(char type, const void **ptr, size_t *size);
|
||||
|
||||
template <typename _Key, typename _Value, typename _Callback>
|
||||
Message& serializeDictionary(const _Callback& callback);
|
||||
template <typename _Key, typename _Value>
|
||||
Message& serializeDictionary(const std::initializer_list<DictEntry<_Key, _Value>>& dictEntries);
|
||||
template <typename _Key, typename _Value, typename _Callback>
|
||||
Message& deserializeDictionary(const _Callback& callback);
|
||||
|
||||
explicit operator bool() const;
|
||||
void clearFlags();
|
||||
|
||||
@ -238,11 +249,6 @@ namespace sdbus {
|
||||
template <typename _Element, typename _Allocator>
|
||||
void deserializeArraySlow(std::vector<_Element, _Allocator>& items);
|
||||
|
||||
template <typename _Dictionary>
|
||||
void serializeDictionary(const _Dictionary& items);
|
||||
template <typename _Dictionary>
|
||||
void deserializeDictionary(_Dictionary& items);
|
||||
|
||||
protected:
|
||||
Message() = default;
|
||||
explicit Message(internal::IConnection* connection) noexcept;
|
||||
@ -402,10 +408,21 @@ namespace sdbus {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename _Key, typename _Value>
|
||||
inline Message& Message::operator<<(const DictEntry<_Key, _Value>& value)
|
||||
{
|
||||
openDictEntry<_Key, _Value>();
|
||||
*this << value.first;
|
||||
*this << value.second;
|
||||
closeDictEntry();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
|
||||
inline Message& Message::operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items)
|
||||
{
|
||||
serializeDictionary(items);
|
||||
serializeDictionary<_Key, _Value>([&items](Message& msg){ for (const auto& item : items) msg << item; });
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -413,28 +430,27 @@ namespace sdbus {
|
||||
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
|
||||
inline Message& Message::operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
|
||||
{
|
||||
serializeDictionary(items);
|
||||
serializeDictionary<_Key, _Value>([&items](Message& msg){ for (const auto& item : items) msg << item; });
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Dictionary>
|
||||
inline void Message::serializeDictionary(const _Dictionary& items)
|
||||
template <typename _Key, typename _Value>
|
||||
inline Message& Message::serializeDictionary(const std::initializer_list<DictEntry<_Key, _Value>>& items)
|
||||
{
|
||||
using KeyType = typename _Dictionary::key_type;
|
||||
using MappedType = typename _Dictionary::mapped_type;
|
||||
serializeDictionary<_Key, _Value>([&](Message& msg){ for (const auto& item : items) msg << item; });
|
||||
|
||||
openContainer<DictEntry<KeyType, MappedType>>();
|
||||
|
||||
for (const auto& item : items)
|
||||
{
|
||||
openDictEntry<KeyType, MappedType>();
|
||||
*this << item.first;
|
||||
*this << item.second;
|
||||
closeDictEntry();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Key, typename _Value, typename _Callback>
|
||||
inline Message& Message::serializeDictionary(const _Callback& callback)
|
||||
{
|
||||
openContainer<DictEntry<_Key, _Value>>();
|
||||
callback(*this);
|
||||
closeContainer();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
namespace detail
|
||||
@ -619,10 +635,21 @@ namespace sdbus {
|
||||
exitContainer();
|
||||
}
|
||||
|
||||
template <typename _Key, typename _Value>
|
||||
inline Message& Message::operator>>(DictEntry<_Key, _Value>& value)
|
||||
{
|
||||
if (!enterDictEntry<_Key, _Value>())
|
||||
return *this;
|
||||
*this >> value.first >> value.second;
|
||||
exitDictEntry();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
|
||||
inline Message& Message::operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items)
|
||||
{
|
||||
deserializeDictionary(items);
|
||||
deserializeDictionary<_Key, _Value>([&items](auto dictEntry){ items.insert(std::move(dictEntry)); });
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -630,37 +657,30 @@ namespace sdbus {
|
||||
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
|
||||
inline Message& Message::operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
|
||||
{
|
||||
deserializeDictionary(items);
|
||||
deserializeDictionary<_Key, _Value>([&items](auto dictEntry){ items.insert(std::move(dictEntry)); });
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Dictionary>
|
||||
inline void Message::deserializeDictionary(_Dictionary& items)
|
||||
template <typename _Key, typename _Value, typename _Callback>
|
||||
inline Message& Message::deserializeDictionary(const _Callback& callback)
|
||||
{
|
||||
using KeyType = typename _Dictionary::key_type;
|
||||
using MappedType = typename _Dictionary::mapped_type;
|
||||
|
||||
if (!enterContainer<DictEntry<KeyType, MappedType>>())
|
||||
return;
|
||||
if (!enterContainer<DictEntry<_Key, _Value>>())
|
||||
return *this;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!enterDictEntry<KeyType, MappedType>())
|
||||
DictEntry<_Key, _Value> dictEntry;
|
||||
*this >> dictEntry;
|
||||
if (!*this)
|
||||
break;
|
||||
|
||||
KeyType key;
|
||||
MappedType value;
|
||||
*this >> key >> value;
|
||||
|
||||
items.emplace(std::move(key), std::move(value));
|
||||
|
||||
exitDictEntry();
|
||||
callback(std::move(dictEntry));
|
||||
}
|
||||
|
||||
clearFlags();
|
||||
|
||||
exitContainer();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
namespace detail
|
||||
|
@ -273,6 +273,7 @@ namespace sdbus {
|
||||
{
|
||||
static constexpr std::array contents = (signature_of_v<_ValueTypes> + ...);
|
||||
static constexpr std::array value = std::array{'('} + contents + std::array{')'};
|
||||
static constexpr char type_value{'r'}; /* Not actually used in signatures on D-Bus, see specs */
|
||||
static constexpr bool is_valid = true;
|
||||
static constexpr bool is_trivial_dbus_type = false;
|
||||
};
|
||||
@ -317,6 +318,7 @@ namespace sdbus {
|
||||
struct signature_of<DictEntry<_T1, _T2>>
|
||||
{
|
||||
static constexpr std::array value = std::array{'{'} + signature_of_v<std::tuple<_T1, _T2>> + std::array{'}'};
|
||||
static constexpr char type_value{'e'}; /* Not actually used in signatures on D-Bus, see specs */
|
||||
static constexpr bool is_valid = true;
|
||||
static constexpr bool is_trivial_dbus_type = false;
|
||||
};
|
||||
@ -349,9 +351,7 @@ namespace sdbus {
|
||||
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
|
||||
struct signature_of<std::map<_Key, _Value, _Compare, _Allocator>>
|
||||
{
|
||||
static constexpr std::array contents = signature_of_v<std::tuple<_Key, _Value>>;
|
||||
static constexpr std::array dict_entry = std::array{'{'} + contents + std::array{'}'};
|
||||
static constexpr std::array value = std::array{'a'} + dict_entry;
|
||||
static constexpr std::array value = std::array{'a'} + signature_of_v<DictEntry<_Key, _Value>>;
|
||||
static constexpr bool is_valid = true;
|
||||
static constexpr bool is_trivial_dbus_type = false;
|
||||
};
|
||||
@ -575,6 +575,36 @@ namespace sdbus {
|
||||
constexpr bool is_one_of_variants_types<std::variant<_VariantTypes...>, _QueriedType>
|
||||
= (std::is_same_v<_QueriedType, _VariantTypes> || ...);
|
||||
|
||||
// Wrapper (tag) denoting we want to serialize user-defined struct
|
||||
// into a D-Bus message as a dictionary of strings to variants.
|
||||
template <typename _Struct>
|
||||
struct as_dictionary
|
||||
{
|
||||
explicit as_dictionary(const _Struct& s) : m_struct(s) {}
|
||||
const _Struct& m_struct;
|
||||
};
|
||||
|
||||
template <typename _Type>
|
||||
const _Type& as_dictionary_if_struct(const _Type& object)
|
||||
{
|
||||
return object; // identity in case _Type is not struct (user-defined structs shall provide an overload)
|
||||
}
|
||||
|
||||
// By default, the dict-as-struct deserialization strategy is strict.
|
||||
// Strict means that every key of the deserialized dictionary must have its counterpart member in the struct, otherwise an exception is thrown.
|
||||
// Relaxed means that a key that does not have a matching struct member is silently ignored.
|
||||
// The behavior can be overridden for user-defined struct by specializing this variable template.
|
||||
template <typename _Struct>
|
||||
constexpr auto strict_dict_as_struct_deserialization_v = true;
|
||||
|
||||
// By default, the struct-as-dict serialization strategy is single-level only (as opposed to nested).
|
||||
// Single-level means that only the specific struct is serialized as a dictionary, serializing members that are structs always as structs.
|
||||
// Nested means that the struct *and* its members that are structs are all serialized as a dictionary. If nested strategy is also
|
||||
// defined for the nested struct, then the same behavior applies for that struct, recursively.
|
||||
// The behavior can be overridden for user-defined struct by specializing this variable template.
|
||||
template <typename _Struct>
|
||||
constexpr auto nested_struct_as_dict_serialization_v = false;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <class _Function, class _Tuple, typename... _Args, std::size_t... _I>
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <sdbus-c++/TypeTraits.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@ -66,6 +67,15 @@ namespace sdbus {
|
||||
msg_.seal();
|
||||
}
|
||||
|
||||
template <typename _Struct>
|
||||
explicit Variant(const as_dictionary<_Struct>& value) : Variant()
|
||||
{
|
||||
msg_.openVariant<std::map<std::string, Variant>>();
|
||||
msg_ << as_dictionary(value.m_struct);
|
||||
msg_.closeVariant();
|
||||
msg_.seal();
|
||||
}
|
||||
|
||||
template <typename... _Elements>
|
||||
Variant(const std::variant<_Elements...>& value)
|
||||
: Variant()
|
||||
@ -77,10 +87,10 @@ namespace sdbus {
|
||||
template <typename _ValueType>
|
||||
_ValueType get() const
|
||||
{
|
||||
_ValueType val;
|
||||
msg_.rewind(false);
|
||||
|
||||
msg_.enterVariant<_ValueType>();
|
||||
_ValueType val;
|
||||
msg_ >> val;
|
||||
msg_.exitVariant();
|
||||
return val;
|
||||
@ -405,6 +415,9 @@ struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
|
||||
* clients to use their struct conveniently instead of the (too
|
||||
* generic and less expressive) `sdbus::Struct<...>` in sdbus-c++ API.
|
||||
*
|
||||
* It also enables to serialize a user-defined struct as an a{sv} dictionary,
|
||||
* and to deserialize an a{sv} dictionary into the user-defined struct.
|
||||
*
|
||||
* The first argument is the struct type name and the remaining arguments
|
||||
* are names of struct members. Members must be of types supported by
|
||||
* sdbus-c++ (or of user-defined types that sdbus-c++ was taught to support).
|
||||
@ -426,31 +439,96 @@ struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
|
||||
*
|
||||
* SDBUSCPP_REGISTER_STRUCT(foo::ABC, number, name, data);
|
||||
*
|
||||
* The macro effectively generates the `sdbus::Message` serialization
|
||||
* and deserialization operators and the `sdbus::signature_of`
|
||||
* specialization for `foo::ABC`.
|
||||
*
|
||||
* Up to 16 struct members are supported by the macro.
|
||||
*
|
||||
***********************************************/
|
||||
#define SDBUSCPP_REGISTER_STRUCT(STRUCT, ...) \
|
||||
namespace sdbus { \
|
||||
static_assert(SDBUSCPP_PP_NARG(__VA_ARGS__) <= 16, \
|
||||
"Not more than 16 struct members are supported, please open an issue if you need more"); \
|
||||
inline sdbus::Message& operator<<(sdbus::Message& msg, const STRUCT& items) \
|
||||
{ \
|
||||
return msg << sdbus::Struct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \
|
||||
} \
|
||||
inline sdbus::Message& operator>>(sdbus::Message& msg, STRUCT& items) \
|
||||
{ \
|
||||
sdbus::Struct s{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \
|
||||
return msg >> s; \
|
||||
} \
|
||||
template <> \
|
||||
struct signature_of<STRUCT> \
|
||||
: signature_of<sdbus::Struct<SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, __VA_ARGS__)>> \
|
||||
{}; \
|
||||
} \
|
||||
#define SDBUSCPP_REGISTER_STRUCT(STRUCT, ...) \
|
||||
namespace sdbus { \
|
||||
static_assert(SDBUSCPP_PP_NARG(__VA_ARGS__) <= 16, \
|
||||
"Not more than 16 struct members are supported, please open an issue if you need more"); \
|
||||
\
|
||||
template <> \
|
||||
struct signature_of<STRUCT> \
|
||||
: signature_of<sdbus::Struct<SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, __VA_ARGS__)>> \
|
||||
{}; \
|
||||
\
|
||||
inline auto as_dictionary_if_struct(const STRUCT& object) \
|
||||
{ \
|
||||
return as_dictionary<STRUCT>(object); \
|
||||
} \
|
||||
\
|
||||
inline sdbus::Message& operator<<(sdbus::Message& msg, const STRUCT& items) \
|
||||
{ \
|
||||
return msg << sdbus::Struct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \
|
||||
} \
|
||||
\
|
||||
inline Message& operator<<(Message& msg, const as_dictionary<STRUCT>& s) \
|
||||
{ \
|
||||
if constexpr (!nested_struct_as_dict_serialization_v<STRUCT>) \
|
||||
return msg.serializeDictionary<std::string, Variant>({SDBUSCPP_STRUCT_MEMBERS_AS_DICT_ENTRIES(s.m_struct, __VA_ARGS__)}); \
|
||||
else \
|
||||
return msg.serializeDictionary<std::string, Variant>({SDBUSCPP_STRUCT_MEMBERS_AS_NESTED_DICT_ENTRIES(s.m_struct, __VA_ARGS__)}); \
|
||||
} \
|
||||
\
|
||||
inline Message& operator>>(Message& msg, STRUCT& s) \
|
||||
{ \
|
||||
/* First, try to deserialize as a struct */ \
|
||||
if (msg.peekType().first == signature_of<STRUCT>::type_value) \
|
||||
{ \
|
||||
Struct sdbusStruct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(s, __VA_ARGS__))}; \
|
||||
return msg >> sdbusStruct; \
|
||||
} \
|
||||
\
|
||||
/* Otherwise try to deserialize as a dictionary of strings to variants */ \
|
||||
\
|
||||
return msg.deserializeDictionary<std::string, Variant>([&s](const auto& dictEntry) \
|
||||
{ \
|
||||
const std::string& key = dictEntry.first; /* Intentionally not using structured bindings */ \
|
||||
const Variant& value = dictEntry.second; \
|
||||
\
|
||||
using namespace std::string_literals; \
|
||||
/* This also handles members which are structs serialized as dict of strings to variants, recursively */ \
|
||||
SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBERS(s, __VA_ARGS__) \
|
||||
SDBUS_THROW_ERROR_IF( strict_dict_as_struct_deserialization_v<STRUCT> \
|
||||
, ("Failed to deserialize struct from a dictionary: could not find field '"s += key) += "' in struct 'my::Struct'" \
|
||||
, EINVAL ); \
|
||||
}); \
|
||||
} \
|
||||
} \
|
||||
/**/
|
||||
|
||||
/********************************************//**
|
||||
* @name SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION
|
||||
*
|
||||
* Enables relaxed deserialization of an a{sv} dictionary into a user-defined struct STRUCT.
|
||||
*
|
||||
* The default (strict) deserialization mode is that if there are entries in the dictionary
|
||||
* which do not have a corresponding field in the struct, an exception is thrown.
|
||||
* In the relaxed mode, such entries are silently skipped.
|
||||
*
|
||||
* The macro can only be used in combination with SDBUSCPP_REGISTER_STRUCT macro,
|
||||
* and must be placed before SDBUSCPP_REGISTER_STRUCT macro.
|
||||
***********************************************/
|
||||
#define SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION(STRUCT) \
|
||||
template <> \
|
||||
constexpr auto sdbus::strict_dict_as_struct_deserialization_v<STRUCT> = false; \
|
||||
/**/
|
||||
|
||||
/********************************************//**
|
||||
* @name SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION
|
||||
*
|
||||
* Enables nested serialization of user-defined struct STRUCT as an a{sv} dictionary.
|
||||
*
|
||||
* By default, STRUCT fields which are structs themselves are serialized as D-Bus structs.
|
||||
* This macro tells sdbus-c++ to also serialize nested structs, in a recursive fashion,
|
||||
* as a{sv} dictionaries.
|
||||
*
|
||||
* The macro can only be used in combination with SDBUSCPP_REGISTER_STRUCT macro,
|
||||
* and must be placed before SDBUSCPP_REGISTER_STRUCT macro.
|
||||
***********************************************/
|
||||
#define SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION(STRUCT) \
|
||||
template <> \
|
||||
constexpr auto sdbus::nested_struct_as_dict_serialization_v<STRUCT> = true \
|
||||
/**/
|
||||
|
||||
/*!
|
||||
@ -458,49 +536,54 @@ struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
|
||||
*
|
||||
* Internal helper preprocessor facilities
|
||||
*/
|
||||
#define SDBUSCPP_STRUCT_MEMBERS(STRUCT, ...) \
|
||||
SDBUSCPP_PP_CAT(SDBUSCPP_STRUCT_MEMBERS_, SDBUSCPP_PP_NARG(__VA_ARGS__))(STRUCT, __VA_ARGS__) \
|
||||
#define SDBUSCPP_STRUCT_MEMBERS(STRUCT, ...) \
|
||||
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
|
||||
/**/
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_1(S, M1) S.M1
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_2(S, M1, M2) S.M1, S.M2
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_3(S, M1, M2, M3) S.M1, S.M2, S.M3
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_4(S, M1, M2, M3, M4) S.M1, S.M2, S.M3, S.M4
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_5(S, M1, M2, M3, M4, M5) S.M1, S.M2, S.M3, S.M4, S.M5
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_6(S, M1, M2, M3, M4, M5, M6) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_7(S, M1, M2, M3, M4, M5, M6, M7) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_8(S, M1, M2, M3, M4, M5, M6, M7, M8) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_9(S, M1, M2, M3, M4, M5, M6, M7, M8, M9) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_10(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_11(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_12(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_13(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_14(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_15(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14, S.M15
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_16(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14, S.M15, S.M16
|
||||
#define SDBUSCPP_STRUCT_MEMBER(STRUCT, MEMBER) STRUCT.MEMBER
|
||||
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, ...) \
|
||||
SDBUSCPP_PP_CAT(SDBUSCPP_STRUCT_MEMBER_TYPES_, SDBUSCPP_PP_NARG(__VA_ARGS__))(STRUCT, __VA_ARGS__) \
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, ...) \
|
||||
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_TYPE, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
|
||||
/**/
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_1(S, M1) decltype(S::M1)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_2(S, M1, M2) decltype(S::M1), decltype(S::M2)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_3(S, M1, M2, M3) decltype(S::M1), decltype(S::M2), decltype(S::M3)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_4(S, M1, M2, M3, M4) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_5(S, M1, M2, M3, M4, M5) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_6(S, M1, M2, M3, M4, M5, M6) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_7(S, M1, M2, M3, M4, M5, M6, M7) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_8(S, M1, M2, M3, M4, M5, M6, M7, M8) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_9(S, M1, M2, M3, M4, M5, M6, M7, M8, M9) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_10(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_11(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_12(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_13(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_14(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_15(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14), decltype(S::M15)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPES_16(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14), decltype(S::M15), decltype(S::M16)
|
||||
#define SDBUSCPP_STRUCT_MEMBER_TYPE(STRUCT, MEMBER) decltype(STRUCT::MEMBER)
|
||||
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_AS_DICT_ENTRIES(STRUCT, ...) \
|
||||
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_AS_DICT_ENTRY, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
|
||||
/**/
|
||||
#define SDBUSCPP_STRUCT_MEMBER_AS_DICT_ENTRY(STRUCT, MEMBER) {#MEMBER, Variant{STRUCT.MEMBER}}
|
||||
|
||||
#define SDBUSCPP_STRUCT_MEMBERS_AS_NESTED_DICT_ENTRIES(STRUCT, ...) \
|
||||
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_AS_NESTED_DICT_ENTRY, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
|
||||
/**/
|
||||
#define SDBUSCPP_STRUCT_MEMBER_AS_NESTED_DICT_ENTRY(STRUCT, MEMBER) {#MEMBER, Variant{as_dictionary_if_struct(STRUCT.MEMBER)}}
|
||||
|
||||
#define SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBERS(STRUCT, ...) \
|
||||
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBER, SDBUSCPP_PP_SPACE, STRUCT, __VA_ARGS__) \
|
||||
/**/
|
||||
#define SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBER(STRUCT, MEMBER) if (key == #MEMBER) STRUCT.MEMBER = value.get<decltype(STRUCT.MEMBER)>(); else
|
||||
|
||||
#define SDBUSCPP_FOR_EACH_1(M, D, S, M1) M(S, M1)
|
||||
#define SDBUSCPP_FOR_EACH_2(M, D, S, M1, M2) M(S, M1) D M(S, M2)
|
||||
#define SDBUSCPP_FOR_EACH_3(M, D, S, M1, M2, M3) M(S, M1) D M(S, M2) D M(S, M3)
|
||||
#define SDBUSCPP_FOR_EACH_4(M, D, S, M1, M2, M3, M4) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4)
|
||||
#define SDBUSCPP_FOR_EACH_5(M, D, S, M1, M2, M3, M4, M5) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5)
|
||||
#define SDBUSCPP_FOR_EACH_6(M, D, S, M1, M2, M3, M4, M5, M6) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6)
|
||||
#define SDBUSCPP_FOR_EACH_7(M, D, S, M1, M2, M3, M4, M5, M6, M7) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7)
|
||||
#define SDBUSCPP_FOR_EACH_8(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8)
|
||||
#define SDBUSCPP_FOR_EACH_9(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9)
|
||||
#define SDBUSCPP_FOR_EACH_10(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10)
|
||||
#define SDBUSCPP_FOR_EACH_11(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11)
|
||||
#define SDBUSCPP_FOR_EACH_12(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12)
|
||||
#define SDBUSCPP_FOR_EACH_13(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13)
|
||||
#define SDBUSCPP_FOR_EACH_14(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14)
|
||||
#define SDBUSCPP_FOR_EACH_15(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14) D M(S, M15)
|
||||
#define SDBUSCPP_FOR_EACH_16(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14) D M(S, M15) D M(S, M16)
|
||||
|
||||
#define SDBUSCPP_PP_CAT(X, Y) SDBUSCPP_PP_CAT_IMPL(X, Y)
|
||||
#define SDBUSCPP_PP_CAT_IMPL(X, Y) X##Y
|
||||
#define SDBUSCPP_PP_NARG(...) SDBUSCPP_PP_NARG_IMPL(__VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
|
||||
#define SDBUSCPP_PP_NARG_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _N, ...) _N
|
||||
|
||||
#define SDBUSCPP_PP_COMMA ,
|
||||
#define SDBUSCPP_PP_SPACE
|
||||
|
||||
#endif /* SDBUS_CXX_TYPES_H_ */
|
||||
|
@ -48,8 +48,22 @@ using ::testing::ElementsAre;
|
||||
using ::testing::SizeIs;
|
||||
using ::testing::NotNull;
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::string_literals;
|
||||
using namespace sdbus::test;
|
||||
|
||||
namespace my {
|
||||
struct Struct
|
||||
{
|
||||
int i;
|
||||
std::string s;
|
||||
std::vector<double> l;
|
||||
|
||||
friend bool operator==(const Struct &lhs, const Struct &rhs) = default;
|
||||
};
|
||||
}
|
||||
|
||||
SDBUSCPP_REGISTER_STRUCT(my::Struct, i, s, l);
|
||||
|
||||
/*-------------------------------------*/
|
||||
/* -- TEST CASES -- */
|
||||
/*-------------------------------------*/
|
||||
@ -249,6 +263,21 @@ TYPED_TEST(SdbusTestObject, CanReceiveSignalWhileMakingMethodCall)
|
||||
EXPECT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap));
|
||||
}
|
||||
|
||||
TYPED_TEST(SdbusTestObject, CanSendAndReceiveDictionariesAsCustomStructsImplicitly)
|
||||
{
|
||||
// This test demonstrates that sdbus-c++ can send a SDBUSCPP_REGISTER_STRUCT-described struct as a dictionary of strings to variants,
|
||||
// and that sdbus-c++ can automatically deserialize a dictionary of strings to variants into such a struct (instead of a map).
|
||||
const my::Struct structSent{3545342, "hello"s, {3.14, 2.4568546}};
|
||||
my::Struct structReceived;
|
||||
|
||||
this->m_proxy->getProxy().callMethod("returnDictionary")
|
||||
.onInterface("org.sdbuscpp.integrationtests")
|
||||
.withArguments(sdbus::as_dictionary(structSent))
|
||||
.storeResultsTo(structReceived);
|
||||
|
||||
ASSERT_THAT(structReceived, Eq(structSent));
|
||||
}
|
||||
|
||||
TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInMethodCallHandler)
|
||||
{
|
||||
this->m_proxy->doOperation(10); // This will save pointer to method call message on server side
|
||||
|
@ -246,6 +246,11 @@ void TestAdaptor::sendLargeMessage(const std::map<int, std::string>& /*collectio
|
||||
//printf("Adaptor: got collection with %zu items", collection.size());
|
||||
}
|
||||
|
||||
std::map<std::string, sdbus::Variant> TestAdaptor::returnDictionary(const std::map<std::string, sdbus::Variant>& dict)
|
||||
{
|
||||
return dict;
|
||||
}
|
||||
|
||||
std::string TestAdaptor::state()
|
||||
{
|
||||
return m_state;
|
||||
|
@ -85,6 +85,7 @@ protected:
|
||||
void doPrivilegedStuff() override;
|
||||
void emitTwoSimpleSignals() override;
|
||||
void sendLargeMessage(const std::map<int, std::string>& collection) override;
|
||||
std::map<std::string, sdbus::Variant> returnDictionary(const std::map<std::string, sdbus::Variant>& dict) override;
|
||||
|
||||
uint32_t action() override;
|
||||
void action(const uint32_t& value) override;
|
||||
@ -147,6 +148,7 @@ protected:
|
||||
void doPrivilegedStuff() override {}
|
||||
void emitTwoSimpleSignals() override {}
|
||||
void sendLargeMessage(const std::map<int, std::string>&) override {}
|
||||
std::map<std::string, sdbus::Variant> returnDictionary(const std::map<std::string, sdbus::Variant>&) override { return {}; }
|
||||
|
||||
uint32_t action() override { return {}; }
|
||||
void action(const uint32_t&) override {}
|
||||
|
@ -58,6 +58,7 @@ protected:
|
||||
, sdbus::registerMethod("doPrivilegedStuff").implementedAs([this](){ return this->doPrivilegedStuff(); }).markAsPrivileged()
|
||||
, sdbus::registerMethod("emitTwoSimpleSignals").implementedAs([this](){ return this->emitTwoSimpleSignals(); })
|
||||
, sdbus::registerMethod("sendLargeMessage").implementedAs([this](const std::map<int, std::string>& collection){ this->sendLargeMessage(collection); })
|
||||
, sdbus::registerMethod("returnDictionary").implementedAs([this](const std::map<std::string, sdbus::Variant>& dictionary){ return this->returnDictionary(dictionary); })
|
||||
, sdbus::registerSignal("simpleSignal").markAsDeprecated()
|
||||
, sdbus::registerSignal("signalWithMap").withParameters<std::map<int32_t, std::string>>("aMap")
|
||||
, sdbus::registerSignal("signalWithVariant").withParameters<sdbus::Variant>("aVariant")
|
||||
@ -108,6 +109,7 @@ private:
|
||||
virtual void doPrivilegedStuff() = 0;
|
||||
virtual void emitTwoSimpleSignals() = 0;
|
||||
virtual void sendLargeMessage(const std::map<int, std::string>& collection) = 0;
|
||||
virtual std::map<std::string, sdbus::Variant> returnDictionary(const std::map<std::string, sdbus::Variant>& dict) = 0;
|
||||
|
||||
private:
|
||||
virtual uint32_t action() = 0;
|
||||
|
@ -36,6 +36,8 @@ using ::testing::StrEq;
|
||||
using ::testing::Gt;
|
||||
using ::testing::DoubleEq;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::SizeIs;
|
||||
using ::testing::ElementsAre;
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace
|
||||
@ -53,6 +55,7 @@ namespace sdbus {
|
||||
template <typename _ElementType>
|
||||
sdbus::Message& operator<<(sdbus::Message& msg, const std::list<_ElementType>& items)
|
||||
{
|
||||
// TODO: This can also be simplified on the basis of a callback (see dictionary...)
|
||||
msg.openContainer<_ElementType>();
|
||||
|
||||
for (const auto& item : items)
|
||||
@ -96,7 +99,8 @@ namespace my {
|
||||
enum class Enum
|
||||
{
|
||||
Value1,
|
||||
Value2
|
||||
Value2,
|
||||
Value3
|
||||
};
|
||||
|
||||
struct Struct
|
||||
@ -108,10 +112,36 @@ namespace my {
|
||||
|
||||
friend bool operator==(const Struct& lhs, const Struct& rhs) = default;
|
||||
};
|
||||
|
||||
struct RelaxedStruct
|
||||
{
|
||||
int i;
|
||||
std::string s;
|
||||
std::list<double> l;
|
||||
Enum e;
|
||||
|
||||
friend bool operator==(const RelaxedStruct& lhs, const RelaxedStruct& rhs) = default;
|
||||
};
|
||||
|
||||
struct NestedStruct
|
||||
{
|
||||
int i;
|
||||
std::string s;
|
||||
Enum e;
|
||||
Struct x;
|
||||
|
||||
friend bool operator==(const NestedStruct& lhs, const NestedStruct& rhs) = default;
|
||||
};
|
||||
}
|
||||
|
||||
SDBUSCPP_REGISTER_STRUCT(my::Struct, i, s, l, e);
|
||||
|
||||
SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION(my::RelaxedStruct);
|
||||
SDBUSCPP_REGISTER_STRUCT(my::RelaxedStruct, i, s, l, e);
|
||||
|
||||
SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION(my::NestedStruct);
|
||||
SDBUSCPP_REGISTER_STRUCT(my::NestedStruct, i, s, e, x);
|
||||
|
||||
/*-------------------------------------*/
|
||||
/* -- TEST CASES -- */
|
||||
/*-------------------------------------*/
|
||||
@ -490,7 +520,7 @@ TEST(AMessage, CanCarryDBusArrayGivenAsCustomType)
|
||||
ASSERT_THAT(dataRead, Eq(dataWritten));
|
||||
}
|
||||
|
||||
TEST(AMessage, CanCarryDBusStructGivenAsCustomType)
|
||||
TEST(AMessage, CanCarryUserDefinedStruct)
|
||||
{
|
||||
auto msg = sdbus::createPlainMessage();
|
||||
|
||||
@ -505,6 +535,116 @@ TEST(AMessage, CanCarryDBusStructGivenAsCustomType)
|
||||
ASSERT_THAT(dataRead, Eq(dataWritten));
|
||||
}
|
||||
|
||||
TEST(AMessage, CanCarryNestedUserDefinedStruct)
|
||||
{
|
||||
auto msg = sdbus::createPlainMessage();
|
||||
|
||||
const my::NestedStruct dataWritten{3545342, "hello"s, my::Enum::Value2, {12, "world"s, {3.14, 2.4568546}, my::Enum::Value3}};
|
||||
|
||||
msg << dataWritten;
|
||||
msg.seal();
|
||||
|
||||
my::NestedStruct dataRead;
|
||||
msg >> dataRead;
|
||||
|
||||
ASSERT_THAT(dataRead, Eq(dataWritten));
|
||||
}
|
||||
|
||||
TEST(AMessage, CanSerializeUserDefinedStructAsDictionaryOfStringsToVariants)
|
||||
{
|
||||
auto msg = sdbus::createPlainMessage();
|
||||
|
||||
const my::Struct dataWritten{3545342, "hello"s, {3.14, 2.4568546}, my::Enum::Value2};
|
||||
|
||||
msg << sdbus::as_dictionary{dataWritten};
|
||||
msg.seal();
|
||||
|
||||
std::map<std::string, sdbus::Variant> dataRead;
|
||||
msg >> dataRead;
|
||||
|
||||
ASSERT_THAT(dataRead, SizeIs(4));
|
||||
ASSERT_THAT(dataRead.at("i").get<int>(), Eq(3545342));
|
||||
ASSERT_THAT(dataRead.at("s").get<std::string>(), Eq("hello"));
|
||||
ASSERT_THAT(dataRead.at("l").get<std::list<double>>(), ElementsAre(3.14, 2.4568546));
|
||||
ASSERT_THAT(dataRead.at("e").get<my::Enum>(), Eq(my::Enum::Value2));
|
||||
}
|
||||
|
||||
TEST(AMessage, CanRecursivelySerializeUserDefinedStructAsDictionaryOfStringsToVariants)
|
||||
{
|
||||
auto msg = sdbus::createPlainMessage();
|
||||
|
||||
const my::NestedStruct dataWritten{3545342, "hello"s, my::Enum::Value2, {12, "world"s, {3.14, 2.4568546}, my::Enum::Value3}};
|
||||
|
||||
msg << sdbus::as_dictionary{dataWritten};
|
||||
msg.seal();
|
||||
|
||||
std::map<std::string, sdbus::Variant> dataRead;
|
||||
msg >> dataRead;
|
||||
|
||||
ASSERT_THAT(dataRead, SizeIs(4));
|
||||
ASSERT_THAT(dataRead.at("i").get<int>(), Eq(3545342));
|
||||
ASSERT_THAT(dataRead.at("s").get<std::string>(), Eq("hello"));
|
||||
ASSERT_THAT(dataRead.at("e").get<my::Enum>(), Eq(my::Enum::Value2));
|
||||
auto nestedStructRead = dataRead.at("x").get<std::map<std::string, sdbus::Variant>>(); // Nested struct serialized as dict
|
||||
ASSERT_THAT(nestedStructRead.at("i").get<int>(), Eq(12));
|
||||
ASSERT_THAT(nestedStructRead.at("s").get<std::string>(), Eq("world"));
|
||||
ASSERT_THAT(nestedStructRead.at("l").get<std::list<double>>(), ElementsAre(3.14, 2.4568546));
|
||||
ASSERT_THAT(nestedStructRead.at("e").get<my::Enum>(), Eq(my::Enum::Value3));
|
||||
}
|
||||
|
||||
TEST(AMessage, CanDeserializeDictionaryOfStringsToVariantsIntoUserDefinedStruct)
|
||||
{
|
||||
auto msg = sdbus::createPlainMessage();
|
||||
|
||||
std::map<std::string, sdbus::Variant> dataWritten{ {"i", sdbus::Variant{3545342}}
|
||||
, {"s", sdbus::Variant{"hello"s}}
|
||||
, {"l", sdbus::Variant{std::list<double>{3.14, 2.4568546}}}
|
||||
, {"e", sdbus::Variant{my::Enum::Value2}} };
|
||||
|
||||
msg << dataWritten;
|
||||
msg.seal();
|
||||
|
||||
my::Struct dataRead;
|
||||
msg >> dataRead;
|
||||
|
||||
ASSERT_THAT(dataRead, Eq(my::Struct{3545342, "hello"s, {3.14, 2.4568546}, my::Enum::Value2}));
|
||||
}
|
||||
|
||||
TEST(AMessage, FailsDeserializingDictionaryIntoUserDefinedStructIfStructMemberIsNotFound)
|
||||
{
|
||||
auto msg = sdbus::createPlainMessage();
|
||||
|
||||
std::map<std::string, sdbus::Variant> dataWritten{ {"i", sdbus::Variant{3545342}}
|
||||
, {"nonexistent", sdbus::Variant{"hello"s}}
|
||||
, {"l", sdbus::Variant{std::list<double>{3.14, 2.4568546}}}
|
||||
, {"e", sdbus::Variant{my::Enum::Value2}} };
|
||||
|
||||
msg << dataWritten;
|
||||
msg.seal();
|
||||
|
||||
my::Struct dataRead;
|
||||
|
||||
ASSERT_THROW(msg >> dataRead, sdbus::Error);
|
||||
}
|
||||
|
||||
TEST(AMessage, DeserializesDictionaryIntoStructWithMissingMembersSuccessfullyIfRelaxedOptionIsSet)
|
||||
{
|
||||
auto msg = sdbus::createPlainMessage();
|
||||
|
||||
std::map<std::string, sdbus::Variant> dataWritten{ {"some_nonexistent_struct_member", sdbus::Variant{3545342}}
|
||||
, {"another_nonexistent_struct_member", sdbus::Variant{"hello"s}}
|
||||
, {"l", sdbus::Variant{std::list<double>{3.14, 2.4568546}}}
|
||||
, {"e", sdbus::Variant{my::Enum::Value2}} };
|
||||
|
||||
msg << dataWritten;
|
||||
msg.seal();
|
||||
|
||||
my::RelaxedStruct dataRead{};
|
||||
msg >> dataRead;
|
||||
|
||||
ASSERT_THAT(dataRead, Eq(my::RelaxedStruct{{}, {}, {3.14, 2.4568546}, my::Enum::Value2}));
|
||||
}
|
||||
|
||||
class AMessage : public ::testing::TestWithParam<std::variant<int32_t, std::string, my::Struct>>
|
||||
{
|
||||
};
|
||||
|
Reference in New Issue
Block a user