Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cmake/expected.cmake
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions libs/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
7 changes: 5 additions & 2 deletions libs/common/include/config/detail/endpoints_builder.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#pragma once

#include "service_endpoints.hpp"
#include "config/detail/service_endpoints.hpp"
#include "error.hpp"

#include <tl/expected.hpp>

#include <memory>
#include <optional>
Expand Down Expand Up @@ -68,7 +71,7 @@ class EndpointsBuilder {
* returns nullptr.
* @return Unique pointer to ServiceEndpoints, or nullptr.
*/
[[nodiscard]] std::unique_ptr<ServiceEndpoints> build();
[[nodiscard]] tl::expected<ServiceEndpoints, Error> build();
};

} // namespace launchdarkly::config::detail
20 changes: 20 additions & 0 deletions libs/common/include/error.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include <cstdint>
#include <limits>

namespace launchdarkly {

enum class Error : std::uint32_t {
KReserved1 = 0,
KReserved2 = 1,
/* Common lib errors: 2-99 */
Copy link
Contributor Author

@cwaldren-ld cwaldren-ld Mar 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case we decide to do:

  • some kind of casting to LDBoolean (0/1)
  • Generic success
  • Generic failure

I've just reserved the first two values.

kConfig_Endpoints_EmptyURL = 2,
kConfig_Endpoints_AllURLsMustBeSet = 3,
/* Client-side errors: 100-199 */
/* Server-side errors: 200-299 */

kMax = std::numeric_limits<std::uint32_t>::max()
Copy link
Contributor Author

@cwaldren-ld cwaldren-ld Mar 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kMax is just here to make us remember to do something similar in C, to help with ABI stability as we add new values in the future.

};

} // namespace launchdarkly
2 changes: 1 addition & 1 deletion libs/common/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 7 additions & 9 deletions libs/common/src/config/endpoints_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,16 @@ bool empty_string(std::optional<std::string> const& opt_string) {
}

template <typename SDK>
std::unique_ptr<ServiceEndpoints> EndpointsBuilder<SDK>::build() {
tl::expected<ServiceEndpoints, Error> EndpointsBuilder<SDK>::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<ServiceEndpoints>(
detail::Defaults<SDK>::endpoints());
return detail::Defaults<SDK>::endpoints();
}

// If all URLs were set, trim any trailing slashes and construct custom
Expand All @@ -63,14 +62,13 @@ std::unique_ptr<ServiceEndpoints> EndpointsBuilder<SDK>::build() {
auto trim_trailing_slashes = [](std::string const& url) -> std::string {
return trim_right_matches(url, '/');
};
return std::make_unique<ServiceEndpoints>(
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 <typename SDK>
Expand Down
91 changes: 43 additions & 48 deletions libs/common/tests/service_endpoints_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

#include "config/client.hpp"
#include "config/server.hpp"
#include "error.hpp"

class ServiceEndpointTest : public testing::Test {};

using ClientEndpointsBuilder = launchdarkly::client::EndpointsBuilder;

using ServerEndpointsBuilder = launchdarkly::server::EndpointsBuilder;

using launchdarkly::Error;

TEST(ServiceEndpointTest, DefaultClientBuilderURLs) {
ClientEndpointsBuilder builder;
auto eps = builder.build();
Expand All @@ -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");
Expand All @@ -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);
}