diff --git a/cmake/expected.cmake b/cmake/expected.cmake new file mode 100644 index 000000000..7e3b1d620 --- /dev/null +++ b/cmake/expected.cmake @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.11) + +include(FetchContent) + +FetchContent_Declare(tl-expected + GIT_REPOSITORY https://github.com/TartanLlama/expected.git + GIT_TAG 292eff8bd8ee230a7df1d6a1c00c4ea0eb2f0362 + ) + +FetchContent_MakeAvailable(tl-expected) diff --git a/libs/common/CMakeLists.txt b/libs/common/CMakeLists.txt index 290a64fe5..04dfabd7d 100644 --- a/libs/common/CMakeLists.txt +++ b/libs/common/CMakeLists.txt @@ -30,6 +30,8 @@ set(Boost_USE_STATIC_RUNTIME OFF) find_package(Boost 1.80 REQUIRED) message(STATUS "LaunchDarkly: using Boost v${Boost_VERSION}") +include(${CMAKE_FILES}/expected.cmake) + # Add main SDK sources. add_subdirectory(src) diff --git a/libs/common/include/config/detail/endpoints_builder.hpp b/libs/common/include/config/detail/endpoints_builder.hpp index 1eadcd888..a1bb0a1de 100644 --- a/libs/common/include/config/detail/endpoints_builder.hpp +++ b/libs/common/include/config/detail/endpoints_builder.hpp @@ -1,6 +1,9 @@ #pragma once -#include "service_endpoints.hpp" +#include "config/detail/service_endpoints.hpp" +#include "error.hpp" + +#include #include #include @@ -68,7 +71,7 @@ class EndpointsBuilder { * returns nullptr. * @return Unique pointer to ServiceEndpoints, or nullptr. */ - [[nodiscard]] std::unique_ptr build(); + [[nodiscard]] tl::expected build(); }; } // namespace launchdarkly::config::detail diff --git a/libs/common/include/error.hpp b/libs/common/include/error.hpp new file mode 100644 index 000000000..1ebe46328 --- /dev/null +++ b/libs/common/include/error.hpp @@ -0,0 +1,20 @@ +#pragma once + +#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 */ + + kMax = std::numeric_limits::max() +}; + +} // namespace launchdarkly diff --git a/libs/common/src/CMakeLists.txt b/libs/common/src/CMakeLists.txt index 321dce69c..9862254da 100644 --- a/libs/common/src/CMakeLists.txt +++ b/libs/common/src/CMakeLists.txt @@ -25,7 +25,7 @@ add_library(${LIBNAME} add_library(launchdarkly::common ALIAS ${LIBNAME}) -target_link_libraries(${LIBNAME} Boost::headers) +target_link_libraries(${LIBNAME} Boost::headers tl::expected) # Need the public headers to build. target_include_directories(${LIBNAME} PUBLIC ../include) diff --git a/libs/common/src/config/endpoints_builder.cpp b/libs/common/src/config/endpoints_builder.cpp index cbd897791..0197a90f7 100644 --- a/libs/common/src/config/endpoints_builder.cpp +++ b/libs/common/src/config/endpoints_builder.cpp @@ -44,17 +44,16 @@ bool empty_string(std::optional const& opt_string) { } template -std::unique_ptr EndpointsBuilder::build() { +tl::expected EndpointsBuilder::build() { // Empty URLs are not allowed. if (empty_string(polling_base_url_) || empty_string(streaming_base_url_) || empty_string(events_base_url_)) { - return nullptr; + return tl::unexpected(Error::kConfig_Endpoints_EmptyURL); } // If no URLs were set, return the default endpoints for this SDK. if (!polling_base_url_ && !streaming_base_url_ && !events_base_url_) { - return std::make_unique( - detail::Defaults::endpoints()); + return detail::Defaults::endpoints(); } // If all URLs were set, trim any trailing slashes and construct custom @@ -63,14 +62,13 @@ std::unique_ptr EndpointsBuilder::build() { auto trim_trailing_slashes = [](std::string const& url) -> std::string { return trim_right_matches(url, '/'); }; - return std::make_unique( - trim_trailing_slashes(*polling_base_url_), - trim_trailing_slashes(*streaming_base_url_), - trim_trailing_slashes(*events_base_url_)); + return ServiceEndpoints(trim_trailing_slashes(*polling_base_url_), + trim_trailing_slashes(*streaming_base_url_), + trim_trailing_slashes(*events_base_url_)); } // Otherwise if a subset of URLs were set, this is an error. - return nullptr; + return tl::unexpected(Error::kConfig_Endpoints_AllURLsMustBeSet); } template diff --git a/libs/common/tests/service_endpoints_test.cpp b/libs/common/tests/service_endpoints_test.cpp index 0875865ae..676728791 100644 --- a/libs/common/tests/service_endpoints_test.cpp +++ b/libs/common/tests/service_endpoints_test.cpp @@ -2,6 +2,7 @@ #include "config/client.hpp" #include "config/server.hpp" +#include "error.hpp" class ServiceEndpointTest : public testing::Test {}; @@ -9,6 +10,8 @@ using ClientEndpointsBuilder = launchdarkly::client::EndpointsBuilder; using ServerEndpointsBuilder = launchdarkly::server::EndpointsBuilder; +using launchdarkly::Error; + TEST(ServiceEndpointTest, DefaultClientBuilderURLs) { ClientEndpointsBuilder builder; auto eps = builder.build(); @@ -28,28 +31,22 @@ TEST(ServiceEndpointTest, DefaultServerBuilderURLs) { ASSERT_EQ(eps->events_base_url(), "https://events.launchdarkly.com"); } -TEST(ServiceEndpointTest, ModifySingleURLCausesError_Polling) { - ClientEndpointsBuilder builder; - builder.polling_base_url("foo"); - ASSERT_FALSE(builder.build()); -} +TEST(ServiceEndpointTest, ModifySingleURLCausesError) { + auto result = ClientEndpointsBuilder().polling_base_url("foo").build(); + ASSERT_FALSE(result); + ASSERT_EQ(result.error(), Error::kConfig_Endpoints_AllURLsMustBeSet); -TEST(ServiceEndpointTest, ModifySingleURLCausesError_Streaming) { - ClientEndpointsBuilder builder; - builder.streaming_base_url("foo"); - ASSERT_FALSE(builder.build()); -} + result = ClientEndpointsBuilder().streaming_base_url("foo").build(); + ASSERT_FALSE(result); + ASSERT_EQ(result.error(), Error::kConfig_Endpoints_AllURLsMustBeSet); -TEST(ServiceEndpointTest, ModifySingleURLCausesError_Events) { - ClientEndpointsBuilder builder; - builder.events_base_url("foo"); - ASSERT_FALSE(builder.build()); + result = ClientEndpointsBuilder().events_base_url("foo").build(); + ASSERT_FALSE(result); + ASSERT_EQ(result.error(), Error::kConfig_Endpoints_AllURLsMustBeSet); } TEST(ServiceEndpointsTest, RelaySetsAllURLS) { - ClientEndpointsBuilder builder; - builder.relay_proxy("foo"); - auto eps = builder.build(); + auto eps = ClientEndpointsBuilder().relay_proxy("foo").build(); ASSERT_TRUE(eps); ASSERT_EQ(eps->streaming_base_url(), "foo"); ASSERT_EQ(eps->polling_base_url(), "foo"); @@ -58,52 +55,50 @@ TEST(ServiceEndpointsTest, RelaySetsAllURLS) { TEST(ServiceEndpointsTest, TrimsTrailingSlashes) { { - ClientEndpointsBuilder builder; - builder.relay_proxy("foo/"); - auto eps = builder.build(); + auto eps = ClientEndpointsBuilder().relay_proxy("foo/").build(); ASSERT_TRUE(eps); ASSERT_EQ(eps->streaming_base_url(), "foo"); } { - ClientEndpointsBuilder builder; - builder.relay_proxy("foo////////"); - auto eps = builder.build(); + auto eps = ClientEndpointsBuilder().relay_proxy("foo////////").build(); ASSERT_TRUE(eps); ASSERT_EQ(eps->streaming_base_url(), "foo"); } { - ClientEndpointsBuilder builder; - builder.relay_proxy("/"); - auto eps = builder.build(); + auto eps = ClientEndpointsBuilder().relay_proxy("/").build(); ASSERT_TRUE(eps); ASSERT_EQ(eps->streaming_base_url(), ""); } } TEST(ServiceEndpointsTest, EmptyURLsAreInvalid) { - { - ClientEndpointsBuilder builder; - builder.relay_proxy(""); - auto eps = builder.build(); - ASSERT_FALSE(eps); - } - { - ClientEndpointsBuilder builder; - auto eps = builder.streaming_base_url("") - .events_base_url("foo") - .polling_base_url("bar") - .build(); - ASSERT_FALSE(eps); - } + auto result = ClientEndpointsBuilder().relay_proxy("").build(); + ASSERT_FALSE(result); + ASSERT_EQ(result.error(), Error::kConfig_Endpoints_EmptyURL); - { - ClientEndpointsBuilder builder; - auto eps = builder.streaming_base_url("foo") - .events_base_url("bar") - .polling_base_url("") - .build(); - ASSERT_FALSE(eps); - } + result = ClientEndpointsBuilder() + .streaming_base_url("") + .events_base_url("foo") + .polling_base_url("bar") + .build(); + ASSERT_FALSE(result); + ASSERT_EQ(result.error(), Error::kConfig_Endpoints_EmptyURL); + + result = ClientEndpointsBuilder() + .streaming_base_url("foo") + .events_base_url("") + .polling_base_url("bar") + .build(); + ASSERT_FALSE(result); + ASSERT_EQ(result.error(), Error::kConfig_Endpoints_EmptyURL); + + result = ClientEndpointsBuilder() + .streaming_base_url("foo") + .events_base_url("bar") + .polling_base_url("") + .build(); + ASSERT_FALSE(result); + ASSERT_EQ(result.error(), Error::kConfig_Endpoints_EmptyURL); }