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
4978JsonLogicEvaluator::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
0 commit comments