diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..f464d0864 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,4 @@ +--- +CheckOptions: + - { key: readability-identifier-length.IgnoredParameterNames, value: 'i|j|k|c|os|it' } + - { key: readability-identifier-length.IgnoredLoopCounterNames, value: 'i|j|k|c|os|it' } diff --git a/libs/common/include/config/client.hpp b/libs/common/include/config/client.hpp index ec411e645..9af977c40 100644 --- a/libs/common/include/config/client.hpp +++ b/libs/common/include/config/client.hpp @@ -1,5 +1,6 @@ #pragma once +#include "config/detail/application_info.hpp" #include "config/detail/config_builder.hpp" #include "config/detail/endpoints_builder.hpp" #include "config/detail/sdks.hpp" @@ -8,7 +9,8 @@ namespace launchdarkly::client { using SDK = config::detail::ClientSDK; -using EndpointsBuilder = config::detail::EndpointsBuilder; +using ApplicationInfo = config::detail::ApplicationInfo; +using Endpoints = config::detail::EndpointsBuilder; using ConfigBuilder = config::detail::ConfigBuilder; using Config = config::detail::Config; diff --git a/libs/common/include/config/detail/application_info.hpp b/libs/common/include/config/detail/application_info.hpp new file mode 100644 index 000000000..e8c44565f --- /dev/null +++ b/libs/common/include/config/detail/application_info.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "logger.hpp" + +#include +#include +#include +#include +#include "error.hpp" + +namespace launchdarkly::config::detail { + +class ApplicationInfo { + public: + ApplicationInfo() = default; + ApplicationInfo& app_identifier(std::string app_id); + ApplicationInfo& app_version(std::string version); + [[nodiscard]] std::optional build(Logger& logger) const; + + private: + struct Tag { + std::string key; + std::string value; + std::optional error; + Tag(std::string key, std::string value); + [[nodiscard]] tl::expected build() const; + }; + std::vector tags_; + ApplicationInfo& add_tag(std::string key, std::string value); +}; + +bool ValidChar(char c); +std::optional IsValidTag(std::string const& key, + std::string const& value); + +} // namespace launchdarkly::config::detail diff --git a/libs/common/include/config/detail/config.hpp b/libs/common/include/config/detail/config.hpp index 0ac67d7a7..775abfe89 100644 --- a/libs/common/include/config/detail/config.hpp +++ b/libs/common/include/config/detail/config.hpp @@ -14,9 +14,11 @@ struct Config { std::string sdk_key; bool offline; detail::EndpointsBuilder service_endpoints_builder; + std::optional application_tag; Config(std::string sdk_key, bool offline, - detail::EndpointsBuilder service_endpoints_builder); + detail::EndpointsBuilder service_endpoints_builder, + std::optional application_tag); }; } // namespace launchdarkly::config::detail diff --git a/libs/common/include/config/detail/config_builder.hpp b/libs/common/include/config/detail/config_builder.hpp index d3f6326be..2f89ccde0 100644 --- a/libs/common/include/config/detail/config_builder.hpp +++ b/libs/common/include/config/detail/config_builder.hpp @@ -2,8 +2,10 @@ #include #include +#include "config/detail/application_info.hpp" #include "config/detail/config.hpp" #include "config/detail/endpoints_builder.hpp" +#include "logger.hpp" namespace launchdarkly::config::detail { @@ -31,6 +33,14 @@ class ConfigBuilder { */ ConfigBuilder& service_endpoints(detail::EndpointsBuilder builder); + /** + * To include metadata about the application that is utilizing the SDK, + * pass in an ApplicationInfo builder. + * @param builder An ApplicationInfo builder. + * @return Reference to this builder. + */ + ConfigBuilder& application_info(detail::ApplicationInfo builder); + /** * To enable or disable "offline" mode, pass a boolean value. True means * offline mode is enabled. @@ -43,12 +53,13 @@ class ConfigBuilder { * Builds a Configuration, suitable for passing into an instance of Client. * @return */ - ConfigType build() const; + ConfigType build(Logger& logger) const; private: std::string sdk_key_; std::optional offline_; std::optional service_endpoints_builder_; + std::optional application_info_builder_; }; } // namespace launchdarkly::config::detail diff --git a/libs/common/include/config/detail/endpoints_builder.hpp b/libs/common/include/config/detail/endpoints_builder.hpp index a1bb0a1de..e9305623c 100644 --- a/libs/common/include/config/detail/endpoints_builder.hpp +++ b/libs/common/include/config/detail/endpoints_builder.hpp @@ -25,11 +25,6 @@ bool operator==(EndpointsBuilder const& lhs, */ template class EndpointsBuilder { - private: - std::optional polling_base_url_; - std::optional streaming_base_url_; - std::optional events_base_url_; - public: friend bool operator==(EndpointsBuilder const& lhs, EndpointsBuilder const& rhs); @@ -72,6 +67,11 @@ class EndpointsBuilder { * @return Unique pointer to ServiceEndpoints, or nullptr. */ [[nodiscard]] tl::expected build(); + + private: + std::optional polling_base_url_; + std::optional streaming_base_url_; + std::optional events_base_url_; }; } // namespace launchdarkly::config::detail diff --git a/libs/common/include/config/detail/service_endpoints.hpp b/libs/common/include/config/detail/service_endpoints.hpp index 174e36a45..95112dced 100644 --- a/libs/common/include/config/detail/service_endpoints.hpp +++ b/libs/common/include/config/detail/service_endpoints.hpp @@ -9,11 +9,6 @@ namespace launchdarkly::config { * service or a Relay Proxy instance. */ class ServiceEndpoints { - private: - std::string polling_base_url_; - std::string streaming_base_url_; - std::string events_base_url_; - public: /** * Constructs a ServiceEndpoints from individual polling, streaming, and @@ -45,6 +40,11 @@ class ServiceEndpoints { * @return Base events URL. */ [[nodiscard]] std::string const& events_base_url() const; + + private: + std::string polling_base_url_; + std::string streaming_base_url_; + std::string events_base_url_; }; bool operator==(ServiceEndpoints const& lhs, ServiceEndpoints const& rhs); diff --git a/libs/common/include/config/server.hpp b/libs/common/include/config/server.hpp index 3a9a1b59d..f879c8de6 100644 --- a/libs/common/include/config/server.hpp +++ b/libs/common/include/config/server.hpp @@ -1,5 +1,6 @@ #pragma once +#include "config/detail/application_info.hpp" #include "config/detail/config_builder.hpp" #include "config/detail/endpoints_builder.hpp" #include "config/detail/sdks.hpp" @@ -8,7 +9,8 @@ namespace launchdarkly::server { using SDK = config::detail::ServerSDK; -using EndpointsBuilder = config::detail::EndpointsBuilder; +using ApplicationInfo = config::detail::ApplicationInfo; +using Endpoints = config::detail::EndpointsBuilder; using ConfigBuilder = config::detail::ConfigBuilder; using Config = config::detail::Config; diff --git a/libs/common/include/error.hpp b/libs/common/include/error.hpp index 1ebe46328..3a0bc2db3 100644 --- a/libs/common/include/error.hpp +++ b/libs/common/include/error.hpp @@ -2,19 +2,29 @@ #include #include +#include namespace launchdarkly { enum class Error : std::uint32_t { KReserved1 = 0, KReserved2 = 1, - /* Common lib errors: 2-99 */ - kConfig_Endpoints_EmptyURL = 2, - kConfig_Endpoints_AllURLsMustBeSet = 3, - /* Client-side errors: 100-199 */ - /* Server-side errors: 200-299 */ + /* Common lib errors: 2-9999 */ + kConfig_Endpoints_EmptyURL = 100, + kConfig_Endpoints_AllURLsMustBeSet = 101, + + kConfig_ApplicationInfo_EmptyKeyOrValue = 200, + kConfig_ApplicationInfo_ValueTooLong = 201, + kConfig_ApplicationInfo_InvalidKeyCharacters = 202, + kConfig_ApplicationInfo_InvalidValueCharacters = 203, + + /* Client-side errors: 10000-19999 */ + /* Server-side errors: 20000-29999 */ kMax = std::numeric_limits::max() }; +char const* ErrorToString(Error err); +std::ostream& operator<<(std::ostream& os, Error const& err); + } // namespace launchdarkly diff --git a/libs/common/src/CMakeLists.txt b/libs/common/src/CMakeLists.txt index 9862254da..16ab71d11 100644 --- a/libs/common/src/CMakeLists.txt +++ b/libs/common/src/CMakeLists.txt @@ -16,10 +16,12 @@ add_library(${LIBNAME} attributes.cpp value.cpp attributes_builder.cpp + error.cpp config/service_endpoints.cpp config/endpoints_builder.cpp config/config_builder.cpp config/config.cpp + config/application_info.cpp ) diff --git a/libs/common/src/config/application_info.cpp b/libs/common/src/config/application_info.cpp new file mode 100644 index 000000000..03e2d10f6 --- /dev/null +++ b/libs/common/src/config/application_info.cpp @@ -0,0 +1,96 @@ +#include "config/detail/application_info.hpp" + +#include + +#include +#include + +namespace launchdarkly::config::detail { + +// Defines the maximum character length for an Application Tag value. +constexpr std::size_t kMaxTagValueLength = 64; + +ApplicationInfo::Tag::Tag(std::string key, std::string value) + : key(std::move(key)), value(std::move(value)) {} + +tl::expected ApplicationInfo::Tag::build() const { + if (auto err = IsValidTag(key, value)) { + return tl::unexpected(*err); + } + return key + '/' + value; +} + +bool ValidChar(char c) { + return std::isalnum(c) != 0 || c == '-' || c == '.' || c == '_'; +} + +std::optional IsValidTag(std::string const& key, + std::string const& value) { + if (value.empty() || key.empty()) { + return Error::kConfig_ApplicationInfo_EmptyKeyOrValue; + } + if (value.length() > kMaxTagValueLength) { + return Error::kConfig_ApplicationInfo_ValueTooLong; + } + if (!std::all_of(key.begin(), key.end(), ValidChar)) { + return Error::kConfig_ApplicationInfo_InvalidKeyCharacters; + } + if (!std::all_of(value.begin(), value.end(), ValidChar)) { + return Error::kConfig_ApplicationInfo_InvalidValueCharacters; + } + return std::nullopt; +} + +ApplicationInfo& ApplicationInfo::add_tag(std::string key, std::string value) { + tags_.emplace_back(std::move(key), std::move(value)); + return *this; +} +ApplicationInfo& ApplicationInfo::app_identifier(std::string app_id) { + return add_tag("application-id", std::move(app_id)); +} + +ApplicationInfo& ApplicationInfo::app_version(std::string version) { + return add_tag("application-version", std::move(version)); +} + +std::optional ApplicationInfo::build(Logger& logger) const { + if (tags_.empty()) { + LD_LOG(logger, LogLevel::kDebug) << "no application tags configured"; + return std::nullopt; + } + + // Build all the tags, which results in pairs of (tag key, value | error). + std::vector>> + unvalidated(tags_.size()); + + std::transform( + tags_.cbegin(), tags_.cend(), unvalidated.begin(), + [](auto tag) { return std::make_pair(tag.key, tag.build()); }); + + std::vector validated; + + for (auto const& tag : unvalidated) { + if (!tag.second) { + LD_LOG(logger, LogLevel::kWarn) + << tag.second.error() << " for tag '" << tag.first << "'"; + } else { + validated.push_back(tag.second.value()); + } + } + + if (validated.empty()) { + return std::nullopt; + } + + // Sort so the result is deterministic. + std::sort(validated.begin(), validated.end()); + + // Remove any duplicate tags. + validated.erase(std::unique(validated.begin(), validated.end()), + validated.end()); + + // Concatenate with space as the delimiter. + return boost::algorithm::join(validated, " "); +} + +} // namespace launchdarkly::config::detail diff --git a/libs/common/src/config/config.cpp b/libs/common/src/config/config.cpp index a06cad886..b37add192 100644 --- a/libs/common/src/config/config.cpp +++ b/libs/common/src/config/config.cpp @@ -5,10 +5,12 @@ namespace launchdarkly::config::detail { template Config::Config(std::string sdk_key, bool offline, - detail::EndpointsBuilder service_endpoints_builder) + detail::EndpointsBuilder service_endpoints_builder, + std::optional application_tag) : sdk_key(std::move(sdk_key)), offline(offline), - service_endpoints_builder(std::move(service_endpoints_builder)) {} + service_endpoints_builder(std::move(service_endpoints_builder)), + application_tag(std::move(application_tag)) {} template class Config; template class Config; diff --git a/libs/common/src/config/config_builder.cpp b/libs/common/src/config/config_builder.cpp index 7f154cdfb..301599c88 100644 --- a/libs/common/src/config/config_builder.cpp +++ b/libs/common/src/config/config_builder.cpp @@ -15,6 +15,13 @@ ConfigBuilder& ConfigBuilder::service_endpoints( return *this; } +template +ConfigBuilder& ConfigBuilder::application_info( + detail::ApplicationInfo builder) { + application_info_builder_ = std::move(builder); + return *this; +} + template ConfigBuilder& ConfigBuilder::offline(bool offline) { offline_ = offline; @@ -22,10 +29,17 @@ ConfigBuilder& ConfigBuilder::offline(bool offline) { } template -typename ConfigBuilder::ConfigType ConfigBuilder::build() const { - return {sdk_key_, offline_.value_or(Defaults::offline()), - service_endpoints_builder_.value_or( - ConfigBuilder::EndpointsBuilder())}; +typename ConfigBuilder::ConfigType ConfigBuilder::build( + Logger& logger) const { + auto key = sdk_key_; + auto offline = offline_.value_or(Defaults::offline()); + auto endpoints = service_endpoints_builder_.value_or( + ConfigBuilder::EndpointsBuilder()); + std::optional app_tag; + if (application_info_builder_) { + app_tag = application_info_builder_->build(logger); + } + return {std::move(key), offline, std::move(endpoints), std::move(app_tag)}; } template class ConfigBuilder; diff --git a/libs/common/src/error.cpp b/libs/common/src/error.cpp new file mode 100644 index 000000000..901e85cac --- /dev/null +++ b/libs/common/src/error.cpp @@ -0,0 +1,34 @@ +#include "error.hpp" + +namespace launchdarkly { + +std::ostream& operator<<(std::ostream& os, Error const& err) { + os << ErrorToString(err); + return os; +} + +char const* ErrorToString(Error err) { + switch (err) { + case Error::KReserved1: + return "reserved1"; + case Error::KReserved2: + return "reserved2"; + case Error::kConfig_Endpoints_EmptyURL: + return "endpoints: cannot specify empty URL"; + case Error::kConfig_Endpoints_AllURLsMustBeSet: + return "endpoints: if any endpoint is specified, then all " + "endpoints must be specified"; + case Error::kConfig_ApplicationInfo_EmptyKeyOrValue: + return "application info: cannot specify an empty key or value"; + case Error::kConfig_ApplicationInfo_ValueTooLong: + return "application info: the specified value is too long"; + case Error::kConfig_ApplicationInfo_InvalidKeyCharacters: + return "application info: the key contains invalid characters"; + case Error::kConfig_ApplicationInfo_InvalidValueCharacters: + return "application info: the value contains invalid characters"; + case Error::kMax: + break; + } + return "unknown"; +} +} // namespace launchdarkly diff --git a/libs/common/tests/CMakeLists.txt b/libs/common/tests/CMakeLists.txt index e83e84c70..58ba79553 100644 --- a/libs/common/tests/CMakeLists.txt +++ b/libs/common/tests/CMakeLists.txt @@ -8,6 +8,7 @@ file(GLOB tests "${PROJECT_SOURCE_DIR}/tests/*.cpp") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_executable(gtest_${LIBNAME} + null_logger.cpp ${tests}) target_link_libraries(gtest_${LIBNAME} launchdarkly::common GTest::gtest_main) diff --git a/libs/common/tests/application_tags_test.cpp b/libs/common/tests/application_tags_test.cpp new file mode 100644 index 000000000..8c8381091 --- /dev/null +++ b/libs/common/tests/application_tags_test.cpp @@ -0,0 +1,112 @@ + +#include +#include +#include +#include "config/detail/application_info.hpp" +#include "error.hpp" +#include "null_logger.hpp" + +using namespace launchdarkly::config::detail; +using launchdarkly::Error; +using launchdarkly::Logger; +using launchdarkly::LogLevel; + +struct TagValidity { + std::string key; + std::string value; + std::optional error; +}; + +class TagValidityFixture : public ::testing::TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P( + AppTagsTest, + TagValidityFixture, + testing::Values( + TagValidity{"", "abc", Error::kConfig_ApplicationInfo_EmptyKeyOrValue}, + TagValidity{" ", "abc", + Error::kConfig_ApplicationInfo_InvalidKeyCharacters}, + TagValidity{"/", "abc", + Error::kConfig_ApplicationInfo_InvalidKeyCharacters}, + TagValidity{":", "abc", + Error::kConfig_ApplicationInfo_InvalidKeyCharacters}, + TagValidity{"abcABC123.-_", "abc", std::nullopt}, + TagValidity{"abc", "", Error::kConfig_ApplicationInfo_EmptyKeyOrValue}, + TagValidity{"abc", " ", + Error::kConfig_ApplicationInfo_InvalidValueCharacters}, + TagValidity{"abc", "/", + Error::kConfig_ApplicationInfo_InvalidValueCharacters}, + TagValidity{"abc", ":", + Error::kConfig_ApplicationInfo_InvalidValueCharacters}, + TagValidity{"abc", "abcABC123.-_", std::nullopt}, + TagValidity{ + "abc", + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl", + std::nullopt, + }, + TagValidity{ + "abc", + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij" + "klm", // > 64 chars + Error::kConfig_ApplicationInfo_ValueTooLong})); + +TEST_P(TagValidityFixture, ValidTagValues) { + auto param = GetParam(); + std::optional maybe_error = + launchdarkly::config::detail::IsValidTag(param.key, param.value); + ASSERT_EQ(maybe_error, param.error); +} + +struct TagBuild { + std::optional app_id; + std::optional app_version; + std::optional concat; + std::string name; +}; + +class TagBuildFixture : public ::testing::TestWithParam {}; + +auto const kInvalidTag = "!*@!#"; + +INSTANTIATE_TEST_SUITE_P( + AppTagsTest, + TagBuildFixture, + testing::Values( + TagBuild{std::nullopt, std::nullopt, std::nullopt, + "no tags means no output"}, + TagBuild{"microservice-a", "1.0", + "application-id/microservice-a application-version/1.0", + "multiple tags are ordered correctly"}, + TagBuild{"microservice-a", std::nullopt, + "application-id/microservice-a", + "single app id tag is output"}, + TagBuild{std::nullopt, "1.0", "application-version/1.0", + "single app version tag is output"}, + TagBuild{kInvalidTag, std::nullopt, std::nullopt, + "invalid tag is not output"}, + TagBuild{kInvalidTag, kInvalidTag, std::nullopt, + "two invalid tags are not output"}, + TagBuild{"microservice-a", kInvalidTag, "application-id/microservice-a", + "presence of invalid tag does not prevent valid tag from " + "being output"}), + [](testing::TestParamInfo const& info) { + std::string name = info.param.name; + std::replace_if( + name.begin(), name.end(), [](char c) { return c == ' '; }, '_'); + return "test" + std::to_string(info.index) + "_" + name; + }); + +TEST_P(TagBuildFixture, BuiltTags) { + auto params = GetParam(); + + auto logger = NullLogger(); + + ApplicationInfo info; + if (params.app_id) { + info.app_identifier(*params.app_id); + } + if (params.app_version) { + info.app_version(*params.app_version); + } + ASSERT_EQ(info.build(logger), params.concat); +} diff --git a/libs/common/tests/config_builder_test.cpp b/libs/common/tests/config_builder_test.cpp index 0061e79d3..6f2081e0a 100644 --- a/libs/common/tests/config_builder_test.cpp +++ b/libs/common/tests/config_builder_test.cpp @@ -2,47 +2,60 @@ #include "config/client.hpp" #include "config/server.hpp" +#include "null_logger.hpp" -class ConfigBuilderTest : public testing::Test {}; +class ConfigBuilderTest + : public ::testing:: + Test { // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) + protected: + launchdarkly::Logger logger; + ConfigBuilderTest() : logger(NullLogger()) {} +}; -TEST(ConfigBuilderTest, DefaultConstruction_ClientConfig) { +TEST_F(ConfigBuilderTest, DefaultConstruction_ClientConfig) { using namespace launchdarkly::client; ConfigBuilder builder("sdk-123"); - Config cfg = builder.build(); + Config cfg = builder.build(logger); ASSERT_EQ(cfg.sdk_key, "sdk-123"); } -TEST(ConfigBuilderTest, DefaultConstruction_ServerConfig) { +TEST_F(ConfigBuilderTest, DefaultConstruction_ServerConfig) { using namespace launchdarkly::server; ConfigBuilder builder("sdk-123"); - Config cfg = builder.build(); + Config cfg = builder.build(logger); ASSERT_EQ(cfg.sdk_key, "sdk-123"); } -TEST(ConfigBuilderTest, DefaultConstruction_UsesDefaultEndpointsIfNotSupplied) { +TEST_F(ConfigBuilderTest, + DefaultConstruction_UsesDefaultEndpointsIfNotSupplied) { using namespace launchdarkly::client; ConfigBuilder builder("sdk-123"); - Config cfg = builder.build(); + Config cfg = builder.build(logger); ASSERT_EQ(cfg.service_endpoints_builder, ConfigBuilder::EndpointsBuilder()); } -TEST(ConfigBuilderTest, - DefaultConstruction_UsesDefaultOfflineModeIfNotSupplied) { +TEST_F(ConfigBuilderTest, + DefaultConstruction_UsesDefaultOfflineModeIfNotSupplied) { using namespace launchdarkly::client; ConfigBuilder builder("sdk-123"); - Config cfg = builder.build(); + Config cfg = builder.build(logger); ASSERT_FALSE(cfg.offline); } -TEST(ConfigBuilderTest, CustomBuilderReflectsChanges) { +// This test should exercise all of the config options. +TEST_F(ConfigBuilderTest, CustomBuilderReflectsChanges) { using namespace launchdarkly::client; - auto config = ConfigBuilder("sdk-123") - .offline(true) - .service_endpoints(EndpointsBuilder().relay_proxy("foo")) - .build(); + auto config = + ConfigBuilder("sdk-123") + .offline(true) + .service_endpoints(Endpoints().relay_proxy("foo")) + .application_info( + ApplicationInfo().app_identifier("bar").app_version("baz")) + .build(logger); ASSERT_EQ(config.sdk_key, "sdk-123"); ASSERT_TRUE(config.offline); - ASSERT_EQ(config.service_endpoints_builder, - EndpointsBuilder().relay_proxy("foo")); + ASSERT_EQ(config.service_endpoints_builder, Endpoints().relay_proxy("foo")); + ASSERT_EQ(config.application_tag, + "application-id/bar application-version/baz"); } diff --git a/libs/common/tests/null_logger.cpp b/libs/common/tests/null_logger.cpp new file mode 100644 index 000000000..18976417f --- /dev/null +++ b/libs/common/tests/null_logger.cpp @@ -0,0 +1,14 @@ +#include "null_logger.hpp" + +using launchdarkly::Logger; +using launchdarkly::LogLevel; + +bool NullLoggerBackend::enabled(LogLevel level) { + return false; +} + +void NullLoggerBackend::write(LogLevel level, std::string message) {} + +Logger NullLogger() { + return {std::make_unique()}; +} diff --git a/libs/common/tests/null_logger.hpp b/libs/common/tests/null_logger.hpp new file mode 100644 index 000000000..890b6ab72 --- /dev/null +++ b/libs/common/tests/null_logger.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "logger.hpp" + +/** + * Creates a throwaway logger suitable for tests where assertions about the + * log messages aren't relevant. + * + * @return Logger suitable for passing to functions accepting a + * launchdarkly::Logger. + */ +launchdarkly::Logger NullLogger(); +class NullLoggerBackend : public launchdarkly::ILogBackend { + public: + /** + * Always returns false. + */ + bool enabled(launchdarkly::LogLevel level) override; + + /** + * No-op. + */ + void write(launchdarkly::LogLevel level, std::string message) override; +}; diff --git a/libs/common/tests/service_endpoints_test.cpp b/libs/common/tests/service_endpoints_test.cpp index 676728791..bc49b27c6 100644 --- a/libs/common/tests/service_endpoints_test.cpp +++ b/libs/common/tests/service_endpoints_test.cpp @@ -6,9 +6,9 @@ class ServiceEndpointTest : public testing::Test {}; -using ClientEndpointsBuilder = launchdarkly::client::EndpointsBuilder; +using ClientEndpointsBuilder = launchdarkly::client::Endpoints; -using ServerEndpointsBuilder = launchdarkly::server::EndpointsBuilder; +using ServerEndpointsBuilder = launchdarkly::server::Endpoints; using launchdarkly::Error;