feat: add support for dumping variant to string (#526)

This allows Message and Variant contents serialization to a string -- based on `sd_bus_message_dump` sd-bus API function.
This commit is contained in:
Stanislav Angelovič
2026-01-15 23:24:54 +01:00
committed by GitHub
parent 9f3e89eb6a
commit c2bb343f8a
5 changed files with 93 additions and 2 deletions

View File

@@ -55,7 +55,7 @@ CheckOptions:
- key: hicpp-move-const-arg.CheckTriviallyCopyableMove
value: '0'
- key: misc-include-cleaner.IgnoreHeaders
value: 'systemd/.*|sdbus-c\+\+/.*|gtest/.*|gmock/.*|bits/chrono.h|bits/basic_string.h|time.h|poll.h|stdlib.h'
value: 'systemd/.*|sdbus-c\+\+/.*|gtest/.*|gmock/.*|bits/chrono.h|bits/basic_string.h|time.h|poll.h|stdlib.h|stdio.h'
- key: readability-simplify-boolean-expr.IgnoreMacros
value: '1'
- key: cppcoreguidelines-rvalue-reference-param-not-moved.IgnoreUnnamedParams

View File

@@ -228,6 +228,15 @@ namespace sdbus {
void seal();
void rewind(bool complete);
enum class DumpFlags : uint64_t // NOLINT(performance-enum-size): using size from sd-bus
{
Default = 0ULL,
WithHeader = 1ULL << 0,
SubtreeOnly = 1ULL << 1,
SubtreeOnlyWithHeader = WithHeader | SubtreeOnly
};
[[nodiscard]] std::string dumpToString(DumpFlags flags) const;
pid_t getCredsPid() const;
uid_t getCredsUid() const;
uid_t getCredsEuid() const;
@@ -260,7 +269,6 @@ namespace sdbus {
friend Factory;
void* msg_{};
internal::IConnection* connection_{};
mutable bool ok_{true};

View File

@@ -103,6 +103,13 @@ namespace sdbus {
return val;
}
[[nodiscard]] std::string dumpToString() const
{
msg_.rewind(false);
return msg_.dumpToString(Message::DumpFlags::SubtreeOnly);
}
// Only allow conversion operator for true D-Bus type representations in C++
// NOLINTNEXTLINE(modernize-use-constraints): TODO for future: Use `requires signature_of<_ValueType>::is_valid` (when we stop supporting C++17 in public API)
template <typename ValueType, typename = std::enable_if_t<signature_of<ValueType>::is_valid>>

View File

@@ -35,7 +35,9 @@
#include "ScopeGuard.h"
#include <cassert>
#include <cerrno>
#include <cstdint> // int16_t, uint64_t, ...
#include <cstdio>
#include <cstdlib> // atexit
#include <cstring>
#include <string>
@@ -628,6 +630,28 @@ void Message::rewind(bool complete)
SDBUS_THROW_ERROR_IF(r < 0, "Failed to rewind the message", -r);
}
std::string Message::dumpToString(DumpFlags flags) const
{
#if LIBSYSTEMD_VERSION>=245 && !defined(SDBUS_basu)
char* buffer{};
SCOPE_EXIT{ free(buffer); }; // NOLINT(cppcoreguidelines-no-malloc,hicpp-no-malloc,cppcoreguidelines-owning-memory)
size_t size{};
const std::unique_ptr<FILE, int(*)(FILE*)> stream{open_memstream(&buffer, &size), fclose};
SDBUS_THROW_ERROR_IF(!stream, "Failed to open memory stream", errno);
auto r = sd_bus_message_dump(static_cast<sd_bus_message*>(msg_), stream.get(), static_cast<uint64_t>(flags));
SDBUS_THROW_ERROR_IF(r < 0, "Failed to dump the message", -r);
(void)fflush(stream.get());
return {buffer, size};
#else
(void)flags;
throw Error(Error::Name{SD_BUS_ERROR_NOT_SUPPORTED}, "Dumping sd-bus message not supported by underlying version of libsystemd");
#endif
}
const char* Message::getInterfaceName() const
{
return sd_bus_message_get_interface(static_cast<sd_bus_message*>(msg_));

View File

@@ -45,6 +45,7 @@
using ::testing::Eq;
using ::testing::Gt;
using ::testing::HasSubstr;
using namespace std::string_literals;
namespace
@@ -148,6 +149,24 @@ TEST(ASimpleVariant, ReturnsTheSimpleValueWhenAsked)
ASSERT_THAT(variant.get<int>(), Eq(value));
}
#ifndef SDBUS_basu // Dumping message or variant to a string is not supported on basu backend
TEST(ASimpleVariant, CanBeDumpedToAString)
{
const int value = 5;
const sdbus::Variant variant(value);
// This should produce something like:
// VARIANT "i" {
// INT32 5;
// };
const auto str = variant.dumpToString();
EXPECT_THAT(str, ::HasSubstr("VARIANT \"i\""));
EXPECT_THAT(str, ::HasSubstr("INT32"));
EXPECT_THAT(str, ::HasSubstr("5"));
}
#endif // SDBUS_basu
TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
@@ -158,6 +177,39 @@ TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked)
ASSERT_THAT(variant.get<ComplexType>(), Eq(value));
}
#ifndef SDBUS_basu // Dumping message or variant to a string is not supported on basu backend
TEST(AComplexVariant, CanBeDumpedToAString)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
const ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
const sdbus::Variant variant(value);
// This should produce something like:
// VARIANT "a{ta(sd)}" {
// ARRAY "{ta(sd)}" {
// DICT_ENTRY "ta(sd)" {
// UINT64 84578348354;
// ARRAY "(sd)" {
// STRUCT "sd" {
// STRING "hello";
// DOUBLE 3.14;
// };
// STRUCT "sd" {
// STRING "world";
// DOUBLE 3.14;
// };
// };
// };
// };
// };
const auto str = variant.dumpToString();
EXPECT_THAT(str, ::HasSubstr("VARIANT \"a{ta(sd)}\""));
EXPECT_THAT(str, ::HasSubstr("hello"));
EXPECT_THAT(str, ::HasSubstr("world"));
}
#endif // SDBUS_basu
TEST(AVariant, HasConceptuallyNonmutableGetMethodWhichCanBeCalledXTimes)
{
const std::string value{"I am a string"};