Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a0e4bff
Add FFI bindings for datadog-ffe
sameerank Oct 24, 2025
96c3e15
Fix missing newlines at ends of files
sameerank Oct 24, 2025
37764b0
Fix Rust formatting issues in datadog-ffe-ffi
sameerank Oct 24, 2025
c927269
Remove unused NULL_POINTER_ERROR constant from error.rs
sameerank Oct 24, 2025
dfc60e3
Fix formatting in assignment.rs by removing trailing whitespace from …
sameerank Oct 24, 2025
5dfbd46
Update LICENSE-3rdparty.yml to include datadog-ffe-ffi
sameerank Oct 24, 2025
911cf1f
Add datadog-ffe-ffi Cargo.toml to Dockerfile.build
sameerank Oct 27, 2025
47e0a5c
Set datadog-ffe-ffi version to 0.1.0 to match pre-release status
sameerank Oct 27, 2025
168718a
Update Cargo.lock
sameerank Oct 27, 2025
43ec366
Refactor build script and cbindgen configuration
sameerank Oct 27, 2025
a3c6612
Update CODEOWNERS to include datadog-ffe-ffi and team
sameerank Oct 28, 2025
10298cb
Update datadog-ffe-ffi/Cargo.toml
sameerank Oct 28, 2025
c453cb9
refactor(ffe): simplify error handling (#1285)
dd-oleksii Oct 28, 2025
1e0586a
Replace single-attribute constructor with multi-attribute API
sameerank Oct 29, 2025
2fcf32b
Remove unnecessary whitespace in evaluation_context.rs
sameerank Oct 29, 2025
7b7af45
Fix documentation formatting in evaluation_context.rs
sameerank Oct 29, 2025
2d11e90
Fix Cargo.toml
sameerank Oct 29, 2025
7a7d500
Prevent malloc error in drop functions
sameerank Oct 29, 2025
006b709
Update drop functions to simplify error handling
sameerank Oct 29, 2025
2f1c2ff
Refactor EvaluationContext API
sameerank Oct 29, 2025
2d4e32d
Merge branch 'main' into sameerank/FFL-1284-Create-datadog-ffe-ffi-crate
sameerank Oct 29, 2025
b2481f0
Update datadog-ffe-ffi/src/evaluation_context.rs
sameerank Nov 3, 2025
f49b07f
Lint fix
sameerank Nov 3, 2025
3ee6f4b
Replace Assignment with ResolutionDetails structure
sameerank Nov 4, 2025
61582eb
Replace AssignmentValue with C-compatible value representation
sameerank Nov 4, 2025
8314d12
Add error mapping for ResolutionDetails error handling
sameerank Nov 4, 2025
b3321aa
Lint fixes
sameerank Nov 4, 2025
95a04fe
Clippy fixes
sameerank Nov 4, 2025
c0fae17
Merge remote-tracking branch 'origin/ffe-pyo3-methods' into HEAD
dd-oleksii Nov 4, 2025
b360449
feat: handle non-string attributes
dd-oleksii Nov 4, 2025
7afa96b
feat(ffe): adding accessors for ResolutionDetails
dd-oleksii Nov 4, 2025
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ data-pipeline*/ @Datadog/libdatadog-apm
datadog-tracer-flare @Datadog/libdatadog-apm
ddsketch @Datadog/libdatadog-apm @Datadog/libdatadog-telemetry
datadog-ffe @Datadog/feature-flagging-and-experimentation-sdk
datadog-ffe-ffi @Datadog/feature-flagging-and-experimentation-sdk

# Most of the bin_tests are owned by the profiling team, but some are owned by the core team
bin_tests/ @Datadog/libdatadog-profiling
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"datadog-crashtracker",
"datadog-crashtracker-ffi",
"datadog-ffe",
"datadog-ffe-ffi",
"datadog-ipc",
"datadog-ipc-macros",
"datadog-library-config",
Expand Down
2 changes: 1 addition & 1 deletion LICENSE-3rdparty.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
root_name: builder, build_common, tools, datadog-alloc, datadog-crashtracker, ddcommon, ddtelemetry, datadog-ddsketch, datadog-crashtracker-ffi, ddcommon-ffi, datadog-ffe, datadog-ipc, datadog-ipc-macros, tarpc, tarpc-plugins, tinybytes, spawn_worker, datadog-library-config, datadog-library-config-ffi, datadog-live-debugger, datadog-live-debugger-ffi, datadog-profiling, datadog-profiling-protobuf, datadog-profiling-ffi, data-pipeline-ffi, data-pipeline, datadog-trace-protobuf, datadog-trace-stats, datadog-trace-utils, datadog-trace-normalization, dogstatsd-client, datadog-log, datadog-log-ffi, ddsketch-ffi, ddtelemetry-ffi, symbolizer-ffi, datadog-profiling-replayer, datadog-remote-config, datadog-sidecar, datadog-sidecar-macros, datadog-sidecar-ffi, datadog-trace-obfuscation, datadog-tracer-flare, sidecar_mockgen, test_spawn_from_lib
root_name: builder, build_common, tools, datadog-alloc, datadog-crashtracker, ddcommon, ddtelemetry, datadog-ddsketch, datadog-crashtracker-ffi, ddcommon-ffi, datadog-ffe, datadog-ffe-ffi, datadog-ipc, datadog-ipc-macros, tarpc, tarpc-plugins, tinybytes, spawn_worker, datadog-library-config, datadog-library-config-ffi, datadog-live-debugger, datadog-live-debugger-ffi, datadog-profiling, datadog-profiling-protobuf, datadog-profiling-ffi, data-pipeline-ffi, data-pipeline, datadog-trace-protobuf, datadog-trace-stats, datadog-trace-utils, datadog-trace-normalization, dogstatsd-client, datadog-log, datadog-log-ffi, ddsketch-ffi, ddtelemetry-ffi, symbolizer-ffi, datadog-profiling-replayer, datadog-remote-config, datadog-sidecar, datadog-sidecar-macros, datadog-sidecar-ffi, datadog-trace-obfuscation, datadog-tracer-flare, sidecar_mockgen, test_spawn_from_lib
third_party_libraries:
- package_name: addr2line
package_version: 0.24.2
Expand Down
28 changes: 28 additions & 0 deletions datadog-ffe-ffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
# SPDX-License-Identifier: Apache-2.0

[package]
name = "datadog-ffe-ffi"
edition.workspace = true
version = "0.1.0"
Copy link
Author

@sameerank sameerank Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To match with

version = "0.1.0"

to indicate that datadog-ffe-ffi is not production ready

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can pin this, so that datadog-ffe and datadog-ffe-ffi versions are always aligned? Maybe we should put FFI functions into datadog-ffe directly

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a temporary issue. When FFI is ready, we can update datadog-ffe/Cargo.toml and datadog-ffe-ffi/Cargo.toml to

version.workspace = true  # Would use "22.1.0"

From

version = "22.1.0"

Also the version in the dependency should cause a build failure, which is another way of ensuring that they match

datadog-ffe = { path = "../datadog-ffe", version = "=0.1.0" }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we will not use the workspace version.

  1. It doesn't really suit us to have use the workspace version as it makes version bumps more complicated requiring cross-team alignment without any benefit
  2. libdatadog is migrating into the direction of each package having its own version

rust-version.workspace = true
license.workspace = true

[lib]
crate-type = ["lib", "staticlib", "cdylib"]
bench = false

[features]
default = ["cbindgen"]
cbindgen = ["build_common/cbindgen", "ddcommon-ffi/cbindgen"]

[build-dependencies]
build_common = { path = "../build-common" }

[dependencies]
anyhow = "1.0.93"
datadog-ffe = { path = "../datadog-ffe", version = "=0.1.0" }
ddcommon-ffi = { path = "../ddcommon-ffi", default-features = false }
function_name = "0.3.0"

[dev-dependencies]
11 changes: 11 additions & 0 deletions datadog-ffe-ffi/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0
extern crate build_common;

use build_common::generate_and_configure_header;

fn main() {
println!("cargo:rerun-if-changed=src/*");
let header_name = "datadog_ffe.h";
generate_and_configure_header(header_name);
}
41 changes: 41 additions & 0 deletions datadog-ffe-ffi/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
# SPDX-License-Identifier: Apache-2.0

language = "C"
cpp_compat = true
tab_width = 2
header = """// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0
"""
include_guard = "DDOG_FFE_H"
style = "tag"
usize_is_size_t = true
pragma_once = true

no_includes = true
sys_includes = ["stdbool.h", "stddef.h", "stdint.h"]
includes = ["common.h"]

[export]
include = ["datadog-ffe-ffi"]
prefix = "ddog_ffe_"
renaming_overrides_prefixing = true

[export.rename]
"VoidResult" = "ddog_VoidResult"
"Error" = "ddog_Error"
"Vec_u8" = "ddog_Vec_U8"

[export.mangle]
rename_types = "PascalCase"

[enum]
prefix_with_name = true
rename_variants = "ScreamingSnakeCase"

[fn]
must_use = "DDOG_CHECK_RETURN"

[parse]
parse_deps = true
include = ["ddcommon-ffi", "datadog-ffe"]
177 changes: 177 additions & 0 deletions datadog-ffe-ffi/src/assignment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

use std::ffi::{c_char, c_uchar, CStr};

use datadog_ffe::rules_based::{
now, Assignment, AssignmentValue, Configuration, EvaluationContext, EvaluationError, Str,
VariationType,
};

use crate::Handle;

/// Opaque type representing a result of evaluation.
#[allow(unused)]
pub struct ResolutionDetails(Result<Assignment, EvaluationError>);

#[repr(C)]
pub enum FlagType {
Unknown,
String,
Integer,
Float,
Boolean,
Object,
}

impl From<VariationType> for FlagType {
fn from(value: VariationType) -> Self {
match value {
VariationType::String => FlagType::String,
VariationType::Integer => FlagType::Integer,
VariationType::Numeric => FlagType::Float,
VariationType::Boolean => FlagType::Boolean,
VariationType::Json => FlagType::Object,
}
}
}

#[repr(C)]
pub enum ErrorCode {
Ok,
TypeMismatch,
ParseError,
FlagNotFound,
TargetingKeyMissing,
InvalidContext,
ProviderNotReady,
General,
}

#[repr(C)]
pub enum Reason {
Static,
Default,
TargetingMatch,
Split,
Disabled,
Error,
}

/// Evaluates a feature flag.
///
/// # Ownership
///
/// The caller must call `ddog_ffe_assignment_drop` on the returned value to free resources.
///
/// # Safety
/// - `config` must be a valid `Configuration` handle
/// - `flag_key` must be a valid C string
/// - `context` must be a valid `EvaluationContext` handle
#[no_mangle]
pub unsafe extern "C" fn ddog_ffe_get_assignment(
config: Handle<Configuration>,
flag_key: *const c_char,
_expected_type: FlagType,
context: Handle<EvaluationContext>,
) -> Handle<ResolutionDetails> {
if flag_key.is_null() {
return Handle::from(ResolutionDetails(Err(EvaluationError::Internal(
Str::from_static_str("ddog_ffe_get_assignment: flag_key must not be NULL"),
))));
}

let config = unsafe { config.as_ref() };
let context = unsafe { context.as_ref() };

let Ok(flag_key) = unsafe {
// SAFETY: we checked that flag_key is not NULL
CStr::from_ptr(flag_key)
}
.to_str() else {
return Handle::from(ResolutionDetails(Err(EvaluationError::Internal(
Str::from_static_str("ddog_ffe_get_assignment: flag_key is not a valid UTF-8 string"),
))));
};

let assignment_result = config.eval_flag(flag_key, context, None, now());

Handle::from(ResolutionDetails(assignment_result))
}

#[repr(C)]
pub enum VariantValue {
/// Evaluation did not produce any value.
None,
String(*const c_uchar),
Integer(i64),
Float(f64),
Boolean(bool),
Object(*const c_char),
}

/// Get value produced by evaluation.
///
/// # Ownership
///
/// The returned `VariantValue` borrows from `assignment`. It must not be used after `assignment` is
/// freed.
#[no_mangle]
pub unsafe extern "C" fn ddog_ffe_assignment_get_value(
assignment: Handle<ResolutionDetails>,
) -> VariantValue {
match unsafe { assignment.as_ref() } {
ResolutionDetails(Ok(assignment)) => match &assignment.value {
AssignmentValue::String(s) => VariantValue::String(s.as_ptr()),
AssignmentValue::Integer(v) => VariantValue::Integer(*v),
AssignmentValue::Float(v) => VariantValue::Float(*v),
AssignmentValue::Boolean(v) => VariantValue::Boolean(*v),
AssignmentValue::Json(_value) => todo!("make AssignmentValue hold onto raw json value"),
},
_ => VariantValue::None,
}
}

/// Get variant key produced by evaluation. Returns `NULL` if evaluation did not produce any value.
///
/// # Ownership
///
/// The returned string borrows from `assignment`. It must not be used after `assignment` is
/// freed.
#[no_mangle]
pub unsafe extern "C" fn ddog_ffe_assignment_get_variant(
assignment: Handle<ResolutionDetails>,
) -> *const c_uchar {
match unsafe { assignment.as_ref() } {
ResolutionDetails(Ok(assignment)) => assignment.variation_key.as_ptr(),
_ => std::ptr::null(),
}
}

/// Get allocation key produced by evaluation. Returns `NULL` if evaluation did not produce any
/// value.
///
/// # Ownership
///
/// The returned string borrows from `assignment`. It must not be used after `assignment` is
/// freed.
#[no_mangle]
pub unsafe extern "C" fn ddog_ffe_assignment_get_allocation_key(
assignment: Handle<ResolutionDetails>,
) -> *const c_uchar {
match unsafe { assignment.as_ref() } {
ResolutionDetails(Ok(assignment)) => assignment.allocation_key.as_ptr(),
_ => std::ptr::null(),
}
}

// TODO: add accessors for various data inside ResolutionDetails.

/// Frees an Assignment handle.
///
/// # Safety
/// - `assignment` must be a valid Assignment handle
#[no_mangle]
pub unsafe extern "C" fn ddog_ffe_assignment_drop(assignment: *mut Handle<ResolutionDetails>) {
unsafe { Handle::free(assignment) }
}
49 changes: 49 additions & 0 deletions datadog-ffe-ffi/src/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

use std::ffi::{c_char, CStr};

use anyhow::ensure;
use function_name::named;

use datadog_ffe::rules_based::{Configuration, UniversalFlagConfig};
use ddcommon_ffi::{wrap_with_ffi_result, Result};

use crate::Handle;

/// Creates a new Configuration from JSON bytes.
///
/// # Ownership
///
/// The caller must call `ddog_ffe_configuration_drop` to release resources allocated for
/// configuration.
///
/// # Safety
///
/// - `json_str` must be a valid C string.
#[no_mangle]
#[named]
pub unsafe extern "C" fn ddog_ffe_configuration_new(
json_str: *const c_char,
) -> Result<Handle<Configuration>> {
wrap_with_ffi_result!({
ensure!(!json_str.is_null(), "json_str must not be NULL");

let json_bytes = unsafe { CStr::from_ptr(json_str) }.to_bytes().to_vec();

let configuration =
Configuration::from_server_response(UniversalFlagConfig::from_json(json_bytes)?);

Ok(Handle::from(configuration))
})
}

/// Frees a Configuration.
///
/// # Safety
///
/// `config` must be a valid Configuration handle created by `ddog_ffe_configuration_new`.
#[no_mangle]
pub unsafe extern "C" fn ddog_ffe_configuration_drop(config: *mut Handle<Configuration>) {
unsafe { Handle::free(config) };
}
Loading
Loading