Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: server-side payload filters #435

Merged
merged 9 commits into from
Sep 4, 2024
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
29 changes: 26 additions & 3 deletions contract-tests/data-model/include/data_model/data_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
#include <optional>
#include <string>
#include <unordered_map>
#include "nlohmann/json.hpp"

Check failure on line 6 in contract-tests/data-model/include/data_model/data_model.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/data-model/include/data_model/data_model.hpp:6:10 [clang-diagnostic-error]

'nlohmann/json.hpp' file not found

namespace nlohmann {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Formatter inserted these newlines. The main changes are in Config[Streaming|Polling]Params adding the new filter key param.


template <typename T>
struct adl_serializer<std::optional<T>> {
static void to_json(json& j, std::optional<T> const& opt) {
Expand Down Expand Up @@ -41,20 +40,26 @@
struct ConfigStreamingParams {
std::optional<std::string> baseUri;
std::optional<uint32_t> initialRetryDelayMs;
std::optional<std::string> filter;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigStreamingParams,
baseUri,
initialRetryDelayMs);
initialRetryDelayMs,
filter);

struct ConfigPollingParams {
std::optional<std::string> baseUri;
std::optional<uint32_t> pollIntervalMs;
std::optional<std::string> filter;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPollingParams,
baseUri,
pollIntervalMs);
pollIntervalMs,
filter);

struct ConfigEventParams {

Check warning on line 62 in contract-tests/data-model/include/data_model/data_model.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/data-model/include/data_model/data_model.hpp:62:8 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: globalPrivateAttributes
std::optional<std::string> baseUri;
std::optional<uint32_t> capacity;
std::optional<bool> enableDiagnostics;
Expand All @@ -62,28 +67,32 @@
std::vector<std::string> globalPrivateAttributes;
std::optional<int> flushIntervalMs;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigEventParams,
baseUri,
capacity,
enableDiagnostics,
allAttributesPrivate,
globalPrivateAttributes,
flushIntervalMs);

struct ConfigServiceEndpointsParams {
std::optional<std::string> streaming;
std::optional<std::string> polling;
std::optional<std::string> events;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigServiceEndpointsParams,
streaming,
polling,
events);

struct ConfigClientSideParams {

Check warning on line 90 in contract-tests/data-model/include/data_model/data_model.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/data-model/include/data_model/data_model.hpp:90:8 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: initialContext
nlohmann::json initialContext;
std::optional<bool> evaluationReasons;
std::optional<bool> useReport;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigClientSideParams,
initialContext,
evaluationReasons,
Expand All @@ -93,6 +102,7 @@
std::optional<std::string> applicationId;
std::optional<std::string> applicationVersion;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigTags,
applicationId,
applicationVersion);
Expand All @@ -109,6 +119,7 @@
std::optional<ConfigTags> tags;
std::optional<ConfigTLSParams> tls;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams,
credential,
startWaitTimeMs,
Expand All @@ -121,7 +132,7 @@
tags,
tls);

struct ContextSingleParams {

Check warning on line 135 in contract-tests/data-model/include/data_model/data_model.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/data-model/include/data_model/data_model.hpp:135:8 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: custom
std::optional<std::string> kind;
std::string key;
std::optional<std::string> name;
Expand All @@ -141,6 +152,7 @@
nlohmann_json_j["private"] = nlohmann_json_t._private;
nlohmann_json_j["custom"] = nlohmann_json_t.custom;
}

inline void from_json(nlohmann::json const& nlohmann_json_j,
ContextSingleParams& nlohmann_json_t) {
ContextSingleParams nlohmann_json_default_obj;
Expand Down Expand Up @@ -184,11 +196,13 @@
ConfigParams configuration;
std::string tag;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(CreateInstanceParams,
configuration,
tag);

enum class ValueType { Bool = 1, Int, Double, String, Any, Unspecified };

NLOHMANN_JSON_SERIALIZE_ENUM(ValueType,
{{ValueType::Bool, "bool"},
{ValueType::Int, "int"},
Expand All @@ -205,6 +219,7 @@
bool detail;
EvaluateFlagParams();
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateFlagParams,
flagKey,
context,
Expand All @@ -212,30 +227,34 @@
defaultValue,
detail);

struct EvaluateFlagResponse {

Check warning on line 230 in contract-tests/data-model/include/data_model/data_model.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/data-model/include/data_model/data_model.hpp:230:8 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: value, reason
nlohmann::json value;
std::optional<uint32_t> variationIndex;
std::optional<nlohmann::json> reason;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateFlagResponse,
value,
variationIndex,
reason);

struct EvaluateAllFlagParams {

Check warning on line 241 in contract-tests/data-model/include/data_model/data_model.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/data-model/include/data_model/data_model.hpp:241:8 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: context
std::optional<nlohmann::json> context;
std::optional<bool> withReasons;
std::optional<bool> clientSideOnly;
std::optional<bool> detailsOnlyForTrackedFlags;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateAllFlagParams,
context,
withReasons,
clientSideOnly,
detailsOnlyForTrackedFlags);

struct EvaluateAllFlagsResponse {

Check warning on line 254 in contract-tests/data-model/include/data_model/data_model.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/data-model/include/data_model/data_model.hpp:254:8 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: state
nlohmann::json state;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateAllFlagsResponse,
state);

Expand All @@ -246,6 +265,7 @@
std::optional<bool> omitNullData;
std::optional<double> metricValue;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(CustomEventParams,
eventKey,
context,
Expand All @@ -253,9 +273,10 @@
omitNullData,
metricValue);

struct IdentifyEventParams {

Check warning on line 276 in contract-tests/data-model/include/data_model/data_model.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/data-model/include/data_model/data_model.hpp:276:8 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: context
nlohmann::json context;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(IdentifyEventParams, context);

enum class Command {
Expand All @@ -268,6 +289,7 @@
ContextBuild,
ContextConvert
};

NLOHMANN_JSON_SERIALIZE_ENUM(Command,
{{Command::Unknown, nullptr},
{Command::EvaluateFlag, "evaluate"},
Expand All @@ -288,6 +310,7 @@
std::optional<ContextConvertParams> contextConvert;
CommandParams();
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(CommandParams,
command,
evaluate,
Expand Down
10 changes: 8 additions & 2 deletions contract-tests/server-contract-tests/src/entity_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "entity_manager.hpp"

Check failure on line 1 in contract-tests/server-contract-tests/src/entity_manager.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/server-contract-tests/src/entity_manager.cpp:1:10 [clang-diagnostic-error]

'entity_manager.hpp' file not found

#include <launchdarkly/context_builder.hpp>
#include <launchdarkly/serialization/json_context.hpp>
Expand Down Expand Up @@ -52,12 +52,15 @@
if (in.streaming->baseUri) {
endpoints.StreamingBaseUrl(*in.streaming->baseUri);
}
auto streaming = decltype(datasystem)::Streaming();
if (in.streaming->initialRetryDelayMs) {
auto streaming = decltype(datasystem)::Streaming();
streaming.InitialReconnectDelay(
std::chrono::milliseconds(*in.streaming->initialRetryDelayMs));
datasystem.Synchronizer(std::move(streaming));
}
if (in.streaming->filter) {
streaming.Filter(*in.streaming->filter);
}
datasystem.Synchronizer(std::move(streaming));
}

if (in.polling) {
Expand All @@ -72,6 +75,9 @@
std::chrono::milliseconds(
*in.polling->pollIntervalMs)));
}
if (in.polling->filter) {
method.Filter(*in.polling->filter);
}
datasystem.Synchronizer(std::move(method));
}
}
Expand Down
7 changes: 4 additions & 3 deletions contract-tests/server-contract-tests/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "server.hpp"

Check failure on line 1 in contract-tests/server-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/server-contract-tests/src/main.cpp:1:10 [clang-diagnostic-error]

'server.hpp' file not found

#include <launchdarkly/logging/console_backend.hpp>

Expand All @@ -18,7 +18,7 @@
using launchdarkly::LogLevel;

int main(int argc, char* argv[]) {
launchdarkly::Logger logger{

Check warning on line 21 in contract-tests/server-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/server-contract-tests/src/main.cpp:21:26 [cppcoreguidelines-init-variables]

variable 'logger' is not initialized
std::make_unique<ConsoleBackend>("server-contract-tests")};

std::string const default_port = "8123";
Expand All @@ -31,8 +31,8 @@
try {
net::io_context ioc{1};

auto p = boost::lexical_cast<unsigned short>(port);
server srv(ioc, "0.0.0.0", p, logger);
auto const p = boost::lexical_cast<unsigned short>(port);

Check warning on line 34 in contract-tests/server-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/server-contract-tests/src/main.cpp:34:20 [readability-identifier-length]

variable name 'p' is too short, expected at least 3 characters
server srv{ioc, "0.0.0.0", p, logger};

Check warning on line 35 in contract-tests/server-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/contract-tests/server-contract-tests/src/main.cpp:35:16 [cppcoreguidelines-init-variables]

variable 'srv' is not initialized

srv.add_capability("server-side");
srv.add_capability("strongly-typed");
Expand All @@ -45,7 +45,8 @@
srv.add_capability("tls:verify-peer");
srv.add_capability("tls:skip-verify-peer");
srv.add_capability("tls:custom-ca");

srv.add_capability("filtering");
srv.add_capability("filtering-strict");
net::signal_set signals{ioc, SIGINT, SIGTERM};

boost::asio::spawn(ioc.get_executor(), [&](auto yield) mutable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@
#include <launchdarkly/config/shared/defaults.hpp>
#include <launchdarkly/config/shared/sdks.hpp>

#include <launchdarkly/error.hpp>

#include <tl/expected.hpp>

#include <chrono>
#include <type_traits>
#include <variant>

namespace launchdarkly::config::shared::builders {

/**
* Used to construct a DataSourceConfiguration for the specified SDK type.
* @tparam SDK ClientSDK or ServerSDK.
*/
template <typename SDK>
class DataSourceBuilder;

template <typename T>
struct is_server_sdk : std::false_type {};

template <>
struct is_server_sdk<ServerSDK> : std::true_type {};

/**
* Builds a configuration for a streaming data source.
*/
Expand All @@ -43,6 +45,30 @@ class StreamingBuilder {
StreamingBuilder& InitialReconnectDelay(
std::chrono::milliseconds initial_reconnect_delay);

/**
* Sets the filter key for the streaming connection.
*
* By default, the SDK is able to evaluate all flags in an environment.
*
* If this is undesirable - for example, because the environment contains
* thousands of flags, but this application only needs to evaluate
* a smaller, known subset - then a filter may be setup in LaunchDarkly,
* and the filter's key specified here.
*
* Evaluations for flags that aren't part of the filtered environment will
* return default values.
* @param filter_key The filter key. If the key is malformed or nonexistent,
* then a full LaunchDarkly environment will be fetched. In the case of a
* malformed key, the SDK will additionally log a runtime error.
* @return Reference to this builder.
*/
template <typename T = SDK>
std::enable_if_t<is_server_sdk<T>::value, StreamingBuilder&> Filter(
std::string filter_key) {
config_.filter_key = std::move(filter_key);
return *this;
}

/**
* Build the streaming config. Used internal to the SDK.
* @return The built config.
Expand All @@ -68,6 +94,31 @@ class PollingBuilder {
*/
PollingBuilder& PollInterval(std::chrono::seconds poll_interval);

/**
* Sets the filter key for the polling connection.
*
* By default, the SDK is able to evaluate all flags in an environment.
*
* If this is undesirable - for example, because the environment contains
* thousands of flags, but this application only needs to evaluate
* a smaller, known subset - then a filter may be setup in LaunchDarkly,
* and the filter's key specified here.
*
* Evaluations for flags that aren't part of the filtered environment will
* return default values.
*
* @param filter_key The filter key. If the key is malformed or nonexistent,
* then a full LaunchDarkly environment will be fetched. In the case of a
* malformed key, the SDK will additionally log a runtime error.
* @return Reference to this builder.
*/
template <typename T = SDK>
std::enable_if_t<is_server_sdk<T>::value, PollingBuilder&> Filter(
std::string filter_key) {
config_.filter_key = std::move(filter_key);
return *this;
}

/**
* Build the polling config. Used internal to the SDK.
* @return The built config.
Expand Down Expand Up @@ -100,7 +151,7 @@ class DataSourceBuilder<ClientSDK> {
DataSourceBuilder& WithReasons(bool value);

/**
* Whether or not to use the REPORT verb to fetch flag settings.
* Whether to use the REPORT verb to fetch flag settings.
*
* If this is true, flag settings will be fetched with a REPORT request
* including a JSON entity body with the context object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ template <>
struct StreamingConfig<ServerSDK> {
std::chrono::milliseconds initial_reconnect_delay;
std::string streaming_path;
std::optional<std::string> filter_key;
};

inline bool operator==(StreamingConfig<ServerSDK> const& lhs,
StreamingConfig<ServerSDK> const& rhs) {
return lhs.initial_reconnect_delay == rhs.initial_reconnect_delay &&
lhs.streaming_path == rhs.streaming_path;
lhs.streaming_path == rhs.streaming_path &&
lhs.filter_key == rhs.filter_key;
}

template <typename SDK>
Expand All @@ -46,6 +48,7 @@ struct PollingConfig<ServerSDK> {
std::chrono::seconds poll_interval;
std::string polling_get_path;
std::chrono::seconds min_polling_interval;
std::optional<std::string> filter_key;
};

template <typename SDK>
Expand Down
Loading
Loading