From 607024a1028eecc75efd7bb53616c0369c93b9f3 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 16 Aug 2023 16:43:06 -0700 Subject: [PATCH 01/55] add header --- libs/server-sdk/src/data_store/dependency_tracker.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/server-sdk/src/data_store/dependency_tracker.hpp b/libs/server-sdk/src/data_store/dependency_tracker.hpp index 13e32df7f..d62ba2c7c 100644 --- a/libs/server-sdk/src/data_store/dependency_tracker.hpp +++ b/libs/server-sdk/src/data_store/dependency_tracker.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include From f49d534c7f13a4fdf1e7c5a2dd40e9df18db675c Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 16 Aug 2023 16:49:00 -0700 Subject: [PATCH 02/55] add another missing --- libs/server-sdk/src/data_store/persistent/expiration_tracker.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/server-sdk/src/data_store/persistent/expiration_tracker.hpp b/libs/server-sdk/src/data_store/persistent/expiration_tracker.hpp index bc57398c0..219b660f8 100644 --- a/libs/server-sdk/src/data_store/persistent/expiration_tracker.hpp +++ b/libs/server-sdk/src/data_store/persistent/expiration_tracker.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include From 53c8c546e1fddb6eec131ea47143954999e0d873 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 20 Jul 2023 16:57:30 -0700 Subject: [PATCH 03/55] rename sdk-contract-tests to client-contract-tests --- .github/workflows/client.yml | 4 ++-- contract-tests/CMakeLists.txt | 2 +- .../CMakeLists.txt | 0 .../{sdk-contract-tests => client-contract-tests}/README.md | 0 .../include/client_entity.hpp | 0 .../include/definitions.hpp | 0 .../include/entity_manager.hpp | 0 .../include/server.hpp | 0 .../include/session.hpp | 0 .../src/client_entity.cpp | 0 .../src/definitions.cpp | 0 .../src/entity_manager.cpp | 0 .../src/main.cpp | 2 +- .../src/server.cpp | 0 .../src/session.cpp | 0 .../test-suppressions.txt | 0 16 files changed, 4 insertions(+), 4 deletions(-) rename contract-tests/{sdk-contract-tests => client-contract-tests}/CMakeLists.txt (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/README.md (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/include/client_entity.hpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/include/definitions.hpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/include/entity_manager.hpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/include/server.hpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/include/session.hpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/src/client_entity.cpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/src/definitions.cpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/src/entity_manager.cpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/src/main.cpp (96%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/src/server.cpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/src/session.cpp (100%) rename contract-tests/{sdk-contract-tests => client-contract-tests}/test-suppressions.txt (100%) diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index 1b116027c..d63cf5fe1 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -16,7 +16,7 @@ jobs: env: # Port the test service (implemented in this repo) should bind to. TEST_SERVICE_PORT: 8123 - TEST_SERVICE_BINARY: ./build/contract-tests/sdk-contract-tests/sdk-tests + TEST_SERVICE_BINARY: ./build/contract-tests/client-contract-tests/sdk-tests steps: - uses: actions/checkout@v3 - uses: ./.github/actions/ci @@ -29,7 +29,7 @@ jobs: with: # Inform the test harness of test service's port. test_service_port: ${{ env.TEST_SERVICE_PORT }} - extra_params: '-skip-from ./contract-tests/sdk-contract-tests/test-suppressions.txt' + extra_params: '-skip-from ./contract-tests/client-contract-tests/test-suppressions.txt' build-test-client: runs-on: ubuntu-22.04 steps: diff --git a/contract-tests/CMakeLists.txt b/contract-tests/CMakeLists.txt index cf6a9dd44..c0d218a59 100644 --- a/contract-tests/CMakeLists.txt +++ b/contract-tests/CMakeLists.txt @@ -1,2 +1,2 @@ add_subdirectory(sse-contract-tests) -add_subdirectory(sdk-contract-tests) +add_subdirectory(client-contract-tests) diff --git a/contract-tests/sdk-contract-tests/CMakeLists.txt b/contract-tests/client-contract-tests/CMakeLists.txt similarity index 100% rename from contract-tests/sdk-contract-tests/CMakeLists.txt rename to contract-tests/client-contract-tests/CMakeLists.txt diff --git a/contract-tests/sdk-contract-tests/README.md b/contract-tests/client-contract-tests/README.md similarity index 100% rename from contract-tests/sdk-contract-tests/README.md rename to contract-tests/client-contract-tests/README.md diff --git a/contract-tests/sdk-contract-tests/include/client_entity.hpp b/contract-tests/client-contract-tests/include/client_entity.hpp similarity index 100% rename from contract-tests/sdk-contract-tests/include/client_entity.hpp rename to contract-tests/client-contract-tests/include/client_entity.hpp diff --git a/contract-tests/sdk-contract-tests/include/definitions.hpp b/contract-tests/client-contract-tests/include/definitions.hpp similarity index 100% rename from contract-tests/sdk-contract-tests/include/definitions.hpp rename to contract-tests/client-contract-tests/include/definitions.hpp diff --git a/contract-tests/sdk-contract-tests/include/entity_manager.hpp b/contract-tests/client-contract-tests/include/entity_manager.hpp similarity index 100% rename from contract-tests/sdk-contract-tests/include/entity_manager.hpp rename to contract-tests/client-contract-tests/include/entity_manager.hpp diff --git a/contract-tests/sdk-contract-tests/include/server.hpp b/contract-tests/client-contract-tests/include/server.hpp similarity index 100% rename from contract-tests/sdk-contract-tests/include/server.hpp rename to contract-tests/client-contract-tests/include/server.hpp diff --git a/contract-tests/sdk-contract-tests/include/session.hpp b/contract-tests/client-contract-tests/include/session.hpp similarity index 100% rename from contract-tests/sdk-contract-tests/include/session.hpp rename to contract-tests/client-contract-tests/include/session.hpp diff --git a/contract-tests/sdk-contract-tests/src/client_entity.cpp b/contract-tests/client-contract-tests/src/client_entity.cpp similarity index 100% rename from contract-tests/sdk-contract-tests/src/client_entity.cpp rename to contract-tests/client-contract-tests/src/client_entity.cpp diff --git a/contract-tests/sdk-contract-tests/src/definitions.cpp b/contract-tests/client-contract-tests/src/definitions.cpp similarity index 100% rename from contract-tests/sdk-contract-tests/src/definitions.cpp rename to contract-tests/client-contract-tests/src/definitions.cpp diff --git a/contract-tests/sdk-contract-tests/src/entity_manager.cpp b/contract-tests/client-contract-tests/src/entity_manager.cpp similarity index 100% rename from contract-tests/sdk-contract-tests/src/entity_manager.cpp rename to contract-tests/client-contract-tests/src/entity_manager.cpp diff --git a/contract-tests/sdk-contract-tests/src/main.cpp b/contract-tests/client-contract-tests/src/main.cpp similarity index 96% rename from contract-tests/sdk-contract-tests/src/main.cpp rename to contract-tests/client-contract-tests/src/main.cpp index c886b34b8..78d2b3afe 100644 --- a/contract-tests/sdk-contract-tests/src/main.cpp +++ b/contract-tests/client-contract-tests/src/main.cpp @@ -19,7 +19,7 @@ using launchdarkly::LogLevel; int main(int argc, char* argv[]) { launchdarkly::Logger logger{ - std::make_unique("sdk-contract-tests")}; + std::make_unique("client-contract-tests")}; const std::string default_port = "8123"; std::string port = default_port; diff --git a/contract-tests/sdk-contract-tests/src/server.cpp b/contract-tests/client-contract-tests/src/server.cpp similarity index 100% rename from contract-tests/sdk-contract-tests/src/server.cpp rename to contract-tests/client-contract-tests/src/server.cpp diff --git a/contract-tests/sdk-contract-tests/src/session.cpp b/contract-tests/client-contract-tests/src/session.cpp similarity index 100% rename from contract-tests/sdk-contract-tests/src/session.cpp rename to contract-tests/client-contract-tests/src/session.cpp diff --git a/contract-tests/sdk-contract-tests/test-suppressions.txt b/contract-tests/client-contract-tests/test-suppressions.txt similarity index 100% rename from contract-tests/sdk-contract-tests/test-suppressions.txt rename to contract-tests/client-contract-tests/test-suppressions.txt From d14350bf05ddc0f8fd16dea7a4a3923fb6b2276a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 20 Jul 2023 17:08:02 -0700 Subject: [PATCH 04/55] copy client-side contract test server to server-side --- contract-tests/CMakeLists.txt | 1 + .../client-contract-tests/CMakeLists.txt | 10 +- .../server-contract-tests/CMakeLists.txt | 30 ++ .../server-contract-tests/README.md | 35 ++ .../include/client_entity.hpp | 36 ++ .../include/definitions.hpp | 281 ++++++++++++++ .../include/entity_manager.hpp | 53 +++ .../server-contract-tests/include/server.hpp | 50 +++ .../server-contract-tests/include/session.hpp | 95 +++++ .../src/client_entity.cpp | 348 ++++++++++++++++++ .../server-contract-tests/src/definitions.cpp | 6 + .../src/entity_manager.cpp | 173 +++++++++ .../server-contract-tests/src/main.cpp | 66 ++++ .../server-contract-tests/src/server.cpp | 34 ++ .../server-contract-tests/src/session.cpp | 142 +++++++ .../test-suppressions.txt | 1 + 16 files changed, 1356 insertions(+), 5 deletions(-) create mode 100644 contract-tests/server-contract-tests/CMakeLists.txt create mode 100644 contract-tests/server-contract-tests/README.md create mode 100644 contract-tests/server-contract-tests/include/client_entity.hpp create mode 100644 contract-tests/server-contract-tests/include/definitions.hpp create mode 100644 contract-tests/server-contract-tests/include/entity_manager.hpp create mode 100644 contract-tests/server-contract-tests/include/server.hpp create mode 100644 contract-tests/server-contract-tests/include/session.hpp create mode 100644 contract-tests/server-contract-tests/src/client_entity.cpp create mode 100644 contract-tests/server-contract-tests/src/definitions.cpp create mode 100644 contract-tests/server-contract-tests/src/entity_manager.cpp create mode 100644 contract-tests/server-contract-tests/src/main.cpp create mode 100644 contract-tests/server-contract-tests/src/server.cpp create mode 100644 contract-tests/server-contract-tests/src/session.cpp create mode 100644 contract-tests/server-contract-tests/test-suppressions.txt diff --git a/contract-tests/CMakeLists.txt b/contract-tests/CMakeLists.txt index c0d218a59..c3fba4f1a 100644 --- a/contract-tests/CMakeLists.txt +++ b/contract-tests/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(sse-contract-tests) add_subdirectory(client-contract-tests) +add_subdirectory(server-contract-tests) diff --git a/contract-tests/client-contract-tests/CMakeLists.txt b/contract-tests/client-contract-tests/CMakeLists.txt index 9e1b0af2a..fb3ea46ae 100644 --- a/contract-tests/client-contract-tests/CMakeLists.txt +++ b/contract-tests/client-contract-tests/CMakeLists.txt @@ -2,15 +2,15 @@ cmake_minimum_required(VERSION 3.19) project( - LaunchDarklyCPPSDKTestHarness + LaunchDarklyCPPClientSDKTestHarness VERSION 0.1 - DESCRIPTION "LaunchDarkly CPP SDK Test Harness" + DESCRIPTION "LaunchDarkly CPP Client-side SDK Test Harness" LANGUAGES CXX ) include(${CMAKE_FILES}/json.cmake) -add_executable(sdk-tests +add_executable(client-tests src/main.cpp src/server.cpp src/session.cpp @@ -19,7 +19,7 @@ add_executable(sdk-tests src/client_entity.cpp ) -target_link_libraries(sdk-tests PRIVATE +target_link_libraries(client-tests PRIVATE launchdarkly::client launchdarkly::internal foxy @@ -27,4 +27,4 @@ target_link_libraries(sdk-tests PRIVATE Boost::coroutine ) -target_include_directories(sdk-tests PUBLIC include) +target_include_directories(client-tests PUBLIC include) diff --git a/contract-tests/server-contract-tests/CMakeLists.txt b/contract-tests/server-contract-tests/CMakeLists.txt new file mode 100644 index 000000000..c9412fb0e --- /dev/null +++ b/contract-tests/server-contract-tests/CMakeLists.txt @@ -0,0 +1,30 @@ +# Required for Apple Silicon support. +cmake_minimum_required(VERSION 3.19) + +project( + LaunchDarklyCPPServerSDKTestHarness + VERSION 0.1 + DESCRIPTION "LaunchDarkly CPP Server-side SDK Test Harness" + LANGUAGES CXX +) + +include(${CMAKE_FILES}/json.cmake) + +add_executable(server-tests + src/main.cpp + src/server.cpp + src/session.cpp + src/definitions.cpp + src/entity_manager.cpp + src/client_entity.cpp + ) + +target_link_libraries(server-tests PRIVATE + launchdarkly::server + launchdarkly::internal + foxy + nlohmann_json::nlohmann_json + Boost::coroutine + ) + +target_include_directories(server-tests PUBLIC include) diff --git a/contract-tests/server-contract-tests/README.md b/contract-tests/server-contract-tests/README.md new file mode 100644 index 000000000..d59cbd039 --- /dev/null +++ b/contract-tests/server-contract-tests/README.md @@ -0,0 +1,35 @@ +## SDK contract tests + +Contract tests have a "test service" on one side, and the "test harness" on +the other. + +This project implements the test service for the C++ Server-side SDK. + +**session (session.hpp)** + +This provides a simple REST API for creating/destroying +test entities. Examples: + +`GET /` - returns the capabilities of this service. + +`DELETE /` - shutdown the service. + +`POST /` - create a new test entity, and return its ID. + +`DELETE /entity/1` - delete the an entity identified by `1`. + +**entity manager (entity_manager.hpp)** + +This manages "entities", which are unique instances of the SDK client. + +**definitions (definitions.hpp)** + +Contains JSON definitions that are used to communicate with the test harness. + +**server (server.hpp)** + +Glues everything together, mainly providing the TCP acceptor that spawns new sessions. + +**session (session.hpp)** + +Prepares HTTP responses based on the results of commands sent to entities. diff --git a/contract-tests/server-contract-tests/include/client_entity.hpp b/contract-tests/server-contract-tests/include/client_entity.hpp new file mode 100644 index 000000000..d5f8c1b90 --- /dev/null +++ b/contract-tests/server-contract-tests/include/client_entity.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include "definitions.hpp" + +class ClientEntity { + public: + explicit ClientEntity( + std::unique_ptr client); + + tl::expected Command(CommandParams params); + + private: + tl::expected Evaluate( + EvaluateFlagParams const&); + + tl::expected EvaluateDetail( + EvaluateFlagParams const&); + + tl::expected EvaluateAll( + EvaluateAllFlagParams const&); + + tl::expected Identify( + IdentifyEventParams const&); + + tl::expected Custom(CustomEventParams const&); + + std::unique_ptr client_; +}; + +static tl::expected ContextConvert( + ContextConvertParams const&); + +static tl::expected ContextBuild( + ContextBuildParams const&); diff --git a/contract-tests/server-contract-tests/include/definitions.hpp b/contract-tests/server-contract-tests/include/definitions.hpp new file mode 100644 index 000000000..7c08d367c --- /dev/null +++ b/contract-tests/server-contract-tests/include/definitions.hpp @@ -0,0 +1,281 @@ +#pragma once + +#include +#include +#include +#include "nlohmann/json.hpp" + +namespace nlohmann { + +template +struct adl_serializer> { + static void to_json(json& j, std::optional const& opt) { + if (opt == std::nullopt) { + j = nullptr; + } else { + j = *opt; // this will call adl_serializer::to_json which will + // find the free function to_json in T's namespace! + } + } + + static void from_json(json const& j, std::optional& opt) { + if (j.is_null()) { + opt = std::nullopt; + } else { + opt = j.get(); // same as above, but with + // adl_serializer::from_json + } + } +}; +} // namespace nlohmann + +struct ConfigStreamingParams { + std::optional baseUri; + std::optional initialRetryDelayMs; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigStreamingParams, + baseUri, + initialRetryDelayMs); + +struct ConfigPollingParams { + std::optional baseUri; + std::optional pollIntervalMs; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPollingParams, + baseUri, + pollIntervalMs); + +struct ConfigEventParams { + std::optional baseUri; + std::optional capacity; + std::optional enableDiagnostics; + std::optional allAttributesPrivate; + std::vector globalPrivateAttributes; + std::optional flushIntervalMs; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigEventParams, + baseUri, + capacity, + enableDiagnostics, + allAttributesPrivate, + globalPrivateAttributes, + flushIntervalMs); +struct ConfigServiceEndpointsParams { + std::optional streaming; + std::optional polling; + std::optional events; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigServiceEndpointsParams, + streaming, + polling, + events); + +struct ConfigClientSideParams { + nlohmann::json initialContext; + std::optional evaluationReasons; + std::optional useReport; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigClientSideParams, + initialContext, + evaluationReasons, + useReport); + +struct ConfigTags { + std::optional applicationId; + std::optional applicationVersion; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigTags, + applicationId, + applicationVersion); + +struct ConfigParams { + std::string credential; + std::optional startWaitTimeMs; + std::optional initCanFail; + std::optional streaming; + std::optional polling; + std::optional events; + std::optional serviceEndpoints; + std::optional clientSide; + std::optional tags; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams, + credential, + startWaitTimeMs, + initCanFail, + streaming, + polling, + events, + serviceEndpoints, + clientSide, + tags); + +struct ContextSingleParams { + std::optional kind; + std::string key; + std::optional name; + std::optional anonymous; + std::optional> _private; + std::optional> custom; +}; + +// These are defined manually because of the 'private' field, which is a +// reserved keyword in C++. +inline void to_json(nlohmann::json& nlohmann_json_j, + ContextSingleParams const& nlohmann_json_t) { + nlohmann_json_j["kind"] = nlohmann_json_t.kind; + nlohmann_json_j["key"] = nlohmann_json_t.key; + nlohmann_json_j["name"] = nlohmann_json_t.name; + nlohmann_json_j["anonymous"] = nlohmann_json_t.anonymous; + 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; + nlohmann_json_t.kind = + nlohmann_json_j.value("kind", nlohmann_json_default_obj.kind); + nlohmann_json_t.key = + nlohmann_json_j.value("key", nlohmann_json_default_obj.key); + nlohmann_json_t.name = + nlohmann_json_j.value("name", nlohmann_json_default_obj.name); + nlohmann_json_t.anonymous = + nlohmann_json_j.value("anonymous", nlohmann_json_default_obj.anonymous); + nlohmann_json_t._private = + nlohmann_json_j.value("private", nlohmann_json_default_obj._private); + nlohmann_json_t.custom = + nlohmann_json_j.value("custom", nlohmann_json_default_obj.custom); +} + +struct ContextBuildParams { + std::optional single; + std::optional> multi; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ContextBuildParams, + single, + multi); + +struct ContextConvertParams { + std::string input; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ContextConvertParams, input); + +struct ContextResponse { + std::optional output; + std::optional error; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ContextResponse, output, error); + +struct CreateInstanceParams { + 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"}, + {ValueType::Double, "double"}, + {ValueType::String, "string"}, + {ValueType::Any, "any"}, + {ValueType::Unspecified, ""}}) + +struct EvaluateFlagParams { + std::string flagKey; + ValueType valueType; + nlohmann::json defaultValue; + bool detail; + EvaluateFlagParams(); +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateFlagParams, + flagKey, + valueType, + defaultValue, + detail); + +struct EvaluateFlagResponse { + nlohmann::json value; + std::optional variationIndex; + std::optional reason; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateFlagResponse, + value, + variationIndex, + reason); + +struct EvaluateAllFlagParams { + std::optional withReasons; + std::optional clientSideOnly; + std::optional detailsOnlyForTrackedFlags; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateAllFlagParams, + withReasons, + clientSideOnly, + detailsOnlyForTrackedFlags); +struct EvaluateAllFlagsResponse { + nlohmann::json state; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateAllFlagsResponse, + state); + +struct CustomEventParams { + std::string eventKey; + std::optional data; + std::optional omitNullData; + std::optional metricValue; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(CustomEventParams, + eventKey, + data, + omitNullData, + metricValue); + +struct IdentifyEventParams { + nlohmann::json context; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(IdentifyEventParams, context); + +enum class Command { + Unknown = -1, + EvaluateFlag, + EvaluateAllFlags, + IdentifyEvent, + CustomEvent, + FlushEvents, + ContextBuild, + ContextConvert +}; +NLOHMANN_JSON_SERIALIZE_ENUM(Command, + {{Command::Unknown, nullptr}, + {Command::EvaluateFlag, "evaluate"}, + {Command::EvaluateAllFlags, "evaluateAll"}, + {Command::IdentifyEvent, "identifyEvent"}, + {Command::CustomEvent, "customEvent"}, + {Command::FlushEvents, "flushEvents"}, + {Command::ContextBuild, "contextBuild"}, + {Command::ContextConvert, "contextConvert"}}); + +struct CommandParams { + Command command; + std::optional evaluate; + std::optional evaluateAll; + std::optional customEvent; + std::optional identifyEvent; + std::optional contextBuild; + std::optional contextConvert; + CommandParams(); +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(CommandParams, + command, + evaluate, + evaluateAll, + customEvent, + identifyEvent, + contextBuild, + contextConvert); diff --git a/contract-tests/server-contract-tests/include/entity_manager.hpp b/contract-tests/server-contract-tests/include/entity_manager.hpp new file mode 100644 index 000000000..ad2b89f33 --- /dev/null +++ b/contract-tests/server-contract-tests/include/entity_manager.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include +#include + +#include "client_entity.hpp" +#include "definitions.hpp" + +#include +#include +#include +#include +#include + +class EventOutbox; + +class EntityManager { + std::unordered_map entities_; + + std::size_t counter_; + boost::asio::any_io_executor executor_; + + launchdarkly::Logger& logger_; + + public: + /** + * Create an entity manager, which can be used to create and destroy + * entities (SSE clients + event channel back to test harness). + * @param executor Executor. + * @param logger Logger. + */ + EntityManager(boost::asio::any_io_executor executor, + launchdarkly::Logger& logger); + /** + * Create an entity with the given configuration. + * @param params Config of the entity. + * @return An ID representing the entity, or none if the entity couldn't + * be created. + */ + std::optional create(ConfigParams const& params); + /** + * Destroy an entity with the given ID. + * @param id ID of the entity. + * @return True if the entity was found and destroyed. + */ + bool destroy(std::string const& id); + + tl::expected command( + std::string const& id, + CommandParams const& params); +}; diff --git a/contract-tests/server-contract-tests/include/server.hpp b/contract-tests/server-contract-tests/include/server.hpp new file mode 100644 index 000000000..655321f04 --- /dev/null +++ b/contract-tests/server-contract-tests/include/server.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "entity_manager.hpp" + +#include + +#include +#include +#include +#include + +#include + +#include + +namespace net = boost::asio; // from + +using tcp = boost::asio::ip::tcp; // from + +class server { + EntityManager manager_; + launchdarkly::foxy::listener listener_; + std::vector caps_; + launchdarkly::Logger& logger_; + + public: + /** + * Constructs a server, which stands up a REST API at the given + * port and address. The server is ready to accept connections upon + * construction. + * @param ioc IO context. + * @param address Address to bind. + * @param port Port to bind. + * @param logger Logger. + */ + server(net::io_context& ioc, + std::string const& address, + unsigned short port, + launchdarkly::Logger& logger); + /** + * Advertise an optional test-harness capability, such as "comments". + * @param cap + */ + void add_capability(std::string cap); + + /** + * Shuts down the server. + */ + void shutdown(); +}; diff --git a/contract-tests/server-contract-tests/include/session.hpp b/contract-tests/server-contract-tests/include/session.hpp new file mode 100644 index 000000000..029a559e0 --- /dev/null +++ b/contract-tests/server-contract-tests/include/session.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include + +#include "entity_manager.hpp" + +#include +#include +#include +#include + +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace net = boost::asio; // from +using tcp = boost::asio::ip::tcp; // from + +class Session : boost::asio::coroutine { + public: + using Request = http::request; + using Response = http::response; + + struct Frame { + Request request_; + Response resp_; + }; + + /** + * Constructs a session, which provides a REST API. + * @param session The HTTP session. + * @param manager Manager through which entities can be created/destroyed. + * @param caps Test service capabilities to advertise. + * @param logger Logger. + */ + Session(launchdarkly::foxy::server_session& session, + EntityManager& manager, + std::vector& caps, + launchdarkly::Logger& logger); + + template + auto operator()(Self& self, + boost::system::error_code ec = {}, + std::size_t const bytes_transferred = 0) -> void { + using launchdarkly::LogLevel; + auto& f = *frame_; + + reenter(*this) { + while (true) { + f.resp_ = {}; + f.request_ = {}; + + yield session_.async_read(f.request_, std::move(self)); + if (ec) { + LD_LOG(logger_, LogLevel::kWarn) + << "session: read: " << ec.what(); + break; + } + + if (auto response = generate_response(f.request_)) { + f.resp_ = *response; + } else { + LD_LOG(logger_, LogLevel::kWarn) + << "session: shutdown requested by client"; + std::exit(0); + } + + yield session_.async_write(f.resp_, std::move(self)); + + if (ec) { + LD_LOG(logger_, LogLevel::kWarn) + << "session: write: " << ec.what(); + break; + } + + if (!f.request_.keep_alive()) { + break; + } + } + + return self.complete({}, 0); + } + } + + std::optional generate_response(Request& req); + + private: + launchdarkly::foxy::server_session& session_; + EntityManager& manager_; + std::unique_ptr frame_; + std::vector& caps_; + launchdarkly::Logger& logger_; +}; + +#include diff --git a/contract-tests/server-contract-tests/src/client_entity.cpp b/contract-tests/server-contract-tests/src/client_entity.cpp new file mode 100644 index 000000000..be27a8c42 --- /dev/null +++ b/contract-tests/server-contract-tests/src/client_entity.cpp @@ -0,0 +1,348 @@ +#include "client_entity.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +ClientEntity::ClientEntity( + std::unique_ptr client) + : client_(std::move(client)) {} + +tl::expected ClientEntity::Identify( + IdentifyEventParams const& params) { + boost::system::error_code ec; + auto json_value = boost::json::parse(params.context.dump(), ec); + if (ec) { + return tl::make_unexpected(ec.what()); + } + + auto maybe_ctx = boost::json::value_to< + tl::expected>( + json_value); + + if (!maybe_ctx) { + return tl::make_unexpected( + launchdarkly::ErrorToString(maybe_ctx.error())); + } + + if (!maybe_ctx->Valid()) { + return tl::make_unexpected(maybe_ctx->errors()); + } + + // The typical way to identify in contract tests would be .wait(), to ensure + // identify completes fully before proceeding. Some of the contract tests + // send invalid data to the data source though, so identify will never + // complete because the data source can't start. So, limit the amount of + // time such that: 1) For most tests, this is *basically* synchronous 2) For + // the problematic tests, this eventually does unblock and let the test + // proceed. + // TODO: SC-204250 + client_->IdentifyAsync(*maybe_ctx).wait_for(std::chrono::seconds(5)); + return nlohmann::json{}; +} + +static void BuildContextFromParams(launchdarkly::ContextBuilder& builder, + ContextSingleParams const& single) { + auto& attrs = builder.Kind(single.kind.value_or("user"), single.key); + if (single.anonymous) { + attrs.Anonymous(*single.anonymous); + } + if (single.name) { + attrs.Name(*single.name); + } + + if (single._private) { + attrs.AddPrivateAttributes(*single._private); + } + + if (single.custom) { + for (auto const& [key, value] : *single.custom) { + auto maybe_attr = boost::json::value_to< + tl::expected>( + boost::json::parse(value.dump())); + if (maybe_attr) { + attrs.Set(key, *maybe_attr); + } + } + } +} + +tl::expected ContextBuild( + ContextBuildParams const& params) { + ContextResponse resp{}; + + auto builder = launchdarkly::ContextBuilder(); + + if (params.multi) { + for (auto const& single : *params.multi) { + BuildContextFromParams(builder, single); + } + } else { + BuildContextFromParams(builder, *params.single); + } + + auto ctx = builder.Build(); + if (!ctx.Valid()) { + resp.error = ctx.errors(); + return resp; + } + + resp.output = boost::json::serialize(boost::json::value_from(ctx)); + return resp; +} + +tl::expected ContextConvert( + ContextConvertParams const& params) { + ContextResponse resp{}; + + boost::system::error_code ec; + auto json_value = boost::json::parse(params.input, ec); + if (ec) { + resp.error = ec.what(); + return resp; + } + + auto maybe_ctx = boost::json::value_to< + tl::expected>( + json_value); + + if (!maybe_ctx) { + resp.error = launchdarkly::ErrorToString(maybe_ctx.error()); + return resp; + } + + if (!maybe_ctx->Valid()) { + resp.error = maybe_ctx->errors(); + return resp; + } + + resp.output = boost::json::serialize(boost::json::value_from(*maybe_ctx)); + return resp; +} + +tl::expected ClientEntity::Custom( + CustomEventParams const& params) { + auto data = + params.data + ? boost::json::value_to< + tl::expected>( + boost::json::parse(params.data->dump())) + : launchdarkly::Value::Null(); + + if (!data) { + return tl::make_unexpected("couldn't parse custom event data"); + } + + if (params.omitNullData.value_or(false) && !params.metricValue && + !params.data) { + client_->Track(params.eventKey); + return nlohmann::json{}; + } + + if (!params.metricValue) { + client_->Track(params.eventKey, std::move(*data)); + return nlohmann::json{}; + } + + client_->Track(params.eventKey, std::move(*data), *params.metricValue); + return nlohmann::json{}; +} + +tl::expected ClientEntity::EvaluateAll( + EvaluateAllFlagParams const& params) { + EvaluateAllFlagsResponse resp{}; + + boost::ignore_unused(params); + + for (auto& [key, value] : client_->AllFlags()) { + resp.state[key] = nlohmann::json::parse( + boost::json::serialize(boost::json::value_from(value))); + } + + return resp; +} + +tl::expected ClientEntity::EvaluateDetail( + EvaluateFlagParams const& params) { + auto const& key = params.flagKey; + + auto const& defaultVal = params.defaultValue; + + EvaluateFlagResponse result; + + std::optional reason; + + switch (params.valueType) { + case ValueType::Bool: { + auto detail = + client_->BoolVariationDetail(key, defaultVal.get()); + result.value = *detail; + reason = detail.Reason(); + result.variationIndex = detail.VariationIndex(); + break; + } + case ValueType::Int: { + auto detail = + client_->IntVariationDetail(key, defaultVal.get()); + result.value = *detail; + reason = detail.Reason(); + result.variationIndex = detail.VariationIndex(); + break; + } + case ValueType::Double: { + auto detail = + client_->DoubleVariationDetail(key, defaultVal.get()); + result.value = *detail; + reason = detail.Reason(); + result.variationIndex = detail.VariationIndex(); + break; + } + case ValueType::String: { + auto detail = client_->StringVariationDetail( + key, defaultVal.get()); + result.value = *detail; + reason = detail.Reason(); + result.variationIndex = detail.VariationIndex(); + break; + } + case ValueType::Any: + case ValueType::Unspecified: { + auto maybe_fallback = boost::json::value_to< + tl::expected>( + boost::json::parse(defaultVal.dump())); + if (!maybe_fallback) { + return tl::make_unexpected("unable to parse fallback value"); + } + + /* This switcharoo from nlohmann/json to boost/json to Value, then + * back is because we're using nlohmann/json for the test harness + * protocol, but boost::json in the SDK. We could swap over to + * boost::json entirely here to remove the awkwardness. */ + + auto detail = client_->JsonVariationDetail(key, *maybe_fallback); + + auto serialized = + boost::json::serialize(boost::json::value_from(*detail)); + + result.value = nlohmann::json::parse(serialized); + reason = detail.Reason(); + result.variationIndex = detail.VariationIndex(); + break; + } + default: + return tl::make_unexpected("unknown variation type"); + } + + result.reason = + reason.has_value() + ? std::make_optional(nlohmann::json::parse( + boost::json::serialize(boost::json::value_from(*reason)))) + : std::nullopt; + + return result; +} +tl::expected ClientEntity::Evaluate( + EvaluateFlagParams const& params) { + if (params.detail) { + return EvaluateDetail(params); + } + + auto const& key = params.flagKey; + + auto const& defaultVal = params.defaultValue; + + EvaluateFlagResponse result; + + switch (params.valueType) { + case ValueType::Bool: + result.value = client_->BoolVariation(key, defaultVal.get()); + break; + case ValueType::Int: + result.value = client_->IntVariation(key, defaultVal.get()); + break; + case ValueType::Double: + result.value = + client_->DoubleVariation(key, defaultVal.get()); + break; + case ValueType::String: { + result.value = + client_->StringVariation(key, defaultVal.get()); + break; + } + case ValueType::Any: + case ValueType::Unspecified: { + auto maybe_fallback = boost::json::value_to< + tl::expected>( + boost::json::parse(defaultVal.dump())); + if (!maybe_fallback) { + return tl::make_unexpected("unable to parse fallback value"); + } + /* This switcharoo from nlohmann/json to boost/json to Value, then + * back is because we're using nlohmann/json for the test harness + * protocol, but boost::json in the SDK. We could swap over to + * boost::json entirely here to remove the awkwardness. */ + + auto evaluation = client_->JsonVariation(key, *maybe_fallback); + + auto serialized = + boost::json::serialize(boost::json::value_from(evaluation)); + + result.value = nlohmann::json::parse(serialized); + break; + } + default: + return tl::make_unexpected("unknown variation type"); + } + + return result; +} +tl::expected ClientEntity::Command( + CommandParams params) { + switch (params.command) { + case Command::Unknown: + return tl::make_unexpected("unknown command"); + case Command::EvaluateFlag: + if (!params.evaluate) { + return tl::make_unexpected("evaluate params must be set"); + } + return Evaluate(*params.evaluate); + case Command::EvaluateAllFlags: + if (!params.evaluateAll) { + return tl::make_unexpected("evaluateAll params must be set"); + } + return EvaluateAll(*params.evaluateAll); + case Command::IdentifyEvent: + if (!params.identifyEvent) { + return tl::make_unexpected("identifyEvent params must be set"); + } + return Identify(*params.identifyEvent); + case Command::CustomEvent: + if (!params.customEvent) { + return tl::make_unexpected("customEvent params must be set"); + } + return Custom(*params.customEvent); + case Command::FlushEvents: + client_->FlushAsync(); + return nlohmann::json{}; + case Command::ContextBuild: + if (!params.contextBuild) { + return tl::make_unexpected("contextBuild params must be set"); + } + return ContextBuild(*params.contextBuild); + case Command::ContextConvert: + if (!params.contextConvert) { + return tl::make_unexpected("contextConvert params must be set"); + } + return ContextConvert(*params.contextConvert); + } + return tl::make_unexpected("unrecognized command"); +} diff --git a/contract-tests/server-contract-tests/src/definitions.cpp b/contract-tests/server-contract-tests/src/definitions.cpp new file mode 100644 index 000000000..aba257b39 --- /dev/null +++ b/contract-tests/server-contract-tests/src/definitions.cpp @@ -0,0 +1,6 @@ +#include "definitions.hpp" + +EvaluateFlagParams::EvaluateFlagParams() + : valueType{ValueType::Unspecified}, detail{false} {} + +CommandParams::CommandParams() : command{Command::Unknown} {} diff --git a/contract-tests/server-contract-tests/src/entity_manager.cpp b/contract-tests/server-contract-tests/src/entity_manager.cpp new file mode 100644 index 000000000..1910eef25 --- /dev/null +++ b/contract-tests/server-contract-tests/src/entity_manager.cpp @@ -0,0 +1,173 @@ +#include "entity_manager.hpp" +#include + +#include +#include +#include + +using launchdarkly::LogLevel; +using namespace launchdarkly::server_side; + +EntityManager::EntityManager(boost::asio::any_io_executor executor, + launchdarkly::Logger& logger) + : entities_(), + counter_{0}, + executor_{std::move(executor)}, + logger_{logger} {} + +static tl::expected +ParseContext(nlohmann::json value) { + auto boost_json_val = boost::json::parse(value.dump()); + return boost::json::value_to< + tl::expected>( + boost_json_val); +} + +std::optional EntityManager::create(ConfigParams const& in) { + std::string id = std::to_string(counter_++); + + auto config_builder = ConfigBuilder(in.credential); + + auto default_endpoints = + launchdarkly::server_side::Defaults::ServiceEndpoints(); + + auto& endpoints = + config_builder.ServiceEndpoints() + .EventsBaseUrl(default_endpoints.EventsBaseUrl()) + .PollingBaseUrl(default_endpoints.PollingBaseUrl()) + .StreamingBaseUrl(default_endpoints.StreamingBaseUrl()); + + if (in.serviceEndpoints) { + if (in.serviceEndpoints->streaming) { + endpoints.StreamingBaseUrl(*in.serviceEndpoints->streaming); + } + if (in.serviceEndpoints->polling) { + endpoints.PollingBaseUrl(*in.serviceEndpoints->polling); + } + if (in.serviceEndpoints->events) { + endpoints.EventsBaseUrl(*in.serviceEndpoints->events); + } + } + + if (in.streaming) { + if (in.streaming->baseUri) { + endpoints.StreamingBaseUrl(*in.streaming->baseUri); + } + } + + auto& datasource = config_builder.DataSource(); + + if (in.polling) { + if (in.polling->baseUri) { + endpoints.PollingBaseUrl(*in.polling->baseUri); + } + if (!in.streaming) { + auto method = DataSourceBuilder::Polling(); + if (in.polling->pollIntervalMs) { + method.PollInterval( + std::chrono::duration_cast( + std::chrono::milliseconds( + *in.polling->pollIntervalMs))); + } + datasource.Method(std::move(method)); + } + } + + auto& event_config = config_builder.Events(); + + if (in.events) { + ConfigEventParams const& events = *in.events; + + if (events.baseUri) { + endpoints.EventsBaseUrl(*events.baseUri); + } + + if (events.allAttributesPrivate) { + event_config.AllAttributesPrivate(*events.allAttributesPrivate); + } + + if (!events.globalPrivateAttributes.empty()) { + launchdarkly::AttributeReference::SetType attrs( + events.globalPrivateAttributes.begin(), + events.globalPrivateAttributes.end()); + event_config.PrivateAttributes(std::move(attrs)); + } + + if (events.capacity) { + event_config.Capacity(*events.capacity); + } + + if (events.flushIntervalMs) { + event_config.FlushInterval( + std::chrono::milliseconds(*events.flushIntervalMs)); + } + + } else { + event_config.Disable(); + } + + if (in.clientSide->evaluationReasons) { + datasource.WithReasons(*in.clientSide->evaluationReasons); + } + + if (in.clientSide->useReport) { + datasource.UseReport(*in.clientSide->useReport); + } + + if (in.tags) { + if (in.tags->applicationId) { + config_builder.AppInfo().Identifier(*in.tags->applicationId); + } + if (in.tags->applicationVersion) { + config_builder.AppInfo().Version(*in.tags->applicationVersion); + } + } + + auto config = config_builder.Build(); + if (!config) { + LD_LOG(logger_, LogLevel::kWarn) + << "entity_manager: couldn't build config: " << config.error(); + return std::nullopt; + } + + auto maybe_context = ParseContext(in.clientSide->initialContext); + if (!maybe_context) { + LD_LOG(logger_, LogLevel::kWarn) + << "entity_manager: initial context provided was invalid"; + return std::nullopt; + } + + auto client = std::make_unique(std::move(*config), *maybe_context); + + std::chrono::milliseconds waitForClient = std::chrono::seconds(5); + if (in.startWaitTimeMs) { + waitForClient = std::chrono::milliseconds(*in.startWaitTimeMs); + } + + auto init = client->StartAsync(); + init.wait_for(waitForClient); + + entities_.try_emplace(id, std::move(client)); + + return id; +} + +bool EntityManager::destroy(std::string const& id) { + auto it = entities_.find(id); + if (it == entities_.end()) { + return false; + } + + entities_.erase(it); + return true; +} + +tl::expected EntityManager::command( + std::string const& id, + CommandParams const& params) { + auto it = entities_.find(id); + if (it == entities_.end()) { + return tl::make_unexpected("entity not found"); + } + return it->second.Command(params); +} diff --git a/contract-tests/server-contract-tests/src/main.cpp b/contract-tests/server-contract-tests/src/main.cpp new file mode 100644 index 000000000..c32e41845 --- /dev/null +++ b/contract-tests/server-contract-tests/src/main.cpp @@ -0,0 +1,66 @@ +#include "server.hpp" + +#include + +#include +#include +#include +#include +#include + +#include + +namespace net = boost::asio; +namespace beast = boost::beast; + +using launchdarkly::logging::ConsoleBackend; + +using launchdarkly::LogLevel; + +int main(int argc, char* argv[]) { + launchdarkly::Logger logger{ + std::make_unique("server-contract-tests")}; + + const std::string default_port = "8123"; + std::string port = default_port; + if (argc == 2) { + port = + argv[1]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + + try { + net::io_context ioc{1}; + + auto p = boost::lexical_cast(port); + server srv(ioc, "0.0.0.0", p, logger); + + srv.add_capability("client-side"); + srv.add_capability("mobile"); + srv.add_capability("strongly-typed"); + srv.add_capability("context-type"); + srv.add_capability("service-endpoints"); + srv.add_capability("tags"); + + net::signal_set signals{ioc, SIGINT, SIGTERM}; + + boost::asio::spawn(ioc.get_executor(), [&](auto yield) mutable { + signals.async_wait(yield); + LD_LOG(logger, LogLevel::kInfo) << "shutting down.."; + srv.shutdown(); + }); + + ioc.run(); + LD_LOG(logger, LogLevel::kInfo) << "bye!"; + + } catch (boost::bad_lexical_cast&) { + LD_LOG(logger, LogLevel::kError) + << "invalid port (" << port + << "), provide a number (no arguments defaults " + "to port " + << default_port << ")"; + return EXIT_FAILURE; + } catch (std::exception const& e) { + LD_LOG(logger, LogLevel::kError) << e.what(); + return EXIT_FAILURE; + } +} diff --git a/contract-tests/server-contract-tests/src/server.cpp b/contract-tests/server-contract-tests/src/server.cpp new file mode 100644 index 000000000..b7c0a8b88 --- /dev/null +++ b/contract-tests/server-contract-tests/src/server.cpp @@ -0,0 +1,34 @@ +#include "server.hpp" +#include "session.hpp" + +#include +#include +#include +#include + +using launchdarkly::LogLevel; + +server::server(net::io_context& ioc, + std::string const& address, + unsigned short port, + launchdarkly::Logger& logger) + : manager_(ioc.get_executor(), logger), + listener_{ioc.get_executor(), + tcp::endpoint(boost::asio::ip::make_address(address), port)}, + logger_{logger} { + LD_LOG(logger_, LogLevel::kInfo) + << "server: listening on " << address << ":" << port; + listener_.async_accept([this](auto& server) { + return Session(server, manager_, caps_, logger_); + }); +} + +void server::add_capability(std::string cap) { + LD_LOG(logger_, LogLevel::kDebug) + << "server: test capability: <" << cap << ">"; + caps_.push_back(std::move(cap)); +} + +void server::shutdown() { + listener_.shutdown(); +} diff --git a/contract-tests/server-contract-tests/src/session.cpp b/contract-tests/server-contract-tests/src/session.cpp new file mode 100644 index 000000000..8de83db1a --- /dev/null +++ b/contract-tests/server-contract-tests/src/session.cpp @@ -0,0 +1,142 @@ +#include "session.hpp" +#include +#include +#include + +const std::string kEntityPath = "/entity/"; + +namespace net = boost::asio; + +Session::Session(launchdarkly::foxy::server_session& session, + EntityManager& manager, + std::vector& caps, + launchdarkly::Logger& logger) + : session_(session), + frame_(std::make_unique()), + manager_(manager), + caps_(caps), + logger_(logger) {} + +std::optional Session::generate_response(Request& req) { + auto const bad_request = [&req](beast::string_view why) { + Response res{http::status::bad_request, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "application/json"); + res.keep_alive(req.keep_alive()); + res.body() = nlohmann::json{"error", why}.dump(); + res.prepare_payload(); + return res; + }; + + auto const not_found = [&req](beast::string_view target) { + Response res{http::status::not_found, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = + "The resource '" + std::string(target) + "' was not found."; + res.prepare_payload(); + return res; + }; + + auto const server_error = [&req](beast::string_view what) { + Response res{http::status::internal_server_error, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = "An error occurred: '" + std::string(what) + "'"; + res.prepare_payload(); + return res; + }; + + auto const capabilities_response = + [&req](std::vector const& caps, std::string const& name, + std::string const& version) { + Response res{http::status::ok, req.version()}; + res.set(http::field::content_type, "application/json"); + res.keep_alive(req.keep_alive()); + res.body() = nlohmann::json{ + {"capabilities", caps}, + {"name", name}, + {"clientVersion", + version}}.dump(); + res.prepare_payload(); + return res; + }; + + auto const create_entity_response = [&req](std::string const& id) { + Response res{http::status::ok, req.version()}; + res.keep_alive(req.keep_alive()); + res.set("Location", kEntityPath + id); + res.prepare_payload(); + return res; + }; + + auto const destroy_entity_response = [&req](bool erased) { + auto status = erased ? http::status::ok : http::status::not_found; + Response res{status, req.version()}; + res.keep_alive(req.keep_alive()); + res.prepare_payload(); + return res; + }; + + if (req.method() == http::verb::get && req.target() == "/") { + return capabilities_response(caps_, "c-client-sdk", "0.0.0"); + } + + if (req.method() == http::verb::head && req.target() == "/") { + return http::response{http::status::ok, + req.version()}; + } + + if (req.method() == http::verb::delete_ && req.target() == "/") { + return std::nullopt; + } + + if (req.method() == http::verb::post && req.target() == "/") { + try { + auto json = nlohmann::json::parse(req.body()); + auto params = json.get(); + if (auto entity_id = manager_.create(params.configuration)) { + return create_entity_response(*entity_id); + } + return server_error("couldn't create client entity"); + } catch (nlohmann::json::exception& e) { + return bad_request("unable to parse config JSON"); + } + } + + if (req.method() == http::verb::post && + req.target().starts_with(kEntityPath)) { + std::string entity_id = req.target(); + boost::erase_first(entity_id, kEntityPath); + + try { + auto json = nlohmann::json::parse(req.body()); + auto params = json.get(); + tl::expected res = + manager_.command(entity_id, params); + if (res.has_value()) { + auto response = http::response{ + http::status::ok, req.version()}; + response.body() = res->dump(); + response.prepare_payload(); + return response; + } else { + return bad_request(res.error()); + } + } catch (nlohmann::json::exception& e) { + return bad_request("unable to parse config JSON"); + } + } + + if (req.method() == http::verb::delete_ && + req.target().starts_with(kEntityPath)) { + std::string entity_id = req.target(); + boost::erase_first(entity_id, kEntityPath); + bool erased = manager_.destroy(entity_id); + return destroy_entity_response(erased); + } + + return not_found(req.target()); +} diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -0,0 +1 @@ + From f1ebea74c76982bd5c98a114fc8ab4f60737638a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 21 Jul 2023 11:33:15 -0700 Subject: [PATCH 05/55] refactor contract tests data model into dedicated library --- contract-tests/CMakeLists.txt | 1 + .../client-contract-tests/CMakeLists.txt | 2 +- .../include/client_entity.hpp | 2 +- .../include/entity_manager.hpp | 2 +- contract-tests/data-model/CMakeLists.txt | 20 ++ .../include/data_model/data_model.hpp} | 0 .../src/data_model.cpp} | 2 +- .../server-contract-tests/CMakeLists.txt | 3 +- .../include/client_entity.hpp | 2 +- .../include/definitions.hpp | 281 ------------------ .../include/entity_manager.hpp | 5 +- .../src/client_entity.cpp | 8 +- .../server-contract-tests/src/definitions.cpp | 6 - .../src/entity_manager.cpp | 59 ++-- .../server-contract-tests/src/main.cpp | 4 +- 15 files changed, 65 insertions(+), 332 deletions(-) create mode 100644 contract-tests/data-model/CMakeLists.txt rename contract-tests/{client-contract-tests/include/definitions.hpp => data-model/include/data_model/data_model.hpp} (100%) rename contract-tests/{client-contract-tests/src/definitions.cpp => data-model/src/data_model.cpp} (81%) delete mode 100644 contract-tests/server-contract-tests/include/definitions.hpp delete mode 100644 contract-tests/server-contract-tests/src/definitions.cpp diff --git a/contract-tests/CMakeLists.txt b/contract-tests/CMakeLists.txt index c3fba4f1a..4da668ade 100644 --- a/contract-tests/CMakeLists.txt +++ b/contract-tests/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(data-model) add_subdirectory(sse-contract-tests) add_subdirectory(client-contract-tests) add_subdirectory(server-contract-tests) diff --git a/contract-tests/client-contract-tests/CMakeLists.txt b/contract-tests/client-contract-tests/CMakeLists.txt index fb3ea46ae..a77e15c6c 100644 --- a/contract-tests/client-contract-tests/CMakeLists.txt +++ b/contract-tests/client-contract-tests/CMakeLists.txt @@ -14,7 +14,6 @@ add_executable(client-tests src/main.cpp src/server.cpp src/session.cpp - src/definitions.cpp src/entity_manager.cpp src/client_entity.cpp ) @@ -25,6 +24,7 @@ target_link_libraries(client-tests PRIVATE foxy nlohmann_json::nlohmann_json Boost::coroutine + contract-test-data-model ) target_include_directories(client-tests PUBLIC include) diff --git a/contract-tests/client-contract-tests/include/client_entity.hpp b/contract-tests/client-contract-tests/include/client_entity.hpp index 66a60d9cd..2fc40a3fd 100644 --- a/contract-tests/client-contract-tests/include/client_entity.hpp +++ b/contract-tests/client-contract-tests/include/client_entity.hpp @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include "definitions.hpp" class ClientEntity { public: diff --git a/contract-tests/client-contract-tests/include/entity_manager.hpp b/contract-tests/client-contract-tests/include/entity_manager.hpp index e8a5802ad..92c165d62 100644 --- a/contract-tests/client-contract-tests/include/entity_manager.hpp +++ b/contract-tests/client-contract-tests/include/entity_manager.hpp @@ -5,8 +5,8 @@ #include #include +#include #include "client_entity.hpp" -#include "definitions.hpp" #include #include diff --git a/contract-tests/data-model/CMakeLists.txt b/contract-tests/data-model/CMakeLists.txt new file mode 100644 index 000000000..af39cf433 --- /dev/null +++ b/contract-tests/data-model/CMakeLists.txt @@ -0,0 +1,20 @@ +# Required for Apple Silicon support. +cmake_minimum_required(VERSION 3.19) + +project( + LaunchDarklyCPPSDKTestHarnessDataModel + VERSION 0.1 + DESCRIPTION "LaunchDarkly CPP SDK Test Harness Data Model definitions" + LANGUAGES CXX +) + +include(${CMAKE_FILES}/json.cmake) + + +add_library(contract-test-data-model src/data_model.cpp) +target_link_libraries(contract-test-data-model PUBLIC nlohmann_json::nlohmann_json + ) +target_include_directories(contract-test-data-model PUBLIC + $ + $ + ) diff --git a/contract-tests/client-contract-tests/include/definitions.hpp b/contract-tests/data-model/include/data_model/data_model.hpp similarity index 100% rename from contract-tests/client-contract-tests/include/definitions.hpp rename to contract-tests/data-model/include/data_model/data_model.hpp diff --git a/contract-tests/client-contract-tests/src/definitions.cpp b/contract-tests/data-model/src/data_model.cpp similarity index 81% rename from contract-tests/client-contract-tests/src/definitions.cpp rename to contract-tests/data-model/src/data_model.cpp index aba257b39..f1d020e47 100644 --- a/contract-tests/client-contract-tests/src/definitions.cpp +++ b/contract-tests/data-model/src/data_model.cpp @@ -1,4 +1,4 @@ -#include "definitions.hpp" +#include "data_model/data_model.hpp" EvaluateFlagParams::EvaluateFlagParams() : valueType{ValueType::Unspecified}, detail{false} {} diff --git a/contract-tests/server-contract-tests/CMakeLists.txt b/contract-tests/server-contract-tests/CMakeLists.txt index c9412fb0e..7f9b26f45 100644 --- a/contract-tests/server-contract-tests/CMakeLists.txt +++ b/contract-tests/server-contract-tests/CMakeLists.txt @@ -14,9 +14,7 @@ add_executable(server-tests src/main.cpp src/server.cpp src/session.cpp - src/definitions.cpp src/entity_manager.cpp - src/client_entity.cpp ) target_link_libraries(server-tests PRIVATE @@ -25,6 +23,7 @@ target_link_libraries(server-tests PRIVATE foxy nlohmann_json::nlohmann_json Boost::coroutine + contract-test-data-model ) target_include_directories(server-tests PUBLIC include) diff --git a/contract-tests/server-contract-tests/include/client_entity.hpp b/contract-tests/server-contract-tests/include/client_entity.hpp index d5f8c1b90..a2b64910a 100644 --- a/contract-tests/server-contract-tests/include/client_entity.hpp +++ b/contract-tests/server-contract-tests/include/client_entity.hpp @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include "definitions.hpp" class ClientEntity { public: diff --git a/contract-tests/server-contract-tests/include/definitions.hpp b/contract-tests/server-contract-tests/include/definitions.hpp deleted file mode 100644 index 7c08d367c..000000000 --- a/contract-tests/server-contract-tests/include/definitions.hpp +++ /dev/null @@ -1,281 +0,0 @@ -#pragma once - -#include -#include -#include -#include "nlohmann/json.hpp" - -namespace nlohmann { - -template -struct adl_serializer> { - static void to_json(json& j, std::optional const& opt) { - if (opt == std::nullopt) { - j = nullptr; - } else { - j = *opt; // this will call adl_serializer::to_json which will - // find the free function to_json in T's namespace! - } - } - - static void from_json(json const& j, std::optional& opt) { - if (j.is_null()) { - opt = std::nullopt; - } else { - opt = j.get(); // same as above, but with - // adl_serializer::from_json - } - } -}; -} // namespace nlohmann - -struct ConfigStreamingParams { - std::optional baseUri; - std::optional initialRetryDelayMs; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigStreamingParams, - baseUri, - initialRetryDelayMs); - -struct ConfigPollingParams { - std::optional baseUri; - std::optional pollIntervalMs; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigPollingParams, - baseUri, - pollIntervalMs); - -struct ConfigEventParams { - std::optional baseUri; - std::optional capacity; - std::optional enableDiagnostics; - std::optional allAttributesPrivate; - std::vector globalPrivateAttributes; - std::optional flushIntervalMs; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigEventParams, - baseUri, - capacity, - enableDiagnostics, - allAttributesPrivate, - globalPrivateAttributes, - flushIntervalMs); -struct ConfigServiceEndpointsParams { - std::optional streaming; - std::optional polling; - std::optional events; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigServiceEndpointsParams, - streaming, - polling, - events); - -struct ConfigClientSideParams { - nlohmann::json initialContext; - std::optional evaluationReasons; - std::optional useReport; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigClientSideParams, - initialContext, - evaluationReasons, - useReport); - -struct ConfigTags { - std::optional applicationId; - std::optional applicationVersion; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigTags, - applicationId, - applicationVersion); - -struct ConfigParams { - std::string credential; - std::optional startWaitTimeMs; - std::optional initCanFail; - std::optional streaming; - std::optional polling; - std::optional events; - std::optional serviceEndpoints; - std::optional clientSide; - std::optional tags; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams, - credential, - startWaitTimeMs, - initCanFail, - streaming, - polling, - events, - serviceEndpoints, - clientSide, - tags); - -struct ContextSingleParams { - std::optional kind; - std::string key; - std::optional name; - std::optional anonymous; - std::optional> _private; - std::optional> custom; -}; - -// These are defined manually because of the 'private' field, which is a -// reserved keyword in C++. -inline void to_json(nlohmann::json& nlohmann_json_j, - ContextSingleParams const& nlohmann_json_t) { - nlohmann_json_j["kind"] = nlohmann_json_t.kind; - nlohmann_json_j["key"] = nlohmann_json_t.key; - nlohmann_json_j["name"] = nlohmann_json_t.name; - nlohmann_json_j["anonymous"] = nlohmann_json_t.anonymous; - 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; - nlohmann_json_t.kind = - nlohmann_json_j.value("kind", nlohmann_json_default_obj.kind); - nlohmann_json_t.key = - nlohmann_json_j.value("key", nlohmann_json_default_obj.key); - nlohmann_json_t.name = - nlohmann_json_j.value("name", nlohmann_json_default_obj.name); - nlohmann_json_t.anonymous = - nlohmann_json_j.value("anonymous", nlohmann_json_default_obj.anonymous); - nlohmann_json_t._private = - nlohmann_json_j.value("private", nlohmann_json_default_obj._private); - nlohmann_json_t.custom = - nlohmann_json_j.value("custom", nlohmann_json_default_obj.custom); -} - -struct ContextBuildParams { - std::optional single; - std::optional> multi; -}; - -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ContextBuildParams, - single, - multi); - -struct ContextConvertParams { - std::string input; -}; - -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ContextConvertParams, input); - -struct ContextResponse { - std::optional output; - std::optional error; -}; - -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ContextResponse, output, error); - -struct CreateInstanceParams { - 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"}, - {ValueType::Double, "double"}, - {ValueType::String, "string"}, - {ValueType::Any, "any"}, - {ValueType::Unspecified, ""}}) - -struct EvaluateFlagParams { - std::string flagKey; - ValueType valueType; - nlohmann::json defaultValue; - bool detail; - EvaluateFlagParams(); -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateFlagParams, - flagKey, - valueType, - defaultValue, - detail); - -struct EvaluateFlagResponse { - nlohmann::json value; - std::optional variationIndex; - std::optional reason; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateFlagResponse, - value, - variationIndex, - reason); - -struct EvaluateAllFlagParams { - std::optional withReasons; - std::optional clientSideOnly; - std::optional detailsOnlyForTrackedFlags; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateAllFlagParams, - withReasons, - clientSideOnly, - detailsOnlyForTrackedFlags); -struct EvaluateAllFlagsResponse { - nlohmann::json state; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateAllFlagsResponse, - state); - -struct CustomEventParams { - std::string eventKey; - std::optional data; - std::optional omitNullData; - std::optional metricValue; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(CustomEventParams, - eventKey, - data, - omitNullData, - metricValue); - -struct IdentifyEventParams { - nlohmann::json context; -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(IdentifyEventParams, context); - -enum class Command { - Unknown = -1, - EvaluateFlag, - EvaluateAllFlags, - IdentifyEvent, - CustomEvent, - FlushEvents, - ContextBuild, - ContextConvert -}; -NLOHMANN_JSON_SERIALIZE_ENUM(Command, - {{Command::Unknown, nullptr}, - {Command::EvaluateFlag, "evaluate"}, - {Command::EvaluateAllFlags, "evaluateAll"}, - {Command::IdentifyEvent, "identifyEvent"}, - {Command::CustomEvent, "customEvent"}, - {Command::FlushEvents, "flushEvents"}, - {Command::ContextBuild, "contextBuild"}, - {Command::ContextConvert, "contextConvert"}}); - -struct CommandParams { - Command command; - std::optional evaluate; - std::optional evaluateAll; - std::optional customEvent; - std::optional identifyEvent; - std::optional contextBuild; - std::optional contextConvert; - CommandParams(); -}; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(CommandParams, - command, - evaluate, - evaluateAll, - customEvent, - identifyEvent, - contextBuild, - contextConvert); diff --git a/contract-tests/server-contract-tests/include/entity_manager.hpp b/contract-tests/server-contract-tests/include/entity_manager.hpp index ad2b89f33..bedb29bbd 100644 --- a/contract-tests/server-contract-tests/include/entity_manager.hpp +++ b/contract-tests/server-contract-tests/include/entity_manager.hpp @@ -5,8 +5,7 @@ #include #include -#include "client_entity.hpp" -#include "definitions.hpp" +#include #include #include @@ -17,7 +16,7 @@ class EventOutbox; class EntityManager { - std::unordered_map entities_; + //std::unordered_map entities_; std::size_t counter_; boost::asio::any_io_executor executor_; diff --git a/contract-tests/server-contract-tests/src/client_entity.cpp b/contract-tests/server-contract-tests/src/client_entity.cpp index be27a8c42..bc1de26f5 100644 --- a/contract-tests/server-contract-tests/src/client_entity.cpp +++ b/contract-tests/server-contract-tests/src/client_entity.cpp @@ -46,7 +46,7 @@ tl::expected ClientEntity::Identify( // the problematic tests, this eventually does unblock and let the test // proceed. // TODO: SC-204250 - client_->IdentifyAsync(*maybe_ctx).wait_for(std::chrono::seconds(5)); + // client_->IdentifyAsync(*maybe_ctx).wait_for(std::chrono::seconds(5)); return nlohmann::json{}; } @@ -144,16 +144,16 @@ tl::expected ClientEntity::Custom( if (params.omitNullData.value_or(false) && !params.metricValue && !params.data) { - client_->Track(params.eventKey); + // client_->Track(params.eventKey); return nlohmann::json{}; } if (!params.metricValue) { - client_->Track(params.eventKey, std::move(*data)); + // client_->Track(params.eventKey, std::move(*data)); return nlohmann::json{}; } - client_->Track(params.eventKey, std::move(*data), *params.metricValue); + // client_->Track(params.eventKey, std::move(*data), *params.metricValue); return nlohmann::json{}; } diff --git a/contract-tests/server-contract-tests/src/definitions.cpp b/contract-tests/server-contract-tests/src/definitions.cpp deleted file mode 100644 index aba257b39..000000000 --- a/contract-tests/server-contract-tests/src/definitions.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "definitions.hpp" - -EvaluateFlagParams::EvaluateFlagParams() - : valueType{ValueType::Unspecified}, detail{false} {} - -CommandParams::CommandParams() : command{Command::Unknown} {} diff --git a/contract-tests/server-contract-tests/src/entity_manager.cpp b/contract-tests/server-contract-tests/src/entity_manager.cpp index 1910eef25..2d581b605 100644 --- a/contract-tests/server-contract-tests/src/entity_manager.cpp +++ b/contract-tests/server-contract-tests/src/entity_manager.cpp @@ -10,18 +10,18 @@ using namespace launchdarkly::server_side; EntityManager::EntityManager(boost::asio::any_io_executor executor, launchdarkly::Logger& logger) - : entities_(), + : counter_{0}, executor_{std::move(executor)}, logger_{logger} {} -static tl::expected -ParseContext(nlohmann::json value) { - auto boost_json_val = boost::json::parse(value.dump()); - return boost::json::value_to< - tl::expected>( - boost_json_val); -} +//static tl::expected +//ParseContext(nlohmann::json value) { +// auto boost_json_val = boost::json::parse(value.dump()); +// return boost::json::value_to< +// tl::expected>( +// boost_json_val); +//} std::optional EntityManager::create(ConfigParams const& in) { std::string id = std::to_string(counter_++); @@ -107,11 +107,11 @@ std::optional EntityManager::create(ConfigParams const& in) { } if (in.clientSide->evaluationReasons) { - datasource.WithReasons(*in.clientSide->evaluationReasons); + // datasource.WithReasons(*in.clientSide->evaluationReasons); } if (in.clientSide->useReport) { - datasource.UseReport(*in.clientSide->useReport); + //datasource.UseReport(*in.clientSide->useReport); } if (in.tags) { @@ -130,14 +130,14 @@ std::optional EntityManager::create(ConfigParams const& in) { return std::nullopt; } - auto maybe_context = ParseContext(in.clientSide->initialContext); - if (!maybe_context) { - LD_LOG(logger_, LogLevel::kWarn) - << "entity_manager: initial context provided was invalid"; - return std::nullopt; - } +// auto maybe_context = ParseContext(in.clientSide->initialContext); +// if (!maybe_context) { +// LD_LOG(logger_, LogLevel::kWarn) +// << "entity_manager: initial context provided was invalid"; +// return std::nullopt; +// } - auto client = std::make_unique(std::move(*config), *maybe_context); + auto client = std::make_unique(std::move(*config)); std::chrono::milliseconds waitForClient = std::chrono::seconds(5); if (in.startWaitTimeMs) { @@ -147,27 +147,28 @@ std::optional EntityManager::create(ConfigParams const& in) { auto init = client->StartAsync(); init.wait_for(waitForClient); - entities_.try_emplace(id, std::move(client)); + //entities_.try_emplace(id, std::move(client)); return id; } bool EntityManager::destroy(std::string const& id) { - auto it = entities_.find(id); - if (it == entities_.end()) { - return false; - } - - entities_.erase(it); +// auto it = entities_.find(id); +// if (it == entities_.end()) { +// return false; +// } +// +// entities_.erase(it); return true; } tl::expected EntityManager::command( std::string const& id, CommandParams const& params) { - auto it = entities_.find(id); - if (it == entities_.end()) { - return tl::make_unexpected("entity not found"); - } - return it->second.Command(params); +// auto it = entities_.find(id); +// if (it == entities_.end()) { +// return tl::make_unexpected("entity not found"); +// } +// return it->second.Command(params); + return tl::make_unexpected("not supported"); } diff --git a/contract-tests/server-contract-tests/src/main.cpp b/contract-tests/server-contract-tests/src/main.cpp index c32e41845..61245d1af 100644 --- a/contract-tests/server-contract-tests/src/main.cpp +++ b/contract-tests/server-contract-tests/src/main.cpp @@ -34,12 +34,12 @@ int main(int argc, char* argv[]) { auto p = boost::lexical_cast(port); server srv(ioc, "0.0.0.0", p, logger); - srv.add_capability("client-side"); - srv.add_capability("mobile"); + srv.add_capability("server-side"); srv.add_capability("strongly-typed"); srv.add_capability("context-type"); srv.add_capability("service-endpoints"); srv.add_capability("tags"); + srv.add_capability("server-side-polling"); net::signal_set signals{ioc, SIGINT, SIGTERM}; From aa8592092b7cf4f8674b8c16d47ef9ccb8d4f6fd Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 15 Aug 2023 15:28:47 -0700 Subject: [PATCH 06/55] fix client name and version in contract test binaries --- .../client-contract-tests/src/session.cpp | 6 +++++- .../server-contract-tests/src/entity_manager.cpp | 15 --------------- .../server-contract-tests/src/session.cpp | 6 +++++- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/contract-tests/client-contract-tests/src/session.cpp b/contract-tests/client-contract-tests/src/session.cpp index 8de83db1a..5213ab6f9 100644 --- a/contract-tests/client-contract-tests/src/session.cpp +++ b/contract-tests/client-contract-tests/src/session.cpp @@ -1,6 +1,10 @@ #include "session.hpp" + +#include + #include #include + #include const std::string kEntityPath = "/entity/"; @@ -81,7 +85,7 @@ std::optional Session::generate_response(Request& req) { }; if (req.method() == http::verb::get && req.target() == "/") { - return capabilities_response(caps_, "c-client-sdk", "0.0.0"); + return capabilities_response(caps_, "cpp-client-sdk", launchdarkly::client_side::Client::Version()); } if (req.method() == http::verb::head && req.target() == "/") { diff --git a/contract-tests/server-contract-tests/src/entity_manager.cpp b/contract-tests/server-contract-tests/src/entity_manager.cpp index 2d581b605..26a33c5f3 100644 --- a/contract-tests/server-contract-tests/src/entity_manager.cpp +++ b/contract-tests/server-contract-tests/src/entity_manager.cpp @@ -106,14 +106,6 @@ std::optional EntityManager::create(ConfigParams const& in) { event_config.Disable(); } - if (in.clientSide->evaluationReasons) { - // datasource.WithReasons(*in.clientSide->evaluationReasons); - } - - if (in.clientSide->useReport) { - //datasource.UseReport(*in.clientSide->useReport); - } - if (in.tags) { if (in.tags->applicationId) { config_builder.AppInfo().Identifier(*in.tags->applicationId); @@ -130,13 +122,6 @@ std::optional EntityManager::create(ConfigParams const& in) { return std::nullopt; } -// auto maybe_context = ParseContext(in.clientSide->initialContext); -// if (!maybe_context) { -// LD_LOG(logger_, LogLevel::kWarn) -// << "entity_manager: initial context provided was invalid"; -// return std::nullopt; -// } - auto client = std::make_unique(std::move(*config)); std::chrono::milliseconds waitForClient = std::chrono::seconds(5); diff --git a/contract-tests/server-contract-tests/src/session.cpp b/contract-tests/server-contract-tests/src/session.cpp index 8de83db1a..16a643c50 100644 --- a/contract-tests/server-contract-tests/src/session.cpp +++ b/contract-tests/server-contract-tests/src/session.cpp @@ -1,6 +1,10 @@ #include "session.hpp" + +#include + #include #include + #include const std::string kEntityPath = "/entity/"; @@ -81,7 +85,7 @@ std::optional Session::generate_response(Request& req) { }; if (req.method() == http::verb::get && req.target() == "/") { - return capabilities_response(caps_, "c-client-sdk", "0.0.0"); + return capabilities_response(caps_, "cpp-server-sdk", launchdarkly::server_side::Client::Version()); } if (req.method() == http::verb::head && req.target() == "/") { From 709c995c4dcbf5a2a59fbd45a4515165c426791e Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 15 Aug 2023 15:45:23 -0700 Subject: [PATCH 07/55] update evaluation/evaluation detail commands to deserialize contexts --- .../include/data_model/data_model.hpp | 2 + contract-tests/data-model/src/data_model.cpp | 2 +- .../server-contract-tests/CMakeLists.txt | 1 + .../include/client_entity.hpp | 3 +- .../src/client_entity.cpp | 91 +++++++++++-------- .../src/entity_manager.cpp | 8 +- 6 files changed, 59 insertions(+), 48 deletions(-) diff --git a/contract-tests/data-model/include/data_model/data_model.hpp b/contract-tests/data-model/include/data_model/data_model.hpp index 7c08d367c..04e7cdc6f 100644 --- a/contract-tests/data-model/include/data_model/data_model.hpp +++ b/contract-tests/data-model/include/data_model/data_model.hpp @@ -188,6 +188,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(ValueType, struct EvaluateFlagParams { std::string flagKey; + std::optional context; ValueType valueType; nlohmann::json defaultValue; bool detail; @@ -195,6 +196,7 @@ struct EvaluateFlagParams { }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateFlagParams, flagKey, + context, valueType, defaultValue, detail); diff --git a/contract-tests/data-model/src/data_model.cpp b/contract-tests/data-model/src/data_model.cpp index f1d020e47..3172a7c4b 100644 --- a/contract-tests/data-model/src/data_model.cpp +++ b/contract-tests/data-model/src/data_model.cpp @@ -1,6 +1,6 @@ #include "data_model/data_model.hpp" EvaluateFlagParams::EvaluateFlagParams() - : valueType{ValueType::Unspecified}, detail{false} {} + : valueType{ValueType::Unspecified}, detail{false}, context{std::nullopt} {} CommandParams::CommandParams() : command{Command::Unknown} {} diff --git a/contract-tests/server-contract-tests/CMakeLists.txt b/contract-tests/server-contract-tests/CMakeLists.txt index 7f9b26f45..3bd94cb96 100644 --- a/contract-tests/server-contract-tests/CMakeLists.txt +++ b/contract-tests/server-contract-tests/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(server-tests src/server.cpp src/session.cpp src/entity_manager.cpp + src/client_entity.cpp ) target_link_libraries(server-tests PRIVATE diff --git a/contract-tests/server-contract-tests/include/client_entity.hpp b/contract-tests/server-contract-tests/include/client_entity.hpp index a2b64910a..39b4a3be3 100644 --- a/contract-tests/server-contract-tests/include/client_entity.hpp +++ b/contract-tests/server-contract-tests/include/client_entity.hpp @@ -16,7 +16,8 @@ class ClientEntity { EvaluateFlagParams const&); tl::expected EvaluateDetail( - EvaluateFlagParams const&); + EvaluateFlagParams const&, + launchdarkly::Context const&); tl::expected EvaluateAll( EvaluateAllFlagParams const&); diff --git a/contract-tests/server-contract-tests/src/client_entity.cpp b/contract-tests/server-contract-tests/src/client_entity.cpp index bc1de26f5..9ccea3558 100644 --- a/contract-tests/server-contract-tests/src/client_entity.cpp +++ b/contract-tests/server-contract-tests/src/client_entity.cpp @@ -13,40 +13,45 @@ #include #include +tl::expected +ParseContext(nlohmann::json value) { + boost::system::error_code ec; + auto boost_json_val = boost::json::parse(value.dump(), ec); + if (ec) { + return tl::make_unexpected(ec.what()); + } + + auto maybe_ctx = boost::json::value_to< + tl::expected>( + boost_json_val); + if (!maybe_ctx) { + return tl::make_unexpected( + launchdarkly::ErrorToString(maybe_ctx.error())); + } + + if (!maybe_ctx->Valid()) { + return tl::make_unexpected(maybe_ctx->errors()); + } + + return *maybe_ctx; +} + + ClientEntity::ClientEntity( std::unique_ptr client) : client_(std::move(client)) {} + tl::expected ClientEntity::Identify( IdentifyEventParams const& params) { boost::system::error_code ec; - auto json_value = boost::json::parse(params.context.dump(), ec); - if (ec) { - return tl::make_unexpected(ec.what()); - } - - auto maybe_ctx = boost::json::value_to< - tl::expected>( - json_value); + auto maybe_ctx = ParseContext(params.context); if (!maybe_ctx) { return tl::make_unexpected( - launchdarkly::ErrorToString(maybe_ctx.error())); + maybe_ctx.error()); } - - if (!maybe_ctx->Valid()) { - return tl::make_unexpected(maybe_ctx->errors()); - } - - // The typical way to identify in contract tests would be .wait(), to ensure - // identify completes fully before proceeding. Some of the contract tests - // send invalid data to the data source though, so identify will never - // complete because the data source can't start. So, limit the amount of - // time such that: 1) For most tests, this is *basically* synchronous 2) For - // the problematic tests, this eventually does unblock and let the test - // proceed. - // TODO: SC-204250 - // client_->IdentifyAsync(*maybe_ctx).wait_for(std::chrono::seconds(5)); + client_->Identify(*maybe_ctx); return nlohmann::json{}; } @@ -163,16 +168,17 @@ tl::expected ClientEntity::EvaluateAll( boost::ignore_unused(params); - for (auto& [key, value] : client_->AllFlags()) { - resp.state[key] = nlohmann::json::parse( - boost::json::serialize(boost::json::value_from(value))); - } + // TODO: fix when AllFlags available +// for (auto& [key, value] : client_->AllFlags()) { +// resp.state[key] = nlohmann::json::parse( +// boost::json::serialize(boost::json::value_from(value))); +// } return resp; } tl::expected ClientEntity::EvaluateDetail( - EvaluateFlagParams const& params) { + EvaluateFlagParams const& params, launchdarkly::Context const& ctx ) { auto const& key = params.flagKey; auto const& defaultVal = params.defaultValue; @@ -184,7 +190,7 @@ tl::expected ClientEntity::EvaluateDetail( switch (params.valueType) { case ValueType::Bool: { auto detail = - client_->BoolVariationDetail(key, defaultVal.get()); + client_->BoolVariationDetail(ctx, key, defaultVal.get()); result.value = *detail; reason = detail.Reason(); result.variationIndex = detail.VariationIndex(); @@ -192,7 +198,7 @@ tl::expected ClientEntity::EvaluateDetail( } case ValueType::Int: { auto detail = - client_->IntVariationDetail(key, defaultVal.get()); + client_->IntVariationDetail(ctx,key, defaultVal.get()); result.value = *detail; reason = detail.Reason(); result.variationIndex = detail.VariationIndex(); @@ -200,14 +206,14 @@ tl::expected ClientEntity::EvaluateDetail( } case ValueType::Double: { auto detail = - client_->DoubleVariationDetail(key, defaultVal.get()); + client_->DoubleVariationDetail(ctx,key, defaultVal.get()); result.value = *detail; reason = detail.Reason(); result.variationIndex = detail.VariationIndex(); break; } case ValueType::String: { - auto detail = client_->StringVariationDetail( + auto detail = client_->StringVariationDetail(ctx, key, defaultVal.get()); result.value = *detail; reason = detail.Reason(); @@ -228,7 +234,7 @@ tl::expected ClientEntity::EvaluateDetail( * protocol, but boost::json in the SDK. We could swap over to * boost::json entirely here to remove the awkwardness. */ - auto detail = client_->JsonVariationDetail(key, *maybe_fallback); + auto detail = client_->JsonVariationDetail(ctx, key, *maybe_fallback); auto serialized = boost::json::serialize(boost::json::value_from(*detail)); @@ -252,30 +258,37 @@ tl::expected ClientEntity::EvaluateDetail( } tl::expected ClientEntity::Evaluate( EvaluateFlagParams const& params) { + + auto maybe_ctx = ParseContext(params.context); + if (!maybe_ctx) { + return tl::make_unexpected(maybe_ctx.error()); + } + if (params.detail) { - return EvaluateDetail(params); + return EvaluateDetail(params, *maybe_ctx); } auto const& key = params.flagKey; auto const& defaultVal = params.defaultValue; + EvaluateFlagResponse result; switch (params.valueType) { case ValueType::Bool: - result.value = client_->BoolVariation(key, defaultVal.get()); + result.value = client_->BoolVariation(*maybe_ctx, key, defaultVal.get()); break; case ValueType::Int: - result.value = client_->IntVariation(key, defaultVal.get()); + result.value = client_->IntVariation(*maybe_ctx, key, defaultVal.get()); break; case ValueType::Double: result.value = - client_->DoubleVariation(key, defaultVal.get()); + client_->DoubleVariation(*maybe_ctx,key, defaultVal.get()); break; case ValueType::String: { result.value = - client_->StringVariation(key, defaultVal.get()); + client_->StringVariation(*maybe_ctx,key, defaultVal.get()); break; } case ValueType::Any: @@ -291,7 +304,7 @@ tl::expected ClientEntity::Evaluate( * protocol, but boost::json in the SDK. We could swap over to * boost::json entirely here to remove the awkwardness. */ - auto evaluation = client_->JsonVariation(key, *maybe_fallback); + auto evaluation = client_->JsonVariation(*maybe_ctx,key, *maybe_fallback); auto serialized = boost::json::serialize(boost::json::value_from(evaluation)); diff --git a/contract-tests/server-contract-tests/src/entity_manager.cpp b/contract-tests/server-contract-tests/src/entity_manager.cpp index 26a33c5f3..7fc7ec86e 100644 --- a/contract-tests/server-contract-tests/src/entity_manager.cpp +++ b/contract-tests/server-contract-tests/src/entity_manager.cpp @@ -15,13 +15,7 @@ EntityManager::EntityManager(boost::asio::any_io_executor executor, executor_{std::move(executor)}, logger_{logger} {} -//static tl::expected -//ParseContext(nlohmann::json value) { -// auto boost_json_val = boost::json::parse(value.dump()); -// return boost::json::value_to< -// tl::expected>( -// boost_json_val); -//} + std::optional EntityManager::create(ConfigParams const& in) { std::string id = std::to_string(counter_++); From 5406b59227ddf55811cfe2c387c861b2722f79b8 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 15 Aug 2023 15:58:54 -0700 Subject: [PATCH 08/55] re-enable client-entity --- .../include/entity_manager.hpp | 4 ++- .../src/entity_manager.cpp | 25 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/contract-tests/server-contract-tests/include/entity_manager.hpp b/contract-tests/server-contract-tests/include/entity_manager.hpp index bedb29bbd..b99633c32 100644 --- a/contract-tests/server-contract-tests/include/entity_manager.hpp +++ b/contract-tests/server-contract-tests/include/entity_manager.hpp @@ -1,5 +1,7 @@ #pragma once +#include "client_entity.hpp" + #include #include @@ -16,7 +18,7 @@ class EventOutbox; class EntityManager { - //std::unordered_map entities_; + std::unordered_map entities_; std::size_t counter_; boost::asio::any_io_executor executor_; diff --git a/contract-tests/server-contract-tests/src/entity_manager.cpp b/contract-tests/server-contract-tests/src/entity_manager.cpp index 7fc7ec86e..c5578d540 100644 --- a/contract-tests/server-contract-tests/src/entity_manager.cpp +++ b/contract-tests/server-contract-tests/src/entity_manager.cpp @@ -126,28 +126,27 @@ std::optional EntityManager::create(ConfigParams const& in) { auto init = client->StartAsync(); init.wait_for(waitForClient); - //entities_.try_emplace(id, std::move(client)); + entities_.try_emplace(id, std::move(client)); return id; } bool EntityManager::destroy(std::string const& id) { -// auto it = entities_.find(id); -// if (it == entities_.end()) { -// return false; -// } -// -// entities_.erase(it); + auto it = entities_.find(id); + if (it == entities_.end()) { + return false; + } + + entities_.erase(it); return true; } tl::expected EntityManager::command( std::string const& id, CommandParams const& params) { -// auto it = entities_.find(id); -// if (it == entities_.end()) { -// return tl::make_unexpected("entity not found"); -// } -// return it->second.Command(params); - return tl::make_unexpected("not supported"); + auto it = entities_.find(id); + if (it == entities_.end()) { + return tl::make_unexpected("entity not found"); + } + return it->second.Command(params); } From e45741b04505732a732c1293515fffd5f2e00a82 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 16 Aug 2023 12:28:29 -0700 Subject: [PATCH 09/55] value-init data model types during deserialization --- .../test-suppressions.txt | 936 +++++++++++++++++- .../src/serialization/json_segment.cpp | 2 +- libs/server-sdk/src/client_impl.cpp | 2 + .../data_source_event_handler.cpp | 2 +- 4 files changed, 939 insertions(+), 3 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 8b1378917..315234f93 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -1 +1,935 @@ - +events/feature events +events/debug events +evaluation/bucketing/bucket by non-key attribute/in rollouts/invalid value type +evaluation/parameterized/bad attribute reference errors - tilde followed by nothing/test-flag/evaluate flag with detail +evaluation/parameterized/bad attribute reference errors - tilde followed by invalid escape character/test-flag/evaluate flag with detail +evaluation/parameterized/bad attribute reference errors - empty path component/test-flag/evaluate flag with detail +evaluation/parameterized/wrong type errors/want string, got number/want string, got number/evaluate flag with detail +evaluation/parameterized/bad attribute reference errors - clause with no attribute/test-flag/evaluate flag with detail +evaluation/parameterized/clause kind matching/negated kind non-match for multi-kind context/negated kind non-match for multi-kind context/evaluate all flags +evaluation/parameterized/clause kind matching/negated kind match for single-kind context/negated kind match for single-kind context/evaluate all flags +evaluation/parameterized/wrong type errors/want bool, got number/want bool, got number/evaluate flag with detail +evaluation/parameterized/wrong type errors/want bool, got string/want bool, got string/evaluate flag with detail +evaluation/parameterized/wrong type errors/want int, got bool/want int, got bool/evaluate flag with detail +evaluation/parameterized/attribute references/top-level attribute with no slash prefix/top-level attribute with no slash prefix/evaluate all flags +evaluation/parameterized/attribute references/top-level attribute with slash prefix/top-level attribute with slash prefix/evaluate all flags +evaluation/parameterized/attribute references/top-level attribute name containing unescaped slash/top-level attribute name containing unescaped slash/evaluate all flags +evaluation/parameterized/attribute references/top-level attribute with slash prefix containing escaped slash/top-level attribute with slash prefix containing escaped slash/evaluate all flags +evaluation/parameterized/attribute references/top-level attribute name containing unescaped tilde/top-level attribute name containing unescaped tilde/evaluate all flags +evaluation/parameterized/attribute references/top-level attribute with slash prefix containing escaped tilde/top-level attribute with slash prefix containing escaped tilde/evaluate all flags +evaluation/parameterized/attribute references/attribute is interpreted as unescaped name if there is no contextKind/attribute is interpreted as unescaped name if there is no contextKind/evaluate all flags +evaluation/parameterized/attribute references/property in object/property in object/evaluate all flags +evaluation/parameterized/attribute references/property with escape sequence in object/property with escape sequence in object/evaluate all flags +evaluation/parameterized/attribute references/property in nested object/property in nested object/evaluate all flags +evaluation/parameterized/attribute references/property that is a numeric string/property that is a numeric string/evaluate all flags +evaluation/parameterized/attribute references/nonexistent property automatically fails clause/nonexistent property automatically fails clause/evaluate all flags +evaluation/parameterized/attribute references/property reference in non-object automatically fails clause/property reference in non-object automatically fails clause/evaluate all flags +evaluation/parameterized/attribute references/array index is not supported/array index is not supported/evaluate all flags +evaluation/parameterized/built-in attributes other than kind/key match/key match/evaluate all flags +evaluation/parameterized/built-in attributes other than kind/key non-match/key non-match/evaluate all flags +evaluation/parameterized/built-in attributes other than kind/name match/name match/evaluate all flags +evaluation/parameterized/built-in attributes other than kind/name non-match/name non-match/evaluate all flags +evaluation/parameterized/built-in attributes other than kind/name not found is non-match regardless of op and values/name not found is non-match regardless of op and values/evaluate all flags +evaluation/parameterized/built-in attributes other than kind/anonymous match false/anonymous match false/evaluate all flags +evaluation/parameterized/built-in attributes other than kind/anonymous match true/anonymous match true/evaluate all flags +evaluation/parameterized/clause kind matching/multi-kind context with user, clause has default kind, match/multi-kind context with user, clause has default kind, match/evaluate all flags +evaluation/parameterized/clause kind matching/multi-kind context with user, clause has default kind, no match/multi-kind context with user, clause has default kind, no match/evaluate all flags +evaluation/parameterized/clause kind matching/multi-kind context, clause specifies existing kind, match/multi-kind context, clause specifies existing kind, match/evaluate all flags +evaluation/parameterized/clause kind matching/multi-kind context, clause specifies existing kind, no match/multi-kind context, clause specifies existing kind, no match/evaluate all flags +evaluation/parameterized/clause kind matching/kind match for single-kind context/kind match for single-kind context/evaluate all flags +evaluation/parameterized/clause kind matching/kind match for single-kind context using matches operator/kind match for single-kind context using matches operator/evaluate all flags +evaluation/parameterized/clause kind matching/kind non-match for single-kind context using matches operator/kind non-match for single-kind context using matches operator/evaluate all flags +evaluation/parameterized/clause kind matching/kind match for multi-kind context using matches operator/kind match for multi-kind context using matches operator/evaluate all flags +evaluation/parameterized/clause kind matching/kind non-match for multi-kind context using matches operator/kind non-match for multi-kind context using matches operator/evaluate all flags +evaluation/parameterized/clause kind matching/kind non-match for single-kind context/kind non-match for single-kind context/evaluate all flags +evaluation/parameterized/clause kind matching/kind match for multi-kind context/kind match for multi-kind context/evaluate all flags +evaluation/parameterized/clause kind matching/kind non-match for multi-kind context/kind non-match for multi-kind context/evaluate all flags +evaluation/parameterized/wrong type errors/want int, got string/want int, got string/evaluate flag with detail +evaluation/parameterized/wrong type errors/want string, got bool/want string, got bool/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/flag not found/flag not found/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/off variation too low/off variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/off variation too high/off variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/target variation too low/target variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/target variation too high/target variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/rule variation too low/rule variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (bool)/rule variation too high/rule variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/flag not found/flag not found/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/off variation too low/off variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/off variation too high/off variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/target variation too low/target variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/target variation too high/target variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/rule variation too low/rule variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (int)/rule variation too high/rule variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/flag not found/flag not found/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/off variation too low/off variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/off variation too high/off variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/target variation too low/target variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/target variation too high/target variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/rule variation too low/rule variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (double)/rule variation too high/rule variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/flag not found/flag not found/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/off variation too low/off variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/off variation too high/off variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/target variation too low/target variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/target variation too high/target variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/rule variation too low/rule variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (string)/rule variation too high/rule variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/flag not found/flag not found/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/off variation too low/off variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/off variation too high/off variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/target variation too low/target variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/target variation too high/target variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/rule variation too low/rule variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/rule variation too high/rule variation too high/evaluate flag with detail +evaluation/parameterized/clause negation and value iteration/negated, match if expression does not match single clause value/negated, match if expression does not match single clause value/evaluate all flags +evaluation/parameterized/clause negation and value iteration/negated, non-match if expression matches for single clause value/negated, non-match if expression matches for single clause value/evaluate all flags +evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple clause values/match if expression matches any of multiple clause values/evaluate all flags +evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple context values with single clause value/match if expression matches any of multiple context values with single clause value/evaluate all flags +evaluation/parameterized/clause negation and value iteration/negated, non-match if expression matches any of multiple context values with single clause value/negated, non-match if expression matches any of multiple context values with single clause value/evaluate all flags +evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple context values with any of multiple clause values/match if expression matches any of multiple context values with any of multiple clause values/evaluate all flags +evaluation/parameterized/clause negation and value iteration/negated, non-match if expression matches any of multiple context values with any of multiple clause values/negated, non-match if expression matches any of multiple context values with any of multiple clause values/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06X00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before 2017-12-06X00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before 2017-12-06 fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 before 2017-12-06T00:00:00.000-07:00 fails/00:00:00.000-07.00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before 00:00:00.000-07.00 fails/2017-12-06T00:00:00.000-07:00 before 00:00:00.000-07.00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/badstring before 2017-12-06T00:00:00.000-07:00 fails/badstring before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before badstring fails/2017-12-06T00:00:00.000-07:00 before badstring fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/ before 2017-12-06T00:00:00.000-07:00 fails/ before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before fails/2017-12-06T00:00:00.000-07:00 before fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/2017-12-06X00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before 2017-12-06X00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z before 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06 before 2017-12-06T07:00:00.000Z fails/2017-12-06 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before 2017-12-06 fails/2017-12-06T07:00:00.000Z before 2017-12-06 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 before 2017-12-06T07:00:00.000Z fails/00:00:00.000-07.00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before 00:00:00.000-07.00 fails/2017-12-06T07:00:00.000Z before 00:00:00.000-07.00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/badstring before 2017-12-06T07:00:00.000Z fails/badstring before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before badstring fails/2017-12-06T07:00:00.000Z before badstring fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/ before 2017-12-06T07:00:00.000Z fails/ before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before fails/2017-12-06T07:00:00.000Z before fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before 2017-12-06T07:00:00.000Z fails/1512543600000 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before 1512543600000 fails/2017-12-06T07:00:00.000Z before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 before 1512543600000 fails/2017-12-06X00:00:00.000-07:00 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before 2017-12-06X00:00:00.000-07:00 fails/1512543600000 before 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06 before 1512543600000 fails/2017-12-06 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before 2017-12-06 fails/1512543600000 before 2017-12-06 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 before 1512543600000 fails/00:00:00.000-07.00 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before 00:00:00.000-07.00 fails/1512543600000 before 00:00:00.000-07.00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/badstring before 1512543600000 fails/badstring before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before badstring fails/1512543600000 before badstring fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/ before 1512543600000 fails/ before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before fails/1512543600000 before fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before 1512543600000 fails/1512543600000 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 before 1512543600000 fails/1512543600000 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06X00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after 2017-12-06X00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06 after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after 2017-12-06 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 after 2017-12-06T00:00:00.000-07:00 fails/00:00:00.000-07.00 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after 00:00:00.000-07.00 fails/2017-12-06T00:00:00.000-07:00 after 00:00:00.000-07.00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/badstring after 2017-12-06T00:00:00.000-07:00 fails/badstring after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after badstring fails/2017-12-06T00:00:00.000-07:00 after badstring fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/ after 2017-12-06T00:00:00.000-07:00 fails/ after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after fails/2017-12-06T00:00:00.000-07:00 after fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/2017-12-06X00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after 2017-12-06X00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z after 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06 after 2017-12-06T07:00:00.000Z fails/2017-12-06 after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after 2017-12-06 fails/2017-12-06T07:00:00.000Z after 2017-12-06 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 after 2017-12-06T07:00:00.000Z fails/00:00:00.000-07.00 after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after 00:00:00.000-07.00 fails/2017-12-06T07:00:00.000Z after 00:00:00.000-07.00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/badstring after 2017-12-06T07:00:00.000Z fails/badstring after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after badstring fails/2017-12-06T07:00:00.000Z after badstring fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/ after 2017-12-06T07:00:00.000Z fails/ after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after fails/2017-12-06T07:00:00.000Z after fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after 2017-12-06T07:00:00.000Z fails/1512543600000 after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after 1512543600000 fails/2017-12-06T07:00:00.000Z after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 after 1512543600000 fails/2017-12-06X00:00:00.000-07:00 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after 2017-12-06X00:00:00.000-07:00 fails/1512543600000 after 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/2017-12-06 after 1512543600000 fails/2017-12-06 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after 2017-12-06 fails/1512543600000 after 2017-12-06 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 after 1512543600000 fails/00:00:00.000-07.00 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after 00:00:00.000-07.00 fails/1512543600000 after 00:00:00.000-07.00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/badstring after 1512543600000 fails/badstring after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after badstring fails/1512543600000 after badstring fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/ after 1512543600000 fails/ after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after fails/1512543600000 after fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after 1512543600000 fails/1512543600000 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad syntax/1512543600000 after 1512543600000 fails/1512543600000 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/null (null) before 2017-12-06T00:00:00.000-07:00 fails/null (null) before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 before null (null) fails/2017-12-06T00:00:00.000-07:00 before null (null) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/true (boolean) before 2017-12-06T00:00:00.000-07:00 fails/true (boolean) before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 before true (boolean) fails/2017-12-06T00:00:00.000-07:00 before true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/{} (object) before 2017-12-06T00:00:00.000-07:00 fails/{} (object) before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 before {} (object) fails/2017-12-06T00:00:00.000-07:00 before {} (object) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/null (null) before 2017-12-06T07:00:00.000Z fails/null (null) before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z before null (null) fails/2017-12-06T07:00:00.000Z before null (null) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/true (boolean) before 2017-12-06T07:00:00.000Z fails/true (boolean) before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z before true (boolean) fails/2017-12-06T07:00:00.000Z before true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/{} (object) before 2017-12-06T07:00:00.000Z fails/{} (object) before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z before {} (object) fails/2017-12-06T07:00:00.000Z before {} (object) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/null (null) before 1512543600000 fails/null (null) before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/1512543600000 before null (null) fails/1512543600000 before null (null) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/true (boolean) before 1512543600000 fails/true (boolean) before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/1512543600000 before true (boolean) fails/1512543600000 before true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/{} (object) before 1512543600000 fails/{} (object) before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/1512543600000 before {} (object) fails/1512543600000 before {} (object) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/null (null) after 2017-12-06T00:00:00.000-07:00 fails/null (null) after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 after null (null) fails/2017-12-06T00:00:00.000-07:00 after null (null) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/true (boolean) after 2017-12-06T00:00:00.000-07:00 fails/true (boolean) after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 after true (boolean) fails/2017-12-06T00:00:00.000-07:00 after true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/{} (object) after 2017-12-06T00:00:00.000-07:00 fails/{} (object) after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 after {} (object) fails/2017-12-06T00:00:00.000-07:00 after {} (object) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/null (null) after 2017-12-06T07:00:00.000Z fails/null (null) after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z after null (null) fails/2017-12-06T07:00:00.000Z after null (null) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/true (boolean) after 2017-12-06T07:00:00.000Z fails/true (boolean) after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z after true (boolean) fails/2017-12-06T07:00:00.000Z after true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/{} (object) after 2017-12-06T07:00:00.000Z fails/{} (object) after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z after {} (object) fails/2017-12-06T07:00:00.000Z after {} (object) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/null (null) after 1512543600000 fails/null (null) after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/1512543600000 after null (null) fails/1512543600000 after null (null) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/true (boolean) after 1512543600000 fails/true (boolean) after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/1512543600000 after true (boolean) fails/1512543600000 after true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/{} (object) after 1512543600000 fails/{} (object) after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - bad type/1512543600000 after {} (object) fails/1512543600000 after {} (object) fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00Z fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T07:00:00.000Z fails/1512543600000 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 1512543600000 fails/2017-12-06T07:00:00.000Z before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00Z fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00Z before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00Z before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T07:00:00Z fails/1512543600000 before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 1512543600000 fails/2017-12-06T07:00:00Z before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 1512543600000 fails/2017-12-06T07:00:00.000Z before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T07:00:00.000Z fails/1512543600000 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 1512543600000 fails/2017-12-06T07:00:00Z before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T07:00:00Z fails/1512543600000 before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 before 1512543600000 fails/1512543600000 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 before 1512543600000 fails/1512543600000 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00Z after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T07:00:00.000Z fails/1512543600000 after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 1512543600000 fails/2017-12-06T07:00:00.000Z after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00Z after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T07:00:00Z fails/1512543600000 after 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 1512543600000 fails/2017-12-06T07:00:00Z after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 1512543600000 fails/2017-12-06T07:00:00.000Z after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T07:00:00.000Z fails/1512543600000 after 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 1512543600000 fails/2017-12-06T07:00:00Z after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T07:00:00Z fails/1512543600000 after 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 after 1512543600000 fails/1512543600000 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - equal/1512543600000 after 1512543600000 fails/1512543600000 after 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:01:01.000-07:00 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:01:01.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T00:00:00.000-07:00 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:01:01.000-07:00 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:01:01.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z after 2017-12-06T00:01:01.000-07:00 fails/2017-12-06T07:00:00.000Z after 2017-12-06T00:01:01.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00.000Z passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00.000Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T07:00:00.000Z before 2017-12-06T00:01:01.000-07:00 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 before 2017-12-06T07:00:00.000Z fails/2017-12-06T00:01:01.000-07:00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z after 2017-12-06T00:01:01.000-07:00 fails/2017-12-06T07:00:00Z after 2017-12-06T00:01:01.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00Z passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T07:00:00Z before 2017-12-06T00:01:01.000-07:00 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 before 2017-12-06T07:00:00Z fails/2017-12-06T00:01:01.000-07:00 before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543600000 after 2017-12-06T00:01:01.000-07:00 fails/1512543600000 after 2017-12-06T00:01:01.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 1512543600000 passes/2017-12-06T00:01:01.000-07:00 after 1512543600000 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T00:01:01.000-07:00 passes/1512543600000 before 2017-12-06T00:01:01.000-07:00 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 before 1512543600000 fails/2017-12-06T00:01:01.000-07:00 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:01:01.000Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:01:01.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T07:01:01.000Z after 2017-12-06T00:00:00.000-07:00 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01.000Z passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01.000Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:01:01.000Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z after 2017-12-06T07:01:01.000Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:01:01.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00.000Z passes/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00.000Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01.000Z passes/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01.000Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:01:01.000Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z after 2017-12-06T07:01:01.000Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:01:01.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00Z passes/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T07:01:01.000Z passes/2017-12-06T07:00:00Z before 2017-12-06T07:01:01.000Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:01:01.000Z before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543600000 after 2017-12-06T07:01:01.000Z fails/1512543600000 after 2017-12-06T07:01:01.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 1512543600000 passes/2017-12-06T07:01:01.000Z after 1512543600000 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T07:01:01.000Z passes/1512543600000 before 2017-12-06T07:01:01.000Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z before 1512543600000 fails/2017-12-06T07:01:01.000Z before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:01:01Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:01:01Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T07:01:01Z after 2017-12-06T00:00:00.000-07:00 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01Z passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:01:01Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z after 2017-12-06T07:01:01Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:01:01Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T07:00:00.000Z passes/2017-12-06T07:01:01Z after 2017-12-06T07:00:00.000Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01Z passes/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:01:01Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z after 2017-12-06T07:01:01Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:01:01Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T07:00:00Z passes/2017-12-06T07:01:01Z after 2017-12-06T07:00:00Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T07:01:01Z passes/2017-12-06T07:00:00Z before 2017-12-06T07:01:01Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:01:01Z before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543600000 after 2017-12-06T07:01:01Z fails/1512543600000 after 2017-12-06T07:01:01Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 1512543600000 passes/2017-12-06T07:01:01Z after 1512543600000 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T07:01:01Z passes/1512543600000 before 2017-12-06T07:01:01Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z before 1512543600000 fails/2017-12-06T07:01:01Z before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 after 1512543601000 fails/2017-12-06T00:00:00.000-07:00 after 1512543601000 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T00:00:00.000-07:00 passes/1512543601000 after 2017-12-06T00:00:00.000-07:00 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 1512543601000 passes/2017-12-06T00:00:00.000-07:00 before 1512543601000 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543601000 before 2017-12-06T00:00:00.000-07:00 fails/1512543601000 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z after 1512543601000 fails/2017-12-06T07:00:00.000Z after 1512543601000 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T07:00:00.000Z passes/1512543601000 after 2017-12-06T07:00:00.000Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 1512543601000 passes/2017-12-06T07:00:00.000Z before 1512543601000 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543601000 before 2017-12-06T07:00:00.000Z fails/1512543601000 before 2017-12-06T07:00:00.000Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z after 1512543601000 fails/2017-12-06T07:00:00Z after 1512543601000 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T07:00:00Z passes/1512543601000 after 2017-12-06T07:00:00Z passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 1512543601000 passes/2017-12-06T07:00:00Z before 1512543601000 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543601000 before 2017-12-06T07:00:00Z fails/1512543601000 before 2017-12-06T07:00:00Z fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543600000 after 1512543601000 fails/1512543600000 after 1512543601000 fails/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543601000 after 1512543600000 passes/1512543601000 after 1512543600000 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543600000 before 1512543601000 passes/1512543600000 before 1512543601000 passes/evaluate all flags +evaluation/parameterized/operators - date - unequal/1512543601000 before 1512543600000 fails/1512543601000 before 1512543600000 fails/evaluate all flags +evaluation/parameterized/operators - equality (bool)/user matches clause with single value/user matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (bool)/user does not match clause with single value/user does not match clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (bool)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags +evaluation/parameterized/operators - equality (bool)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (bool)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags +evaluation/parameterized/operators - equality (int)/user matches clause with single value/user matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (int)/user does not match clause with single value/user does not match clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (int)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags +evaluation/parameterized/operators - equality (int)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (int)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags +evaluation/parameterized/operators - equality (double)/user matches clause with single value/user matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (double)/user does not match clause with single value/user does not match clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (double)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags +evaluation/parameterized/operators - equality (double)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (double)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags +evaluation/parameterized/operators - equality (string)/user matches clause with single value/user matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (string)/user does not match clause with single value/user does not match clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (string)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags +evaluation/parameterized/operators - equality (string)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (string)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags +evaluation/parameterized/operators - equality (any)/user matches clause with single value/user matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (any)/user does not match clause with single value/user does not match clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (any)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags +evaluation/parameterized/operators - equality (any)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags +evaluation/parameterized/operators - equality (any)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags +evaluation/parameterized/operators - numeric/99 lessThan 100/99 lessThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/100 lessThan 100/100 lessThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/100.1 lessThan 100/100.1 lessThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/101 lessThan 100/101 lessThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/99 lessThanOrEqual 100/99 lessThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/100 lessThanOrEqual 100/100 lessThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/100.1 lessThanOrEqual 100/100.1 lessThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/101 lessThanOrEqual 100/101 lessThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/99 greaterThan 100/99 greaterThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/100 greaterThan 100/100 greaterThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/100.1 greaterThan 100/100.1 greaterThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/101 greaterThan 100/101 greaterThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/99 greaterThanOrEqual 100/99 greaterThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/100 greaterThanOrEqual 100/100 greaterThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/100.1 greaterThanOrEqual 100/100.1 greaterThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/101 greaterThanOrEqual 100/101 greaterThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/99 lessThan 100/99 lessThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/100 lessThanOrEqual 100/100 lessThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - numeric/101 greaterThan 100/101 greaterThan 100/evaluate all flags +evaluation/parameterized/operators - numeric/100 greaterThanOrEqual 100/100 greaterThanOrEqual 100/evaluate all flags +evaluation/parameterized/operators - semver - bad syntax/02.0.0 semVerEqual 2.0.0 fails/02.0.0 semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad syntax/2.0.0 semVerEqual 02.0.0 fails/2.0.0 semVerEqual 02.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad syntax/v2.0.0 semVerEqual 2.0.0 fails/v2.0.0 semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad syntax/2.0.0 semVerEqual v2.0.0 fails/2.0.0 semVerEqual v2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad syntax/2.0.0.0 semVerEqual 2.0.0 fails/2.0.0.0 semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad syntax/2.0.0 semVerEqual 2.0.0.0 fails/2.0.0 semVerEqual 2.0.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerEqual 2.0.0 fails/null (null) semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerEqual null (null) fails/2.0.0 semVerEqual null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerEqual 2.0.0 fails/true (boolean) semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerEqual true (boolean) fails/2.0.0 semVerEqual true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerEqual 2.0.0 fails/2 (number) semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerEqual 2 (number) fails/2.0.0 semVerEqual 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerEqual 2.0.0 fails/{} (object) semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerEqual {} (object) fails/2.0.0 semVerEqual {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerEqual 2.0 fails/null (null) semVerEqual 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerEqual null (null) fails/2.0 semVerEqual null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerEqual 2.0 fails/true (boolean) semVerEqual 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerEqual true (boolean) fails/2.0 semVerEqual true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerEqual 2.0 fails/2 (number) semVerEqual 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerEqual 2 (number) fails/2.0 semVerEqual 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerEqual 2.0 fails/{} (object) semVerEqual 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerEqual {} (object) fails/2.0 semVerEqual {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerEqual 2 fails/null (null) semVerEqual 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerEqual null (null) fails/2 semVerEqual null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerEqual 2 fails/true (boolean) semVerEqual 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerEqual true (boolean) fails/2 semVerEqual true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerEqual 2 fails/2 (number) semVerEqual 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerEqual 2 (number) fails/2 semVerEqual 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerEqual 2 fails/{} (object) semVerEqual 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerEqual {} (object) fails/2 semVerEqual {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerLessThan 2.0.0 fails/null (null) semVerLessThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerLessThan null (null) fails/2.0.0 semVerLessThan null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerLessThan 2.0.0 fails/true (boolean) semVerLessThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerLessThan true (boolean) fails/2.0.0 semVerLessThan true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerLessThan 2.0.0 fails/2 (number) semVerLessThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerLessThan 2 (number) fails/2.0.0 semVerLessThan 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerLessThan 2.0.0 fails/{} (object) semVerLessThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerLessThan {} (object) fails/2.0.0 semVerLessThan {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerLessThan 2.0 fails/null (null) semVerLessThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerLessThan null (null) fails/2.0 semVerLessThan null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerLessThan 2.0 fails/true (boolean) semVerLessThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerLessThan true (boolean) fails/2.0 semVerLessThan true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerLessThan 2.0 fails/2 (number) semVerLessThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerLessThan 2 (number) fails/2.0 semVerLessThan 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerLessThan 2.0 fails/{} (object) semVerLessThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerLessThan {} (object) fails/2.0 semVerLessThan {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerLessThan 2 fails/null (null) semVerLessThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerLessThan null (null) fails/2 semVerLessThan null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerLessThan 2 fails/true (boolean) semVerLessThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerLessThan true (boolean) fails/2 semVerLessThan true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerLessThan 2 fails/2 (number) semVerLessThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerLessThan 2 (number) fails/2 semVerLessThan 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerLessThan 2 fails/{} (object) semVerLessThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerLessThan {} (object) fails/2 semVerLessThan {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerGreaterThan 2.0.0 fails/null (null) semVerGreaterThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerGreaterThan null (null) fails/2.0.0 semVerGreaterThan null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerGreaterThan 2.0.0 fails/true (boolean) semVerGreaterThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerGreaterThan true (boolean) fails/2.0.0 semVerGreaterThan true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerGreaterThan 2.0.0 fails/2 (number) semVerGreaterThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerGreaterThan 2 (number) fails/2.0.0 semVerGreaterThan 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerGreaterThan 2.0.0 fails/{} (object) semVerGreaterThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0.0 semVerGreaterThan {} (object) fails/2.0.0 semVerGreaterThan {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerGreaterThan 2.0 fails/null (null) semVerGreaterThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerGreaterThan null (null) fails/2.0 semVerGreaterThan null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerGreaterThan 2.0 fails/true (boolean) semVerGreaterThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerGreaterThan true (boolean) fails/2.0 semVerGreaterThan true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerGreaterThan 2.0 fails/2 (number) semVerGreaterThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerGreaterThan 2 (number) fails/2.0 semVerGreaterThan 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerGreaterThan 2.0 fails/{} (object) semVerGreaterThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2.0 semVerGreaterThan {} (object) fails/2.0 semVerGreaterThan {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/null (null) semVerGreaterThan 2 fails/null (null) semVerGreaterThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerGreaterThan null (null) fails/2 semVerGreaterThan null (null) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/true (boolean) semVerGreaterThan 2 fails/true (boolean) semVerGreaterThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerGreaterThan true (boolean) fails/2 semVerGreaterThan true (boolean) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 (number) semVerGreaterThan 2 fails/2 (number) semVerGreaterThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerGreaterThan 2 (number) fails/2 semVerGreaterThan 2 (number) fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/{} (object) semVerGreaterThan 2 fails/{} (object) semVerGreaterThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - bad type/2 semVerGreaterThan {} (object) fails/2 semVerGreaterThan {} (object) fails/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0.0 passes/2.0.0 semVerEqual 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0.0 passes/2.0.0 semVerEqual 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0 semVerEqual 2.0.0 passes/2.0 semVerEqual 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0 passes/2.0.0 semVerEqual 2.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2 semVerEqual 2.0.0 passes/2 semVerEqual 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2 passes/2.0.0 semVerEqual 2 passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0+build semVerEqual 2.0.0 passes/2.0.0+build semVerEqual 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0.0+build passes/2.0.0 semVerEqual 2.0.0+build passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0.0-rc passes/2.0.0-rc semVerEqual 2.0.0-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0.0-rc passes/2.0.0-rc semVerEqual 2.0.0-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0-rc semVerEqual 2.0.0-rc passes/2.0-rc semVerEqual 2.0.0-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0-rc passes/2.0.0-rc semVerEqual 2.0-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2-rc semVerEqual 2.0.0-rc passes/2-rc semVerEqual 2.0.0-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2-rc passes/2.0.0-rc semVerEqual 2-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0-rc+build semVerEqual 2.0.0-rc passes/2.0.0-rc+build semVerEqual 2.0.0-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0.0-rc+build passes/2.0.0-rc semVerEqual 2.0.0-rc+build passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1.0.0 semVerEqual 2.0.0 fails/1.0.0 semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerEqual 1.0.0 fails/2.0.0 semVerEqual 1.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1.0.0 semVerLessThan 2.0.0 passes/1.0.0 semVerLessThan 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 1.0.0 fails/2.0.0 semVerLessThan 1.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 1.0.0 passes/2.0.0 semVerGreaterThan 1.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1.0.0 semVerGreaterThan 2.0.0 fails/1.0.0 semVerGreaterThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1.0 semVerEqual 2.0 fails/1.0 semVerEqual 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0 semVerEqual 1.0 fails/2.0 semVerEqual 1.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1.0 semVerLessThan 2.0 passes/1.0 semVerLessThan 2.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0 semVerLessThan 1.0 fails/2.0 semVerLessThan 1.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0 semVerGreaterThan 1.0 passes/2.0 semVerGreaterThan 1.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1.0 semVerGreaterThan 2.0 fails/1.0 semVerGreaterThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1 semVerEqual 2 fails/1 semVerEqual 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2 semVerEqual 1 fails/2 semVerEqual 1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1 semVerLessThan 2 passes/1 semVerLessThan 2 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2 semVerLessThan 1 fails/2 semVerLessThan 1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2 semVerGreaterThan 1 passes/2 semVerGreaterThan 1 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/1 semVerGreaterThan 2 fails/1 semVerGreaterThan 2 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerEqual 2.1.0 fails/2.0.0 semVerEqual 2.1.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.1.0 semVerEqual 2.0.0 fails/2.1.0 semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 2.1.0 passes/2.0.0 semVerLessThan 2.1.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.1.0 semVerLessThan 2.0.0 fails/2.1.0 semVerLessThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.1.0 semVerGreaterThan 2.0.0 passes/2.1.0 semVerGreaterThan 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 2.1.0 fails/2.0.0 semVerGreaterThan 2.1.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0 semVerEqual 2.1 fails/2.0 semVerEqual 2.1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.1 semVerEqual 2.0 fails/2.1 semVerEqual 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0 semVerLessThan 2.1 passes/2.0 semVerLessThan 2.1 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.1 semVerLessThan 2.0 fails/2.1 semVerLessThan 2.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.1 semVerGreaterThan 2.0 passes/2.1 semVerGreaterThan 2.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0 semVerGreaterThan 2.1 fails/2.0 semVerGreaterThan 2.1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerEqual 2.0.1 fails/2.0.0 semVerEqual 2.0.1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.1 semVerEqual 2.0.0 fails/2.0.1 semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 2.0.1 passes/2.0.0 semVerLessThan 2.0.1 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.1 semVerLessThan 2.0.0 fails/2.0.1 semVerLessThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.1 semVerGreaterThan 2.0.0 passes/2.0.1 semVerGreaterThan 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 2.0.1 fails/2.0.0 semVerGreaterThan 2.0.1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerEqual 2.0.0 fails/2.0.0-rc semVerEqual 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerEqual 2.0.0-rc fails/2.0.0 semVerEqual 2.0.0-rc fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerLessThan 2.0.0 passes/2.0.0-rc semVerLessThan 2.0.0 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 2.0.0-rc fails/2.0.0 semVerLessThan 2.0.0-rc fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 2.0.0-rc passes/2.0.0 semVerGreaterThan 2.0.0-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerGreaterThan 2.0.0 fails/2.0.0-rc semVerGreaterThan 2.0.0 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerEqual 2.0.0-rc.1 fails/2.0.0-rc semVerEqual 2.0.0-rc.1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.1 semVerEqual 2.0.0-rc fails/2.0.0-rc.1 semVerEqual 2.0.0-rc fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerLessThan 2.0.0-rc.1 passes/2.0.0-rc semVerLessThan 2.0.0-rc.1 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.1 semVerLessThan 2.0.0-rc fails/2.0.0-rc.1 semVerLessThan 2.0.0-rc fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.1 semVerGreaterThan 2.0.0-rc passes/2.0.0-rc.1 semVerGreaterThan 2.0.0-rc passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerGreaterThan 2.0.0-rc.1 fails/2.0.0-rc semVerGreaterThan 2.0.0-rc.1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerEqual 2.0.0-rc.10.green fails/2.0.0-rc.2.green semVerEqual 2.0.0-rc.10.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.10.green semVerEqual 2.0.0-rc.2.green fails/2.0.0-rc.10.green semVerEqual 2.0.0-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.10.green passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.10.green passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.10.green semVerLessThan 2.0.0-rc.2.green fails/2.0.0-rc.10.green semVerLessThan 2.0.0-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.10.green semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.10.green semVerGreaterThan 2.0.0-rc.2.green passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.10.green fails/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.10.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerEqual 2.0.0-rc.2.red fails/2.0.0-rc.2.green semVerEqual 2.0.0-rc.2.red fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.red semVerEqual 2.0.0-rc.2.green fails/2.0.0-rc.2.red semVerEqual 2.0.0-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.red passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.red passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.red semVerLessThan 2.0.0-rc.2.green fails/2.0.0-rc.2.red semVerLessThan 2.0.0-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.red semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.2.red semVerGreaterThan 2.0.0-rc.2.green passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.2.red fails/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.2.red fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerEqual 2.0.0-rc.2.green.1 fails/2.0.0-rc.2.green semVerEqual 2.0.0-rc.2.green.1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green.1 semVerEqual 2.0.0-rc.2.green fails/2.0.0-rc.2.green.1 semVerEqual 2.0.0-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.green.1 passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.green.1 passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green.1 semVerLessThan 2.0.0-rc.2.green fails/2.0.0-rc.2.green.1 semVerLessThan 2.0.0-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green.1 semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.2.green.1 semVerGreaterThan 2.0.0-rc.2.green passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.2.green.1 fails/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.2.green.1 fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerEqual 2.0.1-rc.2.green fails/2.0.0-rc.2.green semVerEqual 2.0.1-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.1-rc.2.green semVerEqual 2.0.0-rc.2.green fails/2.0.1-rc.2.green semVerEqual 2.0.0-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.1-rc.2.green passes/2.0.0-rc.2.green semVerLessThan 2.0.1-rc.2.green passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.1-rc.2.green semVerLessThan 2.0.0-rc.2.green fails/2.0.1-rc.2.green semVerLessThan 2.0.0-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.1-rc.2.green semVerGreaterThan 2.0.0-rc.2.green passes/2.0.1-rc.2.green semVerGreaterThan 2.0.0-rc.2.green passes/evaluate all flags +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerGreaterThan 2.0.1-rc.2.green fails/2.0.0-rc.2.green semVerGreaterThan 2.0.1-rc.2.green fails/evaluate all flags +evaluation/parameterized/operators - string/prefix startsWith prefix/prefix startsWith prefix/evaluate all flags +evaluation/parameterized/operators - string/[prefix] startsWith prefix/[prefix] startsWith prefix/evaluate all flags +evaluation/parameterized/operators - string/prefixabc startsWith prefix/prefixabc startsWith prefix/evaluate all flags +evaluation/parameterized/operators - string/[prefixabc] startsWith prefix/[prefixabc] startsWith prefix/evaluate all flags +evaluation/parameterized/operators - string/notprefix startsWith prefix/notprefix startsWith prefix/evaluate all flags +evaluation/parameterized/operators - string/[notprefix] startsWith prefix/[notprefix] startsWith prefix/evaluate all flags +evaluation/parameterized/operators - string/Prefixabc startsWith prefix/Prefixabc startsWith prefix/evaluate all flags +evaluation/parameterized/operators - string/[Prefixabc] startsWith prefix/[Prefixabc] startsWith prefix/evaluate all flags +evaluation/parameterized/operators - string/suffix endsWith suffix/suffix endsWith suffix/evaluate all flags +evaluation/parameterized/operators - string/[suffix] endsWith suffix/[suffix] endsWith suffix/evaluate all flags +evaluation/parameterized/operators - string/abcsuffix endsWith suffix/abcsuffix endsWith suffix/evaluate all flags +evaluation/parameterized/operators - string/[abcsuffix] endsWith suffix/[abcsuffix] endsWith suffix/evaluate all flags +evaluation/parameterized/operators - string/suffixno endsWith suffix/suffixno endsWith suffix/evaluate all flags +evaluation/parameterized/operators - string/[suffixno] endsWith suffix/[suffixno] endsWith suffix/evaluate all flags +evaluation/parameterized/operators - string/abcSuffix endsWith suffix/abcSuffix endsWith suffix/evaluate all flags +evaluation/parameterized/operators - string/[abcSuffix] endsWith suffix/[abcSuffix] endsWith suffix/evaluate all flags +evaluation/parameterized/operators - string/substring contains substring/substring contains substring/evaluate all flags +evaluation/parameterized/operators - string/[substring] contains substring/[substring] contains substring/evaluate all flags +evaluation/parameterized/operators - string/abcsubstringxyz contains substring/abcsubstringxyz contains substring/evaluate all flags +evaluation/parameterized/operators - string/[abcsubstringxyz] contains substring/[abcsubstringxyz] contains substring/evaluate all flags +evaluation/parameterized/operators - string/abcsubstxyz contains substring/abcsubstxyz contains substring/evaluate all flags +evaluation/parameterized/operators - string/[abcsubstxyz] contains substring/[abcsubstxyz] contains substring/evaluate all flags +evaluation/parameterized/operators - string/abcSubstringxyz contains substring/abcSubstringxyz contains substring/evaluate all flags +evaluation/parameterized/operators - string/[abcSubstringxyz] contains substring/[abcSubstringxyz] contains substring/evaluate all flags +evaluation/parameterized/operators - string/matchThisName matches ^[A-Za-z]+/matchThisName matches ^[A-Za-z]+/evaluate all flags +evaluation/parameterized/operators - string/[matchThisName] matches ^[A-Za-z]+/[matchThisName] matches ^[A-Za-z]+/evaluate all flags +evaluation/parameterized/operators - string/_Don't match This Name matches ^[A-Za-z]+/_Don't match This Name matches ^[A-Za-z]+/evaluate all flags +evaluation/parameterized/operators - string/[_Don't match This Name] matches ^[A-Za-z]+/[_Don't match This Name] matches ^[A-Za-z]+/evaluate all flags +evaluation/parameterized/operators - string/true startsWith true/true startsWith true/evaluate all flags +evaluation/parameterized/operators - string/[true] startsWith true/[true] startsWith true/evaluate all flags +evaluation/parameterized/operators - string/true endsWith true/true endsWith true/evaluate all flags +evaluation/parameterized/operators - string/[true] endsWith true/[true] endsWith true/evaluate all flags +evaluation/parameterized/operators - string/true contains true/true contains true/evaluate all flags +evaluation/parameterized/operators - string/[true] contains true/[true] contains true/evaluate all flags +evaluation/parameterized/operators - string/true matches true/true matches true/evaluate all flags +evaluation/parameterized/operators - string/[true] matches true/[true] matches true/evaluate all flags +evaluation/parameterized/operators - string/99 startsWith 99/99 startsWith 99/evaluate all flags +evaluation/parameterized/operators - string/[99] startsWith 99/[99] startsWith 99/evaluate all flags +evaluation/parameterized/operators - string/99 endsWith 99/99 endsWith 99/evaluate all flags +evaluation/parameterized/operators - string/[99] endsWith 99/[99] endsWith 99/evaluate all flags +evaluation/parameterized/operators - string/99 contains 99/99 contains 99/evaluate all flags +evaluation/parameterized/operators - string/[99] contains 99/[99] contains 99/evaluate all flags +evaluation/parameterized/operators - string/99 matches 99/99 matches 99/evaluate all flags +evaluation/parameterized/operators - string/[99] matches 99/[99] matches 99/evaluate all flags +evaluation/parameterized/operators - string/null startsWith null/null startsWith null/evaluate all flags +evaluation/parameterized/operators - string/[null] startsWith null/[null] startsWith null/evaluate all flags +evaluation/parameterized/operators - string/null endsWith null/null endsWith null/evaluate all flags +evaluation/parameterized/operators - string/[null] endsWith null/[null] endsWith null/evaluate all flags +evaluation/parameterized/operators - string/null contains null/null contains null/evaluate all flags +evaluation/parameterized/operators - string/[null] contains null/[null] contains null/evaluate all flags +evaluation/parameterized/operators - string/null matches null/null matches null/evaluate all flags +evaluation/parameterized/operators - string/[null] matches null/[null] matches null/evaluate all flags +evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule matches/prerequisite succeeds, flag rule matches/evaluate flag without detail +evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule matches/prerequisite succeeds, flag rule matches/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule matches/prerequisite succeeds, flag rule matches/evaluate all flags +evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule does not match/prerequisite succeeds, flag rule does not match/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule does not match/prerequisite succeeds, flag rule does not match/evaluate all flags +evaluation/parameterized/prerequisites/prerequisite fails/prerequisite fails/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite fails/prerequisite fails/evaluate all flags +evaluation/parameterized/prerequisites/two prerequisites, both succeed, flag rule matches/two prerequisites, both succeed, flag rule matches/evaluate flag without detail +evaluation/parameterized/prerequisites/two prerequisites, both succeed, flag rule matches/two prerequisites, both succeed, flag rule matches/evaluate flag with detail +evaluation/parameterized/prerequisites/two prerequisites, both succeed, flag rule matches/two prerequisites, both succeed, flag rule matches/evaluate all flags +evaluation/parameterized/prerequisites/two prerequisites, both valid, flag rule matches/two prerequisites, both valid, flag rule matches/evaluate flag without detail +evaluation/parameterized/prerequisites/two prerequisites, both valid, flag rule matches/two prerequisites, both valid, flag rule matches/evaluate flag with detail +evaluation/parameterized/prerequisites/two prerequisites, both valid, flag rule matches/two prerequisites, both valid, flag rule matches/evaluate all flags +evaluation/parameterized/prerequisites/two prerequisites, second fails/two prerequisites, second fails/evaluate flag with detail +evaluation/parameterized/prerequisites/two prerequisites, second fails/two prerequisites, second fails/evaluate all flags +evaluation/parameterized/prerequisites/nested prerequisites, all succeed/nested prerequisites, all succeed/evaluate flag without detail +evaluation/parameterized/prerequisites/nested prerequisites, all succeed/nested prerequisites, all succeed/evaluate flag with detail +evaluation/parameterized/prerequisites/nested prerequisites, all succeed/nested prerequisites, all succeed/evaluate all flags +evaluation/parameterized/prerequisites/nested prerequisites, transitive one fails/nested prerequisites, transitive one fails/evaluate flag with detail +evaluation/parameterized/prerequisites/nested prerequisites, transitive one fails/nested prerequisites, transitive one fails/evaluate all flags +evaluation/parameterized/prerequisites/prerequisite flag not found/prerequisite flag not found/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite flag not found/prerequisite flag not found/evaluate all flags +evaluation/parameterized/prerequisites/failed prerequisite always returns off variation/failed prerequisite always returns off variation/evaluate flag without detail +evaluation/parameterized/prerequisites/failed prerequisite always returns off variation/failed prerequisite always returns off variation/evaluate flag with detail +evaluation/parameterized/prerequisites/failed prerequisite always returns off variation/failed prerequisite always returns off variation/evaluate all flags +evaluation/parameterized/prerequisites/prerequisite fails regardless of variation if it is off/prerequisite fails regardless of variation if it is off/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite fails regardless of variation if it is off/prerequisite fails regardless of variation if it is off/evaluate all flags +evaluation/parameterized/prerequisites/prerequisite cycle is detected at top level, recursion stops/prerequisite cycle is detected at top level, recursion stops/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite cycle is detected at deeper level, recursion stops/prerequisite cycle is detected at deeper level, recursion stops/evaluate flag with detail +evaluation/parameterized/prerequisites/first prerequisite is a prerequisite of the second prerequisite/first prerequisite is a prerequisite of the second prerequisite/evaluate flag without detail +evaluation/parameterized/prerequisites/first prerequisite is a prerequisite of the second prerequisite/first prerequisite is a prerequisite of the second prerequisite/evaluate flag with detail +evaluation/parameterized/prerequisites/first prerequisite is a prerequisite of the second prerequisite/first prerequisite is a prerequisite of the second prerequisite/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches first bucket in rollout/fallthrough rollout/fallthrough rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches first bucket in rollout/rule rollout/rule rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches second bucket in rollout/fallthrough rollout/fallthrough rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches second bucket in rollout/rule rollout/rule rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - last rollout bucket is used if value is past the end/fallthrough rollout/fallthrough rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - last rollout bucket is used if value is past the end/rule rollout/rule rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches first bucket in experiment/fallthrough rollout/fallthrough rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches first bucket in experiment/rule rollout/rule rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches second bucket in experiment/fallthrough rollout/fallthrough rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches second bucket in experiment/rule rollout/rule rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches untracked bucket in experiment/fallthrough rollout/fallthrough rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - user matches untracked bucket in experiment/rule rollout/rule rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - last experiment bucket is used if value is past the end/fallthrough rollout/fallthrough rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - last experiment bucket is used if value is past the end/rule rollout/rule rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate flag with detail +evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/rule rollout/rule rollout/evaluate flag with detail +evaluation/parameterized/rule match ()/all clauses of rule1 match/all clauses of rule1 match/evaluate all flags +evaluation/parameterized/rule match ()/rule2 matches, after one clause of rule1 fails/rule2 matches, after one clause of rule1 fails/evaluate all flags +evaluation/parameterized/rule match ()/unknown operator is treated as a non-match, can still match another rule/unknown operator is treated as a non-match, can still match another rule/evaluate all flags +evaluation/parameterized/rule match ()/no rules match, fallthrough/no rules match, fallthrough/evaluate all flags +evaluation/parameterized/rule match ()/rules are ignored when flag is off/rules are ignored when flag is off/evaluate all flags +evaluation/parameterized/rule match ()/negated clause, non-match becomes match/negated clause, non-match becomes match/evaluate all flags +evaluation/parameterized/rule match ()/negated clause, match becomes non-match/negated clause, match becomes non-match/evaluate all flags +evaluation/parameterized/rule match ()/negated clause, null user attribute is always non-match regardless of negation/negated clause, null user attribute is always non-match regardless of negation/evaluate all flags +evaluation/parameterized/segment match/user matches via include/user matches via include/evaluate all flags +evaluation/parameterized/segment match/user matches via rule/user matches via rule/evaluate all flags +evaluation/parameterized/segment match/user matches via rule but is excluded/user matches via rule but is excluded/evaluate all flags +evaluation/parameterized/segment match/user does not match and is not included/user does not match and is not included/evaluate all flags +evaluation/parameterized/segment match/clause matches if any of the segments match/clause matches if any of the segments match/evaluate all flags +evaluation/parameterized/segment match/unknown segment is non-match, not an error/unknown segment is non-match, not an error/evaluate all flags +evaluation/parameterized/segment match/included list is specific to user kind/included list is specific to user kind/evaluate all flags +evaluation/parameterized/segment match/includedContexts match for non-default context kind/includedContexts match for non-default context kind/evaluate all flags +evaluation/parameterized/segment match/includedContexts non-match for default context kind/includedContexts non-match for default context kind/evaluate all flags +evaluation/parameterized/segment match/includedContexts non-match for non-default context kind/includedContexts non-match for non-default context kind/evaluate all flags +evaluation/parameterized/segment match/excluded list is specific to user kind/excluded list is specific to user kind/evaluate all flags +evaluation/parameterized/segment match/rule match for non-default context kind/rule match for non-default context kind/evaluate all flags +evaluation/parameterized/segment match/excludedContexts match for non-default context kind/excludedContexts match for non-default context kind/evaluate all flags +evaluation/parameterized/segment match/excludedContexts non-match for default context kind/excludedContexts non-match for default context kind/evaluate all flags +evaluation/parameterized/segment match/excludedContexts non-match for non-default context kind/excludedContexts non-match for non-default context kind/evaluate all flags +evaluation/parameterized/segment match/key does not bucket into segment/key does not bucket into segment/evaluate all flags +evaluation/parameterized/segment match/key does bucket into segment/key does bucket into segment/evaluate all flags +evaluation/parameterized/segment recursion/simple single-level recursion/simple single-level recursion/evaluate all flags +evaluation/parameterized/segment recursion/single level, two clauses in one rule, both match/single level, two clauses in one rule, both match/evaluate all flags +evaluation/parameterized/segment recursion/single level, two clauses in one rule, one does not match/single level, two clauses in one rule, one does not match/evaluate all flags +evaluation/parameterized/segment recursion/single level, two rules, one does not match/single level, two rules, one does not match/evaluate all flags +evaluation/parameterized/segment recursion/cycle is detected at top level, recursion stops/cycle is detected at top level, recursion stops/evaluate flag with detail +evaluation/parameterized/segment recursion/cycle is detected below top level, recursion stops/cycle is detected below top level, recursion stops/evaluate flag with detail +evaluation/parameterized/target match/user targets only, match user 1/user targets only, match user 1/evaluate all flags +evaluation/parameterized/target match/user targets only, match user 2/user targets only, match user 2/evaluate all flags +evaluation/parameterized/target match/user targets only, match user 3/user targets only, match user 3/evaluate all flags +evaluation/parameterized/target match/user targets only, match user 4/user targets only, match user 4/evaluate all flags +evaluation/parameterized/target match/user targets only, non-match user key/user targets only, non-match user key/evaluate all flags +evaluation/parameterized/target match/user targets only, match user key from multi-kind context/user targets only, match user key from multi-kind context/evaluate all flags +evaluation/parameterized/target match/user targets only, non-match user key from multi-kind context/user targets only, non-match user key from multi-kind context/evaluate all flags +evaluation/parameterized/target match/context targets, match user 1/context targets, match user 1/evaluate all flags +evaluation/parameterized/target match/context targets, match user 2/context targets, match user 2/evaluate all flags +evaluation/parameterized/target match/context targets, match user 3/context targets, match user 3/evaluate all flags +evaluation/parameterized/target match/context targets, match user 4/context targets, match user 4/evaluate all flags +evaluation/parameterized/target match/context targets, match other 1/context targets, match other 1/evaluate all flags +evaluation/parameterized/target match/context targets, match other 2/context targets, match other 2/evaluate all flags +evaluation/parameterized/target match/context targets, match other 3/context targets, match other 3/evaluate all flags +evaluation/parameterized/target match/context targets, match other 4/context targets, match other 4/evaluate all flags +evaluation/parameterized/target match/context targets, non-match user key/context targets, non-match user key/evaluate all flags +evaluation/parameterized/target match/context targets, non-match other key/context targets, non-match other key/evaluate all flags +evaluation/parameterized/target match/context targets, match user key from multi-kind context/context targets, match user key from multi-kind context/evaluate all flags +evaluation/parameterized/target match/context targets, match other key from multi-kind context/context targets, match other key from multi-kind context/evaluate all flags +evaluation/parameterized/target match/context targets, non-match multi-kind context/context targets, non-match multi-kind context/evaluate all flags +evaluation/parameterized/target match/user targets ignored when flag is off/user targets ignored when flag is off/evaluate all flags +evaluation/parameterized/target match/context targets ignored when flag is off/context targets ignored when flag is off/evaluate all flags +evaluation/parameterized/target match/target matching takes precedence over rules/target matching takes precedence over rules/evaluate all flags +evaluation/parameterized/variation value types (bool)/off/off/evaluate all flags +evaluation/parameterized/variation value types (bool)/fallthrough/fallthrough/evaluate all flags +evaluation/parameterized/variation value types (bool)/match variation 1/match variation 1/evaluate all flags +evaluation/parameterized/variation value types (bool)/match variation 2/match variation 2/evaluate all flags +evaluation/parameterized/variation value types (int)/off/off/evaluate all flags +evaluation/parameterized/variation value types (int)/fallthrough/fallthrough/evaluate all flags +evaluation/parameterized/variation value types (int)/match variation 1/match variation 1/evaluate all flags +evaluation/parameterized/variation value types (int)/match variation 2/match variation 2/evaluate all flags +evaluation/parameterized/variation value types (double)/off/off/evaluate all flags +evaluation/parameterized/variation value types (double)/fallthrough/fallthrough/evaluate all flags +evaluation/parameterized/variation value types (double)/match variation 1/match variation 1/evaluate all flags +evaluation/parameterized/variation value types (double)/match variation 2/match variation 2/evaluate all flags +evaluation/parameterized/variation value types (string)/off/off/evaluate all flags +evaluation/parameterized/variation value types (string)/fallthrough/fallthrough/evaluate all flags +evaluation/parameterized/variation value types (string)/match variation 1/match variation 1/evaluate all flags +evaluation/parameterized/variation value types (string)/match variation 2/match variation 2/evaluate all flags +evaluation/parameterized/variation value types (any)/off/off/evaluate all flags +evaluation/parameterized/variation value types (any)/fallthrough/fallthrough/evaluate all flags +evaluation/parameterized/variation value types (any)/match variation 1/match variation 1/evaluate all flags +evaluation/parameterized/variation value types (any)/match variation 2/match variation 2/evaluate all flags +evaluation/bucketing/bucket by non-key attribute/in rollouts/attribute not found +evaluation/all flags state/default behavior +evaluation/all flags state/experimentation +evaluation/all flags state/error in flag/without reasons +evaluation/all flags state/error in flag/with reasons +evaluation/all flags state/client not ready +events/requests/method and headers +events/requests/URL path is computed correctly/base URI has no trailing slash +events/requests/URL path is computed correctly/base URI has a trailing slash +events/requests/new payload ID for each post +events/summary events/basic counter behavior +events/summary events/contextKinds +events/summary events/unknown flag +events/summary events/reset after each flush +events/summary events/prerequisites +events/summary events/flag versions +events/feature prerequisite events/without reasons +events/feature prerequisite events/with reasons +events/experimentation/experiment in rule +events/experimentation/experiment in fallthrough +events/identify events/identify event makes index event for same user unnecessary/single kind default +events/identify events/identify event makes index event for same user unnecessary/single kind non-default +events/identify events/identify event makes index event for same user unnecessary/multi-kind +events/custom events/data and metricValue parameters/data=null +events/custom events/data and metricValue parameters/data=false +events/custom events/data and metricValue parameters/data=true +events/custom events/data and metricValue parameters/data=-1000 +events/custom events/data and metricValue parameters/data=0 +events/custom events/data and metricValue parameters/data=1000 +events/custom events/data and metricValue parameters/data=-1000.5 +events/custom events/data and metricValue parameters/data=1000.5 +events/custom events/data and metricValue parameters/data="" +events/custom events/data and metricValue parameters/data="abc" +events/custom events/data and metricValue parameters/data="has \"escaped\" characters" +events/custom events/data and metricValue parameters/data=[] +events/custom events/data and metricValue parameters/data=["a","b"] +events/custom events/data and metricValue parameters/data={} +events/custom events/data and metricValue parameters/data={"a":1} +events/custom events/data and metricValue parameters/data=null, omitNullData +events/custom events/data and metricValue parameters/data=null, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=false, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=true, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=-1000, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=0, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=1000, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=-1000.5, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=1000.5, metricValue=0.000000 +events/custom events/data and metricValue parameters/data="", metricValue=0.000000 +events/custom events/data and metricValue parameters/data="abc", metricValue=0.000000 +events/custom events/data and metricValue parameters/data="has \"escaped\" characters", metricValue=0.000000 +events/custom events/data and metricValue parameters/data=[], metricValue=0.000000 +events/custom events/data and metricValue parameters/data=["a","b"], metricValue=0.000000 +events/custom events/data and metricValue parameters/data={}, metricValue=0.000000 +events/custom events/data and metricValue parameters/data={"a":1}, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=null, omitNullData, metricValue=0.000000 +events/custom events/data and metricValue parameters/data=null, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=false, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=true, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=-1000, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=0, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=1000, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=-1000.5, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=1000.5, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data="", metricValue=-1.500000 +events/custom events/data and metricValue parameters/data="abc", metricValue=-1.500000 +events/custom events/data and metricValue parameters/data="has \"escaped\" characters", metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=[], metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=["a","b"], metricValue=-1.500000 +events/custom events/data and metricValue parameters/data={}, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data={"a":1}, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=null, omitNullData, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data=null, metricValue=1.500000 +events/custom events/data and metricValue parameters/data=false, metricValue=1.500000 +events/custom events/data and metricValue parameters/data=true, metricValue=1.500000 +events/custom events/data and metricValue parameters/data=-1000, metricValue=1.500000 +events/custom events/data and metricValue parameters/data=0, metricValue=1.500000 +events/custom events/data and metricValue parameters/data=1000, metricValue=1.500000 +events/custom events/data and metricValue parameters/data=-1000.5, metricValue=1.500000 +events/custom events/data and metricValue parameters/data=1000.5, metricValue=1.500000 +events/custom events/data and metricValue parameters/data="", metricValue=1.500000 +events/custom events/data and metricValue parameters/data="abc", metricValue=1.500000 +events/custom events/data and metricValue parameters/data="has \"escaped\" characters", metricValue=1.500000 +events/custom events/data and metricValue parameters/data=[], metricValue=1.500000 +events/custom events/data and metricValue parameters/data=["a","b"], metricValue=1.500000 +events/custom events/data and metricValue parameters/data={}, metricValue=1.500000 +events/custom events/data and metricValue parameters/data={"a":1}, metricValue=1.500000 +events/custom events/data and metricValue parameters/data=null, omitNullData, metricValue=1.500000 +events/custom events/basic properties/single kind default +events/custom events/basic properties/single kind non-default +events/custom events/basic properties/multi-kind +events/index events/basic properties +events/index events/only one index event per evaluation context/from feature event +events/index events/only one index event per evaluation context/from custom event +events/context properties/single-kind minimal +events/context properties/multi-kind minimal +events/context properties/single-kind with attributes, nothing private +events/context properties/single-kind, allAttributesPrivate +events/context properties/single-kind, specific private attributes +events/context properties/single-kind, private attribute nested property +events/context properties/custom attribute with value false +events/context properties/custom attribute with value true +events/context properties/custom attribute with value -1000 +events/context properties/custom attribute with value 0 +events/context properties/custom attribute with value 1000 +events/context properties/custom attribute with value -1000.5 +events/context properties/custom attribute with value 1000.5 +events/context properties/custom attribute with value "" +events/context properties/custom attribute with value "abc" +events/context properties/custom attribute with value "has \"escaped\" characters" +events/context properties/custom attribute with value [] +events/context properties/custom attribute with value ["a","b"] +events/context properties/custom attribute with value {} +events/context properties/custom attribute with value {"a":1} +events/event capacity/capacity is enforced +events/event capacity/buffer is reset after flush +events/event capacity/summary event is still included even if buffer was full +events/disabling/evaluation +events/disabling/identify event +events/disabling/custom event +streaming/requests/method and headers/GET +streaming/requests/URL path is computed correctly/no environment filter/base URI has no trailing slash/GET +streaming/requests/URL path is computed correctly/no environment filter/base URI has a trailing slash/GET +streaming/updates/flag patch with higher version is applied +streaming/updates/segment patch with higher version is applied +streaming/updates/flag patch with same version is not applied +streaming/updates/segment patch with same version is not applied +streaming/updates/flag patch with lower version is not applied +streaming/updates/segment patch with lower version is not applied +streaming/updates/flag patch for previously nonexistent flag is applied +streaming/updates/segment patch for previously nonexistent segment is applied +streaming/updates/flag delete with higher version is applied +streaming/updates/segment delete with higher version is applied +streaming/updates/flag delete with same version is not applied +streaming/updates/segment delete with same version is not applied +streaming/updates/flag delete with lower version is not applied +streaming/updates/segment delete with lower version is not applied +streaming/updates/flag delete for previously nonexistent flag is applied +streaming/updates/segment delete for previously nonexistent segment is applied +streaming/retry behavior/retry after stream is closed +streaming/retry behavior/initial retry delay is applied +streaming/retry behavior/retry after IO error on initial connect +streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 400 +streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 408 +streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 429 +streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 500 +streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 503 +streaming/retry behavior/retry after IO error on reconnect +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 400 +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 408 +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 429 +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 500 +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 503 +streaming/retry behavior/do not retry after unrecoverable HTTP error on initial connect/error 401 +streaming/retry behavior/do not retry after unrecoverable HTTP error on initial connect/error 403 +streaming/retry behavior/do not retry after unrecoverable HTTP error on initial connect/error 405 +streaming/retry behavior/do not retry after unrecoverable HTTP error on reconnect/error 401 +streaming/retry behavior/do not retry after unrecoverable HTTP error on reconnect/error 403 +streaming/retry behavior/do not retry after unrecoverable HTTP error on reconnect/error 405 +streaming/validation/drop and reconnect if stream event has malformed JSON/put event +streaming/validation/drop and reconnect if stream event has malformed JSON/patch event +streaming/validation/drop and reconnect if stream event has malformed JSON/delete event +streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/put event +streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/patch event +streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/delete event +streaming/validation/unrecognized data that can be safely ignored/unknown event name with JSON body +streaming/validation/unrecognized data that can be safely ignored/unknown event name with non-JSON body +streaming/validation/unrecognized data that can be safely ignored/patch event with unrecognized path kind +polling/requests/method and headers/GET +polling/requests/URL path is computed correctly/no environment filter/base URI has no trailing slash/GET +polling/requests/URL path is computed correctly/no environment filter/base URI has a trailing slash/GET +service endpoints/using per-component configuration/streaming +service endpoints/using per-component configuration/events +service endpoints/using separate service endpoints properties/streaming +service endpoints/using separate service endpoints properties/events +tags/stream requests/{"applicationId":null,"applicationVersion":null} +tags/stream requests/{"applicationId":null,"applicationVersion":""} +tags/stream requests/{"applicationId":null,"applicationVersion":""} +tags/stream requests/{"applicationId":null,"applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":null} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":null} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":null} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/stream requests/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":null,"applicationVersion":null} +tags/event posts/{"applicationId":null,"applicationVersion":""} +tags/event posts/{"applicationId":null,"applicationVersion":""} +tags/event posts/{"applicationId":null,"applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":null} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":null} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":null} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/event posts/{"applicationId":"","applicationVersion":""} +tags/disallowed characters +context type/build +context type/convert diff --git a/libs/internal/src/serialization/json_segment.cpp b/libs/internal/src/serialization/json_segment.cpp index dfd744191..157d26521 100644 --- a/libs/internal/src/serialization/json_segment.cpp +++ b/libs/internal/src/serialization/json_segment.cpp @@ -18,7 +18,7 @@ tl::expected, JsonError> tag_invoke( REQUIRE_OBJECT(json_value); auto const& obj = json_value.as_object(); - data_model::Segment::Target target; + data_model::Segment::Target target{}; PARSE_FIELD_DEFAULT(target.contextKind, obj, "contextKind", "user"); diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 9cd59eb22..215a164a0 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -144,6 +144,8 @@ std::future ClientImpl::StartAsyncInternal( return false; /* keep the change listener */ }); + data_source_->Start(); + return fut; } diff --git a/libs/server-sdk/src/data_sources/data_source_event_handler.cpp b/libs/server-sdk/src/data_sources/data_source_event_handler.cpp index c3a3d7970..27ee00ece 100644 --- a/libs/server-sdk/src/data_sources/data_source_event_handler.cpp +++ b/libs/server-sdk/src/data_sources/data_source_event_handler.cpp @@ -148,7 +148,7 @@ DataSourceEventHandler::MessageStatus DataSourceEventHandler::HandleMessage( boost::json::value_to, JsonError>>( parsed); - if (!res.has_value()) { + if (!res) { LD_LOG(logger_, LogLevel::kError) << kErrorPutInvalid; status_manager_.SetError( DataSourceStatus::ErrorInfo::ErrorKind::kInvalidData, From 298c6d8a6f9c41e50c5bd962c3f63ac1ff848d28 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 16 Aug 2023 12:39:17 -0700 Subject: [PATCH 10/55] add server Github action workflow --- .github/workflows/server.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 175ed5db3..1ae481daf 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -11,6 +11,25 @@ on: - '**.md' jobs: + contract-tests: + runs-on: ubuntu-22.04 + env: + # Port the test service (implemented in this repo) should bind to. + TEST_SERVICE_PORT: 8123 + TEST_SERVICE_BINARY: ./build/contract-tests/server-contract-tests/server-tests + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/ci + with: + cmake_target: server-tests + run_tests: false + - name: 'Launch test service as background task' + run: $TEST_SERVICE_BINARY $TEST_SERVICE_PORT 2>&1 & + - uses: ./.github/actions/contract-tests + with: + # Inform the test harness of test service's port. + test_service_port: ${{ env.TEST_SERVICE_PORT }} + extra_params: '-skip-from ./contract-tests/server-contract-tests/test-suppressions.txt' build-test-server: runs-on: ubuntu-22.04 steps: From d1976dff03566ba9bd458f2a9ae374dec77b77b7 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 16 Aug 2023 12:39:34 -0700 Subject: [PATCH 11/55] update client Github action workflow to fix contract test binary path --- .github/workflows/client.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index d63cf5fe1..173969727 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -16,12 +16,12 @@ jobs: env: # Port the test service (implemented in this repo) should bind to. TEST_SERVICE_PORT: 8123 - TEST_SERVICE_BINARY: ./build/contract-tests/client-contract-tests/sdk-tests + TEST_SERVICE_BINARY: ./build/contract-tests/client-contract-tests/client-tests steps: - uses: actions/checkout@v3 - uses: ./.github/actions/ci with: - cmake_target: sdk-tests + cmake_target: client-tests run_tests: false - name: 'Launch test service as background task' run: $TEST_SERVICE_BINARY $TEST_SERVICE_PORT 2>&1 & From dbb541dd5ab7128db56686a31815a1fbc3c2319a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 16 Aug 2023 16:46:41 -0700 Subject: [PATCH 12/55] be accepting of an empty PUT path --- libs/server-sdk/src/data_sources/data_source_event_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/server-sdk/src/data_sources/data_source_event_handler.cpp b/libs/server-sdk/src/data_sources/data_source_event_handler.cpp index 27ee00ece..2d8c436b3 100644 --- a/libs/server-sdk/src/data_sources/data_source_event_handler.cpp +++ b/libs/server-sdk/src/data_sources/data_source_event_handler.cpp @@ -62,7 +62,7 @@ tl::expected, JsonError> tag_invoke( auto const& obj = json_value.as_object(); PARSE_FIELD(path, obj, "path"); // We don't know what to do with a path other than "/". - if (path != "/") { + if (!(path == "/" || path.empty())) { return std::nullopt; } PARSE_FIELD(put.data, obj, "data"); From 0b3f00a96055f2e7e27b638802169c385e1487b7 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 16 Aug 2023 17:07:50 -0700 Subject: [PATCH 13/55] more suppressions --- contract-tests/server-contract-tests/test-suppressions.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 315234f93..e4ad014c2 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -725,6 +725,11 @@ evaluation/parameterized/variation value types (any)/fallthrough/fallthrough/eva evaluation/parameterized/variation value types (any)/match variation 1/match variation 1/evaluate all flags evaluation/parameterized/variation value types (any)/match variation 2/match variation 2/evaluate all flags evaluation/bucketing/bucket by non-key attribute/in rollouts/attribute not found +evaluation/parameterized/segment match/user matched via include can be negated/user matched via include can be negated/evaluate all flags +evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag without detail +evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag with detail +evaluation/parameterized/segment match/user does not match and can be negated/user does not match and can be negated/evaluate all flags +polling/payload/large payloads evaluation/all flags state/default behavior evaluation/all flags state/experimentation evaluation/all flags state/error in flag/without reasons From ac96b10181aade34e1b50a844f768f00b8101e6b Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 17 Aug 2023 16:25:01 -0700 Subject: [PATCH 14/55] fix all custom event contract tests --- .../include/data_model/data_model.hpp | 2 + .../src/client_entity.cpp | 15 ++++- .../test-suppressions.txt | 67 ------------------- .../events/data/common_events.hpp | 7 +- .../launchdarkly/events/data/events.hpp | 6 +- .../src/events/asio_event_processor.cpp | 18 ++++- libs/internal/src/events/common_events.cpp | 1 - libs/server-sdk/src/client_impl.cpp | 7 +- 8 files changed, 45 insertions(+), 78 deletions(-) diff --git a/contract-tests/data-model/include/data_model/data_model.hpp b/contract-tests/data-model/include/data_model/data_model.hpp index 04e7cdc6f..32cfadc36 100644 --- a/contract-tests/data-model/include/data_model/data_model.hpp +++ b/contract-tests/data-model/include/data_model/data_model.hpp @@ -228,12 +228,14 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateAllFlagsResponse, struct CustomEventParams { std::string eventKey; + std::optional context; std::optional data; std::optional omitNullData; std::optional metricValue; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(CustomEventParams, eventKey, + context, data, omitNullData, metricValue); diff --git a/contract-tests/server-contract-tests/src/client_entity.cpp b/contract-tests/server-contract-tests/src/client_entity.cpp index 9ccea3558..4c9400a1e 100644 --- a/contract-tests/server-contract-tests/src/client_entity.cpp +++ b/contract-tests/server-contract-tests/src/client_entity.cpp @@ -147,18 +147,27 @@ tl::expected ClientEntity::Custom( return tl::make_unexpected("couldn't parse custom event data"); } + if (!params.context) { + return tl::make_unexpected("context is required"); + } + + auto maybe_ctx = ParseContext(*params.context); + if (!maybe_ctx) { + return tl::make_unexpected(maybe_ctx.error()); + } + if (params.omitNullData.value_or(false) && !params.metricValue && !params.data) { - // client_->Track(params.eventKey); + client_->Track(*maybe_ctx, params.eventKey); return nlohmann::json{}; } if (!params.metricValue) { - // client_->Track(params.eventKey, std::move(*data)); + client_->Track(*maybe_ctx, params.eventKey, std::move(*data)); return nlohmann::json{}; } - // client_->Track(params.eventKey, std::move(*data), *params.metricValue); + client_->Track(*maybe_ctx, params.eventKey, std::move(*data), *params.metricValue); return nlohmann::json{}; } diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index e4ad014c2..22188e534 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -752,73 +752,6 @@ events/experimentation/experiment in fallthrough events/identify events/identify event makes index event for same user unnecessary/single kind default events/identify events/identify event makes index event for same user unnecessary/single kind non-default events/identify events/identify event makes index event for same user unnecessary/multi-kind -events/custom events/data and metricValue parameters/data=null -events/custom events/data and metricValue parameters/data=false -events/custom events/data and metricValue parameters/data=true -events/custom events/data and metricValue parameters/data=-1000 -events/custom events/data and metricValue parameters/data=0 -events/custom events/data and metricValue parameters/data=1000 -events/custom events/data and metricValue parameters/data=-1000.5 -events/custom events/data and metricValue parameters/data=1000.5 -events/custom events/data and metricValue parameters/data="" -events/custom events/data and metricValue parameters/data="abc" -events/custom events/data and metricValue parameters/data="has \"escaped\" characters" -events/custom events/data and metricValue parameters/data=[] -events/custom events/data and metricValue parameters/data=["a","b"] -events/custom events/data and metricValue parameters/data={} -events/custom events/data and metricValue parameters/data={"a":1} -events/custom events/data and metricValue parameters/data=null, omitNullData -events/custom events/data and metricValue parameters/data=null, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=false, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=true, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=-1000, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=0, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=1000, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=-1000.5, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=1000.5, metricValue=0.000000 -events/custom events/data and metricValue parameters/data="", metricValue=0.000000 -events/custom events/data and metricValue parameters/data="abc", metricValue=0.000000 -events/custom events/data and metricValue parameters/data="has \"escaped\" characters", metricValue=0.000000 -events/custom events/data and metricValue parameters/data=[], metricValue=0.000000 -events/custom events/data and metricValue parameters/data=["a","b"], metricValue=0.000000 -events/custom events/data and metricValue parameters/data={}, metricValue=0.000000 -events/custom events/data and metricValue parameters/data={"a":1}, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=null, omitNullData, metricValue=0.000000 -events/custom events/data and metricValue parameters/data=null, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=false, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=true, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=-1000, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=0, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=1000, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=-1000.5, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=1000.5, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data="", metricValue=-1.500000 -events/custom events/data and metricValue parameters/data="abc", metricValue=-1.500000 -events/custom events/data and metricValue parameters/data="has \"escaped\" characters", metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=[], metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=["a","b"], metricValue=-1.500000 -events/custom events/data and metricValue parameters/data={}, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data={"a":1}, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=null, omitNullData, metricValue=-1.500000 -events/custom events/data and metricValue parameters/data=null, metricValue=1.500000 -events/custom events/data and metricValue parameters/data=false, metricValue=1.500000 -events/custom events/data and metricValue parameters/data=true, metricValue=1.500000 -events/custom events/data and metricValue parameters/data=-1000, metricValue=1.500000 -events/custom events/data and metricValue parameters/data=0, metricValue=1.500000 -events/custom events/data and metricValue parameters/data=1000, metricValue=1.500000 -events/custom events/data and metricValue parameters/data=-1000.5, metricValue=1.500000 -events/custom events/data and metricValue parameters/data=1000.5, metricValue=1.500000 -events/custom events/data and metricValue parameters/data="", metricValue=1.500000 -events/custom events/data and metricValue parameters/data="abc", metricValue=1.500000 -events/custom events/data and metricValue parameters/data="has \"escaped\" characters", metricValue=1.500000 -events/custom events/data and metricValue parameters/data=[], metricValue=1.500000 -events/custom events/data and metricValue parameters/data=["a","b"], metricValue=1.500000 -events/custom events/data and metricValue parameters/data={}, metricValue=1.500000 -events/custom events/data and metricValue parameters/data={"a":1}, metricValue=1.500000 -events/custom events/data and metricValue parameters/data=null, omitNullData, metricValue=1.500000 -events/custom events/basic properties/single kind default -events/custom events/basic properties/single kind non-default -events/custom events/basic properties/multi-kind events/index events/basic properties events/index events/only one index event per evaluation context/from feature event events/index events/only one index event per evaluation context/from custom event diff --git a/libs/internal/include/launchdarkly/events/data/common_events.hpp b/libs/internal/include/launchdarkly/events/data/common_events.hpp index 667567c30..62066b4f2 100644 --- a/libs/internal/include/launchdarkly/events/data/common_events.hpp +++ b/libs/internal/include/launchdarkly/events/data/common_events.hpp @@ -33,7 +33,12 @@ struct TrackEventParams { std::optional metric_value; }; -// Track (custom) events are directly serialized from their parameters. +struct ServerTrackEventParams : public TrackEventParams { + Context context; +}; + +using ClientTrackEventParams = TrackEventParams; + using TrackEvent = TrackEventParams; struct IdentifyEventParams { diff --git a/libs/internal/include/launchdarkly/events/data/events.hpp b/libs/internal/include/launchdarkly/events/data/events.hpp index 1f474f2de..a0e00eae6 100644 --- a/libs/internal/include/launchdarkly/events/data/events.hpp +++ b/libs/internal/include/launchdarkly/events/data/events.hpp @@ -5,8 +5,10 @@ namespace launchdarkly::events { -using InputEvent = - std::variant; +using InputEvent = std::variant; using OutputEvent = std::variant AsioEventProcessor::Process( out.emplace_back(IdentifyEvent{event.creation_date, filter_.filter(event.context)}); }, - [&](TrackEventParams&& event) { + [&](ClientTrackEventParams&& event) { + out.emplace_back(std::move(event)); + }, + [&](ServerTrackEventParams&& event) { + if constexpr (std::is_same::value) { + if (!context_key_cache_.Notice( + event.context.CanonicalKey())) { + out.emplace_back(server_side::IndexEvent{ + event.creation_date, + filter_.filter(event.context)}); + } + } + + // Object slicing on purpose; the context will be stripped out + // of the ServerTrackEventParams when converted to a + // TrackEventParams. out.emplace_back(std::move(event)); }}, std::move(input_event)); diff --git a/libs/internal/src/events/common_events.cpp b/libs/internal/src/events/common_events.cpp index d3d324ec8..623f8d187 100644 --- a/libs/internal/src/events/common_events.cpp +++ b/libs/internal/src/events/common_events.cpp @@ -9,5 +9,4 @@ FeatureEventBase::FeatureEventBase(FeatureEventParams const& params) value(params.value), reason(params.reason), default_(params.default_) {} - } // namespace launchdarkly::events diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 215a164a0..446e1d42a 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -172,9 +172,10 @@ void ClientImpl::TrackInternal(Context const& ctx, std::string event_name, std::optional data, std::optional metric_value) { - event_processor_->SendAsync(events::TrackEventParams{ - std::chrono::system_clock::now(), std::move(event_name), - ctx.KindsToKeys(), std::move(data), metric_value}); + event_processor_->SendAsync(events::ServerTrackEventParams{ + {std::chrono::system_clock::now(), std::move(event_name), + ctx.KindsToKeys(), std::move(data), metric_value}, + ctx}); } void ClientImpl::Track(Context const& ctx, From 2d4e5b8a322f6873fa8fdc2257a621dfba3f419a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 17 Aug 2023 16:27:05 -0700 Subject: [PATCH 15/55] attempt to make CI run --- .github/workflows/server.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 1ae481daf..43c0276eb 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -6,7 +6,7 @@ on: paths-ignore: - '**.md' #Do not need to run CI for markdown changes. pull_request: - branches: [ main, server-side ] + branches: [ main, server-side, cw/sc-206686/server-build-ci ] paths-ignore: - '**.md' From 0829bcaf9f94b976918c157de701b77f3d9bf15a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 17 Aug 2023 17:53:06 -0700 Subject: [PATCH 16/55] fix some event tests --- .../test-suppressions.txt | 66 +++++++++++++-- libs/client-sdk/src/client_impl.cpp | 1 - .../launchdarkly/data/evaluation_detail.hpp | 8 ++ libs/common/src/data/evaluation_detail.cpp | 5 ++ .../include/launchdarkly/data_model/flag.hpp | 2 +- libs/internal/src/serialization/json_flag.cpp | 5 +- libs/server-sdk/src/client_impl.cpp | 82 ++++++++++++++++--- libs/server-sdk/src/client_impl.hpp | 3 +- libs/server-sdk/src/evaluation/evaluator.cpp | 2 +- 9 files changed, 153 insertions(+), 21 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 22188e534..5b970f510 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -1,4 +1,3 @@ -events/feature events events/debug events evaluation/bucketing/bucket by non-key attribute/in rollouts/invalid value type evaluation/parameterized/bad attribute reference errors - tilde followed by nothing/test-flag/evaluate flag with detail @@ -735,10 +734,6 @@ evaluation/all flags state/experimentation evaluation/all flags state/error in flag/without reasons evaluation/all flags state/error in flag/with reasons evaluation/all flags state/client not ready -events/requests/method and headers -events/requests/URL path is computed correctly/base URI has no trailing slash -events/requests/URL path is computed correctly/base URI has a trailing slash -events/requests/new payload ID for each post events/summary events/basic counter behavior events/summary events/contextKinds events/summary events/unknown flag @@ -871,3 +866,64 @@ tags/event posts/{"applicationId":"","applicationVersion":""} tags/disallowed characters context type/build context type/convert +events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: bool +events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: int +events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: double +events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: string +events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: any +events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: bool +events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: int +events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: double +events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: string +events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: any +events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: bool +events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: int +events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: double +events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: string +events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: any +events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: bool +events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: int +events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: double +events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: string +events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: any +events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: bool +events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: int +events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: double +events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: string +events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: any +events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: bool +events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: int +events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: double +events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: string +events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: any +events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: bool +events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: int +events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: double +events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: string +events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: any +events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: bool +events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: int +events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: double +events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: string +events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: any +events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: bool +events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: int +events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: double +events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: string +events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: any +events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: bool +events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: int +events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: double +events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: string +events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: any +events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: bool +events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: int +events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: double +events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: string +events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: any +events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: bool +events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: int +events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: double +events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: string +events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: any +events/feature events/evaluating all flags generates no events diff --git a/libs/client-sdk/src/client_impl.cpp b/libs/client-sdk/src/client_impl.cpp index fee995486..2b4aae39a 100644 --- a/libs/client-sdk/src/client_impl.cpp +++ b/libs/client-sdk/src/client_impl.cpp @@ -252,7 +252,6 @@ EvaluationDetail ClientImpl::VariationInternal(FlagKey const& key, << "LaunchDarkly client has not yet been initialized. " "Returning default value"; - // TODO: SC-199918 auto error_reason = EvaluationReason(EvaluationReason::ErrorKind::kClientNotReady); if (eval_reasons_available_) { diff --git a/libs/common/include/launchdarkly/data/evaluation_detail.hpp b/libs/common/include/launchdarkly/data/evaluation_detail.hpp index e0d2cceec..d32428d05 100644 --- a/libs/common/include/launchdarkly/data/evaluation_detail.hpp +++ b/libs/common/include/launchdarkly/data/evaluation_detail.hpp @@ -60,6 +60,14 @@ class EvaluationDetail { */ [[nodiscard]] std::optional const& Reason() const; + /** + * Check if an evaluation reason exists, and if so, if it is of a particular + * kind. + * @param kind Kind to check. + * @return True if a reason exists and matches the given kind. + */ + [[nodiscard]] bool ReasonKindIs(enum EvaluationReason::Kind kind) const; + /** * @return True if the evaluation resulted in an error. * TODO(sc209960) diff --git a/libs/common/src/data/evaluation_detail.cpp b/libs/common/src/data/evaluation_detail.cpp index 81a7aba3b..c6415848d 100644 --- a/libs/common/src/data/evaluation_detail.cpp +++ b/libs/common/src/data/evaluation_detail.cpp @@ -35,6 +35,11 @@ std::optional const& EvaluationDetail::Reason() const { return reason_; } +template +bool EvaluationDetail::ReasonKindIs(enum EvaluationReason::Kind kind) const { + return reason_.has_value() && reason_->Kind() == kind; +} + template std::optional EvaluationDetail::VariationIndex() const { return variation_index_; diff --git a/libs/internal/include/launchdarkly/data_model/flag.hpp b/libs/internal/include/launchdarkly/data_model/flag.hpp index dee59c11e..78198fc22 100644 --- a/libs/internal/include/launchdarkly/data_model/flag.hpp +++ b/libs/internal/include/launchdarkly/data_model/flag.hpp @@ -88,7 +88,7 @@ struct Flag { std::vector targets; std::vector contextTargets; std::vector rules; - std::optional offVariation; + std::optional offVariation; bool clientSide; ClientSideAvailability clientSideAvailability; std::optional salt; diff --git a/libs/internal/src/serialization/json_flag.cpp b/libs/internal/src/serialization/json_flag.cpp index 85e797757..68cc4e78a 100644 --- a/libs/internal/src/serialization/json_flag.cpp +++ b/libs/internal/src/serialization/json_flag.cpp @@ -203,7 +203,10 @@ tag_invoke(boost::json::value_to_tag< } data_model::Flag::Variation variation{}; - PARSE_REQUIRED_FIELD(variation, obj, "variation"); + /* If there's no rollout, this must be a variation. If there's no + * data at all, we treat it as variation 0 (since upstream encoders may + * omit it. */ + PARSE_FIELD_DEFAULT(variation, obj, "variation", 0); return std::make_optional(variation); } diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 446e1d42a..eff6f4569 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -203,7 +203,21 @@ template EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, FlagKey const& key, Value default_value, - bool check_type) { + bool check_type, + bool detailed) { + events::FeatureEventParams event = { + std::chrono::system_clock::now(), + key, + ctx, + default_value, + default_value, + std::nullopt, + std::nullopt, + std::nullopt, + false, + std::nullopt, + }; + auto desc = memory_store_.GetFlag(key); if (!desc || !desc->item) { @@ -214,6 +228,12 @@ EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, auto error_reason = EvaluationReason(EvaluationReason::ErrorKind::kClientNotReady); + + if (detailed) { + event.reason = error_reason; + } + + event_processor_->SendAsync(std::move(event)); return EvaluationDetail(std::move(default_value), std::nullopt, std::move(error_reason)); } @@ -223,6 +243,11 @@ EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, auto error_reason = EvaluationReason(EvaluationReason::ErrorKind::kFlagNotFound); + + if (detailed) { + event.reason = error_reason; + } + event_processor_->SendAsync(std::move(event)); return EvaluationDetail(std::move(default_value), std::nullopt, std::move(error_reason)); @@ -243,10 +268,43 @@ EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, auto error_reason = EvaluationReason(EvaluationReason::ErrorKind::kWrongType); + if (detailed) { + event.reason = error_reason; + } + event_processor_->SendAsync(std::move(event)); return EvaluationDetail(std::move(default_value), std::nullopt, error_reason); } + event.value = detail.Value(); + event.variation = detail.VariationIndex(); + + bool track_fallthrough = + flag.trackEventsFallthrough && + detail.ReasonKindIs(EvaluationReason::Kind::kFallthrough); + + bool track_rule_match = + detail.ReasonKindIs(EvaluationReason::Kind::kRuleMatch); + + if (track_rule_match) { + assert(detail.Reason()->RuleIndex().has_value() && + "evaluation algorithm must produce a rule index in the case of " + "rule " + "match"); + + assert(*detail.Reason()->RuleIndex() < flag.rules.size() && + "evaluation algorithm must produce a valid rule index in the " + "case of " + "rule match"); + + track_rule_match = + flag.rules.at(*detail.Reason()->RuleIndex()).trackEvents; + } + + if (detailed || flag.trackEvents || track_fallthrough || track_rule_match) { + event.reason = detail.Reason(); + } + event_processor_->SendAsync(std::move(event)); return EvaluationDetail(detail.Value(), detail.VariationIndex(), detail.Reason()); } @@ -255,13 +313,13 @@ EvaluationDetail ClientImpl::BoolVariationDetail( Context const& ctx, IClient::FlagKey const& key, bool default_value) { - return VariationInternal(ctx, key, default_value, true); + return VariationInternal(ctx, key, default_value, true, true); } bool ClientImpl::BoolVariation(Context const& ctx, IClient::FlagKey const& key, bool default_value) { - return *VariationInternal(ctx, key, default_value, true); + return *VariationInternal(ctx, key, default_value, true, false); } EvaluationDetail ClientImpl::StringVariationDetail( @@ -269,53 +327,55 @@ EvaluationDetail ClientImpl::StringVariationDetail( ClientImpl::FlagKey const& key, std::string default_value) { return VariationInternal(ctx, key, std::move(default_value), - true); + true, true); } std::string ClientImpl::StringVariation(Context const& ctx, IClient::FlagKey const& key, std::string default_value) { return *VariationInternal(ctx, key, std::move(default_value), - true); + true, false); } EvaluationDetail ClientImpl::DoubleVariationDetail( Context const& ctx, ClientImpl::FlagKey const& key, double default_value) { - return VariationInternal(ctx, key, default_value, true); + return VariationInternal(ctx, key, default_value, true, true); } double ClientImpl::DoubleVariation(Context const& ctx, IClient::FlagKey const& key, double default_value) { - return *VariationInternal(ctx, key, default_value, true); + return *VariationInternal(ctx, key, default_value, true, false); } EvaluationDetail ClientImpl::IntVariationDetail( Context const& ctx, IClient::FlagKey const& key, int default_value) { - return VariationInternal(ctx, key, default_value, true); + return VariationInternal(ctx, key, default_value, true, true); } int ClientImpl::IntVariation(Context const& ctx, IClient::FlagKey const& key, int default_value) { - return *VariationInternal(ctx, key, default_value, true); + return *VariationInternal(ctx, key, default_value, true, false); } EvaluationDetail ClientImpl::JsonVariationDetail( Context const& ctx, IClient::FlagKey const& key, Value default_value) { - return VariationInternal(ctx, key, std::move(default_value), false); + return VariationInternal(ctx, key, std::move(default_value), false, + true); } Value ClientImpl::JsonVariation(Context const& ctx, IClient::FlagKey const& key, Value default_value) { - return *VariationInternal(ctx, key, std::move(default_value), false); + return *VariationInternal(ctx, key, std::move(default_value), false, + false); } // data_sources::IDataSourceStatusProvider& ClientImpl::DataSourceStatus() { diff --git a/libs/server-sdk/src/client_impl.hpp b/libs/server-sdk/src/client_impl.hpp index f5491527d..40c5a69b7 100644 --- a/libs/server-sdk/src/client_impl.hpp +++ b/libs/server-sdk/src/client_impl.hpp @@ -111,7 +111,8 @@ class ClientImpl : public IClient { [[nodiscard]] EvaluationDetail VariationInternal(Context const& ctx, FlagKey const& key, Value default_value, - bool check_type); + bool check_type, + bool detailed); void TrackInternal(Context const& ctx, std::string event_name, std::optional data, diff --git a/libs/server-sdk/src/evaluation/evaluator.cpp b/libs/server-sdk/src/evaluation/evaluator.cpp index 9241832a6..4a326e180 100644 --- a/libs/server-sdk/src/evaluation/evaluator.cpp +++ b/libs/server-sdk/src/evaluation/evaluator.cpp @@ -138,7 +138,7 @@ EvaluationDetail Evaluator::FlagVariation( Flag const& flag, Flag::Variation variation_index, EvaluationReason reason) const { - if (variation_index >= flag.variations.size()) { + if (variation_index < 0 || variation_index >= flag.variations.size()) { LogError(flag.key, Error::NonexistentVariationIndex(variation_index)); return EvaluationReason::MalformedFlag(); } From e2d01dfe48690f301d50809ddf5631bfa9497396 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 17 Aug 2023 18:00:11 -0700 Subject: [PATCH 17/55] remove a bunch of suppressions --- .../test-suppressions.txt | 98 +++++++------------ 1 file changed, 36 insertions(+), 62 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 5b970f510..0fb4e0f7b 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -1,5 +1,4 @@ events/debug events -evaluation/bucketing/bucket by non-key attribute/in rollouts/invalid value type evaluation/parameterized/bad attribute reference errors - tilde followed by nothing/test-flag/evaluate flag with detail evaluation/parameterized/bad attribute reference errors - tilde followed by invalid escape character/test-flag/evaluate flag with detail evaluation/parameterized/bad attribute reference errors - empty path component/test-flag/evaluate flag with detail @@ -55,6 +54,8 @@ evaluation/parameterized/evaluation failures (bool)/target variation too low/tar evaluation/parameterized/evaluation failures (bool)/target variation too high/target variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (bool)/rule variation too low/rule variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (bool)/rule variation too high/rule variation too high/evaluate flag with detail +evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate flag without detail +evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/rule rollout/rule rollout/evaluate flag without detail evaluation/parameterized/evaluation failures (int)/flag not found/flag not found/evaluate flag with detail evaluation/parameterized/evaluation failures (int)/off variation too low/off variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (int)/off variation too high/off variation too high/evaluate flag with detail @@ -723,20 +724,16 @@ evaluation/parameterized/variation value types (any)/off/off/evaluate all flags evaluation/parameterized/variation value types (any)/fallthrough/fallthrough/evaluate all flags evaluation/parameterized/variation value types (any)/match variation 1/match variation 1/evaluate all flags evaluation/parameterized/variation value types (any)/match variation 2/match variation 2/evaluate all flags -evaluation/bucketing/bucket by non-key attribute/in rollouts/attribute not found evaluation/parameterized/segment match/user matched via include can be negated/user matched via include can be negated/evaluate all flags evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag without detail evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag with detail evaluation/parameterized/segment match/user does not match and can be negated/user does not match and can be negated/evaluate all flags -polling/payload/large payloads evaluation/all flags state/default behavior evaluation/all flags state/experimentation evaluation/all flags state/error in flag/without reasons evaluation/all flags state/error in flag/with reasons evaluation/all flags state/client not ready events/summary events/basic counter behavior -events/summary events/contextKinds -events/summary events/unknown flag events/summary events/reset after each flush events/summary events/prerequisites events/summary events/flag versions @@ -744,12 +741,6 @@ events/feature prerequisite events/without reasons events/feature prerequisite events/with reasons events/experimentation/experiment in rule events/experimentation/experiment in fallthrough -events/identify events/identify event makes index event for same user unnecessary/single kind default -events/identify events/identify event makes index event for same user unnecessary/single kind non-default -events/identify events/identify event makes index event for same user unnecessary/multi-kind -events/index events/basic properties -events/index events/only one index event per evaluation context/from feature event -events/index events/only one index event per evaluation context/from custom event events/context properties/single-kind minimal events/context properties/multi-kind minimal events/context properties/single-kind with attributes, nothing private @@ -770,15 +761,6 @@ events/context properties/custom attribute with value [] events/context properties/custom attribute with value ["a","b"] events/context properties/custom attribute with value {} events/context properties/custom attribute with value {"a":1} -events/event capacity/capacity is enforced -events/event capacity/buffer is reset after flush -events/event capacity/summary event is still included even if buffer was full -events/disabling/evaluation -events/disabling/identify event -events/disabling/custom event -streaming/requests/method and headers/GET -streaming/requests/URL path is computed correctly/no environment filter/base URI has no trailing slash/GET -streaming/requests/URL path is computed correctly/no environment filter/base URI has a trailing slash/GET streaming/updates/flag patch with higher version is applied streaming/updates/segment patch with higher version is applied streaming/updates/flag patch with same version is not applied @@ -824,48 +806,6 @@ streaming/validation/drop and reconnect if stream event has well-formed JSON not streaming/validation/unrecognized data that can be safely ignored/unknown event name with JSON body streaming/validation/unrecognized data that can be safely ignored/unknown event name with non-JSON body streaming/validation/unrecognized data that can be safely ignored/patch event with unrecognized path kind -polling/requests/method and headers/GET -polling/requests/URL path is computed correctly/no environment filter/base URI has no trailing slash/GET -polling/requests/URL path is computed correctly/no environment filter/base URI has a trailing slash/GET -service endpoints/using per-component configuration/streaming -service endpoints/using per-component configuration/events -service endpoints/using separate service endpoints properties/streaming -service endpoints/using separate service endpoints properties/events -tags/stream requests/{"applicationId":null,"applicationVersion":null} -tags/stream requests/{"applicationId":null,"applicationVersion":""} -tags/stream requests/{"applicationId":null,"applicationVersion":""} -tags/stream requests/{"applicationId":null,"applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":null} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":null} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":null} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/stream requests/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":null,"applicationVersion":null} -tags/event posts/{"applicationId":null,"applicationVersion":""} -tags/event posts/{"applicationId":null,"applicationVersion":""} -tags/event posts/{"applicationId":null,"applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":null} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":null} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":null} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/event posts/{"applicationId":"","applicationVersion":""} -tags/disallowed characters -context type/build -context type/convert events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: bool events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: int events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: double @@ -927,3 +867,37 @@ events/feature events/full feature event for tracked flag/with reason/multi-kind events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: string events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: any events/feature events/evaluating all flags generates no events + +# The Server doesn't need to know how to deserialize users. +context type/convert/old user to context/{"key": ""} +context type/convert/old user to context/{"key": "a"} +context type/convert/old user to context/{"key": "a"} +context type/convert/old user to context/{"key": "a", "custom": {"b": true}} +context type/convert/old user to context/{"key": "a", "custom": {"b": 1}} +context type/convert/old user to context/{"key": "a", "custom": {"b": "c"}} +context type/convert/old user to context/{"key": "a", "custom": {"b": [1, 2]}} +context type/convert/old user to context/{"key": "a", "custom": {"b": {"c": 1}}} +context type/convert/old user to context/{"key": "a", "custom": {"b": 1, "c": 2}} +context type/convert/old user to context/{"key": "a", "custom": {"b": 1, "c": null}} +context type/convert/old user to context/{"key": "a", "custom": {}} +context type/convert/old user to context/{"key": "a", "custom": null} +context type/convert/old user to context/{"key": "a", "anonymous": true} +context type/convert/old user to context/{"key": "a", "anonymous": false} +context type/convert/old user to context/{"key": "a", "anonymous": null} +context type/convert/old user to context/{"key": "a", "privateAttributeNames": ["b"]} +context type/convert/old user to context/{"key": "a", "privateAttributeNames": []} +context type/convert/old user to context/{"key": "a", "privateAttributeNames": null} +context type/convert/old user to context/{"key": "a", "name": "b"} +context type/convert/old user to context/{"key": "a", "name": null} +context type/convert/old user to context/{"key": "a", "firstName": "b"} +context type/convert/old user to context/{"key": "a", "firstName": null} +context type/convert/old user to context/{"key": "a", "lastName": "b"} +context type/convert/old user to context/{"key": "a", "lastName": null} +context type/convert/old user to context/{"key": "a", "email": "b"} +context type/convert/old user to context/{"key": "a", "email": null} +context type/convert/old user to context/{"key": "a", "country": "b"} +context type/convert/old user to context/{"key": "a", "country": null} +context type/convert/old user to context/{"key": "a", "avatar": "b"} +context type/convert/old user to context/{"key": "a", "avatar": null} +context type/convert/old user to context/{"key": "a", "ip": "b"} +context type/convert/old user to context/{"key": "a", "ip": null} From 37e604aeb8431b1ac01858dcbcb3414c8cfe5f53 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 18 Aug 2023 16:03:07 -0700 Subject: [PATCH 18/55] add missing version to feature events --- contract-tests/server-contract-tests/test-suppressions.txt | 3 --- libs/server-sdk/src/client_impl.cpp | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 0fb4e0f7b..62be3b5e9 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -733,10 +733,7 @@ evaluation/all flags state/experimentation evaluation/all flags state/error in flag/without reasons evaluation/all flags state/error in flag/with reasons evaluation/all flags state/client not ready -events/summary events/basic counter behavior -events/summary events/reset after each flush events/summary events/prerequisites -events/summary events/flag versions events/feature prerequisite events/without reasons events/feature prerequisite events/with reasons events/experimentation/experiment in rule diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index eff6f4569..9ad41b198 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -278,6 +278,7 @@ EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, event.value = detail.Value(); event.variation = detail.VariationIndex(); + event.version = flag.Version(); bool track_fallthrough = flag.trackEventsFallthrough && From 22911a286230e0b4ea3b5f9c60f5ffe47a861d5a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 18 Aug 2023 16:20:31 -0700 Subject: [PATCH 19/55] fix debug event tests --- contract-tests/server-contract-tests/test-suppressions.txt | 1 - libs/server-sdk/src/client_impl.cpp | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 62be3b5e9..933543720 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -1,4 +1,3 @@ -events/debug events evaluation/parameterized/bad attribute reference errors - tilde followed by nothing/test-flag/evaluate flag with detail evaluation/parameterized/bad attribute reference errors - tilde followed by invalid escape character/test-flag/evaluate flag with detail evaluation/parameterized/bad attribute reference errors - empty path component/test-flag/evaluate flag with detail diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 9ad41b198..750f494d7 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -280,6 +280,12 @@ EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, event.variation = detail.VariationIndex(); event.version = flag.Version(); + if (flag.debugEventsUntilDate) { + event.debug_events_until_date = + events::Date{std::chrono::system_clock::time_point{ + std::chrono::milliseconds{*flag.debugEventsUntilDate}}}; + } + bool track_fallthrough = flag.trackEventsFallthrough && detail.ReasonKindIs(EvaluationReason::Kind::kFallthrough); From 2ddf6725e2697aef2f81e540496ff672eab89040 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 18 Aug 2023 16:37:54 -0700 Subject: [PATCH 20/55] fix some feature event emission --- .../test-suppressions.txt | 25 ------------------- libs/server-sdk/src/client_impl.cpp | 9 ++++--- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 933543720..b982f0f7c 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -802,61 +802,36 @@ streaming/validation/drop and reconnect if stream event has well-formed JSON not streaming/validation/unrecognized data that can be safely ignored/unknown event name with JSON body streaming/validation/unrecognized data that can be safely ignored/unknown event name with non-JSON body streaming/validation/unrecognized data that can be safely ignored/patch event with unrecognized path kind -events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: bool -events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: int -events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: double -events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: string -events/feature events/full feature event for tracked flag/without reason/single kind default/valid flag/type: any events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: bool events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: int events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: double events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: string events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: any events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: bool -events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: int -events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: double -events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: string -events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: any events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: bool events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: int events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: double events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: string events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: any events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: bool -events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: int -events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: double -events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: string -events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: any events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: bool events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: int events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: double events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: string events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: any events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: bool -events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: int -events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: double -events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: string -events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: any events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: bool events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: int events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: double events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: string events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: any events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: bool -events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: int -events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: double -events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: string -events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: any events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: bool events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: int events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: double events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: string events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: any events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: bool -events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: int -events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: double -events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: string -events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: any events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: bool events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: int events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: double diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 750f494d7..c681fe317 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -280,6 +280,10 @@ EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, event.variation = detail.VariationIndex(); event.version = flag.Version(); + if (detailed) { + event.reason = detail.Reason(); + } + if (flag.debugEventsUntilDate) { event.debug_events_until_date = events::Date{std::chrono::system_clock::time_point{ @@ -308,9 +312,8 @@ EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, flag.rules.at(*detail.Reason()->RuleIndex()).trackEvents; } - if (detailed || flag.trackEvents || track_fallthrough || track_rule_match) { - event.reason = detail.Reason(); - } + event.require_full_event = + flag.trackEvents || track_fallthrough || track_rule_match; event_processor_->SendAsync(std::move(event)); return EvaluationDetail(detail.Value(), detail.VariationIndex(), detail.Reason()); From a41423f59bd72e87ea2ba3502cf2c74863ae8d53 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 09:56:23 -0700 Subject: [PATCH 21/55] remove old PR branch from server.yml --- .github/workflows/server.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 43c0276eb..1ae481daf 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -6,7 +6,7 @@ on: paths-ignore: - '**.md' #Do not need to run CI for markdown changes. pull_request: - branches: [ main, server-side, cw/sc-206686/server-build-ci ] + branches: [ main, server-side ] paths-ignore: - '**.md' From d5ce5af3f60768270f9cd492d8abfe0d50d4d56e Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 10:08:21 -0700 Subject: [PATCH 22/55] update comment --- libs/internal/src/serialization/json_flag.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/internal/src/serialization/json_flag.cpp b/libs/internal/src/serialization/json_flag.cpp index 68cc4e78a..c681a76ac 100644 --- a/libs/internal/src/serialization/json_flag.cpp +++ b/libs/internal/src/serialization/json_flag.cpp @@ -204,8 +204,8 @@ tag_invoke(boost::json::value_to_tag< data_model::Flag::Variation variation{}; /* If there's no rollout, this must be a variation. If there's no - * data at all, we treat it as variation 0 (since upstream encoders may - * omit it. */ + * data at all, we must treat it as variation 0 (since upstream encoders may + * omit 0.) */ PARSE_FIELD_DEFAULT(variation, obj, "variation", 0); return std::make_optional(variation); From 0145069ff0d3f9a10727579956c362173e93903e Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 16 Aug 2023 17:33:11 -0700 Subject: [PATCH 23/55] add server.yml --- .github/workflows/server.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 1ae481daf..17cf8b19e 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -6,7 +6,7 @@ on: paths-ignore: - '**.md' #Do not need to run CI for markdown changes. pull_request: - branches: [ main, server-side ] + branches: [ main, server-side, cw/sc-206687/contract-tests ] paths-ignore: - '**.md' From 57f5f9c4702ff216b35302030d8658c538a4a1b7 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 18 Aug 2023 14:37:34 -0700 Subject: [PATCH 24/55] initial implementation of AllFlagsState method and structs --- .../include/launchdarkly/data_model/flag.hpp | 16 ++- .../launchdarkly/server_side/client.hpp | 28 +++- .../server_side/feature_flags_state.hpp | 50 +++++++ .../server_side/feature_flags_state_json.hpp | 16 +++ libs/server-sdk/src/CMakeLists.txt | 2 + libs/server-sdk/src/client.cpp | 6 +- libs/server-sdk/src/client_impl.cpp | 125 ++++++++++++++++-- libs/server-sdk/src/client_impl.hpp | 6 +- libs/server-sdk/src/feature_flags_state.cpp | 42 ++++++ .../src/feature_flags_state_json.cpp | 45 +++++++ libs/server-sdk/tests/client_test.cpp | 5 + 11 files changed, 314 insertions(+), 27 deletions(-) create mode 100644 libs/server-sdk/include/launchdarkly/server_side/feature_flags_state.hpp create mode 100644 libs/server-sdk/include/launchdarkly/server_side/feature_flags_state_json.hpp create mode 100644 libs/server-sdk/src/feature_flags_state.cpp create mode 100644 libs/server-sdk/src/feature_flags_state_json.cpp diff --git a/libs/internal/include/launchdarkly/data_model/flag.hpp b/libs/internal/include/launchdarkly/data_model/flag.hpp index 78198fc22..5af2f95f8 100644 --- a/libs/internal/include/launchdarkly/data_model/flag.hpp +++ b/libs/internal/include/launchdarkly/data_model/flag.hpp @@ -16,8 +16,10 @@ namespace launchdarkly::data_model { struct Flag { - using Variation = std::uint64_t; - using Weight = std::uint64_t; + using Variation = std::int64_t; + using Weight = std::int64_t; + using FlagVersion = std::uint64_t; + using Date = std::uint64_t; struct Rollout { enum class Kind { @@ -56,12 +58,12 @@ struct Flag { struct Prerequisite { std::string key; - std::uint64_t variation; + Variation variation; }; struct Target { std::vector values; - std::uint64_t variation; + Variation variation; ContextKind contextKind; }; @@ -79,7 +81,7 @@ struct Flag { }; std::string key; - std::uint64_t version; + FlagVersion version; bool on; VariationOrRollout fallthrough; std::vector variations; @@ -88,13 +90,13 @@ struct Flag { std::vector targets; std::vector contextTargets; std::vector rules; - std::optional offVariation; + std::optional offVariation; bool clientSide; ClientSideAvailability clientSideAvailability; std::optional salt; bool trackEvents; bool trackEventsFallthrough; - std::optional debugEventsUntilDate; + std::optional debugEventsUntilDate; /** * Returns the flag's version. Satisfies ItemDescriptor template diff --git a/libs/server-sdk/include/launchdarkly/server_side/client.hpp b/libs/server-sdk/include/launchdarkly/server_side/client.hpp index 71614f4cd..a80fe5cca 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/client.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/client.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include #include @@ -13,6 +15,21 @@ namespace launchdarkly::server_side { +enum class AllFlagsStateOptions : std::uint8_t { + /** @brief Use default behavior. */ + Default = 0, + /** @brief Include evaluation reasons in the state object. By default, they + are not. */ + IncludeReasons = (1 << 0), + /** @brief Include detailed flag metadata only for flags with event tracking + * or debugging turned on. This reduces the size of the JSON data if you are + * passing the flag state to the front end. */ + DetailsOnlyForTrackedFlags = (1 << 1), + /** @brief Include only flags marked for use with the client-side SDK. By + default, all flags are included. */ + ClientSideOnly = (1 << 2) +}; + /** * Interface for the standard SDK client methods and properties. */ @@ -58,8 +75,9 @@ class IClient { * * @return A map from feature flag keys to values for the current context. */ - [[nodiscard]] virtual std::unordered_map AllFlagsState() - const = 0; + [[nodiscard]] virtual FeatureFlagsState AllFlagsState( + Context const& context, + enum AllFlagsStateOptions options = AllFlagsStateOptions::Default) = 0; /** * Tracks that the current context performed an event for the given event @@ -258,8 +276,10 @@ class Client : public IClient { [[nodiscard]] bool Initialized() const override; using FlagKey = std::string; - [[nodiscard]] std::unordered_map AllFlagsState() - const override; + [[nodiscard]] FeatureFlagsState AllFlagsState( + Context const& context, + enum AllFlagsStateOptions options = + AllFlagsStateOptions::Default) override; void Track(Context const& ctx, std::string event_name, diff --git a/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state.hpp b/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state.hpp new file mode 100644 index 000000000..fe90c27cf --- /dev/null +++ b/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace launchdarkly::server_side { + +class FeatureFlagsState { + public: + struct FlagState { + std::optional version; + std::optional variation; + std::optional reason; + bool track_events; + bool track_reason; + std::optional debug_events_until_date; + }; + + [[nodiscard]] bool Valid() const; + + [[nodiscard]] std::unordered_map const& FlagsState() + const; + [[nodiscard]] std::unordered_map const& Evaluations() + const; + + /** + * Creates an invalid instance of FeatureFlagsState. + */ + FeatureFlagsState(); + + FeatureFlagsState(std::unordered_map evaluations, + std::unordered_map flags_state); + + private: + bool const valid_; + const std::unordered_map flags_state_; + const std::unordered_map evaluations_; +}; + +bool operator==(FeatureFlagsState::FlagState const& lhs, + FeatureFlagsState::FlagState const& rhs); + +bool operator==(FeatureFlagsState const& lhs, FeatureFlagsState const& rhs); + +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state_json.hpp b/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state_json.hpp new file mode 100644 index 000000000..8556d259d --- /dev/null +++ b/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state_json.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include + +namespace launchdarkly::server_side { + +void tag_invoke(boost::json::value_from_tag const& unused, + boost::json::value& json_value, + server_side::FeatureFlagsState::FlagState const& state); + +void tag_invoke(boost::json::value_from_tag const& unused, + boost::json::value& json_value, + server_side::FeatureFlagsState const& state); +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/CMakeLists.txt b/libs/server-sdk/src/CMakeLists.txt index 0ff18837b..3508edcbd 100644 --- a/libs/server-sdk/src/CMakeLists.txt +++ b/libs/server-sdk/src/CMakeLists.txt @@ -11,6 +11,8 @@ add_library(${LIBNAME} boost.cpp client.cpp client_impl.cpp + feature_flags_state.cpp + feature_flags_state_json.cpp data_sources/data_source_update_sink.hpp data_store/data_store.hpp data_store/data_store_updater.hpp diff --git a/libs/server-sdk/src/client.cpp b/libs/server-sdk/src/client.cpp index c9b84acde..cf5968a3f 100644 --- a/libs/server-sdk/src/client.cpp +++ b/libs/server-sdk/src/client.cpp @@ -16,8 +16,10 @@ std::future Client::StartAsync() { } using FlagKey = std::string; -[[nodiscard]] std::unordered_map Client::AllFlagsState() const { - return client->AllFlagsState(); +[[nodiscard]] FeatureFlagsState Client::AllFlagsState( + Context const& context, + enum AllFlagsStateOptions options) { + return client->AllFlagsState(context, options); } void Client::Track(Context const& ctx, diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index c681fe317..5faf86bba 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -34,6 +34,14 @@ using launchdarkly::config::shared::built::DataSourceConfig; using launchdarkly::config::shared::built::HttpProperties; using launchdarkly::server_side::data_sources::DataSourceStatus; +bool IsExperimentationEnabled(data_model::Flag const& flag, + std::optional const& reason); + +AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, + AllFlagsStateOptions rhs); + +bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag); + static std::shared_ptr<::launchdarkly::data_sources::IDataSource> MakeDataSource(HttpProperties const& http_properties, Config const& config, @@ -157,15 +165,97 @@ bool ClientImpl::Initialized() const { return IsInitializedSuccessfully(status_manager_.Status().State()); } -std::unordered_map ClientImpl::AllFlagsState() const { +FeatureFlagsState ClientImpl::AllFlagsState(Context const& context, + enum AllFlagsStateOptions options) { std::unordered_map result; - // TODO: implement all flags state (and update signature). - // for (auto& [key, descriptor] : memory_store_.AllFlags()) { - // if (descriptor->item) { - // result.try_emplace(key, descriptor->item->Value()); - // } - // } - return result; + + if (config_.Offline()) { + LD_LOG(logger_, LogLevel::kWarn) + << "AllFlagsState() called, but client is in offline mode. " + "Returning empty state"; + return FeatureFlagsState{}; + } + + if (!Initialized()) { + LD_LOG(logger_, LogLevel::kWarn) + << "AllFlagsState() called before client has finished " + "initializing! Feature store unavailable - returning empty " + "state"; + return FeatureFlagsState{}; + } + + std::unordered_map flags_state; + std::unordered_map evaluations; + + for (auto [key, flag_desc] : memory_store_.AllFlags()) { + if (!flag_desc || !flag_desc->item) { + continue; + } + auto const& flag = (*flag_desc->item); + if (IsSet(options, AllFlagsStateOptions::ClientSideOnly) && + !flag.clientSideAvailability.usingEnvironmentId) { + continue; + } + + auto detail = evaluator_.Evaluate(flag, context); + + bool require_experiment_data = + IsExperimentationEnabled(flag, detail.Reason()); + bool track_events = flag.trackEvents || require_experiment_data; + bool track_reason = require_experiment_data; + bool currently_debugging = false; + if (flag.debugEventsUntilDate) { + auto now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + currently_debugging = *flag.debugEventsUntilDate > now; + } + + bool omit_details = + (IsSet(options, AllFlagsStateOptions::DetailsOnlyForTrackedFlags) && + !(track_events || track_reason || currently_debugging)); + + std::optional reason = + (!IsSet(options, AllFlagsStateOptions::IncludeReasons) && + !track_reason) + ? std::nullopt + : detail.Reason(); + + if (omit_details) { + reason = std::nullopt; + /* version = std::nullopt; */ + } + + evaluations.emplace(key, detail.Value()); + flags_state.emplace( + key, FeatureFlagsState::FlagState{ + flag.version, detail.VariationIndex(), reason, + track_events, track_reason, flag.debugEventsUntilDate}); + } + + return FeatureFlagsState{std::move(evaluations), std::move(flags_state)}; +} + +bool IsExperimentationEnabled(data_model::Flag const& flag, + std::optional const& reason) { + if (!reason) { + return false; + } + if (reason->InExperiment()) { + return true; + } + switch (reason->Kind()) { + case EvaluationReason::Kind::kFallthrough: + return flag.trackEventsFallthrough; + case EvaluationReason::Kind::kRuleMatch: + if (!reason->RuleIndex() || + reason->RuleIndex() >= flag.rules.size()) { + return false; + } + return flag.rules.at(*reason->RuleIndex()).trackEvents; + default: + return false; + } } void ClientImpl::TrackInternal(Context const& ctx, @@ -298,18 +388,18 @@ EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, detail.ReasonKindIs(EvaluationReason::Kind::kRuleMatch); if (track_rule_match) { - assert(detail.Reason()->RuleIndex().has_value() && + auto const& rule_index = detail.Reason()->RuleIndex(); + assert(rule_index && "evaluation algorithm must produce a rule index in the case of " "rule " "match"); - assert(*detail.Reason()->RuleIndex() < flag.rules.size() && + assert(*rule_index < flag.rules.size() && "evaluation algorithm must produce a valid rule index in the " "case of " "rule match"); - track_rule_match = - flag.rules.at(*detail.Reason()->RuleIndex()).trackEvents; + track_rule_match = flag.rules.at(*rule_index).trackEvents; } event.require_full_event = @@ -402,4 +492,15 @@ ClientImpl::~ClientImpl() { run_thread_.join(); } +AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, + AllFlagsStateOptions rhs) { + return static_cast( + static_cast>(lhs) & + static_cast>(rhs)); +} + +bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag) { + return (options & flag) == flag; +} + } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/client_impl.hpp b/libs/server-sdk/src/client_impl.hpp index 40c5a69b7..0cfde5359 100644 --- a/libs/server-sdk/src/client_impl.hpp +++ b/libs/server-sdk/src/client_impl.hpp @@ -44,8 +44,10 @@ class ClientImpl : public IClient { bool Initialized() const override; using FlagKey = std::string; - [[nodiscard]] std::unordered_map AllFlagsState() - const override; + [[nodiscard]] FeatureFlagsState AllFlagsState( + Context const& context, + enum AllFlagsStateOptions options = + AllFlagsStateOptions::Default) override; void Track(Context const& ctx, std::string event_name, diff --git a/libs/server-sdk/src/feature_flags_state.cpp b/libs/server-sdk/src/feature_flags_state.cpp new file mode 100644 index 000000000..93041d4f7 --- /dev/null +++ b/libs/server-sdk/src/feature_flags_state.cpp @@ -0,0 +1,42 @@ +#include + +namespace launchdarkly::server_side { +FeatureFlagsState::FeatureFlagsState() + : valid_(false), evaluations_(), flags_state_() {} + +FeatureFlagsState::FeatureFlagsState( + std::unordered_map evaluations, + std::unordered_map flags_state) + : valid_(true), + evaluations_(std::move(evaluations)), + flags_state_(std::move(flags_state)) {} + +bool FeatureFlagsState::Valid() const { + return valid_; +} + +std::unordered_map const& +FeatureFlagsState::FlagsState() const { + return flags_state_; +} + +std::unordered_map const& FeatureFlagsState::Evaluations() + const { + return evaluations_; +} + +bool operator==(FeatureFlagsState const& lhs, FeatureFlagsState const& rhs) { + return lhs.Valid() == rhs.Valid() && + lhs.Evaluations() == rhs.Evaluations() && + lhs.FlagsState() == rhs.FlagsState(); +} + +bool operator==(FeatureFlagsState::FlagState const& lhs, + FeatureFlagsState::FlagState const& rhs) { + return lhs.version == rhs.version && lhs.variation == rhs.variation && + lhs.reason == rhs.reason && lhs.track_events == rhs.track_events && + lhs.track_reason == rhs.track_reason && + lhs.debug_events_until_date == rhs.debug_events_until_date; +} + +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/feature_flags_state_json.cpp b/libs/server-sdk/src/feature_flags_state_json.cpp new file mode 100644 index 000000000..23730b8e5 --- /dev/null +++ b/libs/server-sdk/src/feature_flags_state_json.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +namespace launchdarkly::server_side { + +void tag_invoke(boost::json::value_from_tag const& unused, + boost::json::value& json_value, + server_side::FeatureFlagsState::FlagState const& state) { + auto& obj = json_value.emplace_object(); + if (state.version) { + obj.emplace("version", *state.version); + } + if (state.variation) { + obj.emplace("variation", *state.variation); + } + if (state.reason) { + obj.emplace("reason", boost::json::value_from(*state.reason)); + } + if (state.track_events) { + obj.emplace("track_events", true); + } + if (state.track_reason) { + obj.emplace("track_reason", true); + } + if (state.debug_events_until_date) { + obj.emplace("debug_events_until_date", + boost::json::value_from(*state.debug_events_until_date)); + } +} + +void tag_invoke(boost::json::value_from_tag const& unused, + boost::json::value& json_value, + server_side::FeatureFlagsState const& state) { + auto& obj = json_value.emplace_object(); + obj.emplace("$valid", state.Valid()); + + obj.emplace("$flagsState", boost::json::value_from(state.FlagsState())); + + for (auto const [k, v] : state.Evaluations()) { + obj.emplace(k, boost::json::value_from(v)); + } +} +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/tests/client_test.cpp b/libs/server-sdk/tests/client_test.cpp index 315c0a40d..719f6ad0a 100644 --- a/libs/server-sdk/tests/client_test.cpp +++ b/libs/server-sdk/tests/client_test.cpp @@ -68,3 +68,8 @@ TEST_F(ClientTest, JsonVariationDefaultPassesThrough) { ASSERT_EQ(*client_.JsonVariationDetail(context_, flag, v), v); } } + +TEST_F(ClientTest, AllFlagsStateDefaultOptions) { + auto flags = client_.AllFlagsState(context_); + ASSERT_FALSE(flags.Valid()); +} From e9c068de8e87306bc232c730fb5ecb10630e1d18 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 18 Aug 2023 15:15:34 -0700 Subject: [PATCH 25/55] begin adding unit tests --- libs/server-sdk/src/CMakeLists.txt | 1 + .../all_flags_state_builder.cpp | 104 ++++++++++++++++++ .../all_flags_state_builder.hpp | 22 ++++ libs/server-sdk/src/client_impl.cpp | 94 +--------------- libs/server-sdk/tests/all_flags_state.cpp | 76 +++++++++++++ libs/server-sdk/tests/client_test.cpp | 4 +- libs/server-sdk/tests/test_store.hpp | 12 ++ 7 files changed, 221 insertions(+), 92 deletions(-) create mode 100644 libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp create mode 100644 libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp create mode 100644 libs/server-sdk/tests/all_flags_state.cpp diff --git a/libs/server-sdk/src/CMakeLists.txt b/libs/server-sdk/src/CMakeLists.txt index 3508edcbd..39be5987c 100644 --- a/libs/server-sdk/src/CMakeLists.txt +++ b/libs/server-sdk/src/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(${LIBNAME} client_impl.cpp feature_flags_state.cpp feature_flags_state_json.cpp + all_flags_state/all_flags_state_builder.cpp data_sources/data_source_update_sink.hpp data_store/data_store.hpp data_store/data_store_updater.hpp diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp new file mode 100644 index 000000000..7944419d4 --- /dev/null +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp @@ -0,0 +1,104 @@ +#include "all_flags_state_builder.hpp" + +namespace launchdarkly::server_side { + +bool IsExperimentationEnabled(data_model::Flag const& flag, + std::optional const& reason); +bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag); + +AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, + AllFlagsStateOptions rhs); + +AllFlagsStateBuilder::AllFlagsStateBuilder(evaluation::Evaluator& evaluator, + data_store::IDataStore const& store) + : evaluator_(evaluator), store_(store) {} + +FeatureFlagsState AllFlagsStateBuilder::Build( + launchdarkly::Context const& context, + enum AllFlagsStateOptions options) { + std::unordered_map flags_state; + std::unordered_map evaluations; + + for (auto [key, flag_desc] : store_.AllFlags()) { + if (!flag_desc || !flag_desc->item) { + continue; + } + auto const& flag = (*flag_desc->item); + if (IsSet(options, AllFlagsStateOptions::ClientSideOnly) && + !flag.clientSideAvailability.usingEnvironmentId) { + continue; + } + + auto detail = evaluator_.Evaluate(flag, context); + + bool require_experiment_data = + IsExperimentationEnabled(flag, detail.Reason()); + bool track_events = flag.trackEvents || require_experiment_data; + bool track_reason = require_experiment_data; + bool currently_debugging = false; + if (flag.debugEventsUntilDate) { + auto now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + currently_debugging = *flag.debugEventsUntilDate > now; + } + + bool omit_details = + (IsSet(options, AllFlagsStateOptions::DetailsOnlyForTrackedFlags) && + !(track_events || track_reason || currently_debugging)); + + std::optional reason = + (!IsSet(options, AllFlagsStateOptions::IncludeReasons) && + !track_reason) + ? std::nullopt + : detail.Reason(); + + if (omit_details) { + reason = std::nullopt; + /* version = std::nullopt; */ + } + + evaluations.emplace(key, detail.Value()); + flags_state.emplace( + key, FeatureFlagsState::FlagState{ + flag.version, detail.VariationIndex(), reason, + track_events, track_reason, flag.debugEventsUntilDate}); + } + + return FeatureFlagsState{std::move(evaluations), std::move(flags_state)}; +} + +bool IsExperimentationEnabled(data_model::Flag const& flag, + std::optional const& reason) { + if (!reason) { + return false; + } + if (reason->InExperiment()) { + return true; + } + switch (reason->Kind()) { + case EvaluationReason::Kind::kFallthrough: + return flag.trackEventsFallthrough; + case EvaluationReason::Kind::kRuleMatch: + if (!reason->RuleIndex() || + reason->RuleIndex() >= flag.rules.size()) { + return false; + } + return flag.rules.at(*reason->RuleIndex()).trackEvents; + default: + return false; + } +} + +AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, + AllFlagsStateOptions rhs) { + return static_cast( + static_cast>(lhs) & + static_cast>(rhs)); +} + +bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag) { + return (options & flag) == flag; +} + +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp new file mode 100644 index 000000000..c3a137758 --- /dev/null +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "../data_store/data_store.hpp" +#include "../evaluation/evaluator.hpp" + +namespace launchdarkly::server_side { +class AllFlagsStateBuilder { + public: + AllFlagsStateBuilder(evaluation::Evaluator& evaluator, + data_store::IDataStore const& store); + + [[nodiscard]] FeatureFlagsState Build(Context const& context, + enum AllFlagsStateOptions options); + + private: + evaluation::Evaluator& evaluator_; + data_store::IDataStore const& store_; +}; +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 5faf86bba..5b321949d 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -6,6 +6,7 @@ #include "client_impl.hpp" +#include "all_flags_state/all_flags_state_builder.hpp" #include "data_sources/null_data_source.hpp" #include "data_sources/polling_data_source.hpp" #include "data_sources/streaming_data_source.hpp" @@ -34,14 +35,6 @@ using launchdarkly::config::shared::built::DataSourceConfig; using launchdarkly::config::shared::built::HttpProperties; using launchdarkly::server_side::data_sources::DataSourceStatus; -bool IsExperimentationEnabled(data_model::Flag const& flag, - std::optional const& reason); - -AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, - AllFlagsStateOptions rhs); - -bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag); - static std::shared_ptr<::launchdarkly::data_sources::IDataSource> MakeDataSource(HttpProperties const& http_properties, Config const& config, @@ -184,78 +177,9 @@ FeatureFlagsState ClientImpl::AllFlagsState(Context const& context, return FeatureFlagsState{}; } - std::unordered_map flags_state; - std::unordered_map evaluations; + AllFlagsStateBuilder builder{evaluator_, memory_store_}; - for (auto [key, flag_desc] : memory_store_.AllFlags()) { - if (!flag_desc || !flag_desc->item) { - continue; - } - auto const& flag = (*flag_desc->item); - if (IsSet(options, AllFlagsStateOptions::ClientSideOnly) && - !flag.clientSideAvailability.usingEnvironmentId) { - continue; - } - - auto detail = evaluator_.Evaluate(flag, context); - - bool require_experiment_data = - IsExperimentationEnabled(flag, detail.Reason()); - bool track_events = flag.trackEvents || require_experiment_data; - bool track_reason = require_experiment_data; - bool currently_debugging = false; - if (flag.debugEventsUntilDate) { - auto now = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - currently_debugging = *flag.debugEventsUntilDate > now; - } - - bool omit_details = - (IsSet(options, AllFlagsStateOptions::DetailsOnlyForTrackedFlags) && - !(track_events || track_reason || currently_debugging)); - - std::optional reason = - (!IsSet(options, AllFlagsStateOptions::IncludeReasons) && - !track_reason) - ? std::nullopt - : detail.Reason(); - - if (omit_details) { - reason = std::nullopt; - /* version = std::nullopt; */ - } - - evaluations.emplace(key, detail.Value()); - flags_state.emplace( - key, FeatureFlagsState::FlagState{ - flag.version, detail.VariationIndex(), reason, - track_events, track_reason, flag.debugEventsUntilDate}); - } - - return FeatureFlagsState{std::move(evaluations), std::move(flags_state)}; -} - -bool IsExperimentationEnabled(data_model::Flag const& flag, - std::optional const& reason) { - if (!reason) { - return false; - } - if (reason->InExperiment()) { - return true; - } - switch (reason->Kind()) { - case EvaluationReason::Kind::kFallthrough: - return flag.trackEventsFallthrough; - case EvaluationReason::Kind::kRuleMatch: - if (!reason->RuleIndex() || - reason->RuleIndex() >= flag.rules.size()) { - return false; - } - return flag.rules.at(*reason->RuleIndex()).trackEvents; - default: - return false; - } + return builder.Build(context, options); } void ClientImpl::TrackInternal(Context const& ctx, @@ -491,16 +415,4 @@ ClientImpl::~ClientImpl() { // TODO: Probably not the best. run_thread_.join(); } - -AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, - AllFlagsStateOptions rhs) { - return static_cast( - static_cast>(lhs) & - static_cast>(rhs)); -} - -bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag) { - return (options & flag) == flag; -} - } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/tests/all_flags_state.cpp b/libs/server-sdk/tests/all_flags_state.cpp new file mode 100644 index 000000000..3a3beee02 --- /dev/null +++ b/libs/server-sdk/tests/all_flags_state.cpp @@ -0,0 +1,76 @@ +#include + +#include "all_flags_state/all_flags_state_builder.hpp" +#include "data_store/memory_store.hpp" +#include "test_store.hpp" + +#include +#include +#include + +#include + +using namespace launchdarkly; +using namespace launchdarkly::server_side; + +class AllFlagsTest : public ::testing::Test { + protected: + AllFlagsTest() + : logger_(logging::NullLogger()), + store_(test_store::Empty()), + evaluator_(logger_, *store_), + builder_(evaluator_, *store_), + context_(ContextBuilder().Kind("shadow", "cat").Build()) {} + Logger logger_; + std::unique_ptr store_; + evaluation::Evaluator evaluator_; + AllFlagsStateBuilder builder_; + Context context_; +}; + +TEST_F(AllFlagsTest, Empty) { + auto state = builder_.Build(context_, AllFlagsStateOptions::Default); + ASSERT_TRUE(state.Valid()); + ASSERT_TRUE(state.FlagsState().empty()); + ASSERT_TRUE(state.Evaluations().empty()); +} + +TEST_F(AllFlagsTest, DefaultOptions) { + auto mem_store = static_cast(store_.get()); + mem_store->Upsert("myFlag", test_store::Flag(R"({ + "key": "myFlag", + "version": 42, + "on": true, + "targets": [], + "rules": [], + "prerequisites": [], + "fallthrough": {"variation": 1}, + "offVariation": 0, + "variations": [false, true], + "clientSideAvailability": { + "usingMobileKey": false, + "usingEnvironmentId": false + }, + "salt": "kosher" + })")); + + auto state = builder_.Build(context_, AllFlagsStateOptions::Default); + ASSERT_TRUE(state.Valid()); + + auto expected = boost::json::parse(R"({ + "myFlag": true, + "$flagsState": { + "myFlag": { + "version": 42, + "variation": 1 + } + }, + "$valid": true + })"); + + auto got = boost::json::value_from(state); + + ASSERT_EQ(got, expected); +} + +TEST_F(AllFlagsTest, HandlesExperimentationReasons) {} diff --git a/libs/server-sdk/tests/client_test.cpp b/libs/server-sdk/tests/client_test.cpp index 719f6ad0a..ad1913db2 100644 --- a/libs/server-sdk/tests/client_test.cpp +++ b/libs/server-sdk/tests/client_test.cpp @@ -69,7 +69,9 @@ TEST_F(ClientTest, JsonVariationDefaultPassesThrough) { } } -TEST_F(ClientTest, AllFlagsStateDefaultOptions) { +TEST_F(ClientTest, AllFlagsStateNotValid) { + // Since we don't have any ability to insert into the data store at the time + // this test is written, just check that the result is not valid. auto flags = client_.AllFlagsState(context_); ASSERT_FALSE(flags.Valid()); } diff --git a/libs/server-sdk/tests/test_store.hpp b/libs/server-sdk/tests/test_store.hpp index 4b0a37c44..bfccc008e 100644 --- a/libs/server-sdk/tests/test_store.hpp +++ b/libs/server-sdk/tests/test_store.hpp @@ -16,4 +16,16 @@ std::unique_ptr TestData(); */ std::unique_ptr Empty(); +/** + * Returns a flag suitable for inserting into a memory store, parsed from the + * given JSON representation. + */ +data_store::FlagDescriptor Flag(char const* json); + +/** + * Returns a segment suitable for inserting into a memory store, parsed from the + * given JSON representation. + */ +data_store::SegmentDescriptor Segment(char const* json); + } // namespace launchdarkly::server_side::test_store From e578ad1d0025ffa53d048c3787384be4a23ed150 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Fri, 18 Aug 2023 15:40:07 -0700 Subject: [PATCH 26/55] unsupress contract tests due to lack of AllFlagsState API --- .../include/data_model/data_model.hpp | 2 + .../src/client_entity.cpp | 97 ++- .../test-suppressions.txt | 800 ++---------------- .../launchdarkly/server_side/client.hpp | 2 + ..._json.hpp => json_feature_flags_state.hpp} | 0 libs/server-sdk/src/client.cpp | 6 + .../src/feature_flags_state_json.cpp | 12 +- libs/server-sdk/tests/all_flags_state.cpp | 2 +- 8 files changed, 127 insertions(+), 794 deletions(-) rename libs/server-sdk/include/launchdarkly/server_side/{feature_flags_state_json.hpp => json_feature_flags_state.hpp} (100%) diff --git a/contract-tests/data-model/include/data_model/data_model.hpp b/contract-tests/data-model/include/data_model/data_model.hpp index 32cfadc36..b4baccb9b 100644 --- a/contract-tests/data-model/include/data_model/data_model.hpp +++ b/contract-tests/data-model/include/data_model/data_model.hpp @@ -212,11 +212,13 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateFlagResponse, reason); struct EvaluateAllFlagParams { + std::optional context; std::optional withReasons; std::optional clientSideOnly; std::optional detailsOnlyForTrackedFlags; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EvaluateAllFlagParams, + context, withReasons, clientSideOnly, detailsOnlyForTrackedFlags); diff --git a/contract-tests/server-contract-tests/src/client_entity.cpp b/contract-tests/server-contract-tests/src/client_entity.cpp index 4c9400a1e..26f3535c7 100644 --- a/contract-tests/server-contract-tests/src/client_entity.cpp +++ b/contract-tests/server-contract-tests/src/client_entity.cpp @@ -8,48 +8,48 @@ #include #include #include +#include #include #include #include -tl::expected -ParseContext(nlohmann::json value) { +using namespace launchdarkly::server_side; + +tl::expected ParseContext( + nlohmann::json value) { boost::system::error_code ec; auto boost_json_val = boost::json::parse(value.dump(), ec); if (ec) { return tl::make_unexpected(ec.what()); } - auto maybe_ctx = boost::json::value_to< + auto maybe_ctx = boost::json::value_to< tl::expected>( boost_json_val); - if (!maybe_ctx) { - return tl::make_unexpected( - launchdarkly::ErrorToString(maybe_ctx.error())); - } + if (!maybe_ctx) { + return tl::make_unexpected( + launchdarkly::ErrorToString(maybe_ctx.error())); + } - if (!maybe_ctx->Valid()) { - return tl::make_unexpected(maybe_ctx->errors()); - } + if (!maybe_ctx->Valid()) { + return tl::make_unexpected(maybe_ctx->errors()); + } - return *maybe_ctx; + return *maybe_ctx; } - ClientEntity::ClientEntity( std::unique_ptr client) : client_(std::move(client)) {} - tl::expected ClientEntity::Identify( IdentifyEventParams const& params) { boost::system::error_code ec; auto maybe_ctx = ParseContext(params.context); if (!maybe_ctx) { - return tl::make_unexpected( - maybe_ctx.error()); + return tl::make_unexpected(maybe_ctx.error()); } client_->Identify(*maybe_ctx); return nlohmann::json{}; @@ -167,7 +167,8 @@ tl::expected ClientEntity::Custom( return nlohmann::json{}; } - client_->Track(*maybe_ctx, params.eventKey, std::move(*data), *params.metricValue); + client_->Track(*maybe_ctx, params.eventKey, std::move(*data), + *params.metricValue); return nlohmann::json{}; } @@ -177,17 +178,37 @@ tl::expected ClientEntity::EvaluateAll( boost::ignore_unused(params); - // TODO: fix when AllFlags available -// for (auto& [key, value] : client_->AllFlags()) { -// resp.state[key] = nlohmann::json::parse( -// boost::json::serialize(boost::json::value_from(value))); -// } + if (!params.context) { + return tl::make_unexpected("context is required"); + } + + auto maybe_ctx = ParseContext(*params.context); + if (!maybe_ctx) { + return tl::make_unexpected(maybe_ctx.error()); + } + + AllFlagsStateOptions options = AllFlagsStateOptions::Default; + if (params.withReasons.value_or(false)) { + options |= AllFlagsStateOptions::IncludeReasons; + } + if (params.clientSideOnly.value_or(false)) { + options |= AllFlagsStateOptions::ClientSideOnly; + } + if (params.detailsOnlyForTrackedFlags.value_or(false)) { + options |= AllFlagsStateOptions::DetailsOnlyForTrackedFlags; + } + + auto state = client_->AllFlagsState(*maybe_ctx, options); + + resp.state = nlohmann::json::parse( + boost::json::serialize(boost::json::value_from(state))); return resp; } tl::expected ClientEntity::EvaluateDetail( - EvaluateFlagParams const& params, launchdarkly::Context const& ctx ) { + EvaluateFlagParams const& params, + launchdarkly::Context const& ctx) { auto const& key = params.flagKey; auto const& defaultVal = params.defaultValue; @@ -207,23 +228,23 @@ tl::expected ClientEntity::EvaluateDetail( } case ValueType::Int: { auto detail = - client_->IntVariationDetail(ctx,key, defaultVal.get()); + client_->IntVariationDetail(ctx, key, defaultVal.get()); result.value = *detail; reason = detail.Reason(); result.variationIndex = detail.VariationIndex(); break; } case ValueType::Double: { - auto detail = - client_->DoubleVariationDetail(ctx,key, defaultVal.get()); + auto detail = client_->DoubleVariationDetail( + ctx, key, defaultVal.get()); result.value = *detail; reason = detail.Reason(); result.variationIndex = detail.VariationIndex(); break; } case ValueType::String: { - auto detail = client_->StringVariationDetail(ctx, - key, defaultVal.get()); + auto detail = client_->StringVariationDetail( + ctx, key, defaultVal.get()); result.value = *detail; reason = detail.Reason(); result.variationIndex = detail.VariationIndex(); @@ -243,7 +264,8 @@ tl::expected ClientEntity::EvaluateDetail( * protocol, but boost::json in the SDK. We could swap over to * boost::json entirely here to remove the awkwardness. */ - auto detail = client_->JsonVariationDetail(ctx, key, *maybe_fallback); + auto detail = + client_->JsonVariationDetail(ctx, key, *maybe_fallback); auto serialized = boost::json::serialize(boost::json::value_from(*detail)); @@ -267,7 +289,6 @@ tl::expected ClientEntity::EvaluateDetail( } tl::expected ClientEntity::Evaluate( EvaluateFlagParams const& params) { - auto maybe_ctx = ParseContext(params.context); if (!maybe_ctx) { return tl::make_unexpected(maybe_ctx.error()); @@ -281,23 +302,24 @@ tl::expected ClientEntity::Evaluate( auto const& defaultVal = params.defaultValue; - EvaluateFlagResponse result; switch (params.valueType) { case ValueType::Bool: - result.value = client_->BoolVariation(*maybe_ctx, key, defaultVal.get()); + result.value = + client_->BoolVariation(*maybe_ctx, key, defaultVal.get()); break; case ValueType::Int: - result.value = client_->IntVariation(*maybe_ctx, key, defaultVal.get()); + result.value = + client_->IntVariation(*maybe_ctx, key, defaultVal.get()); break; case ValueType::Double: - result.value = - client_->DoubleVariation(*maybe_ctx,key, defaultVal.get()); + result.value = client_->DoubleVariation(*maybe_ctx, key, + defaultVal.get()); break; case ValueType::String: { - result.value = - client_->StringVariation(*maybe_ctx,key, defaultVal.get()); + result.value = client_->StringVariation( + *maybe_ctx, key, defaultVal.get()); break; } case ValueType::Any: @@ -313,7 +335,8 @@ tl::expected ClientEntity::Evaluate( * protocol, but boost::json in the SDK. We could swap over to * boost::json entirely here to remove the awkwardness. */ - auto evaluation = client_->JsonVariation(*maybe_ctx,key, *maybe_fallback); + auto evaluation = + client_->JsonVariation(*maybe_ctx, key, *maybe_fallback); auto serialized = boost::json::serialize(boost::json::value_from(evaluation)); diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index b982f0f7c..7f0c9f8c7 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -1,49 +1,7 @@ -evaluation/parameterized/bad attribute reference errors - tilde followed by nothing/test-flag/evaluate flag with detail -evaluation/parameterized/bad attribute reference errors - tilde followed by invalid escape character/test-flag/evaluate flag with detail -evaluation/parameterized/bad attribute reference errors - empty path component/test-flag/evaluate flag with detail -evaluation/parameterized/wrong type errors/want string, got number/want string, got number/evaluate flag with detail evaluation/parameterized/bad attribute reference errors - clause with no attribute/test-flag/evaluate flag with detail -evaluation/parameterized/clause kind matching/negated kind non-match for multi-kind context/negated kind non-match for multi-kind context/evaluate all flags -evaluation/parameterized/clause kind matching/negated kind match for single-kind context/negated kind match for single-kind context/evaluate all flags -evaluation/parameterized/wrong type errors/want bool, got number/want bool, got number/evaluate flag with detail -evaluation/parameterized/wrong type errors/want bool, got string/want bool, got string/evaluate flag with detail -evaluation/parameterized/wrong type errors/want int, got bool/want int, got bool/evaluate flag with detail -evaluation/parameterized/attribute references/top-level attribute with no slash prefix/top-level attribute with no slash prefix/evaluate all flags -evaluation/parameterized/attribute references/top-level attribute with slash prefix/top-level attribute with slash prefix/evaluate all flags -evaluation/parameterized/attribute references/top-level attribute name containing unescaped slash/top-level attribute name containing unescaped slash/evaluate all flags -evaluation/parameterized/attribute references/top-level attribute with slash prefix containing escaped slash/top-level attribute with slash prefix containing escaped slash/evaluate all flags -evaluation/parameterized/attribute references/top-level attribute name containing unescaped tilde/top-level attribute name containing unescaped tilde/evaluate all flags -evaluation/parameterized/attribute references/top-level attribute with slash prefix containing escaped tilde/top-level attribute with slash prefix containing escaped tilde/evaluate all flags -evaluation/parameterized/attribute references/attribute is interpreted as unescaped name if there is no contextKind/attribute is interpreted as unescaped name if there is no contextKind/evaluate all flags -evaluation/parameterized/attribute references/property in object/property in object/evaluate all flags -evaluation/parameterized/attribute references/property with escape sequence in object/property with escape sequence in object/evaluate all flags -evaluation/parameterized/attribute references/property in nested object/property in nested object/evaluate all flags -evaluation/parameterized/attribute references/property that is a numeric string/property that is a numeric string/evaluate all flags -evaluation/parameterized/attribute references/nonexistent property automatically fails clause/nonexistent property automatically fails clause/evaluate all flags -evaluation/parameterized/attribute references/property reference in non-object automatically fails clause/property reference in non-object automatically fails clause/evaluate all flags -evaluation/parameterized/attribute references/array index is not supported/array index is not supported/evaluate all flags -evaluation/parameterized/built-in attributes other than kind/key match/key match/evaluate all flags -evaluation/parameterized/built-in attributes other than kind/key non-match/key non-match/evaluate all flags -evaluation/parameterized/built-in attributes other than kind/name match/name match/evaluate all flags -evaluation/parameterized/built-in attributes other than kind/name non-match/name non-match/evaluate all flags -evaluation/parameterized/built-in attributes other than kind/name not found is non-match regardless of op and values/name not found is non-match regardless of op and values/evaluate all flags -evaluation/parameterized/built-in attributes other than kind/anonymous match false/anonymous match false/evaluate all flags -evaluation/parameterized/built-in attributes other than kind/anonymous match true/anonymous match true/evaluate all flags -evaluation/parameterized/clause kind matching/multi-kind context with user, clause has default kind, match/multi-kind context with user, clause has default kind, match/evaluate all flags -evaluation/parameterized/clause kind matching/multi-kind context with user, clause has default kind, no match/multi-kind context with user, clause has default kind, no match/evaluate all flags -evaluation/parameterized/clause kind matching/multi-kind context, clause specifies existing kind, match/multi-kind context, clause specifies existing kind, match/evaluate all flags -evaluation/parameterized/clause kind matching/multi-kind context, clause specifies existing kind, no match/multi-kind context, clause specifies existing kind, no match/evaluate all flags -evaluation/parameterized/clause kind matching/kind match for single-kind context/kind match for single-kind context/evaluate all flags -evaluation/parameterized/clause kind matching/kind match for single-kind context using matches operator/kind match for single-kind context using matches operator/evaluate all flags -evaluation/parameterized/clause kind matching/kind non-match for single-kind context using matches operator/kind non-match for single-kind context using matches operator/evaluate all flags -evaluation/parameterized/clause kind matching/kind match for multi-kind context using matches operator/kind match for multi-kind context using matches operator/evaluate all flags -evaluation/parameterized/clause kind matching/kind non-match for multi-kind context using matches operator/kind non-match for multi-kind context using matches operator/evaluate all flags -evaluation/parameterized/clause kind matching/kind non-match for single-kind context/kind non-match for single-kind context/evaluate all flags -evaluation/parameterized/clause kind matching/kind match for multi-kind context/kind match for multi-kind context/evaluate all flags -evaluation/parameterized/clause kind matching/kind non-match for multi-kind context/kind non-match for multi-kind context/evaluate all flags -evaluation/parameterized/wrong type errors/want int, got string/want int, got string/evaluate flag with detail -evaluation/parameterized/wrong type errors/want string, got bool/want string, got bool/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/flag not found/flag not found/evaluate flag with detail +evaluation/parameterized/bad attribute reference errors - empty path component/test-flag/evaluate flag with detail +evaluation/parameterized/bad attribute reference errors - tilde followed by invalid escape character/test-flag/evaluate flag with detail +evaluation/parameterized/bad attribute reference errors - tilde followed by nothing/test-flag/evaluate flag with detail evaluation/parameterized/evaluation failures (bool)/off variation too low/off variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (bool)/off variation too high/off variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (bool)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail @@ -53,9 +11,6 @@ evaluation/parameterized/evaluation failures (bool)/target variation too low/tar evaluation/parameterized/evaluation failures (bool)/target variation too high/target variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (bool)/rule variation too low/rule variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (bool)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate flag without detail -evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/rule rollout/rule rollout/evaluate flag without detail -evaluation/parameterized/evaluation failures (int)/flag not found/flag not found/evaluate flag with detail evaluation/parameterized/evaluation failures (int)/off variation too low/off variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (int)/off variation too high/off variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (int)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail @@ -65,7 +20,6 @@ evaluation/parameterized/evaluation failures (int)/target variation too low/targ evaluation/parameterized/evaluation failures (int)/target variation too high/target variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (int)/rule variation too low/rule variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (int)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/flag not found/flag not found/evaluate flag with detail evaluation/parameterized/evaluation failures (double)/off variation too low/off variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (double)/off variation too high/off variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (double)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail @@ -75,7 +29,6 @@ evaluation/parameterized/evaluation failures (double)/target variation too low/t evaluation/parameterized/evaluation failures (double)/target variation too high/target variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (double)/rule variation too low/rule variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (double)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/flag not found/flag not found/evaluate flag with detail evaluation/parameterized/evaluation failures (string)/off variation too low/off variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (string)/off variation too high/off variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (string)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail @@ -85,723 +38,40 @@ evaluation/parameterized/evaluation failures (string)/target variation too low/t evaluation/parameterized/evaluation failures (string)/target variation too high/target variation too high/evaluate flag with detail evaluation/parameterized/evaluation failures (string)/rule variation too low/rule variation too low/evaluate flag with detail evaluation/parameterized/evaluation failures (string)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/flag not found/flag not found/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/off variation too low/off variation too low/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/off variation too low/off variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/off variation too high/off variation too high/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/off variation too high/off variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/fallthrough variation too low/fallthrough variation too low/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/fallthrough variation too high/fallthrough variation too high/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/target variation too low/target variation too low/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/target variation too low/target variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/target variation too high/target variation too high/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/target variation too high/target variation too high/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/rule variation too low/rule variation too low/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/rule variation too low/rule variation too low/evaluate flag with detail +evaluation/parameterized/evaluation failures (any)/rule variation too high/rule variation too high/evaluate flag without detail evaluation/parameterized/evaluation failures (any)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/clause negation and value iteration/negated, match if expression does not match single clause value/negated, match if expression does not match single clause value/evaluate all flags -evaluation/parameterized/clause negation and value iteration/negated, non-match if expression matches for single clause value/negated, non-match if expression matches for single clause value/evaluate all flags -evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple clause values/match if expression matches any of multiple clause values/evaluate all flags -evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple context values with single clause value/match if expression matches any of multiple context values with single clause value/evaluate all flags -evaluation/parameterized/clause negation and value iteration/negated, non-match if expression matches any of multiple context values with single clause value/negated, non-match if expression matches any of multiple context values with single clause value/evaluate all flags -evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple context values with any of multiple clause values/match if expression matches any of multiple context values with any of multiple clause values/evaluate all flags -evaluation/parameterized/clause negation and value iteration/negated, non-match if expression matches any of multiple context values with any of multiple clause values/negated, non-match if expression matches any of multiple context values with any of multiple clause values/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06X00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before 2017-12-06X00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before 2017-12-06 fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 before 2017-12-06T00:00:00.000-07:00 fails/00:00:00.000-07.00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before 00:00:00.000-07.00 fails/2017-12-06T00:00:00.000-07:00 before 00:00:00.000-07.00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/badstring before 2017-12-06T00:00:00.000-07:00 fails/badstring before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before badstring fails/2017-12-06T00:00:00.000-07:00 before badstring fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/ before 2017-12-06T00:00:00.000-07:00 fails/ before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before fails/2017-12-06T00:00:00.000-07:00 before fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/2017-12-06X00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before 2017-12-06X00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z before 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06 before 2017-12-06T07:00:00.000Z fails/2017-12-06 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before 2017-12-06 fails/2017-12-06T07:00:00.000Z before 2017-12-06 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 before 2017-12-06T07:00:00.000Z fails/00:00:00.000-07.00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before 00:00:00.000-07.00 fails/2017-12-06T07:00:00.000Z before 00:00:00.000-07.00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/badstring before 2017-12-06T07:00:00.000Z fails/badstring before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before badstring fails/2017-12-06T07:00:00.000Z before badstring fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/ before 2017-12-06T07:00:00.000Z fails/ before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before fails/2017-12-06T07:00:00.000Z before fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before 2017-12-06T07:00:00.000Z fails/1512543600000 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z before 1512543600000 fails/2017-12-06T07:00:00.000Z before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 before 1512543600000 fails/2017-12-06X00:00:00.000-07:00 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before 2017-12-06X00:00:00.000-07:00 fails/1512543600000 before 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06 before 1512543600000 fails/2017-12-06 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before 2017-12-06 fails/1512543600000 before 2017-12-06 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 before 1512543600000 fails/00:00:00.000-07.00 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before 00:00:00.000-07.00 fails/1512543600000 before 00:00:00.000-07.00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/badstring before 1512543600000 fails/badstring before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before badstring fails/1512543600000 before badstring fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/ before 1512543600000 fails/ before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before fails/1512543600000 before fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before 1512543600000 fails/1512543600000 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 before 1512543600000 fails/1512543600000 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06X00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after 2017-12-06X00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06 after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after 2017-12-06 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 after 2017-12-06T00:00:00.000-07:00 fails/00:00:00.000-07.00 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after 00:00:00.000-07.00 fails/2017-12-06T00:00:00.000-07:00 after 00:00:00.000-07.00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/badstring after 2017-12-06T00:00:00.000-07:00 fails/badstring after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after badstring fails/2017-12-06T00:00:00.000-07:00 after badstring fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/ after 2017-12-06T00:00:00.000-07:00 fails/ after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after fails/2017-12-06T00:00:00.000-07:00 after fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/2017-12-06X00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after 2017-12-06X00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z after 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06 after 2017-12-06T07:00:00.000Z fails/2017-12-06 after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after 2017-12-06 fails/2017-12-06T07:00:00.000Z after 2017-12-06 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 after 2017-12-06T07:00:00.000Z fails/00:00:00.000-07.00 after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after 00:00:00.000-07.00 fails/2017-12-06T07:00:00.000Z after 00:00:00.000-07.00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/badstring after 2017-12-06T07:00:00.000Z fails/badstring after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after badstring fails/2017-12-06T07:00:00.000Z after badstring fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/ after 2017-12-06T07:00:00.000Z fails/ after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after fails/2017-12-06T07:00:00.000Z after fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after 2017-12-06T07:00:00.000Z fails/1512543600000 after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06T07:00:00.000Z after 1512543600000 fails/2017-12-06T07:00:00.000Z after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06X00:00:00.000-07:00 after 1512543600000 fails/2017-12-06X00:00:00.000-07:00 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after 2017-12-06X00:00:00.000-07:00 fails/1512543600000 after 2017-12-06X00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/2017-12-06 after 1512543600000 fails/2017-12-06 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after 2017-12-06 fails/1512543600000 after 2017-12-06 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/00:00:00.000-07.00 after 1512543600000 fails/00:00:00.000-07.00 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after 00:00:00.000-07.00 fails/1512543600000 after 00:00:00.000-07.00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/badstring after 1512543600000 fails/badstring after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after badstring fails/1512543600000 after badstring fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/ after 1512543600000 fails/ after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after fails/1512543600000 after fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after 1512543600000 fails/1512543600000 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad syntax/1512543600000 after 1512543600000 fails/1512543600000 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/null (null) before 2017-12-06T00:00:00.000-07:00 fails/null (null) before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 before null (null) fails/2017-12-06T00:00:00.000-07:00 before null (null) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/true (boolean) before 2017-12-06T00:00:00.000-07:00 fails/true (boolean) before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 before true (boolean) fails/2017-12-06T00:00:00.000-07:00 before true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/{} (object) before 2017-12-06T00:00:00.000-07:00 fails/{} (object) before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 before {} (object) fails/2017-12-06T00:00:00.000-07:00 before {} (object) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/null (null) before 2017-12-06T07:00:00.000Z fails/null (null) before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z before null (null) fails/2017-12-06T07:00:00.000Z before null (null) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/true (boolean) before 2017-12-06T07:00:00.000Z fails/true (boolean) before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z before true (boolean) fails/2017-12-06T07:00:00.000Z before true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/{} (object) before 2017-12-06T07:00:00.000Z fails/{} (object) before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z before {} (object) fails/2017-12-06T07:00:00.000Z before {} (object) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/null (null) before 1512543600000 fails/null (null) before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/1512543600000 before null (null) fails/1512543600000 before null (null) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/true (boolean) before 1512543600000 fails/true (boolean) before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/1512543600000 before true (boolean) fails/1512543600000 before true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/{} (object) before 1512543600000 fails/{} (object) before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/1512543600000 before {} (object) fails/1512543600000 before {} (object) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/null (null) after 2017-12-06T00:00:00.000-07:00 fails/null (null) after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 after null (null) fails/2017-12-06T00:00:00.000-07:00 after null (null) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/true (boolean) after 2017-12-06T00:00:00.000-07:00 fails/true (boolean) after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 after true (boolean) fails/2017-12-06T00:00:00.000-07:00 after true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/{} (object) after 2017-12-06T00:00:00.000-07:00 fails/{} (object) after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T00:00:00.000-07:00 after {} (object) fails/2017-12-06T00:00:00.000-07:00 after {} (object) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/null (null) after 2017-12-06T07:00:00.000Z fails/null (null) after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z after null (null) fails/2017-12-06T07:00:00.000Z after null (null) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/true (boolean) after 2017-12-06T07:00:00.000Z fails/true (boolean) after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z after true (boolean) fails/2017-12-06T07:00:00.000Z after true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/{} (object) after 2017-12-06T07:00:00.000Z fails/{} (object) after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/2017-12-06T07:00:00.000Z after {} (object) fails/2017-12-06T07:00:00.000Z after {} (object) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/null (null) after 1512543600000 fails/null (null) after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/1512543600000 after null (null) fails/1512543600000 after null (null) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/true (boolean) after 1512543600000 fails/true (boolean) after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/1512543600000 after true (boolean) fails/1512543600000 after true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/{} (object) after 1512543600000 fails/{} (object) after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - bad type/1512543600000 after {} (object) fails/1512543600000 after {} (object) fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00Z fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T07:00:00.000Z fails/1512543600000 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 1512543600000 fails/2017-12-06T07:00:00.000Z before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00Z fails/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00.000Z before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00Z before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00Z before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T07:00:00Z fails/1512543600000 before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 1512543600000 fails/2017-12-06T07:00:00Z before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/2017-12-06T00:00:00.000-07:00 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/1512543600000 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z before 1512543600000 fails/2017-12-06T07:00:00.000Z before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T07:00:00.000Z fails/1512543600000 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z before 1512543600000 fails/2017-12-06T07:00:00Z before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 before 2017-12-06T07:00:00Z fails/1512543600000 before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 before 1512543600000 fails/1512543600000 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 before 1512543600000 fails/1512543600000 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00Z after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00.000Z after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T07:00:00.000Z fails/1512543600000 after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 1512543600000 fails/2017-12-06T07:00:00.000Z after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:00:00Z after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T07:00:00.000Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 2017-12-06T07:00:00Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T07:00:00Z fails/1512543600000 after 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 1512543600000 fails/2017-12-06T07:00:00Z after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/2017-12-06T00:00:00.000-07:00 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/1512543600000 after 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00.000Z after 1512543600000 fails/2017-12-06T07:00:00.000Z after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T07:00:00.000Z fails/1512543600000 after 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/2017-12-06T07:00:00Z after 1512543600000 fails/2017-12-06T07:00:00Z after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 after 2017-12-06T07:00:00Z fails/1512543600000 after 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 after 1512543600000 fails/1512543600000 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - equal/1512543600000 after 1512543600000 fails/1512543600000 after 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:01:01.000-07:00 fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T00:01:01.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T00:00:00.000-07:00 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:01:01.000-07:00 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T00:01:01.000-07:00 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z after 2017-12-06T00:01:01.000-07:00 fails/2017-12-06T07:00:00.000Z after 2017-12-06T00:01:01.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00.000Z passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00.000Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T07:00:00.000Z before 2017-12-06T00:01:01.000-07:00 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 before 2017-12-06T07:00:00.000Z fails/2017-12-06T00:01:01.000-07:00 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z after 2017-12-06T00:01:01.000-07:00 fails/2017-12-06T07:00:00Z after 2017-12-06T00:01:01.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00Z passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T07:00:00Z before 2017-12-06T00:01:01.000-07:00 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 before 2017-12-06T07:00:00Z fails/2017-12-06T00:01:01.000-07:00 before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543600000 after 2017-12-06T00:01:01.000-07:00 fails/1512543600000 after 2017-12-06T00:01:01.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 1512543600000 passes/2017-12-06T00:01:01.000-07:00 after 1512543600000 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T00:01:01.000-07:00 passes/1512543600000 before 2017-12-06T00:01:01.000-07:00 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 before 1512543600000 fails/2017-12-06T00:01:01.000-07:00 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:01:01.000Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:01:01.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T07:01:01.000Z after 2017-12-06T00:00:00.000-07:00 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01.000Z passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01.000Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:01:01.000Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z after 2017-12-06T07:01:01.000Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:01:01.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00.000Z passes/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00.000Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01.000Z passes/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01.000Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:01:01.000Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z after 2017-12-06T07:01:01.000Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:01:01.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00Z passes/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T07:01:01.000Z passes/2017-12-06T07:00:00Z before 2017-12-06T07:01:01.000Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:01:01.000Z before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543600000 after 2017-12-06T07:01:01.000Z fails/1512543600000 after 2017-12-06T07:01:01.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 1512543600000 passes/2017-12-06T07:01:01.000Z after 1512543600000 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T07:01:01.000Z passes/1512543600000 before 2017-12-06T07:01:01.000Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z before 1512543600000 fails/2017-12-06T07:01:01.000Z before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:01:01Z fails/2017-12-06T00:00:00.000-07:00 after 2017-12-06T07:01:01Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T07:01:01Z after 2017-12-06T00:00:00.000-07:00 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01Z passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z before 2017-12-06T00:00:00.000-07:00 fails/2017-12-06T07:01:01Z before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z after 2017-12-06T07:01:01Z fails/2017-12-06T07:00:00.000Z after 2017-12-06T07:01:01Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T07:00:00.000Z passes/2017-12-06T07:01:01Z after 2017-12-06T07:00:00.000Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01Z passes/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z before 2017-12-06T07:00:00.000Z fails/2017-12-06T07:01:01Z before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z after 2017-12-06T07:01:01Z fails/2017-12-06T07:00:00Z after 2017-12-06T07:01:01Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T07:00:00Z passes/2017-12-06T07:01:01Z after 2017-12-06T07:00:00Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T07:01:01Z passes/2017-12-06T07:00:00Z before 2017-12-06T07:01:01Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z before 2017-12-06T07:00:00Z fails/2017-12-06T07:01:01Z before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543600000 after 2017-12-06T07:01:01Z fails/1512543600000 after 2017-12-06T07:01:01Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 1512543600000 passes/2017-12-06T07:01:01Z after 1512543600000 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T07:01:01Z passes/1512543600000 before 2017-12-06T07:01:01Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z before 1512543600000 fails/2017-12-06T07:01:01Z before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 after 1512543601000 fails/2017-12-06T00:00:00.000-07:00 after 1512543601000 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T00:00:00.000-07:00 passes/1512543601000 after 2017-12-06T00:00:00.000-07:00 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 1512543601000 passes/2017-12-06T00:00:00.000-07:00 before 1512543601000 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543601000 before 2017-12-06T00:00:00.000-07:00 fails/1512543601000 before 2017-12-06T00:00:00.000-07:00 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z after 1512543601000 fails/2017-12-06T07:00:00.000Z after 1512543601000 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T07:00:00.000Z passes/1512543601000 after 2017-12-06T07:00:00.000Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 1512543601000 passes/2017-12-06T07:00:00.000Z before 1512543601000 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543601000 before 2017-12-06T07:00:00.000Z fails/1512543601000 before 2017-12-06T07:00:00.000Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z after 1512543601000 fails/2017-12-06T07:00:00Z after 1512543601000 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T07:00:00Z passes/1512543601000 after 2017-12-06T07:00:00Z passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 1512543601000 passes/2017-12-06T07:00:00Z before 1512543601000 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543601000 before 2017-12-06T07:00:00Z fails/1512543601000 before 2017-12-06T07:00:00Z fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543600000 after 1512543601000 fails/1512543600000 after 1512543601000 fails/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543601000 after 1512543600000 passes/1512543601000 after 1512543600000 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543600000 before 1512543601000 passes/1512543600000 before 1512543601000 passes/evaluate all flags -evaluation/parameterized/operators - date - unequal/1512543601000 before 1512543600000 fails/1512543601000 before 1512543600000 fails/evaluate all flags -evaluation/parameterized/operators - equality (bool)/user matches clause with single value/user matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (bool)/user does not match clause with single value/user does not match clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (bool)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags -evaluation/parameterized/operators - equality (bool)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (bool)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags -evaluation/parameterized/operators - equality (int)/user matches clause with single value/user matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (int)/user does not match clause with single value/user does not match clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (int)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags -evaluation/parameterized/operators - equality (int)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (int)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags -evaluation/parameterized/operators - equality (double)/user matches clause with single value/user matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (double)/user does not match clause with single value/user does not match clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (double)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags -evaluation/parameterized/operators - equality (double)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (double)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags -evaluation/parameterized/operators - equality (string)/user matches clause with single value/user matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (string)/user does not match clause with single value/user does not match clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (string)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags -evaluation/parameterized/operators - equality (string)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (string)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags -evaluation/parameterized/operators - equality (any)/user matches clause with single value/user matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (any)/user does not match clause with single value/user does not match clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (any)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate all flags -evaluation/parameterized/operators - equality (any)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate all flags -evaluation/parameterized/operators - equality (any)/user with no value for attribute does not match clause/user with no value for attribute does not match clause/evaluate all flags -evaluation/parameterized/operators - numeric/99 lessThan 100/99 lessThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/100 lessThan 100/100 lessThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/100.1 lessThan 100/100.1 lessThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/101 lessThan 100/101 lessThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/99 lessThanOrEqual 100/99 lessThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/100 lessThanOrEqual 100/100 lessThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/100.1 lessThanOrEqual 100/100.1 lessThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/101 lessThanOrEqual 100/101 lessThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/99 greaterThan 100/99 greaterThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/100 greaterThan 100/100 greaterThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/100.1 greaterThan 100/100.1 greaterThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/101 greaterThan 100/101 greaterThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/99 greaterThanOrEqual 100/99 greaterThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/100 greaterThanOrEqual 100/100 greaterThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/100.1 greaterThanOrEqual 100/100.1 greaterThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/101 greaterThanOrEqual 100/101 greaterThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/99 lessThan 100/99 lessThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/100 lessThanOrEqual 100/100 lessThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - numeric/101 greaterThan 100/101 greaterThan 100/evaluate all flags -evaluation/parameterized/operators - numeric/100 greaterThanOrEqual 100/100 greaterThanOrEqual 100/evaluate all flags -evaluation/parameterized/operators - semver - bad syntax/02.0.0 semVerEqual 2.0.0 fails/02.0.0 semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad syntax/2.0.0 semVerEqual 02.0.0 fails/2.0.0 semVerEqual 02.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad syntax/v2.0.0 semVerEqual 2.0.0 fails/v2.0.0 semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad syntax/2.0.0 semVerEqual v2.0.0 fails/2.0.0 semVerEqual v2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad syntax/2.0.0.0 semVerEqual 2.0.0 fails/2.0.0.0 semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad syntax/2.0.0 semVerEqual 2.0.0.0 fails/2.0.0 semVerEqual 2.0.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerEqual 2.0.0 fails/null (null) semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerEqual null (null) fails/2.0.0 semVerEqual null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerEqual 2.0.0 fails/true (boolean) semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerEqual true (boolean) fails/2.0.0 semVerEqual true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerEqual 2.0.0 fails/2 (number) semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerEqual 2 (number) fails/2.0.0 semVerEqual 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerEqual 2.0.0 fails/{} (object) semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerEqual {} (object) fails/2.0.0 semVerEqual {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerEqual 2.0 fails/null (null) semVerEqual 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerEqual null (null) fails/2.0 semVerEqual null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerEqual 2.0 fails/true (boolean) semVerEqual 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerEqual true (boolean) fails/2.0 semVerEqual true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerEqual 2.0 fails/2 (number) semVerEqual 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerEqual 2 (number) fails/2.0 semVerEqual 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerEqual 2.0 fails/{} (object) semVerEqual 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerEqual {} (object) fails/2.0 semVerEqual {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerEqual 2 fails/null (null) semVerEqual 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerEqual null (null) fails/2 semVerEqual null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerEqual 2 fails/true (boolean) semVerEqual 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerEqual true (boolean) fails/2 semVerEqual true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerEqual 2 fails/2 (number) semVerEqual 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerEqual 2 (number) fails/2 semVerEqual 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerEqual 2 fails/{} (object) semVerEqual 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerEqual {} (object) fails/2 semVerEqual {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerLessThan 2.0.0 fails/null (null) semVerLessThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerLessThan null (null) fails/2.0.0 semVerLessThan null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerLessThan 2.0.0 fails/true (boolean) semVerLessThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerLessThan true (boolean) fails/2.0.0 semVerLessThan true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerLessThan 2.0.0 fails/2 (number) semVerLessThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerLessThan 2 (number) fails/2.0.0 semVerLessThan 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerLessThan 2.0.0 fails/{} (object) semVerLessThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerLessThan {} (object) fails/2.0.0 semVerLessThan {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerLessThan 2.0 fails/null (null) semVerLessThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerLessThan null (null) fails/2.0 semVerLessThan null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerLessThan 2.0 fails/true (boolean) semVerLessThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerLessThan true (boolean) fails/2.0 semVerLessThan true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerLessThan 2.0 fails/2 (number) semVerLessThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerLessThan 2 (number) fails/2.0 semVerLessThan 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerLessThan 2.0 fails/{} (object) semVerLessThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerLessThan {} (object) fails/2.0 semVerLessThan {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerLessThan 2 fails/null (null) semVerLessThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerLessThan null (null) fails/2 semVerLessThan null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerLessThan 2 fails/true (boolean) semVerLessThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerLessThan true (boolean) fails/2 semVerLessThan true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerLessThan 2 fails/2 (number) semVerLessThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerLessThan 2 (number) fails/2 semVerLessThan 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerLessThan 2 fails/{} (object) semVerLessThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerLessThan {} (object) fails/2 semVerLessThan {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerGreaterThan 2.0.0 fails/null (null) semVerGreaterThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerGreaterThan null (null) fails/2.0.0 semVerGreaterThan null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerGreaterThan 2.0.0 fails/true (boolean) semVerGreaterThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerGreaterThan true (boolean) fails/2.0.0 semVerGreaterThan true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerGreaterThan 2.0.0 fails/2 (number) semVerGreaterThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerGreaterThan 2 (number) fails/2.0.0 semVerGreaterThan 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerGreaterThan 2.0.0 fails/{} (object) semVerGreaterThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0.0 semVerGreaterThan {} (object) fails/2.0.0 semVerGreaterThan {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerGreaterThan 2.0 fails/null (null) semVerGreaterThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerGreaterThan null (null) fails/2.0 semVerGreaterThan null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerGreaterThan 2.0 fails/true (boolean) semVerGreaterThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerGreaterThan true (boolean) fails/2.0 semVerGreaterThan true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerGreaterThan 2.0 fails/2 (number) semVerGreaterThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerGreaterThan 2 (number) fails/2.0 semVerGreaterThan 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerGreaterThan 2.0 fails/{} (object) semVerGreaterThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2.0 semVerGreaterThan {} (object) fails/2.0 semVerGreaterThan {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/null (null) semVerGreaterThan 2 fails/null (null) semVerGreaterThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerGreaterThan null (null) fails/2 semVerGreaterThan null (null) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/true (boolean) semVerGreaterThan 2 fails/true (boolean) semVerGreaterThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerGreaterThan true (boolean) fails/2 semVerGreaterThan true (boolean) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 (number) semVerGreaterThan 2 fails/2 (number) semVerGreaterThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerGreaterThan 2 (number) fails/2 semVerGreaterThan 2 (number) fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/{} (object) semVerGreaterThan 2 fails/{} (object) semVerGreaterThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - bad type/2 semVerGreaterThan {} (object) fails/2 semVerGreaterThan {} (object) fails/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0.0 passes/2.0.0 semVerEqual 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0.0 passes/2.0.0 semVerEqual 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0 semVerEqual 2.0.0 passes/2.0 semVerEqual 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0 passes/2.0.0 semVerEqual 2.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2 semVerEqual 2.0.0 passes/2 semVerEqual 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2 passes/2.0.0 semVerEqual 2 passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0+build semVerEqual 2.0.0 passes/2.0.0+build semVerEqual 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0.0+build passes/2.0.0 semVerEqual 2.0.0+build passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0.0-rc passes/2.0.0-rc semVerEqual 2.0.0-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0.0-rc passes/2.0.0-rc semVerEqual 2.0.0-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0-rc semVerEqual 2.0.0-rc passes/2.0-rc semVerEqual 2.0.0-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0-rc passes/2.0.0-rc semVerEqual 2.0-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2-rc semVerEqual 2.0.0-rc passes/2-rc semVerEqual 2.0.0-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2-rc passes/2.0.0-rc semVerEqual 2-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0-rc+build semVerEqual 2.0.0-rc passes/2.0.0-rc+build semVerEqual 2.0.0-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0.0-rc+build passes/2.0.0-rc semVerEqual 2.0.0-rc+build passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1.0.0 semVerEqual 2.0.0 fails/1.0.0 semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerEqual 1.0.0 fails/2.0.0 semVerEqual 1.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1.0.0 semVerLessThan 2.0.0 passes/1.0.0 semVerLessThan 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 1.0.0 fails/2.0.0 semVerLessThan 1.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 1.0.0 passes/2.0.0 semVerGreaterThan 1.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1.0.0 semVerGreaterThan 2.0.0 fails/1.0.0 semVerGreaterThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1.0 semVerEqual 2.0 fails/1.0 semVerEqual 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0 semVerEqual 1.0 fails/2.0 semVerEqual 1.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1.0 semVerLessThan 2.0 passes/1.0 semVerLessThan 2.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0 semVerLessThan 1.0 fails/2.0 semVerLessThan 1.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0 semVerGreaterThan 1.0 passes/2.0 semVerGreaterThan 1.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1.0 semVerGreaterThan 2.0 fails/1.0 semVerGreaterThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1 semVerEqual 2 fails/1 semVerEqual 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2 semVerEqual 1 fails/2 semVerEqual 1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1 semVerLessThan 2 passes/1 semVerLessThan 2 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2 semVerLessThan 1 fails/2 semVerLessThan 1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2 semVerGreaterThan 1 passes/2 semVerGreaterThan 1 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/1 semVerGreaterThan 2 fails/1 semVerGreaterThan 2 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerEqual 2.1.0 fails/2.0.0 semVerEqual 2.1.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.1.0 semVerEqual 2.0.0 fails/2.1.0 semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 2.1.0 passes/2.0.0 semVerLessThan 2.1.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.1.0 semVerLessThan 2.0.0 fails/2.1.0 semVerLessThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.1.0 semVerGreaterThan 2.0.0 passes/2.1.0 semVerGreaterThan 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 2.1.0 fails/2.0.0 semVerGreaterThan 2.1.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0 semVerEqual 2.1 fails/2.0 semVerEqual 2.1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.1 semVerEqual 2.0 fails/2.1 semVerEqual 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0 semVerLessThan 2.1 passes/2.0 semVerLessThan 2.1 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.1 semVerLessThan 2.0 fails/2.1 semVerLessThan 2.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.1 semVerGreaterThan 2.0 passes/2.1 semVerGreaterThan 2.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0 semVerGreaterThan 2.1 fails/2.0 semVerGreaterThan 2.1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerEqual 2.0.1 fails/2.0.0 semVerEqual 2.0.1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.1 semVerEqual 2.0.0 fails/2.0.1 semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 2.0.1 passes/2.0.0 semVerLessThan 2.0.1 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.1 semVerLessThan 2.0.0 fails/2.0.1 semVerLessThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.1 semVerGreaterThan 2.0.0 passes/2.0.1 semVerGreaterThan 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 2.0.1 fails/2.0.0 semVerGreaterThan 2.0.1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerEqual 2.0.0 fails/2.0.0-rc semVerEqual 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerEqual 2.0.0-rc fails/2.0.0 semVerEqual 2.0.0-rc fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerLessThan 2.0.0 passes/2.0.0-rc semVerLessThan 2.0.0 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 2.0.0-rc fails/2.0.0 semVerLessThan 2.0.0-rc fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 2.0.0-rc passes/2.0.0 semVerGreaterThan 2.0.0-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerGreaterThan 2.0.0 fails/2.0.0-rc semVerGreaterThan 2.0.0 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerEqual 2.0.0-rc.1 fails/2.0.0-rc semVerEqual 2.0.0-rc.1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.1 semVerEqual 2.0.0-rc fails/2.0.0-rc.1 semVerEqual 2.0.0-rc fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerLessThan 2.0.0-rc.1 passes/2.0.0-rc semVerLessThan 2.0.0-rc.1 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.1 semVerLessThan 2.0.0-rc fails/2.0.0-rc.1 semVerLessThan 2.0.0-rc fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.1 semVerGreaterThan 2.0.0-rc passes/2.0.0-rc.1 semVerGreaterThan 2.0.0-rc passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerGreaterThan 2.0.0-rc.1 fails/2.0.0-rc semVerGreaterThan 2.0.0-rc.1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerEqual 2.0.0-rc.10.green fails/2.0.0-rc.2.green semVerEqual 2.0.0-rc.10.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.10.green semVerEqual 2.0.0-rc.2.green fails/2.0.0-rc.10.green semVerEqual 2.0.0-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.10.green passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.10.green passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.10.green semVerLessThan 2.0.0-rc.2.green fails/2.0.0-rc.10.green semVerLessThan 2.0.0-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.10.green semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.10.green semVerGreaterThan 2.0.0-rc.2.green passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.10.green fails/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.10.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerEqual 2.0.0-rc.2.red fails/2.0.0-rc.2.green semVerEqual 2.0.0-rc.2.red fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.red semVerEqual 2.0.0-rc.2.green fails/2.0.0-rc.2.red semVerEqual 2.0.0-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.red passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.red passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.red semVerLessThan 2.0.0-rc.2.green fails/2.0.0-rc.2.red semVerLessThan 2.0.0-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.red semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.2.red semVerGreaterThan 2.0.0-rc.2.green passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.2.red fails/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.2.red fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerEqual 2.0.0-rc.2.green.1 fails/2.0.0-rc.2.green semVerEqual 2.0.0-rc.2.green.1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green.1 semVerEqual 2.0.0-rc.2.green fails/2.0.0-rc.2.green.1 semVerEqual 2.0.0-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.green.1 passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.green.1 passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green.1 semVerLessThan 2.0.0-rc.2.green fails/2.0.0-rc.2.green.1 semVerLessThan 2.0.0-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green.1 semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.2.green.1 semVerGreaterThan 2.0.0-rc.2.green passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.2.green.1 fails/2.0.0-rc.2.green semVerGreaterThan 2.0.0-rc.2.green.1 fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerEqual 2.0.1-rc.2.green fails/2.0.0-rc.2.green semVerEqual 2.0.1-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.1-rc.2.green semVerEqual 2.0.0-rc.2.green fails/2.0.1-rc.2.green semVerEqual 2.0.0-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.1-rc.2.green passes/2.0.0-rc.2.green semVerLessThan 2.0.1-rc.2.green passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.1-rc.2.green semVerLessThan 2.0.0-rc.2.green fails/2.0.1-rc.2.green semVerLessThan 2.0.0-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.1-rc.2.green semVerGreaterThan 2.0.0-rc.2.green passes/2.0.1-rc.2.green semVerGreaterThan 2.0.0-rc.2.green passes/evaluate all flags -evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerGreaterThan 2.0.1-rc.2.green fails/2.0.0-rc.2.green semVerGreaterThan 2.0.1-rc.2.green fails/evaluate all flags -evaluation/parameterized/operators - string/prefix startsWith prefix/prefix startsWith prefix/evaluate all flags -evaluation/parameterized/operators - string/[prefix] startsWith prefix/[prefix] startsWith prefix/evaluate all flags -evaluation/parameterized/operators - string/prefixabc startsWith prefix/prefixabc startsWith prefix/evaluate all flags -evaluation/parameterized/operators - string/[prefixabc] startsWith prefix/[prefixabc] startsWith prefix/evaluate all flags -evaluation/parameterized/operators - string/notprefix startsWith prefix/notprefix startsWith prefix/evaluate all flags -evaluation/parameterized/operators - string/[notprefix] startsWith prefix/[notprefix] startsWith prefix/evaluate all flags -evaluation/parameterized/operators - string/Prefixabc startsWith prefix/Prefixabc startsWith prefix/evaluate all flags -evaluation/parameterized/operators - string/[Prefixabc] startsWith prefix/[Prefixabc] startsWith prefix/evaluate all flags -evaluation/parameterized/operators - string/suffix endsWith suffix/suffix endsWith suffix/evaluate all flags -evaluation/parameterized/operators - string/[suffix] endsWith suffix/[suffix] endsWith suffix/evaluate all flags -evaluation/parameterized/operators - string/abcsuffix endsWith suffix/abcsuffix endsWith suffix/evaluate all flags -evaluation/parameterized/operators - string/[abcsuffix] endsWith suffix/[abcsuffix] endsWith suffix/evaluate all flags -evaluation/parameterized/operators - string/suffixno endsWith suffix/suffixno endsWith suffix/evaluate all flags -evaluation/parameterized/operators - string/[suffixno] endsWith suffix/[suffixno] endsWith suffix/evaluate all flags -evaluation/parameterized/operators - string/abcSuffix endsWith suffix/abcSuffix endsWith suffix/evaluate all flags -evaluation/parameterized/operators - string/[abcSuffix] endsWith suffix/[abcSuffix] endsWith suffix/evaluate all flags -evaluation/parameterized/operators - string/substring contains substring/substring contains substring/evaluate all flags -evaluation/parameterized/operators - string/[substring] contains substring/[substring] contains substring/evaluate all flags -evaluation/parameterized/operators - string/abcsubstringxyz contains substring/abcsubstringxyz contains substring/evaluate all flags -evaluation/parameterized/operators - string/[abcsubstringxyz] contains substring/[abcsubstringxyz] contains substring/evaluate all flags -evaluation/parameterized/operators - string/abcsubstxyz contains substring/abcsubstxyz contains substring/evaluate all flags -evaluation/parameterized/operators - string/[abcsubstxyz] contains substring/[abcsubstxyz] contains substring/evaluate all flags -evaluation/parameterized/operators - string/abcSubstringxyz contains substring/abcSubstringxyz contains substring/evaluate all flags -evaluation/parameterized/operators - string/[abcSubstringxyz] contains substring/[abcSubstringxyz] contains substring/evaluate all flags -evaluation/parameterized/operators - string/matchThisName matches ^[A-Za-z]+/matchThisName matches ^[A-Za-z]+/evaluate all flags -evaluation/parameterized/operators - string/[matchThisName] matches ^[A-Za-z]+/[matchThisName] matches ^[A-Za-z]+/evaluate all flags -evaluation/parameterized/operators - string/_Don't match This Name matches ^[A-Za-z]+/_Don't match This Name matches ^[A-Za-z]+/evaluate all flags -evaluation/parameterized/operators - string/[_Don't match This Name] matches ^[A-Za-z]+/[_Don't match This Name] matches ^[A-Za-z]+/evaluate all flags -evaluation/parameterized/operators - string/true startsWith true/true startsWith true/evaluate all flags -evaluation/parameterized/operators - string/[true] startsWith true/[true] startsWith true/evaluate all flags -evaluation/parameterized/operators - string/true endsWith true/true endsWith true/evaluate all flags -evaluation/parameterized/operators - string/[true] endsWith true/[true] endsWith true/evaluate all flags -evaluation/parameterized/operators - string/true contains true/true contains true/evaluate all flags -evaluation/parameterized/operators - string/[true] contains true/[true] contains true/evaluate all flags -evaluation/parameterized/operators - string/true matches true/true matches true/evaluate all flags -evaluation/parameterized/operators - string/[true] matches true/[true] matches true/evaluate all flags -evaluation/parameterized/operators - string/99 startsWith 99/99 startsWith 99/evaluate all flags -evaluation/parameterized/operators - string/[99] startsWith 99/[99] startsWith 99/evaluate all flags -evaluation/parameterized/operators - string/99 endsWith 99/99 endsWith 99/evaluate all flags -evaluation/parameterized/operators - string/[99] endsWith 99/[99] endsWith 99/evaluate all flags -evaluation/parameterized/operators - string/99 contains 99/99 contains 99/evaluate all flags -evaluation/parameterized/operators - string/[99] contains 99/[99] contains 99/evaluate all flags -evaluation/parameterized/operators - string/99 matches 99/99 matches 99/evaluate all flags -evaluation/parameterized/operators - string/[99] matches 99/[99] matches 99/evaluate all flags -evaluation/parameterized/operators - string/null startsWith null/null startsWith null/evaluate all flags -evaluation/parameterized/operators - string/[null] startsWith null/[null] startsWith null/evaluate all flags -evaluation/parameterized/operators - string/null endsWith null/null endsWith null/evaluate all flags -evaluation/parameterized/operators - string/[null] endsWith null/[null] endsWith null/evaluate all flags -evaluation/parameterized/operators - string/null contains null/null contains null/evaluate all flags -evaluation/parameterized/operators - string/[null] contains null/[null] contains null/evaluate all flags -evaluation/parameterized/operators - string/null matches null/null matches null/evaluate all flags -evaluation/parameterized/operators - string/[null] matches null/[null] matches null/evaluate all flags -evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule matches/prerequisite succeeds, flag rule matches/evaluate flag without detail -evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule matches/prerequisite succeeds, flag rule matches/evaluate flag with detail -evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule matches/prerequisite succeeds, flag rule matches/evaluate all flags -evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule does not match/prerequisite succeeds, flag rule does not match/evaluate flag with detail -evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule does not match/prerequisite succeeds, flag rule does not match/evaluate all flags -evaluation/parameterized/prerequisites/prerequisite fails/prerequisite fails/evaluate flag with detail -evaluation/parameterized/prerequisites/prerequisite fails/prerequisite fails/evaluate all flags -evaluation/parameterized/prerequisites/two prerequisites, both succeed, flag rule matches/two prerequisites, both succeed, flag rule matches/evaluate flag without detail -evaluation/parameterized/prerequisites/two prerequisites, both succeed, flag rule matches/two prerequisites, both succeed, flag rule matches/evaluate flag with detail -evaluation/parameterized/prerequisites/two prerequisites, both succeed, flag rule matches/two prerequisites, both succeed, flag rule matches/evaluate all flags -evaluation/parameterized/prerequisites/two prerequisites, both valid, flag rule matches/two prerequisites, both valid, flag rule matches/evaluate flag without detail -evaluation/parameterized/prerequisites/two prerequisites, both valid, flag rule matches/two prerequisites, both valid, flag rule matches/evaluate flag with detail -evaluation/parameterized/prerequisites/two prerequisites, both valid, flag rule matches/two prerequisites, both valid, flag rule matches/evaluate all flags -evaluation/parameterized/prerequisites/two prerequisites, second fails/two prerequisites, second fails/evaluate flag with detail -evaluation/parameterized/prerequisites/two prerequisites, second fails/two prerequisites, second fails/evaluate all flags -evaluation/parameterized/prerequisites/nested prerequisites, all succeed/nested prerequisites, all succeed/evaluate flag without detail -evaluation/parameterized/prerequisites/nested prerequisites, all succeed/nested prerequisites, all succeed/evaluate flag with detail -evaluation/parameterized/prerequisites/nested prerequisites, all succeed/nested prerequisites, all succeed/evaluate all flags -evaluation/parameterized/prerequisites/nested prerequisites, transitive one fails/nested prerequisites, transitive one fails/evaluate flag with detail -evaluation/parameterized/prerequisites/nested prerequisites, transitive one fails/nested prerequisites, transitive one fails/evaluate all flags -evaluation/parameterized/prerequisites/prerequisite flag not found/prerequisite flag not found/evaluate flag with detail -evaluation/parameterized/prerequisites/prerequisite flag not found/prerequisite flag not found/evaluate all flags -evaluation/parameterized/prerequisites/failed prerequisite always returns off variation/failed prerequisite always returns off variation/evaluate flag without detail -evaluation/parameterized/prerequisites/failed prerequisite always returns off variation/failed prerequisite always returns off variation/evaluate flag with detail -evaluation/parameterized/prerequisites/failed prerequisite always returns off variation/failed prerequisite always returns off variation/evaluate all flags -evaluation/parameterized/prerequisites/prerequisite fails regardless of variation if it is off/prerequisite fails regardless of variation if it is off/evaluate flag with detail -evaluation/parameterized/prerequisites/prerequisite fails regardless of variation if it is off/prerequisite fails regardless of variation if it is off/evaluate all flags evaluation/parameterized/prerequisites/prerequisite cycle is detected at top level, recursion stops/prerequisite cycle is detected at top level, recursion stops/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite cycle is detected at top level, recursion stops/prerequisite cycle is detected at top level, recursion stops/evaluate all flags evaluation/parameterized/prerequisites/prerequisite cycle is detected at deeper level, recursion stops/prerequisite cycle is detected at deeper level, recursion stops/evaluate flag with detail -evaluation/parameterized/prerequisites/first prerequisite is a prerequisite of the second prerequisite/first prerequisite is a prerequisite of the second prerequisite/evaluate flag without detail -evaluation/parameterized/prerequisites/first prerequisite is a prerequisite of the second prerequisite/first prerequisite is a prerequisite of the second prerequisite/evaluate flag with detail -evaluation/parameterized/prerequisites/first prerequisite is a prerequisite of the second prerequisite/first prerequisite is a prerequisite of the second prerequisite/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches first bucket in rollout/fallthrough rollout/fallthrough rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches first bucket in rollout/rule rollout/rule rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches second bucket in rollout/fallthrough rollout/fallthrough rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches second bucket in rollout/rule rollout/rule rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - last rollout bucket is used if value is past the end/fallthrough rollout/fallthrough rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - last rollout bucket is used if value is past the end/rule rollout/rule rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches first bucket in experiment/fallthrough rollout/fallthrough rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches first bucket in experiment/rule rollout/rule rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches second bucket in experiment/fallthrough rollout/fallthrough rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches second bucket in experiment/rule rollout/rule rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches untracked bucket in experiment/fallthrough rollout/fallthrough rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - user matches untracked bucket in experiment/rule rollout/rule rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - last experiment bucket is used if value is past the end/fallthrough rollout/fallthrough rollout/evaluate all flags -evaluation/parameterized/rollout or experiment - last experiment bucket is used if value is past the end/rule rollout/rule rollout/evaluate all flags +evaluation/parameterized/prerequisites/prerequisite cycle is detected at deeper level, recursion stops/prerequisite cycle is detected at deeper level, recursion stops/evaluate all flags +evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate flag without detail evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate flag with detail +evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate all flags +evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/rule rollout/rule rollout/evaluate flag without detail evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/rule rollout/rule rollout/evaluate flag with detail -evaluation/parameterized/rule match ()/all clauses of rule1 match/all clauses of rule1 match/evaluate all flags -evaluation/parameterized/rule match ()/rule2 matches, after one clause of rule1 fails/rule2 matches, after one clause of rule1 fails/evaluate all flags -evaluation/parameterized/rule match ()/unknown operator is treated as a non-match, can still match another rule/unknown operator is treated as a non-match, can still match another rule/evaluate all flags -evaluation/parameterized/rule match ()/no rules match, fallthrough/no rules match, fallthrough/evaluate all flags -evaluation/parameterized/rule match ()/rules are ignored when flag is off/rules are ignored when flag is off/evaluate all flags -evaluation/parameterized/rule match ()/negated clause, non-match becomes match/negated clause, non-match becomes match/evaluate all flags -evaluation/parameterized/rule match ()/negated clause, match becomes non-match/negated clause, match becomes non-match/evaluate all flags -evaluation/parameterized/rule match ()/negated clause, null user attribute is always non-match regardless of negation/negated clause, null user attribute is always non-match regardless of negation/evaluate all flags -evaluation/parameterized/segment match/user matches via include/user matches via include/evaluate all flags -evaluation/parameterized/segment match/user matches via rule/user matches via rule/evaluate all flags -evaluation/parameterized/segment match/user matches via rule but is excluded/user matches via rule but is excluded/evaluate all flags -evaluation/parameterized/segment match/user does not match and is not included/user does not match and is not included/evaluate all flags -evaluation/parameterized/segment match/clause matches if any of the segments match/clause matches if any of the segments match/evaluate all flags -evaluation/parameterized/segment match/unknown segment is non-match, not an error/unknown segment is non-match, not an error/evaluate all flags -evaluation/parameterized/segment match/included list is specific to user kind/included list is specific to user kind/evaluate all flags -evaluation/parameterized/segment match/includedContexts match for non-default context kind/includedContexts match for non-default context kind/evaluate all flags -evaluation/parameterized/segment match/includedContexts non-match for default context kind/includedContexts non-match for default context kind/evaluate all flags -evaluation/parameterized/segment match/includedContexts non-match for non-default context kind/includedContexts non-match for non-default context kind/evaluate all flags -evaluation/parameterized/segment match/excluded list is specific to user kind/excluded list is specific to user kind/evaluate all flags -evaluation/parameterized/segment match/rule match for non-default context kind/rule match for non-default context kind/evaluate all flags -evaluation/parameterized/segment match/excludedContexts match for non-default context kind/excludedContexts match for non-default context kind/evaluate all flags -evaluation/parameterized/segment match/excludedContexts non-match for default context kind/excludedContexts non-match for default context kind/evaluate all flags -evaluation/parameterized/segment match/excludedContexts non-match for non-default context kind/excludedContexts non-match for non-default context kind/evaluate all flags -evaluation/parameterized/segment match/key does not bucket into segment/key does not bucket into segment/evaluate all flags -evaluation/parameterized/segment match/key does bucket into segment/key does bucket into segment/evaluate all flags -evaluation/parameterized/segment recursion/simple single-level recursion/simple single-level recursion/evaluate all flags -evaluation/parameterized/segment recursion/single level, two clauses in one rule, both match/single level, two clauses in one rule, both match/evaluate all flags -evaluation/parameterized/segment recursion/single level, two clauses in one rule, one does not match/single level, two clauses in one rule, one does not match/evaluate all flags -evaluation/parameterized/segment recursion/single level, two rules, one does not match/single level, two rules, one does not match/evaluate all flags -evaluation/parameterized/segment recursion/cycle is detected at top level, recursion stops/cycle is detected at top level, recursion stops/evaluate flag with detail -evaluation/parameterized/segment recursion/cycle is detected below top level, recursion stops/cycle is detected below top level, recursion stops/evaluate flag with detail -evaluation/parameterized/target match/user targets only, match user 1/user targets only, match user 1/evaluate all flags -evaluation/parameterized/target match/user targets only, match user 2/user targets only, match user 2/evaluate all flags -evaluation/parameterized/target match/user targets only, match user 3/user targets only, match user 3/evaluate all flags -evaluation/parameterized/target match/user targets only, match user 4/user targets only, match user 4/evaluate all flags -evaluation/parameterized/target match/user targets only, non-match user key/user targets only, non-match user key/evaluate all flags -evaluation/parameterized/target match/user targets only, match user key from multi-kind context/user targets only, match user key from multi-kind context/evaluate all flags -evaluation/parameterized/target match/user targets only, non-match user key from multi-kind context/user targets only, non-match user key from multi-kind context/evaluate all flags -evaluation/parameterized/target match/context targets, match user 1/context targets, match user 1/evaluate all flags -evaluation/parameterized/target match/context targets, match user 2/context targets, match user 2/evaluate all flags -evaluation/parameterized/target match/context targets, match user 3/context targets, match user 3/evaluate all flags -evaluation/parameterized/target match/context targets, match user 4/context targets, match user 4/evaluate all flags -evaluation/parameterized/target match/context targets, match other 1/context targets, match other 1/evaluate all flags -evaluation/parameterized/target match/context targets, match other 2/context targets, match other 2/evaluate all flags -evaluation/parameterized/target match/context targets, match other 3/context targets, match other 3/evaluate all flags -evaluation/parameterized/target match/context targets, match other 4/context targets, match other 4/evaluate all flags -evaluation/parameterized/target match/context targets, non-match user key/context targets, non-match user key/evaluate all flags -evaluation/parameterized/target match/context targets, non-match other key/context targets, non-match other key/evaluate all flags -evaluation/parameterized/target match/context targets, match user key from multi-kind context/context targets, match user key from multi-kind context/evaluate all flags -evaluation/parameterized/target match/context targets, match other key from multi-kind context/context targets, match other key from multi-kind context/evaluate all flags -evaluation/parameterized/target match/context targets, non-match multi-kind context/context targets, non-match multi-kind context/evaluate all flags -evaluation/parameterized/target match/user targets ignored when flag is off/user targets ignored when flag is off/evaluate all flags -evaluation/parameterized/target match/context targets ignored when flag is off/context targets ignored when flag is off/evaluate all flags -evaluation/parameterized/target match/target matching takes precedence over rules/target matching takes precedence over rules/evaluate all flags -evaluation/parameterized/variation value types (bool)/off/off/evaluate all flags -evaluation/parameterized/variation value types (bool)/fallthrough/fallthrough/evaluate all flags -evaluation/parameterized/variation value types (bool)/match variation 1/match variation 1/evaluate all flags -evaluation/parameterized/variation value types (bool)/match variation 2/match variation 2/evaluate all flags -evaluation/parameterized/variation value types (int)/off/off/evaluate all flags -evaluation/parameterized/variation value types (int)/fallthrough/fallthrough/evaluate all flags -evaluation/parameterized/variation value types (int)/match variation 1/match variation 1/evaluate all flags -evaluation/parameterized/variation value types (int)/match variation 2/match variation 2/evaluate all flags -evaluation/parameterized/variation value types (double)/off/off/evaluate all flags -evaluation/parameterized/variation value types (double)/fallthrough/fallthrough/evaluate all flags -evaluation/parameterized/variation value types (double)/match variation 1/match variation 1/evaluate all flags -evaluation/parameterized/variation value types (double)/match variation 2/match variation 2/evaluate all flags -evaluation/parameterized/variation value types (string)/off/off/evaluate all flags -evaluation/parameterized/variation value types (string)/fallthrough/fallthrough/evaluate all flags -evaluation/parameterized/variation value types (string)/match variation 1/match variation 1/evaluate all flags -evaluation/parameterized/variation value types (string)/match variation 2/match variation 2/evaluate all flags -evaluation/parameterized/variation value types (any)/off/off/evaluate all flags -evaluation/parameterized/variation value types (any)/fallthrough/fallthrough/evaluate all flags -evaluation/parameterized/variation value types (any)/match variation 1/match variation 1/evaluate all flags -evaluation/parameterized/variation value types (any)/match variation 2/match variation 2/evaluate all flags -evaluation/parameterized/segment match/user matched via include can be negated/user matched via include can be negated/evaluate all flags +evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/rule rollout/rule rollout/evaluate all flags evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag without detail evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag with detail -evaluation/parameterized/segment match/user does not match and can be negated/user does not match and can be negated/evaluate all flags -evaluation/all flags state/default behavior -evaluation/all flags state/experimentation -evaluation/all flags state/error in flag/without reasons -evaluation/all flags state/error in flag/with reasons -evaluation/all flags state/client not ready +evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate all flags +evaluation/parameterized/segment recursion/cycle is detected at top level, recursion stops/cycle is detected at top level, recursion stops/evaluate flag with detail +evaluation/parameterized/segment recursion/cycle is detected below top level, recursion stops/cycle is detected below top level, recursion stops/evaluate flag with detail events/summary events/prerequisites -events/feature prerequisite events/without reasons -events/feature prerequisite events/with reasons -events/experimentation/experiment in rule -events/experimentation/experiment in fallthrough -events/context properties/single-kind minimal -events/context properties/multi-kind minimal -events/context properties/single-kind with attributes, nothing private -events/context properties/single-kind, allAttributesPrivate -events/context properties/single-kind, specific private attributes -events/context properties/single-kind, private attribute nested property -events/context properties/custom attribute with value false -events/context properties/custom attribute with value true -events/context properties/custom attribute with value -1000 -events/context properties/custom attribute with value 0 -events/context properties/custom attribute with value 1000 -events/context properties/custom attribute with value -1000.5 -events/context properties/custom attribute with value 1000.5 -events/context properties/custom attribute with value "" -events/context properties/custom attribute with value "abc" -events/context properties/custom attribute with value "has \"escaped\" characters" -events/context properties/custom attribute with value [] -events/context properties/custom attribute with value ["a","b"] -events/context properties/custom attribute with value {} -events/context properties/custom attribute with value {"a":1} -streaming/updates/flag patch with higher version is applied -streaming/updates/segment patch with higher version is applied -streaming/updates/flag patch with same version is not applied -streaming/updates/segment patch with same version is not applied -streaming/updates/flag patch with lower version is not applied -streaming/updates/segment patch with lower version is not applied -streaming/updates/flag patch for previously nonexistent flag is applied -streaming/updates/segment patch for previously nonexistent segment is applied -streaming/updates/flag delete with higher version is applied -streaming/updates/segment delete with higher version is applied -streaming/updates/flag delete with same version is not applied -streaming/updates/segment delete with same version is not applied -streaming/updates/flag delete with lower version is not applied -streaming/updates/segment delete with lower version is not applied -streaming/updates/flag delete for previously nonexistent flag is applied -streaming/updates/segment delete for previously nonexistent segment is applied -streaming/retry behavior/retry after stream is closed -streaming/retry behavior/initial retry delay is applied -streaming/retry behavior/retry after IO error on initial connect -streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 400 -streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 408 -streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 429 -streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 500 -streaming/retry behavior/retry after recoverable HTTP error on initial connect/error 503 -streaming/retry behavior/retry after IO error on reconnect -streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 400 -streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 408 -streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 429 -streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 500 -streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 503 -streaming/retry behavior/do not retry after unrecoverable HTTP error on initial connect/error 401 -streaming/retry behavior/do not retry after unrecoverable HTTP error on initial connect/error 403 -streaming/retry behavior/do not retry after unrecoverable HTTP error on initial connect/error 405 -streaming/retry behavior/do not retry after unrecoverable HTTP error on reconnect/error 401 -streaming/retry behavior/do not retry after unrecoverable HTTP error on reconnect/error 403 -streaming/retry behavior/do not retry after unrecoverable HTTP error on reconnect/error 405 -streaming/validation/drop and reconnect if stream event has malformed JSON/put event -streaming/validation/drop and reconnect if stream event has malformed JSON/patch event -streaming/validation/drop and reconnect if stream event has malformed JSON/delete event -streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/put event -streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/patch event -streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/delete event -streaming/validation/unrecognized data that can be safely ignored/unknown event name with JSON body -streaming/validation/unrecognized data that can be safely ignored/unknown event name with non-JSON body -streaming/validation/unrecognized data that can be safely ignored/patch event with unrecognized path kind events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: bool events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: int events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: double @@ -838,8 +108,34 @@ events/feature events/full feature event for tracked flag/with reason/multi-kind events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: string events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: any events/feature events/evaluating all flags generates no events +events/feature prerequisite events/without reasons +events/feature prerequisite events/with reasons +events/experimentation/experiment in rule +events/experimentation/experiment in fallthrough +streaming/updates/flag patch with same version is not applied +streaming/updates/segment patch with same version is not applied +streaming/updates/flag patch with lower version is not applied +streaming/updates/segment patch with lower version is not applied +streaming/updates/flag delete with same version is not applied +streaming/updates/segment delete with same version is not applied +streaming/updates/flag delete with lower version is not applied +streaming/updates/segment delete with lower version is not applied +streaming/updates/flag delete for previously nonexistent flag is applied +streaming/updates/segment delete for previously nonexistent segment is applied +streaming/retry behavior/retry after IO error on reconnect +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 400 +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 408 +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 429 +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 500 +streaming/retry behavior/retry after recoverable HTTP error on reconnect/error 503 +streaming/validation/drop and reconnect if stream event has malformed JSON/put event +streaming/validation/drop and reconnect if stream event has malformed JSON/patch event +streaming/validation/drop and reconnect if stream event has malformed JSON/delete event +streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/put event +streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/patch event +streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/delete event -# The Server doesn't need to know how to deserialize users. +# The server-side SDK doesn't need to know how to serialize old-style users. context type/convert/old user to context/{"key": ""} context type/convert/old user to context/{"key": "a"} context type/convert/old user to context/{"key": "a"} diff --git a/libs/server-sdk/include/launchdarkly/server_side/client.hpp b/libs/server-sdk/include/launchdarkly/server_side/client.hpp index a80fe5cca..ed2a5d36e 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/client.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/client.hpp @@ -30,6 +30,8 @@ enum class AllFlagsStateOptions : std::uint8_t { ClientSideOnly = (1 << 2) }; +void operator|=(AllFlagsStateOptions& lhs, AllFlagsStateOptions rhs); + /** * Interface for the standard SDK client methods and properties. */ diff --git a/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state_json.hpp b/libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp similarity index 100% rename from libs/server-sdk/include/launchdarkly/server_side/feature_flags_state_json.hpp rename to libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp diff --git a/libs/server-sdk/src/client.cpp b/libs/server-sdk/src/client.cpp index cf5968a3f..becbb5199 100644 --- a/libs/server-sdk/src/client.cpp +++ b/libs/server-sdk/src/client.cpp @@ -4,6 +4,12 @@ namespace launchdarkly::server_side { +void operator|=(AllFlagsStateOptions& lhs, AllFlagsStateOptions rhs) { + lhs = static_cast( + static_cast>(lhs) | + static_cast>(rhs)); +} + Client::Client(Config config) : client(std::make_unique(std::move(config), kVersion)) {} diff --git a/libs/server-sdk/src/feature_flags_state_json.cpp b/libs/server-sdk/src/feature_flags_state_json.cpp index 23730b8e5..8e394d17e 100644 --- a/libs/server-sdk/src/feature_flags_state_json.cpp +++ b/libs/server-sdk/src/feature_flags_state_json.cpp @@ -1,13 +1,16 @@ #include #include #include -#include +#include + +#include namespace launchdarkly::server_side { void tag_invoke(boost::json::value_from_tag const& unused, boost::json::value& json_value, server_side::FeatureFlagsState::FlagState const& state) { + boost::ignore_unused(unused); auto& obj = json_value.emplace_object(); if (state.version) { obj.emplace("version", *state.version); @@ -19,13 +22,13 @@ void tag_invoke(boost::json::value_from_tag const& unused, obj.emplace("reason", boost::json::value_from(*state.reason)); } if (state.track_events) { - obj.emplace("track_events", true); + obj.emplace("trackEvents", true); } if (state.track_reason) { - obj.emplace("track_reason", true); + obj.emplace("trackReason", true); } if (state.debug_events_until_date) { - obj.emplace("debug_events_until_date", + obj.emplace("debugEventsUntilDate", boost::json::value_from(*state.debug_events_until_date)); } } @@ -33,6 +36,7 @@ void tag_invoke(boost::json::value_from_tag const& unused, void tag_invoke(boost::json::value_from_tag const& unused, boost::json::value& json_value, server_side::FeatureFlagsState const& state) { + boost::ignore_unused(unused); auto& obj = json_value.emplace_object(); obj.emplace("$valid", state.Valid()); diff --git a/libs/server-sdk/tests/all_flags_state.cpp b/libs/server-sdk/tests/all_flags_state.cpp index 3a3beee02..9b9ca60ac 100644 --- a/libs/server-sdk/tests/all_flags_state.cpp +++ b/libs/server-sdk/tests/all_flags_state.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include From 9b9a984b2bc4c72461d517a1109539a534271106 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 13:47:58 -0700 Subject: [PATCH 27/55] cleanup AllFlagsState api and types --- .../test-suppressions.txt | 2 +- .../server_side/all_flags_state.hpp | 75 ++++++++++++ .../launchdarkly/server_side/client.hpp | 37 ++++-- .../server_side/feature_flags_state.hpp | 50 -------- .../server_side/json_feature_flags_state.hpp | 6 +- libs/server-sdk/src/CMakeLists.txt | 4 +- .../src/all_flags_state/all_flags_state.cpp | 89 ++++++++++++++ .../all_flags_state_builder.cpp | 103 ++++++---------- .../all_flags_state_builder.hpp | 39 ++++-- .../all_flags_state/json_all_flags_state.cpp | 56 +++++++++ libs/server-sdk/src/client.cpp | 16 ++- libs/server-sdk/src/client_impl.cpp | 34 +++++- libs/server-sdk/src/client_impl.hpp | 2 +- libs/server-sdk/src/feature_flags_state.cpp | 42 ------- .../src/feature_flags_state_json.cpp | 49 -------- libs/server-sdk/tests/all_flags_state.cpp | 76 ------------ .../server-sdk/tests/all_flags_state_test.cpp | 113 ++++++++++++++++++ libs/server-sdk/tests/client_test.cpp | 4 +- 18 files changed, 478 insertions(+), 319 deletions(-) create mode 100644 libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp delete mode 100644 libs/server-sdk/include/launchdarkly/server_side/feature_flags_state.hpp create mode 100644 libs/server-sdk/src/all_flags_state/all_flags_state.cpp create mode 100644 libs/server-sdk/src/all_flags_state/json_all_flags_state.cpp delete mode 100644 libs/server-sdk/src/feature_flags_state.cpp delete mode 100644 libs/server-sdk/src/feature_flags_state_json.cpp delete mode 100644 libs/server-sdk/tests/all_flags_state.cpp create mode 100644 libs/server-sdk/tests/all_flags_state_test.cpp diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 7f0c9f8c7..5d712bb67 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -135,7 +135,7 @@ streaming/validation/drop and reconnect if stream event has well-formed JSON not streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/patch event streaming/validation/drop and reconnect if stream event has well-formed JSON not matching schema/delete event -# The server-side SDK doesn't need to know how to serialize old-style users. +# The Server doesn't need to know how to deserialize users. context type/convert/old user to context/{"key": ""} context type/convert/old user to context/{"key": "a"} context type/convert/old user to context/{"key": "a"} diff --git a/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp b/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp new file mode 100644 index 000000000..0c3b296ee --- /dev/null +++ b/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace launchdarkly::server_side { + +class AllFlagsState { + public: + class Metadata { + public: + Metadata(std::uint64_t version, + std::optional variation, + std::optional reason, + bool track_events, + bool track_reason, + std::optional debug_events_until_date); + + [[nodiscard]] std::uint64_t Version() const; + [[nodiscard]] std::optional Variation() const; + [[nodiscard]] std::optional const& Reason() const; + [[nodiscard]] bool TrackEvents() const; + [[nodiscard]] bool TrackReason() const; + [[nodiscard]] std::optional const& DebugEventsUntilDate() + const; + [[nodiscard]] bool OmitDetails() const; + + friend class AllFlagsStateBuilder; + + private: + std::uint64_t version_; + std::optional variation_; + std::optional reason_; + bool track_events_; + bool track_reason_; + std::optional debug_events_until_date_; + bool omit_details_; + }; + + [[nodiscard]] bool Valid() const; + + [[nodiscard]] std::unordered_map const& FlagsState() + const; + [[nodiscard]] std::unordered_map const& Evaluations() + const; + + /** + * Constructs an invalid instance of AllFlagsState. + */ + AllFlagsState(); + + /** + * Constructs a valid instance of AllFlagsState. + * @param evaluations A map of evaluation results for each flag. + * @param flags_state A map of metadata for each flag. + */ + AllFlagsState(std::unordered_map evaluations, + std::unordered_map flags_state); + + private: + bool const valid_; + const std::unordered_map flags_state_; + const std::unordered_map evaluations_; +}; + +bool operator==(AllFlagsState::Metadata const& lhs, + AllFlagsState::Metadata const& rhs); + +bool operator==(AllFlagsState const& lhs, AllFlagsState const& rhs); + +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/include/launchdarkly/server_side/client.hpp b/libs/server-sdk/include/launchdarkly/server_side/client.hpp index ed2a5d36e..988ccf8cf 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/client.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/client.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -16,21 +16,36 @@ namespace launchdarkly::server_side { enum class AllFlagsStateOptions : std::uint8_t { - /** @brief Use default behavior. */ + /** + * Default behavior. + */ Default = 0, - /** @brief Include evaluation reasons in the state object. By default, they - are not. */ + /** + * Include evaluation reasons in the state object. By default, they + * are not. + */ IncludeReasons = (1 << 0), - /** @brief Include detailed flag metadata only for flags with event tracking - * or debugging turned on. This reduces the size of the JSON data if you are - * passing the flag state to the front end. */ + /** + * Include detailed flag metadata only for flags with event tracking + * or debugging turned on. + * + * This reduces the size of the JSON data if you are + * passing the flag state to the front end. + */ DetailsOnlyForTrackedFlags = (1 << 1), - /** @brief Include only flags marked for use with the client-side SDK. By - default, all flags are included. */ + /** + * Include only flags marked for use with the client-side SDK. + * By default, all flags are included. + */ ClientSideOnly = (1 << 2) }; void operator|=(AllFlagsStateOptions& lhs, AllFlagsStateOptions rhs); +AllFlagsStateOptions operator|(AllFlagsStateOptions lhs, + AllFlagsStateOptions rhs); + +AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, + AllFlagsStateOptions rhs); /** * Interface for the standard SDK client methods and properties. @@ -77,7 +92,7 @@ class IClient { * * @return A map from feature flag keys to values for the current context. */ - [[nodiscard]] virtual FeatureFlagsState AllFlagsState( + [[nodiscard]] virtual AllFlagsState AllFlagsState( Context const& context, enum AllFlagsStateOptions options = AllFlagsStateOptions::Default) = 0; @@ -278,7 +293,7 @@ class Client : public IClient { [[nodiscard]] bool Initialized() const override; using FlagKey = std::string; - [[nodiscard]] FeatureFlagsState AllFlagsState( + [[nodiscard]] class AllFlagsState AllFlagsState( Context const& context, enum AllFlagsStateOptions options = AllFlagsStateOptions::Default) override; diff --git a/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state.hpp b/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state.hpp deleted file mode 100644 index fe90c27cf..000000000 --- a/libs/server-sdk/include/launchdarkly/server_side/feature_flags_state.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include - -namespace launchdarkly::server_side { - -class FeatureFlagsState { - public: - struct FlagState { - std::optional version; - std::optional variation; - std::optional reason; - bool track_events; - bool track_reason; - std::optional debug_events_until_date; - }; - - [[nodiscard]] bool Valid() const; - - [[nodiscard]] std::unordered_map const& FlagsState() - const; - [[nodiscard]] std::unordered_map const& Evaluations() - const; - - /** - * Creates an invalid instance of FeatureFlagsState. - */ - FeatureFlagsState(); - - FeatureFlagsState(std::unordered_map evaluations, - std::unordered_map flags_state); - - private: - bool const valid_; - const std::unordered_map flags_state_; - const std::unordered_map evaluations_; -}; - -bool operator==(FeatureFlagsState::FlagState const& lhs, - FeatureFlagsState::FlagState const& rhs); - -bool operator==(FeatureFlagsState const& lhs, FeatureFlagsState const& rhs); - -} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp b/libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp index 8556d259d..3e98dff01 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include @@ -8,9 +8,9 @@ namespace launchdarkly::server_side { void tag_invoke(boost::json::value_from_tag const& unused, boost::json::value& json_value, - server_side::FeatureFlagsState::FlagState const& state); + server_side::AllFlagsState::Metadata const& state); void tag_invoke(boost::json::value_from_tag const& unused, boost::json::value& json_value, - server_side::FeatureFlagsState const& state); + server_side::AllFlagsState const& state); } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/CMakeLists.txt b/libs/server-sdk/src/CMakeLists.txt index 39be5987c..e0a327085 100644 --- a/libs/server-sdk/src/CMakeLists.txt +++ b/libs/server-sdk/src/CMakeLists.txt @@ -11,8 +11,8 @@ add_library(${LIBNAME} boost.cpp client.cpp client_impl.cpp - feature_flags_state.cpp - feature_flags_state_json.cpp + all_flags_state/all_flags_state.cpp + all_flags_state/json_all_flags_state.cpp all_flags_state/all_flags_state_builder.cpp data_sources/data_source_update_sink.hpp data_store/data_store.hpp diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state.cpp b/libs/server-sdk/src/all_flags_state/all_flags_state.cpp new file mode 100644 index 000000000..a2a42671e --- /dev/null +++ b/libs/server-sdk/src/all_flags_state/all_flags_state.cpp @@ -0,0 +1,89 @@ +#include "launchdarkly/server_side/all_flags_state.hpp" + +namespace launchdarkly::server_side { + +AllFlagsState::Metadata::Metadata( + std::uint64_t version, + std::optional variation, + std::optional reason, + bool track_events, + bool track_reason, + std::optional debug_events_until_date) + : version_(version), + variation_(variation), + reason_(reason), + track_events_(track_events), + track_reason_(track_reason), + debug_events_until_date_(debug_events_until_date), + omit_details_(false) {} + +std::uint64_t AllFlagsState::Metadata::Version() const { + return version_; +} + +std::optional AllFlagsState::Metadata::Variation() const { + return variation_; +} + +std::optional const& AllFlagsState::Metadata::Reason() const { + return reason_; +} + +bool AllFlagsState::Metadata::TrackEvents() const { + return track_events_; +} + +bool AllFlagsState::Metadata::TrackReason() const { + return track_reason_; +} + +std::optional const& +AllFlagsState::Metadata::DebugEventsUntilDate() const { + return debug_events_until_date_; +} + +bool AllFlagsState::Metadata::OmitDetails() const { + return omit_details_; +} + +AllFlagsState::AllFlagsState() + : valid_(false), evaluations_(), flags_state_() {} + +AllFlagsState::AllFlagsState( + std::unordered_map evaluations, + std::unordered_map flags_state) + : valid_(true), + evaluations_(std::move(evaluations)), + flags_state_(std::move(flags_state)) {} + +bool AllFlagsState::Valid() const { + return valid_; +} + +std::unordered_map const& +AllFlagsState::FlagsState() const { + return flags_state_; +} + +std::unordered_map const& AllFlagsState::Evaluations() + const { + return evaluations_; +} + +bool operator==(AllFlagsState const& lhs, AllFlagsState const& rhs) { + return lhs.Valid() == rhs.Valid() && + lhs.Evaluations() == rhs.Evaluations() && + lhs.FlagsState() == rhs.FlagsState(); +} + +bool operator==(AllFlagsState::Metadata const& lhs, + AllFlagsState::Metadata const& rhs) { + return lhs.Version() == rhs.Version() && + lhs.Variation() == rhs.Variation() && lhs.Reason() == rhs.Reason() && + lhs.TrackEvents() == rhs.TrackEvents() && + lhs.TrackReason() == rhs.TrackReason() && + lhs.DebugEventsUntilDate() == rhs.DebugEventsUntilDate() && + lhs.OmitDetails() == rhs.OmitDetails(); +} + +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp index 7944419d4..ea5aadfdf 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp @@ -2,70 +2,30 @@ namespace launchdarkly::server_side { -bool IsExperimentationEnabled(data_model::Flag const& flag, - std::optional const& reason); -bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag); - -AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, - AllFlagsStateOptions rhs); - -AllFlagsStateBuilder::AllFlagsStateBuilder(evaluation::Evaluator& evaluator, - data_store::IDataStore const& store) - : evaluator_(evaluator), store_(store) {} - -FeatureFlagsState AllFlagsStateBuilder::Build( - launchdarkly::Context const& context, - enum AllFlagsStateOptions options) { - std::unordered_map flags_state; - std::unordered_map evaluations; - - for (auto [key, flag_desc] : store_.AllFlags()) { - if (!flag_desc || !flag_desc->item) { - continue; - } - auto const& flag = (*flag_desc->item); - if (IsSet(options, AllFlagsStateOptions::ClientSideOnly) && - !flag.clientSideAvailability.usingEnvironmentId) { - continue; - } - - auto detail = evaluator_.Evaluate(flag, context); - - bool require_experiment_data = - IsExperimentationEnabled(flag, detail.Reason()); - bool track_events = flag.trackEvents || require_experiment_data; - bool track_reason = require_experiment_data; - bool currently_debugging = false; - if (flag.debugEventsUntilDate) { - auto now = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - currently_debugging = *flag.debugEventsUntilDate > now; - } - - bool omit_details = - (IsSet(options, AllFlagsStateOptions::DetailsOnlyForTrackedFlags) && - !(track_events || track_reason || currently_debugging)); - - std::optional reason = - (!IsSet(options, AllFlagsStateOptions::IncludeReasons) && - !track_reason) - ? std::nullopt - : detail.Reason(); - - if (omit_details) { - reason = std::nullopt; - /* version = std::nullopt; */ +bool IsDebuggingEnabled(std::optional debug_events_until); + +AllFlagsStateBuilder::AllFlagsStateBuilder(enum AllFlagsStateOptions options) + : options_(options), flags_state_(), evaluations_() {} + +void AllFlagsStateBuilder::AddFlag(std::string const& key, + Value value, + AllFlagsState::Metadata flag) { + if (IsSet(options_, AllFlagsStateOptions::DetailsOnlyForTrackedFlags)) { + if (!flag.TrackEvents() && !flag.TrackReason() && + !IsDebuggingEnabled(flag.DebugEventsUntilDate())) { + flag.omit_details_ = true; } - - evaluations.emplace(key, detail.Value()); - flags_state.emplace( - key, FeatureFlagsState::FlagState{ - flag.version, detail.VariationIndex(), reason, - track_events, track_reason, flag.debugEventsUntilDate}); } + if (NotSet(options_, AllFlagsStateOptions::IncludeReasons) || + flag.TrackReason()) { + flag.reason_ = std::nullopt; + } + flags_state_.emplace(key, std::move(flag)); + evaluations_.emplace(std::move(key), std::move(value)); +} - return FeatureFlagsState{std::move(evaluations), std::move(flags_state)}; +AllFlagsState AllFlagsStateBuilder::Build() { + return AllFlagsState{std::move(evaluations_), std::move(flags_state_)}; } bool IsExperimentationEnabled(data_model::Flag const& flag, @@ -90,15 +50,22 @@ bool IsExperimentationEnabled(data_model::Flag const& flag, } } -AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, - AllFlagsStateOptions rhs) { - return static_cast( - static_cast>(lhs) & - static_cast>(rhs)); -} - bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag) { return (options & flag) == flag; } +bool NotSet(AllFlagsStateOptions options, AllFlagsStateOptions flag) { + return !IsSet(options, flag); +} + +std::uint64_t NowUnixMillis() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); +} + +bool IsDebuggingEnabled(std::optional debug_events_until) { + return debug_events_until && *debug_events_until > NowUnixMillis(); +} + } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp index c3a137758..94c086ba8 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp @@ -1,22 +1,47 @@ #pragma once +#include #include -#include #include "../data_store/data_store.hpp" #include "../evaluation/evaluator.hpp" namespace launchdarkly::server_side { + +bool IsExperimentationEnabled(data_model::Flag const& flag, + std::optional const& reason); + +bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag); +bool NotSet(AllFlagsStateOptions options, AllFlagsStateOptions flag); + class AllFlagsStateBuilder { public: - AllFlagsStateBuilder(evaluation::Evaluator& evaluator, - data_store::IDataStore const& store); + /** + * Constructs a builder capable of generating a AllFlagsState structure. + * @param options Options affecting the behavior of the builder. + */ + AllFlagsStateBuilder(enum AllFlagsStateOptions options); + + /** + * Adds a flag, including its evaluation result and additional state. + * @param key Key of the flag. + * @param value Value of the flag. + * @param state State of the flag. + */ + void AddFlag(std::string const& key, + Value value, + AllFlagsState::Metadata state); - [[nodiscard]] FeatureFlagsState Build(Context const& context, - enum AllFlagsStateOptions options); + /** + * Builds a AllFlagsState structure from the flags added to the builder. + * This operation consumes the builder, and must only be called once. + * @return + */ + [[nodiscard]] AllFlagsState Build(); private: - evaluation::Evaluator& evaluator_; - data_store::IDataStore const& store_; + enum AllFlagsStateOptions options_; + std::unordered_map flags_state_; + std::unordered_map evaluations_; }; } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/all_flags_state/json_all_flags_state.cpp b/libs/server-sdk/src/all_flags_state/json_all_flags_state.cpp new file mode 100644 index 000000000..ef93f9cbd --- /dev/null +++ b/libs/server-sdk/src/all_flags_state/json_all_flags_state.cpp @@ -0,0 +1,56 @@ +#include "launchdarkly/serialization/json_evaluation_reason.hpp" +#include "launchdarkly/serialization/json_primitives.hpp" +#include "launchdarkly/serialization/json_value.hpp" +#include "launchdarkly/server_side/json_feature_flags_state.hpp" + +#include + +namespace launchdarkly::server_side { + +void tag_invoke(boost::json::value_from_tag const& unused, + boost::json::value& json_value, + server_side::AllFlagsState::Metadata const& state) { + boost::ignore_unused(unused); + auto& obj = json_value.emplace_object(); + + if (!state.OmitDetails()) { + obj.emplace("version", state.Version()); + + if (auto const& reason = state.Reason()) { + obj.emplace("reason", boost::json::value_from(*reason)); + } + } + + if (auto const& variation = state.Variation()) { + obj.emplace("variation", *variation); + } + + if (state.TrackEvents()) { + obj.emplace("trackEvents", true); + } + + if (state.TrackReason()) { + obj.emplace("trackReason", true); + } + + if (auto const& date = state.DebugEventsUntilDate()) { + if (*date > 0) { + obj.emplace("debugEventsUntilDate", boost::json::value_from(*date)); + } + } +} + +void tag_invoke(boost::json::value_from_tag const& unused, + boost::json::value& json_value, + server_side::AllFlagsState const& state) { + boost::ignore_unused(unused); + auto& obj = json_value.emplace_object(); + obj.emplace("$valid", state.Valid()); + + obj.emplace("$flagsState", boost::json::value_from(state.FlagsState())); + + for (auto const& [k, v] : state.Evaluations()) { + obj.emplace(k, boost::json::value_from(v)); + } +} +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/client.cpp b/libs/server-sdk/src/client.cpp index becbb5199..b5831b6f4 100644 --- a/libs/server-sdk/src/client.cpp +++ b/libs/server-sdk/src/client.cpp @@ -5,11 +5,23 @@ namespace launchdarkly::server_side { void operator|=(AllFlagsStateOptions& lhs, AllFlagsStateOptions rhs) { - lhs = static_cast( + lhs = lhs | rhs; +} + +AllFlagsStateOptions operator|(AllFlagsStateOptions lhs, + AllFlagsStateOptions rhs) { + return static_cast( static_cast>(lhs) | static_cast>(rhs)); } +AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, + AllFlagsStateOptions rhs) { + return static_cast( + static_cast>(lhs) & + static_cast>(rhs)); +} + Client::Client(Config config) : client(std::make_unique(std::move(config), kVersion)) {} @@ -22,7 +34,7 @@ std::future Client::StartAsync() { } using FlagKey = std::string; -[[nodiscard]] FeatureFlagsState Client::AllFlagsState( +[[nodiscard]] AllFlagsState Client::AllFlagsState( Context const& context, enum AllFlagsStateOptions options) { return client->AllFlagsState(context, options); diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 5b321949d..6ceee1a39 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -158,15 +158,15 @@ bool ClientImpl::Initialized() const { return IsInitializedSuccessfully(status_manager_.Status().State()); } -FeatureFlagsState ClientImpl::AllFlagsState(Context const& context, - enum AllFlagsStateOptions options) { +AllFlagsState ClientImpl::AllFlagsState(Context const& context, + enum AllFlagsStateOptions options) { std::unordered_map result; if (config_.Offline()) { LD_LOG(logger_, LogLevel::kWarn) << "AllFlagsState() called, but client is in offline mode. " "Returning empty state"; - return FeatureFlagsState{}; + return {}; } if (!Initialized()) { @@ -174,12 +174,34 @@ FeatureFlagsState ClientImpl::AllFlagsState(Context const& context, << "AllFlagsState() called before client has finished " "initializing! Feature store unavailable - returning empty " "state"; - return FeatureFlagsState{}; + return {}; } - AllFlagsStateBuilder builder{evaluator_, memory_store_}; + AllFlagsStateBuilder builder{options}; - return builder.Build(context, options); + for (auto const& [k, v] : memory_store_.AllFlags()) { + if (!v || !v->item) { + continue; + } + + auto const& flag = *(v->item); + + if (IsSet(options, AllFlagsStateOptions::ClientSideOnly) && + !flag.clientSideAvailability.usingEnvironmentId) { + continue; + } + + EvaluationDetail detail = evaluator_.Evaluate(flag, context); + + bool in_experiment = IsExperimentationEnabled(flag, detail.Reason()); + builder.AddFlag(k, detail.Value(), + AllFlagsState::Metadata{ + flag.Version(), detail.VariationIndex(), + detail.Reason(), flag.trackEvents || in_experiment, + in_experiment, flag.debugEventsUntilDate}); + } + + return builder.Build(); } void ClientImpl::TrackInternal(Context const& ctx, diff --git a/libs/server-sdk/src/client_impl.hpp b/libs/server-sdk/src/client_impl.hpp index 0cfde5359..cf24fbdaf 100644 --- a/libs/server-sdk/src/client_impl.hpp +++ b/libs/server-sdk/src/client_impl.hpp @@ -44,7 +44,7 @@ class ClientImpl : public IClient { bool Initialized() const override; using FlagKey = std::string; - [[nodiscard]] FeatureFlagsState AllFlagsState( + [[nodiscard]] class AllFlagsState AllFlagsState( Context const& context, enum AllFlagsStateOptions options = AllFlagsStateOptions::Default) override; diff --git a/libs/server-sdk/src/feature_flags_state.cpp b/libs/server-sdk/src/feature_flags_state.cpp deleted file mode 100644 index 93041d4f7..000000000 --- a/libs/server-sdk/src/feature_flags_state.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include - -namespace launchdarkly::server_side { -FeatureFlagsState::FeatureFlagsState() - : valid_(false), evaluations_(), flags_state_() {} - -FeatureFlagsState::FeatureFlagsState( - std::unordered_map evaluations, - std::unordered_map flags_state) - : valid_(true), - evaluations_(std::move(evaluations)), - flags_state_(std::move(flags_state)) {} - -bool FeatureFlagsState::Valid() const { - return valid_; -} - -std::unordered_map const& -FeatureFlagsState::FlagsState() const { - return flags_state_; -} - -std::unordered_map const& FeatureFlagsState::Evaluations() - const { - return evaluations_; -} - -bool operator==(FeatureFlagsState const& lhs, FeatureFlagsState const& rhs) { - return lhs.Valid() == rhs.Valid() && - lhs.Evaluations() == rhs.Evaluations() && - lhs.FlagsState() == rhs.FlagsState(); -} - -bool operator==(FeatureFlagsState::FlagState const& lhs, - FeatureFlagsState::FlagState const& rhs) { - return lhs.version == rhs.version && lhs.variation == rhs.variation && - lhs.reason == rhs.reason && lhs.track_events == rhs.track_events && - lhs.track_reason == rhs.track_reason && - lhs.debug_events_until_date == rhs.debug_events_until_date; -} - -} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/feature_flags_state_json.cpp b/libs/server-sdk/src/feature_flags_state_json.cpp deleted file mode 100644 index 8e394d17e..000000000 --- a/libs/server-sdk/src/feature_flags_state_json.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include -#include -#include -#include - -#include - -namespace launchdarkly::server_side { - -void tag_invoke(boost::json::value_from_tag const& unused, - boost::json::value& json_value, - server_side::FeatureFlagsState::FlagState const& state) { - boost::ignore_unused(unused); - auto& obj = json_value.emplace_object(); - if (state.version) { - obj.emplace("version", *state.version); - } - if (state.variation) { - obj.emplace("variation", *state.variation); - } - if (state.reason) { - obj.emplace("reason", boost::json::value_from(*state.reason)); - } - if (state.track_events) { - obj.emplace("trackEvents", true); - } - if (state.track_reason) { - obj.emplace("trackReason", true); - } - if (state.debug_events_until_date) { - obj.emplace("debugEventsUntilDate", - boost::json::value_from(*state.debug_events_until_date)); - } -} - -void tag_invoke(boost::json::value_from_tag const& unused, - boost::json::value& json_value, - server_side::FeatureFlagsState const& state) { - boost::ignore_unused(unused); - auto& obj = json_value.emplace_object(); - obj.emplace("$valid", state.Valid()); - - obj.emplace("$flagsState", boost::json::value_from(state.FlagsState())); - - for (auto const [k, v] : state.Evaluations()) { - obj.emplace(k, boost::json::value_from(v)); - } -} -} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/tests/all_flags_state.cpp b/libs/server-sdk/tests/all_flags_state.cpp deleted file mode 100644 index 9b9ca60ac..000000000 --- a/libs/server-sdk/tests/all_flags_state.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include - -#include "all_flags_state/all_flags_state_builder.hpp" -#include "data_store/memory_store.hpp" -#include "test_store.hpp" - -#include -#include -#include - -#include - -using namespace launchdarkly; -using namespace launchdarkly::server_side; - -class AllFlagsTest : public ::testing::Test { - protected: - AllFlagsTest() - : logger_(logging::NullLogger()), - store_(test_store::Empty()), - evaluator_(logger_, *store_), - builder_(evaluator_, *store_), - context_(ContextBuilder().Kind("shadow", "cat").Build()) {} - Logger logger_; - std::unique_ptr store_; - evaluation::Evaluator evaluator_; - AllFlagsStateBuilder builder_; - Context context_; -}; - -TEST_F(AllFlagsTest, Empty) { - auto state = builder_.Build(context_, AllFlagsStateOptions::Default); - ASSERT_TRUE(state.Valid()); - ASSERT_TRUE(state.FlagsState().empty()); - ASSERT_TRUE(state.Evaluations().empty()); -} - -TEST_F(AllFlagsTest, DefaultOptions) { - auto mem_store = static_cast(store_.get()); - mem_store->Upsert("myFlag", test_store::Flag(R"({ - "key": "myFlag", - "version": 42, - "on": true, - "targets": [], - "rules": [], - "prerequisites": [], - "fallthrough": {"variation": 1}, - "offVariation": 0, - "variations": [false, true], - "clientSideAvailability": { - "usingMobileKey": false, - "usingEnvironmentId": false - }, - "salt": "kosher" - })")); - - auto state = builder_.Build(context_, AllFlagsStateOptions::Default); - ASSERT_TRUE(state.Valid()); - - auto expected = boost::json::parse(R"({ - "myFlag": true, - "$flagsState": { - "myFlag": { - "version": 42, - "variation": 1 - } - }, - "$valid": true - })"); - - auto got = boost::json::value_from(state); - - ASSERT_EQ(got, expected); -} - -TEST_F(AllFlagsTest, HandlesExperimentationReasons) {} diff --git a/libs/server-sdk/tests/all_flags_state_test.cpp b/libs/server-sdk/tests/all_flags_state_test.cpp new file mode 100644 index 000000000..7996c883a --- /dev/null +++ b/libs/server-sdk/tests/all_flags_state_test.cpp @@ -0,0 +1,113 @@ +#include + +#include "all_flags_state/all_flags_state_builder.hpp" +#include "data_store/memory_store.hpp" +#include "test_store.hpp" + +#include +#include +#include + +#include + +using namespace launchdarkly; +using namespace launchdarkly::server_side; + +TEST(AllFlagsTest, Empty) { + AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; + auto state = builder.Build(); + ASSERT_TRUE(state.Valid()); + ASSERT_TRUE(state.FlagsState().empty()); + ASSERT_TRUE(state.Evaluations().empty()); +} + +TEST(AllFlagsTest, DefaultOptions) { + AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; + + builder.AddFlag("myFlag", true, + AllFlagsState::Metadata{42, 1, std::nullopt, false, false, + std::nullopt}); + + auto state = builder.Build(); + ASSERT_TRUE(state.Valid()); + + auto expected = boost::json::parse(R"({ + "myFlag": true, + "$flagsState": { + "myFlag": { + "version": 42, + "variation": 1 + } + }, + "$valid": true + })"); + + auto got = boost::json::value_from(state); + ASSERT_EQ(got, expected); +} + +TEST(AllFlagsTest, DetailsOnlyForTrackedFlags) { + AllFlagsStateBuilder builder{ + AllFlagsStateOptions::DetailsOnlyForTrackedFlags}; + builder.AddFlag( + "myFlagTracked", true, + AllFlagsState::Metadata{42, 1, EvaluationReason::Fallthrough(false), + true, true, std::nullopt}); + builder.AddFlag( + "myFlagUntracked", true, + AllFlagsState::Metadata{42, 1, EvaluationReason::Fallthrough(false), + false, false, std::nullopt}); + + auto state = builder.Build(); + ASSERT_TRUE(state.Valid()); + + auto expected = boost::json::parse(R"({ + "myFlagTracked" : true, + "myFlagUntracked" : true, + "$flagsState": { + "myFlagTracked": { + "version": 42, + "variation": 1, + "reason" : { + "kind": "FALLTHROUGH" + }, + "trackReason" : true, + "trackEvents" : true + }, + "myFlagUntracked" : { + "variation" : 1 + } + }, + "$valid": true + })"); + + auto got = boost::json::value_from(state); + ASSERT_EQ(got, expected); +} + +TEST(AllFlagsTest, ClientSide) { + AllFlagsStateBuilder builder{AllFlagsStateOptions::IncludeReasons}; + builder.AddFlag( + "myFlag", true, + AllFlagsState::Metadata{42, 1, EvaluationReason::Fallthrough(false), + false, false, std::nullopt}); + auto state = builder.Build(); + ASSERT_TRUE(state.Valid()); + + auto expected = boost::json::parse(R"({ + "myFlag": true, + "$flagsState": { + "myFlag": { + "version": 42, + "variation": 1, + "reason" : { + "kind": "FALLTHROUGH" + } + } + }, + "$valid": true + })"); + + auto got = boost::json::value_from(state); + ASSERT_EQ(got, expected); +} diff --git a/libs/server-sdk/tests/client_test.cpp b/libs/server-sdk/tests/client_test.cpp index ad1913db2..b5475ca5b 100644 --- a/libs/server-sdk/tests/client_test.cpp +++ b/libs/server-sdk/tests/client_test.cpp @@ -72,6 +72,8 @@ TEST_F(ClientTest, JsonVariationDefaultPassesThrough) { TEST_F(ClientTest, AllFlagsStateNotValid) { // Since we don't have any ability to insert into the data store at the time // this test is written, just check that the result is not valid. - auto flags = client_.AllFlagsState(context_); + auto flags = client_.AllFlagsState( + context_, AllFlagsStateOptions::IncludeReasons | + AllFlagsStateOptions::ClientSideOnly); ASSERT_FALSE(flags.Valid()); } From 796ff708a35af868335ee54091569759f917799c Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 13:57:15 -0700 Subject: [PATCH 28/55] doc comments on AllFlagsState --- .../server_side/all_flags_state.hpp | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp b/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp index 0c3b296ee..3df632112 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp @@ -9,8 +9,19 @@ namespace launchdarkly::server_side { +/** + * AllFlagsState is a snapshot of the state of multiple feature flags with + * regard to a specific evaluation context. + * + * Serializing this object to JSON using boost::json::value_from will produce + * the appropriate data structure for bootstrapping the LaunchDarkly JavaScript + * client. + */ class AllFlagsState { public: + /** + * Metadata contains information pertaining to a single feature flag. + */ class Metadata { public: Metadata(std::uint64_t version, @@ -20,13 +31,52 @@ class AllFlagsState { bool track_reason, std::optional debug_events_until_date); + /** + * @return The flag's version number when it was evaluated. + */ [[nodiscard]] std::uint64_t Version() const; + + /** + * @return The variation index that was selected for the specified + * evaluation context. + */ [[nodiscard]] std::optional Variation() const; + + /** + * @return The reason that the flag evaluation produced the specified + * variation. + */ [[nodiscard]] std::optional const& Reason() const; + + /** + * @return True if a full feature event must be sent when evaluating + * this flag. This will be true if tracking was explicitly enabled for + * this flag for data export, or if the evaluation involved an + * experiment, or both. + */ [[nodiscard]] bool TrackEvents() const; + + /** + * @return True if the evaluation reason should always be included in + * any full feature event created for this flag, regardless of whether a + * VariationDetail method was called. This will be true if the + * evaluation involved an experiment. + */ [[nodiscard]] bool TrackReason() const; + + /** + * @return The date on which debug mode expires for this flag, if + * enabled. + */ [[nodiscard]] std::optional const& DebugEventsUntilDate() const; + + /** + * + * @return True if the options passed to AllFlagsState, combined with + * the obtained flag state, indicate that some metadata can be left out + * of the JSON serialization. + */ [[nodiscard]] bool OmitDetails() const; friend class AllFlagsStateBuilder; @@ -41,10 +91,22 @@ class AllFlagsState { bool omit_details_; }; + /** + * @return True if the call to AllFlagsState succeeded. False if there was + * an error, such as the data store being unavailable. When false, the other + * accessors will return empty maps. + */ [[nodiscard]] bool Valid() const; + /** + * @return A map of metadata for each flag. + */ [[nodiscard]] std::unordered_map const& FlagsState() const; + + /** + * @return A map of evaluation results for each flag. + */ [[nodiscard]] std::unordered_map const& Evaluations() const; From 3713595cd73ed725f76edd564caac8a74b0a5a38 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 14:47:41 -0700 Subject: [PATCH 29/55] renamings --- .../src/client_entity.cpp | 2 +- .../server_side/all_flags_state.hpp | 35 +++++---- .../json_all_flags_state.hpp} | 2 +- .../src/all_flags_state/all_flags_state.cpp | 39 +++++----- .../all_flags_state_builder.cpp | 2 +- .../all_flags_state_builder.hpp | 5 +- .../all_flags_state/json_all_flags_state.cpp | 14 ++-- libs/server-sdk/src/client_impl.cpp | 2 +- .../server-sdk/tests/all_flags_state_test.cpp | 75 ++++++++++++++----- 9 files changed, 106 insertions(+), 70 deletions(-) rename libs/server-sdk/include/launchdarkly/server_side/{json_feature_flags_state.hpp => serialization/json_all_flags_state.hpp} (87%) diff --git a/contract-tests/server-contract-tests/src/client_entity.cpp b/contract-tests/server-contract-tests/src/client_entity.cpp index 26f3535c7..cf70e7f3b 100644 --- a/contract-tests/server-contract-tests/src/client_entity.cpp +++ b/contract-tests/server-contract-tests/src/client_entity.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include diff --git a/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp b/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp index 3df632112..06f9a4e27 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp @@ -16,20 +16,25 @@ namespace launchdarkly::server_side { * Serializing this object to JSON using boost::json::value_from will produce * the appropriate data structure for bootstrapping the LaunchDarkly JavaScript * client. + * + * To do this, the header + * must be + * included to make the appropriate `tag_invoke` implementations available to + * boost. */ class AllFlagsState { public: /** - * Metadata contains information pertaining to a single feature flag. + * State contains information pertaining to a single feature flag. */ - class Metadata { + class State { public: - Metadata(std::uint64_t version, - std::optional variation, - std::optional reason, - bool track_events, - bool track_reason, - std::optional debug_events_until_date); + State(std::uint64_t version, + std::optional variation, + std::optional reason, + bool track_events, + bool track_reason, + std::optional debug_events_until_date); /** * @return The flag's version number when it was evaluated. @@ -101,14 +106,12 @@ class AllFlagsState { /** * @return A map of metadata for each flag. */ - [[nodiscard]] std::unordered_map const& FlagsState() - const; + [[nodiscard]] std::unordered_map const& States() const; /** * @return A map of evaluation results for each flag. */ - [[nodiscard]] std::unordered_map const& Evaluations() - const; + [[nodiscard]] std::unordered_map const& Values() const; /** * Constructs an invalid instance of AllFlagsState. @@ -121,16 +124,16 @@ class AllFlagsState { * @param flags_state A map of metadata for each flag. */ AllFlagsState(std::unordered_map evaluations, - std::unordered_map flags_state); + std::unordered_map flags_state); private: bool const valid_; - const std::unordered_map flags_state_; + const std::unordered_map flags_state_; const std::unordered_map evaluations_; }; -bool operator==(AllFlagsState::Metadata const& lhs, - AllFlagsState::Metadata const& rhs); +bool operator==(class AllFlagsState::State const& lhs, + class AllFlagsState::State const& rhs); bool operator==(AllFlagsState const& lhs, AllFlagsState const& rhs); diff --git a/libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp b/libs/server-sdk/include/launchdarkly/server_side/serialization/json_all_flags_state.hpp similarity index 87% rename from libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp rename to libs/server-sdk/include/launchdarkly/server_side/serialization/json_all_flags_state.hpp index 3e98dff01..b644119a1 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/json_feature_flags_state.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/serialization/json_all_flags_state.hpp @@ -8,7 +8,7 @@ namespace launchdarkly::server_side { void tag_invoke(boost::json::value_from_tag const& unused, boost::json::value& json_value, - server_side::AllFlagsState::Metadata const& state); + server_side::AllFlagsState::State const& state); void tag_invoke(boost::json::value_from_tag const& unused, boost::json::value& json_value, diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state.cpp b/libs/server-sdk/src/all_flags_state/all_flags_state.cpp index a2a42671e..d2b1254ab 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state.cpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state.cpp @@ -2,7 +2,7 @@ namespace launchdarkly::server_side { -AllFlagsState::Metadata::Metadata( +AllFlagsState::State::State( std::uint64_t version, std::optional variation, std::optional reason, @@ -17,41 +17,40 @@ AllFlagsState::Metadata::Metadata( debug_events_until_date_(debug_events_until_date), omit_details_(false) {} -std::uint64_t AllFlagsState::Metadata::Version() const { +std::uint64_t AllFlagsState::State::Version() const { return version_; } -std::optional AllFlagsState::Metadata::Variation() const { +std::optional AllFlagsState::State::Variation() const { return variation_; } -std::optional const& AllFlagsState::Metadata::Reason() const { +std::optional const& AllFlagsState::State::Reason() const { return reason_; } -bool AllFlagsState::Metadata::TrackEvents() const { +bool AllFlagsState::State::TrackEvents() const { return track_events_; } -bool AllFlagsState::Metadata::TrackReason() const { +bool AllFlagsState::State::TrackReason() const { return track_reason_; } -std::optional const& -AllFlagsState::Metadata::DebugEventsUntilDate() const { +std::optional const& AllFlagsState::State::DebugEventsUntilDate() + const { return debug_events_until_date_; } -bool AllFlagsState::Metadata::OmitDetails() const { +bool AllFlagsState::State::OmitDetails() const { return omit_details_; } AllFlagsState::AllFlagsState() : valid_(false), evaluations_(), flags_state_() {} -AllFlagsState::AllFlagsState( - std::unordered_map evaluations, - std::unordered_map flags_state) +AllFlagsState::AllFlagsState(std::unordered_map evaluations, + std::unordered_map flags_state) : valid_(true), evaluations_(std::move(evaluations)), flags_state_(std::move(flags_state)) {} @@ -60,24 +59,22 @@ bool AllFlagsState::Valid() const { return valid_; } -std::unordered_map const& -AllFlagsState::FlagsState() const { +std::unordered_map const& +AllFlagsState::States() const { return flags_state_; } -std::unordered_map const& AllFlagsState::Evaluations() - const { +std::unordered_map const& AllFlagsState::Values() const { return evaluations_; } bool operator==(AllFlagsState const& lhs, AllFlagsState const& rhs) { - return lhs.Valid() == rhs.Valid() && - lhs.Evaluations() == rhs.Evaluations() && - lhs.FlagsState() == rhs.FlagsState(); + return lhs.Valid() == rhs.Valid() && lhs.Values() == rhs.Values() && + lhs.States() == rhs.States(); } -bool operator==(AllFlagsState::Metadata const& lhs, - AllFlagsState::Metadata const& rhs) { +bool operator==(AllFlagsState::State const& lhs, + AllFlagsState::State const& rhs) { return lhs.Version() == rhs.Version() && lhs.Variation() == rhs.Variation() && lhs.Reason() == rhs.Reason() && lhs.TrackEvents() == rhs.TrackEvents() && diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp index ea5aadfdf..3a0a7e5ff 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp @@ -9,7 +9,7 @@ AllFlagsStateBuilder::AllFlagsStateBuilder(enum AllFlagsStateOptions options) void AllFlagsStateBuilder::AddFlag(std::string const& key, Value value, - AllFlagsState::Metadata flag) { + AllFlagsState::State flag) { if (IsSet(options_, AllFlagsStateOptions::DetailsOnlyForTrackedFlags)) { if (!flag.TrackEvents() && !flag.TrackReason() && !IsDebuggingEnabled(flag.DebugEventsUntilDate())) { diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp index 94c086ba8..88d643d0b 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include "../data_store/data_store.hpp" @@ -30,7 +29,7 @@ class AllFlagsStateBuilder { */ void AddFlag(std::string const& key, Value value, - AllFlagsState::Metadata state); + AllFlagsState::State state); /** * Builds a AllFlagsState structure from the flags added to the builder. @@ -41,7 +40,7 @@ class AllFlagsStateBuilder { private: enum AllFlagsStateOptions options_; - std::unordered_map flags_state_; + std::unordered_map flags_state_; std::unordered_map evaluations_; }; } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/all_flags_state/json_all_flags_state.cpp b/libs/server-sdk/src/all_flags_state/json_all_flags_state.cpp index ef93f9cbd..222ac27f7 100644 --- a/libs/server-sdk/src/all_flags_state/json_all_flags_state.cpp +++ b/libs/server-sdk/src/all_flags_state/json_all_flags_state.cpp @@ -1,15 +1,15 @@ -#include "launchdarkly/serialization/json_evaluation_reason.hpp" -#include "launchdarkly/serialization/json_primitives.hpp" -#include "launchdarkly/serialization/json_value.hpp" -#include "launchdarkly/server_side/json_feature_flags_state.hpp" +#include +#include +#include #include +#include namespace launchdarkly::server_side { void tag_invoke(boost::json::value_from_tag const& unused, boost::json::value& json_value, - server_side::AllFlagsState::Metadata const& state) { + server_side::AllFlagsState::State const& state) { boost::ignore_unused(unused); auto& obj = json_value.emplace_object(); @@ -47,9 +47,9 @@ void tag_invoke(boost::json::value_from_tag const& unused, auto& obj = json_value.emplace_object(); obj.emplace("$valid", state.Valid()); - obj.emplace("$flagsState", boost::json::value_from(state.FlagsState())); + obj.emplace("$flagsState", boost::json::value_from(state.States())); - for (auto const& [k, v] : state.Evaluations()) { + for (auto const& [k, v] : state.Values()) { obj.emplace(k, boost::json::value_from(v)); } } diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 6ceee1a39..43bb4980a 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -195,7 +195,7 @@ AllFlagsState ClientImpl::AllFlagsState(Context const& context, bool in_experiment = IsExperimentationEnabled(flag, detail.Reason()); builder.AddFlag(k, detail.Value(), - AllFlagsState::Metadata{ + AllFlagsState::State{ flag.Version(), detail.VariationIndex(), detail.Reason(), flag.trackEvents || in_experiment, in_experiment, flag.debugEventsUntilDate}); diff --git a/libs/server-sdk/tests/all_flags_state_test.cpp b/libs/server-sdk/tests/all_flags_state_test.cpp index 7996c883a..0f6a54cba 100644 --- a/libs/server-sdk/tests/all_flags_state_test.cpp +++ b/libs/server-sdk/tests/all_flags_state_test.cpp @@ -1,14 +1,8 @@ #include #include "all_flags_state/all_flags_state_builder.hpp" -#include "data_store/memory_store.hpp" -#include "test_store.hpp" -#include -#include -#include - -#include +#include using namespace launchdarkly; using namespace launchdarkly::server_side; @@ -17,16 +11,16 @@ TEST(AllFlagsTest, Empty) { AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; auto state = builder.Build(); ASSERT_TRUE(state.Valid()); - ASSERT_TRUE(state.FlagsState().empty()); - ASSERT_TRUE(state.Evaluations().empty()); + ASSERT_TRUE(state.States().empty()); + ASSERT_TRUE(state.Values().empty()); } TEST(AllFlagsTest, DefaultOptions) { AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; - builder.AddFlag("myFlag", true, - AllFlagsState::Metadata{42, 1, std::nullopt, false, false, - std::nullopt}); + builder.AddFlag( + "myFlag", true, + AllFlagsState::State{42, 1, std::nullopt, false, false, std::nullopt}); auto state = builder.Build(); ASSERT_TRUE(state.Valid()); @@ -51,12 +45,12 @@ TEST(AllFlagsTest, DetailsOnlyForTrackedFlags) { AllFlagsStateOptions::DetailsOnlyForTrackedFlags}; builder.AddFlag( "myFlagTracked", true, - AllFlagsState::Metadata{42, 1, EvaluationReason::Fallthrough(false), - true, true, std::nullopt}); + AllFlagsState::State{42, 1, EvaluationReason::Fallthrough(false), true, + true, std::nullopt}); builder.AddFlag( "myFlagUntracked", true, - AllFlagsState::Metadata{42, 1, EvaluationReason::Fallthrough(false), - false, false, std::nullopt}); + AllFlagsState::State{42, 1, EvaluationReason::Fallthrough(false), false, + false, std::nullopt}); auto state = builder.Build(); ASSERT_TRUE(state.Valid()); @@ -85,12 +79,12 @@ TEST(AllFlagsTest, DetailsOnlyForTrackedFlags) { ASSERT_EQ(got, expected); } -TEST(AllFlagsTest, ClientSide) { +TEST(AllFlagsTest, IncludeReasons) { AllFlagsStateBuilder builder{AllFlagsStateOptions::IncludeReasons}; builder.AddFlag( "myFlag", true, - AllFlagsState::Metadata{42, 1, EvaluationReason::Fallthrough(false), - false, false, std::nullopt}); + AllFlagsState::State{42, 1, EvaluationReason::Fallthrough(false), false, + false, std::nullopt}); auto state = builder.Build(); ASSERT_TRUE(state.Valid()); @@ -111,3 +105,46 @@ TEST(AllFlagsTest, ClientSide) { auto got = boost::json::value_from(state); ASSERT_EQ(got, expected); } + +TEST(AllFlagsTest, FlagValues) { + AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; + + const std::size_t kNumFlags = 10; + + for (std::size_t i = 0; i < kNumFlags; i++) { + builder.AddFlag("myFlag" + std::to_string(i), "value", + AllFlagsState::State{42, 1, std::nullopt, false, false, + std::nullopt}); + } + + auto state = builder.Build(); + + auto const& vals = state.Values(); + + ASSERT_EQ(vals.size(), kNumFlags); + + ASSERT_TRUE(std::all_of(vals.begin(), vals.end(), [](auto const& kvp) { + return kvp.second.AsString() == "value"; + })); +} + +TEST(AllFlagsTest, FlagState) { + AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; + + const std::size_t kNumFlags = 10; + + AllFlagsState::State state{42, 1, std::nullopt, false, false, std::nullopt}; + for (std::size_t i = 0; i < kNumFlags; i++) { + builder.AddFlag("myFlag" + std::to_string(i), "value", state); + } + + auto all_flags_state = builder.Build(); + + auto const& states = all_flags_state.States(); + + ASSERT_EQ(states.size(), kNumFlags); + + ASSERT_TRUE(std::all_of(states.begin(), states.end(), [&](auto const& kvp) { + return kvp.second == state; + })); +} From 144d7052482c4e62ee50481187d17fca2786352f Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 15:03:08 -0700 Subject: [PATCH 30/55] refactor AllFlagsStateOptions into enum nested in AllFlagsState struct --- .../src/client_entity.cpp | 8 ++-- .../server_side/all_flags_state.hpp | 32 +++++++++++++++ .../launchdarkly/server_side/client.hpp | 39 ++----------------- .../all_flags_state_builder.cpp | 10 ++--- .../all_flags_state_builder.hpp | 8 ++-- libs/server-sdk/src/client.cpp | 24 ++++++------ libs/server-sdk/src/client_impl.cpp | 4 +- libs/server-sdk/src/client_impl.hpp | 4 +- .../server-sdk/tests/all_flags_state_test.cpp | 15 +++---- libs/server-sdk/tests/client_test.cpp | 8 ++-- 10 files changed, 74 insertions(+), 78 deletions(-) diff --git a/contract-tests/server-contract-tests/src/client_entity.cpp b/contract-tests/server-contract-tests/src/client_entity.cpp index cf70e7f3b..c8fdb40ca 100644 --- a/contract-tests/server-contract-tests/src/client_entity.cpp +++ b/contract-tests/server-contract-tests/src/client_entity.cpp @@ -187,15 +187,15 @@ tl::expected ClientEntity::EvaluateAll( return tl::make_unexpected(maybe_ctx.error()); } - AllFlagsStateOptions options = AllFlagsStateOptions::Default; + AllFlagsState::Options options = AllFlagsState::Options::Default; if (params.withReasons.value_or(false)) { - options |= AllFlagsStateOptions::IncludeReasons; + options |= AllFlagsState::Options::IncludeReasons; } if (params.clientSideOnly.value_or(false)) { - options |= AllFlagsStateOptions::ClientSideOnly; + options |= AllFlagsState::Options::ClientSideOnly; } if (params.detailsOnlyForTrackedFlags.value_or(false)) { - options |= AllFlagsStateOptions::DetailsOnlyForTrackedFlags; + options |= AllFlagsState::Options::DetailsOnlyForTrackedFlags; } auto state = client_->AllFlagsState(*maybe_ctx, options); diff --git a/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp b/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp index 06f9a4e27..6f4b50b63 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/all_flags_state.hpp @@ -24,6 +24,31 @@ namespace launchdarkly::server_side { */ class AllFlagsState { public: + enum class Options : std::uint8_t { + /** + * Default behavior. + */ + Default = 0, + /** + * Include evaluation reasons in the state object. By default, they + * are not. + */ + IncludeReasons = (1 << 0), + /** + * Include detailed flag metadata only for flags with event tracking + * or debugging turned on. + * + * This reduces the size of the JSON data if you are + * passing the flag state to the front end. + */ + DetailsOnlyForTrackedFlags = (1 << 1), + /** + * Include only flags marked for use with the client-side SDK. + * By default, all flags are included. + */ + ClientSideOnly = (1 << 2) + }; + /** * State contains information pertaining to a single feature flag. */ @@ -132,6 +157,13 @@ class AllFlagsState { const std::unordered_map evaluations_; }; +void operator|=(AllFlagsState::Options& lhs, AllFlagsState::Options rhs); +AllFlagsState::Options operator|(AllFlagsState::Options lhs, + AllFlagsState::Options rhs); + +AllFlagsState::Options operator&(AllFlagsState::Options lhs, + AllFlagsState::Options rhs); + bool operator==(class AllFlagsState::State const& lhs, class AllFlagsState::State const& rhs); diff --git a/libs/server-sdk/include/launchdarkly/server_side/client.hpp b/libs/server-sdk/include/launchdarkly/server_side/client.hpp index 988ccf8cf..921a59853 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/client.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/client.hpp @@ -14,39 +14,6 @@ #include namespace launchdarkly::server_side { - -enum class AllFlagsStateOptions : std::uint8_t { - /** - * Default behavior. - */ - Default = 0, - /** - * Include evaluation reasons in the state object. By default, they - * are not. - */ - IncludeReasons = (1 << 0), - /** - * Include detailed flag metadata only for flags with event tracking - * or debugging turned on. - * - * This reduces the size of the JSON data if you are - * passing the flag state to the front end. - */ - DetailsOnlyForTrackedFlags = (1 << 1), - /** - * Include only flags marked for use with the client-side SDK. - * By default, all flags are included. - */ - ClientSideOnly = (1 << 2) -}; - -void operator|=(AllFlagsStateOptions& lhs, AllFlagsStateOptions rhs); -AllFlagsStateOptions operator|(AllFlagsStateOptions lhs, - AllFlagsStateOptions rhs); - -AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, - AllFlagsStateOptions rhs); - /** * Interface for the standard SDK client methods and properties. */ @@ -94,7 +61,7 @@ class IClient { */ [[nodiscard]] virtual AllFlagsState AllFlagsState( Context const& context, - enum AllFlagsStateOptions options = AllFlagsStateOptions::Default) = 0; + AllFlagsState::Options options = AllFlagsState::Options::Default) = 0; /** * Tracks that the current context performed an event for the given event @@ -295,8 +262,8 @@ class Client : public IClient { using FlagKey = std::string; [[nodiscard]] class AllFlagsState AllFlagsState( Context const& context, - enum AllFlagsStateOptions options = - AllFlagsStateOptions::Default) override; + enum AllFlagsState::Options options = + AllFlagsState::Options::Default) override; void Track(Context const& ctx, std::string event_name, diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp index 3a0a7e5ff..3ef95ac44 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp @@ -4,19 +4,19 @@ namespace launchdarkly::server_side { bool IsDebuggingEnabled(std::optional debug_events_until); -AllFlagsStateBuilder::AllFlagsStateBuilder(enum AllFlagsStateOptions options) +AllFlagsStateBuilder::AllFlagsStateBuilder(AllFlagsState::Options options) : options_(options), flags_state_(), evaluations_() {} void AllFlagsStateBuilder::AddFlag(std::string const& key, Value value, AllFlagsState::State flag) { - if (IsSet(options_, AllFlagsStateOptions::DetailsOnlyForTrackedFlags)) { + if (IsSet(options_, AllFlagsState::Options::DetailsOnlyForTrackedFlags)) { if (!flag.TrackEvents() && !flag.TrackReason() && !IsDebuggingEnabled(flag.DebugEventsUntilDate())) { flag.omit_details_ = true; } } - if (NotSet(options_, AllFlagsStateOptions::IncludeReasons) || + if (NotSet(options_, AllFlagsState::Options::IncludeReasons) || flag.TrackReason()) { flag.reason_ = std::nullopt; } @@ -50,11 +50,11 @@ bool IsExperimentationEnabled(data_model::Flag const& flag, } } -bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag) { +bool IsSet(AllFlagsState::Options options, AllFlagsState::Options flag) { return (options & flag) == flag; } -bool NotSet(AllFlagsStateOptions options, AllFlagsStateOptions flag) { +bool NotSet(AllFlagsState::Options options, AllFlagsState::Options flag) { return !IsSet(options, flag); } diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp index 88d643d0b..bda58d815 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp @@ -10,8 +10,8 @@ namespace launchdarkly::server_side { bool IsExperimentationEnabled(data_model::Flag const& flag, std::optional const& reason); -bool IsSet(AllFlagsStateOptions options, AllFlagsStateOptions flag); -bool NotSet(AllFlagsStateOptions options, AllFlagsStateOptions flag); +bool IsSet(AllFlagsState::Options options, AllFlagsState::Options flag); +bool NotSet(AllFlagsState::Options options, AllFlagsState::Options flag); class AllFlagsStateBuilder { public: @@ -19,7 +19,7 @@ class AllFlagsStateBuilder { * Constructs a builder capable of generating a AllFlagsState structure. * @param options Options affecting the behavior of the builder. */ - AllFlagsStateBuilder(enum AllFlagsStateOptions options); + AllFlagsStateBuilder(AllFlagsState::Options options); /** * Adds a flag, including its evaluation result and additional state. @@ -39,7 +39,7 @@ class AllFlagsStateBuilder { [[nodiscard]] AllFlagsState Build(); private: - enum AllFlagsStateOptions options_; + enum AllFlagsState::Options options_; std::unordered_map flags_state_; std::unordered_map evaluations_; }; diff --git a/libs/server-sdk/src/client.cpp b/libs/server-sdk/src/client.cpp index b5831b6f4..4a88b054a 100644 --- a/libs/server-sdk/src/client.cpp +++ b/libs/server-sdk/src/client.cpp @@ -4,22 +4,22 @@ namespace launchdarkly::server_side { -void operator|=(AllFlagsStateOptions& lhs, AllFlagsStateOptions rhs) { +void operator|=(AllFlagsState::Options& lhs, AllFlagsState::Options rhs) { lhs = lhs | rhs; } -AllFlagsStateOptions operator|(AllFlagsStateOptions lhs, - AllFlagsStateOptions rhs) { - return static_cast( - static_cast>(lhs) | - static_cast>(rhs)); +AllFlagsState::Options operator|(AllFlagsState::Options lhs, + AllFlagsState::Options rhs) { + return static_cast( + static_cast>(lhs) | + static_cast>(rhs)); } -AllFlagsStateOptions operator&(AllFlagsStateOptions lhs, - AllFlagsStateOptions rhs) { - return static_cast( - static_cast>(lhs) & - static_cast>(rhs)); +AllFlagsState::Options operator&(AllFlagsState::Options lhs, + AllFlagsState::Options rhs) { + return static_cast( + static_cast>(lhs) & + static_cast>(rhs)); } Client::Client(Config config) @@ -36,7 +36,7 @@ std::future Client::StartAsync() { using FlagKey = std::string; [[nodiscard]] AllFlagsState Client::AllFlagsState( Context const& context, - enum AllFlagsStateOptions options) { + enum AllFlagsState::Options options) { return client->AllFlagsState(context, options); } diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 43bb4980a..e1c7e6292 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -159,7 +159,7 @@ bool ClientImpl::Initialized() const { } AllFlagsState ClientImpl::AllFlagsState(Context const& context, - enum AllFlagsStateOptions options) { + AllFlagsState::Options options) { std::unordered_map result; if (config_.Offline()) { @@ -186,7 +186,7 @@ AllFlagsState ClientImpl::AllFlagsState(Context const& context, auto const& flag = *(v->item); - if (IsSet(options, AllFlagsStateOptions::ClientSideOnly) && + if (IsSet(options, AllFlagsState::Options::ClientSideOnly) && !flag.clientSideAvailability.usingEnvironmentId) { continue; } diff --git a/libs/server-sdk/src/client_impl.hpp b/libs/server-sdk/src/client_impl.hpp index cf24fbdaf..beab76374 100644 --- a/libs/server-sdk/src/client_impl.hpp +++ b/libs/server-sdk/src/client_impl.hpp @@ -46,8 +46,8 @@ class ClientImpl : public IClient { using FlagKey = std::string; [[nodiscard]] class AllFlagsState AllFlagsState( Context const& context, - enum AllFlagsStateOptions options = - AllFlagsStateOptions::Default) override; + AllFlagsState::Options options = + AllFlagsState::Options::Default) override; void Track(Context const& ctx, std::string event_name, diff --git a/libs/server-sdk/tests/all_flags_state_test.cpp b/libs/server-sdk/tests/all_flags_state_test.cpp index 0f6a54cba..839cb47b8 100644 --- a/libs/server-sdk/tests/all_flags_state_test.cpp +++ b/libs/server-sdk/tests/all_flags_state_test.cpp @@ -8,7 +8,7 @@ using namespace launchdarkly; using namespace launchdarkly::server_side; TEST(AllFlagsTest, Empty) { - AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; + AllFlagsStateBuilder builder{AllFlagsState::Options::Default}; auto state = builder.Build(); ASSERT_TRUE(state.Valid()); ASSERT_TRUE(state.States().empty()); @@ -16,7 +16,7 @@ TEST(AllFlagsTest, Empty) { } TEST(AllFlagsTest, DefaultOptions) { - AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; + AllFlagsStateBuilder builder{AllFlagsState::Options::Default}; builder.AddFlag( "myFlag", true, @@ -42,7 +42,7 @@ TEST(AllFlagsTest, DefaultOptions) { TEST(AllFlagsTest, DetailsOnlyForTrackedFlags) { AllFlagsStateBuilder builder{ - AllFlagsStateOptions::DetailsOnlyForTrackedFlags}; + AllFlagsState::Options::DetailsOnlyForTrackedFlags}; builder.AddFlag( "myFlagTracked", true, AllFlagsState::State{42, 1, EvaluationReason::Fallthrough(false), true, @@ -62,9 +62,6 @@ TEST(AllFlagsTest, DetailsOnlyForTrackedFlags) { "myFlagTracked": { "version": 42, "variation": 1, - "reason" : { - "kind": "FALLTHROUGH" - }, "trackReason" : true, "trackEvents" : true }, @@ -80,7 +77,7 @@ TEST(AllFlagsTest, DetailsOnlyForTrackedFlags) { } TEST(AllFlagsTest, IncludeReasons) { - AllFlagsStateBuilder builder{AllFlagsStateOptions::IncludeReasons}; + AllFlagsStateBuilder builder{AllFlagsState::Options::IncludeReasons}; builder.AddFlag( "myFlag", true, AllFlagsState::State{42, 1, EvaluationReason::Fallthrough(false), false, @@ -107,7 +104,7 @@ TEST(AllFlagsTest, IncludeReasons) { } TEST(AllFlagsTest, FlagValues) { - AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; + AllFlagsStateBuilder builder{AllFlagsState::Options::Default}; const std::size_t kNumFlags = 10; @@ -129,7 +126,7 @@ TEST(AllFlagsTest, FlagValues) { } TEST(AllFlagsTest, FlagState) { - AllFlagsStateBuilder builder{AllFlagsStateOptions::Default}; + AllFlagsStateBuilder builder{AllFlagsState::Options::Default}; const std::size_t kNumFlags = 10; diff --git a/libs/server-sdk/tests/client_test.cpp b/libs/server-sdk/tests/client_test.cpp index b5475ca5b..73d0c0dd7 100644 --- a/libs/server-sdk/tests/client_test.cpp +++ b/libs/server-sdk/tests/client_test.cpp @@ -70,10 +70,10 @@ TEST_F(ClientTest, JsonVariationDefaultPassesThrough) { } TEST_F(ClientTest, AllFlagsStateNotValid) { - // Since we don't have any ability to insert into the data store at the time - // this test is written, just check that the result is not valid. + // Since we don't have any ability to insert into the data store, assert + // only that the state is not valid. auto flags = client_.AllFlagsState( - context_, AllFlagsStateOptions::IncludeReasons | - AllFlagsStateOptions::ClientSideOnly); + context_, AllFlagsState::Options::IncludeReasons | + AllFlagsState::Options::ClientSideOnly); ASSERT_FALSE(flags.Valid()); } From 059b21a350207fd8c3d65bac895e35b9f4dd0bbc Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 15:11:14 -0700 Subject: [PATCH 31/55] fix build --- libs/internal/tests/data_model_serialization_test.cpp | 6 ++---- libs/server-sdk/include/launchdarkly/server_side/client.hpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libs/internal/tests/data_model_serialization_test.cpp b/libs/internal/tests/data_model_serialization_test.cpp index 00ecfcfd9..1509d8228 100644 --- a/libs/internal/tests/data_model_serialization_test.cpp +++ b/libs/internal/tests/data_model_serialization_test.cpp @@ -419,8 +419,7 @@ TEST(RolloutTests, SerializeAllFields) { } TEST(VariationOrRolloutTests, SerializeVariation) { - uint64_t value(5); - data_model::Flag::VariationOrRollout variation = value; + data_model::Flag::VariationOrRollout variation = 5; auto json = boost::json::value_from(variation); @@ -578,12 +577,11 @@ TEST(FlagRuleTests, SerializeAllRollout) { } TEST(FlagTests, SerializeAll) { - uint64_t fallthrough(42); data_model::Flag flag{ "the-key", 21, // version true, // on - fallthrough, // fallthrough + 42, // fallthrough {"a", "b"}, // variations {{"prereqA", 2}, {"prereqB", 3}}, // prerequisites {{{ diff --git a/libs/server-sdk/include/launchdarkly/server_side/client.hpp b/libs/server-sdk/include/launchdarkly/server_side/client.hpp index 921a59853..50a9fda72 100644 --- a/libs/server-sdk/include/launchdarkly/server_side/client.hpp +++ b/libs/server-sdk/include/launchdarkly/server_side/client.hpp @@ -59,7 +59,7 @@ class IClient { * * @return A map from feature flag keys to values for the current context. */ - [[nodiscard]] virtual AllFlagsState AllFlagsState( + [[nodiscard]] virtual class AllFlagsState AllFlagsState( Context const& context, AllFlagsState::Options options = AllFlagsState::Options::Default) = 0; From 43f27cb5ecc0b75598d77928208eb53abd021535 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 15:23:22 -0700 Subject: [PATCH 32/55] fix regressed test --- .../src/all_flags_state/all_flags_state_builder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp index 3ef95ac44..efa4314b6 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp @@ -16,8 +16,8 @@ void AllFlagsStateBuilder::AddFlag(std::string const& key, flag.omit_details_ = true; } } - if (NotSet(options_, AllFlagsState::Options::IncludeReasons) || - flag.TrackReason()) { + if (NotSet(options_, AllFlagsState::Options::IncludeReasons) && + !flag.TrackReason()) { flag.reason_ = std::nullopt; } flags_state_.emplace(key, std::move(flag)); From 07663f765c4c89da4842e20cfcbe2ef22722b78c Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 16:06:50 -0700 Subject: [PATCH 33/55] revert change to DetailsOnlyForTrackedFlags unit test --- libs/server-sdk/tests/all_flags_state_test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/server-sdk/tests/all_flags_state_test.cpp b/libs/server-sdk/tests/all_flags_state_test.cpp index 839cb47b8..c08a4fe54 100644 --- a/libs/server-sdk/tests/all_flags_state_test.cpp +++ b/libs/server-sdk/tests/all_flags_state_test.cpp @@ -62,6 +62,9 @@ TEST(AllFlagsTest, DetailsOnlyForTrackedFlags) { "myFlagTracked": { "version": 42, "variation": 1, + "reason":{ + "kind" : "FALLTHROUGH" + }, "trackReason" : true, "trackEvents" : true }, From daef5cb29e8ee68fdfb471697009053cad304a76 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 21 Aug 2023 17:27:22 -0700 Subject: [PATCH 34/55] only return empty AllFlagsState if not initialized and memory store unavailable --- libs/server-sdk/src/client_impl.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index e1c7e6292..a54144f29 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -162,19 +162,18 @@ AllFlagsState ClientImpl::AllFlagsState(Context const& context, AllFlagsState::Options options) { std::unordered_map result; - if (config_.Offline()) { - LD_LOG(logger_, LogLevel::kWarn) - << "AllFlagsState() called, but client is in offline mode. " - "Returning empty state"; - return {}; - } - if (!Initialized()) { - LD_LOG(logger_, LogLevel::kWarn) - << "AllFlagsState() called before client has finished " - "initializing! Feature store unavailable - returning empty " - "state"; - return {}; + if (memory_store_.Initialized()) { + LD_LOG(logger_, LogLevel::kWarn) + << "AllFlagsState() called before client has finished " + "initializing; using last known values from data store"; + } else { + LD_LOG(logger_, LogLevel::kWarn) + << "AllFlagsState() called before client has finished " + "initializing. Data store not available. Returning empty " + "state"; + return {}; + } } AllFlagsStateBuilder builder{options}; From 47ad8bb5f807f7ef299218d70edb073bd216b323 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 12:54:17 -0700 Subject: [PATCH 35/55] additional suppressions for bucketing --- contract-tests/server-contract-tests/test-suppressions.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 5d712bb67..eb6130c87 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -168,3 +168,7 @@ context type/convert/old user to context/{"key": "a", "avatar": "b"} context type/convert/old user to context/{"key": "a", "avatar": null} context type/convert/old user to context/{"key": "a", "ip": "b"} context type/convert/old user to context/{"key": "a", "ip": null} + +# These appear to only fail in CI, but not locally on Mac. Requires investigation. +evaluation/bucketing/bucket by non-key attribute/in rollouts/invalid value type +evaluation/bucketing/bucket by non-key attribute/in rollouts/attribute not found From ca9a74eff18df5cf9806f95b4943c1ff315e791c Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 16:00:57 -0700 Subject: [PATCH 36/55] refactor variation methods to separate out event generation --- .../launchdarkly/data/evaluation_reason.hpp | 12 + libs/server-sdk/src/CMakeLists.txt | 1 + libs/server-sdk/src/client_impl.cpp | 294 +++++++++++------- libs/server-sdk/src/client_impl.hpp | 42 ++- libs/server-sdk/src/events/event_factory.cpp | 110 +++++++ libs/server-sdk/src/events/event_factory.hpp | 50 +++ libs/server-sdk/src/events/event_scope.hpp | 12 + 7 files changed, 396 insertions(+), 125 deletions(-) create mode 100644 libs/server-sdk/src/events/event_factory.cpp create mode 100644 libs/server-sdk/src/events/event_factory.hpp create mode 100644 libs/server-sdk/src/events/event_scope.hpp diff --git a/libs/common/include/launchdarkly/data/evaluation_reason.hpp b/libs/common/include/launchdarkly/data/evaluation_reason.hpp index 75a6fa282..634e01083 100644 --- a/libs/common/include/launchdarkly/data/evaluation_reason.hpp +++ b/libs/common/include/launchdarkly/data/evaluation_reason.hpp @@ -140,6 +140,18 @@ class EvaluationReason { */ static EvaluationReason Fallthrough(bool in_experiment); + /** + * The client wasn't ready. + */ + static EvaluationReason ClientNotReady(); + + /** + * The context wasn't valid. + */ + static EvaluationReason ContextNotSpecified(); + + static EvaluationReason FlagNotFound(); + /** * The flag evaluated to a particular variation because it matched a rule. * @param rule_index Index of the rule. diff --git a/libs/server-sdk/src/CMakeLists.txt b/libs/server-sdk/src/CMakeLists.txt index e0a327085..f3afa8aab 100644 --- a/libs/server-sdk/src/CMakeLists.txt +++ b/libs/server-sdk/src/CMakeLists.txt @@ -43,6 +43,7 @@ add_library(${LIBNAME} data_store/persistent/expiration_tracker.hpp data_store/persistent/persistent_data_store.cpp data_store/persistent/expiration_tracker.cpp + events/event_factory.cpp ) if (MSVC OR (NOT BUILD_SHARED_LIBS)) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index a54144f29..89bc1fad3 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -95,7 +95,9 @@ ClientImpl::ClientImpl(Config config, std::string const& version) status_manager_, logger_)), event_processor_(nullptr), - evaluator_(logger_, memory_store_) { + evaluator_(logger_, memory_store_), + events_default_{config_.Offline(), EventFactory::WithoutReasons()}, + events_with_reasons_{config_.Offline(), EventFactory::WithReasons()} { if (config.Events().Enabled() && !config.Offline()) { event_processor_ = std::make_unique>( @@ -125,8 +127,12 @@ static bool IsInitialized(DataSourceStatus::DataSourceState state) { } void ClientImpl::Identify(Context context) { - event_processor_->SendAsync(events::IdentifyEventParams{ - std::chrono::system_clock::now(), std::move(context)}); + if (events_default_.disabled) { + return; + } + + event_processor_->SendAsync( + events_default_.factory.Identify(std::move(context))); } std::future ClientImpl::StartAsyncInternal( @@ -234,193 +240,243 @@ void ClientImpl::FlushAsync() { event_processor_->FlushAsync(); } -template -EvaluationDetail ClientImpl::VariationInternal(Context const& ctx, - FlagKey const& key, - Value default_value, - bool check_type, - bool detailed) { - events::FeatureEventParams event = { - std::chrono::system_clock::now(), - key, - ctx, - default_value, - default_value, - std::nullopt, - std::nullopt, - std::nullopt, - false, - std::nullopt, - }; - - auto desc = memory_store_.GetFlag(key); - - if (!desc || !desc->item) { - if (!Initialized()) { - LD_LOG(logger_, LogLevel::kWarn) - << "LaunchDarkly client has not yet been initialized. " - "Returning default value"; - - auto error_reason = - EvaluationReason(EvaluationReason::ErrorKind::kClientNotReady); - - if (detailed) { - event.reason = error_reason; - } - - event_processor_->SendAsync(std::move(event)); - return EvaluationDetail(std::move(default_value), std::nullopt, - std::move(error_reason)); - } - - LD_LOG(logger_, LogLevel::kInfo) - << "Unknown feature flag " << key << "; returning default value"; +// void ClientImpl::LogVariationCall(std::string const& key, +// data_store::FlagDescriptor const* +// flag_desc, bool initialized) { +// bool flag_found = flag_desc != nullptr && flag_desc->item.has_value(); +// +// if (initialized) { +// if (!flag_found) { +// LD_LOG(logger_, LogLevel::kInfo) << "Unknown feature flag " << +// key +// << "; returning default value"; +// } +// } else { +// if (flag_found) { +// LD_LOG(logger_, LogLevel::kInfo) +// << "LaunchDarkly client has not yet been initialized; using " +// "last " +// "known flag rules from data store"; +// } else { +// LD_LOG(logger_, LogLevel::kInfo) +// << "LaunchDarkly client has not yet been initialize; +// returning " +// "default value"; +// } +// } +// } - auto error_reason = - EvaluationReason(EvaluationReason::ErrorKind::kFlagNotFound); +// TO evaluate internally: +// 1. Try to get the flag from the memory store. +// 2. Conditionally emit a log message based on the result of step 1 and the +// initialization status of the client. +// 3. Evaluate the flag. +// 4. If the detail value is NULL, then: +// - Substitute in the default value, +// - Propagate the error reason +// - wipe out the variation index +// 3. Conditionally emit an event based on 2: +// a) If the flag was found: emit an eval event. +// b) If the flag wasn't found: emit an "unknown flag" event. +// 4. In detail methods, do the typechecking. If the type is correct, then go +// ahead and return it. +// Otherwise, substitute in the default value with the kWrongType. + +bool FlagNotFound( + std::shared_ptr const& flag_desc) { + return !flag_desc || !flag_desc->item; +} - if (detailed) { - event.reason = error_reason; - } - event_processor_->SendAsync(std::move(event)); - return EvaluationDetail(std::move(default_value), std::nullopt, - std::move(error_reason)); - - } else if (!Initialized()) { - LD_LOG(logger_, LogLevel::kInfo) - << "LaunchDarkly client has not yet been initialized. " - "Returning cached value"; +std::optional ClientImpl::PreEvaluationChecks( + Context const& context) { + if (!memory_store_.Initialized()) { + return EvaluationReason::ErrorKind::kClientNotReady; } + if (!context.Valid()) { + return EvaluationReason::ErrorKind::kUserNotSpecified; + } + return std::nullopt; +} - assert(desc->item); - - auto const& flag = *(desc->item); - - EvaluationDetail const detail = evaluator_.Evaluate(flag, ctx); - - if (check_type && default_value.Type() != Value::Type::kNull && - detail.Value().Type() != default_value.Type()) { - auto error_reason = - EvaluationReason(EvaluationReason::ErrorKind::kWrongType); - - if (detailed) { - event.reason = error_reason; +EvaluationDetail ClientImpl::PostEvaluation( + std::string const& key, + Context const& context, + Value const& default_value, + std::variant> + result, + EventScope& event_scope, + std::optional const& flag) { + if (!event_scope.disabled) { + if (auto const error( + std::get_if(&result)); + error) { + if (*error == EvaluationReason::ErrorKind::kFlagNotFound) { + event_processor_->SendAsync(event_scope.factory.UnknownFlag( + key, context, + EvaluationDetail{*error, default_value}, + default_value)); + } + } else { + event_processor_->SendAsync(event_scope.factory.Eval( + key, context, flag, std::get>(result), + default_value, std::nullopt)); } - event_processor_->SendAsync(std::move(event)); - return EvaluationDetail(std::move(default_value), std::nullopt, - error_reason); } - event.value = detail.Value(); - event.variation = detail.VariationIndex(); - event.version = flag.Version(); - - if (detailed) { - event.reason = detail.Reason(); + if (auto const error( + std::get_if(&result)); + error) { + return EvaluationDetail{*error, default_value}; } - if (flag.debugEventsUntilDate) { - event.debug_events_until_date = - events::Date{std::chrono::system_clock::time_point{ - std::chrono::milliseconds{*flag.debugEventsUntilDate}}}; + EvaluationDetail res = std::get>(result); + if (res.Value().IsNull()) { + return EvaluationDetail{default_value, res.VariationIndex(), + res.Reason()}; } + return res; +} - bool track_fallthrough = - flag.trackEventsFallthrough && - detail.ReasonKindIs(EvaluationReason::Kind::kFallthrough); +EvaluationDetail ClientImpl::VariationInternal( + Context const& context, + IClient::FlagKey const& key, + Value const& default_value, + EventScope& scope) { + if (auto error = PreEvaluationChecks(context)) { + return PostEvaluation(key, context, default_value, *error, scope, + std::nullopt); + } - bool track_rule_match = - detail.ReasonKindIs(EvaluationReason::Kind::kRuleMatch); + auto flag_rule = memory_store_.GetFlag(key); - if (track_rule_match) { - auto const& rule_index = detail.Reason()->RuleIndex(); - assert(rule_index && - "evaluation algorithm must produce a rule index in the case of " - "rule " - "match"); + if (FlagNotFound(flag_rule)) { + return PostEvaluation(key, context, default_value, + EvaluationReason::ErrorKind::kFlagNotFound, scope, + std::nullopt); + } - assert(*rule_index < flag.rules.size() && - "evaluation algorithm must produce a valid rule index in the " - "case of " - "rule match"); + EvaluationDetail result = + evaluator_.Evaluate(*flag_rule->item, context); + return PostEvaluation(key, context, default_value, result, scope, + flag_rule.get()->item); +} - track_rule_match = flag.rules.at(*rule_index).trackEvents; - } +EvaluationDetail ClientImpl::VariationDetail( + Context const& ctx, + IClient::FlagKey const& key, + Value const& default_value) { + return VariationInternal(ctx, key, default_value, events_with_reasons_); +} - event.require_full_event = - flag.trackEvents || track_fallthrough || track_rule_match; - event_processor_->SendAsync(std::move(event)); - return EvaluationDetail(detail.Value(), detail.VariationIndex(), - detail.Reason()); +Value ClientImpl::Variation(Context const& ctx, + IClient::FlagKey const& key, + Value const& default_value) { + return *VariationInternal(ctx, key, default_value, events_default_); } EvaluationDetail ClientImpl::BoolVariationDetail( Context const& ctx, IClient::FlagKey const& key, bool default_value) { - return VariationInternal(ctx, key, default_value, true, true); + auto result = VariationDetail(ctx, key, default_value); + if (result.Value().IsBool()) { + return EvaluationDetail{result.Value(), result.VariationIndex(), + result.Reason()}; + } + return EvaluationDetail{EvaluationReason::ErrorKind::kWrongType, + default_value}; } bool ClientImpl::BoolVariation(Context const& ctx, IClient::FlagKey const& key, bool default_value) { - return *VariationInternal(ctx, key, default_value, true, false); + auto result = Variation(ctx, key, default_value); + if (result.IsBool()) { + return result; + } + return default_value; } EvaluationDetail ClientImpl::StringVariationDetail( Context const& ctx, ClientImpl::FlagKey const& key, std::string default_value) { - return VariationInternal(ctx, key, std::move(default_value), - true, true); + auto result = VariationDetail(ctx, key, default_value); + if (result.Value().IsString()) { + return EvaluationDetail{ + result.Value(), result.VariationIndex(), result.Reason()}; + } + return EvaluationDetail{ + EvaluationReason::ErrorKind::kWrongType, default_value}; } std::string ClientImpl::StringVariation(Context const& ctx, IClient::FlagKey const& key, std::string default_value) { - return *VariationInternal(ctx, key, std::move(default_value), - true, false); + auto result = Variation(ctx, key, default_value); + if (result.IsString()) { + return result; + } + return default_value; } EvaluationDetail ClientImpl::DoubleVariationDetail( Context const& ctx, ClientImpl::FlagKey const& key, double default_value) { - return VariationInternal(ctx, key, default_value, true, true); + auto result = VariationDetail(ctx, key, default_value); + if (result.Value().IsNumber()) { + return EvaluationDetail{result.Value(), result.VariationIndex(), + result.Reason()}; + } + return EvaluationDetail{EvaluationReason::ErrorKind::kWrongType, + default_value}; } double ClientImpl::DoubleVariation(Context const& ctx, IClient::FlagKey const& key, double default_value) { - return *VariationInternal(ctx, key, default_value, true, false); + auto result = Variation(ctx, key, default_value); + if (result.IsNumber()) { + return result; + } + return default_value; } EvaluationDetail ClientImpl::IntVariationDetail( Context const& ctx, IClient::FlagKey const& key, int default_value) { - return VariationInternal(ctx, key, default_value, true, true); + auto result = VariationDetail(ctx, key, default_value); + if (result.Value().IsNumber()) { + return EvaluationDetail{result.Value(), result.VariationIndex(), + result.Reason()}; + } + return EvaluationDetail{EvaluationReason::ErrorKind::kWrongType, + default_value}; } int ClientImpl::IntVariation(Context const& ctx, IClient::FlagKey const& key, int default_value) { - return *VariationInternal(ctx, key, default_value, true, false); + auto result = Variation(ctx, key, default_value); + if (result.IsNumber()) { + return result; + } + return default_value; } EvaluationDetail ClientImpl::JsonVariationDetail( Context const& ctx, IClient::FlagKey const& key, Value default_value) { - return VariationInternal(ctx, key, std::move(default_value), false, - true); + return VariationDetail(ctx, key, default_value); } Value ClientImpl::JsonVariation(Context const& ctx, IClient::FlagKey const& key, Value default_value) { - return *VariationInternal(ctx, key, std::move(default_value), false, - false); + return Variation(ctx, key, default_value); } // data_sources::IDataSourceStatusProvider& ClientImpl::DataSourceStatus() { diff --git a/libs/server-sdk/src/client_impl.hpp b/libs/server-sdk/src/client_impl.hpp index beab76374..5c55312e3 100644 --- a/libs/server-sdk/src/client_impl.hpp +++ b/libs/server-sdk/src/client_impl.hpp @@ -17,6 +17,8 @@ #include "evaluation/evaluator.hpp" +#include "events/event_scope.hpp" + #include #include @@ -109,12 +111,33 @@ class ClientImpl : public IClient { std::future StartAsync() override; private: - template - [[nodiscard]] EvaluationDetail VariationInternal(Context const& ctx, - FlagKey const& key, - Value default_value, - bool check_type, - bool detailed); + [[nodiscard]] EvaluationDetail VariationInternal( + Context const& ctx, + FlagKey const& key, + Value const& default_value, + EventScope& scope); + + [[nodiscard]] EvaluationDetail VariationDetail( + Context const& ctx, + FlagKey const& key, + Value const& default_value); + + [[nodiscard]] Value Variation(Context const& ctx, + std::string const& key, + Value const& default_value); + + [[nodiscard]] EvaluationDetail PostEvaluation( + std::string const& key, + Context const& context, + Value const& default_value, + std::variant> + result, + EventScope& event_scope, + std::optional const& flag); + + [[nodiscard]] std::optional + PreEvaluationChecks(Context const& context); + void TrackInternal(Context const& ctx, std::string event_name, std::optional data, @@ -124,6 +147,10 @@ class ClientImpl : public IClient { std::function predicate); + // void LogVariationCall(std::string const& key, + // data_store::FlagDescriptor const* flag_desc, + // bool initialized); + Config config_; Logger logger_; @@ -146,6 +173,9 @@ class ClientImpl : public IClient { evaluation::Evaluator evaluator_; + EventScope events_default_; + EventScope events_with_reasons_; + std::thread run_thread_; }; } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/events/event_factory.cpp b/libs/server-sdk/src/events/event_factory.cpp new file mode 100644 index 000000000..9a89b0626 --- /dev/null +++ b/libs/server-sdk/src/events/event_factory.cpp @@ -0,0 +1,110 @@ +#include "event_factory.hpp" + +#include +namespace launchdarkly::server_side { + +bool IsExperimentationEnabled(data_model::Flag const& flag, + std::optional const& reason) { + if (!reason) { + return false; + } + if (reason->InExperiment()) { + return true; + } + switch (reason->Kind()) { + case EvaluationReason::Kind::kFallthrough: + return flag.trackEventsFallthrough; + case EvaluationReason::Kind::kRuleMatch: + if (!reason->RuleIndex() || + reason->RuleIndex() >= flag.rules.size()) { + return false; + } + return flag.rules.at(*reason->RuleIndex()).trackEvents; + default: + return false; + } +} + +events::Date Now() { + return events::Date{std::chrono::system_clock::now()}; +} + +EventFactory::EventFactory( + launchdarkly::server_side::EventFactory::ReasonPolicy reason_policy) + : reason_policy_(reason_policy) {} + +EventFactory EventFactory::WithReasons() { + return {ReasonPolicy::Send}; +} + +EventFactory EventFactory::WithoutReasons() { + return {ReasonPolicy::Default}; +} + +events::InputEvent EventFactory::UnknownFlag( + std::string const& key, + launchdarkly::Context const& ctx, + EvaluationDetail detail, + launchdarkly::Value default_val) { + return FeatureRequest(key, ctx, std::nullopt, detail, default_val, + std::nullopt); +} + +events::InputEvent EventFactory::Eval( + std::string const& key, + Context const& ctx, + std::optional const& flag, + EvaluationDetail detail, + Value default_value, + std::optional prereq_of) { + return FeatureRequest(key, ctx, flag, detail, default_value, prereq_of); +} + +events::InputEvent EventFactory::Identify(launchdarkly::Context ctx) { + return events::IdentifyEventParams{Now(), std::move(ctx)}; +} + +events::InputEvent EventFactory::FeatureRequest( + std::string const& key, + launchdarkly::Context const& context, + std::optional const& flag, + EvaluationDetail detail, + launchdarkly::Value default_val, + std::optional prereq_of) { + bool flag_track_events = false; + bool require_experiment_data = false; + std::optional debug_events_until_date; + + if (flag.has_value()) { + flag_track_events = flag->trackEvents; + require_experiment_data = + IsExperimentationEnabled(*flag, detail.Reason()); + if (flag->debugEventsUntilDate) { + debug_events_until_date = + events::Date{std::chrono::system_clock::time_point{ + std::chrono::milliseconds(*flag->debugEventsUntilDate)}}; + } + } else { + flag_track_events = false; + require_experiment_data = false; + } + + std::optional reason; + if (reason_policy_ == ReasonPolicy::Send || require_experiment_data) { + reason = detail.Reason(); + } + + return events::FeatureEventParams{ + events::Date{std::chrono::system_clock::now()}, + key, + context, + detail.Value(), + default_val, + flag.has_value() ? std::make_optional(flag->version) : std::nullopt, + detail.VariationIndex(), + reason, + flag_track_events || require_experiment_data, + debug_events_until_date}; +} + +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/events/event_factory.hpp b/libs/server-sdk/src/events/event_factory.hpp new file mode 100644 index 000000000..ca16351ba --- /dev/null +++ b/libs/server-sdk/src/events/event_factory.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace launchdarkly::server_side { + +class EventFactory { + enum class ReasonPolicy { + Default = 0, + Send = 1, + }; + + public: + [[nodiscard]] static EventFactory WithReasons(); + [[nodiscard]] static EventFactory WithoutReasons(); + + [[nodiscard]] events::InputEvent UnknownFlag(std::string const& key, + Context const& ctx, + EvaluationDetail detail, + Value default_val); + + [[nodiscard]] events::InputEvent Eval( + std::string const& key, + Context const& ctx, + std::optional const& flag, + EvaluationDetail detail, + Value default_value, + std::optional prereq_of); + + [[nodiscard]] events::InputEvent Identify(Context ctx); + + private: + EventFactory(ReasonPolicy reason_policy); + [[nodiscard]] events::InputEvent FeatureRequest( + std::string const& key, + Context const& ctx, + std::optional const& flag, + EvaluationDetail detail, + Value default_val, + std::optional prereq_of); + + ReasonPolicy reason_policy_; +}; +} // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/events/event_scope.hpp b/libs/server-sdk/src/events/event_scope.hpp new file mode 100644 index 000000000..524b3af29 --- /dev/null +++ b/libs/server-sdk/src/events/event_scope.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "event_factory.hpp" + +namespace launchdarkly::server_side { + +struct EventScope { + bool const disabled; + EventFactory factory; +}; + +} // namespace launchdarkly::server_side From d5cd0fa45a4dee4ea77552ec53cb6443aeb970a3 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 16:14:56 -0700 Subject: [PATCH 37/55] refactor IsExperimentationEnabled into data_model::Flag --- .../include/launchdarkly/data_model/flag.hpp | 4 ++++ libs/internal/src/data_model/flag.cpp | 22 +++++++++++++++++ .../all_flags_state_builder.cpp | 22 ----------------- .../all_flags_state_builder.hpp | 3 --- libs/server-sdk/src/client_impl.cpp | 2 +- libs/server-sdk/src/events/event_factory.cpp | 24 +------------------ 6 files changed, 28 insertions(+), 49 deletions(-) diff --git a/libs/internal/include/launchdarkly/data_model/flag.hpp b/libs/internal/include/launchdarkly/data_model/flag.hpp index 5af2f95f8..cf774d4b1 100644 --- a/libs/internal/include/launchdarkly/data_model/flag.hpp +++ b/libs/internal/include/launchdarkly/data_model/flag.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -104,5 +105,8 @@ struct Flag { * @return Version of this flag. */ [[nodiscard]] inline std::uint64_t Version() const { return version; } + + [[nodiscard]] bool IsExperimentationEnabled( + std::optional const& reason) const; }; } // namespace launchdarkly::data_model diff --git a/libs/internal/src/data_model/flag.cpp b/libs/internal/src/data_model/flag.cpp index 646f36d83..2a03074a7 100644 --- a/libs/internal/src/data_model/flag.cpp +++ b/libs/internal/src/data_model/flag.cpp @@ -26,4 +26,26 @@ Flag::Rollout::Rollout(std::vector variations_) bucketBy("key"), contextKind("user") {} +bool Flag::IsExperimentationEnabled( + std::optional const& reason) const { + if (!reason) { + return false; + } + if (reason->InExperiment()) { + return true; + } + switch (reason->Kind()) { + case EvaluationReason::Kind::kFallthrough: + return this->trackEventsFallthrough; + case EvaluationReason::Kind::kRuleMatch: + if (!reason->RuleIndex() || + reason->RuleIndex() >= this->rules.size()) { + return false; + } + return this->rules.at(*reason->RuleIndex()).trackEvents; + default: + return false; + } +} + } // namespace launchdarkly::data_model diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp index efa4314b6..2dcdc6706 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.cpp @@ -28,28 +28,6 @@ AllFlagsState AllFlagsStateBuilder::Build() { return AllFlagsState{std::move(evaluations_), std::move(flags_state_)}; } -bool IsExperimentationEnabled(data_model::Flag const& flag, - std::optional const& reason) { - if (!reason) { - return false; - } - if (reason->InExperiment()) { - return true; - } - switch (reason->Kind()) { - case EvaluationReason::Kind::kFallthrough: - return flag.trackEventsFallthrough; - case EvaluationReason::Kind::kRuleMatch: - if (!reason->RuleIndex() || - reason->RuleIndex() >= flag.rules.size()) { - return false; - } - return flag.rules.at(*reason->RuleIndex()).trackEvents; - default: - return false; - } -} - bool IsSet(AllFlagsState::Options options, AllFlagsState::Options flag) { return (options & flag) == flag; } diff --git a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp index bda58d815..34e1a839b 100644 --- a/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp +++ b/libs/server-sdk/src/all_flags_state/all_flags_state_builder.hpp @@ -7,9 +7,6 @@ namespace launchdarkly::server_side { -bool IsExperimentationEnabled(data_model::Flag const& flag, - std::optional const& reason); - bool IsSet(AllFlagsState::Options options, AllFlagsState::Options flag); bool NotSet(AllFlagsState::Options options, AllFlagsState::Options flag); diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 89bc1fad3..c94584437 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -198,7 +198,7 @@ AllFlagsState ClientImpl::AllFlagsState(Context const& context, EvaluationDetail detail = evaluator_.Evaluate(flag, context); - bool in_experiment = IsExperimentationEnabled(flag, detail.Reason()); + bool in_experiment = flag.IsExperimentationEnabled(detail.Reason()); builder.AddFlag(k, detail.Value(), AllFlagsState::State{ flag.Version(), detail.VariationIndex(), diff --git a/libs/server-sdk/src/events/event_factory.cpp b/libs/server-sdk/src/events/event_factory.cpp index 9a89b0626..394a105a8 100644 --- a/libs/server-sdk/src/events/event_factory.cpp +++ b/libs/server-sdk/src/events/event_factory.cpp @@ -3,28 +3,6 @@ #include namespace launchdarkly::server_side { -bool IsExperimentationEnabled(data_model::Flag const& flag, - std::optional const& reason) { - if (!reason) { - return false; - } - if (reason->InExperiment()) { - return true; - } - switch (reason->Kind()) { - case EvaluationReason::Kind::kFallthrough: - return flag.trackEventsFallthrough; - case EvaluationReason::Kind::kRuleMatch: - if (!reason->RuleIndex() || - reason->RuleIndex() >= flag.rules.size()) { - return false; - } - return flag.rules.at(*reason->RuleIndex()).trackEvents; - default: - return false; - } -} - events::Date Now() { return events::Date{std::chrono::system_clock::now()}; } @@ -78,7 +56,7 @@ events::InputEvent EventFactory::FeatureRequest( if (flag.has_value()) { flag_track_events = flag->trackEvents; require_experiment_data = - IsExperimentationEnabled(*flag, detail.Reason()); + flag->IsExperimentationEnabled(detail.Reason()); if (flag->debugEventsUntilDate) { debug_events_until_date = events::Date{std::chrono::system_clock::time_point{ From 219d7a9ab6853f25708582055e99e82bc5395ebe Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 16:15:08 -0700 Subject: [PATCH 38/55] remove a bunch of evaluation suppressions --- .../test-suppressions.txt | 61 +------------------ 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index eb6130c87..7671210f5 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -1,61 +1,3 @@ -evaluation/parameterized/bad attribute reference errors - clause with no attribute/test-flag/evaluate flag with detail -evaluation/parameterized/bad attribute reference errors - empty path component/test-flag/evaluate flag with detail -evaluation/parameterized/bad attribute reference errors - tilde followed by invalid escape character/test-flag/evaluate flag with detail -evaluation/parameterized/bad attribute reference errors - tilde followed by nothing/test-flag/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/off variation too low/off variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/off variation too high/off variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/target variation too low/target variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/target variation too high/target variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/rule variation too low/rule variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (bool)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/off variation too low/off variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/off variation too high/off variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/target variation too low/target variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/target variation too high/target variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/rule variation too low/rule variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (int)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/off variation too low/off variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/off variation too high/off variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/target variation too low/target variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/target variation too high/target variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/rule variation too low/rule variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (double)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/off variation too low/off variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/off variation too high/off variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/target variation too low/target variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/target variation too high/target variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/rule variation too low/rule variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (string)/rule variation too high/rule variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/off variation too low/off variation too low/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/off variation too low/off variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/off variation too high/off variation too high/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/off variation too high/off variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/flag is off but has no off variation/flag is off but has no off variation/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/fallthrough variation too low/fallthrough variation too low/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/fallthrough variation too low/fallthrough variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/fallthrough variation too high/fallthrough variation too high/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/fallthrough variation too high/fallthrough variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/target variation too low/target variation too low/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/target variation too low/target variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/target variation too high/target variation too high/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/target variation too high/target variation too high/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/rule variation too low/rule variation too low/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/rule variation too low/rule variation too low/evaluate flag with detail -evaluation/parameterized/evaluation failures (any)/rule variation too high/rule variation too high/evaluate flag without detail -evaluation/parameterized/evaluation failures (any)/rule variation too high/rule variation too high/evaluate flag with detail evaluation/parameterized/prerequisites/prerequisite cycle is detected at top level, recursion stops/prerequisite cycle is detected at top level, recursion stops/evaluate flag with detail evaluation/parameterized/prerequisites/prerequisite cycle is detected at top level, recursion stops/prerequisite cycle is detected at top level, recursion stops/evaluate all flags evaluation/parameterized/prerequisites/prerequisite cycle is detected at deeper level, recursion stops/prerequisite cycle is detected at deeper level, recursion stops/evaluate flag with detail @@ -69,8 +11,7 @@ evaluation/parameterized/rollout or experiment - error for empty variations list evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag without detail evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag with detail evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate all flags -evaluation/parameterized/segment recursion/cycle is detected at top level, recursion stops/cycle is detected at top level, recursion stops/evaluate flag with detail -evaluation/parameterized/segment recursion/cycle is detected below top level, recursion stops/cycle is detected below top level, recursion stops/evaluate flag with detail + events/summary events/prerequisites events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: bool events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: int From 33944c76e4b7d3ba7890ad52ef3b0246135d14b1 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 16:18:47 -0700 Subject: [PATCH 39/55] remove some event suppressions --- .../server-contract-tests/test-suppressions.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 7671210f5..1881c83d5 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -18,41 +18,33 @@ events/feature events/full feature event for tracked flag/without reason/single events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: double events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: string events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: any -events/feature events/full feature event for tracked flag/without reason/single kind non-default/valid flag/type: bool events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: bool events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: int events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: double events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: string events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: any -events/feature events/full feature event for tracked flag/without reason/multi-kind/valid flag/type: bool events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: bool events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: int events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: double events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: string events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: any -events/feature events/full feature event for tracked flag/with reason/single kind default/valid flag/type: bool events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: bool events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: int events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: double events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: string events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: any -events/feature events/full feature event for tracked flag/with reason/single kind non-default/valid flag/type: bool events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: bool events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: int events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: double events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: string events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: any -events/feature events/full feature event for tracked flag/with reason/multi-kind/valid flag/type: bool events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: bool events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: int events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: double events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: string events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: any -events/feature events/evaluating all flags generates no events events/feature prerequisite events/without reasons events/feature prerequisite events/with reasons -events/experimentation/experiment in rule -events/experimentation/experiment in fallthrough streaming/updates/flag patch with same version is not applied streaming/updates/segment patch with same version is not applied streaming/updates/flag patch with lower version is not applied From 41a9e4c659c2990acff765b6b05ede903210400e Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 17:04:38 -0700 Subject: [PATCH 40/55] remove more event suppressions --- .../test-suppressions.txt | 32 +---------- libs/server-sdk/src/client_impl.cpp | 54 +++++++++---------- 2 files changed, 27 insertions(+), 59 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index 1881c83d5..2813892b4 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -12,39 +12,11 @@ evaluation/parameterized/segment match/user matches via include in multi-kind us evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag with detail evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate all flags +# SC-209589: Prerequisites events not yet supported. events/summary events/prerequisites -events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: bool -events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: int -events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: double -events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: string -events/feature events/full feature event for tracked flag/without reason/single kind default/malformed flag/type: any -events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: bool -events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: int -events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: double -events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: string -events/feature events/full feature event for tracked flag/without reason/single kind non-default/malformed flag/type: any -events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: bool -events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: int -events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: double -events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: string -events/feature events/full feature event for tracked flag/without reason/multi-kind/malformed flag/type: any -events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: bool -events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: int -events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: double -events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: string -events/feature events/full feature event for tracked flag/with reason/single kind default/malformed flag/type: any -events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: bool -events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: int -events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: double -events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: string -events/feature events/full feature event for tracked flag/with reason/single kind non-default/malformed flag/type: any -events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: bool -events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: int -events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: double -events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: string -events/feature events/full feature event for tracked flag/with reason/multi-kind/malformed flag/type: any events/feature prerequisite events/without reasons events/feature prerequisite events/with reasons + streaming/updates/flag patch with same version is not applied streaming/updates/segment patch with same version is not applied streaming/updates/flag patch with lower version is not applied diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index c94584437..ace577dbb 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -303,38 +303,34 @@ EvaluationDetail ClientImpl::PostEvaluation( Context const& context, Value const& default_value, std::variant> - result, + error_or_detail, EventScope& event_scope, std::optional const& flag) { - if (!event_scope.disabled) { - if (auto const error( - std::get_if(&result)); - error) { - if (*error == EvaluationReason::ErrorKind::kFlagNotFound) { - event_processor_->SendAsync(event_scope.factory.UnknownFlag( - key, context, - EvaluationDetail{*error, default_value}, - default_value)); + return std::visit( + [&](auto&& arg) { + using T = std::decay_t; + // VARIANT - ErrorKind + if constexpr (std::is_same_v) { + auto ret = EvaluationDetail{arg, default_value}; + if (!event_scope.disabled && + arg == EvaluationReason::ErrorKind::kFlagNotFound) { + event_processor_->SendAsync(event_scope.factory.UnknownFlag( + key, context, ret, default_value)); + } + return ret; } - } else { - event_processor_->SendAsync(event_scope.factory.Eval( - key, context, flag, std::get>(result), - default_value, std::nullopt)); - } - } - - if (auto const error( - std::get_if(&result)); - error) { - return EvaluationDetail{*error, default_value}; - } - - EvaluationDetail res = std::get>(result); - if (res.Value().IsNull()) { - return EvaluationDetail{default_value, res.VariationIndex(), - res.Reason()}; - } - return res; + // VARIANT: EvaluationDetail + else if constexpr (std::is_same_v>) { + auto ret = EvaluationDetail{ + (!arg.VariationIndex() ? default_value : arg.Value()), + arg.VariationIndex(), arg.Reason()}; + + event_processor_->SendAsync(event_scope.factory.Eval( + key, context, flag, ret, default_value, std::nullopt)); + return ret; + } + }, + std::move(error_or_detail)); } EvaluationDetail ClientImpl::VariationInternal( From 14e0c1446dcbbbf66f9142843275c9a007237ed4 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 17:06:38 -0700 Subject: [PATCH 41/55] remove obsolete methods --- .../include/launchdarkly/data/evaluation_reason.hpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/libs/common/include/launchdarkly/data/evaluation_reason.hpp b/libs/common/include/launchdarkly/data/evaluation_reason.hpp index 634e01083..75a6fa282 100644 --- a/libs/common/include/launchdarkly/data/evaluation_reason.hpp +++ b/libs/common/include/launchdarkly/data/evaluation_reason.hpp @@ -140,18 +140,6 @@ class EvaluationReason { */ static EvaluationReason Fallthrough(bool in_experiment); - /** - * The client wasn't ready. - */ - static EvaluationReason ClientNotReady(); - - /** - * The context wasn't valid. - */ - static EvaluationReason ContextNotSpecified(); - - static EvaluationReason FlagNotFound(); - /** * The flag evaluated to a particular variation because it matched a rule. * @param rule_index Index of the rule. From 1a3a4054d5cf6bee895216c9581523c7ebedd7d6 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 17:43:00 -0700 Subject: [PATCH 42/55] better encapsulation of EventScope --- libs/server-sdk/src/client_impl.cpp | 51 +++++++------------- libs/server-sdk/src/events/event_factory.cpp | 30 +++++++----- libs/server-sdk/src/events/event_factory.hpp | 15 ++++-- libs/server-sdk/src/events/event_scope.hpp | 18 +++++-- 4 files changed, 63 insertions(+), 51 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index ace577dbb..89aa56ce7 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -127,12 +127,9 @@ static bool IsInitialized(DataSourceStatus::DataSourceState state) { } void ClientImpl::Identify(Context context) { - if (events_default_.disabled) { - return; - } - - event_processor_->SendAsync( - events_default_.factory.Identify(std::move(context))); + events_default_.Get([&](EventFactory const& factory) { + event_processor_->SendAsync(factory.Identify(std::move(context))); + }); } std::future ClientImpl::StartAsyncInternal( @@ -213,10 +210,10 @@ void ClientImpl::TrackInternal(Context const& ctx, std::string event_name, std::optional data, std::optional metric_value) { - event_processor_->SendAsync(events::ServerTrackEventParams{ - {std::chrono::system_clock::now(), std::move(event_name), - ctx.KindsToKeys(), std::move(data), metric_value}, - ctx}); + events_default_.Get([&](EventFactory const& factory) { + event_processor_->SendAsync(factory.Custom( + ctx, std::move(event_name), std::move(data), metric_value)); + }); } void ClientImpl::Track(Context const& ctx, @@ -266,22 +263,6 @@ void ClientImpl::FlushAsync() { // } // } -// TO evaluate internally: -// 1. Try to get the flag from the memory store. -// 2. Conditionally emit a log message based on the result of step 1 and the -// initialization status of the client. -// 3. Evaluate the flag. -// 4. If the detail value is NULL, then: -// - Substitute in the default value, -// - Propagate the error reason -// - wipe out the variation index -// 3. Conditionally emit an event based on 2: -// a) If the flag was found: emit an eval event. -// b) If the flag wasn't found: emit an "unknown flag" event. -// 4. In detail methods, do the typechecking. If the type is correct, then go -// ahead and return it. -// Otherwise, substitute in the default value with the kWrongType. - bool FlagNotFound( std::shared_ptr const& flag_desc) { return !flag_desc || !flag_desc->item; @@ -312,11 +293,12 @@ EvaluationDetail ClientImpl::PostEvaluation( // VARIANT - ErrorKind if constexpr (std::is_same_v) { auto ret = EvaluationDetail{arg, default_value}; - if (!event_scope.disabled && - arg == EvaluationReason::ErrorKind::kFlagNotFound) { - event_processor_->SendAsync(event_scope.factory.UnknownFlag( - key, context, ret, default_value)); - } + + event_scope.Get([&](EventFactory const& factory) { + event_processor_->SendAsync( + factory.UnknownFlag(key, context, ret, default_value)); + }); + return ret; } // VARIANT: EvaluationDetail @@ -325,8 +307,11 @@ EvaluationDetail ClientImpl::PostEvaluation( (!arg.VariationIndex() ? default_value : arg.Value()), arg.VariationIndex(), arg.Reason()}; - event_processor_->SendAsync(event_scope.factory.Eval( - key, context, flag, ret, default_value, std::nullopt)); + event_scope.Get([&](EventFactory const& factory) { + event_processor_->SendAsync(factory.Eval( + key, context, flag, ret, default_value, std::nullopt)); + }); + return ret; } }, diff --git a/libs/server-sdk/src/events/event_factory.cpp b/libs/server-sdk/src/events/event_factory.cpp index 394a105a8..42140c88a 100644 --- a/libs/server-sdk/src/events/event_factory.cpp +++ b/libs/server-sdk/src/events/event_factory.cpp @@ -3,13 +3,10 @@ #include namespace launchdarkly::server_side { -events::Date Now() { - return events::Date{std::chrono::system_clock::now()}; -} - EventFactory::EventFactory( launchdarkly::server_side::EventFactory::ReasonPolicy reason_policy) - : reason_policy_(reason_policy) {} + : reason_policy_(reason_policy), + now_([]() { return events::Date{std::chrono::system_clock::now()}; }) {} EventFactory EventFactory::WithReasons() { return {ReasonPolicy::Send}; @@ -23,7 +20,7 @@ events::InputEvent EventFactory::UnknownFlag( std::string const& key, launchdarkly::Context const& ctx, EvaluationDetail detail, - launchdarkly::Value default_val) { + launchdarkly::Value default_val) const { return FeatureRequest(key, ctx, std::nullopt, detail, default_val, std::nullopt); } @@ -34,12 +31,23 @@ events::InputEvent EventFactory::Eval( std::optional const& flag, EvaluationDetail detail, Value default_value, - std::optional prereq_of) { + std::optional prereq_of) const { return FeatureRequest(key, ctx, flag, detail, default_value, prereq_of); } -events::InputEvent EventFactory::Identify(launchdarkly::Context ctx) { - return events::IdentifyEventParams{Now(), std::move(ctx)}; +events::InputEvent EventFactory::Identify(launchdarkly::Context ctx) const { + return events::IdentifyEventParams{now_(), std::move(ctx)}; +} + +events::InputEvent EventFactory::Custom( + Context const& ctx, + std::string event_name, + std::optional data, + std::optional metric_value) const { + return events::ServerTrackEventParams{ + {now_(), std::move(event_name), ctx.KindsToKeys(), std::move(data), + metric_value}, + ctx}; } events::InputEvent EventFactory::FeatureRequest( @@ -48,7 +56,7 @@ events::InputEvent EventFactory::FeatureRequest( std::optional const& flag, EvaluationDetail detail, launchdarkly::Value default_val, - std::optional prereq_of) { + std::optional prereq_of) const { bool flag_track_events = false; bool require_experiment_data = false; std::optional debug_events_until_date; @@ -73,7 +81,7 @@ events::InputEvent EventFactory::FeatureRequest( } return events::FeatureEventParams{ - events::Date{std::chrono::system_clock::now()}, + now_(), key, context, detail.Value(), diff --git a/libs/server-sdk/src/events/event_factory.hpp b/libs/server-sdk/src/events/event_factory.hpp index ca16351ba..98c7cd463 100644 --- a/libs/server-sdk/src/events/event_factory.hpp +++ b/libs/server-sdk/src/events/event_factory.hpp @@ -23,7 +23,7 @@ class EventFactory { [[nodiscard]] events::InputEvent UnknownFlag(std::string const& key, Context const& ctx, EvaluationDetail detail, - Value default_val); + Value default_val) const; [[nodiscard]] events::InputEvent Eval( std::string const& key, @@ -31,9 +31,15 @@ class EventFactory { std::optional const& flag, EvaluationDetail detail, Value default_value, - std::optional prereq_of); + std::optional prereq_of) const; - [[nodiscard]] events::InputEvent Identify(Context ctx); + [[nodiscard]] events::InputEvent Identify(Context ctx) const; + + [[nodiscard]] events::InputEvent Custom( + Context const& ctx, + std::string event_name, + std::optional data, + std::optional metric_value) const; private: EventFactory(ReasonPolicy reason_policy); @@ -43,8 +49,9 @@ class EventFactory { std::optional const& flag, EvaluationDetail detail, Value default_val, - std::optional prereq_of); + std::optional prereq_of) const; ReasonPolicy reason_policy_; + std::function now_; }; } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/events/event_scope.hpp b/libs/server-sdk/src/events/event_scope.hpp index 524b3af29..2e5579689 100644 --- a/libs/server-sdk/src/events/event_scope.hpp +++ b/libs/server-sdk/src/events/event_scope.hpp @@ -4,9 +4,21 @@ namespace launchdarkly::server_side { -struct EventScope { - bool const disabled; - EventFactory factory; +class EventScope { + public: + EventScope(bool disabled, EventFactory factory) + : disabled_(disabled), factory_(std::move(factory)) {} + + template + void Get(Callable&& callable) { + if (!disabled_) { + callable(factory_); + } + } + + private: + bool const disabled_; + EventFactory const factory_; }; } // namespace launchdarkly::server_side From d8754a53454c372093c881d8eb9461335fd2ff3d Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 18:11:23 -0700 Subject: [PATCH 43/55] re-add variation call logging --- libs/server-sdk/src/client_impl.cpp | 149 ++++++++++++++-------------- libs/server-sdk/src/client_impl.hpp | 4 +- 2 files changed, 78 insertions(+), 75 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 89aa56ce7..7d79377b3 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -76,6 +76,12 @@ static Logger MakeLogger(config::shared::built::Logging const& config) { std::make_shared(config.level, config.tag)}; } +/** + * Returns true if the flag pointer is valid and the underlying item is present. + */ +bool IsFlagPresent( + std::shared_ptr const& flag_desc); + ClientImpl::ClientImpl(Config config, std::string const& version) : config_(config), http_properties_( @@ -237,35 +243,65 @@ void ClientImpl::FlushAsync() { event_processor_->FlushAsync(); } -// void ClientImpl::LogVariationCall(std::string const& key, -// data_store::FlagDescriptor const* -// flag_desc, bool initialized) { -// bool flag_found = flag_desc != nullptr && flag_desc->item.has_value(); -// -// if (initialized) { -// if (!flag_found) { -// LD_LOG(logger_, LogLevel::kInfo) << "Unknown feature flag " << -// key -// << "; returning default value"; -// } -// } else { -// if (flag_found) { -// LD_LOG(logger_, LogLevel::kInfo) -// << "LaunchDarkly client has not yet been initialized; using " -// "last " -// "known flag rules from data store"; -// } else { -// LD_LOG(logger_, LogLevel::kInfo) -// << "LaunchDarkly client has not yet been initialize; -// returning " -// "default value"; -// } -// } -// } +void ClientImpl::LogVariationCall(std::string const& key, bool flag_present) { + if (Initialized()) { + if (!flag_present) { + LD_LOG(logger_, LogLevel::kInfo) << "Unknown feature flag " << key + << "; returning default value"; + } + } else { + if (flag_present) { + LD_LOG(logger_, LogLevel::kInfo) + << "LaunchDarkly client has not yet been initialized; using " + "last " + "known flag rules from data store"; + } else { + LD_LOG(logger_, LogLevel::kInfo) + << "LaunchDarkly client has not yet been initialized; " + "returning default value"; + } + } +} -bool FlagNotFound( - std::shared_ptr const& flag_desc) { - return !flag_desc || !flag_desc->item; +Value ClientImpl::Variation(Context const& ctx, + IClient::FlagKey const& key, + Value const& default_value) { + return *VariationInternal(ctx, key, default_value, events_default_); +} + +EvaluationDetail ClientImpl::VariationDetail( + Context const& ctx, + IClient::FlagKey const& key, + Value const& default_value) { + return VariationInternal(ctx, key, default_value, events_with_reasons_); +} + +EvaluationDetail ClientImpl::VariationInternal( + Context const& context, + IClient::FlagKey const& key, + Value const& default_value, + EventScope& scope) { + if (auto error = PreEvaluationChecks(context)) { + return PostEvaluation(key, context, default_value, *error, scope, + std::nullopt); + } + + auto flag_rule = memory_store_.GetFlag(key); + + bool flag_present = IsFlagPresent(flag_rule); + + LogVariationCall(key, flag_present); + + if (!flag_present) { + return PostEvaluation(key, context, default_value, + EvaluationReason::ErrorKind::kFlagNotFound, scope, + std::nullopt); + } + + EvaluationDetail result = + evaluator_.Evaluate(*flag_rule->item, context); + return PostEvaluation(key, context, default_value, result, scope, + flag_rule.get()->item); } std::optional ClientImpl::PreEvaluationChecks( @@ -290,69 +326,38 @@ EvaluationDetail ClientImpl::PostEvaluation( return std::visit( [&](auto&& arg) { using T = std::decay_t; - // VARIANT - ErrorKind + // VARIANT: ErrorKind if constexpr (std::is_same_v) { - auto ret = EvaluationDetail{arg, default_value}; + auto detail = EvaluationDetail{arg, default_value}; event_scope.Get([&](EventFactory const& factory) { - event_processor_->SendAsync( - factory.UnknownFlag(key, context, ret, default_value)); + event_processor_->SendAsync(factory.UnknownFlag( + key, context, detail, default_value)); }); - return ret; + return detail; } // VARIANT: EvaluationDetail else if constexpr (std::is_same_v>) { - auto ret = EvaluationDetail{ + auto detail = EvaluationDetail{ (!arg.VariationIndex() ? default_value : arg.Value()), arg.VariationIndex(), arg.Reason()}; event_scope.Get([&](EventFactory const& factory) { - event_processor_->SendAsync(factory.Eval( - key, context, flag, ret, default_value, std::nullopt)); + event_processor_->SendAsync( + factory.Eval(key, context, flag, detail, default_value, + std::nullopt)); }); - return ret; + return detail; } }, std::move(error_or_detail)); } -EvaluationDetail ClientImpl::VariationInternal( - Context const& context, - IClient::FlagKey const& key, - Value const& default_value, - EventScope& scope) { - if (auto error = PreEvaluationChecks(context)) { - return PostEvaluation(key, context, default_value, *error, scope, - std::nullopt); - } - - auto flag_rule = memory_store_.GetFlag(key); - - if (FlagNotFound(flag_rule)) { - return PostEvaluation(key, context, default_value, - EvaluationReason::ErrorKind::kFlagNotFound, scope, - std::nullopt); - } - - EvaluationDetail result = - evaluator_.Evaluate(*flag_rule->item, context); - return PostEvaluation(key, context, default_value, result, scope, - flag_rule.get()->item); -} - -EvaluationDetail ClientImpl::VariationDetail( - Context const& ctx, - IClient::FlagKey const& key, - Value const& default_value) { - return VariationInternal(ctx, key, default_value, events_with_reasons_); -} - -Value ClientImpl::Variation(Context const& ctx, - IClient::FlagKey const& key, - Value const& default_value) { - return *VariationInternal(ctx, key, default_value, events_default_); +bool IsFlagPresent( + std::shared_ptr const& flag_desc) { + return flag_desc && flag_desc->item; } EvaluationDetail ClientImpl::BoolVariationDetail( diff --git a/libs/server-sdk/src/client_impl.hpp b/libs/server-sdk/src/client_impl.hpp index 5c55312e3..90274998f 100644 --- a/libs/server-sdk/src/client_impl.hpp +++ b/libs/server-sdk/src/client_impl.hpp @@ -147,9 +147,7 @@ class ClientImpl : public IClient { std::function predicate); - // void LogVariationCall(std::string const& key, - // data_store::FlagDescriptor const* flag_desc, - // bool initialized); + void LogVariationCall(std::string const& key, bool flag_present); Config config_; Logger logger_; From 405693fe3f279216b2ed7111da265cc2e9274c4b Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 18:16:56 -0700 Subject: [PATCH 44/55] const correctness for EventScope --- libs/server-sdk/src/client_impl.cpp | 4 ++-- libs/server-sdk/src/client_impl.hpp | 8 ++++---- libs/server-sdk/src/events/event_scope.hpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 7d79377b3..f35933f69 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -280,7 +280,7 @@ EvaluationDetail ClientImpl::VariationInternal( Context const& context, IClient::FlagKey const& key, Value const& default_value, - EventScope& scope) { + EventScope const& scope) { if (auto error = PreEvaluationChecks(context)) { return PostEvaluation(key, context, default_value, *error, scope, std::nullopt); @@ -321,7 +321,7 @@ EvaluationDetail ClientImpl::PostEvaluation( Value const& default_value, std::variant> error_or_detail, - EventScope& event_scope, + EventScope const& event_scope, std::optional const& flag) { return std::visit( [&](auto&& arg) { diff --git a/libs/server-sdk/src/client_impl.hpp b/libs/server-sdk/src/client_impl.hpp index 90274998f..536863574 100644 --- a/libs/server-sdk/src/client_impl.hpp +++ b/libs/server-sdk/src/client_impl.hpp @@ -115,7 +115,7 @@ class ClientImpl : public IClient { Context const& ctx, FlagKey const& key, Value const& default_value, - EventScope& scope); + EventScope const& scope); [[nodiscard]] EvaluationDetail VariationDetail( Context const& ctx, @@ -132,7 +132,7 @@ class ClientImpl : public IClient { Value const& default_value, std::variant> result, - EventScope& event_scope, + EventScope const& event_scope, std::optional const& flag); [[nodiscard]] std::optional @@ -171,8 +171,8 @@ class ClientImpl : public IClient { evaluation::Evaluator evaluator_; - EventScope events_default_; - EventScope events_with_reasons_; + EventScope const events_default_; + EventScope const events_with_reasons_; std::thread run_thread_; }; diff --git a/libs/server-sdk/src/events/event_scope.hpp b/libs/server-sdk/src/events/event_scope.hpp index 2e5579689..981d1aa01 100644 --- a/libs/server-sdk/src/events/event_scope.hpp +++ b/libs/server-sdk/src/events/event_scope.hpp @@ -10,7 +10,7 @@ class EventScope { : disabled_(disabled), factory_(std::move(factory)) {} template - void Get(Callable&& callable) { + void Get(Callable&& callable) const { if (!disabled_) { callable(factory_); } From 1cf96664744e43c8c2e757799c31dd9ce3dd354c Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 22 Aug 2023 18:18:22 -0700 Subject: [PATCH 45/55] add const to LogVariationCall --- libs/server-sdk/src/client_impl.cpp | 3 ++- libs/server-sdk/src/client_impl.hpp | 2 +- libs/server-sdk/src/events/event_factory.cpp | 3 --- libs/server-sdk/src/events/event_factory.hpp | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index f35933f69..bcf8b60a8 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -243,7 +243,8 @@ void ClientImpl::FlushAsync() { event_processor_->FlushAsync(); } -void ClientImpl::LogVariationCall(std::string const& key, bool flag_present) { +void ClientImpl::LogVariationCall(std::string const& key, + bool flag_present) const { if (Initialized()) { if (!flag_present) { LD_LOG(logger_, LogLevel::kInfo) << "Unknown feature flag " << key diff --git a/libs/server-sdk/src/client_impl.hpp b/libs/server-sdk/src/client_impl.hpp index 536863574..aebf2ef7d 100644 --- a/libs/server-sdk/src/client_impl.hpp +++ b/libs/server-sdk/src/client_impl.hpp @@ -147,7 +147,7 @@ class ClientImpl : public IClient { std::function predicate); - void LogVariationCall(std::string const& key, bool flag_present); + void LogVariationCall(std::string const& key, bool flag_present) const; Config config_; Logger logger_; diff --git a/libs/server-sdk/src/events/event_factory.cpp b/libs/server-sdk/src/events/event_factory.cpp index 42140c88a..b22755c98 100644 --- a/libs/server-sdk/src/events/event_factory.cpp +++ b/libs/server-sdk/src/events/event_factory.cpp @@ -70,9 +70,6 @@ events::InputEvent EventFactory::FeatureRequest( events::Date{std::chrono::system_clock::time_point{ std::chrono::milliseconds(*flag->debugEventsUntilDate)}}; } - } else { - flag_track_events = false; - require_experiment_data = false; } std::optional reason; diff --git a/libs/server-sdk/src/events/event_factory.hpp b/libs/server-sdk/src/events/event_factory.hpp index 98c7cd463..91b21f36a 100644 --- a/libs/server-sdk/src/events/event_factory.hpp +++ b/libs/server-sdk/src/events/event_factory.hpp @@ -51,7 +51,7 @@ class EventFactory { Value default_val, std::optional prereq_of) const; - ReasonPolicy reason_policy_; + ReasonPolicy const reason_policy_; std::function now_; }; } // namespace launchdarkly::server_side From 4ce9dc9d824979c5be0e0e32a804bac59e9594b0 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 11:09:39 -0700 Subject: [PATCH 46/55] refactor event scope to pass IEventProcessor in callback --- libs/server-sdk/src/client_impl.cpp | 69 ++++++++++++++-------- libs/server-sdk/src/events/event_scope.hpp | 17 ++++-- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index bcf8b60a8..02c7b905e 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -76,6 +76,23 @@ static Logger MakeLogger(config::shared::built::Logging const& config) { std::make_shared(config.level, config.tag)}; } +bool EventsEnabled(Config const& config) { + return config.Events().Enabled() && !config.Offline(); +} + +std::unique_ptr MakeEventProcessor( + Config const& config, + boost::asio::any_io_executor exec, + HttpProperties const& http_properties, + Logger& logger) { + if (EventsEnabled(config)) { + return std::make_unique>( + exec, config.ServiceEndpoints(), config.Events(), http_properties, + logger); + } + return std::make_unique(); +} + /** * Returns true if the flag pointer is valid and the underlying item is present. */ @@ -100,19 +117,17 @@ ClientImpl::ClientImpl(Config config, std::string const& version) memory_store_, status_manager_, logger_)), - event_processor_(nullptr), + event_processor_(MakeEventProcessor(config, + ioc_.get_executor(), + http_properties_, + logger_)), evaluator_(logger_, memory_store_), - events_default_{config_.Offline(), EventFactory::WithoutReasons()}, - events_with_reasons_{config_.Offline(), EventFactory::WithReasons()} { - if (config.Events().Enabled() && !config.Offline()) { - event_processor_ = - std::make_unique>( - ioc_.get_executor(), config.ServiceEndpoints(), config.Events(), - http_properties_, logger_); - } else { - event_processor_ = std::make_unique(); - } - + events_default_(EventsEnabled(config), + *event_processor_.get(), + EventFactory::WithoutReasons()), + events_with_reasons_(EventsEnabled(config), + *event_processor_.get(), + EventFactory::WithReasons()) { run_thread_ = std::move(std::thread([&]() { ioc_.run(); })); } @@ -133,9 +148,10 @@ static bool IsInitialized(DataSourceStatus::DataSourceState state) { } void ClientImpl::Identify(Context context) { - events_default_.Get([&](EventFactory const& factory) { - event_processor_->SendAsync(factory.Identify(std::move(context))); - }); + events_default_.Get( + [&](events::IEventProcessor& processor, EventFactory const& factory) { + processor.SendAsync(factory.Identify(std::move(context))); + }); } std::future ClientImpl::StartAsyncInternal( @@ -216,10 +232,11 @@ void ClientImpl::TrackInternal(Context const& ctx, std::string event_name, std::optional data, std::optional metric_value) { - events_default_.Get([&](EventFactory const& factory) { - event_processor_->SendAsync(factory.Custom( - ctx, std::move(event_name), std::move(data), metric_value)); - }); + events_default_.Get( + [&](events::IEventProcessor& processor, EventFactory const& factory) { + processor.SendAsync(factory.Custom(ctx, std::move(event_name), + std::move(data), metric_value)); + }); } void ClientImpl::Track(Context const& ctx, @@ -331,8 +348,9 @@ EvaluationDetail ClientImpl::PostEvaluation( if constexpr (std::is_same_v) { auto detail = EvaluationDetail{arg, default_value}; - event_scope.Get([&](EventFactory const& factory) { - event_processor_->SendAsync(factory.UnknownFlag( + event_scope.Get([&](events::IEventProcessor& processor, + EventFactory const& factory) { + processor.SendAsync(factory.UnknownFlag( key, context, detail, default_value)); }); @@ -344,10 +362,11 @@ EvaluationDetail ClientImpl::PostEvaluation( (!arg.VariationIndex() ? default_value : arg.Value()), arg.VariationIndex(), arg.Reason()}; - event_scope.Get([&](EventFactory const& factory) { - event_processor_->SendAsync( - factory.Eval(key, context, flag, detail, default_value, - std::nullopt)); + event_scope.Get([&](events::IEventProcessor& processor, + EventFactory const& factory) { + processor.SendAsync(factory.Eval(key, context, flag, detail, + default_value, + std::nullopt)); }); return detail; diff --git a/libs/server-sdk/src/events/event_scope.hpp b/libs/server-sdk/src/events/event_scope.hpp index 981d1aa01..e89a3a586 100644 --- a/libs/server-sdk/src/events/event_scope.hpp +++ b/libs/server-sdk/src/events/event_scope.hpp @@ -1,23 +1,30 @@ #pragma once +#include + #include "event_factory.hpp" namespace launchdarkly::server_side { class EventScope { public: - EventScope(bool disabled, EventFactory factory) - : disabled_(disabled), factory_(std::move(factory)) {} + EventScope(bool enabled, + events::IEventProcessor& processor, + EventFactory factory) + : enabled_(enabled), + processor_(processor), + factory_(std::move(factory)) {} template void Get(Callable&& callable) const { - if (!disabled_) { - callable(factory_); + if (enabled_) { + callable(processor_, factory_); } } private: - bool const disabled_; + bool const enabled_; + events::IEventProcessor& processor_; EventFactory const factory_; }; From 63e3ad172d15dfe420119cae4d1c0113cbfd261e Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 11:41:18 -0700 Subject: [PATCH 47/55] remove disabled bool from EventScope and rely on polymorphism --- libs/server-sdk/src/client_impl.cpp | 38 +++++++++------------- libs/server-sdk/src/events/event_scope.hpp | 13 ++------ 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 02c7b905e..f4da73754 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -122,11 +122,8 @@ ClientImpl::ClientImpl(Config config, std::string const& version) http_properties_, logger_)), evaluator_(logger_, memory_store_), - events_default_(EventsEnabled(config), - *event_processor_.get(), - EventFactory::WithoutReasons()), - events_with_reasons_(EventsEnabled(config), - *event_processor_.get(), + events_default_(*event_processor_.get(), EventFactory::WithoutReasons()), + events_with_reasons_(*event_processor_.get(), EventFactory::WithReasons()) { run_thread_ = std::move(std::thread([&]() { ioc_.run(); })); } @@ -148,10 +145,9 @@ static bool IsInitialized(DataSourceStatus::DataSourceState state) { } void ClientImpl::Identify(Context context) { - events_default_.Get( - [&](events::IEventProcessor& processor, EventFactory const& factory) { - processor.SendAsync(factory.Identify(std::move(context))); - }); + events_default_.Get([&](EventFactory const& factory) { + return factory.Identify(std::move(context)); + }); } std::future ClientImpl::StartAsyncInternal( @@ -232,11 +228,10 @@ void ClientImpl::TrackInternal(Context const& ctx, std::string event_name, std::optional data, std::optional metric_value) { - events_default_.Get( - [&](events::IEventProcessor& processor, EventFactory const& factory) { - processor.SendAsync(factory.Custom(ctx, std::move(event_name), - std::move(data), metric_value)); - }); + events_default_.Get([&](EventFactory const& factory) { + return factory.Custom(ctx, std::move(event_name), std::move(data), + metric_value); + }); } void ClientImpl::Track(Context const& ctx, @@ -348,10 +343,9 @@ EvaluationDetail ClientImpl::PostEvaluation( if constexpr (std::is_same_v) { auto detail = EvaluationDetail{arg, default_value}; - event_scope.Get([&](events::IEventProcessor& processor, - EventFactory const& factory) { - processor.SendAsync(factory.UnknownFlag( - key, context, detail, default_value)); + event_scope.Get([&](EventFactory const& factory) { + return factory.UnknownFlag(key, context, detail, + default_value); }); return detail; @@ -362,11 +356,9 @@ EvaluationDetail ClientImpl::PostEvaluation( (!arg.VariationIndex() ? default_value : arg.Value()), arg.VariationIndex(), arg.Reason()}; - event_scope.Get([&](events::IEventProcessor& processor, - EventFactory const& factory) { - processor.SendAsync(factory.Eval(key, context, flag, detail, - default_value, - std::nullopt)); + event_scope.Get([&](EventFactory const& factory) { + return factory.Eval(key, context, flag, detail, + default_value, std::nullopt); }); return detail; diff --git a/libs/server-sdk/src/events/event_scope.hpp b/libs/server-sdk/src/events/event_scope.hpp index e89a3a586..301662288 100644 --- a/libs/server-sdk/src/events/event_scope.hpp +++ b/libs/server-sdk/src/events/event_scope.hpp @@ -8,22 +8,15 @@ namespace launchdarkly::server_side { class EventScope { public: - EventScope(bool enabled, - events::IEventProcessor& processor, - EventFactory factory) - : enabled_(enabled), - processor_(processor), - factory_(std::move(factory)) {} + EventScope(events::IEventProcessor& processor, EventFactory factory) + : processor_(processor), factory_(std::move(factory)) {} template void Get(Callable&& callable) const { - if (enabled_) { - callable(processor_, factory_); - } + processor_.SendAsync(callable(factory_)); } private: - bool const enabled_; events::IEventProcessor& processor_; EventFactory const factory_; }; From 2e49214a036a1425bac780da9f1a1bb0ce44b514 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 15:06:22 -0700 Subject: [PATCH 48/55] rename EventScope::Get to Send --- libs/server-sdk/src/client_impl.cpp | 8 ++++---- libs/server-sdk/src/events/event_factory.cpp | 4 ++-- libs/server-sdk/src/events/event_factory.hpp | 2 +- libs/server-sdk/src/events/event_scope.hpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index f4da73754..512480a63 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -145,7 +145,7 @@ static bool IsInitialized(DataSourceStatus::DataSourceState state) { } void ClientImpl::Identify(Context context) { - events_default_.Get([&](EventFactory const& factory) { + events_default_.Send([&](EventFactory const& factory) { return factory.Identify(std::move(context)); }); } @@ -228,7 +228,7 @@ void ClientImpl::TrackInternal(Context const& ctx, std::string event_name, std::optional data, std::optional metric_value) { - events_default_.Get([&](EventFactory const& factory) { + events_default_.Send([&](EventFactory const& factory) { return factory.Custom(ctx, std::move(event_name), std::move(data), metric_value); }); @@ -343,7 +343,7 @@ EvaluationDetail ClientImpl::PostEvaluation( if constexpr (std::is_same_v) { auto detail = EvaluationDetail{arg, default_value}; - event_scope.Get([&](EventFactory const& factory) { + event_scope.Send([&](EventFactory const& factory) { return factory.UnknownFlag(key, context, detail, default_value); }); @@ -356,7 +356,7 @@ EvaluationDetail ClientImpl::PostEvaluation( (!arg.VariationIndex() ? default_value : arg.Value()), arg.VariationIndex(), arg.Reason()}; - event_scope.Get([&](EventFactory const& factory) { + event_scope.Send([&](EventFactory const& factory) { return factory.Eval(key, context, flag, detail, default_value, std::nullopt); }); diff --git a/libs/server-sdk/src/events/event_factory.cpp b/libs/server-sdk/src/events/event_factory.cpp index b22755c98..0e10b74f6 100644 --- a/libs/server-sdk/src/events/event_factory.cpp +++ b/libs/server-sdk/src/events/event_factory.cpp @@ -9,7 +9,7 @@ EventFactory::EventFactory( now_([]() { return events::Date{std::chrono::system_clock::now()}; }) {} EventFactory EventFactory::WithReasons() { - return {ReasonPolicy::Send}; + return {ReasonPolicy::Require}; } EventFactory EventFactory::WithoutReasons() { @@ -73,7 +73,7 @@ events::InputEvent EventFactory::FeatureRequest( } std::optional reason; - if (reason_policy_ == ReasonPolicy::Send || require_experiment_data) { + if (reason_policy_ == ReasonPolicy::Require || require_experiment_data) { reason = detail.Reason(); } diff --git a/libs/server-sdk/src/events/event_factory.hpp b/libs/server-sdk/src/events/event_factory.hpp index 91b21f36a..dd54f8d9f 100644 --- a/libs/server-sdk/src/events/event_factory.hpp +++ b/libs/server-sdk/src/events/event_factory.hpp @@ -13,7 +13,7 @@ namespace launchdarkly::server_side { class EventFactory { enum class ReasonPolicy { Default = 0, - Send = 1, + Require = 1, }; public: diff --git a/libs/server-sdk/src/events/event_scope.hpp b/libs/server-sdk/src/events/event_scope.hpp index 301662288..39a706ba8 100644 --- a/libs/server-sdk/src/events/event_scope.hpp +++ b/libs/server-sdk/src/events/event_scope.hpp @@ -12,7 +12,7 @@ class EventScope { : processor_(processor), factory_(std::move(factory)) {} template - void Get(Callable&& callable) const { + void Send(Callable&& callable) const { processor_.SendAsync(callable(factory_)); } From 5b7e08e858c7ccb393dd6070a6069d2dfdee15d8 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 15:40:07 -0700 Subject: [PATCH 49/55] remove NullEventProcessor from server-side SDK and document EventScope --- libs/server-sdk/src/client_impl.cpp | 13 +++++----- libs/server-sdk/src/events/event_scope.hpp | 30 +++++++++++++++++++--- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 512480a63..c1e0fae3d 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -80,7 +79,7 @@ bool EventsEnabled(Config const& config) { return config.Events().Enabled() && !config.Offline(); } -std::unique_ptr MakeEventProcessor( +std::unique_ptr> MakeEventProcessor( Config const& config, boost::asio::any_io_executor exec, HttpProperties const& http_properties, @@ -90,7 +89,7 @@ std::unique_ptr MakeEventProcessor( exec, config.ServiceEndpoints(), config.Events(), http_properties, logger); } - return std::make_unique(); + return nullptr; } /** @@ -122,8 +121,8 @@ ClientImpl::ClientImpl(Config config, std::string const& version) http_properties_, logger_)), evaluator_(logger_, memory_store_), - events_default_(*event_processor_.get(), EventFactory::WithoutReasons()), - events_with_reasons_(*event_processor_.get(), + events_default_(event_processor_.get(), EventFactory::WithoutReasons()), + events_with_reasons_(event_processor_.get(), EventFactory::WithReasons()) { run_thread_ = std::move(std::thread([&]() { ioc_.run(); })); } @@ -252,7 +251,9 @@ void ClientImpl::Track(Context const& ctx, std::string event_name) { } void ClientImpl::FlushAsync() { - event_processor_->FlushAsync(); + if (event_processor_) { + event_processor_->FlushAsync(); + } } void ClientImpl::LogVariationCall(std::string const& key, diff --git a/libs/server-sdk/src/events/event_scope.hpp b/libs/server-sdk/src/events/event_scope.hpp index 39a706ba8..2eb32ade4 100644 --- a/libs/server-sdk/src/events/event_scope.hpp +++ b/libs/server-sdk/src/events/event_scope.hpp @@ -6,18 +6,42 @@ namespace launchdarkly::server_side { +/** + * EventScope is responsible for forwarding events to an + * IEventProcessor. If the given interface is nullptr, then events will not + * be forwarded at all. + */ class EventScope { public: - EventScope(events::IEventProcessor& processor, EventFactory factory) + /** + * Constructs an EventScope with a non-owned IEventProcessor and factory. + * When Send is called, the factory will be passed to the caller, which must + * return a constructed event. + * @param processor The event processor to forward events to. + * @param factory The factory used for generating events. + */ + EventScope(events::IEventProcessor* processor, EventFactory factory) : processor_(processor), factory_(std::move(factory)) {} + /** + * Default constructs an EventScope which will not forward events. + */ + EventScope() : EventScope(nullptr, EventFactory::WithoutReasons()) {} + + /** + * Sends an event created by the given callable. The callable will be + * passed an EventFactory. + * @param callable Returns an InputEvent. + */ template void Send(Callable&& callable) const { - processor_.SendAsync(callable(factory_)); + if (processor_) { + processor_->SendAsync(callable(factory_)); + } } private: - events::IEventProcessor& processor_; + events::IEventProcessor* processor_; EventFactory const factory_; }; From a6589e7095fcde3eaa5f5dc842a59ebf5b8e62e1 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 15:49:12 -0700 Subject: [PATCH 50/55] feat: implement prerequisite event recording (#204) Implements prerequisite event recording, and fixes some bugs related to prereq event generation. --- .../test-suppressions.txt | 11 --------- .../events/data/common_events.hpp | 2 ++ libs/internal/src/events/common_events.cpp | 3 ++- .../src/serialization/events/json_events.cpp | 3 +++ libs/server-sdk/src/client_impl.cpp | 17 ++++++++------ libs/server-sdk/src/evaluation/evaluator.cpp | 23 ++++++++++++------- libs/server-sdk/src/evaluation/evaluator.hpp | 9 +++++--- libs/server-sdk/src/events/event_factory.cpp | 3 ++- libs/server-sdk/src/events/event_factory.hpp | 2 +- 9 files changed, 41 insertions(+), 32 deletions(-) diff --git a/contract-tests/server-contract-tests/test-suppressions.txt b/contract-tests/server-contract-tests/test-suppressions.txt index c0b1612df..bb58b099c 100644 --- a/contract-tests/server-contract-tests/test-suppressions.txt +++ b/contract-tests/server-contract-tests/test-suppressions.txt @@ -1,9 +1,3 @@ -# Server doesn't yet support prerequisite event recording. -evaluation/parameterized/prerequisites/prerequisite cycle is detected at top level, recursion stops/prerequisite cycle is detected at top level, recursion stops/evaluate flag with detail -evaluation/parameterized/prerequisites/prerequisite cycle is detected at top level, recursion stops/prerequisite cycle is detected at top level, recursion stops/evaluate all flags -evaluation/parameterized/prerequisites/prerequisite cycle is detected at deeper level, recursion stops/prerequisite cycle is detected at deeper level, recursion stops/evaluate flag with detail -evaluation/parameterized/prerequisites/prerequisite cycle is detected at deeper level, recursion stops/prerequisite cycle is detected at deeper level, recursion stops/evaluate all flags - evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate flag without detail evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate flag with detail evaluation/parameterized/rollout or experiment - error for empty variations list in rollout/fallthrough rollout/fallthrough rollout/evaluate all flags @@ -17,11 +11,6 @@ evaluation/parameterized/segment match/user matches via include in multi-kind us evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag with detail evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate all flags -# Server doesn't yet support prerequisite event recording. -events/summary events/prerequisites -events/feature prerequisite events/without reasons -events/feature prerequisite events/with reasons - streaming/updates/flag patch with same version is not applied streaming/updates/segment patch with same version is not applied streaming/updates/flag patch with lower version is not applied diff --git a/libs/internal/include/launchdarkly/events/data/common_events.hpp b/libs/internal/include/launchdarkly/events/data/common_events.hpp index 62066b4f2..b8740319d 100644 --- a/libs/internal/include/launchdarkly/events/data/common_events.hpp +++ b/libs/internal/include/launchdarkly/events/data/common_events.hpp @@ -62,6 +62,7 @@ struct FeatureEventParams { std::optional reason; bool require_full_event; std::optional debug_events_until_date; + std::optional prereq_of; }; struct FeatureEventBase { @@ -72,6 +73,7 @@ struct FeatureEventBase { Value value; std::optional reason; Value default_; + std::optional prereq_of; explicit FeatureEventBase(FeatureEventParams const& params); }; diff --git a/libs/internal/src/events/common_events.cpp b/libs/internal/src/events/common_events.cpp index 623f8d187..e6fdbc096 100644 --- a/libs/internal/src/events/common_events.cpp +++ b/libs/internal/src/events/common_events.cpp @@ -8,5 +8,6 @@ FeatureEventBase::FeatureEventBase(FeatureEventParams const& params) variation(params.variation), value(params.value), reason(params.reason), - default_(params.default_) {} + default_(params.default_), + prereq_of(params.prereq_of) {} } // namespace launchdarkly::events diff --git a/libs/internal/src/serialization/events/json_events.cpp b/libs/internal/src/serialization/events/json_events.cpp index 3c13687c3..36a9b4553 100644 --- a/libs/internal/src/serialization/events/json_events.cpp +++ b/libs/internal/src/serialization/events/json_events.cpp @@ -39,6 +39,9 @@ void tag_invoke(boost::json::value_from_tag const& tag, obj.emplace("reason", boost::json::value_from(*event.reason)); } obj.emplace("default", boost::json::value_from(event.default_)); + if (event.prereq_of) { + obj.emplace("prereqOf", *event.prereq_of); + } } void tag_invoke(boost::json::value_from_tag const& tag, diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index c1e0fae3d..229c026bf 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -198,6 +198,8 @@ AllFlagsState ClientImpl::AllFlagsState(Context const& context, AllFlagsStateBuilder builder{options}; + EventScope no_events; + for (auto const& [k, v] : memory_store_.AllFlags()) { if (!v || !v->item) { continue; @@ -210,7 +212,8 @@ AllFlagsState ClientImpl::AllFlagsState(Context const& context, continue; } - EvaluationDetail detail = evaluator_.Evaluate(flag, context); + EvaluationDetail detail = + evaluator_.Evaluate(flag, context, no_events); bool in_experiment = flag.IsExperimentationEnabled(detail.Reason()); builder.AddFlag(k, detail.Value(), @@ -294,9 +297,9 @@ EvaluationDetail ClientImpl::VariationInternal( Context const& context, IClient::FlagKey const& key, Value const& default_value, - EventScope const& scope) { + EventScope const& event_scope) { if (auto error = PreEvaluationChecks(context)) { - return PostEvaluation(key, context, default_value, *error, scope, + return PostEvaluation(key, context, default_value, *error, event_scope, std::nullopt); } @@ -308,13 +311,13 @@ EvaluationDetail ClientImpl::VariationInternal( if (!flag_present) { return PostEvaluation(key, context, default_value, - EvaluationReason::ErrorKind::kFlagNotFound, scope, - std::nullopt); + EvaluationReason::ErrorKind::kFlagNotFound, + event_scope, std::nullopt); } EvaluationDetail result = - evaluator_.Evaluate(*flag_rule->item, context); - return PostEvaluation(key, context, default_value, result, scope, + evaluator_.Evaluate(*flag_rule->item, context, event_scope); + return PostEvaluation(key, context, default_value, result, event_scope, flag_rule.get()->item); } diff --git a/libs/server-sdk/src/evaluation/evaluator.cpp b/libs/server-sdk/src/evaluation/evaluator.cpp index 4a326e180..464ab531c 100644 --- a/libs/server-sdk/src/evaluation/evaluator.cpp +++ b/libs/server-sdk/src/evaluation/evaluator.cpp @@ -24,14 +24,16 @@ Evaluator::Evaluator(Logger& logger, data_store::IDataStore const& store) EvaluationDetail Evaluator::Evaluate( Flag const& flag, - launchdarkly::Context const& context) { - return Evaluate("", flag, context); + launchdarkly::Context const& context, + EventScope const& event_scope) { + return Evaluate(std::nullopt, flag, context, event_scope); } EvaluationDetail Evaluator::Evaluate( - std::string const& parent_key, + std::optional parent_key, Flag const& flag, - launchdarkly::Context const& context) { + launchdarkly::Context const& context, + EventScope const& event_scope) { if (auto guard = stack_.NoticePrerequisite(flag.key)) { if (!flag.on) { return OffValue(flag, EvaluationReason::Off()); @@ -56,7 +58,7 @@ EvaluationDetail Evaluator::Evaluate( // Recursive call; cycles are detected by the guard. EvaluationDetail detailed_evaluation = - Evaluate(flag.key, *descriptor.item, context); + Evaluate(flag.key, *descriptor.item, context, event_scope); if (detailed_evaluation.IsError()) { return detailed_evaluation; @@ -65,7 +67,11 @@ EvaluationDetail Evaluator::Evaluate( std::optional variation_index = detailed_evaluation.VariationIndex(); - // TODO(209589) prerequisite events. + event_scope.Send([&](EventFactory const& factory) { + return factory.Eval(p.key, context, *descriptor.item, + detailed_evaluation, Value::Null(), + flag.key); + }); if (!descriptor.item->on || variation_index != p.variation) { return OffValue(flag, @@ -73,8 +79,9 @@ EvaluationDetail Evaluator::Evaluate( } } } else { - LogError(parent_key, Error::CyclicPrerequisiteReference(flag.key)); - return OffValue(flag, EvaluationReason::MalformedFlag()); + LogError(parent_key.value_or("(no parent)"), + Error::CyclicPrerequisiteReference(flag.key)); + return EvaluationReason::MalformedFlag(); } // If the flag is on, all prerequisites are on and valid, then diff --git a/libs/server-sdk/src/evaluation/evaluator.hpp b/libs/server-sdk/src/evaluation/evaluator.hpp index 6e22405b9..97882c529 100644 --- a/libs/server-sdk/src/evaluation/evaluator.hpp +++ b/libs/server-sdk/src/evaluation/evaluator.hpp @@ -7,6 +7,7 @@ #include #include "../data_store/data_store.hpp" +#include "../events/event_scope.hpp" #include "bucketing.hpp" #include "detail/evaluation_stack.hpp" #include "evaluation_error.hpp" @@ -25,13 +26,15 @@ class Evaluator { */ [[nodiscard]] EvaluationDetail Evaluate( data_model::Flag const& flag, - launchdarkly::Context const& context); + launchdarkly::Context const& context, + EventScope const& event_scope); private: [[nodiscard]] EvaluationDetail Evaluate( - std::string const& parent_key, + std::optional parent_key, data_model::Flag const& flag, - launchdarkly::Context const& context); + launchdarkly::Context const& context, + EventScope const& event_scope); [[nodiscard]] EvaluationDetail FlagVariation( data_model::Flag const& flag, diff --git a/libs/server-sdk/src/events/event_factory.cpp b/libs/server-sdk/src/events/event_factory.cpp index 0e10b74f6..2be43f6fe 100644 --- a/libs/server-sdk/src/events/event_factory.cpp +++ b/libs/server-sdk/src/events/event_factory.cpp @@ -87,7 +87,8 @@ events::InputEvent EventFactory::FeatureRequest( detail.VariationIndex(), reason, flag_track_events || require_experiment_data, - debug_events_until_date}; + debug_events_until_date, + prereq_of}; } } // namespace launchdarkly::server_side diff --git a/libs/server-sdk/src/events/event_factory.hpp b/libs/server-sdk/src/events/event_factory.hpp index dd54f8d9f..e9a10eec3 100644 --- a/libs/server-sdk/src/events/event_factory.hpp +++ b/libs/server-sdk/src/events/event_factory.hpp @@ -43,7 +43,7 @@ class EventFactory { private: EventFactory(ReasonPolicy reason_policy); - [[nodiscard]] events::InputEvent FeatureRequest( + events::InputEvent FeatureRequest( std::string const& key, Context const& ctx, std::optional const& flag, From 6b1f6475274ba77b6b29cd2157ccea572f40cedc Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 16:15:32 -0700 Subject: [PATCH 51/55] add unit tests for EventScope --- libs/server-sdk/tests/event_scope_test.cpp | 73 +++++++++++++++++++ libs/server-sdk/tests/spy_event_processor.hpp | 65 +++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 libs/server-sdk/tests/event_scope_test.cpp create mode 100644 libs/server-sdk/tests/spy_event_processor.hpp diff --git a/libs/server-sdk/tests/event_scope_test.cpp b/libs/server-sdk/tests/event_scope_test.cpp new file mode 100644 index 000000000..4497d6bb3 --- /dev/null +++ b/libs/server-sdk/tests/event_scope_test.cpp @@ -0,0 +1,73 @@ +#include + +#include + +#include "spy_event_processor.hpp" + +#include "events/event_scope.hpp" + +using namespace launchdarkly; +using namespace launchdarkly::server_side; + +TEST(EventScope, DefaultConstructedScopeHasNoObservableEffects) { + EventScope default_scope; + default_scope.Send([](EventFactory const& factory) { + return factory.Identify(ContextBuilder().Kind("cat", "shadow").Build()); + }); +} + +TEST(EventScope, SendWithNullProcessorHasNoObservableEffects) { + EventScope scope(nullptr, EventFactory::WithoutReasons()); + scope.Send([](EventFactory const& factory) { + return factory.Identify(ContextBuilder().Kind("cat", "shadow").Build()); + }); +} + +TEST(EventScope, ForwardsEvents) { + SpyEventProcessor processor; + EventScope scope(&processor, EventFactory::WithoutReasons()); + + const std::size_t kEventCount = 10; + + for (std::size_t i = 0; i < kEventCount; ++i) { + scope.Send([](EventFactory const& factory) { + return factory.Identify( + ContextBuilder().Kind("cat", "shadow").Build()); + }); + } + + ASSERT_TRUE(processor.Count(kEventCount)); +} + +TEST(EventScope, ForwardsCorrectEventTypes) { + SpyEventProcessor processor; + EventScope scope(&processor, EventFactory::WithoutReasons()); + + scope.Send([](EventFactory const& factory) { + return factory.Identify(ContextBuilder().Kind("cat", "shadow").Build()); + }); + + scope.Send([](EventFactory const& factory) { + return factory.UnknownFlag( + "flag", ContextBuilder().Kind("cat", "shadow").Build(), + EvaluationReason::Fallthrough(false), true); + }); + + scope.Send([](EventFactory const& factory) { + return factory.Eval("flag", + ContextBuilder().Kind("cat", "shadow").Build(), + std::nullopt, EvaluationReason::Fallthrough(false), + false, std::nullopt); + }); + + scope.Send([](EventFactory const& factory) { + return factory.Custom(ContextBuilder().Kind("cat", "shadow").Build(), + "event", std::nullopt, std::nullopt); + }); + + ASSERT_TRUE(processor.Count(4)); + ASSERT_TRUE(processor.Kind(0)); + ASSERT_TRUE(processor.Kind(1)); + ASSERT_TRUE(processor.Kind(2)); + ASSERT_TRUE(processor.Kind(3)); +} diff --git a/libs/server-sdk/tests/spy_event_processor.hpp b/libs/server-sdk/tests/spy_event_processor.hpp new file mode 100644 index 000000000..13c10305f --- /dev/null +++ b/libs/server-sdk/tests/spy_event_processor.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +#include + +namespace launchdarkly { +class SpyEventProcessor : public events::IEventProcessor { + public: + struct Flush {}; + struct Shutdown {}; + + using Record = events::InputEvent; + + SpyEventProcessor() : events_() {} + + void SendAsync(events::InputEvent event) override { + events_.push_back(std::move(event)); + } + + void FlushAsync() override {} + + void ShutdownAsync() override {} + + /** + * Asserts that 'count' events/commands were recorded. + * @param count Number of expected events/commands. + */ + [[nodiscard]] testing::AssertionResult Count(std::size_t count) const { + if (events_.size() == count) { + return testing::AssertionSuccess(); + } + return testing::AssertionFailure() + << "Expected " << count << " events, got " << events_.size(); + } + + template + [[nodiscard]] testing::AssertionResult Kind(std::size_t index) const { + return GetIndex(index, [&](auto const& actual) { + if (std::holds_alternative(actual)) { + return testing::AssertionSuccess(); + } else { + return testing::AssertionFailure() + << "Expected message " << index << " to be of kind " + << typeid(T).name() << ", got variant index " + << actual.index(); + } + }); + } + + private: + [[nodiscard]] testing::AssertionResult GetIndex( + std::size_t index, + std::function const& f) const { + if (index >= events_.size()) { + return testing::AssertionFailure() + << "Event index " << index << " out of range"; + } + auto const& record = events_[index]; + return f(record); + } + using Records = std::vector; + Records events_; +}; +} // namespace launchdarkly From 15f362ef6377493adf303cb7253f6f403f8a5306 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 16:24:03 -0700 Subject: [PATCH 52/55] fix evaluator tests --- libs/server-sdk/src/evaluation/evaluator.cpp | 6 ++++++ libs/server-sdk/src/evaluation/evaluator.hpp | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/libs/server-sdk/src/evaluation/evaluator.cpp b/libs/server-sdk/src/evaluation/evaluator.cpp index 464ab531c..97057fbd2 100644 --- a/libs/server-sdk/src/evaluation/evaluator.cpp +++ b/libs/server-sdk/src/evaluation/evaluator.cpp @@ -22,6 +22,12 @@ std::optional TargetMatchVariation( Evaluator::Evaluator(Logger& logger, data_store::IDataStore const& store) : logger_(logger), store_(store), stack_() {} +EvaluationDetail Evaluator::Evaluate( + data_model::Flag const& flag, + launchdarkly::Context const& context) { + return Evaluate(flag, context, EventScope{}); +} + EvaluationDetail Evaluator::Evaluate( Flag const& flag, launchdarkly::Context const& context, diff --git a/libs/server-sdk/src/evaluation/evaluator.hpp b/libs/server-sdk/src/evaluation/evaluator.hpp index 97882c529..cf7a15468 100644 --- a/libs/server-sdk/src/evaluation/evaluator.hpp +++ b/libs/server-sdk/src/evaluation/evaluator.hpp @@ -23,12 +23,28 @@ class Evaluator { /** * Evaluates a flag for a given context. * Warning: not thread safe. + * + * @param flag The flag to evaluate. + * @param context The context to evaluate the flag against. + * @param event_scope The event scope used for recording prerequisite + * events. */ [[nodiscard]] EvaluationDetail Evaluate( data_model::Flag const& flag, launchdarkly::Context const& context, EventScope const& event_scope); + /** + * Evaluates a flag for a given context. Does not record prerequisite + * events. Warning: not thread safe. + * + * @param flag The flag to evaluate. + * @param context The context to evaluate the flag against. + */ + [[nodiscard]] EvaluationDetail Evaluate( + data_model::Flag const& flag, + launchdarkly::Context const& context); + private: [[nodiscard]] EvaluationDetail Evaluate( std::optional parent_key, From b1d13988de42e95a8555c9b4111f2a03166fb4ad Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 16:42:13 -0700 Subject: [PATCH 53/55] add some event factory tests --- libs/server-sdk/tests/event_factory_tests.cpp | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 libs/server-sdk/tests/event_factory_tests.cpp diff --git a/libs/server-sdk/tests/event_factory_tests.cpp b/libs/server-sdk/tests/event_factory_tests.cpp new file mode 100644 index 000000000..11eccd1e8 --- /dev/null +++ b/libs/server-sdk/tests/event_factory_tests.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include +#include + +#include "events/event_factory.hpp" + +using namespace launchdarkly; +using namespace launchdarkly::server_side; + +class EventFactoryTests : public testing::Test { + public: + EventFactoryTests() + : context_(ContextBuilder().Kind("cat", "shadow").Build()) {} + Context context_; +}; + +TEST_F(EventFactoryTests, IncludesReasonIfInExperiment) { + auto factory = EventFactory::WithoutReasons(); + auto event = + factory.Eval("flag", context_, data_model::Flag{}, + EvaluationReason::Fallthrough(true), false, std::nullopt); + ASSERT_TRUE(std::get(event).reason.has_value()); +} + +TEST_F(EventFactoryTests, DoesNotIncludeReasonIfNotInExperiment) { + auto factory = EventFactory::WithoutReasons(); + auto event = + factory.Eval("flag", context_, data_model::Flag{}, + EvaluationReason::Fallthrough(false), false, std::nullopt); + ASSERT_FALSE( + std::get(event).reason.has_value()); +} + +TEST_F(EventFactoryTests, IncludesReasonIfForcedByFactory) { + auto factory = EventFactory::WithReasons(); + auto event = + factory.Eval("flag", context_, data_model::Flag{}, + EvaluationReason::Fallthrough(false), false, std::nullopt); + ASSERT_TRUE(std::get(event).reason.has_value()); +} From 8b0fbac756c247400af4f194cd32f2c0ee2ec033 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 23 Aug 2023 16:44:07 -0700 Subject: [PATCH 54/55] typo --- libs/server-sdk/tests/spy_event_processor.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/server-sdk/tests/spy_event_processor.hpp b/libs/server-sdk/tests/spy_event_processor.hpp index 13c10305f..2981f39d0 100644 --- a/libs/server-sdk/tests/spy_event_processor.hpp +++ b/libs/server-sdk/tests/spy_event_processor.hpp @@ -23,8 +23,8 @@ class SpyEventProcessor : public events::IEventProcessor { void ShutdownAsync() override {} /** - * Asserts that 'count' events/commands were recorded. - * @param count Number of expected events/commands. + * Asserts that 'count' events were recorded. + * @param count Number of expected events. */ [[nodiscard]] testing::AssertionResult Count(std::size_t count) const { if (events_.size() == count) { @@ -47,7 +47,7 @@ class SpyEventProcessor : public events::IEventProcessor { } }); } - + private: [[nodiscard]] testing::AssertionResult GetIndex( std::size_t index, From 1cf17bb42b6ae71b4a26a2fe4a75854357b77342 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Thu, 24 Aug 2023 12:59:48 -0700 Subject: [PATCH 55/55] fix a lint --- libs/server-sdk/src/client_impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 229c026bf..0ffca198b 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -81,7 +81,7 @@ bool EventsEnabled(Config const& config) { std::unique_ptr> MakeEventProcessor( Config const& config, - boost::asio::any_io_executor exec, + boost::asio::any_io_executor const& exec, HttpProperties const& http_properties, Logger& logger) { if (EventsEnabled(config)) {