Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -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
# 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
48 changes: 48 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -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")
Expand Down Expand Up @@ -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",
Expand All @@ -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"],
)
15 changes: 7 additions & 8 deletions providers/flagd/src/evaluator/BUILD
Original file line number Diff line number Diff line change
@@ -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",
],
Expand All @@ -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",
Expand Down
48 changes: 48 additions & 0 deletions providers/flagd/src/evaluator/datalogic_engine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "datalogic_engine.h"

#include <memory>
#include <string>

#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<nlohmann::json> 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();
Comment thread
shankar-gpio marked this conversation as resolved.

std::unique_ptr<char, decltype(&datalogic_string_free)> result(
Comment thread
shankar-gpio marked this conversation as resolved.
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
44 changes: 44 additions & 0 deletions providers/flagd/src/evaluator/datalogic_engine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#ifndef PROVIDERS_FLAGD_SRC_EVALUATOR_DATALOGIC_ENGINE_H_
#define PROVIDERS_FLAGD_SRC_EVALUATOR_DATALOGIC_ENGINE_H_

#include <datalogic.h>

#include <nlohmann/json.hpp>

#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<nlohmann::json> Apply(const nlohmann::json& rule,
const nlohmann::json& data) const;

private:
datalogic_engine* engine_;
};

} // namespace flagd

#endif // PROVIDERS_FLAGD_SRC_EVALUATOR_DATALOGIC_ENGINE_H_
10 changes: 2 additions & 8 deletions providers/flagd/src/evaluator/evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -108,12 +107,7 @@ nlohmann::json ContextToJson(const openfeature::EvaluationContext& ctx) {
} // namespace

JsonLogicEvaluator::JsonLogicEvaluator(std::shared_ptr<FlagSync> 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 <typename T>
std::unique_ptr<openfeature::ResolutionDetails<T>>
Expand Down Expand Up @@ -171,7 +165,7 @@ JsonLogicEvaluator::ResolveAny(std::string_view flag_key, T default_value,
};

absl::StatusOr<nlohmann::json> 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<std::string>();
Expand Down
4 changes: 2 additions & 2 deletions providers/flagd/src/evaluator/evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include <nlohmann/json.hpp>
#include <string_view>

#include "flagd/evaluator/json_logic/json_logic.h"
#include "flagd/evaluator/datalogic_engine.h"
#include "flagd/sync/sync.h"

namespace flagd {
Expand Down Expand Up @@ -69,7 +69,7 @@ class JsonLogicEvaluator : public Evaluator {
const openfeature::EvaluationContext& ctx);

std::shared_ptr<FlagSync> sync_;
json_logic::JsonLogic json_logic_;
DatalogicEngine engine_;
};

} // namespace flagd
Expand Down
Loading