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
6 changes: 0 additions & 6 deletions apps/hello-cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ using launchdarkly::LogLevel;
int main() {
Logger logger(std::make_unique<ConsoleBackend>("Hello"));

if (auto num = launchdarkly::foo()) {
LD_LOG(logger, LogLevel::kInfo) << "Got: " << *num << '\n';
} else {
LD_LOG(logger, LogLevel::kInfo) << "Got nothing\n";
}

net::io_context ioc;

char const* key = std::getenv("STG_SDK_KEY");
Expand Down
8 changes: 0 additions & 8 deletions bindings/c/src/api.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,2 @@
#include "launchdarkly/client_side/api.hpp"
#include <launchdarkly/api.h>

bool launchdarkly_foo(int32_t* out_result) {
if (auto val = launchdarkly::foo()) {
*out_result = *val;
return true;
}
return false;
}
58 changes: 53 additions & 5 deletions libs/client-sdk/include/launchdarkly/client_side/api.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,56 @@
#pragma once

#include "cstdint"
#include "optional"
#include <boost/asio/io_context.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <thread>
#include <tl/expected.hpp>
#include "config/client.hpp"
#include "context.hpp"
#include "error.hpp"
#include "events/event_processor.hpp"
#include "logger.hpp"
#include "value.hpp"

namespace launchdarkly {
std::optional<std::int32_t> foo();
} // namespace launchdarkly
namespace launchdarkly::client_side {
class Client {
public:
Client(client::Config config, Context context);

using FlagKey = std::string;
[[nodiscard]] std::unordered_map<FlagKey, Value> AllFlags() const;

void Track(std::string event_name, Value data, double metric_value);

void Track(std::string event_name, Value data);

void Track(std::string event_name);

void AsyncFlush();
Copy link
Member

Choose a reason for hiding this comment

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

Should we use promises for the async methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Forgot to submit my self-review, which addresses this. I think I'll try using CompletionToken first, so can satisfy all the use-cases.


void AsyncIdentify(Context context);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Presumably we'll want synchronous flush and identify.. these are always problematic. I'm hoping we can pull some asio magic and allow for either using the CompletionTokens.


bool BoolVariation(FlagKey const& key, bool default_value);

std::string StringVariation(FlagKey const& key, std::string default_value);

double DoubleVariation(FlagKey const& key, double default_value);

int IntVariation(FlagKey const& key, int default_value);

Value JsonVariation(FlagKey const& key, Value default_value);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Didn't add any of *Detail methods yet.

Copy link
Member

Choose a reason for hiding this comment

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

After this PR I will update it to add a data source, I've not made the EvaluationDetail<T> wrapper yet, but I could do that at the same time.

private:
void TrackInternal(std::string event_name,
std::optional<Value> data,
std::optional<double> metric_value);

Logger logger_;
std::thread thread_;
boost::asio::io_context ioc_;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lots of design points to think about here related to the io_context. For now, I've just constructed one inside the Client.

I'll pose some questions without adding any of my half-baked answers.

Do we construct the ioc internally, or allow for passing in?
Do we somehow handle this with a "platform" abstraction?
Have the client immediately start upon construction, or with a Run method (inert, until called.)

Probably need to use the SDK in a couple scenarios to really determine these more confidently.

Context context_;
std::unique_ptr<events::IEventProcessor> event_processor_;
};

} // namespace launchdarkly::client_side
79 changes: 72 additions & 7 deletions libs/client-sdk/src/api.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,78 @@
#include "launchdarkly/client_side/api.hpp"

#include <cstdint>
#include <chrono>
#include <optional>
#include <utility>

#include "console_backend.hpp"
#include "events/client_events.hpp"
#include "events/detail/asio_event_processor.hpp"

namespace launchdarkly {
namespace launchdarkly::client_side {

auto const kAnswerToLifeTheUniverseAndEverything = 42;
Client::Client(client::Config config, Context context)
: logger_(config.take_logger()),
context_(std::move(context)),
event_processor_(
std::make_unique<launchdarkly::events::detail::AsioEventProcessor>(
ioc_.get_executor(),
config.events_config(),
config.service_endpoints(),
config.sdk_key(),
logger_)) {}

std::unordered_map<Client::FlagKey, Value> Client::AllFlags() const {
return {};
}

std::optional<std::int32_t> foo() {
return kAnswerToLifeTheUniverseAndEverything;
void Client::TrackInternal(std::string event_name,
std::optional<Value> data,
std::optional<double> metric_value) {
event_processor_->AsyncSend(events::TrackEventParams{
std::chrono::system_clock::now(), std::move(event_name),
context_.kinds_to_keys(), std::move(data), metric_value});
}
} // namespace launchdarkly

void Client::Track(std::string event_name, Value data, double metric_value) {
this->TrackInternal(std::move(event_name), data, metric_value);
}

void Client::Track(std::string event_name, Value data) {
this->TrackInternal(std::move(event_name), data, std::nullopt);
}

void Client::Track(std::string event_name) {
this->TrackInternal(std::move(event_name), std::nullopt, std::nullopt);
}

void Client::AsyncFlush() {
event_processor_->AsyncFlush();
}

void Client::AsyncIdentify(Context context) {
event_processor_->AsyncSend(events::client::IdentifyEventParams{
std::chrono::system_clock::now(), std::move(context)});
}

bool Client::BoolVariation(Client::FlagKey const& key, bool default_value) {
return default_value;
}

std::string Client::StringVariation(Client::FlagKey const& key,
std::string default_value) {
return default_value;
}

double Client::DoubleVariation(Client::FlagKey const& key,
double default_value) {
return default_value;
}

int Client::IntVariation(Client::FlagKey const& key, int default_value) {
return default_value;
}

Value Client::JsonVariation(Client::FlagKey const& key, Value default_value) {
return default_value;
}

} // namespace launchdarkly::client_side
18 changes: 18 additions & 0 deletions libs/client-sdk/tests/client_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <gtest/gtest.h>
#include <launchdarkly/client_side/api.hpp>
#include "context_builder.hpp"
using namespace launchdarkly;

TEST(ClientTest, ConstructClientWithConfig) {
tl::expected<client::Config, Error> config =
client::ConfigBuilder("sdk-123").build();

ASSERT_TRUE(config);

auto context = ContextBuilder().kind("cat", "shadow").build();

client_side::Client client(std::move(*config), context);

ASSERT_TRUE(client.AllFlags().empty());
ASSERT_TRUE(client.BoolVariation("cat-food", true));
}
1 change: 1 addition & 0 deletions libs/common/include/config/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace launchdarkly::client {

using SDK = config::detail::ClientSDK;

using Defaults = config::detail::Defaults<SDK>;
using ApplicationInfo = config::detail::ApplicationInfo;
using Endpoints = config::detail::EndpointsBuilder<SDK>;
using ConfigBuilder = config::detail::ConfigBuilder<SDK>;
Expand Down
40 changes: 31 additions & 9 deletions libs/common/include/config/detail/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "config/detail/endpoints_builder.hpp"
#include "config/detail/events_builder.hpp"
#include "config/detail/http_properties.hpp"
#include "logger.hpp"

namespace launchdarkly::config::detail {

Expand All @@ -14,20 +15,41 @@ namespace launchdarkly::config::detail {
*/
template <typename SDK>
struct Config {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed Config to actually hold the config structs, instead of the builders for the config structs.

I think that makes sense since we can use tl::expected - once a user has constructed a Config successfully, they can be sure it is validated and ready to pass into a Client.

std::string sdk_key;
bool offline;
detail::EndpointsBuilder<SDK> service_endpoints_builder;
std::optional<std::string> application_tag;
detail::EventsBuilder<SDK> events_builder;
DataSourceConfig<SDK> data_source_config;
detail::HttpProperties http_properties;
public:
Config(std::string sdk_key,
bool offline,
detail::EndpointsBuilder<SDK> service_endpoints_builder,
detail::EventsBuilder<SDK> events_builder,
launchdarkly::Logger logger,
ServiceEndpoints endpoints,
Events events,
std::optional<std::string> application_tag,
DataSourceConfig<SDK> data_source_config,
detail::HttpProperties http_properties);

std::string const& sdk_key() const;

ServiceEndpoints const& service_endpoints() const;

Events const& events_config() const;

std::optional<std::string> const& application_tag() const;

DataSourceConfig<SDK> const& data_source_config() const;

HttpProperties const& http_properties() const;

bool offline() const;

Logger take_logger();

private:
std::string sdk_key_;
bool offline_;
launchdarkly::Logger logger_;
ServiceEndpoints service_endpoints_;
std::optional<std::string> application_tag_;
Events events_;
DataSourceConfig<SDK> data_source_config_;
detail::HttpProperties http_properties_;
};

} // namespace launchdarkly::config::detail
7 changes: 4 additions & 3 deletions libs/common/include/config/detail/config_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

#include <optional>
#include <string>
#include <tl/expected.hpp>
#include "config/detail/application_info.hpp"
#include "config/detail/config.hpp"
#include "config/detail/data_source_builder.hpp"
#include "config/detail/endpoints_builder.hpp"
#include "config/detail/events_builder.hpp"
#include "config/detail/http_properties_builder.hpp"

#include "logger.hpp"

namespace launchdarkly::config::detail {
Expand All @@ -22,15 +24,14 @@ class ConfigBuilder {
public:
using EndpointsBuilder = detail::EndpointsBuilder<SDK>;
using EventsBuilder = detail::EventsBuilder<SDK>;
using ConfigType = detail::Config<SDK>;
using ConfigResult = tl::expected<detail::Config<SDK>, Error>;
using DataSourceBuilder = detail::DataSourceBuilder<SDK>;
using HttpPropertiesBuilder = detail::HttpPropertiesBuilder<SDK>;
/**
* A minimal configuration consists of a LaunchDarkly SDK Key.
* @param sdk_key SDK Key.
*/
ConfigBuilder(std::string sdk_key);

/**
* To customize the endpoints the SDK uses for streaming, polling, and
* events, pass in an EndpointsBuilder.
Expand Down Expand Up @@ -83,7 +84,7 @@ class ConfigBuilder {
* Builds a Configuration, suitable for passing into an instance of Client.
* @return
*/
ConfigType build(Logger& logger) const;
ConfigResult build() const;

private:
std::string sdk_key_;
Expand Down
4 changes: 4 additions & 0 deletions libs/common/include/config/detail/defaults.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ struct Defaults<ClientSDK> {
static DataSourceConfig<ClientSDK> data_source_config() {
return {Defaults<AnySDK>::streaming_config(), false, false};
}

static bool offline() { return Defaults<AnySDK>::offline(); }
};

template <>
Expand Down Expand Up @@ -83,6 +85,8 @@ struct Defaults<ServerSDK> {
static DataSourceConfig<ServerSDK> data_source_config() {
return {Defaults<AnySDK>::streaming_config()};
}

static bool offline() { return Defaults<AnySDK>::offline(); }
};

} // namespace launchdarkly::config::detail
2 changes: 2 additions & 0 deletions libs/common/include/config/detail/http_properties.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ class HttpProperties final {
// TODO: Proxy.
};

bool operator==(HttpProperties const& lhs, HttpProperties const& rhs);

} // namespace launchdarkly::config::detail
1 change: 1 addition & 0 deletions libs/common/include/config/server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace launchdarkly::server {

using SDK = config::detail::ServerSDK;

using Defaults = config::detail::Defaults<SDK>;
using ApplicationInfo = config::detail::ApplicationInfo;
using Endpoints = config::detail::EndpointsBuilder<SDK>;
using ConfigBuilder = config::detail::ConfigBuilder<SDK>;
Expand Down
2 changes: 2 additions & 0 deletions libs/common/include/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ enum class Error : std::uint32_t {

kConfig_Events_ZeroCapacity = 300,

kConfig_SDKKey_Empty = 400,

/* Client-side errors: 10000-19999 */
/* Server-side errors: 20000-29999 */

Expand Down
Loading