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
4 changes: 4 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -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' }
4 changes: 3 additions & 1 deletion libs/common/include/config/client.hpp
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -8,7 +9,8 @@ namespace launchdarkly::client {

using SDK = config::detail::ClientSDK;

using EndpointsBuilder = config::detail::EndpointsBuilder<SDK>;
using ApplicationInfo = config::detail::ApplicationInfo;
using Endpoints = config::detail::EndpointsBuilder<SDK>;
using ConfigBuilder = config::detail::ConfigBuilder<SDK>;
using Config = config::detail::Config<SDK>;

Expand Down
36 changes: 36 additions & 0 deletions libs/common/include/config/detail/application_info.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include "logger.hpp"

#include <optional>
#include <string>
#include <tl/expected.hpp>
#include <vector>
#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<std::string> build(Logger& logger) const;

private:
struct Tag {
std::string key;
std::string value;
std::optional<Error> error;
Tag(std::string key, std::string value);
[[nodiscard]] tl::expected<std::string, Error> build() const;
};
std::vector<Tag> tags_;
ApplicationInfo& add_tag(std::string key, std::string value);
};

bool ValidChar(char c);
std::optional<Error> IsValidTag(std::string const& key,
std::string const& value);

} // namespace launchdarkly::config::detail
4 changes: 3 additions & 1 deletion libs/common/include/config/detail/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ struct Config {
std::string sdk_key;
bool offline;
detail::EndpointsBuilder<SDK> service_endpoints_builder;
std::optional<std::string> application_tag;
Config(std::string sdk_key,
bool offline,
detail::EndpointsBuilder<SDK> service_endpoints_builder);
detail::EndpointsBuilder<SDK> service_endpoints_builder,
std::optional<std::string> application_tag);
};

} // namespace launchdarkly::config::detail
13 changes: 12 additions & 1 deletion libs/common/include/config/detail/config_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

#include <optional>
#include <string>
#include "config/detail/application_info.hpp"
#include "config/detail/config.hpp"
#include "config/detail/endpoints_builder.hpp"
#include "logger.hpp"

namespace launchdarkly::config::detail {

Expand Down Expand Up @@ -31,6 +33,14 @@ class ConfigBuilder {
*/
ConfigBuilder& service_endpoints(detail::EndpointsBuilder<SDK> 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.
Expand All @@ -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<bool> offline_;
std::optional<EndpointsBuilder> service_endpoints_builder_;
std::optional<ApplicationInfo> application_info_builder_;
};

} // namespace launchdarkly::config::detail
10 changes: 5 additions & 5 deletions libs/common/include/config/detail/endpoints_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ bool operator==(EndpointsBuilder<SDK> const& lhs,
*/
template <typename SDK>
class EndpointsBuilder {
private:
std::optional<std::string> polling_base_url_;
std::optional<std::string> streaming_base_url_;
std::optional<std::string> events_base_url_;

public:
friend bool operator==<SDK>(EndpointsBuilder<SDK> const& lhs,
EndpointsBuilder<SDK> const& rhs);
Expand Down Expand Up @@ -72,6 +67,11 @@ class EndpointsBuilder {
* @return Unique pointer to ServiceEndpoints, or nullptr.
*/
[[nodiscard]] tl::expected<ServiceEndpoints, Error> build();

private:
std::optional<std::string> polling_base_url_;
std::optional<std::string> streaming_base_url_;
std::optional<std::string> events_base_url_;
};

} // namespace launchdarkly::config::detail
10 changes: 5 additions & 5 deletions libs/common/include/config/detail/service_endpoints.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion libs/common/include/config/server.hpp
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -8,7 +9,8 @@ namespace launchdarkly::server {

using SDK = config::detail::ServerSDK;

using EndpointsBuilder = config::detail::EndpointsBuilder<SDK>;
using ApplicationInfo = config::detail::ApplicationInfo;
using Endpoints = config::detail::EndpointsBuilder<SDK>;
using ConfigBuilder = config::detail::ConfigBuilder<SDK>;
using Config = config::detail::Config<SDK>;

Expand Down
20 changes: 15 additions & 5 deletions libs/common/include/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@

#include <cstdint>
#include <limits>
#include <ostream>

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<std::uint32_t>::max()
};

char const* ErrorToString(Error err);
std::ostream& operator<<(std::ostream& os, Error const& err);

} // namespace launchdarkly
2 changes: 2 additions & 0 deletions libs/common/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)


Expand Down
96 changes: 96 additions & 0 deletions libs/common/src/config/application_info.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include "config/detail/application_info.hpp"

#include <boost/algorithm/string.hpp>

#include <algorithm>
#include <cctype>

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<std::string, Error> 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<Error> 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;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will update once the tl::expected PR goes through.

}
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<std::string> 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<std::pair<std::string, tl::expected<std::string, Error>>>
unvalidated(tags_.size());

std::transform(
tags_.cbegin(), tags_.cend(), unvalidated.begin(),
[](auto tag) { return std::make_pair(tag.key, tag.build()); });

std::vector<std::string> 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
6 changes: 4 additions & 2 deletions libs/common/src/config/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ namespace launchdarkly::config::detail {
template <typename SDK>
Config<SDK>::Config(std::string sdk_key,
bool offline,
detail::EndpointsBuilder<SDK> service_endpoints_builder)
detail::EndpointsBuilder<SDK> service_endpoints_builder,
std::optional<std::string> 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<detail::ClientSDK>;
template class Config<detail::ServerSDK>;
Expand Down
22 changes: 18 additions & 4 deletions libs/common/src/config/config_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,31 @@ ConfigBuilder<SDK>& ConfigBuilder<SDK>::service_endpoints(
return *this;
}

template <typename SDK>
ConfigBuilder<SDK>& ConfigBuilder<SDK>::application_info(
detail::ApplicationInfo builder) {
application_info_builder_ = std::move(builder);
return *this;
}

template <typename SDK>
ConfigBuilder<SDK>& ConfigBuilder<SDK>::offline(bool offline) {
offline_ = offline;
return *this;
}

template <typename SDK>
typename ConfigBuilder<SDK>::ConfigType ConfigBuilder<SDK>::build() const {
return {sdk_key_, offline_.value_or(Defaults<detail::AnySDK>::offline()),
service_endpoints_builder_.value_or(
ConfigBuilder<SDK>::EndpointsBuilder())};
typename ConfigBuilder<SDK>::ConfigType ConfigBuilder<SDK>::build(
Logger& logger) const {
auto key = sdk_key_;
auto offline = offline_.value_or(Defaults<detail::AnySDK>::offline());
auto endpoints = service_endpoints_builder_.value_or(
ConfigBuilder<SDK>::EndpointsBuilder());
std::optional<std::string> 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<detail::ClientSDK>;
Expand Down
Loading