From 125c09abdb26efc57be7cae9721f041941898562 Mon Sep 17 00:00:00 2001 From: rhabib15 Date: Thu, 16 Apr 2026 13:05:05 +0100 Subject: [PATCH 1/2] add support for product and version chains --- src/rmq/rmqa/rmqa_rabbitcontextoptions.h | 12 +- src/rmq/rmqamqp/rmqamqp_connection.cpp | 55 ++++++- src/tests/rmqamqp/rmqamqp_connection.t.cpp | 161 ++++++++++++++++++--- 3 files changed, 199 insertions(+), 29 deletions(-) diff --git a/src/rmq/rmqa/rmqa_rabbitcontextoptions.h b/src/rmq/rmqa/rmqa_rabbitcontextoptions.h index 0001d2db..b508997c 100644 --- a/src/rmq/rmqa/rmqa_rabbitcontextoptions.h +++ b/src/rmq/rmqa/rmqa_rabbitcontextoptions.h @@ -81,9 +81,15 @@ class RabbitContextOptions { /// \param name name of client property to set /// \param value value of client property /// NOTE: The following properties are set by default and can be - /// overridden: task, pid, os, os_version, os_patch. The following - /// properties are reserved and cannot be overridden: capabilities, - /// platform, product, version, connection_name + /// overridden: task, pid, os, os_version, os_patch, product, version. + /// If product and version are provided, "product_chain" and + /// "version_chain" fields are automatically populated showing the + /// full library stack (e.g. product_chain="my-wrapper | rmqcpp C++ Client + /// Library", version_chain="1.0.0 | 2.33.0"). + /// The following properties are reserved: capabilities, platform, + /// connection_name. The product_chain and version_chain fields are + /// automatically managed by the library stack and should not be set + /// directly by applications. RabbitContextOptions& setClientProperty(const bsl::string& name, const rmqt::FieldValue& value); diff --git a/src/rmq/rmqamqp/rmqamqp_connection.cpp b/src/rmq/rmqamqp/rmqamqp_connection.cpp index b13efcdb..0d116421 100644 --- a/src/rmq/rmqamqp/rmqamqp_connection.cpp +++ b/src/rmq/rmqamqp/rmqamqp_connection.cpp @@ -81,6 +81,54 @@ const bsl::uint16_t k_HUNG_TIMER_SEC = 60; BALL_LOG_SET_NAMESPACE_CATEGORY("RMQAMQP.CONNECTION") +void setChainProperties(rmqt::FieldTable& props, + const rmqt::FieldTable& base, + const bsl::string& selfProduct, + const bsl::string& selfVersion) +{ + using namespace rmqt; + + FieldTable::const_iterator productChainIt = base.find("product_chain"); + FieldTable::const_iterator versionChainIt = base.find("version_chain"); + FieldTable::const_iterator productIt = base.find("product"); + FieldTable::const_iterator versionIt = base.find("version"); + + if (productChainIt != base.end() && + productChainIt->second.is()) { + // Caller provided chains -- append ourselves + props["product_chain"] = FieldValue( + productChainIt->second.the() + " | " + selfProduct); + props["version_chain"] = + FieldValue((versionChainIt != base.end() && + versionChainIt->second.is() + ? versionChainIt->second.the() + : bsl::string("unknown")) + + " | " + selfVersion); + } + else if (productIt != base.end() && productIt->second.is()) { + // Caller set product/version but no chains -- build from both + props["product_chain"] = FieldValue( + productIt->second.the() + " | " + selfProduct); + props["version_chain"] = FieldValue( + (versionIt != base.end() && versionIt->second.is() + ? versionIt->second.the() + : bsl::string("unknown")) + + " | " + selfVersion); + } + else { + // No wrapper -- just ourselves + props["product_chain"] = FieldValue(selfProduct); + props["version_chain"] = FieldValue(selfVersion); + } + + if (productIt == base.end()) { + props["product"] = FieldValue(bsl::string(selfProduct)); + } + if (versionIt == base.end()) { + props["version"] = FieldValue(bsl::string(selfVersion)); + } +} + rmqt::FieldTable generateClientProperties(const rmqt::FieldTable& base, const bsl::string& connectionName) { @@ -98,8 +146,11 @@ rmqt::FieldTable generateClientProperties(const rmqt::FieldTable& base, props["capabilities"] = FieldValue(capabilities); props["platform"] = FieldValue(bsl::string(rmqamqpt::Constants::PLATFORM)); - props["product"] = FieldValue(bsl::string(rmqamqpt::Constants::PRODUCT)); - props["version"] = FieldValue(bsl::string(rmqamqpt::Constants::VERSION)); + + setChainProperties(props, + base, + bsl::string(rmqamqpt::Constants::PRODUCT), + bsl::string(rmqamqpt::Constants::VERSION)); if (!connectionName.empty()) { props["connection_name"] = FieldValue(connectionName); diff --git a/src/tests/rmqamqp/rmqamqp_connection.t.cpp b/src/tests/rmqamqp/rmqamqp_connection.t.cpp index 97621f5c..7f76774a 100644 --- a/src/tests/rmqamqp/rmqamqp_connection.t.cpp +++ b/src/tests/rmqamqp/rmqamqp_connection.t.cpp @@ -90,6 +90,10 @@ rmqt::FieldTable generateDefaultClientProperties( rmqt::FieldValue(bsl::string(rmqamqpt::Constants::PRODUCT)); props["version"] = rmqt::FieldValue(bsl::string(rmqamqpt::Constants::VERSION)); + props["product_chain"] = + rmqt::FieldValue(bsl::string(rmqamqpt::Constants::PRODUCT)); + props["version_chain"] = + rmqt::FieldValue(bsl::string(rmqamqpt::Constants::VERSION)); if (!connectionName.empty()) { props["connection_name"] = rmqt::FieldValue(connectionName); @@ -405,7 +409,7 @@ class ConnectionTests : public ::testing::Test { , d_sendChannel(bsl::make_shared(d_retryHandlerChannel)) , d_ackQueue(bsl::make_shared()) , d_metricPublisher(bsl::make_shared()) - , d_clientProperties(generateDefaultClientProperties()) + , d_clientProperties() , d_factory(bsl::make_shared(d_resolver, d_timerFactory, d_errorCallback, @@ -679,21 +683,23 @@ TEST_F(ConnectionTests, Handshake) TEST_F(ConnectionTests, ClientProperties) { - rmqt::FieldTable overriddenClientProperties = - generateDefaultClientProperties(); - overriddenClientProperties["FOO"] = - rmqt::FieldValue(bsl::string("BAR")); // Add one more + rmqt::FieldTable inputClientProperties; + inputClientProperties["FOO"] = + rmqt::FieldValue(bsl::string("BAR")); // Add a custom property + d_factory = bsl::make_shared(d_resolver, d_timerFactory, d_errorCallback, d_metricPublisher, - overriddenClientProperties, + inputClientProperties, d_retryHandler, d_heartbeat, d_channelFactory); - expectHeaderAndStartFrames( - overriddenClientProperties); // check it's as expected + rmqt::FieldTable expectedProperties = + generateDefaultClientProperties("test-connection"); + expectedProperties["FOO"] = rmqt::FieldValue(bsl::string("BAR")); + expectHeaderAndStartFrames(expectedProperties); expectTuneFrames(); expectOpenFrame(); @@ -709,30 +715,22 @@ TEST_F(ConnectionTests, ClientProperties) d_eventLoop.run(); } -TEST_F(ConnectionTests, ClientPropertiesCantOverrideReservedOnes) +TEST_F(ConnectionTests, ClientPropertiesCantOverrideConnectionName) { - rmqt::FieldTable overriddenClientProperties = - generateDefaultClientProperties("my random connection name"); - overriddenClientProperties["platform"] = rmqt::FieldValue( - bsl::string("Should get overriden by library")); // Add one more - overriddenClientProperties["product"] = rmqt::FieldValue( - bsl::string("Should get overriden by library")); // Add one more - overriddenClientProperties["version"] = rmqt::FieldValue( - bsl::string("Should get overriden by library")); // Add one more - overriddenClientProperties["connection_name"] = rmqt::FieldValue( - bsl::string("Should get overriden by library")); // Add one more + rmqt::FieldTable inputClientProperties; + inputClientProperties["connection_name"] = + rmqt::FieldValue(bsl::string("Should get overriden by library")); d_factory = bsl::make_shared(d_resolver, d_timerFactory, d_errorCallback, d_metricPublisher, - overriddenClientProperties, + inputClientProperties, d_retryHandler, d_heartbeat, d_channelFactory); expectHeaderAndStartFrames(generateDefaultClientProperties( - "my real connection name")); // despite setting overrides, the library - // has the final say + "my real connection name")); // connection_name is still reserved expectTuneFrames(); expectOpenFrame(); @@ -740,14 +738,129 @@ TEST_F(ConnectionTests, ClientPropertiesCantOverrideReservedOnes) bsl::shared_ptr conn = createAndStartConnection("my real connection name"); - // 1. Handshake up to open with custom client properties d_eventLoop.run(); d_eventLoop.restart(); expectShutdownCalls(); } - // 2. Shutdown cleanly + d_eventLoop.run(); +} + +TEST_F(ConnectionTests, ClientPropertiesDefaultsWhenNoneProvided) +{ + rmqt::FieldTable emptyClientProperties; + d_factory = bsl::make_shared(d_resolver, + d_timerFactory, + d_errorCallback, + d_metricPublisher, + emptyClientProperties, + d_retryHandler, + d_heartbeat, + d_channelFactory); + + expectHeaderAndStartFrames( + generateDefaultClientProperties("my connection")); + expectTuneFrames(); + expectOpenFrame(); + + { + bsl::shared_ptr conn = + createAndStartConnection("my connection"); + + d_eventLoop.run(); + d_eventLoop.restart(); + + expectShutdownCalls(); + } + + d_eventLoop.run(); +} + +TEST_F(ConnectionTests, ClientPropertiesCanOverrideProductAndVersion) +{ + rmqt::FieldTable overriddenClientProperties; + overriddenClientProperties["product"] = + rmqt::FieldValue(bsl::string("xyzlib")); + overriddenClientProperties["version"] = + rmqt::FieldValue(bsl::string("4.5.6")); + d_factory = bsl::make_shared(d_resolver, + d_timerFactory, + d_errorCallback, + d_metricPublisher, + overriddenClientProperties, + d_retryHandler, + d_heartbeat, + d_channelFactory); + + rmqt::FieldTable expectedProperties = + generateDefaultClientProperties("my connection"); + expectedProperties["product"] = rmqt::FieldValue(bsl::string("xyzlib")); + expectedProperties["version"] = rmqt::FieldValue(bsl::string("4.5.6")); + expectedProperties["product_chain"] = rmqt::FieldValue( + bsl::string("xyzlib | ") + bsl::string(rmqamqpt::Constants::PRODUCT)); + expectedProperties["version_chain"] = rmqt::FieldValue( + bsl::string("4.5.6 | ") + bsl::string(rmqamqpt::Constants::VERSION)); + expectHeaderAndStartFrames(expectedProperties); + expectTuneFrames(); + expectOpenFrame(); + + { + bsl::shared_ptr conn = + createAndStartConnection("my connection"); + + d_eventLoop.run(); + d_eventLoop.restart(); + + expectShutdownCalls(); + } + + d_eventLoop.run(); +} + +TEST_F(ConnectionTests, ClientPropertiesWrapperSetsChains) +{ + rmqt::FieldTable wrapperClientProperties; + wrapperClientProperties["product"] = + rmqt::FieldValue(bsl::string("rmqcpp-wrapper")); + wrapperClientProperties["version"] = rmqt::FieldValue(bsl::string("1.2.3")); + wrapperClientProperties["product_chain"] = + rmqt::FieldValue(bsl::string("rmqcpp-wrapper")); + wrapperClientProperties["version_chain"] = + rmqt::FieldValue(bsl::string("1.2.3")); + d_factory = bsl::make_shared(d_resolver, + d_timerFactory, + d_errorCallback, + d_metricPublisher, + wrapperClientProperties, + d_retryHandler, + d_heartbeat, + d_channelFactory); + + rmqt::FieldTable expectedProperties = + generateDefaultClientProperties("my connection"); + expectedProperties["product"] = + rmqt::FieldValue(bsl::string("rmqcpp-wrapper")); + expectedProperties["version"] = rmqt::FieldValue(bsl::string("1.2.3")); + expectedProperties["product_chain"] = + rmqt::FieldValue(bsl::string("rmqcpp-wrapper | ") + + bsl::string(rmqamqpt::Constants::PRODUCT)); + expectedProperties["version_chain"] = rmqt::FieldValue( + bsl::string("1.2.3 | ") + bsl::string(rmqamqpt::Constants::VERSION)); + expectHeaderAndStartFrames(expectedProperties); + expectTuneFrames(); + expectOpenFrame(); + + { + bsl::shared_ptr conn = + createAndStartConnection("my connection"); + + d_eventLoop.run(); + d_eventLoop.restart(); + + expectShutdownCalls(); + } + d_eventLoop.run(); } From 996a8d995b9938bee23a23645da6c2e9e886f419 Mon Sep 17 00:00:00 2001 From: rhabib15 Date: Thu, 16 Apr 2026 13:05:20 +0100 Subject: [PATCH 2/2] Fix Findlibpcre2-8: resolve PCRE2::8BIT alias before aliasing --- CMakeLists.txt | 2 ++ cmake/Findlibpcre2-8.cmake | 16 ++++++++++++++++ vcpkg.json | 1 + 3 files changed, 19 insertions(+) create mode 100644 cmake/Findlibpcre2-8.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 01006667..ecc1048a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.25) project(rmq LANGUAGES C CXX) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + option(ENABLE_COMPRESSION "Enable zstd compression support" ON) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) diff --git a/cmake/Findlibpcre2-8.cmake b/cmake/Findlibpcre2-8.cmake new file mode 100644 index 00000000..e64728de --- /dev/null +++ b/cmake/Findlibpcre2-8.cmake @@ -0,0 +1,16 @@ +# Bridge module: bde's bdlConfig.cmake expects find_package(libpcre2-8), +# but vcpkg's pcre2 port provides find_package(pcre2) with target PCRE2::8BIT. +find_package(pcre2 CONFIG REQUIRED) + +if (TARGET PCRE2::8BIT AND NOT TARGET libpcre2-8::libpcre2-8) + # Resolve PCRE2::8BIT to its underlying IMPORTED target before aliasing, + # matching the approach used by the BDE vcpkg port. + get_target_property(_pcre2_actual PCRE2::8BIT ALIASED_TARGET) + if (_pcre2_actual) + add_library(libpcre2-8::libpcre2-8 ALIAS "${_pcre2_actual}") + else() + add_library(libpcre2-8::libpcre2-8 ALIAS PCRE2::8BIT) + endif() +endif() + +set(libpcre2-8_FOUND TRUE) diff --git a/vcpkg.json b/vcpkg.json index 0faba156..8e0774d6 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,6 +7,7 @@ "openssl", "gtest", "bde", + "pcre2", "zstd" ] }