From c2bb343f8a762c9179dd58bcd13bb8fa69daefbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Thu, 15 Jan 2026 23:24:54 +0100 Subject: [PATCH] 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. --- .clang-tidy | 2 +- include/sdbus-c++/Message.h | 10 ++++++- include/sdbus-c++/Types.h | 7 +++++ src/Message.cpp | 24 ++++++++++++++++ tests/unittests/Types_test.cpp | 52 ++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 5bcbaeb..ab026a3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -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 diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index d668339..421200c 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -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}; diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index 58d4402..cedccde 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -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 ::is_valid>> diff --git a/src/Message.cpp b/src/Message.cpp index 71ae731..d2af822 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -35,7 +35,9 @@ #include "ScopeGuard.h" #include +#include #include // int16_t, uint64_t, ... +#include #include // atexit #include #include @@ -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 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(msg_), stream.get(), static_cast(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(msg_)); diff --git a/tests/unittests/Types_test.cpp b/tests/unittests/Types_test.cpp index f9f1487..7405265 100644 --- a/tests/unittests/Types_test.cpp +++ b/tests/unittests/Types_test.cpp @@ -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(), 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>>; @@ -158,6 +177,39 @@ TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked) ASSERT_THAT(variant.get(), 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>>; + 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"};