Skip to content

Commit dd0b23b

Browse files
authored
Attached JSONLogic engine to evaluator interface (#68)
* Attached JSONLogic engine to evaluator interface Signed-off-by: Marcin Olko <molko@google.com> * Applied gemini suggestions Signed-off-by: Marcin Olko <molko@google.com> * Added explicit var types and log on unsupported targeting rule result type Signed-off-by: Marcin Olko <molko@google.com> * Fixed formatting issue Signed-off-by: Marcin Olko <molko@google.com> --------- Signed-off-by: Marcin Olko <molko@google.com>
1 parent bbfe76f commit dd0b23b

5 files changed

Lines changed: 81 additions & 32 deletions

File tree

.clang-tidy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Checks: "
55
performance-*,
66
-google-readability-todo,
77
-readability-convert-member-functions-to-static,
8+
-readability-function-cognitive-complexity,
89
-modernize-use-trailing-return-type,
910
-modernize-use-nodiscard,
1011
"

providers/flagd/src/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ cc_library(
5757
include_prefix = "flagd",
5858
visibility = ["//visibility:public"],
5959
deps = [
60+
"//providers/flagd/src/evaluator/json_logic:json_logic",
6061
":sync",
6162
"@abseil-cpp//absl/log",
6263
"@abseil-cpp//absl/strings",

providers/flagd/src/evaluator.cpp

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#include "evaluator.h"
22

3+
#include <chrono>
34
#include <memory>
45
#include <nlohmann/json.hpp>
6+
#include <optional>
57

68
#include "absl/log/log.h"
79
#include "absl/strings/str_cat.h"
@@ -44,6 +46,33 @@ openfeature::Value JsonToValue(const nlohmann::json& json_val) {
4446
return {};
4547
}
4648

49+
nlohmann::json ContextToJson(const openfeature::EvaluationContext& ctx) {
50+
nlohmann::json json = nlohmann::json::object();
51+
std::optional<std::string_view> targeting_key = ctx.GetTargetingKey();
52+
if (targeting_key.has_value()) {
53+
json["targetingKey"] = targeting_key.value();
54+
}
55+
56+
for (const auto& [key, value] : ctx.GetAttributes()) {
57+
if (const auto* str_val = std::any_cast<std::string>(&value)) {
58+
json[key] = *str_val;
59+
} else if (const auto* i64_val = std::any_cast<int64_t>(&value)) {
60+
json[key] = *i64_val;
61+
} else if (const auto* u64_val = std::any_cast<uint64_t>(&value)) {
62+
json[key] = *u64_val;
63+
} else if (const auto* int_val = std::any_cast<int>(&value)) {
64+
json[key] = *int_val;
65+
} else if (const auto* dbl_val = std::any_cast<double>(&value)) {
66+
json[key] = *dbl_val;
67+
} else if (const auto* bool_val = std::any_cast<bool>(&value)) {
68+
json[key] = *bool_val;
69+
} else {
70+
LOG(WARNING) << "Unsupported attribute type for key: " << key;
71+
}
72+
}
73+
return json;
74+
}
75+
4776
} // namespace
4877

4978
JsonLogicEvaluator::JsonLogicEvaluator(std::shared_ptr<FlagSync> sync)
@@ -78,33 +107,47 @@ JsonLogicEvaluator::ResolveAny(std::string_view flag_key, T default_value,
78107
absl::StrCat("flag: ", flag_key, " is disabled"));
79108
}
80109

81-
std::string variant;
110+
std::optional<std::string> variant;
82111

83112
if (flag_config.contains("targeting") && !flag_config["targeting"].empty()) {
84-
/*
85-
* TODO(#18): Invoke JsonLogic here once implemented.
86-
*
87-
* Example variables:
88-
* const nlohmann::json& targeting_rule = flag_config["targeting"];
89-
*
90-
* // You would need a way to convert openfeature::EvaluationContext to
91-
* // nlohmann::json, e.g.:
92-
* // nlohmann::json data = evaluation_context_to_json(ctx);
93-
*
94-
* // Then apply the logic:
95-
* // nlohmann::json result = JsonLogic::Apply(targeting_rule, data);
96-
*
97-
* // If the result is a variant name (string):
98-
* // if (result.is_string()) {
99-
* // variant = result.get<std::string>();
100-
* // } else if (result.is_bool()) {
101-
* // variant = result.get<std::bool>() ? "true" : "false";
102-
* // }
103-
*/
113+
nlohmann::json data = ContextToJson(ctx);
114+
// flagd also injects some special variables
115+
data["$flagd"] = {
116+
{"flagKey", flag_key},
117+
{
118+
"timestamp",
119+
std::chrono::system_clock::to_time_t(
120+
std::chrono::system_clock::now()),
121+
},
122+
};
123+
124+
absl::StatusOr<nlohmann::json> result =
125+
json_logic_.Apply(flag_config["targeting"], data);
126+
if (result.ok()) {
127+
if (result.value().is_string()) {
128+
variant = result.value().get<std::string>();
129+
} else if (result.value().is_boolean()) {
130+
variant = result.value().get<bool>() ? "true" : "false";
131+
} else {
132+
// According to the flagd spec, targeting rules must return a string
133+
// (the variant name). Booleans are a special case to support logical
134+
// operations in boolean flags, which we map to "true"/"false" strings.
135+
// Other types are not valid variant identifiers.
136+
LOG(WARNING) << "Targeting rule for flag '" << flag_key
137+
<< "' returned an unsupported type: "
138+
<< result.value().type_name()
139+
<< ". Expected string or boolean.";
140+
}
141+
} else {
142+
LOG(ERROR) << "JsonLogic evaluation failed for flag " << flag_key << ": "
143+
<< result.status().message();
144+
}
104145
}
105146

106147
openfeature::Reason reason;
107-
if (!variant.empty()) {
148+
std::string variant_name;
149+
if (variant.has_value()) {
150+
variant_name = std::move(*variant);
108151
reason = openfeature::Reason::kTargetingMatch;
109152
} else {
110153
if (!flag_config.contains("defaultVariant")) {
@@ -115,18 +158,18 @@ JsonLogicEvaluator::ResolveAny(std::string_view flag_key, T default_value,
115158
" doesn't have defaultVariant defined."));
116159
}
117160

118-
variant = flag_config["defaultVariant"];
161+
variant_name = flag_config["defaultVariant"];
119162
reason = openfeature::Reason::kStatic;
120163
}
121164

122165
const nlohmann::json& variants = flag_config["variants"];
123166

124-
if (!variants.contains(variant)) {
167+
if (!variants.contains(variant_name)) {
125168
return std::make_unique<openfeature::ResolutionDetails<T>>(
126-
std::move(default_value), openfeature::Reason::kError, variant,
169+
std::move(default_value), openfeature::Reason::kError, variant_name,
127170
openfeature::FlagMetadata(), openfeature::ErrorCode::kGeneral,
128-
absl::StrCat("flag: ", flag_key,
129-
" doesn't contain evaluated variant: ", variant, "."));
171+
absl::StrCat("flag: ", flag_key, " doesn't contain evaluated variant: ",
172+
variant_name, "."));
130173
}
131174

132175
// TODO(#29): Currently this function doesn't differentiate between int and
@@ -135,19 +178,19 @@ JsonLogicEvaluator::ResolveAny(std::string_view flag_key, T default_value,
135178
T value;
136179
try {
137180
if constexpr (std::is_same_v<T, openfeature::Value>) {
138-
value = JsonToValue(variants.at(variant));
181+
value = JsonToValue(variants.at(variant_name));
139182
} else {
140-
value = variants.at(variant).get<T>();
183+
value = variants.at(variant_name).get<T>();
141184
}
142185
} catch (const nlohmann::json::exception& err) {
143186
return std::make_unique<openfeature::ResolutionDetails<T>>(
144-
std::move(default_value), openfeature::Reason::kError, variant,
187+
std::move(default_value), openfeature::Reason::kError, variant_name,
145188
openfeature::FlagMetadata(), openfeature::ErrorCode::kTypeMismatch,
146189
err.what());
147190
}
148191

149192
return std::make_unique<openfeature::ResolutionDetails<T>>(
150-
std::move(value), reason, variant, openfeature::FlagMetadata(),
193+
std::move(value), reason, variant_name, openfeature::FlagMetadata(),
151194
std::nullopt, std::nullopt);
152195
}
153196

providers/flagd/src/evaluator.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66

77
#include <memory>
88
#include <nlohmann/json.hpp>
9-
#include <optional>
109
#include <string_view>
1110

11+
#include "flagd/evaluator/json_logic/json_logic.h"
12+
1213
namespace flagd {
1314

1415
class FlagSync;
@@ -69,6 +70,7 @@ class JsonLogicEvaluator : public Evaluator {
6970
const openfeature::EvaluationContext& ctx);
7071

7172
std::shared_ptr<FlagSync> sync_;
73+
json_logic::JsonLogic json_logic_;
7274
};
7375

7476
} // namespace flagd

providers/flagd/src/evaluator/json_logic/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ cc_library(
2020
"string_ops.h",
2121
"utils.h",
2222
],
23+
include_prefix = "flagd/evaluator/json_logic",
24+
strip_include_prefix = ".",
2325
visibility = ["//visibility:public"],
2426
deps = [
2527
"@abseil-cpp//absl/container:flat_hash_map",

0 commit comments

Comments
 (0)