Move logger traits out of detail namespace into logger_traits.hpp

Summary:
related to T15252, #24
- move logger traits out of detail namespace into logger_traits.hpp to allow writers of their own loggers to verify that their implementation satisfies the LoggerType requirements
- move impl/codecs/traits to detail/traits, traits functions are now in detail namespace
- logger outputs the contents of props in debug mode

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D32524
This commit is contained in:
Korina Šimičević
2024-12-02 10:18:43 +01:00
parent 319d024981
commit e5d36cf088
19 changed files with 226 additions and 129 deletions

View File

@ -10,6 +10,7 @@
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/reason_codes.hpp>

View File

@ -13,8 +13,8 @@
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/is_detected.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/types.hpp>
@ -24,83 +24,12 @@ namespace async_mqtt5::detail {
namespace asio = boost::asio;
using boost::system::error_code;
// NOOP Logger
class noop_logger {};
// at_resolve
template <typename T>
using at_resolve_sig = decltype(
std::declval<T&>().at_resolve(
std::declval<error_code>(),
std::declval<std::string_view>(), std::declval<std::string_view>(),
std::declval<const asio::ip::tcp::resolver::results_type&>()
)
);
template <typename T>
constexpr bool has_at_resolve = boost::is_detected<at_resolve_sig, T>::value;
// at_tcp_connect
template <typename T>
using at_tcp_connect_sig = decltype(
std::declval<T&>().at_tcp_connect(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_tcp_connect = boost::is_detected<at_tcp_connect_sig, T>::value;
// at_tls_handshake
template <typename T>
using at_tls_handshake_sig = decltype(
std::declval<T&>().at_tls_handshake(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_tls_handshake = boost::is_detected<at_tls_handshake_sig, T>::value;
// at_ws_handshake
template <typename T>
using at_ws_handshake_sig = decltype(
std::declval<T&>().at_ws_handshake(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_ws_handshake = boost::is_detected<at_ws_handshake_sig, T>::value;
// at_connack
template <typename T>
using at_connack_sig = decltype(
std::declval<T&>().at_connack(
std::declval<reason_code>(),
std::declval<bool>(), std::declval<const connack_props&>()
)
);
template <typename T>
constexpr bool has_at_connack = boost::is_detected<at_connack_sig, T>::value;
// at_disconnect
template <typename T>
using at_disconnect_sig = decltype(
std::declval<T&>().at_disconnect(
std::declval<reason_code>(), std::declval<const disconnect_props&>()
)
);
template <typename T>
constexpr bool has_at_disconnect = boost::is_detected<at_disconnect_sig, T>::value;
template <typename LoggerType = noop_logger>
template <typename LoggerType>
class log_invoke {
LoggerType _logger;
public:
explicit log_invoke(LoggerType&& logger = {}) :
_logger(std::forward<LoggerType>(logger))
explicit log_invoke(LoggerType logger = {}) :
_logger(std::move(logger))
{}
void at_resolve(

View File

@ -16,7 +16,7 @@
#include <boost/range/iterator_range_core.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
namespace async_mqtt5 {
namespace async_mqtt5::detail {
template <typename>
constexpr bool is_optional_impl = false;
@ -58,6 +58,6 @@ constexpr bool is_boost_iterator = is_specialization<
boost::remove_cv_ref_t<T>, boost::iterator_range
>;
} // end namespace async_mqtt5
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_TRAITS_HPP

View File

@ -19,7 +19,7 @@
#include <boost/optional/optional.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/impl/codecs/traits.hpp>
#include <async_mqtt5/detail/traits.hpp>
namespace async_mqtt5::decoders {
@ -80,7 +80,7 @@ constexpr auto to(T& arg) {
return [&](auto& ctx) {
using ctx_type = decltype(ctx);
using attr_type = decltype(x3::_attr(std::declval<const ctx_type&>()));
if constexpr (is_boost_iterator<attr_type>)
if constexpr (detail::is_boost_iterator<attr_type>)
arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() };
else
arg = x3::_attr(ctx);
@ -289,8 +289,8 @@ struct len_prefix_parser : x3::parser<len_prefix_parser> {
}
};
constexpr len_prefix_parser utf8_{};
constexpr len_prefix_parser binary_{};
constexpr len_prefix_parser utf8_ {};
constexpr len_prefix_parser binary_ {};
/*
Boost Spirit incorrectly deduces atribute type for a parser of the form
@ -362,6 +362,7 @@ bool parse_to_prop(
It& iter, const It last,
const Ctx& ctx, RCtx& rctx, Prop& prop
) {
using namespace async_mqtt5::detail;
using prop_type = std::remove_reference_t<decltype(prop)>;
bool rv = false;

View File

@ -20,7 +20,7 @@
#include <boost/type_traits/remove_cv_ref.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/impl/codecs/traits.hpp>
#include <async_mqtt5/detail/traits.hpp>
namespace async_mqtt5::encoders {
@ -72,7 +72,7 @@ public:
template <
typename T,
typename projection = boost::identity,
std::enable_if_t<is_optional<T>, bool> = true
std::enable_if_t<detail::is_optional<T>, bool> = true
>
auto operator()(T&& value, projection proj = {}) const {
if constexpr (std::is_same_v<projection, boost::identity>) {
@ -89,7 +89,7 @@ public:
template <
typename T,
typename projection = boost::identity,
std::enable_if_t<!is_optional<T>, bool> = true
std::enable_if_t<!detail::is_optional<T>, bool> = true
>
auto operator()(T&& value, projection proj = {}) const {
auto val = static_cast<repr>(std::invoke(proj, value));
@ -125,7 +125,7 @@ public:
int_val(T val) : _val(val) {}
size_t byte_size() const {
if constexpr (is_optional<T>) {
if constexpr (detail::is_optional<T>) {
if (_val) return val_length(*_val);
return 0;
}
@ -134,7 +134,7 @@ public:
}
std::string& encode(std::string& s) const {
if constexpr (is_optional<T>) {
if constexpr (detail::is_optional<T>) {
if (_val) return encode_val(s, *_val);
return s;
}
@ -176,7 +176,7 @@ public:
template <typename T, typename projection>
auto operator()(T&& val, projection proj) const {
if constexpr (is_optional<T>) {
if constexpr (detail::is_optional<T>) {
using rv_type = std::invoke_result_t<
projection, typename boost::remove_cv_ref_t<T>::value_type
>;
@ -211,14 +211,14 @@ public:
}
size_t byte_size() const {
if constexpr (is_optional<T>)
if constexpr (detail::is_optional<T>)
return _val ? _with_length * 2 + val_length(*_val) : 0;
else
return _with_length * 2 + val_length(_val);
}
std::string& encode(std::string& s) const {
if constexpr (is_optional<T>) {
if constexpr (detail::is_optional<T>) {
if (_val) return encode_val(s, *_val);
return s;
}
@ -268,7 +268,7 @@ public:
template <typename T, typename projection>
auto operator()(T&& val, projection proj) const {
if constexpr (is_optional<T>) {
if constexpr (detail::is_optional<T>) {
using rv_type = std::invoke_result_t<
projection, typename boost::remove_cv_ref_t<T>::value_type
>;
@ -346,7 +346,7 @@ auto encoder_for_prop_value(const T& val) {
return basic::int_def<uint32_t>{}(val);
else if constexpr (std::is_same_v<T, std::string>)
return basic::utf8_def{}(val);
else if constexpr (is_pair<T>)
else if constexpr (detail::is_pair<T>)
return encoder_for_prop_value(val.first) &
encoder_for_prop_value(val.second);
}
@ -359,7 +359,7 @@ template <
>
class prop_val<
T, p,
std::enable_if_t<!is_vector<T> && is_optional<T>>
std::enable_if_t<!detail::is_vector<T> && detail::is_optional<T>>
> : public basic::encoder {
// allows T to be reference type to std::optional
static inline boost::remove_cv_ref_t<T> nulltype;
@ -388,7 +388,7 @@ template <
>
class prop_val<
T, p,
std::enable_if_t<is_vector<T> || is_small_vector<T>>
std::enable_if_t<detail::is_vector<T> || detail::is_small_vector<T>>
> : public basic::encoder {
// allows T to be reference type to std::vector
static inline boost::remove_cv_ref_t<T> nulltype;
@ -490,7 +490,7 @@ class props_def {
public:
template <typename T>
auto operator()(T&& prop_container) const {
if constexpr (is_optional<T>) {
if constexpr (detail::is_optional<T>) {
if (prop_container.has_value())
return (*this)(*prop_container);
return props_val<

View File

@ -54,10 +54,10 @@ public:
});
}
run_op(run_op&&) noexcept = default;
run_op(run_op&&) = default;
run_op(const run_op&) = delete;
run_op& operator=(run_op&&) noexcept = default;
run_op& operator=(run_op&&) = default;
run_op& operator=(const run_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;

View File

@ -8,17 +8,19 @@
#ifndef ASYNC_MQTT5_LOGGER_HPP
#define ASYNC_MQTT5_LOGGER_HPP
#include <cstdint>
#include <iostream>
#include <string_view>
#include <boost/system/error_code.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/impl/codecs/traits.hpp>
#include <async_mqtt5/detail/traits.hpp>
namespace async_mqtt5 {
@ -83,7 +85,7 @@ public:
if (!ec && _level < log_level::info)
return;
write_prefix();
output_prefix();
std::clog
<< "resolve: "
<< host << ":" << port;
@ -110,7 +112,7 @@ public:
if (!ec && _level < log_level::info)
return;
write_prefix();
output_prefix();
std::clog
<< "connect: "
<< ep.address().to_string() << ":" << ep.port()
@ -128,7 +130,7 @@ public:
if (!ec && _level < log_level::info)
return;
write_prefix();
output_prefix();
std::clog
<< "TLS handshake: "
<< ep.address().to_string() << ":" << ep.port()
@ -146,7 +148,7 @@ public:
if (!ec && _level < log_level::info)
return;
write_prefix();
output_prefix();
std::clog
<< "WebSocket handshake: "
<< ep.address().to_string() << ":" << ep.port()
@ -165,13 +167,17 @@ public:
*/
void at_connack(
reason_code rc,
bool /* session_present */, const connack_props& /* ca_props */
bool session_present, const connack_props& ca_props
) {
if (!rc && _level < log_level::info)
return;
write_prefix();
output_prefix();
std::clog << "connack: " << rc.message() << ".";
if (_level == log_level::debug) {
std::clog << " session_present:" << session_present << " ";
output_props(ca_props);
}
std::clog << std::endl;
}
@ -183,19 +189,67 @@ public:
* \param dc_props \__DISCONNECT_PROPS\__ received in the \__DISCONNECT\__ packet.
*/
void at_disconnect(reason_code rc, const disconnect_props& dc_props) {
write_prefix();
output_prefix();
std::clog << "disconnect: " << rc.message() << ".";
if (dc_props[prop::reason_string].has_value())
std::clog << " Reason string: " << * dc_props[prop::reason_string];
if (_level == log_level::debug)
output_props(dc_props);
std::clog << std::endl;
}
private:
void write_prefix() {
void output_prefix() {
std::clog << prefix << " ";
}
template <typename Props>
void output_props(const Props& props) {
props.visit(
[](const auto& prop, const auto& val) -> bool {
if constexpr (detail::is_optional<decltype(val)>) {
if (val.has_value()) {
std::clog << property_name(prop) << ":";
using value_type = boost::remove_cv_ref_t<decltype(*val)>;
if constexpr (std::is_same_v<value_type, uint8_t>)
std::clog << std::to_string(*val) << " ";
else
std::clog << *val << " ";
}
} else { // is vector
if (val.empty())
return true;
std::clog << property_name(prop) << ":";
std::clog << "[";
for (auto i = 0; i < val.size(); i++) {
if constexpr (detail::is_pair<decltype(val[i])>)
std::clog << "(" << val[i].first << "," << val[i].second << ")";
else
std::clog << std::to_string(val[i]);
if (i + 1 < val.size())
std::clog << ", ";
}
std::clog << "]";
}
return true;
}
);
}
template <prop::property_type p>
static std::string_view property_name(std::integral_constant<prop::property_type, p>) {
return prop::name_v<p>;
}
};
// Verify that the logger class satisfies the LoggerType concept
static_assert(has_at_resolve<logger>);
static_assert(has_at_tcp_connect<logger>);
static_assert(has_at_tls_handshake<logger>);
static_assert(has_at_ws_handshake<logger>);
static_assert(has_at_connack<logger>);
static_assert(has_at_disconnect<logger>);
} // end namespace async_mqtt5

View File

@ -0,0 +1,103 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_LOGGER_TRAITS_HPP
#define ASYNC_MQTT5_LOGGER_TRAITS_HPP
#include <iostream>
#include <string_view>
#include <type_traits>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/is_detected.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5 {
namespace asio = boost::asio;
using boost::system::error_code;
// NOOP Logger
class noop_logger {};
// at_resolve
template <typename T>
using at_resolve_sig = decltype(
std::declval<T&>().at_resolve(
std::declval<error_code>(),
std::declval<std::string_view>(), std::declval<std::string_view>(),
std::declval<const asio::ip::tcp::resolver::results_type&>()
)
);
template <typename T>
constexpr bool has_at_resolve = boost::is_detected<at_resolve_sig, T>::value;
// at_tcp_connect
template <typename T>
using at_tcp_connect_sig = decltype(
std::declval<T&>().at_tcp_connect(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_tcp_connect = boost::is_detected<at_tcp_connect_sig, T>::value;
// at_tls_handshake
template <typename T>
using at_tls_handshake_sig = decltype(
std::declval<T&>().at_tls_handshake(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_tls_handshake = boost::is_detected<at_tls_handshake_sig, T>::value;
// at_ws_handshake
template <typename T>
using at_ws_handshake_sig = decltype(
std::declval<T&>().at_ws_handshake(
std::declval<error_code>(), std::declval<asio::ip::tcp::endpoint>()
)
);
template <typename T>
constexpr bool has_at_ws_handshake = boost::is_detected<at_ws_handshake_sig, T>::value;
// at_connack
template <typename T>
using at_connack_sig = decltype(
std::declval<T&>().at_connack(
std::declval<reason_code>(),
std::declval<bool>(), std::declval<const connack_props&>()
)
);
template <typename T>
constexpr bool has_at_connack = boost::is_detected<at_connack_sig, T>::value;
// at_disconnect
template <typename T>
using at_disconnect_sig = decltype(
std::declval<T&>().at_disconnect(
std::declval<reason_code>(), std::declval<const disconnect_props&>()
)
);
template <typename T>
constexpr bool has_at_disconnect = boost::is_detected<at_disconnect_sig, T>::value;
} // end namespace async_mqtt5
#endif // !ASYNC_MQTT5_LOGGER_TRAITS_HPP

View File

@ -18,6 +18,7 @@
#include <boost/system/error_code.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/log_invoke.hpp>
@ -52,7 +53,7 @@ namespace asio = boost::asio;
template <
typename StreamType,
typename TlsContext = std::monostate,
typename LoggerType = detail::noop_logger
typename LoggerType = noop_logger
>
class mqtt_client {
public:
@ -134,7 +135,6 @@ public:
*/
mqtt_client(mqtt_client&&) noexcept = default;
/**
* \brief Move assignment operator.
*
@ -163,7 +163,6 @@ public:
return _impl->get_executor();
}
/**
* \brief Get the context object used in TLS/SSL connection.
*

View File

@ -12,6 +12,7 @@
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <vector>

View File

@ -20,9 +20,9 @@
#include <boost/range/algorithm/transform.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/traits.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
#include <async_mqtt5/impl/codecs/traits.hpp>
namespace async_mqtt5::test {
@ -89,6 +89,7 @@ template <typename Props>
inline std::string to_readable_props(Props props) {
std::ostringstream stream;
props.visit([&stream](const auto&, const auto& v) -> bool {
using namespace async_mqtt5::detail;
if constexpr (is_optional<decltype(v)>)
if (v.has_value())
stream << *v << " ";

View File

@ -30,7 +30,7 @@ using error_code = boost::system::error_code;
template <
typename StreamType,
typename StreamContext = std::monostate,
typename LoggerType = async_mqtt5::detail::noop_logger
typename LoggerType = async_mqtt5::noop_logger
>
class test_autoconnect_stream {
public:

View File

@ -23,6 +23,7 @@
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/traits.hpp>
#include "test_common/message_exchange.hpp"
#include "test_common/packet_util.hpp"
@ -406,7 +407,7 @@ BOOST_FIXTURE_TEST_CASE(connack_properties, shared_connack_prop_test_data) {
connack_props cprops_ = c.connack_properties();
cprops_.visit([&](const auto& p, const auto& val) -> bool {
BOOST_TEST_REQUIRE(p);
if constexpr (is_vector<decltype(val)>)
if constexpr (detail::is_vector<decltype(val)>)
BOOST_TEST(val == cprops[p]);
else {
BOOST_TEST_REQUIRE(val.has_value());
@ -429,7 +430,7 @@ BOOST_FIXTURE_TEST_CASE(connack_property, shared_connack_prop_test_data) {
std::move(broker_side),
[&](client_type& c) {
cprops.visit([&](const auto& p, const auto& val) -> bool{
if constexpr (is_vector<decltype(val)>)
if constexpr (detail::is_vector<decltype(val)>)
BOOST_TEST(val == c.connack_property(p));
else {
BOOST_TEST_REQUIRE(val.has_value());

View File

@ -34,7 +34,11 @@ using strand_type = asio::strand<asio::any_io_executor>;
BOOST_AUTO_TEST_SUITE(executors)
void run_test(asio::io_context& ioc, strand_type io_ex, auto bind_async_run, auto bind_async_op) {
template <typename AsyncRunOp, typename AsyncOp>
void run_test(
asio::io_context& ioc, strand_type io_ex,
AsyncRunOp&& bind_async_run, AsyncOp&& bind_async_op
) {
using test::after;
using namespace std::chrono_literals;

View File

@ -473,7 +473,7 @@ BOOST_FIXTURE_TEST_CASE(receive_buffer_overflow, shared_test_data) {
.async_run(asio::detached);
asio::steady_timer timer(executor);
timer.expires_after(7s);
timer.expires_after(10s);
timer.async_wait(
[&](error_code) {
c.async_receive([&](

View File

@ -16,6 +16,7 @@
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/log_invoke.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
@ -65,8 +66,8 @@ void run_unit_test(
std::move(h)(ec);
};
detail::log_invoke d;
detail::connect_op<test::test_stream, detail::noop_logger>(
detail::log_invoke<noop_logger> d;
detail::connect_op<test::test_stream, noop_logger>(
stream, mqtt_ctx, d, std::move(handler)
).perform(*std::begin(eps), std::move(ap));

View File

@ -25,6 +25,7 @@
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/logger.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include "test_common/message_exchange.hpp"
#include "test_common/test_service.hpp"
@ -54,12 +55,12 @@ void assign_tls_sni(
void logger_test() {
BOOST_STATIC_ASSERT(detail::has_at_resolve<logger>);
BOOST_STATIC_ASSERT(detail::has_at_tcp_connect<logger>);
BOOST_STATIC_ASSERT(detail::has_at_tls_handshake<logger>);
BOOST_STATIC_ASSERT(detail::has_at_ws_handshake<logger>);
BOOST_STATIC_ASSERT(detail::has_at_connack<logger>);
BOOST_STATIC_ASSERT(detail::has_at_disconnect<logger>);
BOOST_STATIC_ASSERT(has_at_resolve<logger>);
BOOST_STATIC_ASSERT(has_at_tcp_connect<logger>);
BOOST_STATIC_ASSERT(has_at_tls_handshake<logger>);
BOOST_STATIC_ASSERT(has_at_ws_handshake<logger>);
BOOST_STATIC_ASSERT(has_at_connack<logger>);
BOOST_STATIC_ASSERT(has_at_disconnect<logger>);
}
using stream_type = boost::beast::websocket::stream<

View File

@ -14,6 +14,7 @@
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/logger_traits.hpp>
#include <async_mqtt5/detail/log_invoke.hpp>
#include <async_mqtt5/impl/client_service.hpp>
@ -113,7 +114,7 @@ void run_connect_to_localhost_test(int succeed_after) {
);
auto stream_ctx = stream_context(std::monostate {});
auto log = detail::log_invoke();
auto log = detail::log_invoke<noop_logger>();
auto auto_stream = astream(ioc.get_executor(), stream_ctx, log);
auto_stream.brokers("localhost", 1883);
@ -147,7 +148,7 @@ BOOST_AUTO_TEST_CASE(no_servers) {
asio::io_context ioc;
auto stream_ctx = stream_context(std::monostate{});
auto log = detail::log_invoke();
auto log = detail::log_invoke<noop_logger>();
auto auto_stream = astream(ioc.get_executor(), stream_ctx, log);
auto_stream.brokers("", 1883);

View File

@ -692,7 +692,7 @@ BOOST_AUTO_TEST_CASE(test_pingresp) {
BOOST_AUTO_TEST_CASE(subscription_identifiers) {
// check boost::container::small_vector interface
BOOST_TEST_REQUIRE(is_small_vector<prop::subscription_identifiers>);
BOOST_TEST_REQUIRE(detail::is_small_vector<prop::subscription_identifiers>);
// check optional interface
prop::subscription_identifiers sub_ids;