diff --git a/.bazelrc b/.bazelrc index 5ab9634..d7ca741 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,4 +1,8 @@ -# TODO(#57): Workaround for errors related to linker script when compiling abseil: -# error: relocation refers to local symbol "...", which is defined in a discarded section -build --copt=-fno-asynchronous-unwind-tables -build --linkopt=-Wl,--gc-sections \ No newline at end of file +# Auto-applies build:linux / build:macos / build:windows based on host OS. +build --enable_platform_specific_config + +# TODO(#57): Workaround for errors related to linker script when compiling abseil +# on Linux: "error: relocation refers to local symbol '...', which is defined in +# a discarded section". GNU ld-only — macOS ld64 rejects --gc-sections. +build:linux --copt=-fno-asynchronous-unwind-tables +build:linux --linkopt=-Wl,--gc-sections diff --git a/MODULE.bazel b/MODULE.bazel index 9b1adf9..fa5ec94 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,5 +1,6 @@ module(name = "cpp_sdk_contrib") +bazel_dep(name = "platforms", version = "0.0.11") bazel_dep(name = "rules_cc", version = "0.2.18") bazel_dep(name = "rules_python", version = "2.0.0") bazel_dep(name = "rules_proto", version = "7.1.0") @@ -28,6 +29,7 @@ bazel_dep(name = "protobuf", version = "33.6", repo_name = "com_google_protobuf" bazel_dep(name = "nlohmann_json", version = "3.12.0.bcr.1") git_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") git_repository( name = "flagd_schemas", @@ -42,3 +44,49 @@ git_repository( remote = "https://github.com/pboettch/json-schema-validator.git", tag = "2.4.0", ) + +# datalogic-rs C ABI: per-platform tarballs from the official v5.0.0 GitHub +# release. Each tarball bundles the cbindgen header alongside the staticlib. + +DATALOGIC_VERSION = "5.0.0" + +DATALOGIC_RELEASE_BASE = "https://github.com/GoPlasmatic/datalogic-rs/releases/download/v" + DATALOGIC_VERSION + +_DATALOGIC_BUILD_FILE_CONTENT = """\ +exports_files( + ["datalogic.h", "libdatalogic_c.a"], + visibility = ["//visibility:public"], +) +""" + +http_archive( + name = "datalogic_c_linux_x86_64", + build_file_content = _DATALOGIC_BUILD_FILE_CONTENT, + sha256 = "8c147de7129808546144e220955391e841343539c6e4ce66158a699ca141144a", + strip_prefix = "linux_amd64", + urls = [DATALOGIC_RELEASE_BASE + "/go-staticlib-linux-amd64.tar.gz"], +) + +http_archive( + name = "datalogic_c_linux_aarch64", + build_file_content = _DATALOGIC_BUILD_FILE_CONTENT, + sha256 = "46ef85137b543adcba590767a09e52b308528ef4ef0caf431e09d8cf296f61c3", + strip_prefix = "linux_arm64", + urls = [DATALOGIC_RELEASE_BASE + "/go-staticlib-linux-arm64.tar.gz"], +) + +http_archive( + name = "datalogic_c_darwin_x86_64", + build_file_content = _DATALOGIC_BUILD_FILE_CONTENT, + sha256 = "5205bdf7494b465c080fa9704a9dc4663d8bb4e55789e4b4ed9c0f9287c65967", + strip_prefix = "darwin_amd64", + urls = [DATALOGIC_RELEASE_BASE + "/go-staticlib-darwin-amd64.tar.gz"], +) + +http_archive( + name = "datalogic_c_darwin_aarch64", + build_file_content = _DATALOGIC_BUILD_FILE_CONTENT, + sha256 = "76a7708ec353d86f478a6e9a341df83e7d2827e30fe3f4ac007a102d3faf7618", + strip_prefix = "darwin_arm64", + urls = [DATALOGIC_RELEASE_BASE + "/go-staticlib-darwin-arm64.tar.gz"], +) diff --git a/providers/flagd/src/evaluator/BUILD b/providers/flagd/src/evaluator/BUILD index 263391a..54ea96c 100644 --- a/providers/flagd/src/evaluator/BUILD +++ b/providers/flagd/src/evaluator/BUILD @@ -1,16 +1,16 @@ load("@rules_cc//cc:defs.bzl", "cc_library") cc_library( - name = "flagd_ops", - srcs = ["flagd_ops.cpp"], - hdrs = ["flagd_ops.h"], + name = "datalogic_engine", + srcs = ["datalogic_engine.cpp"], + hdrs = ["datalogic_engine.h"], include_prefix = "flagd/evaluator", strip_include_prefix = "", visibility = ["//visibility:public"], deps = [ - "//providers/flagd/src/evaluator/json_logic", - "//providers/flagd/src/evaluator/murmur_hash", - "@abseil-cpp//absl/log", + "//providers/flagd/third_party/datalogic:datalogic_c", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", "@abseil-cpp//absl/strings", "@nlohmann_json//:json", ], @@ -24,8 +24,7 @@ cc_library( strip_include_prefix = "", visibility = ["//visibility:public"], deps = [ - ":flagd_ops", - "//providers/flagd/src/evaluator/json_logic", + ":datalogic_engine", "//providers/flagd/src/sync", "@abseil-cpp//absl/log", "@abseil-cpp//absl/strings", diff --git a/providers/flagd/src/evaluator/datalogic_engine.cpp b/providers/flagd/src/evaluator/datalogic_engine.cpp new file mode 100644 index 0000000..609484b --- /dev/null +++ b/providers/flagd/src/evaluator/datalogic_engine.cpp @@ -0,0 +1,48 @@ +#include "datalogic_engine.h" + +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" + +namespace flagd { + +namespace { + +std::string LastError() { + const char* type = datalogic_last_error_type(); + const char* msg = datalogic_last_error_message(); + return absl::StrCat(type ? type : "Unknown", ": ", msg ? msg : "(no detail)"); +} + +} // namespace + +DatalogicEngine::DatalogicEngine() + : engine_(datalogic_engine_new(/*templating=*/0)) {} + +DatalogicEngine::~DatalogicEngine() { datalogic_engine_free(engine_); } + +absl::StatusOr DatalogicEngine::Apply( + const nlohmann::json& rule, const nlohmann::json& data) const { + if (engine_ == nullptr) { + return absl::InternalError("datalogic engine not initialized"); + } + const std::string rule_str = rule.dump(); + const std::string data_str = data.dump(); + + std::unique_ptr result( + datalogic_engine_apply(engine_, rule_str.c_str(), data_str.c_str()), + datalogic_string_free); + if (result == nullptr) { + return absl::InternalError(LastError()); + } + try { + return nlohmann::json::parse(result.get()); + } catch (const nlohmann::json::exception& err) { + return absl::InternalError( + absl::StrCat("failed to parse datalogic result: ", err.what())); + } +} + +} // namespace flagd diff --git a/providers/flagd/src/evaluator/datalogic_engine.h b/providers/flagd/src/evaluator/datalogic_engine.h new file mode 100644 index 0000000..b505db6 --- /dev/null +++ b/providers/flagd/src/evaluator/datalogic_engine.h @@ -0,0 +1,44 @@ +#ifndef PROVIDERS_FLAGD_SRC_EVALUATOR_DATALOGIC_ENGINE_H_ +#define PROVIDERS_FLAGD_SRC_EVALUATOR_DATALOGIC_ENGINE_H_ + +#include + +#include + +#include "absl/status/statusor.h" + +namespace flagd { + +// RAII wrapper around the datalogic-rs C engine handle. The underlying +// engine is `Send + Sync` (Arc-wrapped Rust handle); one instance can be +// shared across threads. +class DatalogicEngine { + public: + DatalogicEngine(); + ~DatalogicEngine(); + + DatalogicEngine(const DatalogicEngine&) = delete; + DatalogicEngine& operator=(const DatalogicEngine&) = delete; + DatalogicEngine(DatalogicEngine&&) = delete; + DatalogicEngine& operator=(DatalogicEngine&&) = delete; + + // One-shot evaluate: serializes `rule` and `data` to JSON strings, + // calls `datalogic_engine_apply`, parses and returns the result. + // Returns absl::InternalError carrying the thread-local + // `datalogic_last_error_*` context on parse or evaluation failure. + // + // Performance: every call re-serializes the rule and re-parses it + // inside datalogic. For hot paths, the C ABI also exposes + // `datalogic_engine_compile` + `datalogic_rule_evaluate` + // (compile-once, evaluate-many) which this wrapper could cache + // behind. Deferred until profiling motivates it. + absl::StatusOr Apply(const nlohmann::json& rule, + const nlohmann::json& data) const; + + private: + datalogic_engine* engine_; +}; + +} // namespace flagd + +#endif // PROVIDERS_FLAGD_SRC_EVALUATOR_DATALOGIC_ENGINE_H_ diff --git a/providers/flagd/src/evaluator/evaluator.cpp b/providers/flagd/src/evaluator/evaluator.cpp index 5ba18ff..e03b63c 100644 --- a/providers/flagd/src/evaluator/evaluator.cpp +++ b/providers/flagd/src/evaluator/evaluator.cpp @@ -8,7 +8,6 @@ #include "absl/log/log.h" #include "absl/strings/str_cat.h" #include "flagd/sync/sync.h" -#include "flagd_ops.h" namespace flagd { @@ -108,12 +107,7 @@ nlohmann::json ContextToJson(const openfeature::EvaluationContext& ctx) { } // namespace JsonLogicEvaluator::JsonLogicEvaluator(std::shared_ptr sync) - : sync_(std::move(sync)) { - json_logic_.RegisterOperation("starts_with", StartsWith); - json_logic_.RegisterOperation("ends_with", EndsWith); - json_logic_.RegisterOperation("sem_ver", SemVer); - json_logic_.RegisterOperation("fractional", Fractional); -} + : sync_(std::move(sync)) {} template std::unique_ptr> @@ -171,7 +165,7 @@ JsonLogicEvaluator::ResolveAny(std::string_view flag_key, T default_value, }; absl::StatusOr result = - json_logic_.Apply(flag_config["targeting"], data); + engine_.Apply(flag_config["targeting"], data); if (result.ok()) { if (result.value().is_string()) { variant = result.value().get(); diff --git a/providers/flagd/src/evaluator/evaluator.h b/providers/flagd/src/evaluator/evaluator.h index f71ac55..c361d0d 100644 --- a/providers/flagd/src/evaluator/evaluator.h +++ b/providers/flagd/src/evaluator/evaluator.h @@ -8,7 +8,7 @@ #include #include -#include "flagd/evaluator/json_logic/json_logic.h" +#include "flagd/evaluator/datalogic_engine.h" #include "flagd/sync/sync.h" namespace flagd { @@ -69,7 +69,7 @@ class JsonLogicEvaluator : public Evaluator { const openfeature::EvaluationContext& ctx); std::shared_ptr sync_; - json_logic::JsonLogic json_logic_; + DatalogicEngine engine_; }; } // namespace flagd diff --git a/providers/flagd/src/evaluator/flagd_ops.cpp b/providers/flagd/src/evaluator/flagd_ops.cpp deleted file mode 100644 index f20711b..0000000 --- a/providers/flagd/src/evaluator/flagd_ops.cpp +++ /dev/null @@ -1,444 +0,0 @@ -#include "flagd_ops.h" - -#include -#include -#include -#include -#include -#include - -#include "absl/status/status.h" -#include "absl/strings/match.h" -#include "absl/strings/numbers.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" -#include "flagd/evaluator/murmur_hash/MurmurHash3.h" - -namespace flagd { - -namespace { - -// Checks if a string has a leading zero (and is not just "0"). -bool HasLeadingZero(std::string_view str) { - return str.size() > 1 && str[0] == '0'; -} - -// Parses number according to SemVer 2.0.0 specification. -absl::Status ParseSemVerNum(std::string_view num_str, std::string_view name, - uint64_t* out) { - if (HasLeadingZero(num_str)) { - return absl::InvalidArgumentError(absl::StrCat( - name, " version MUST NOT contain leading zeros: ", num_str)); - } - if (!absl::SimpleAtoi(num_str, out)) { - return absl::InvalidArgumentError( - absl::StrCat("Invalid SemVer ", name, " digits: ", num_str)); - } - return absl::OkStatus(); -}; - -// Evaluates and retrieves a fixed number of string arguments from JsonLogic. -absl::StatusOr> GetStrings( - const json_logic::JsonLogic& eval, const nlohmann::json& values, - const nlohmann::json& data, size_t expected_size) { - if (!values.is_array()) { - return absl::InvalidArgumentError("Arguments must be an array"); - } - - if (values.size() != expected_size) { - return absl::InvalidArgumentError(absl::StrCat( - "Operator requires exactly ", expected_size, " arguments")); - } - - std::vector result; - result.reserve(expected_size); - for (const nlohmann::json& item : values) { - absl::StatusOr applied = eval.Apply(item, data); - if (!applied.ok()) return applied.status(); - if (!applied.value().is_string()) { - return absl::InvalidArgumentError( - "All arguments must evaluate to strings"); - } - result.push_back(applied.value().get()); - } - - return result; -} - -// Represents a Semantic Version (SemVer 2.0.0) for comparison. -class SemanticVersion { - private: - uint64_t major_; - uint64_t minor_; - uint64_t patch_; - std::vector pre_release_; - - public: - explicit SemanticVersion(uint64_t major = 0, uint64_t minor = 0, - uint64_t patch = 0, - std::vector pre_release = {}) - : major_(major), - minor_(minor), - patch_(patch), - pre_release_(std::move(pre_release)) {} - - uint64_t GetMajor() const { return major_; } - uint64_t GetMinor() const { return minor_; } - uint64_t GetPatch() const { return patch_; } - const std::vector& GetPreRelease() const { return pre_release_; } - - // Parses a string into a SemanticVersion object. - // Supports partial versions (e.g., "1.2") by defaulting missing parts to 0. - static absl::StatusOr Parse(std::string_view text) { - if (!text.empty() && (text[0] == 'v' || text[0] == 'V')) { - text.remove_prefix(1); - } - - // 1. Remove build metadata (ignored for precedence comparison) - std::vector build_parts = absl::StrSplit(text, '+'); - std::string_view core_and_pre = build_parts[0]; - - // 2. Separate core and pre-release - std::vector pre_parts = - absl::StrSplit(core_and_pre, absl::MaxSplits('-', 1)); - std::string_view core = pre_parts[0]; - - // 3. Parse core components (major.minor.patch) - std::vector core_parts = absl::StrSplit(core, '.'); - if (core_parts.empty() || core_parts.size() > 3) { - return absl::InvalidArgumentError( - absl::StrCat("Invalid SemVer core: ", core)); - } - - uint64_t major = 0; - uint64_t minor = 0; - uint64_t patch = 0; - if (absl::Status status = ParseSemVerNum(core_parts[0], "Major", &major); - !status.ok()) { - return status; - } - - if (core_parts.size() >= 2) { - if (absl::Status status = ParseSemVerNum(core_parts[1], "Minor", &minor); - !status.ok()) { - return status; - } - } - - if (core_parts.size() == 3) { - if (absl::Status status = ParseSemVerNum(core_parts[2], "Patch", &patch); - !status.ok()) { - return status; - } - } - - // 4. Parse pre-release identifiers - std::vector pre_release; - if (pre_parts.size() > 1) { - std::vector identifiers = - absl::StrSplit(pre_parts[1], '.'); - for (std::string_view ident : identifiers) { - if (ident.empty()) { - return absl::InvalidArgumentError("Empty pre-release identifier"); - } - - bool is_numeric = - std::all_of(ident.begin(), ident.end(), - [](unsigned char chr) { return std::isdigit(chr); }); - if (is_numeric && HasLeadingZero(ident)) { - return absl::InvalidArgumentError( - "Numeric pre-release identifiers MUST NOT contain leading zeros"); - } - - pre_release.emplace_back(ident); - } - } - - return SemanticVersion(major, minor, patch, std::move(pre_release)); - } - - // Compares two SemanticVersion objects based on SemVer 2.0.0 precedence - // rules. Returns: -1 if this < other, 0 if equal, 1 if this > other - int Compare(const SemanticVersion& other) const { - if (major_ != other.major_) return major_ < other.major_ ? -1 : 1; - if (minor_ != other.minor_) return minor_ < other.minor_ ? -1 : 1; - if (patch_ != other.patch_) return patch_ < other.patch_ ? -1 : 1; - - // A normal version has higher precedence than a pre-release version - if (pre_release_.empty() && !other.pre_release_.empty()) return 1; - if (!pre_release_.empty() && other.pre_release_.empty()) return -1; - if (pre_release_.empty() && other.pre_release_.empty()) return 0; - - size_t len = std::min(pre_release_.size(), other.pre_release_.size()); - for (size_t i = 0; i < len; ++i) { - const std::string& lhs_part = pre_release_[i]; - const std::string& rhs_part = other.pre_release_[i]; - - // Numeric identifiers have lower precedence than non-numeric identifiers. - bool lhs_is_num = - std::all_of(lhs_part.begin(), lhs_part.end(), - [](unsigned char chr) { return std::isdigit(chr); }); - bool rhs_is_num = - std::all_of(rhs_part.begin(), rhs_part.end(), - [](unsigned char chr) { return std::isdigit(chr); }); - - if (lhs_is_num && rhs_is_num) { - // Compare numerically by length first, then lexicographically. - // This supports arbitrary precision integers without overflow. - if (lhs_part.length() != rhs_part.length()) { - return lhs_part.length() < rhs_part.length() ? -1 : 1; - } - if (lhs_part != rhs_part) return lhs_part < rhs_part ? -1 : 1; - } else if (lhs_is_num && !rhs_is_num) { - return -1; - } else if (!lhs_is_num && rhs_is_num) { - return 1; - } else { - // Non-numeric identifiers are compared lexicographically in ASCII sort - // order. - if (lhs_part != rhs_part) return lhs_part < rhs_part ? -1 : 1; - } - } - - // A larger set of pre-release fields has a higher precedence than a smaller - // set. - if (pre_release_.size() != other.pre_release_.size()) { - return pre_release_.size() < other.pre_release_.size() ? -1 : 1; - } - - return 0; - } -}; - -struct Distribution { - std::string variant; - int32_t weight; -}; - -struct FractionalContext { - std::vector distributions; - uint64_t sum_of_weights; -}; - -// Resolves the bucketing value based on the rule and data. -// If the first argument evaluates to a string, it's used as the bucketing -// property. Otherwise, it falls back to flagKey + targetingKey. -absl::StatusOr ResolveBucketingValue( - const json_logic::JsonLogic& eval, const nlohmann::json& values, - const nlohmann::json& data, bool& first_value_used) { - absl::StatusOr bucketing_property_eval = - eval.Apply(values[0], data); - if (!bucketing_property_eval.ok()) return bucketing_property_eval.status(); - - if (bucketing_property_eval.value().is_string()) { - first_value_used = true; - return bucketing_property_eval.value().get(); - } - - first_value_used = false; - // Fallback logic from spec: Concatenate flagKey and targetingKey if property - // is missing - std::string flag_key; - if (data.contains("$flagd") && data["$flagd"].is_object() && - data["$flagd"].contains("flagKey") && - data["$flagd"]["flagKey"].is_string()) { - flag_key = data["$flagd"]["flagKey"].get(); - } - - std::string targeting_key; - if (data.contains("targetingKey") && data["targetingKey"].is_string()) { - targeting_key = data["targetingKey"].get(); - } - return absl::StrCat(flag_key, targeting_key); -} - -// Parses the distributions from the values array. -absl::StatusOr ParseDistributions( - const json_logic::JsonLogic& eval, const nlohmann::json& values, - const nlohmann::json& data, bool first_value_used) { - std::vector distributions; - uint64_t sum_of_weights = 0; - - for (size_t i = first_value_used ? 1 : 0; i < values.size(); i++) { - absl::StatusOr item = eval.Apply(values[i], data); - if (!item.ok()) return item.status(); - if (!item.value().is_array() || item.value().empty()) { - return absl::InvalidArgumentError("Invalid distribution element"); - } - - if (!item.value()[0].is_string()) { - return absl::InvalidArgumentError("Variant name must be a string"); - } - - int32_t weight = 1; - if (item.value().size() >= 2) { - if (!item.value()[1].is_number()) { - return absl::InvalidArgumentError("Bucket weight must be a number"); - } - weight = item.value()[1].get(); - weight = std::max(weight, 0); - } - - distributions.push_back({item.value()[0].get(), weight}); - sum_of_weights += weight; - } - - if (distributions.empty()) { - return absl::InvalidArgumentError("No distributions found"); - } - - if (sum_of_weights == 0) { - return absl::InvalidArgumentError("Sum of weights must be positive"); - } - - if (sum_of_weights >= - static_cast(std::numeric_limits::max())) { - return absl::InvalidArgumentError("Sum of weights exceeds maximum limit"); - } - - return FractionalContext{std::move(distributions), sum_of_weights}; -} - -// Calculates the hash value for the given input using MurmurHash3. -absl::StatusOr CalculateHash(const std::string& input) { - if (input.length() > static_cast(std::numeric_limits::max())) { - return absl::InvalidArgumentError( - "Input string is too long for MurmurHash3"); - } - uint32_t hash_value; - MurmurHash3_x86_32(input.data(), static_cast(input.length()), 0, - &hash_value); - return hash_value; -} - -// Calculates the bucket and selects the variant based on the hash value. -absl::StatusOr SelectVariant( - const std::vector& distributions, uint64_t sum_of_weights, - uint32_t hash_value) { - // High-precision bucketing using 64-bit math to distribute hash over - // sum_of_weights - uint64_t bucket = (static_cast(hash_value) * sum_of_weights) >> - std::numeric_limits::digits; - - uint64_t range_end = 0; - for (const Distribution& dist : distributions) { - range_end += dist.weight; - if (bucket < range_end) { - return dist.variant; - } - } - - return absl::InternalError("Fractional bucketing failed to find a variant"); -} - -} // namespace - -absl::StatusOr StartsWith(const json_logic::JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> strings_res = - GetStrings(eval, values, data, 2); - if (!strings_res.ok()) return strings_res.status(); - - const std::string& source_str = strings_res.value()[0]; - const std::string& prefix = strings_res.value()[1]; - - return absl::StartsWith(source_str, prefix); -} - -absl::StatusOr EndsWith(const json_logic::JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> strings_res = - GetStrings(eval, values, data, 2); - if (!strings_res.ok()) return strings_res.status(); - - const std::string& source_str = strings_res.value()[0]; - const std::string& suffix = strings_res.value()[1]; - - return absl::EndsWith(source_str, suffix); -} - -absl::StatusOr SemVer(const json_logic::JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> strings_res = - GetStrings(eval, values, data, 3); - if (!strings_res.ok()) return strings_res.status(); - - absl::StatusOr v1_res = - SemanticVersion::Parse(strings_res.value()[0]); - if (!v1_res.ok()) return v1_res.status(); - const SemanticVersion& ver1 = v1_res.value(); - - const std::string& operation = strings_res.value()[1]; - - absl::StatusOr v2_res = - SemanticVersion::Parse(strings_res.value()[2]); - if (!v2_res.ok()) return v2_res.status(); - const SemanticVersion& ver2 = v2_res.value(); - - const int cmp = ver1.Compare(ver2); - - if (operation == "=" || operation == "==") return cmp == 0; - if (operation == "!=") return cmp != 0; - if (operation == ">") return cmp > 0; - if (operation == "<") return cmp < 0; - if (operation == ">=") return cmp >= 0; - if (operation == "<=") return cmp <= 0; - if (operation == "^") { - if (ver1.Compare(ver2) < 0) return false; - if (ver2.GetMajor() > 0) { - return ver1.GetMajor() == ver2.GetMajor(); - } - if (ver2.GetMinor() > 0) { - return ver1.GetMajor() == 0 && ver1.GetMinor() == ver2.GetMinor(); - } - return ver1.GetMajor() == 0 && ver1.GetMinor() == 0 && - ver1.GetPatch() == ver2.GetPatch(); - } - if (operation == "~") { - if (ver1.Compare(ver2) < 0) return false; - return ver1.GetMajor() == ver2.GetMajor() && - ver1.GetMinor() == ver2.GetMinor(); - } - - return absl::InvalidArgumentError( - absl::StrCat("Unknown SemVer operator: ", operation)); -} - -absl::StatusOr Fractional(const json_logic::JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - if (!values.is_array()) { - return absl::InvalidArgumentError( - "fractional evaluation data is not an array"); - } - - if (values.size() < 2) { - return absl::InvalidArgumentError( - "fractional evaluation data has length under 2"); - } - - bool first_value_used = false; - absl::StatusOr bucketing_property_res = - ResolveBucketingValue(eval, values, data, first_value_used); - if (!bucketing_property_res.ok()) return bucketing_property_res.status(); - - absl::StatusOr context_res = - ParseDistributions(eval, values, data, first_value_used); - if (!context_res.ok()) return context_res.status(); - - absl::StatusOr hash_res = - CalculateHash(bucketing_property_res.value()); - if (!hash_res.ok()) return hash_res.status(); - - absl::StatusOr variant_res = - SelectVariant(context_res->distributions, context_res->sum_of_weights, - hash_res.value()); - if (!variant_res.ok()) return variant_res.status(); - - return variant_res.value(); -} - -} // namespace flagd diff --git a/providers/flagd/src/evaluator/flagd_ops.h b/providers/flagd/src/evaluator/flagd_ops.h deleted file mode 100644 index 95bd846..0000000 --- a/providers/flagd/src/evaluator/flagd_ops.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef PROVIDERS_FLAGD_SRC_EVALUATOR_FLAGD_OPS_H_ -#define PROVIDERS_FLAGD_SRC_EVALUATOR_FLAGD_OPS_H_ - -#include - -#include "absl/status/statusor.h" -#include "flagd/evaluator/json_logic/json_logic.h" - -namespace flagd { - -absl::StatusOr StartsWith(const json_logic::JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr EndsWith(const json_logic::JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr SemVer(const json_logic::JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Fractional(const json_logic::JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); - -} // namespace flagd - -#endif // PROVIDERS_FLAGD_SRC_EVALUATOR_FLAGD_OPS_H_ diff --git a/providers/flagd/src/evaluator/json_logic/BUILD b/providers/flagd/src/evaluator/json_logic/BUILD deleted file mode 100644 index 9031c33..0000000 --- a/providers/flagd/src/evaluator/json_logic/BUILD +++ /dev/null @@ -1,42 +0,0 @@ -load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") - -cc_library( - name = "json_logic", - srcs = [ - "array.cpp", - "data.cpp", - "json_logic.cpp", - "logic.cpp", - "numeric.cpp", - "string_ops.cpp", - "utils.cpp", - ], - hdrs = [ - "array.h", - "data.h", - "json_logic.h", - "logic.h", - "numeric.h", - "string_ops.h", - "utils.h", - ], - include_prefix = "flagd/evaluator/json_logic", - strip_include_prefix = ".", - visibility = ["//visibility:public"], - deps = [ - "@abseil-cpp//absl/container:flat_hash_map", - "@abseil-cpp//absl/status:statusor", - "@abseil-cpp//absl/strings", - "@nlohmann_json//:json", - ], -) - -cc_binary( - name = "cli", - srcs = ["cli.cpp"], - deps = [ - ":json_logic", - "@abseil-cpp//absl/status", - "@nlohmann_json//:json", - ], -) diff --git a/providers/flagd/src/evaluator/json_logic/README.md b/providers/flagd/src/evaluator/json_logic/README.md deleted file mode 100644 index ae1875a..0000000 --- a/providers/flagd/src/evaluator/json_logic/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# JSON Logic - -This is a C++ implementation of [JSON Logic](https://jsonlogic.com/). - -## Why a custom implementation? - -We decided to create our own implementation because existing C++ libraries for JSON Logic often lack a straightforward way to supply custom operators. Specifically, we need support for flagd custom features: -- [Fractional Operation](https://flagd.dev/reference/custom-operations/fractional-operation/) -- [Semantic Version Operation](https://flagd.dev/reference/custom-operations/semver-operation/) -- [Starts-With / Ends-With Operation](https://flagd.dev/reference/custom-operations/string-comparison-operation/) - -## Future Plans - -This implementation might be published as a separate repository in the future, solely dedicated to `json_logic`. - -## Specification - -This implementation follows the assumptions and specifications defined in the official [JSON Logic spec](https://jsonlogic.com/). diff --git a/providers/flagd/src/evaluator/json_logic/array.cpp b/providers/flagd/src/evaluator/json_logic/array.cpp deleted file mode 100644 index ad6cb22..0000000 --- a/providers/flagd/src/evaluator/json_logic/array.cpp +++ /dev/null @@ -1,28 +0,0 @@ - -#include "array.h" - -namespace json_logic::ops { - -absl::StatusOr Merge(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr resolved_values_res = eval.Apply(values, data); - if (!resolved_values_res.ok()) return resolved_values_res; - const nlohmann::json& resolved_values = *resolved_values_res; - - if (!resolved_values.is_array()) { - return nlohmann::json::array({resolved_values}); - } - - nlohmann::json result = nlohmann::json::array(); - for (const nlohmann::json& val : resolved_values) { - if (val.is_array()) { - result.insert(result.end(), val.begin(), val.end()); - } else { - result.push_back(val); - } - } - return result; -} - -} // namespace json_logic::ops diff --git a/providers/flagd/src/evaluator/json_logic/array.h b/providers/flagd/src/evaluator/json_logic/array.h deleted file mode 100644 index dfb463c..0000000 --- a/providers/flagd/src/evaluator/json_logic/array.h +++ /dev/null @@ -1,18 +0,0 @@ - -#ifndef CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_ARRAY_H_ -#define CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_ARRAY_H_ - -#include - -#include "absl/status/statusor.h" -#include "json_logic.h" - -namespace json_logic::ops { - -absl::StatusOr Merge(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); - -} // namespace json_logic::ops - -#endif // CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_ARRAY_H_ diff --git a/providers/flagd/src/evaluator/json_logic/cli.cpp b/providers/flagd/src/evaluator/json_logic/cli.cpp deleted file mode 100644 index 202c9d5..0000000 --- a/providers/flagd/src/evaluator/json_logic/cli.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include - -#include "absl/status/status.h" -#include "json_logic.h" -#include "nlohmann/json.hpp" - -int main(int argc, char** argv) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " '' ''\n"; - return 1; - } - - std::string logic_str = argv[1]; - std::string data_str = argv[2]; - - nlohmann::json logic; - nlohmann::json data; - try { - logic = nlohmann::json::parse(logic_str); - } catch (const std::exception& e) { - std::cerr << "Error parsing logic JSON: " << e.what() << "\n"; - return 1; - } - - try { - data = nlohmann::json::parse(data_str); - } catch (const std::exception& e) { - std::cerr << "Error parsing data JSON: " << e.what() << "\n"; - return 1; - } - - try { - json_logic::JsonLogic evaluator; - absl::StatusOr result = evaluator.Apply(logic, data); - - if (!result.ok()) { - std::cerr << "Apply error: " << result.status().ToString() << "\n"; - return 1; - } - - std::cout << result.value().dump() << "\n"; - } catch (const std::exception& e) { - std::cerr << "Exception during evaluation: " << e.what() << "\n"; - return 2; - } catch (...) { - std::cerr << "Unknown exception during evaluation.\n"; - return 2; - } - - return 0; -} diff --git a/providers/flagd/src/evaluator/json_logic/data.cpp b/providers/flagd/src/evaluator/json_logic/data.cpp deleted file mode 100644 index 821eaf7..0000000 --- a/providers/flagd/src/evaluator/json_logic/data.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "data.h" - -#include -#include -#include - -#include "absl/status/status.h" -#include "absl/strings/str_cat.h" -#include "json_logic.h" - -namespace json_logic::ops { - -namespace { - -nlohmann::json::json_pointer CreateJsonPointer(std::string path) { - std::replace(path.begin(), path.end(), '.', '/'); - return nlohmann::json::json_pointer("/" + path); -} - -absl::StatusOr GetVariableValue(const nlohmann::json& data, - const std::string& key) { - try { - nlohmann::json::json_pointer ptr = CreateJsonPointer(key); - if (data.contains(ptr)) { - return data[ptr]; - } - } catch (...) { - // invalid pointer or path not found - } - return absl::InvalidArgumentError(absl::StrCat( - "Var key '", key, "' is missing from object: ", data.dump())); -} - -} // namespace - -absl::StatusOr Var(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - std::optional default_val; - nlohmann::json key_object; - std::string key; - - if (values.is_array()) { - if (!values.empty()) { - absl::StatusOr resolved_key = eval.Apply(values[0], data); - if (!resolved_key.ok()) return resolved_key; - key_object = resolved_key.value(); - } - if (values.size() > 1) { - default_val = values[1]; - } - if (values.size() > 2) { - return absl::InvalidArgumentError("Var requires up to 2 arguments."); - } - } else { - key_object = values; - } - - if (key_object.is_null()) { - return data; - } - - if (!key_object.is_string()) { - return absl::InvalidArgumentError("Var key must be a string."); - } - - key = key_object.get(); - - if (key.empty()) return data; - - absl::StatusOr result = GetVariableValue(data, key); - if (result.ok()) return result.value(); - - if (default_val.has_value()) { - return eval.Apply(default_val.value(), data); - } - - return nlohmann::json(nullptr); -} - -absl::StatusOr Missing(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr resolved_args_res = eval.Apply(values, data); - if (!resolved_args_res.ok()) { - return resolved_args_res; - } - nlohmann::json resolved_args = resolved_args_res.value(); - - if (!resolved_args.is_array()) { - resolved_args = nlohmann::json::array({resolved_args}); - } - - nlohmann::json missing = nlohmann::json::array(); - for (const nlohmann::json& arg : resolved_args) { - if (!arg.is_string()) { - return absl::InvalidArgumentError("Missing key must be a string."); - } - - absl::StatusOr result = - GetVariableValue(data, arg.get()); - - if (!result.ok()) { - missing.push_back(arg); - } - } - return missing; -} - -absl::StatusOr MissingSome(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - if (!values.is_array() || values.size() != 2) { - return absl::InvalidArgumentError( - "MissingSome requires exactly two arguments."); - } - - absl::StatusOr resolved_min_res = eval.Apply(values[0], data); - if (!resolved_min_res.ok()) return resolved_min_res; - const nlohmann::json& resolved_min = *resolved_min_res; - - if (!resolved_min.is_number_unsigned()) { - return absl::InvalidArgumentError( - "MissingSome min_required must be an unsigned integer."); - } - uint64_t min_required = resolved_min.get(); - - const nlohmann::json& keys_logic = values[1]; - absl::StatusOr resolved_keys_res = - eval.Apply(keys_logic, data); - if (!resolved_keys_res.ok()) return resolved_keys_res; - const nlohmann::json& keys = *resolved_keys_res; - - if (!keys.is_array()) { - return absl::InvalidArgumentError("MissingSome keys must be an array."); - } - - absl::StatusOr missing_res = Missing(eval, keys, data); - if (!missing_res.ok()) return missing_res.status(); - - nlohmann::json missing = *missing_res; - - // As we know that `missing` is a subset of keys, we can safely subtract the - // sizes. - if (keys.size() - missing.size() >= min_required) { - return nlohmann::json::array(); - } - - return missing; -} - -} // namespace json_logic::ops diff --git a/providers/flagd/src/evaluator/json_logic/data.h b/providers/flagd/src/evaluator/json_logic/data.h deleted file mode 100644 index 3e3b31e..0000000 --- a/providers/flagd/src/evaluator/json_logic/data.h +++ /dev/null @@ -1,26 +0,0 @@ - -#ifndef CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_DATA_H_ -#define CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_DATA_H_ - -#include - -#include "absl/status/statusor.h" -#include "json_logic.h" - -namespace json_logic::ops { - -// Retrieve data using dot notation or array indices (e.g. "a.b.1"). If the path -// is missing, returns the second argument (if present) or null. -absl::StatusOr Var(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Missing(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr MissingSome(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); - -} // namespace json_logic::ops - -#endif // CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_DATA_H_ diff --git a/providers/flagd/src/evaluator/json_logic/json_logic.cpp b/providers/flagd/src/evaluator/json_logic/json_logic.cpp deleted file mode 100644 index 448bf03..0000000 --- a/providers/flagd/src/evaluator/json_logic/json_logic.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "json_logic.h" - -#include -#include - -#include "absl/status/status.h" -#include "array.h" -#include "data.h" -#include "logic.h" -#include "numeric.h" -#include "string_ops.h" - -namespace json_logic { - -JsonLogic::JsonLogic() { - RegisterOperation("var", ops::Var); - RegisterOperation("missing", ops::Missing); - RegisterOperation("missing_some", ops::MissingSome); - - RegisterOperation("and", ops::And); - RegisterOperation("or", ops::Or); - RegisterOperation("!", ops::Not); - RegisterOperation("!!", ops::DoubleNegation); - RegisterOperation("if", ops::If); - RegisterOperation("?:", ops::If); - RegisterOperation("==", ops::StrictEquals); - RegisterOperation("===", ops::StrictEquals); - RegisterOperation("!=", ops::StrictNotEquals); - RegisterOperation("!==", ops::StrictNotEquals); - - RegisterOperation("+", ops::Add); - RegisterOperation("-", ops::Subtract); - RegisterOperation("*", ops::Multiply); - RegisterOperation("/", ops::Divide); - RegisterOperation("%", ops::Modulo); - RegisterOperation("min", ops::Min); - RegisterOperation("max", ops::Max); - RegisterOperation("<", ops::LessThan); - RegisterOperation("<=", ops::LessThanOrEqual); - RegisterOperation(">", ops::GreaterThan); - RegisterOperation(">=", ops::GreaterThanOrEqual); - - RegisterOperation("cat", ops::Cat); - RegisterOperation("substr", ops::Substr); - RegisterOperation("in", ops::In); - - RegisterOperation("merge", ops::Merge); -} - -void JsonLogic::RegisterOperation(std::string_view operation, - const OpFunc& func) { - operations_.emplace(operation, func); -} - -absl::StatusOr JsonLogic::Apply( - const nlohmann::json& logic, const nlohmann::json& data) const { - if (logic.is_primitive()) { - return logic; - } - - if (logic.is_array()) { - nlohmann::json result = nlohmann::json::array(); - for (const auto& item : logic) { - auto applied = Apply(item, data); - if (!applied.ok()) { - return applied.status(); - } - result.push_back(applied.value()); - } - return result; - } - - if (logic.is_object()) { - if (logic.empty()) { - return logic; - } - - // There is inconsistency between JsonLogic implementations. - // When object have more than one key, some execute only the first one, - // and some treat it as an invalid rule and treat it as data, returning the - // whole object. Here we went with the first option. - auto iter = logic.begin(); - std::string const& operation = iter.key(); - - absl::StatusOr operation_result = - ApplyOp(operation, iter.value(), data); - - if (!operation_result.ok() && - operation_result.status().code() == absl::StatusCode::kNotFound) { - return logic; - } - - return operation_result; - } - - return nlohmann::json(); -} - -absl::StatusOr JsonLogic::ApplyOp( - const std::string& operation, const nlohmann::json& values, - const nlohmann::json& data) const { - auto iter = operations_.find(operation); - if (iter != operations_.end()) { - return iter->second(*this, values, data); - } - - return absl::NotFoundError("Unknown operation: " + operation); -} - -} // namespace json_logic diff --git a/providers/flagd/src/evaluator/json_logic/json_logic.h b/providers/flagd/src/evaluator/json_logic/json_logic.h deleted file mode 100644 index f0efde4..0000000 --- a/providers/flagd/src/evaluator/json_logic/json_logic.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_JSON_LOGIC_H_ -#define CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_JSON_LOGIC_H_ - -#include -#include -#include - -#include "absl/container/flat_hash_map.h" -#include "absl/status/statusor.h" - -namespace json_logic { - -class JsonLogic { - public: - using OpFunc = std::function( - const JsonLogic&, const nlohmann::json&, const nlohmann::json&)>; - - JsonLogic(); - ~JsonLogic() = default; - - absl::StatusOr Apply(const nlohmann::json& logic, - const nlohmann::json& data) const; - - void RegisterOperation(std::string_view operation, const OpFunc& func); - - private: - absl::StatusOr ApplyOp(const std::string& operation, - const nlohmann::json& values, - const nlohmann::json& data) const; - - absl::flat_hash_map operations_; -}; - -} // namespace json_logic - -#endif // CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_JSON_LOGIC_H_ diff --git a/providers/flagd/src/evaluator/json_logic/logic.cpp b/providers/flagd/src/evaluator/json_logic/logic.cpp deleted file mode 100644 index 95e87b1..0000000 --- a/providers/flagd/src/evaluator/json_logic/logic.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "logic.h" - -#include "absl/status/status.h" -#include "providers/flagd/src/evaluator/json_logic/utils.h" - -namespace json_logic::ops { - -namespace { -// Strict Truthy Check -absl::StatusOr GetBool(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr result = eval.Apply(values, data); - if (!result.ok()) return result.status(); - return Truthy(result.value()); -} -} // namespace - -absl::StatusOr And(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - nlohmann::json sugar = values; - if (!values.is_array()) { - sugar = nlohmann::json::array({values}); - } - - if (sugar.empty()) { - return absl::InvalidArgumentError( - "And with empty array is currently undefined behaviour"); - } - - nlohmann::json last_val; - for (const nlohmann::json& val : sugar) { - absl::StatusOr res = eval.Apply(val, data); - if (!res.ok()) return res.status(); - last_val = res.value(); - if (!Truthy(last_val)) return last_val; - } - return last_val; -} - -absl::StatusOr Or(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - nlohmann::json sugar = values; - if (!values.is_array()) { - sugar = nlohmann::json::array({values}); - } - - if (sugar.empty()) { - return absl::InvalidArgumentError( - "Or with empty array is currently undefined behaviour"); - } - - nlohmann::json last_val; - for (const nlohmann::json& val : sugar) { - absl::StatusOr res = eval.Apply(val, data); - if (!res.ok()) return res.status(); - last_val = res.value(); - if (Truthy(last_val)) return last_val; - } - return last_val; -} - -absl::StatusOr Not(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - if (values.is_array() && values.size() > 1) { - return absl::InvalidArgumentError("! accepts only single argument."); - } - nlohmann::json sugar = - (values.is_array() && !values.empty()) ? values[0] : values; - absl::StatusOr res = GetBool(eval, sugar, data); - if (!res.ok()) return res.status(); - return !res.value(); -} - -absl::StatusOr DoubleNegation(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - if (values.is_array() && values.size() > 1) { - return absl::InvalidArgumentError("!! accepts only single argument."); - } - nlohmann::json sugar = - (values.is_array() && !values.empty()) ? values[0] : values; - return GetBool(eval, sugar, data); -} - -absl::StatusOr If(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - if (!values.is_array() || values.empty()) { - return absl::InvalidArgumentError( - "`If` operator accepts only non-empty array argument"); - } - - for (size_t i = 0; i < values.size() - 1; i += 2) { - absl::StatusOr check = GetBool(eval, values[i], data); - if (!check.ok()) return check.status(); - if (*check) { - return eval.Apply(values[i + 1], data); - } - } - if (values.size() % 2 == 1) { - return eval.Apply(values.back(), data); - } - return absl::InvalidArgumentError( - "No `if` operator rule matched and no default provided"); -} - -absl::StatusOr StrictEquals(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - if (!values.is_array() || values.size() != 2) { - return absl::InvalidArgumentError( - "`===` operator requires exactly two arguments."); - } - - absl::StatusOr left = eval.Apply(values[0], data); - if (!left.ok()) return left.status(); - - absl::StatusOr right = eval.Apply(values[1], data); - if (!right.ok()) return right.status(); - - return *left == *right; -} - -absl::StatusOr StrictNotEquals(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - if (!values.is_array() || values.size() != 2) { - return absl::InvalidArgumentError( - "`!==` operator requires exactly two arguments."); - } - - absl::StatusOr left = eval.Apply(values[0], data); - if (!left.ok()) return left.status(); - - absl::StatusOr right = eval.Apply(values[1], data); - if (!right.ok()) return right.status(); - - return *left != *right; -} - -} // namespace json_logic::ops diff --git a/providers/flagd/src/evaluator/json_logic/logic.h b/providers/flagd/src/evaluator/json_logic/logic.h deleted file mode 100644 index 0a473d5..0000000 --- a/providers/flagd/src/evaluator/json_logic/logic.h +++ /dev/null @@ -1,36 +0,0 @@ - -#ifndef CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_LOGIC_H_ -#define CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_LOGIC_H_ - -#include - -#include "absl/status/statusor.h" -#include "json_logic.h" - -namespace json_logic::ops { - -absl::StatusOr And(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Or(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Not(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr DoubleNegation(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr If(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr StrictEquals(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr StrictNotEquals(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); - -} // namespace json_logic::ops - -#endif // CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_LOGIC_H_ diff --git a/providers/flagd/src/evaluator/json_logic/numeric.cpp b/providers/flagd/src/evaluator/json_logic/numeric.cpp deleted file mode 100644 index 2b7d6b8..0000000 --- a/providers/flagd/src/evaluator/json_logic/numeric.cpp +++ /dev/null @@ -1,384 +0,0 @@ -#include "numeric.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "absl/status/status.h" - -namespace json_logic::ops { - -namespace { - -/** - * A robust representation of a JSON number supporting various precisions. - * Preserves integer precision where possible, following C++ promotion rules. - */ -class Number { - public: - explicit Number(const nlohmann::json& json_val) { - if (json_val.is_number_unsigned()) { - value_ = json_val.get(); - } else if (json_val.is_number_integer()) { - value_ = json_val.get(); - } else { - value_ = json_val.get(); - } - } - - explicit Number(int64_t val) : value_(val) {} - explicit Number(uint64_t val) : value_(val) {} - explicit Number(double val) : value_(val) {} - - double AsDouble() const noexcept { - return std::visit([](auto val) { return static_cast(val); }, - value_); - } - - int64_t AsInt64() const noexcept { - return std::visit([](auto val) { return static_cast(val); }, - value_); - } - - bool IsFloat() const noexcept { - return std::holds_alternative(value_); - } - - nlohmann::json ToJson() const { - return std::visit([](auto val) { return nlohmann::json(val); }, value_); - } - - bool operator<(const Number& other) const noexcept { - return std::visit( - [](auto lhs_val, auto rhs_val) -> bool { - using LeftType = decltype(lhs_val); - using RightType = decltype(rhs_val); - if constexpr (std::is_integral_v && - std::is_integral_v) { - if constexpr (std::is_signed_v == - std::is_signed_v) { - return lhs_val < rhs_val; - } else if constexpr (std::is_signed_v) { - // lhs is signed, rhs is unsigned - return lhs_val < 0 || static_cast(lhs_val) < rhs_val; - } else { - // lhs is unsigned, rhs is signed - return rhs_val >= 0 && lhs_val < static_cast(rhs_val); - } - } - return static_cast(lhs_val) < static_cast(rhs_val); - }, - value_, other.value_); - } - - bool operator==(const Number& other) const noexcept { - return std::visit( - [](auto lhs_val, auto rhs_val) -> bool { - using LeftType = decltype(lhs_val); - using RightType = decltype(rhs_val); - if constexpr (std::is_integral_v && - std::is_integral_v) { - if constexpr (std::is_signed_v == - std::is_signed_v) { - return lhs_val == rhs_val; - } else if constexpr (std::is_signed_v) { - return lhs_val >= 0 && static_cast(lhs_val) == rhs_val; - } else { - return rhs_val >= 0 && lhs_val == static_cast(rhs_val); - } - } - return static_cast(lhs_val) == static_cast(rhs_val); - }, - value_, other.value_); - } - - static Number Add(const Number& lhs, const Number& rhs) noexcept { - return std::visit( - [](auto lhs_val, auto rhs_val) -> Number { - if constexpr (std::is_integral_v && - std::is_integral_v) { - return Number(lhs_val + rhs_val); - } - return Number(static_cast(lhs_val) + - static_cast(rhs_val)); - }, - lhs.value_, rhs.value_); - } - - static Number Mul(const Number& lhs, const Number& rhs) noexcept { - return std::visit( - [](auto lhs_val, auto rhs_val) -> Number { - if constexpr (std::is_integral_v && - std::is_integral_v) { - return Number(lhs_val * rhs_val); - } - return Number(static_cast(lhs_val) * - static_cast(rhs_val)); - }, - lhs.value_, rhs.value_); - } - - static Number Sub(const Number& lhs, const Number& rhs) noexcept { - return std::visit( - [](auto lhs_val, auto rhs_val) -> Number { - using LeftType = decltype(lhs_val); - using RightType = decltype(rhs_val); - if constexpr (std::is_integral_v && - std::is_integral_v) { - if ((!std::is_unsigned_v || - lhs_val <= static_cast( - std::numeric_limits::max())) && - (!std::is_unsigned_v || - rhs_val <= static_cast( - std::numeric_limits::max()))) { - return Number(static_cast(lhs_val) - - static_cast(rhs_val)); - } - - // Fallback for very large unsigned values that don't fit in int64_t - if constexpr (std::is_same_v && - std::is_same_v) { - if (lhs_val >= rhs_val) return Number(lhs_val - rhs_val); - } - - return Number(static_cast(lhs_val) - - static_cast(rhs_val)); - } - return Number(static_cast(lhs_val) - - static_cast(rhs_val)); - }, - lhs.value_, rhs.value_); - } - - static absl::StatusOr Div(const Number& lhs, - const Number& rhs) noexcept { - double divisor = rhs.AsDouble(); - if (divisor == 0.0) { - return absl::InvalidArgumentError("Division by zero"); - } - return Number(lhs.AsDouble() / divisor); - } - - static absl::StatusOr Mod(const Number& lhs, - const Number& rhs) noexcept { - return std::visit( - [](auto lhs_val, auto rhs_val) -> absl::StatusOr { - if constexpr (std::is_floating_point_v || - std::is_floating_point_v) { - auto divisor = static_cast(rhs_val); - if (divisor == 0.0) { - return absl::InvalidArgumentError("Modulo by zero"); - } - return Number(std::fmod(static_cast(lhs_val), divisor)); - } else { - if (rhs_val == 0) { - return absl::InvalidArgumentError("Modulo by zero"); - } - if constexpr (std::is_same_v && - std::is_same_v) { - if (lhs_val == std::numeric_limits::min() && - rhs_val == -1) { - return Number(int64_t{0}); - } - } - return Number(lhs_val % rhs_val); - } - }, - lhs.value_, rhs.value_); - } - - private: - std::variant value_; -}; - -/** - * Resolves arguments and converts them to a vector of Number. - * Handles both single values and arrays of values. - */ -absl::StatusOr> GetNumbers(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data, - std::string_view op_name) { - absl::StatusOr resolved_res = eval.Apply(values, data); - if (!resolved_res.ok()) return resolved_res.status(); - const nlohmann::json& resolved = *resolved_res; - - std::vector nums; - if (resolved.is_array()) { - nums.reserve(resolved.size()); - for (const nlohmann::json& item : resolved) { - if (!item.is_number()) { - return absl::InvalidArgumentError(std::string(op_name) + - " with non-numeric argument"); - } - nums.emplace_back(item); - } - } else if (resolved.is_number()) { - nums.emplace_back(resolved); - } else if (!resolved.is_null()) { - return absl::InvalidArgumentError(std::string(op_name) + - " with non-numeric argument"); - } - return nums; -} - -} // namespace - -absl::StatusOr Min(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "min"); - if (!nums_res.ok()) return nums_res.status(); - if (nums_res->empty()) return nullptr; - - return std::min_element(nums_res->begin(), nums_res->end())->ToJson(); -} - -absl::StatusOr Max(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "max"); - if (!nums_res.ok()) return nums_res.status(); - if (nums_res->empty()) return nullptr; - - return std::max_element(nums_res->begin(), nums_res->end())->ToJson(); -} - -absl::StatusOr Add(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "add"); - if (!nums_res.ok()) return nums_res.status(); - if (nums_res->empty()) return 0; - - return std::accumulate(nums_res->begin() + 1, nums_res->end(), (*nums_res)[0], - Number::Add) - .ToJson(); -} - -absl::StatusOr Subtract(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "subtract"); - if (!nums_res.ok()) return nums_res.status(); - - if (nums_res->size() == 1) { - return Number::Sub(Number(int64_t{0}), (*nums_res)[0]).ToJson(); - } - if (nums_res->size() == 2) { - return Number::Sub((*nums_res)[0], (*nums_res)[1]).ToJson(); - } - return absl::InvalidArgumentError( - "Subtract requires exactly one or two arguments"); -} - -absl::StatusOr Multiply(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "multiply"); - if (!nums_res.ok()) return nums_res.status(); - if (nums_res->empty()) return 1; - - return std::accumulate(nums_res->begin() + 1, nums_res->end(), (*nums_res)[0], - Number::Mul) - .ToJson(); -} - -absl::StatusOr Divide(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "/"); - if (!nums_res.ok()) return nums_res.status(); - if (nums_res->size() != 2) { - return absl::InvalidArgumentError("Division requires two arguments"); - } - - absl::StatusOr result_res = - Number::Div((*nums_res)[0], (*nums_res)[1]); - if (!result_res.ok()) return result_res.status(); - return result_res->ToJson(); -} - -absl::StatusOr Modulo(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "%"); - if (!nums_res.ok()) return nums_res.status(); - if (nums_res->size() != 2) { - return absl::InvalidArgumentError("Modulo requires two arguments"); - } - - absl::StatusOr result_res = - Number::Mod((*nums_res)[0], (*nums_res)[1]); - if (!result_res.ok()) return result_res.status(); - return result_res->ToJson(); -} - -absl::StatusOr LessThan(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "<"); - if (!nums_res.ok()) return nums_res.status(); - - if (nums_res->size() == 2) return (*nums_res)[0] < (*nums_res)[1]; - if (nums_res->size() == 3) { - return (*nums_res)[0] < (*nums_res)[1] && (*nums_res)[1] < (*nums_res)[2]; - } - return absl::InvalidArgumentError("Comparison requires 2 or 3 arguments"); -} - -absl::StatusOr LessThanOrEqual(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, "<="); - if (!nums_res.ok()) return nums_res.status(); - - auto is_lte = [](const Number& first, const Number& second) { - return first < second || first == second; - }; - if (nums_res->size() == 2) return is_lte((*nums_res)[0], (*nums_res)[1]); - if (nums_res->size() == 3) { - return is_lte((*nums_res)[0], (*nums_res)[1]) && - is_lte((*nums_res)[1], (*nums_res)[2]); - } - return absl::InvalidArgumentError("Comparison requires 2 or 3 arguments"); -} - -absl::StatusOr GreaterThan(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, ">"); - if (!nums_res.ok()) return nums_res.status(); - - if (nums_res->size() == 2) return (*nums_res)[1] < (*nums_res)[0]; - return absl::InvalidArgumentError("GreaterThan requires 2 arguments"); -} - -absl::StatusOr GreaterThanOrEqual(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr> nums_res = - GetNumbers(eval, values, data, ">="); - if (!nums_res.ok()) return nums_res.status(); - - if (nums_res->size() == 2) { - return (*nums_res)[1] < (*nums_res)[0] || (*nums_res)[0] == (*nums_res)[1]; - } - return absl::InvalidArgumentError("GreaterThanOrEqual requires 2 arguments"); -} - -} // namespace json_logic::ops diff --git a/providers/flagd/src/evaluator/json_logic/numeric.h b/providers/flagd/src/evaluator/json_logic/numeric.h deleted file mode 100644 index f4f544a..0000000 --- a/providers/flagd/src/evaluator/json_logic/numeric.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_NUMERIC_H_ -#define CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_NUMERIC_H_ - -#include - -#include "absl/status/statusor.h" -#include "json_logic.h" - -namespace json_logic::ops { - -absl::StatusOr Min(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Max(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Add(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Subtract(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Multiply(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Divide(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Modulo(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr LessThan(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr LessThanOrEqual(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr GreaterThan(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr GreaterThanOrEqual(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); - -} // namespace json_logic::ops - -#endif // CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_NUMERIC_H_ diff --git a/providers/flagd/src/evaluator/json_logic/string_ops.cpp b/providers/flagd/src/evaluator/json_logic/string_ops.cpp deleted file mode 100644 index 9a527f8..0000000 --- a/providers/flagd/src/evaluator/json_logic/string_ops.cpp +++ /dev/null @@ -1,129 +0,0 @@ - -#include "string_ops.h" - -#include -#include -#include -#include - -#include "absl/status/status.h" -#include "absl/strings/str_join.h" - -namespace json_logic::ops { - -absl::StatusOr Cat(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr resolved_values_res = eval.Apply(values, data); - if (!resolved_values_res.ok()) return resolved_values_res; - const nlohmann::json& resolved_values = *resolved_values_res; - - if (resolved_values.is_string()) { - return resolved_values; - } - if (!resolved_values.is_array()) { - if (resolved_values.is_primitive()) { - return absl::InvalidArgumentError("cat with non-string primitive"); - } - return resolved_values; - } - - std::vector pieces; - pieces.reserve(resolved_values.size()); - for (const nlohmann::json& val : resolved_values) { - if (!val.is_string()) { - return absl::InvalidArgumentError("cat with non-string argument"); - } - pieces.push_back(val.get_ref()); - } - return absl::StrJoin(pieces, ""); -} - -absl::StatusOr Substr(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - absl::StatusOr resolved_values_res = eval.Apply(values, data); - if (!resolved_values_res.ok()) return resolved_values_res; - const nlohmann::json& resolved_values = *resolved_values_res; - - if (!resolved_values.is_array() || resolved_values.empty()) { - return absl::InvalidArgumentError( - "Arguments to substr must be a non-empty array"); - } - - if (!resolved_values[0].is_string()) { - return absl::InvalidArgumentError( - "prop argument to substr must be a string"); - } - - std::string str = resolved_values[0].get(); - int64_t start = 0; - - if (resolved_values.size() > 1) { - if (!resolved_values[1].is_number_integer()) { - return absl::InvalidArgumentError( - "Start argument to substr must be an integer"); - } - start = resolved_values[1].get(); - } - - if (start < 0) { - start = str.length() + start; - start = std::max(start, static_cast(0)); - } - - if (static_cast(start) >= str.length()) return ""; - - if (resolved_values.size() > 2) { - if (!resolved_values[2].is_number_integer()) { - return absl::InvalidArgumentError( - "Length argument to substr must be an integer"); - } - int len = resolved_values[2].get(); - if (len < 0) { - len = str.length() - start + len; - } - if (len < 0) return ""; - return str.substr(start, len); - } - - return str.substr(start); -} - -absl::StatusOr In(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data) { - if (!values.is_array() || values.size() != 2) { - return absl::InvalidArgumentError( - "Operator `in` requires exactly two arguments."); - } - - absl::StatusOr sub_res = eval.Apply(values[0], data); - if (!sub_res.ok()) return sub_res; - const nlohmann::json& sub = *sub_res; - - absl::StatusOr container_res = eval.Apply(values[1], data); - if (!container_res.ok()) return container_res; - const nlohmann::json& container = *container_res; - - if (container.is_string()) { - if (!sub.is_string()) { - return absl::InvalidArgumentError( - "in operator: substring check requires string"); - } - return container.get().find(sub.get()) != - std::string::npos; - } - - if (container.is_array()) { - for (const nlohmann::json& item : container) { - if (item == sub) return true; - } - return false; - } - - return absl::InvalidArgumentError( - "in operator: container must be string or array"); -} - -} // namespace json_logic::ops diff --git a/providers/flagd/src/evaluator/json_logic/string_ops.h b/providers/flagd/src/evaluator/json_logic/string_ops.h deleted file mode 100644 index 4ef0a75..0000000 --- a/providers/flagd/src/evaluator/json_logic/string_ops.h +++ /dev/null @@ -1,24 +0,0 @@ - -#ifndef CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_STRING_OPS_H_ -#define CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_STRING_OPS_H_ - -#include - -#include "absl/status/statusor.h" -#include "json_logic.h" - -namespace json_logic::ops { - -absl::StatusOr Cat(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr Substr(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); -absl::StatusOr In(const JsonLogic& eval, - const nlohmann::json& values, - const nlohmann::json& data); - -} // namespace json_logic::ops - -#endif // CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_STRING_OPS_H_ diff --git a/providers/flagd/src/evaluator/json_logic/utils.cpp b/providers/flagd/src/evaluator/json_logic/utils.cpp deleted file mode 100644 index c64658f..0000000 --- a/providers/flagd/src/evaluator/json_logic/utils.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "utils.h" - -#include - -namespace json_logic::ops { - -bool Truthy(const nlohmann::json& value) { - using JsonType = nlohmann::json::value_t; - - switch (value.type()) { - case JsonType::null: - return false; - - case JsonType::boolean: - return value.get(); - - case JsonType::number_integer: - case JsonType::number_unsigned: - return value.get() != 0; - - case JsonType::number_float: - return value.get() != 0.0; - - case JsonType::string: - return !value.get().empty(); - - case JsonType::array: - return !value.empty(); - - case JsonType::object: - default: - // Objects and any other types are considered truthy. - return true; - } -} - -} // namespace json_logic::ops diff --git a/providers/flagd/src/evaluator/json_logic/utils.h b/providers/flagd/src/evaluator/json_logic/utils.h deleted file mode 100644 index 5d7d2e2..0000000 --- a/providers/flagd/src/evaluator/json_logic/utils.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_UTILS_H_ -#define CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_UTILS_H_ - -#include - -namespace json_logic::ops { - -// Determines if a JSON value is "truthy" according to JsonLogic rules. -// Falsy values: false, 0, [] (empty array), "" (empty string), null. -// All other values are truthy (including empty objects). -bool Truthy(const nlohmann::json& value); - -} // namespace json_logic::ops - -#endif // CPP_SDK_FLAGD_EVALUATOR_JSON_LOGIC_OPS_UTILS_H_ diff --git a/providers/flagd/src/evaluator/murmur_hash/.clang-tidy b/providers/flagd/src/evaluator/murmur_hash/.clang-tidy deleted file mode 100644 index c5d83c4..0000000 --- a/providers/flagd/src/evaluator/murmur_hash/.clang-tidy +++ /dev/null @@ -1,2 +0,0 @@ -# Disable all checks for third-party code but keep a dummy check to avoid errors -Checks: "-*,bugprone-argument-comment" diff --git a/providers/flagd/src/evaluator/murmur_hash/BUILD b/providers/flagd/src/evaluator/murmur_hash/BUILD deleted file mode 100644 index b8bdbd3..0000000 --- a/providers/flagd/src/evaluator/murmur_hash/BUILD +++ /dev/null @@ -1,10 +0,0 @@ -load("@rules_cc//cc:defs.bzl", "cc_library") - -cc_library( - name = "murmur_hash", - srcs = ["MurmurHash3.cpp"], - hdrs = ["MurmurHash3.h"], - include_prefix = "flagd/evaluator/murmur_hash", - strip_include_prefix = "", - visibility = ["//visibility:public"], -) diff --git a/providers/flagd/src/evaluator/murmur_hash/MurmurHash3.cpp b/providers/flagd/src/evaluator/murmur_hash/MurmurHash3.cpp deleted file mode 100644 index bf03ebd..0000000 --- a/providers/flagd/src/evaluator/murmur_hash/MurmurHash3.cpp +++ /dev/null @@ -1,411 +0,0 @@ -//----------------------------------------------------------------------------- -// MurmurHash3 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. -// Source: https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp - -// Note - The x86 and x64 versions do _not_ produce the same results, as the -// algorithms are optimized for their respective platforms. You can still -// compile and run any of them on any platform, but your performance with the -// non-native version will be less than optimal. - -#include "MurmurHash3.h" - -//----------------------------------------------------------------------------- -// Platform-specific functions and macros - -// Microsoft Visual Studio - -#if defined(_MSC_VER) - -#define FORCE_INLINE __forceinline - -#include - -#define ROTL32(x, y) _rotl(x, y) -#define ROTL64(x, y) _rotl64(x, y) - -#define BIG_CONSTANT(x) (x) - -// Other compilers - -#else // defined(_MSC_VER) - -#define FORCE_INLINE inline __attribute__((always_inline)) - -inline uint32_t rotl32(uint32_t x, int8_t r) { - return (x << r) | (x >> (32 - r)); -} - -inline uint64_t rotl64(uint64_t x, int8_t r) { - return (x << r) | (x >> (64 - r)); -} - -#define ROTL32(x, y) rotl32(x, y) -#define ROTL64(x, y) rotl64(x, y) - -#define BIG_CONSTANT(x) (x##LLU) - -#endif // !defined(_MSC_VER) - -//----------------------------------------------------------------------------- -// Block read - if your platform needs to do endian-swapping or can only -// handle aligned reads, do the conversion here - -FORCE_INLINE uint32_t getblock32(const uint32_t* p, int i) { return p[i]; } - -FORCE_INLINE uint64_t getblock64(const uint64_t* p, int i) { return p[i]; } - -//----------------------------------------------------------------------------- -// Finalization mix - force all bits of a hash block to avalanche - -FORCE_INLINE uint32_t fmix32(uint32_t h) { - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - - return h; -} - -//---------- - -FORCE_INLINE uint64_t fmix64(uint64_t k) { - k ^= k >> 33; - k *= BIG_CONSTANT(0xff51afd7ed558ccd); - k ^= k >> 33; - k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); - k ^= k >> 33; - - return k; -} - -//----------------------------------------------------------------------------- - -void MurmurHash3_x86_32(const void* key, int len, uint32_t seed, void* out) { - const uint8_t* data = (const uint8_t*)key; - const int nblocks = len / 4; - - uint32_t h1 = seed; - - const uint32_t c1 = 0xcc9e2d51; - const uint32_t c2 = 0x1b873593; - - //---------- - // body - - const uint32_t* blocks = (const uint32_t*)(data + nblocks * 4); - - for (int i = -nblocks; i; i++) { - uint32_t k1 = getblock32(blocks, i); - - k1 *= c1; - k1 = ROTL32(k1, 15); - k1 *= c2; - - h1 ^= k1; - h1 = ROTL32(h1, 13); - h1 = h1 * 5 + 0xe6546b64; - } - - //---------- - // tail - - const uint8_t* tail = (const uint8_t*)(data + nblocks * 4); - - uint32_t k1 = 0; - - switch (len & 3) { - case 3: - k1 ^= tail[2] << 16; - case 2: - k1 ^= tail[1] << 8; - case 1: - k1 ^= tail[0]; - k1 *= c1; - k1 = ROTL32(k1, 15); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - - h1 ^= len; - - h1 = fmix32(h1); - - *(uint32_t*)out = h1; -} - -//----------------------------------------------------------------------------- - -void MurmurHash3_x86_128(const void* key, const int len, uint32_t seed, - void* out) { - const uint8_t* data = (const uint8_t*)key; - const int nblocks = len / 16; - - uint32_t h1 = seed; - uint32_t h2 = seed; - uint32_t h3 = seed; - uint32_t h4 = seed; - - const uint32_t c1 = 0x239b961b; - const uint32_t c2 = 0xab0e9789; - const uint32_t c3 = 0x38b34ae5; - const uint32_t c4 = 0xa1e38b93; - - //---------- - // body - - const uint32_t* blocks = (const uint32_t*)(data + nblocks * 16); - - for (int i = -nblocks; i; i++) { - uint32_t k1 = getblock32(blocks, i * 4 + 0); - uint32_t k2 = getblock32(blocks, i * 4 + 1); - uint32_t k3 = getblock32(blocks, i * 4 + 2); - uint32_t k4 = getblock32(blocks, i * 4 + 3); - - k1 *= c1; - k1 = ROTL32(k1, 15); - k1 *= c2; - h1 ^= k1; - - h1 = ROTL32(h1, 19); - h1 += h2; - h1 = h1 * 5 + 0x561ccd1b; - - k2 *= c2; - k2 = ROTL32(k2, 16); - k2 *= c3; - h2 ^= k2; - - h2 = ROTL32(h2, 17); - h2 += h3; - h2 = h2 * 5 + 0x0bcaa747; - - k3 *= c3; - k3 = ROTL32(k3, 17); - k3 *= c4; - h3 ^= k3; - - h3 = ROTL32(h3, 15); - h3 += h4; - h3 = h3 * 5 + 0x96cd1c35; - - k4 *= c4; - k4 = ROTL32(k4, 18); - k4 *= c1; - h4 ^= k4; - - h4 = ROTL32(h4, 13); - h4 += h1; - h4 = h4 * 5 + 0x32ac3b17; - } - - //---------- - // tail - - const uint8_t* tail = (const uint8_t*)(data + nblocks * 16); - - uint32_t k1 = 0; - uint32_t k2 = 0; - uint32_t k3 = 0; - uint32_t k4 = 0; - - switch (len & 15) { - case 15: - k4 ^= tail[14] << 16; - case 14: - k4 ^= tail[13] << 8; - case 13: - k4 ^= tail[12] << 0; - k4 *= c4; - k4 = ROTL32(k4, 18); - k4 *= c1; - h4 ^= k4; - - case 12: - k3 ^= tail[11] << 24; - case 11: - k3 ^= tail[10] << 16; - case 10: - k3 ^= tail[9] << 8; - case 9: - k3 ^= tail[8] << 0; - k3 *= c3; - k3 = ROTL32(k3, 17); - k3 *= c4; - h3 ^= k3; - - case 8: - k2 ^= tail[7] << 24; - case 7: - k2 ^= tail[6] << 16; - case 6: - k2 ^= tail[5] << 8; - case 5: - k2 ^= tail[4] << 0; - k2 *= c2; - k2 = ROTL32(k2, 16); - k2 *= c3; - h2 ^= k2; - - case 4: - k1 ^= tail[3] << 24; - case 3: - k1 ^= tail[2] << 16; - case 2: - k1 ^= tail[1] << 8; - case 1: - k1 ^= tail[0] << 0; - k1 *= c1; - k1 = ROTL32(k1, 15); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - - h1 ^= len; - h2 ^= len; - h3 ^= len; - h4 ^= len; - - h1 += h2; - h1 += h3; - h1 += h4; - h2 += h1; - h3 += h1; - h4 += h1; - - h1 = fmix32(h1); - h2 = fmix32(h2); - h3 = fmix32(h3); - h4 = fmix32(h4); - - h1 += h2; - h1 += h3; - h1 += h4; - h2 += h1; - h3 += h1; - h4 += h1; - - ((uint32_t*)out)[0] = h1; - ((uint32_t*)out)[1] = h2; - ((uint32_t*)out)[2] = h3; - ((uint32_t*)out)[3] = h4; -} - -//----------------------------------------------------------------------------- - -void MurmurHash3_x64_128(const void* key, const int len, const uint32_t seed, - void* out) { - const uint8_t* data = (const uint8_t*)key; - const int nblocks = len / 16; - - uint64_t h1 = seed; - uint64_t h2 = seed; - - const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); - const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); - - //---------- - // body - - const uint64_t* blocks = (const uint64_t*)(data); - - for (int i = 0; i < nblocks; i++) { - uint64_t k1 = getblock64(blocks, i * 2 + 0); - uint64_t k2 = getblock64(blocks, i * 2 + 1); - - k1 *= c1; - k1 = ROTL64(k1, 31); - k1 *= c2; - h1 ^= k1; - - h1 = ROTL64(h1, 27); - h1 += h2; - h1 = h1 * 5 + 0x52dce729; - - k2 *= c2; - k2 = ROTL64(k2, 33); - k2 *= c1; - h2 ^= k2; - - h2 = ROTL64(h2, 31); - h2 += h1; - h2 = h2 * 5 + 0x38495ab5; - } - - //---------- - // tail - - const uint8_t* tail = (const uint8_t*)(data + nblocks * 16); - - uint64_t k1 = 0; - uint64_t k2 = 0; - - switch (len & 15) { - case 15: - k2 ^= ((uint64_t)tail[14]) << 48; - case 14: - k2 ^= ((uint64_t)tail[13]) << 40; - case 13: - k2 ^= ((uint64_t)tail[12]) << 32; - case 12: - k2 ^= ((uint64_t)tail[11]) << 24; - case 11: - k2 ^= ((uint64_t)tail[10]) << 16; - case 10: - k2 ^= ((uint64_t)tail[9]) << 8; - case 9: - k2 ^= ((uint64_t)tail[8]) << 0; - k2 *= c2; - k2 = ROTL64(k2, 33); - k2 *= c1; - h2 ^= k2; - - case 8: - k1 ^= ((uint64_t)tail[7]) << 56; - case 7: - k1 ^= ((uint64_t)tail[6]) << 48; - case 6: - k1 ^= ((uint64_t)tail[5]) << 40; - case 5: - k1 ^= ((uint64_t)tail[4]) << 32; - case 4: - k1 ^= ((uint64_t)tail[3]) << 24; - case 3: - k1 ^= ((uint64_t)tail[2]) << 16; - case 2: - k1 ^= ((uint64_t)tail[1]) << 8; - case 1: - k1 ^= ((uint64_t)tail[0]) << 0; - k1 *= c1; - k1 = ROTL64(k1, 31); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - - h1 ^= len; - h2 ^= len; - - h1 += h2; - h2 += h1; - - h1 = fmix64(h1); - h2 = fmix64(h2); - - h1 += h2; - h2 += h1; - - ((uint64_t*)out)[0] = h1; - ((uint64_t*)out)[1] = h2; -} - -//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/providers/flagd/src/evaluator/murmur_hash/MurmurHash3.h b/providers/flagd/src/evaluator/murmur_hash/MurmurHash3.h deleted file mode 100644 index 594c276..0000000 --- a/providers/flagd/src/evaluator/murmur_hash/MurmurHash3.h +++ /dev/null @@ -1,38 +0,0 @@ -//----------------------------------------------------------------------------- -// MurmurHash3 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. -// Source: https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.h - -#ifndef _MURMURHASH3_H_ -#define _MURMURHASH3_H_ - -//----------------------------------------------------------------------------- -// Platform-specific functions and macros - -// Microsoft Visual Studio - -#if defined(_MSC_VER) && (_MSC_VER < 1600) - -typedef unsigned char uint8_t; -typedef unsigned int uint32_t; -typedef unsigned __int64 uint64_t; - -// Other compilers - -#else // defined(_MSC_VER) - -#include - -#endif // !defined(_MSC_VER) - -//----------------------------------------------------------------------------- - -void MurmurHash3_x86_32(const void* key, int len, uint32_t seed, void* out); - -void MurmurHash3_x86_128(const void* key, int len, uint32_t seed, void* out); - -void MurmurHash3_x64_128(const void* key, int len, uint32_t seed, void* out); - -//----------------------------------------------------------------------------- - -#endif // _MURMURHASH3_H_ \ No newline at end of file diff --git a/providers/flagd/src/evaluator/murmur_hash/README.md b/providers/flagd/src/evaluator/murmur_hash/README.md deleted file mode 100644 index d6c4357..0000000 --- a/providers/flagd/src/evaluator/murmur_hash/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# MurmurHash3 - -This directory contains an implementation of MurmurHash3, copied from [aappleby/smhasher](https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp). - -## Origin and License - -All MurmurHash versions are public domain software, and the author (Austin Appleby) disclaims all copyright to their code. - -The code was imported directly as there is no official Bazel package for MurmurHash3 and the full `smhasher` repository contains unnecessary components for this use case. diff --git a/providers/flagd/tests/evaluator/BUILD b/providers/flagd/tests/evaluator/BUILD index 0711852..0bad6b1 100644 --- a/providers/flagd/tests/evaluator/BUILD +++ b/providers/flagd/tests/evaluator/BUILD @@ -12,22 +12,11 @@ cc_test( ) cc_test( - name = "flagd_ops_test", - srcs = ["flagd_ops_test.cpp"], + name = "datalogic_engine_test", + srcs = ["datalogic_engine_test.cpp"], deps = [ - "//providers/flagd/src/evaluator:flagd_ops", - "//providers/flagd/src/evaluator/json_logic", - "@googletest//:gtest_main", - "@nlohmann_json//:json", - ], -) - -cc_test( - name = "flagd_fractional_op_test", - srcs = ["flagd_fractional_op_test.cpp"], - deps = [ - "//providers/flagd/src/evaluator:flagd_ops", - "//providers/flagd/src/evaluator/json_logic", + "//providers/flagd/src/evaluator:datalogic_engine", + "@abseil-cpp//absl/status", "@googletest//:gtest_main", "@nlohmann_json//:json", ], diff --git a/providers/flagd/tests/evaluator/datalogic_engine_test.cpp b/providers/flagd/tests/evaluator/datalogic_engine_test.cpp new file mode 100644 index 0000000..3a070b6 --- /dev/null +++ b/providers/flagd/tests/evaluator/datalogic_engine_test.cpp @@ -0,0 +1,70 @@ +#include "flagd/evaluator/datalogic_engine.h" + +#include + +#include + +#include "absl/status/status.h" + +namespace flagd { +namespace { + +using nlohmann::json; + +TEST(DatalogicEngineTest, EvaluatesVarOperator) { + DatalogicEngine engine; + auto result = + engine.Apply(json::parse(R"({"var": "x"})"), json::parse(R"({"x": 42})")); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(*result, 42); +} + +TEST(DatalogicEngineTest, EvaluatesFlagdStartsWithOperator) { + DatalogicEngine engine; + auto result = engine.Apply( + json::parse(R"({"starts_with": [{"var": "email"}, "hello@"]})"), + json::parse(R"({"email": "hello@example.com"})")); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(*result, true); +} + +TEST(DatalogicEngineTest, EvaluatesFlagdSemVerOperator) { + DatalogicEngine engine; + auto result = + engine.Apply(json::parse(R"({"sem_ver": [{"var": "v"}, ">=", "1.2.3"]})"), + json::parse(R"({"v": "1.2.4"})")); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(*result, true); +} + +TEST(DatalogicEngineTest, FractionalProducesStableVariant) { + DatalogicEngine engine; + const auto rule = json::parse(R"({ + "fractional": [ + {"var": "targetingKey"}, + ["red", 50], + ["blue", 50] + ] + })"); + const auto data = json::parse(R"({"targetingKey": "user-123"})"); + + auto first = engine.Apply(rule, data); + ASSERT_TRUE(first.ok()) << first.status(); + ASSERT_TRUE(first->is_string()); + + auto second = engine.Apply(rule, data); + ASSERT_TRUE(second.ok()) << second.status(); + EXPECT_EQ(*first, *second); + EXPECT_TRUE(*first == "red" || *first == "blue"); +} + +TEST(DatalogicEngineTest, ReportsParseErrorAsInternal) { + DatalogicEngine engine; + auto result = + engine.Apply(json::parse(R"({"not_a_real_op": []})"), json::object()); + EXPECT_FALSE(result.ok()); + EXPECT_EQ(result.status().code(), absl::StatusCode::kInternal); +} + +} // namespace +} // namespace flagd diff --git a/providers/flagd/tests/evaluator/flagd_fractional_op_test.cpp b/providers/flagd/tests/evaluator/flagd_fractional_op_test.cpp deleted file mode 100644 index 5d32f2a..0000000 --- a/providers/flagd/tests/evaluator/flagd_fractional_op_test.cpp +++ /dev/null @@ -1,242 +0,0 @@ -// TODO(#91): This file contains rewritten test cases from -// open-feature/flagd-testbed/blob/main/gherkin/targeting.feature. -// They should be removed once automatic gherkin tests are introduced. -#include - -#include - -#include "providers/flagd/src/evaluator/flagd_ops.h" -#include "providers/flagd/src/evaluator/json_logic/json_logic.h" - -using json_logic::JsonLogic; -using nlohmann::json; - -class FlagdOpsTest : public ::testing::Test { - protected: - void SetUp() override { - json_logic_.RegisterOperation("fractional", flagd::Fractional); - } - - JsonLogic json_logic_; -}; - -TEST_F(FlagdOpsTest, FractionalV2BasicDistribution) { - json data = json::object(); - data["$flagd"] = json::object(); - data["$flagd"]["flagKey"] = "fractional-flag"; - - json logic = json::parse(R"({ - "fractional": [ - {"cat": [ - { "var": "$flagd.flagKey" }, - { "var": "user.name" } - ]}, - [ "clubs", 25 ], - [ "diamonds", 25 ], - [ "hearts", 25 ], - [ "spades", 25 ] - ] - })"); - - // V2 Expected Values - data["user"]["name"] = "jack"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "hearts"); - - data["user"]["name"] = "queen"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "spades"); - - data["user"]["name"] = "ten"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "clubs"); - - data["user"]["name"] = "nine"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "diamonds"); - - data["user"]["name"] = "3"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "clubs"); -} - -TEST_F(FlagdOpsTest, FractionalV2Shorthand) { - json data = json::object(); - data["$flagd"] = json::object(); - data["$flagd"]["flagKey"] = "fractional-flag-shorthand"; - - json logic = json::parse(R"({ - "fractional": [ - [ "heads" ], - [ "tails", 1 ] - ] - })"); - - // V2 Expected Values - data["targetingKey"] = "jon@company.com"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "heads"); - - data["targetingKey"] = "jane@company.com"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "tails"); -} - -TEST_F(FlagdOpsTest, FractionalV2SharedSeed) { - json data = json::object(); - - json logic = json::parse(R"({ - "fractional": [ - { "cat": [ - "shared-seed", - { "var": "user.name" } - ]}, - [ "clubs", 25 ], - [ "diamonds", 25 ], - [ "hearts", 25 ], - [ "spades", 25 ] - ] - })"); - - // V2 Expected Values - data["user"]["name"] = "seven"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "hearts"); - - data["user"]["name"] = "eight"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "diamonds"); - - data["user"]["name"] = "nine"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "clubs"); - - data["user"]["name"] = "two"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "spades"); -} - -TEST_F(FlagdOpsTest, FractionalV2HashEdgeCases) { - json data = json::object(); - - json logic = json::parse(R"({ - "fractional": [ - { "var": "targetingKey" }, - [ "lower", 50 ], - [ "upper", 50 ] - ] - })"); - - // hash = 0 - data["targetingKey"] = "ejOoVL"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "lower"); - - // hash = 1 - data["targetingKey"] = "bY9fO-"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "lower"); - - // hash = 2147483647 (INT32_MAX) - data["targetingKey"] = "SI7p-"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "lower"); - - // hash = 2147483648 (INT32_MIN when cast to signed 32-bit - critical - // threshold) - data["targetingKey"] = "6LvT0"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "upper"); - - // hash = 4294967295 (UINT32_MAX / -1 when signed) - data["targetingKey"] = "ceQdGm"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "upper"); -} - -TEST_F(FlagdOpsTest, FractionalV2NestedIfVariantName) { - json data = json::object(); - - json logic = json::parse(R"({ - "fractional": [ - { "var": "targetingKey" }, - [ - { - "if": [ - { "==": [{ "var": "tier" }, "premium"] }, - "premium", - "standard" - ] - }, - 50 - ], - [ "standard", 50 ] - ] - })"); - - data["targetingKey"] = "jon@company.com"; - data["tier"] = "premium"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "premium"); - - data["tier"] = "basic"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "standard"); - - data["targetingKey"] = "user1"; - data["tier"] = "premium"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "standard"); - - data["tier"] = "basic"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "standard"); -} - -TEST_F(FlagdOpsTest, FractionalV2NestedVarVariantName) { - json data = json::object(); - - json logic = json::parse(R"({ - "fractional": [ - { "var": "targetingKey" }, - [ { "var": "color" }, 50 ], - [ "blue", 50 ] - ] - })"); - - data["targetingKey"] = "jon@company.com"; - - data["color"] = "red"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "red"); - - data["color"] = "green"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "green"); - - data["targetingKey"] = "user1"; - data["color"] = "red"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "blue"); - - // Fallbacks handled by outer engine, json_logic returns the literal var - // evaluations - data["targetingKey"] = "jon@company.com"; - data["color"] = "yellow"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "yellow"); - - data["color"] = ""; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), ""); -} - -TEST_F(FlagdOpsTest, FractionalV2NestedWeightLogic) { - json data = json::object(); - - json logic = json::parse(R"({ - "fractional": [ - { "var": "targetingKey" }, - [ - "red", - { - "if": [ - { "==": [{ "var": "tier" }, "premium"] }, - 100, - 0 - ] - } - ], - [ "blue", 10 ] - ] - })"); - - data["targetingKey"] = "jon@company.com"; - data["tier"] = "premium"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "red"); - - data["tier"] = "basic"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "blue"); - - data["targetingKey"] = "user1"; - data["tier"] = "premium"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "red"); - - data["tier"] = "basic"; - EXPECT_EQ(json_logic_.Apply(logic, data).value(), "blue"); -} diff --git a/providers/flagd/tests/evaluator/flagd_ops_test.cpp b/providers/flagd/tests/evaluator/flagd_ops_test.cpp deleted file mode 100644 index 2e7716d..0000000 --- a/providers/flagd/tests/evaluator/flagd_ops_test.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include "providers/flagd/src/evaluator/flagd_ops.h" - -#include - -#include - -#include "providers/flagd/src/evaluator/json_logic/json_logic.h" - -using json_logic::JsonLogic; -using nlohmann::json; - -class FlagdOpsTest : public ::testing::Test { - protected: - void SetUp() override { - json_logic_.RegisterOperation("starts_with", flagd::StartsWith); - json_logic_.RegisterOperation("ends_with", flagd::EndsWith); - json_logic_.RegisterOperation("sem_ver", flagd::SemVer); - } - - JsonLogic json_logic_; -}; - -TEST_F(FlagdOpsTest, StartsWithBasic) { - json data = json::object(); - - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"starts_with": ["hello world", "hello"]})"), - data) - .value()); - EXPECT_FALSE( - json_logic_ - .Apply(json::parse(R"({"starts_with": ["hello world", "world"]})"), - data) - .value()); -} - -TEST_F(FlagdOpsTest, StartsWithArgEvaluation) { - json data = json::object(); - - data["prefix"] = "hello"; - EXPECT_TRUE( - json_logic_ - .Apply(json::parse( - R"({"starts_with": ["hello world", {"var": "prefix"}]})"), - data) - .value()); -} - -TEST_F(FlagdOpsTest, StartsWithNegative) { - json data = json::object(); - - EXPECT_FALSE( - json_logic_.Apply(json::parse(R"({"starts_with": ["a", "abc"]})"), data) - .value()); - EXPECT_FALSE( - json_logic_.Apply(json::parse(R"({"starts_with": ["", "a"]})"), data) - .value()); -} - -TEST_F(FlagdOpsTest, EndsWithBasic) { - json data = json::object(); - - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"ends_with": ["hello world", "world"]})"), - data) - .value()); - EXPECT_FALSE( - json_logic_ - .Apply(json::parse(R"({"ends_with": ["hello world", "hello"]})"), - data) - .value()); -} - -TEST_F(FlagdOpsTest, EndsWithArgEvaluation) { - json data = json::object(); - - data["suffix"] = "world"; - EXPECT_TRUE( - json_logic_ - .Apply(json::parse( - R"({"ends_with": ["hello world", {"var": "suffix"}]})"), - data) - .value()); -} - -TEST_F(FlagdOpsTest, EndsWithNegative) { - json data = json::object(); - - EXPECT_FALSE( - json_logic_.Apply(json::parse(R"({"ends_with": ["a", "abc"]})"), data) - .value()); - EXPECT_FALSE( - json_logic_.Apply(json::parse(R"({"ends_with": ["", "a"]})"), data) - .value()); -} - -TEST_F(FlagdOpsTest, SemVerBasic) { - json data = json::object(); - - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.2.3", "=", "1.2.3"]})"), data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.2.3", "!=", "1.2.4"]})"), data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["2.0.0", ">", "1.9.9"]})"), data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.0.0", "<", "2.0.0"]})"), data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.2.3", ">=", "1.2.3"]})"), data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.2.3", "<=", "1.2.3"]})"), data) - .value()); -} - -TEST_F(FlagdOpsTest, SemVerPreRelease) { - json data = json::object(); - - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.0.0-alpha", "<", "1.0.0"]})"), - data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse( - R"({"sem_ver": ["1.0.0-alpha", "<", "1.0.0-alpha.1"]})"), - data) - .value()); -} - -TEST_F(FlagdOpsTest, SemVerBuildMetadata) { - json data = json::object(); - - EXPECT_TRUE( - json_logic_ - .Apply(json::parse( - R"({"sem_ver": ["1.0.0+build.1", "=", "1.0.0+build.2"]})"), - data) - .value()); -} - -TEST_F(FlagdOpsTest, SemVerPrefix) { - json data = json::object(); - - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["v1.2.3", "=", "1.2.3"]})"), data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.2.3", "=", "V1.2.3"]})"), data) - .value()); -} - -TEST_F(FlagdOpsTest, SemVerCompatibleOperators) { - json data = json::object(); - - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.2.3", "^", "1.0.0"]})"), data) - .value()); - EXPECT_FALSE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["2.0.0", "^", "1.0.0"]})"), data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.2.3", "~", "1.2.0"]})"), data) - .value()); - EXPECT_FALSE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["1.3.0", "~", "1.2.0"]})"), data) - .value()); -} - -TEST_F(FlagdOpsTest, SemVerPartialVersion) { - json data = json::object(); - - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["v1.2", "=", "1.2.0"]})"), data) - .value()); - EXPECT_TRUE( - json_logic_ - .Apply(json::parse(R"({"sem_ver": ["v1", "=", "1.0.0"]})"), data) - .value()); -} diff --git a/providers/flagd/tests/evaluator/json_logic/BUILD b/providers/flagd/tests/evaluator/json_logic/BUILD deleted file mode 100644 index d8d35bd..0000000 --- a/providers/flagd/tests/evaluator/json_logic/BUILD +++ /dev/null @@ -1,36 +0,0 @@ -load("@rules_cc//cc:defs.bzl", "cc_test") - -cc_test( - name = "json_logic_test", - srcs = ["json_logic_test.cpp"], - deps = [ - "//providers/flagd/src/evaluator/json_logic", - "@googletest//:gtest_main", - "@nlohmann_json//:json", - ], -) - -cc_test( - name = "json_logic_official_test", - srcs = ["json_logic_official_test.cpp"], - data = ["tests.json"], - # TODO(#77): This tag disables those tests from github action check - # We should remove it once JSONLogic is fully implemented - tags = ["manual"], - deps = [ - "//providers/flagd/src/evaluator/json_logic", - "@googletest//:gtest_main", - "@nlohmann_json//:json", - ], -) - -cc_test( - name = "json_logic_annotated_test", - srcs = ["json_logic_annotated_test.cpp"], - data = ["tests_annotated.json"], - deps = [ - "//providers/flagd/src/evaluator/json_logic", - "@googletest//:gtest_main", - "@nlohmann_json//:json", - ], -) diff --git a/providers/flagd/tests/evaluator/json_logic/check_progress.py b/providers/flagd/tests/evaluator/json_logic/check_progress.py deleted file mode 100755 index af3c91c..0000000 --- a/providers/flagd/tests/evaluator/json_logic/check_progress.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -""" -Test Progress Reporter for JsonLogic Evaluator. - -This script executes the Bazel test suites for JsonLogic (both official and annotated) -and parses the output to provide a concise success rate report. - -Usage: - python3 check_progress.py - -Requirements: - - gbazelisk must be in the PATH. - - Must be run from a directory where gbazelisk can find the workspace. -""" -import subprocess -import re - - -def run_tests(test_type: str): - cmd = [ - "gbazelisk", - "test", - f"//providers/flagd/tests/evaluator/json_logic:json_logic_{test_type}_test", - "--test_output=all", - "--cache_test_results=no", - "--keep_going", - ] - - result = subprocess.run( - cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True - ) - - # Return code: 0 - all tests passed, 3 - execution passed but not all testcases - if result.returncode != 0 and result.returncode != 3: - raise Exception("gbazelisk command failed to run:\n" + result.stdout) - - return result.stdout - - -def parse_output(output): - # Look for [ PASSED ] X tests - # Look for [ FAILED ] Y tests - passed_pattern = r"(?:\[\s+PASSED\s+\])\s+(\d+)\s+tests" - failed_pattern = r"(?:\[\s+FAILED\s+\])\s+(\d+)\s+tests" - - passed_match = re.search(passed_pattern, output) - failed_match = re.search(failed_pattern, output) - - passed = int(passed_match.group(1)) if passed_match else 0 - failed = int(failed_match.group(1)) if failed_match else 0 - - return passed, failed - - -def main(test_type: str): - print("\n" + f"Running {test_type} tests...") - output = run_tests(test_type) - - passed, failed = parse_output(output) - total = passed + failed - - print("=" * 40) - print("Test Progress Report") - print("=" * 40) - print(f"Total Tests: {total}") - print(f"Passing: {passed}") - print(f"Failing: {failed}") - - if total > 0: - percent = (passed / total) * 100 - print(f"Success Rate: {percent:.1f}%") - else: - print("No tests found or execution failed completely.") - - print("=" * 40) - - -if __name__ == "__main__": - main("official") - main("annotated") diff --git a/providers/flagd/tests/evaluator/json_logic/generate_tests_annotations.py b/providers/flagd/tests/evaluator/json_logic/generate_tests_annotations.py deleted file mode 100755 index 49f6496..0000000 --- a/providers/flagd/tests/evaluator/json_logic/generate_tests_annotations.py +++ /dev/null @@ -1,301 +0,0 @@ -#!/usr/bin/env python3 -""" -JsonLogic Test Annotator and Filter. - -This script processes the official JsonLogic 'tests.json' file to: -1. Filter out tests that use unsupported or "non-strict" operators (e.g., '==', 'log'). -2. Apply strict type checking rules (e.g., numeric operators must have numeric arguments). -3. Group the remaining valid tests by category and operator. -4. Output the results to 'tests_annotated.json'. - -Usage: - python3 generate_tests_annotations.py - -The script automatically locates 'tests.json' relative to its own location. -""" -import json -import os - -# Operators configuration -STRICT_OPS = { - "===": "Equality", - "!==": "Equality", - "==": "Equality", - "!=": "Equality", - ">": "Numeric", - ">=": "Numeric", - "<": "Numeric", - "<=": "Numeric", - "+": "Numeric", - "-": "Numeric", - "*": "Numeric", - "/": "Numeric", - "%": "Numeric", - "min": "Numeric", - "max": "Numeric", - "!": "Logic", - "!!": "Logic", - "or": "Logic", - "and": "Logic", - "?:": "Logic", - "if": "Logic", - "cat": "String", - "substr": "String", - "in": "String/Array", - "merge": "Array", - "var": "Data", - "missing": "Data", - "missing_some": "Data", -} - -IGNORED_OPS = { - "map", - "reduce", - "filter", - "all", - "some", - "none", - "log", -} - - -def is_literal(x): - return isinstance(x, (int, float, str, bool, type(None))) - - -def get_operator(logic): - if isinstance(logic, dict) and len(logic) > 0: - return list(logic.keys())[0] - return None - - -def get_args(logic, op): - args = logic[op] - if not isinstance(args, list): - return [args] - return args - - -def resolve_var(data, var_name): - if var_name == "" or var_name is None: - return data - if not isinstance(data, (dict, list)): - return None - - parts = str(var_name).split(".") - curr = data - for p in parts: - if isinstance(curr, dict) and p in curr: - curr = curr[p] - elif isinstance(curr, list) and p.isdigit(): - idx = int(p) - if 0 <= idx < len(curr): - curr = curr[idx] - else: - return None - else: - return None - return curr - - -def recursive_scan(node, data): - """ - Scans the logic tree recursively. - Returns exclusion_reason (str) or None. - """ - if not isinstance(node, dict) or not node: - return None - - op = get_operator(node) - if not op: - return None - - if op in IGNORED_OPS: - return f"Uses ignored operator '{op}'" - - if op not in STRICT_OPS: - return f"Uses unknown operator '{op}'" - - args = get_args(node, op) - - # Resolve simple vars for type checking - resolved_args = [] - for arg in args: - if isinstance(arg, dict) and get_operator(arg) == "var": - v_args = get_args(arg, "var") - if isinstance(v_args, list) and len(v_args) > 0: - v_name = v_args[0] - else: - v_name = v_args - - if isinstance(v_name, str): - val = resolve_var(data, v_name) - if val is None and isinstance(v_args, list) and len(v_args) > 1: - val = v_args[1] - resolved_args.append(val) - else: - resolved_args.append(arg) # Can't resolve or invalid key type - else: - resolved_args.append(arg) - - # --- Strict Check for Current Operator --- - - # Numeric Strictness - if op in ["+", "-", "*", "/", "%", "min", "max", "<", "<=", ">", ">="]: - for arg in resolved_args: - if ( - is_literal(arg) - and not isinstance(arg, (int, float)) - and arg is not None - ): - return f"Operator '{op}' has non-numeric literal argument: {arg} (type: {type(arg).__name__})" - if arg is None: - return f"Operator '{op}' has null argument" - - # String Strictness (cat) - if op == "cat": - for arg in resolved_args: - if is_literal(arg) and not isinstance(arg, str): - return f"Operator 'cat' has non-string literal argument: {arg}" - - # Substr strictness - if op == "substr": - if len(resolved_args) > 0: - if is_literal(resolved_args[0]) and not isinstance(resolved_args[0], str): - return f"Operator 'substr' first argument must be string, got {resolved_args[0]}" - if len(resolved_args) > 1: - if is_literal(resolved_args[1]) and not isinstance(resolved_args[1], int): - return f"Operator 'substr' second argument must be integer, got {resolved_args[1]}" - if len(resolved_args) > 2: - if is_literal(resolved_args[2]) and not isinstance(resolved_args[2], int): - return f"Operator 'substr' third argument must be integer, got {resolved_args[2]}" - - # Logic Strictness (if/?:/and/or/!/!!) - if op in ["if", "?:", "and", "or", "!", "!!"]: - pass - - # Equality strictness - if op in ["==", "!="]: - if len(resolved_args) == 2: - left, right = resolved_args[0], resolved_args[1] - if is_literal(left) and is_literal(right): - if type(left) is not type(right): - # Allow int/float cross-comparison as both are numeric - if not ( - isinstance(left, (int, float)) - and isinstance(right, (int, float)) - ): - return f"Operator '{op}' has mismatched types: {type(left).__name__} and {type(right).__name__}" - - # In strictness - if op == "in": - if len(resolved_args) == 2: - sub = resolved_args[0] - container = resolved_args[1] - if is_literal(container) and isinstance(container, str): - if is_literal(sub) and not isinstance(sub, str): - return f"Operator 'in' (string) has non-string member: {sub}" - - # Var strictness - if op == "var": - v_args = get_args(node, "var") - v_name = v_args[0] if isinstance(v_args, list) and len(v_args) > 0 else v_args - if ( - is_literal(v_name) - and not isinstance(v_name, str) - and v_name != "" - and not v_name == None - ): - return f"Operator 'var' has non-string key: {v_name} (type: {type(v_name).__name__})" - - # --- Recursive Step --- - for arg in args: - if isinstance(arg, dict): - reason = recursive_scan(arg, data) - if reason: - return reason - - return None - - -def main(): - script_dir = os.path.dirname(os.path.abspath(__file__)) - path = os.path.join(script_dir, "tests.json") - if not os.path.exists(path): - print(f"File not found: {path}") - return - - with open(path, "r") as f: - tests = json.load(f) - - valid_tests = [t for t in tests if isinstance(t, list) and len(t) >= 3] - - grouped_tests = {} - skipped_details = {} - - for t in valid_tests: - logic = t[0] - data = t[1] - expected = t[2] - - main_op = get_operator(logic) - - reason = recursive_scan(logic, data) - if not reason: - # Check expected value for boolean operators - if main_op in [ - "!", - "!!", - "<", - "<=", - ">", - ">=", - "===", - "!==", - "==", - "!=", - ]: - if not isinstance(expected, bool): - reason = f"Operator '{main_op}' expected to return non-boolean: {expected}" - - if reason: - if reason not in skipped_details: - skipped_details[reason] = [] - skipped_details[reason].append(t) - continue - - if not main_op: - main_op = "primitive" - - category = STRICT_OPS.get(main_op, "Unknown") - group_key = f"{category}/{main_op}" - if group_key not in grouped_tests: - grouped_tests[group_key] = [] - grouped_tests[group_key].append(t) - - # Generate config - output_path = os.path.join(script_dir, "tests_annotated.json") - - # We add skipped details to the JSON root but as an object (not array) - # so the C++ test loader (which expects arrays for groups) will skip it. - final_output = grouped_tests.copy() - final_output["__skipped__"] = skipped_details - - with open(output_path, "w") as f: - json.dump(final_output, f, indent=2) - - print(f"Generated {output_path}.") - print(f"Included {sum(len(v) for v in grouped_tests.values())} tests.") - print( - f"Skipped {len(valid_tests) - sum(len(v) for v in grouped_tests.values())} tests." - ) - - print("\nSkipped Reasons Summary:") - for r, tests_list in sorted(skipped_details.items(), key=lambda x: -len(x[1])): - print(f"\n- {len(tests_list)} tests: {r}") - for st in tests_list: - print(f" Logic: {json.dumps(st[0])} Expected: {json.dumps(st[2])}") - - -if __name__ == "__main__": - main() diff --git a/providers/flagd/tests/evaluator/json_logic/json_logic_annotated_test.cpp b/providers/flagd/tests/evaluator/json_logic/json_logic_annotated_test.cpp deleted file mode 100644 index 5da2793..0000000 --- a/providers/flagd/tests/evaluator/json_logic/json_logic_annotated_test.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include - -#include -#include -#include -#include - -#include "providers/flagd/src/evaluator/json_logic/json_logic.h" - -using json_logic::JsonLogic; -using nlohmann::json; - -class JsonLogicTest : public ::testing::TestWithParam { - protected: - JsonLogic json_logic_; -}; - -TEST_P(JsonLogicTest, RunCase) { - const json& test_case = GetParam(); - - ASSERT_TRUE(test_case.is_array()); - ASSERT_GE(test_case.size(), 3); - - const json& rule = test_case[0]; - const json& data = test_case[1]; - const json& expected = test_case[2]; - - std::cerr << "Running: " << rule << " on Data: " << data << "\n"; - - absl::StatusOr result = json_logic_.Apply(rule, data); - - json result_json = result.ok() ? *result : nullptr; - - EXPECT_EQ(result_json, expected) - << "Rule: " << rule << "\nData: " << data << "\nExpected: " << expected - << "\nGot: " - << (result.ok() ? result->dump() : result.status().ToString()); -} - -std::vector LoadTests() { - // This file is generated from ./generate_tests_annotations.py - std::string file_path = - "providers/flagd/tests/evaluator/json_logic/tests_annotated.json"; - std::ifstream test_file(file_path); - if (!test_file.is_open()) { - std::cerr << "Could not open " << file_path << "\n"; - return {}; - } - json tests_json; - test_file >> tests_json; - std::vector valid_cases; - if (tests_json.is_object()) { - for (const auto& [key, cases] : tests_json.items()) { - if (cases.is_array()) { - for (const auto& element : cases) { - if (element.is_array() && element.size() >= 3) { - valid_cases.push_back(element); - } - } - } - } - } - return valid_cases; -} - -INSTANTIATE_TEST_SUITE_P(JsonLogicSuite, JsonLogicTest, - ::testing::ValuesIn(LoadTests())); diff --git a/providers/flagd/tests/evaluator/json_logic/json_logic_official_test.cpp b/providers/flagd/tests/evaluator/json_logic/json_logic_official_test.cpp deleted file mode 100644 index 662f3f2..0000000 --- a/providers/flagd/tests/evaluator/json_logic/json_logic_official_test.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include - -#include -#include -#include -#include - -#include "providers/flagd/src/evaluator/json_logic/json_logic.h" - -using json_logic::JsonLogic; -using nlohmann::json; - -class JsonLogicTest : public ::testing::TestWithParam { - protected: - JsonLogic json_logic_; -}; - -TEST_P(JsonLogicTest, RunCase) { - const json& test_case = GetParam(); - - ASSERT_TRUE(test_case.is_array()); - ASSERT_GE(test_case.size(), 3); - - const json& rule = test_case[0]; - const json& data = test_case[1]; - const json& expected = test_case[2]; - - std::cerr << "Running: " << rule << " on Data: " << data << "\n"; - - absl::StatusOr result = json_logic_.Apply(rule, data); - - json result_json = result.ok() ? *result : nullptr; - - EXPECT_EQ(result_json, expected) - << "Rule: " << rule << "\nData: " << data << "\nExpected: " << expected - << "\nGot: " - << (result.ok() ? result->dump() : result.status().ToString()); -} - -std::vector LoadTests() { - // This file is downloaded from: https://jsonlogic.com/tests.json - std::string file_path = - "providers/flagd/tests/evaluator/json_logic/tests.json"; - std::ifstream test_file(file_path); - if (!test_file.is_open()) { - // Fallback or error. In Bazel, runfiles might be needed, but relative path - // often works if CWD is correct. Let's try to print error. - std::cerr << "Could not open " << file_path << "\n"; - return {}; - } - json tests_json; - test_file >> tests_json; - std::vector valid_cases; - for (const auto& element : tests_json) { - if (element.is_array() && element.size() >= 3) { - valid_cases.push_back(element); - } - } - return valid_cases; -} - -INSTANTIATE_TEST_SUITE_P(JsonLogicSuite, JsonLogicTest, - ::testing::ValuesIn(LoadTests())); diff --git a/providers/flagd/tests/evaluator/json_logic/json_logic_test.cpp b/providers/flagd/tests/evaluator/json_logic/json_logic_test.cpp deleted file mode 100644 index 54b4e0c..0000000 --- a/providers/flagd/tests/evaluator/json_logic/json_logic_test.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "providers/flagd/src/evaluator/json_logic/json_logic.h" - -#include - -#include - -using json_logic::JsonLogic; -using nlohmann::json; - -class JsonLogicTest : public ::testing::Test { - protected: - JsonLogic json_logic_; -}; - -TEST_F(JsonLogicTest, ApplyPrimitives) { - json data = json::object(); - - EXPECT_EQ(json_logic_.Apply(json(true), data).value(), true); - EXPECT_EQ(json_logic_.Apply(json(false), data).value(), false); - EXPECT_EQ(json_logic_.Apply(json(123), data).value(), 123); - EXPECT_EQ(json_logic_.Apply(json(12.34), data).value(), 12.34); - EXPECT_EQ(json_logic_.Apply(json("string"), data).value(), "string"); - EXPECT_EQ(json_logic_.Apply(json(nullptr), data).value(), nullptr); -} - -TEST_F(JsonLogicTest, ApplyArray) { - json data = json::object(); - json logic = json::array({1, "two", true}); - - auto result_or = json_logic_.Apply(logic, data); - ASSERT_TRUE(result_or.ok()); - const json& result = result_or.value(); - - EXPECT_TRUE(result.is_array()); - EXPECT_EQ(result.size(), 3); - EXPECT_EQ(result[0], 1); - EXPECT_EQ(result[1], "two"); - EXPECT_EQ(result[2], true); -} - -TEST_F(JsonLogicTest, ApplyEmptyObject) { - json data = json::object(); - json logic = json::object(); - - EXPECT_EQ(json_logic_.Apply(logic, data).value(), logic); -} - -TEST_F(JsonLogicTest, ApplyUnknownOperator) { - json data = json::object(); - json logic = json::parse(R"({"unknown_op": [1, 2]})"); - - EXPECT_EQ(json_logic_.Apply(logic, data).value(), logic); -} - -TEST_F(JsonLogicTest, CustomOperation) { - json_logic_.RegisterOperation( - "custom", - [](const JsonLogic&, const json& args, - const json&) -> absl::StatusOr { return "custom_result"; }); - - json logic = json::parse(R"({"custom": []})"); - EXPECT_EQ(json_logic_.Apply(logic, {}).value(), "custom_result"); -} diff --git a/providers/flagd/tests/evaluator/json_logic/tests.json b/providers/flagd/tests/evaluator/json_logic/tests.json deleted file mode 100644 index 6ec9037..0000000 --- a/providers/flagd/tests/evaluator/json_logic/tests.json +++ /dev/null @@ -1,3787 +0,0 @@ -[ - "# Non-rules get passed through", - [ - true, - {}, - true - ], - [ - false, - {}, - false - ], - [ - 17, - {}, - 17 - ], - [ - 3.14, - {}, - 3.14 - ], - [ - "apple", - {}, - "apple" - ], - [ - null, - {}, - null - ], - [ - [ - "a", - "b" - ], - {}, - [ - "a", - "b" - ] - ], - "# Single operator tests", - [ - { - "==": [ - 1, - 1 - ] - }, - {}, - true - ], - [ - { - "==": [ - 1, - "1" - ] - }, - {}, - true - ], - [ - { - "==": [ - 1, - 2 - ] - }, - {}, - false - ], - [ - { - "===": [ - 1, - 1 - ] - }, - {}, - true - ], - [ - { - "===": [ - 1, - "1" - ] - }, - {}, - false - ], - [ - { - "===": [ - 1, - 2 - ] - }, - {}, - false - ], - [ - { - "!=": [ - 1, - 2 - ] - }, - {}, - true - ], - [ - { - "!=": [ - 1, - 1 - ] - }, - {}, - false - ], - [ - { - "!=": [ - 1, - "1" - ] - }, - {}, - false - ], - [ - { - "!==": [ - 1, - 2 - ] - }, - {}, - true - ], - [ - { - "!==": [ - 1, - 1 - ] - }, - {}, - false - ], - [ - { - "!==": [ - 1, - "1" - ] - }, - {}, - true - ], - [ - { - ">": [ - 2, - 1 - ] - }, - {}, - true - ], - [ - { - ">": [ - 1, - 1 - ] - }, - {}, - false - ], - [ - { - ">": [ - 1, - 2 - ] - }, - {}, - false - ], - [ - { - ">": [ - "2", - 1 - ] - }, - {}, - true - ], - [ - { - ">=": [ - 2, - 1 - ] - }, - {}, - true - ], - [ - { - ">=": [ - 1, - 1 - ] - }, - {}, - true - ], - [ - { - ">=": [ - 1, - 2 - ] - }, - {}, - false - ], - [ - { - ">=": [ - "2", - 1 - ] - }, - {}, - true - ], - [ - { - "<": [ - 2, - 1 - ] - }, - {}, - false - ], - [ - { - "<": [ - 1, - 1 - ] - }, - {}, - false - ], - [ - { - "<": [ - 1, - 2 - ] - }, - {}, - true - ], - [ - { - "<": [ - "1", - 2 - ] - }, - {}, - true - ], - [ - { - "<": [ - 1, - 2, - 3 - ] - }, - {}, - true - ], - [ - { - "<": [ - 1, - 1, - 3 - ] - }, - {}, - false - ], - [ - { - "<": [ - 1, - 4, - 3 - ] - }, - {}, - false - ], - [ - { - "<=": [ - 2, - 1 - ] - }, - {}, - false - ], - [ - { - "<=": [ - 1, - 1 - ] - }, - {}, - true - ], - [ - { - "<=": [ - 1, - 2 - ] - }, - {}, - true - ], - [ - { - "<=": [ - "1", - 2 - ] - }, - {}, - true - ], - [ - { - "<=": [ - 1, - 2, - 3 - ] - }, - {}, - true - ], - [ - { - "<=": [ - 1, - 4, - 3 - ] - }, - {}, - false - ], - [ - { - "!": [ - false - ] - }, - {}, - true - ], - [ - { - "!": false - }, - {}, - true - ], - [ - { - "!": [ - true - ] - }, - {}, - false - ], - [ - { - "!": true - }, - {}, - false - ], - [ - { - "!": 0 - }, - {}, - true - ], - [ - { - "!": 1 - }, - {}, - false - ], - [ - { - "or": [ - true, - true - ] - }, - {}, - true - ], - [ - { - "or": [ - false, - true - ] - }, - {}, - true - ], - [ - { - "or": [ - true, - false - ] - }, - {}, - true - ], - [ - { - "or": [ - false, - false - ] - }, - {}, - false - ], - [ - { - "or": [ - false, - false, - true - ] - }, - {}, - true - ], - [ - { - "or": [ - false, - false, - false - ] - }, - {}, - false - ], - [ - { - "or": [ - false - ] - }, - {}, - false - ], - [ - { - "or": [ - true - ] - }, - {}, - true - ], - [ - { - "or": [ - 1, - 3 - ] - }, - {}, - 1 - ], - [ - { - "or": [ - 3, - false - ] - }, - {}, - 3 - ], - [ - { - "or": [ - false, - 3 - ] - }, - {}, - 3 - ], - [ - { - "and": [ - true, - true - ] - }, - {}, - true - ], - [ - { - "and": [ - false, - true - ] - }, - {}, - false - ], - [ - { - "and": [ - true, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - false, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - true, - true, - true - ] - }, - {}, - true - ], - [ - { - "and": [ - true, - true, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - false - ] - }, - {}, - false - ], - [ - { - "and": [ - true - ] - }, - {}, - true - ], - [ - { - "and": [ - 1, - 3 - ] - }, - {}, - 3 - ], - [ - { - "and": [ - 3, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - false, - 3 - ] - }, - {}, - false - ], - [ - { - "?:": [ - true, - 1, - 2 - ] - }, - {}, - 1 - ], - [ - { - "?:": [ - false, - 1, - 2 - ] - }, - {}, - 2 - ], - [ - { - "in": [ - "Bart", - [ - "Bart", - "Homer", - "Lisa", - "Marge", - "Maggie" - ] - ] - }, - {}, - true - ], - [ - { - "in": [ - "Milhouse", - [ - "Bart", - "Homer", - "Lisa", - "Marge", - "Maggie" - ] - ] - }, - {}, - false - ], - [ - { - "in": [ - "Spring", - "Springfield" - ] - }, - {}, - true - ], - [ - { - "in": [ - "i", - "team" - ] - }, - {}, - false - ], - [ - { - "cat": "ice" - }, - {}, - "ice" - ], - [ - { - "cat": [ - "ice" - ] - }, - {}, - "ice" - ], - [ - { - "cat": [ - "ice", - "cream" - ] - }, - {}, - "icecream" - ], - [ - { - "cat": [ - 1, - 2 - ] - }, - {}, - "12" - ], - [ - { - "cat": [ - "Robocop", - 2 - ] - }, - {}, - "Robocop2" - ], - [ - { - "cat": [ - "we all scream for ", - "ice", - "cream" - ] - }, - {}, - "we all scream for icecream" - ], - [ - { - "%": [ - 1, - 2 - ] - }, - {}, - 1 - ], - [ - { - "%": [ - 2, - 2 - ] - }, - {}, - 0 - ], - [ - { - "%": [ - 3, - 2 - ] - }, - {}, - 1 - ], - [ - { - "max": [ - 1, - 2, - 3 - ] - }, - {}, - 3 - ], - [ - { - "max": [ - 1, - 3, - 3 - ] - }, - {}, - 3 - ], - [ - { - "max": [ - 3, - 2, - 1 - ] - }, - {}, - 3 - ], - [ - { - "max": [ - 1 - ] - }, - {}, - 1 - ], - [ - { - "min": [ - 1, - 2, - 3 - ] - }, - {}, - 1 - ], - [ - { - "min": [ - 1, - 1, - 3 - ] - }, - {}, - 1 - ], - [ - { - "min": [ - 3, - 2, - 1 - ] - }, - {}, - 1 - ], - [ - { - "min": [ - 1 - ] - }, - {}, - 1 - ], - [ - { - "+": [ - 1, - 2 - ] - }, - {}, - 3 - ], - [ - { - "+": [ - 2, - 2, - 2 - ] - }, - {}, - 6 - ], - [ - { - "+": [ - 1 - ] - }, - {}, - 1 - ], - [ - { - "+": [ - "1", - 1 - ] - }, - {}, - 2 - ], - [ - { - "*": [ - 3, - 2 - ] - }, - {}, - 6 - ], - [ - { - "*": [ - 2, - 2, - 2 - ] - }, - {}, - 8 - ], - [ - { - "*": [ - 1 - ] - }, - {}, - 1 - ], - [ - { - "*": [ - "1", - 1 - ] - }, - {}, - 1 - ], - [ - { - "-": [ - 2, - 3 - ] - }, - {}, - -1 - ], - [ - { - "-": [ - 3, - 2 - ] - }, - {}, - 1 - ], - [ - { - "-": [ - 3 - ] - }, - {}, - -3 - ], - [ - { - "-": [ - "1", - 1 - ] - }, - {}, - 0 - ], - [ - { - "/": [ - 4, - 2 - ] - }, - {}, - 2 - ], - [ - { - "/": [ - 2, - 4 - ] - }, - {}, - 0.5 - ], - [ - { - "/": [ - "1", - 1 - ] - }, - {}, - 1 - ], - "Substring", - [ - { - "substr": [ - "jsonlogic", - 4 - ] - }, - null, - "logic" - ], - [ - { - "substr": [ - "jsonlogic", - -5 - ] - }, - null, - "logic" - ], - [ - { - "substr": [ - "jsonlogic", - 0, - 1 - ] - }, - null, - "j" - ], - [ - { - "substr": [ - "jsonlogic", - -1, - 1 - ] - }, - null, - "c" - ], - [ - { - "substr": [ - "jsonlogic", - 4, - 5 - ] - }, - null, - "logic" - ], - [ - { - "substr": [ - "jsonlogic", - -5, - 5 - ] - }, - null, - "logic" - ], - [ - { - "substr": [ - "jsonlogic", - -5, - -2 - ] - }, - null, - "log" - ], - [ - { - "substr": [ - "jsonlogic", - 1, - -5 - ] - }, - null, - "son" - ], - "Merge arrays", - [ - { - "merge": [] - }, - null, - [] - ], - [ - { - "merge": [ - [ - 1 - ] - ] - }, - null, - [ - 1 - ] - ], - [ - { - "merge": [ - [ - 1 - ], - [] - ] - }, - null, - [ - 1 - ] - ], - [ - { - "merge": [ - [ - 1 - ], - [ - 2 - ] - ] - }, - null, - [ - 1, - 2 - ] - ], - [ - { - "merge": [ - [ - 1 - ], - [ - 2 - ], - [ - 3 - ] - ] - }, - null, - [ - 1, - 2, - 3 - ] - ], - [ - { - "merge": [ - [ - 1, - 2 - ], - [ - 3 - ] - ] - }, - null, - [ - 1, - 2, - 3 - ] - ], - [ - { - "merge": [ - [ - 1 - ], - [ - 2, - 3 - ] - ] - }, - null, - [ - 1, - 2, - 3 - ] - ], - "Given non-array arguments, merge converts them to arrays", - [ - { - "merge": 1 - }, - null, - [ - 1 - ] - ], - [ - { - "merge": [ - 1, - 2 - ] - }, - null, - [ - 1, - 2 - ] - ], - [ - { - "merge": [ - 1, - [ - 2 - ] - ] - }, - null, - [ - 1, - 2 - ] - ], - "Too few args", - [ - { - "if": [] - }, - null, - null - ], - [ - { - "if": [ - true - ] - }, - null, - true - ], - [ - { - "if": [ - false - ] - }, - null, - false - ], - [ - { - "if": [ - "apple" - ] - }, - null, - "apple" - ], - "Simple if/then/else cases", - [ - { - "if": [ - true, - "apple" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - "apple" - ] - }, - null, - null - ], - [ - { - "if": [ - true, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - "apple", - "banana" - ] - }, - null, - "banana" - ], - "Empty arrays are falsey", - [ - { - "if": [ - [], - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - [ - 1 - ], - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - [ - 1, - 2, - 3, - 4 - ], - "apple", - "banana" - ] - }, - null, - "apple" - ], - "Empty strings are falsey, all other strings are truthy", - [ - { - "if": [ - "", - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - "zucchini", - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - "0", - "apple", - "banana" - ] - }, - null, - "apple" - ], - "You can cast a string to numeric with a unary + ", - [ - { - "===": [ - 0, - "0" - ] - }, - null, - false - ], - [ - { - "===": [ - 0, - { - "+": "0" - } - ] - }, - null, - true - ], - [ - { - "if": [ - { - "+": "0" - }, - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - { - "+": "1" - }, - "apple", - "banana" - ] - }, - null, - "apple" - ], - "Zero is falsy, all other numbers are truthy", - [ - { - "if": [ - 0, - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - 1, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - 3.1416, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - -1, - "apple", - "banana" - ] - }, - null, - "apple" - ], - "Truthy and falsy definitions matter in Boolean operations", - [ - { - "!": [ - [] - ] - }, - {}, - true - ], - [ - { - "!!": [ - [] - ] - }, - {}, - false - ], - [ - { - "and": [ - [], - true - ] - }, - {}, - [] - ], - [ - { - "or": [ - [], - true - ] - }, - {}, - true - ], - [ - { - "!": [ - 0 - ] - }, - {}, - true - ], - [ - { - "!!": [ - 0 - ] - }, - {}, - false - ], - [ - { - "and": [ - 0, - true - ] - }, - {}, - 0 - ], - [ - { - "or": [ - 0, - true - ] - }, - {}, - true - ], - [ - { - "!": [ - "" - ] - }, - {}, - true - ], - [ - { - "!!": [ - "" - ] - }, - {}, - false - ], - [ - { - "and": [ - "", - true - ] - }, - {}, - "" - ], - [ - { - "or": [ - "", - true - ] - }, - {}, - true - ], - [ - { - "!": [ - "0" - ] - }, - {}, - false - ], - [ - { - "!!": [ - "0" - ] - }, - {}, - true - ], - [ - { - "and": [ - "0", - true - ] - }, - {}, - true - ], - [ - { - "or": [ - "0", - true - ] - }, - {}, - "0" - ], - "If the conditional is logic, it gets evaluated", - [ - { - "if": [ - { - ">": [ - 2, - 1 - ] - }, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - { - ">": [ - 1, - 2 - ] - }, - "apple", - "banana" - ] - }, - null, - "banana" - ], - "If the consequents are logic, they get evaluated", - [ - { - "if": [ - true, - { - "cat": [ - "ap", - "ple" - ] - }, - { - "cat": [ - "ba", - "na", - "na" - ] - } - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - { - "cat": [ - "ap", - "ple" - ] - }, - { - "cat": [ - "ba", - "na", - "na" - ] - } - ] - }, - null, - "banana" - ], - "If/then/elseif/then cases", - [ - { - "if": [ - true, - "apple", - true, - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - false, - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - "apple", - true, - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - false, - "apple", - false, - "banana" - ] - }, - null, - null - ], - [ - { - "if": [ - true, - "apple", - true, - "banana", - "carrot" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - false, - "banana", - "carrot" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - "apple", - true, - "banana", - "carrot" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - false, - "apple", - false, - "banana", - "carrot" - ] - }, - null, - "carrot" - ], - [ - { - "if": [ - false, - "apple", - false, - "banana", - false, - "carrot" - ] - }, - null, - null - ], - [ - { - "if": [ - false, - "apple", - false, - "banana", - false, - "carrot", - "date" - ] - }, - null, - "date" - ], - [ - { - "if": [ - false, - "apple", - false, - "banana", - true, - "carrot", - "date" - ] - }, - null, - "carrot" - ], - [ - { - "if": [ - false, - "apple", - true, - "banana", - false, - "carrot", - "date" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - false, - "apple", - true, - "banana", - true, - "carrot", - "date" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - true, - "apple", - false, - "banana", - false, - "carrot", - "date" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - false, - "banana", - true, - "carrot", - "date" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - true, - "banana", - false, - "carrot", - "date" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - true, - "banana", - true, - "carrot", - "date" - ] - }, - null, - "apple" - ], - "Arrays with logic", - [ - [ - 1, - { - "var": "x" - }, - 3 - ], - { - "x": 2 - }, - [ - 1, - 2, - 3 - ] - ], - [ - { - "if": [ - { - "var": "x" - }, - [ - { - "var": "y" - } - ], - 99 - ] - }, - { - "x": true, - "y": 42 - }, - [ - 42 - ] - ], - "# Compound Tests", - [ - { - "and": [ - { - ">": [ - 3, - 1 - ] - }, - true - ] - }, - {}, - true - ], - [ - { - "and": [ - { - ">": [ - 3, - 1 - ] - }, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - { - ">": [ - 3, - 1 - ] - }, - { - "!": true - } - ] - }, - {}, - false - ], - [ - { - "and": [ - { - ">": [ - 3, - 1 - ] - }, - { - "<": [ - 1, - 3 - ] - } - ] - }, - {}, - true - ], - [ - { - "?:": [ - { - ">": [ - 3, - 1 - ] - }, - "visible", - "hidden" - ] - }, - {}, - "visible" - ], - "# Data-Driven", - [ - { - "var": [ - "a" - ] - }, - { - "a": 1 - }, - 1 - ], - [ - { - "var": [ - "b" - ] - }, - { - "a": 1 - }, - null - ], - [ - { - "var": [ - "a" - ] - }, - null, - null - ], - [ - { - "var": "a" - }, - { - "a": 1 - }, - 1 - ], - [ - { - "var": "b" - }, - { - "a": 1 - }, - null - ], - [ - { - "var": "a" - }, - null, - null - ], - [ - { - "var": [ - "a", - 1 - ] - }, - null, - 1 - ], - [ - { - "var": [ - "b", - 2 - ] - }, - { - "a": 1 - }, - 2 - ], - [ - { - "var": "a.b" - }, - { - "a": { - "b": "c" - } - }, - "c" - ], - [ - { - "var": "a.q" - }, - { - "a": { - "b": "c" - } - }, - null - ], - [ - { - "var": [ - "a.q", - 9 - ] - }, - { - "a": { - "b": "c" - } - }, - 9 - ], - [ - { - "var": 1 - }, - [ - "apple", - "banana" - ], - "banana" - ], - [ - { - "var": "1" - }, - [ - "apple", - "banana" - ], - "banana" - ], - [ - { - "var": "1.1" - }, - [ - "apple", - [ - "banana", - "beer" - ] - ], - "beer" - ], - [ - { - "and": [ - { - "<": [ - { - "var": "temp" - }, - 110 - ] - }, - { - "==": [ - { - "var": "pie.filling" - }, - "apple" - ] - } - ] - }, - { - "temp": 100, - "pie": { - "filling": "apple" - } - }, - true - ], - [ - { - "var": [ - { - "?:": [ - { - "<": [ - { - "var": "temp" - }, - 110 - ] - }, - "pie.filling", - "pie.eta" - ] - } - ] - }, - { - "temp": 100, - "pie": { - "filling": "apple", - "eta": "60s" - } - }, - "apple" - ], - [ - { - "in": [ - { - "var": "filling" - }, - [ - "apple", - "cherry" - ] - ] - }, - { - "filling": "apple" - }, - true - ], - [ - { - "var": "a.b.c" - }, - null, - null - ], - [ - { - "var": "a.b.c" - }, - { - "a": null - }, - null - ], - [ - { - "var": "a.b.c" - }, - { - "a": { - "b": null - } - }, - null - ], - [ - { - "var": "" - }, - 1, - 1 - ], - [ - { - "var": null - }, - 1, - 1 - ], - [ - { - "var": [] - }, - 1, - 1 - ], - "Missing", - [ - { - "missing": [] - }, - null, - [] - ], - [ - { - "missing": [ - "a" - ] - }, - null, - [ - "a" - ] - ], - [ - { - "missing": "a" - }, - null, - [ - "a" - ] - ], - [ - { - "missing": "a" - }, - { - "a": "apple" - }, - [] - ], - [ - { - "missing": [ - "a" - ] - }, - { - "a": "apple" - }, - [] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - { - "a": "apple" - }, - [ - "b" - ] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - { - "b": "banana" - }, - [ - "a" - ] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - { - "a": "apple", - "b": "banana" - }, - [] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - {}, - [ - "a", - "b" - ] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - null, - [ - "a", - "b" - ] - ], - [ - { - "missing": [ - "a.b" - ] - }, - null, - [ - "a.b" - ] - ], - [ - { - "missing": [ - "a.b" - ] - }, - { - "a": "apple" - }, - [ - "a.b" - ] - ], - [ - { - "missing": [ - "a.b" - ] - }, - { - "a": { - "c": "apple cake" - } - }, - [ - "a.b" - ] - ], - [ - { - "missing": [ - "a.b" - ] - }, - { - "a": { - "b": "apple brownie" - } - }, - [] - ], - [ - { - "missing": [ - "a.b", - "a.c" - ] - }, - { - "a": { - "b": "apple brownie" - } - }, - [ - "a.c" - ] - ], - "Missing some", - [ - { - "missing_some": [ - 1, - [ - "a", - "b" - ] - ] - }, - { - "a": "apple" - }, - [] - ], - [ - { - "missing_some": [ - 1, - [ - "a", - "b" - ] - ] - }, - { - "b": "banana" - }, - [] - ], - [ - { - "missing_some": [ - 1, - [ - "a", - "b" - ] - ] - }, - { - "a": "apple", - "b": "banana" - }, - [] - ], - [ - { - "missing_some": [ - 1, - [ - "a", - "b" - ] - ] - }, - { - "c": "carrot" - }, - [ - "a", - "b" - ] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "a": "apple", - "b": "banana" - }, - [] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "a": "apple", - "c": "carrot" - }, - [] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "a": "apple", - "b": "banana", - "c": "carrot" - }, - [] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "a": "apple", - "d": "durian" - }, - [ - "b", - "c" - ] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "d": "durian", - "e": "eggplant" - }, - [ - "a", - "b", - "c" - ] - ], - "Missing and If are friends, because empty arrays are falsey in JsonLogic", - [ - { - "if": [ - { - "missing": "a" - }, - "missed it", - "found it" - ] - }, - { - "a": "apple" - }, - "found it" - ], - [ - { - "if": [ - { - "missing": "a" - }, - "missed it", - "found it" - ] - }, - { - "b": "banana" - }, - "missed it" - ], - "Missing, Merge, and If are friends. VIN is always required, APR is only required if financing is true.", - [ - { - "missing": { - "merge": [ - "vin", - { - "if": [ - { - "var": "financing" - }, - [ - "apr" - ], - [] - ] - } - ] - } - }, - { - "financing": true - }, - [ - "vin", - "apr" - ] - ], - [ - { - "missing": { - "merge": [ - "vin", - { - "if": [ - { - "var": "financing" - }, - [ - "apr" - ], - [] - ] - } - ] - } - }, - { - "financing": false - }, - [ - "vin" - ] - ], - "Filter, map, all, none, and some", - [ - { - "filter": [ - { - "var": "integers" - }, - true - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [ - 1, - 2, - 3 - ] - ], - [ - { - "filter": [ - { - "var": "integers" - }, - false - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [] - ], - [ - { - "filter": [ - { - "var": "integers" - }, - { - ">=": [ - { - "var": "" - }, - 2 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [ - 2, - 3 - ] - ], - [ - { - "filter": [ - { - "var": "integers" - }, - { - "%": [ - { - "var": "" - }, - 2 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [ - 1, - 3 - ] - ], - [ - { - "map": [ - { - "var": "integers" - }, - { - "*": [ - { - "var": "" - }, - 2 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [ - 2, - 4, - 6 - ] - ], - [ - { - "map": [ - { - "var": "integers" - }, - { - "*": [ - { - "var": "" - }, - 2 - ] - } - ] - }, - null, - [] - ], - [ - { - "map": [ - { - "var": "desserts" - }, - { - "var": "qty" - } - ] - }, - { - "desserts": [ - { - "name": "apple", - "qty": 1 - }, - { - "name": "brownie", - "qty": 2 - }, - { - "name": "cupcake", - "qty": 3 - } - ] - }, - [ - 1, - 2, - 3 - ] - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "+": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - 0 - ] - }, - { - "integers": [ - 1, - 2, - 3, - 4 - ] - }, - 10 - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "+": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - { - "var": "start_with" - } - ] - }, - { - "integers": [ - 1, - 2, - 3, - 4 - ], - "start_with": 59 - }, - 69 - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "+": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - 0 - ] - }, - null, - 0 - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "*": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - 1 - ] - }, - { - "integers": [ - 1, - 2, - 3, - 4 - ] - }, - 24 - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "*": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - 0 - ] - }, - { - "integers": [ - 1, - 2, - 3, - 4 - ] - }, - 0 - ], - [ - { - "reduce": [ - { - "var": "desserts" - }, - { - "+": [ - { - "var": "accumulator" - }, - { - "var": "current.qty" - } - ] - }, - 0 - ] - }, - { - "desserts": [ - { - "name": "apple", - "qty": 1 - }, - { - "name": "brownie", - "qty": 2 - }, - { - "name": "cupcake", - "qty": 3 - } - ] - }, - 6 - ], - [ - { - "all": [ - { - "var": "integers" - }, - { - ">=": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - true - ], - [ - { - "all": [ - { - "var": "integers" - }, - { - "==": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "all": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "all": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [] - }, - false - ], - [ - { - "all": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - true - ], - [ - { - "all": [ - { - "var": "items" - }, - { - ">": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "all": [ - { - "var": "items" - }, - { - "<": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "all": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [] - }, - false - ], - [ - { - "none": [ - { - "var": "integers" - }, - { - ">=": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "none": [ - { - "var": "integers" - }, - { - "==": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "none": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - true - ], - [ - { - "none": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [] - }, - true - ], - [ - { - "none": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "none": [ - { - "var": "items" - }, - { - ">": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "none": [ - { - "var": "items" - }, - { - "<": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - true - ], - [ - { - "none": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [] - }, - true - ], - [ - { - "some": [ - { - "var": "integers" - }, - { - ">=": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - true - ], - [ - { - "some": [ - { - "var": "integers" - }, - { - "==": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - true - ], - [ - { - "some": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "some": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [] - }, - false - ], - [ - { - "some": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - true - ], - [ - { - "some": [ - { - "var": "items" - }, - { - ">": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - true - ], - [ - { - "some": [ - { - "var": "items" - }, - { - "<": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "some": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [] - }, - false - ], - "EOF" -] \ No newline at end of file diff --git a/providers/flagd/tests/evaluator/json_logic/tests_annotated.json b/providers/flagd/tests/evaluator/json_logic/tests_annotated.json deleted file mode 100644 index f5389b5..0000000 --- a/providers/flagd/tests/evaluator/json_logic/tests_annotated.json +++ /dev/null @@ -1,3863 +0,0 @@ -{ - "Unknown/primitive": [ - [ - true, - {}, - true - ], - [ - false, - {}, - false - ], - [ - 17, - {}, - 17 - ], - [ - 3.14, - {}, - 3.14 - ], - [ - "apple", - {}, - "apple" - ], - [ - null, - {}, - null - ], - [ - [ - "a", - "b" - ], - {}, - [ - "a", - "b" - ] - ], - [ - [ - 1, - { - "var": "x" - }, - 3 - ], - { - "x": 2 - }, - [ - 1, - 2, - 3 - ] - ] - ], - "Equality/==": [ - [ - { - "==": [ - 1, - 1 - ] - }, - {}, - true - ], - [ - { - "==": [ - 1, - 2 - ] - }, - {}, - false - ] - ], - "Equality/===": [ - [ - { - "===": [ - 1, - 1 - ] - }, - {}, - true - ], - [ - { - "===": [ - 1, - "1" - ] - }, - {}, - false - ], - [ - { - "===": [ - 1, - 2 - ] - }, - {}, - false - ], - [ - { - "===": [ - 0, - "0" - ] - }, - null, - false - ] - ], - "Equality/!=": [ - [ - { - "!=": [ - 1, - 2 - ] - }, - {}, - true - ], - [ - { - "!=": [ - 1, - 1 - ] - }, - {}, - false - ] - ], - "Equality/!==": [ - [ - { - "!==": [ - 1, - 2 - ] - }, - {}, - true - ], - [ - { - "!==": [ - 1, - 1 - ] - }, - {}, - false - ], - [ - { - "!==": [ - 1, - "1" - ] - }, - {}, - true - ] - ], - "Numeric/>": [ - [ - { - ">": [ - 2, - 1 - ] - }, - {}, - true - ], - [ - { - ">": [ - 1, - 1 - ] - }, - {}, - false - ], - [ - { - ">": [ - 1, - 2 - ] - }, - {}, - false - ] - ], - "Numeric/>=": [ - [ - { - ">=": [ - 2, - 1 - ] - }, - {}, - true - ], - [ - { - ">=": [ - 1, - 1 - ] - }, - {}, - true - ], - [ - { - ">=": [ - 1, - 2 - ] - }, - {}, - false - ] - ], - "Numeric/<": [ - [ - { - "<": [ - 2, - 1 - ] - }, - {}, - false - ], - [ - { - "<": [ - 1, - 1 - ] - }, - {}, - false - ], - [ - { - "<": [ - 1, - 2 - ] - }, - {}, - true - ], - [ - { - "<": [ - 1, - 2, - 3 - ] - }, - {}, - true - ], - [ - { - "<": [ - 1, - 1, - 3 - ] - }, - {}, - false - ], - [ - { - "<": [ - 1, - 4, - 3 - ] - }, - {}, - false - ] - ], - "Numeric/<=": [ - [ - { - "<=": [ - 2, - 1 - ] - }, - {}, - false - ], - [ - { - "<=": [ - 1, - 1 - ] - }, - {}, - true - ], - [ - { - "<=": [ - 1, - 2 - ] - }, - {}, - true - ], - [ - { - "<=": [ - 1, - 2, - 3 - ] - }, - {}, - true - ], - [ - { - "<=": [ - 1, - 4, - 3 - ] - }, - {}, - false - ] - ], - "Logic/!": [ - [ - { - "!": [ - false - ] - }, - {}, - true - ], - [ - { - "!": false - }, - {}, - true - ], - [ - { - "!": [ - true - ] - }, - {}, - false - ], - [ - { - "!": true - }, - {}, - false - ], - [ - { - "!": 0 - }, - {}, - true - ], - [ - { - "!": 1 - }, - {}, - false - ], - [ - { - "!": [ - [] - ] - }, - {}, - true - ], - [ - { - "!": [ - 0 - ] - }, - {}, - true - ], - [ - { - "!": [ - "" - ] - }, - {}, - true - ], - [ - { - "!": [ - "0" - ] - }, - {}, - false - ] - ], - "Logic/or": [ - [ - { - "or": [ - true, - true - ] - }, - {}, - true - ], - [ - { - "or": [ - false, - true - ] - }, - {}, - true - ], - [ - { - "or": [ - true, - false - ] - }, - {}, - true - ], - [ - { - "or": [ - false, - false - ] - }, - {}, - false - ], - [ - { - "or": [ - false, - false, - true - ] - }, - {}, - true - ], - [ - { - "or": [ - false, - false, - false - ] - }, - {}, - false - ], - [ - { - "or": [ - false - ] - }, - {}, - false - ], - [ - { - "or": [ - true - ] - }, - {}, - true - ], - [ - { - "or": [ - 1, - 3 - ] - }, - {}, - 1 - ], - [ - { - "or": [ - 3, - false - ] - }, - {}, - 3 - ], - [ - { - "or": [ - false, - 3 - ] - }, - {}, - 3 - ], - [ - { - "or": [ - [], - true - ] - }, - {}, - true - ], - [ - { - "or": [ - 0, - true - ] - }, - {}, - true - ], - [ - { - "or": [ - "", - true - ] - }, - {}, - true - ], - [ - { - "or": [ - "0", - true - ] - }, - {}, - "0" - ] - ], - "Logic/and": [ - [ - { - "and": [ - true, - true - ] - }, - {}, - true - ], - [ - { - "and": [ - false, - true - ] - }, - {}, - false - ], - [ - { - "and": [ - true, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - false, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - true, - true, - true - ] - }, - {}, - true - ], - [ - { - "and": [ - true, - true, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - false - ] - }, - {}, - false - ], - [ - { - "and": [ - true - ] - }, - {}, - true - ], - [ - { - "and": [ - 1, - 3 - ] - }, - {}, - 3 - ], - [ - { - "and": [ - 3, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - false, - 3 - ] - }, - {}, - false - ], - [ - { - "and": [ - [], - true - ] - }, - {}, - [] - ], - [ - { - "and": [ - 0, - true - ] - }, - {}, - 0 - ], - [ - { - "and": [ - "", - true - ] - }, - {}, - "" - ], - [ - { - "and": [ - "0", - true - ] - }, - {}, - true - ], - [ - { - "and": [ - { - ">": [ - 3, - 1 - ] - }, - true - ] - }, - {}, - true - ], - [ - { - "and": [ - { - ">": [ - 3, - 1 - ] - }, - false - ] - }, - {}, - false - ], - [ - { - "and": [ - { - ">": [ - 3, - 1 - ] - }, - { - "!": true - } - ] - }, - {}, - false - ], - [ - { - "and": [ - { - ">": [ - 3, - 1 - ] - }, - { - "<": [ - 1, - 3 - ] - } - ] - }, - {}, - true - ], - [ - { - "and": [ - { - "<": [ - { - "var": "temp" - }, - 110 - ] - }, - { - "==": [ - { - "var": "pie.filling" - }, - "apple" - ] - } - ] - }, - { - "temp": 100, - "pie": { - "filling": "apple" - } - }, - true - ] - ], - "Logic/?:": [ - [ - { - "?:": [ - true, - 1, - 2 - ] - }, - {}, - 1 - ], - [ - { - "?:": [ - false, - 1, - 2 - ] - }, - {}, - 2 - ], - [ - { - "?:": [ - { - ">": [ - 3, - 1 - ] - }, - "visible", - "hidden" - ] - }, - {}, - "visible" - ] - ], - "String/Array/in": [ - [ - { - "in": [ - "Bart", - [ - "Bart", - "Homer", - "Lisa", - "Marge", - "Maggie" - ] - ] - }, - {}, - true - ], - [ - { - "in": [ - "Milhouse", - [ - "Bart", - "Homer", - "Lisa", - "Marge", - "Maggie" - ] - ] - }, - {}, - false - ], - [ - { - "in": [ - "Spring", - "Springfield" - ] - }, - {}, - true - ], - [ - { - "in": [ - "i", - "team" - ] - }, - {}, - false - ], - [ - { - "in": [ - { - "var": "filling" - }, - [ - "apple", - "cherry" - ] - ] - }, - { - "filling": "apple" - }, - true - ] - ], - "String/cat": [ - [ - { - "cat": "ice" - }, - {}, - "ice" - ], - [ - { - "cat": [ - "ice" - ] - }, - {}, - "ice" - ], - [ - { - "cat": [ - "ice", - "cream" - ] - }, - {}, - "icecream" - ], - [ - { - "cat": [ - "we all scream for ", - "ice", - "cream" - ] - }, - {}, - "we all scream for icecream" - ] - ], - "Numeric/%": [ - [ - { - "%": [ - 1, - 2 - ] - }, - {}, - 1 - ], - [ - { - "%": [ - 2, - 2 - ] - }, - {}, - 0 - ], - [ - { - "%": [ - 3, - 2 - ] - }, - {}, - 1 - ] - ], - "Numeric/max": [ - [ - { - "max": [ - 1, - 2, - 3 - ] - }, - {}, - 3 - ], - [ - { - "max": [ - 1, - 3, - 3 - ] - }, - {}, - 3 - ], - [ - { - "max": [ - 3, - 2, - 1 - ] - }, - {}, - 3 - ], - [ - { - "max": [ - 1 - ] - }, - {}, - 1 - ] - ], - "Numeric/min": [ - [ - { - "min": [ - 1, - 2, - 3 - ] - }, - {}, - 1 - ], - [ - { - "min": [ - 1, - 1, - 3 - ] - }, - {}, - 1 - ], - [ - { - "min": [ - 3, - 2, - 1 - ] - }, - {}, - 1 - ], - [ - { - "min": [ - 1 - ] - }, - {}, - 1 - ] - ], - "Numeric/+": [ - [ - { - "+": [ - 1, - 2 - ] - }, - {}, - 3 - ], - [ - { - "+": [ - 2, - 2, - 2 - ] - }, - {}, - 6 - ], - [ - { - "+": [ - 1 - ] - }, - {}, - 1 - ] - ], - "Numeric/*": [ - [ - { - "*": [ - 3, - 2 - ] - }, - {}, - 6 - ], - [ - { - "*": [ - 2, - 2, - 2 - ] - }, - {}, - 8 - ], - [ - { - "*": [ - 1 - ] - }, - {}, - 1 - ] - ], - "Numeric/-": [ - [ - { - "-": [ - 2, - 3 - ] - }, - {}, - -1 - ], - [ - { - "-": [ - 3, - 2 - ] - }, - {}, - 1 - ], - [ - { - "-": [ - 3 - ] - }, - {}, - -3 - ] - ], - "Numeric//": [ - [ - { - "/": [ - 4, - 2 - ] - }, - {}, - 2 - ], - [ - { - "/": [ - 2, - 4 - ] - }, - {}, - 0.5 - ] - ], - "String/substr": [ - [ - { - "substr": [ - "jsonlogic", - 4 - ] - }, - null, - "logic" - ], - [ - { - "substr": [ - "jsonlogic", - -5 - ] - }, - null, - "logic" - ], - [ - { - "substr": [ - "jsonlogic", - 0, - 1 - ] - }, - null, - "j" - ], - [ - { - "substr": [ - "jsonlogic", - -1, - 1 - ] - }, - null, - "c" - ], - [ - { - "substr": [ - "jsonlogic", - 4, - 5 - ] - }, - null, - "logic" - ], - [ - { - "substr": [ - "jsonlogic", - -5, - 5 - ] - }, - null, - "logic" - ], - [ - { - "substr": [ - "jsonlogic", - -5, - -2 - ] - }, - null, - "log" - ], - [ - { - "substr": [ - "jsonlogic", - 1, - -5 - ] - }, - null, - "son" - ] - ], - "Array/merge": [ - [ - { - "merge": [] - }, - null, - [] - ], - [ - { - "merge": [ - [ - 1 - ] - ] - }, - null, - [ - 1 - ] - ], - [ - { - "merge": [ - [ - 1 - ], - [] - ] - }, - null, - [ - 1 - ] - ], - [ - { - "merge": [ - [ - 1 - ], - [ - 2 - ] - ] - }, - null, - [ - 1, - 2 - ] - ], - [ - { - "merge": [ - [ - 1 - ], - [ - 2 - ], - [ - 3 - ] - ] - }, - null, - [ - 1, - 2, - 3 - ] - ], - [ - { - "merge": [ - [ - 1, - 2 - ], - [ - 3 - ] - ] - }, - null, - [ - 1, - 2, - 3 - ] - ], - [ - { - "merge": [ - [ - 1 - ], - [ - 2, - 3 - ] - ] - }, - null, - [ - 1, - 2, - 3 - ] - ], - [ - { - "merge": 1 - }, - null, - [ - 1 - ] - ], - [ - { - "merge": [ - 1, - 2 - ] - }, - null, - [ - 1, - 2 - ] - ], - [ - { - "merge": [ - 1, - [ - 2 - ] - ] - }, - null, - [ - 1, - 2 - ] - ] - ], - "Logic/if": [ - [ - { - "if": [] - }, - null, - null - ], - [ - { - "if": [ - true - ] - }, - null, - true - ], - [ - { - "if": [ - false - ] - }, - null, - false - ], - [ - { - "if": [ - "apple" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - "apple" - ] - }, - null, - null - ], - [ - { - "if": [ - true, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - [], - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - [ - 1 - ], - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - [ - 1, - 2, - 3, - 4 - ], - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - "", - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - "zucchini", - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - "0", - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - 0, - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - 1, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - 3.1416, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - -1, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - { - ">": [ - 2, - 1 - ] - }, - "apple", - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - { - ">": [ - 1, - 2 - ] - }, - "apple", - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - true, - { - "cat": [ - "ap", - "ple" - ] - }, - { - "cat": [ - "ba", - "na", - "na" - ] - } - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - { - "cat": [ - "ap", - "ple" - ] - }, - { - "cat": [ - "ba", - "na", - "na" - ] - } - ] - }, - null, - "banana" - ], - [ - { - "if": [ - true, - "apple", - true, - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - false, - "banana" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - "apple", - true, - "banana" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - false, - "apple", - false, - "banana" - ] - }, - null, - null - ], - [ - { - "if": [ - true, - "apple", - true, - "banana", - "carrot" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - false, - "banana", - "carrot" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - false, - "apple", - true, - "banana", - "carrot" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - false, - "apple", - false, - "banana", - "carrot" - ] - }, - null, - "carrot" - ], - [ - { - "if": [ - false, - "apple", - false, - "banana", - false, - "carrot" - ] - }, - null, - null - ], - [ - { - "if": [ - false, - "apple", - false, - "banana", - false, - "carrot", - "date" - ] - }, - null, - "date" - ], - [ - { - "if": [ - false, - "apple", - false, - "banana", - true, - "carrot", - "date" - ] - }, - null, - "carrot" - ], - [ - { - "if": [ - false, - "apple", - true, - "banana", - false, - "carrot", - "date" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - false, - "apple", - true, - "banana", - true, - "carrot", - "date" - ] - }, - null, - "banana" - ], - [ - { - "if": [ - true, - "apple", - false, - "banana", - false, - "carrot", - "date" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - false, - "banana", - true, - "carrot", - "date" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - true, - "banana", - false, - "carrot", - "date" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - true, - "apple", - true, - "banana", - true, - "carrot", - "date" - ] - }, - null, - "apple" - ], - [ - { - "if": [ - { - "var": "x" - }, - [ - { - "var": "y" - } - ], - 99 - ] - }, - { - "x": true, - "y": 42 - }, - [ - 42 - ] - ], - [ - { - "if": [ - { - "missing": "a" - }, - "missed it", - "found it" - ] - }, - { - "a": "apple" - }, - "found it" - ], - [ - { - "if": [ - { - "missing": "a" - }, - "missed it", - "found it" - ] - }, - { - "b": "banana" - }, - "missed it" - ] - ], - "Logic/!!": [ - [ - { - "!!": [ - [] - ] - }, - {}, - false - ], - [ - { - "!!": [ - 0 - ] - }, - {}, - false - ], - [ - { - "!!": [ - "" - ] - }, - {}, - false - ], - [ - { - "!!": [ - "0" - ] - }, - {}, - true - ] - ], - "Data/var": [ - [ - { - "var": [ - "a" - ] - }, - { - "a": 1 - }, - 1 - ], - [ - { - "var": [ - "b" - ] - }, - { - "a": 1 - }, - null - ], - [ - { - "var": [ - "a" - ] - }, - null, - null - ], - [ - { - "var": "a" - }, - { - "a": 1 - }, - 1 - ], - [ - { - "var": "b" - }, - { - "a": 1 - }, - null - ], - [ - { - "var": "a" - }, - null, - null - ], - [ - { - "var": [ - "a", - 1 - ] - }, - null, - 1 - ], - [ - { - "var": [ - "b", - 2 - ] - }, - { - "a": 1 - }, - 2 - ], - [ - { - "var": "a.b" - }, - { - "a": { - "b": "c" - } - }, - "c" - ], - [ - { - "var": "a.q" - }, - { - "a": { - "b": "c" - } - }, - null - ], - [ - { - "var": [ - "a.q", - 9 - ] - }, - { - "a": { - "b": "c" - } - }, - 9 - ], - [ - { - "var": "1" - }, - [ - "apple", - "banana" - ], - "banana" - ], - [ - { - "var": "1.1" - }, - [ - "apple", - [ - "banana", - "beer" - ] - ], - "beer" - ], - [ - { - "var": [ - { - "?:": [ - { - "<": [ - { - "var": "temp" - }, - 110 - ] - }, - "pie.filling", - "pie.eta" - ] - } - ] - }, - { - "temp": 100, - "pie": { - "filling": "apple", - "eta": "60s" - } - }, - "apple" - ], - [ - { - "var": "a.b.c" - }, - null, - null - ], - [ - { - "var": "a.b.c" - }, - { - "a": null - }, - null - ], - [ - { - "var": "a.b.c" - }, - { - "a": { - "b": null - } - }, - null - ], - [ - { - "var": "" - }, - 1, - 1 - ], - [ - { - "var": null - }, - 1, - 1 - ], - [ - { - "var": [] - }, - 1, - 1 - ] - ], - "Data/missing": [ - [ - { - "missing": [] - }, - null, - [] - ], - [ - { - "missing": [ - "a" - ] - }, - null, - [ - "a" - ] - ], - [ - { - "missing": "a" - }, - null, - [ - "a" - ] - ], - [ - { - "missing": "a" - }, - { - "a": "apple" - }, - [] - ], - [ - { - "missing": [ - "a" - ] - }, - { - "a": "apple" - }, - [] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - { - "a": "apple" - }, - [ - "b" - ] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - { - "b": "banana" - }, - [ - "a" - ] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - { - "a": "apple", - "b": "banana" - }, - [] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - {}, - [ - "a", - "b" - ] - ], - [ - { - "missing": [ - "a", - "b" - ] - }, - null, - [ - "a", - "b" - ] - ], - [ - { - "missing": [ - "a.b" - ] - }, - null, - [ - "a.b" - ] - ], - [ - { - "missing": [ - "a.b" - ] - }, - { - "a": "apple" - }, - [ - "a.b" - ] - ], - [ - { - "missing": [ - "a.b" - ] - }, - { - "a": { - "c": "apple cake" - } - }, - [ - "a.b" - ] - ], - [ - { - "missing": [ - "a.b" - ] - }, - { - "a": { - "b": "apple brownie" - } - }, - [] - ], - [ - { - "missing": [ - "a.b", - "a.c" - ] - }, - { - "a": { - "b": "apple brownie" - } - }, - [ - "a.c" - ] - ], - [ - { - "missing": { - "merge": [ - "vin", - { - "if": [ - { - "var": "financing" - }, - [ - "apr" - ], - [] - ] - } - ] - } - }, - { - "financing": true - }, - [ - "vin", - "apr" - ] - ], - [ - { - "missing": { - "merge": [ - "vin", - { - "if": [ - { - "var": "financing" - }, - [ - "apr" - ], - [] - ] - } - ] - } - }, - { - "financing": false - }, - [ - "vin" - ] - ] - ], - "Data/missing_some": [ - [ - { - "missing_some": [ - 1, - [ - "a", - "b" - ] - ] - }, - { - "a": "apple" - }, - [] - ], - [ - { - "missing_some": [ - 1, - [ - "a", - "b" - ] - ] - }, - { - "b": "banana" - }, - [] - ], - [ - { - "missing_some": [ - 1, - [ - "a", - "b" - ] - ] - }, - { - "a": "apple", - "b": "banana" - }, - [] - ], - [ - { - "missing_some": [ - 1, - [ - "a", - "b" - ] - ] - }, - { - "c": "carrot" - }, - [ - "a", - "b" - ] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "a": "apple", - "b": "banana" - }, - [] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "a": "apple", - "c": "carrot" - }, - [] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "a": "apple", - "b": "banana", - "c": "carrot" - }, - [] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "a": "apple", - "d": "durian" - }, - [ - "b", - "c" - ] - ], - [ - { - "missing_some": [ - 2, - [ - "a", - "b", - "c" - ] - ] - }, - { - "d": "durian", - "e": "eggplant" - }, - [ - "a", - "b", - "c" - ] - ] - ], - "__skipped__": { - "Operator '==' has mismatched types: int and str": [ - [ - { - "==": [ - 1, - "1" - ] - }, - {}, - true - ] - ], - "Operator '!=' has mismatched types: int and str": [ - [ - { - "!=": [ - 1, - "1" - ] - }, - {}, - false - ] - ], - "Operator '>' has non-numeric literal argument: 2 (type: str)": [ - [ - { - ">": [ - "2", - 1 - ] - }, - {}, - true - ] - ], - "Operator '>=' has non-numeric literal argument: 2 (type: str)": [ - [ - { - ">=": [ - "2", - 1 - ] - }, - {}, - true - ] - ], - "Operator '<' has non-numeric literal argument: 1 (type: str)": [ - [ - { - "<": [ - "1", - 2 - ] - }, - {}, - true - ] - ], - "Operator '<=' has non-numeric literal argument: 1 (type: str)": [ - [ - { - "<=": [ - "1", - 2 - ] - }, - {}, - true - ] - ], - "Operator 'cat' has non-string literal argument: 1": [ - [ - { - "cat": [ - 1, - 2 - ] - }, - {}, - "12" - ] - ], - "Operator 'cat' has non-string literal argument: 2": [ - [ - { - "cat": [ - "Robocop", - 2 - ] - }, - {}, - "Robocop2" - ] - ], - "Operator '+' has non-numeric literal argument: 1 (type: str)": [ - [ - { - "+": [ - "1", - 1 - ] - }, - {}, - 2 - ], - [ - { - "if": [ - { - "+": "1" - }, - "apple", - "banana" - ] - }, - null, - "apple" - ] - ], - "Operator '*' has non-numeric literal argument: 1 (type: str)": [ - [ - { - "*": [ - "1", - 1 - ] - }, - {}, - 1 - ] - ], - "Operator '-' has non-numeric literal argument: 1 (type: str)": [ - [ - { - "-": [ - "1", - 1 - ] - }, - {}, - 0 - ] - ], - "Operator '/' has non-numeric literal argument: 1 (type: str)": [ - [ - { - "/": [ - "1", - 1 - ] - }, - {}, - 1 - ] - ], - "Operator '+' has non-numeric literal argument: 0 (type: str)": [ - [ - { - "===": [ - 0, - { - "+": "0" - } - ] - }, - null, - true - ], - [ - { - "if": [ - { - "+": "0" - }, - "apple", - "banana" - ] - }, - null, - "banana" - ] - ], - "Operator 'var' has non-string key: 1 (type: int)": [ - [ - { - "var": 1 - }, - [ - "apple", - "banana" - ], - "banana" - ] - ], - "Uses ignored operator 'filter'": [ - [ - { - "filter": [ - { - "var": "integers" - }, - true - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [ - 1, - 2, - 3 - ] - ], - [ - { - "filter": [ - { - "var": "integers" - }, - false - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [] - ], - [ - { - "filter": [ - { - "var": "integers" - }, - { - ">=": [ - { - "var": "" - }, - 2 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [ - 2, - 3 - ] - ], - [ - { - "filter": [ - { - "var": "integers" - }, - { - "%": [ - { - "var": "" - }, - 2 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [ - 1, - 3 - ] - ] - ], - "Uses ignored operator 'map'": [ - [ - { - "map": [ - { - "var": "integers" - }, - { - "*": [ - { - "var": "" - }, - 2 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - [ - 2, - 4, - 6 - ] - ], - [ - { - "map": [ - { - "var": "integers" - }, - { - "*": [ - { - "var": "" - }, - 2 - ] - } - ] - }, - null, - [] - ], - [ - { - "map": [ - { - "var": "desserts" - }, - { - "var": "qty" - } - ] - }, - { - "desserts": [ - { - "name": "apple", - "qty": 1 - }, - { - "name": "brownie", - "qty": 2 - }, - { - "name": "cupcake", - "qty": 3 - } - ] - }, - [ - 1, - 2, - 3 - ] - ] - ], - "Uses ignored operator 'reduce'": [ - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "+": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - 0 - ] - }, - { - "integers": [ - 1, - 2, - 3, - 4 - ] - }, - 10 - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "+": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - { - "var": "start_with" - } - ] - }, - { - "integers": [ - 1, - 2, - 3, - 4 - ], - "start_with": 59 - }, - 69 - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "+": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - 0 - ] - }, - null, - 0 - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "*": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - 1 - ] - }, - { - "integers": [ - 1, - 2, - 3, - 4 - ] - }, - 24 - ], - [ - { - "reduce": [ - { - "var": "integers" - }, - { - "*": [ - { - "var": "current" - }, - { - "var": "accumulator" - } - ] - }, - 0 - ] - }, - { - "integers": [ - 1, - 2, - 3, - 4 - ] - }, - 0 - ], - [ - { - "reduce": [ - { - "var": "desserts" - }, - { - "+": [ - { - "var": "accumulator" - }, - { - "var": "current.qty" - } - ] - }, - 0 - ] - }, - { - "desserts": [ - { - "name": "apple", - "qty": 1 - }, - { - "name": "brownie", - "qty": 2 - }, - { - "name": "cupcake", - "qty": 3 - } - ] - }, - 6 - ] - ], - "Uses ignored operator 'all'": [ - [ - { - "all": [ - { - "var": "integers" - }, - { - ">=": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - true - ], - [ - { - "all": [ - { - "var": "integers" - }, - { - "==": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "all": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "all": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [] - }, - false - ], - [ - { - "all": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - true - ], - [ - { - "all": [ - { - "var": "items" - }, - { - ">": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "all": [ - { - "var": "items" - }, - { - "<": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "all": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [] - }, - false - ] - ], - "Uses ignored operator 'none'": [ - [ - { - "none": [ - { - "var": "integers" - }, - { - ">=": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "none": [ - { - "var": "integers" - }, - { - "==": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "none": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - true - ], - [ - { - "none": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [] - }, - true - ], - [ - { - "none": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "none": [ - { - "var": "items" - }, - { - ">": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "none": [ - { - "var": "items" - }, - { - "<": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - true - ], - [ - { - "none": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [] - }, - true - ] - ], - "Uses ignored operator 'some'": [ - [ - { - "some": [ - { - "var": "integers" - }, - { - ">=": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - true - ], - [ - { - "some": [ - { - "var": "integers" - }, - { - "==": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - true - ], - [ - { - "some": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [ - 1, - 2, - 3 - ] - }, - false - ], - [ - { - "some": [ - { - "var": "integers" - }, - { - "<": [ - { - "var": "" - }, - 1 - ] - } - ] - }, - { - "integers": [] - }, - false - ], - [ - { - "some": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - true - ], - [ - { - "some": [ - { - "var": "items" - }, - { - ">": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - true - ], - [ - { - "some": [ - { - "var": "items" - }, - { - "<": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [ - { - "qty": 1, - "sku": "apple" - }, - { - "qty": 2, - "sku": "banana" - } - ] - }, - false - ], - [ - { - "some": [ - { - "var": "items" - }, - { - ">=": [ - { - "var": "qty" - }, - 1 - ] - } - ] - }, - { - "items": [] - }, - false - ] - ] - } -} \ No newline at end of file diff --git a/providers/flagd/third_party/datalogic/BUILD.bazel b/providers/flagd/third_party/datalogic/BUILD.bazel new file mode 100644 index 0000000..6418e49 --- /dev/null +++ b/providers/flagd/third_party/datalogic/BUILD.bazel @@ -0,0 +1,107 @@ +load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library") + +package(default_visibility = ["//providers/flagd:__subpackages__"]) + +config_setting( + name = "linux_x86_64", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], +) + +config_setting( + name = "linux_aarch64", + constraint_values = [ + "@platforms//cpu:aarch64", + "@platforms//os:linux", + ], +) + +config_setting( + name = "darwin_x86_64", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:macos", + ], +) + +config_setting( + name = "darwin_aarch64", + constraint_values = [ + "@platforms//cpu:aarch64", + "@platforms//os:macos", + ], +) + +cc_import( + name = "datalogic_static_linux_x86_64", + static_library = "@datalogic_c_linux_x86_64//:libdatalogic_c.a", +) + +cc_import( + name = "datalogic_static_linux_aarch64", + static_library = "@datalogic_c_linux_aarch64//:libdatalogic_c.a", +) + +cc_import( + name = "datalogic_static_darwin_x86_64", + static_library = "@datalogic_c_darwin_x86_64//:libdatalogic_c.a", +) + +cc_import( + name = "datalogic_static_darwin_aarch64", + static_library = "@datalogic_c_darwin_aarch64//:libdatalogic_c.a", +) + +# Stage the (platform-agnostic) cbindgen header at a stable path so +# consumers can `#include `. We pull it from whichever +# platform tarball matches the build target — each ships an identical +# copy, so this preserves per-platform SHA pinning end-to-end. +genrule( + name = "datalogic_header_gen", + srcs = select({ + ":linux_x86_64": ["@datalogic_c_linux_x86_64//:datalogic.h"], + ":linux_aarch64": ["@datalogic_c_linux_aarch64//:datalogic.h"], + ":darwin_x86_64": ["@datalogic_c_darwin_x86_64//:datalogic.h"], + ":darwin_aarch64": ["@datalogic_c_darwin_aarch64//:datalogic.h"], + }), + outs = ["include/datalogic.h"], + cmd = "cp $< $@", +) + +# System libraries the Rust staticlib pulls in via std/chrono/getrandom. +# Mirrors the cgo LDFLAGS declared in datalogic-rs's bindings/go/cgo_*.go +# at the v5.0.0 tag. (-lm/-lpthread are already pulled in by abseil's +# linkopts on macOS, so we'd only see duplicate-library warnings if we +# repeated them — keep the macOS list to just the frameworks unique to +# datalogic.) +_LINUX_LINKOPTS = [ + "-lm", + "-ldl", + "-lpthread", +] + +_DARWIN_LINKOPTS = [ + "-framework", + "CoreFoundation", + "-framework", + "Security", +] + +cc_library( + name = "datalogic_c", + hdrs = [":datalogic_header_gen"], + linkopts = select({ + "@platforms//os:linux": _LINUX_LINKOPTS, + "@platforms//os:macos": _DARWIN_LINKOPTS, + }), + strip_include_prefix = "include", + visibility = ["//providers/flagd:__subpackages__"], + deps = select({ + ":linux_x86_64": [":datalogic_static_linux_x86_64"], + ":linux_aarch64": [":datalogic_static_linux_aarch64"], + ":darwin_x86_64": [":datalogic_static_darwin_x86_64"], + ":darwin_aarch64": [":datalogic_static_darwin_aarch64"], + }), +)